flakedzz 发表于 2016-11-16 16:08:17

cocos creator 笔记——做一个弹幕游戏

本帖最后由 flakedzz 于 2016-11-17 16:27 编辑

才开始接触ccc,前几天囫囵的看了一遍文档,又把文档自带的游戏做了一下。
这几天在考虑做循环滚动的2D背景
想法:ABC三个图片,游戏窗口显示中间,三张图片依次向下匀速滚动,当图片接触到特定坐标的时候,将图片坐标改变。
三张图片的运动类似:ABC→CAB→BCA→ABC(重复)
代码已经写出来了,把图片做成了Prefab,似乎不做成Prefab直接在场景中摆上去也可以,效果不太理想,图片与图片之间偶尔会有重叠或者空隙
猜想应该是向下运动的时候不一定会正好接触到特定坐标。
图片上挂的脚本如下
cc.Class({
    extends: cc.Component,

    properties: {
      // foo: {
      //    default: null,      // The default value will be used only when the component attaching
      //                           to a node for the first time
      //    url: cc.Texture2D,// optional, default is typeof default
      //    serializable: true, // optional, default is true
      //    visible: true,      // optional, default is true
      //    displayName: 'Foo', // optional
      //    readonly: false,    // optional, default is false
      // },
      // ...
      moveSpeed:0
    },

    // use this for initialization
    onLoad: function () {
      

    },
    // getPos:function(){
    //   this.posX=this.picScoller.node.width;
    //   this.posY=this.picScoller.node.height;
    //   var moveAction=this.scollPic();
    //   this.node.runAction(moveAction);

      
    //   //this.node.destroy();
    // },
    // scollPic:function(){
    //   var moveDown=cc.moveBy(0.5,cc.p(this.posX*0,-this.posY*2));
      
    //   var moveUp=cc.moveBy(0.5,cc.p(this.posX*0,this.posY*2));
    //   return cc.repeatForever(cc.sequence(moveDown, moveUp));
    // },
   
    moveDown:function(dt){
      this.node.y -= this.moveSpeed * dt;
    },

    // called every frame, uncomment this function to activate update callback
   update: function (dt) {
      //背景向下滚动
      if(this.node.y<=-this.picScoller.posY)
            this.node.y=this.picScoller.posY;
      this.moveDown(dt);
   },
});
注释掉的就别看了,失败的尝试。

接下来想试试直接在scene里放置结点看看效果,也许会解决重叠和空隙的问题。(重叠和空隙也可能不是代码问题,说不定我手残坐标选的不对?)

------------------
解决了,问题大概也能想到,
if(this.node.y<=-this.picScoller.posY)
            this.node.y=this.picScoller.posY;之前是当坐标小于blabla的时候直接给这个结点的坐标赋值
如果这个坐标等于blabla的话,这样是没问题的
但是update并不能保证每一个像素都经过
所以很大概率是小于这个坐标而不是等于
修改之后的代码
if(this.node.y<=-960)
            this.node.y+=1280;640是默认分辨率的宽度
1280正好可以把最下面的图片提到最上面
目前分辨率是写死的,如果做自动适应的话需要在Canvas下挂一个读当前分辨率的脚本来知道应该+多少像素,懒,以后再写这个。
---------------------------------------然后……试着写一下控制角色移动,再然后试着做一下敌机,再再然后是敌机factory。(饭要一口一口吃
似乎一个STG的原型就出来了

2016年11月16日21:53:18
好的,现在把角色移动给写出来了。
现在的想法是做一个能够8方向移动的角色就好(键盘操控,暂时不想加手柄输入)
在角色结点添加一个监听器监听键盘输入(keyUp&keyDown)
当监听到keyDown的时候判断是哪个键,如果是w按下,就把角色的spriteFrame(可以理解成显示的图片)变更为正常状态,然后把runToUp赋值为真
以此类推,把wasd四个按键的按下事件都做好。
keyUp就是把四个控制方向的布尔型变量按照对应方向赋值为假(四个变量 runToUp,runToDown,runToLeft,runToRight)
然后在update函数里通过这四个变量的真假来控制角色移动的方向
代码如下:
cc.Class({
    extends: cc.Component,

    properties: {
      // foo: {
      //    default: null,      // The default value will be used only when the component attaching
      //                           to a node for the first time
      //    url: cc.Texture2D,// optional, default is typeof default
      //    serializable: true, // optional, default is true
      //    visible: true,      // optional, default is true
      //    displayName: 'Foo', // optional
      //    readonly: false,    // optional, default is false
      // },
      // ...
      idle:{
            default:null,
            type:cc.SpriteFrame
      },
      left:{
            default:null,
            type:cc.SpriteFrame
      },
      right:{
            default:null,
            type:cc.SpriteFrame
      },
      moveSpeed:0,
    },

    // use this for initialization
    onLoad: function () {
      this.sprite=this.getComponent(cc.Sprite);
      //sprite.spriteFrame.setTexture(cc.url.raw('ninganyi.jpg'));
      this.setInputControl();
    },
   
    setInputControl:function(){
      cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN,
      this.onKeyDown, this);
      cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP,
      this.onKeyUp, this);
    },
    onKeyDown:function(event){
      //self=this;
      //switch(event.keyCode){
      //             case cc.KEY.a:
      //               this.sprite.spriteFrame=this.left;
      //               this.enabled=false;
      //               this.enabled=true;
      //               this.runToLeft=true;
      //               break;
      //             case cc.KEY.d:
      //               this.sprite.spriteFrame=this.right;
      //               this.enabled=false;
      //               this.enabled=true;
      //               this.runToRight=true;
      //               break;
      //             case cc.KEY.w:
      //               this.sprite.spriteFrame=this.idle;
      //               this.enabled=false;
      //               this.enabled=true;
      //               this.runToUp=true;
      //               break;
      //             case cc.KEY.s:
      //               this.sprite.spriteFrame=this.idle;
      //               this.enabled=false;
      //               this.enabled=true;
      //               this.runToDown=true;
      //               break;
      //         }

      if(event.keyCode==cc.KEY.a){
             this.sprite.spriteFrame=this.left;
             this.enabled=false;
             this.enabled=true;
             this.runToLeft=true;
      }
      if(event.keyCode==cc.KEY.d){
             this.sprite.spriteFrame=this.right;
             this.enabled=false;
             this.enabled=true;
             this.runToRight=true;
      }
      if(event.keyCode==cc.KEY.w){
             this.sprite.spriteFrame=this.idle;
             this.enabled=false;
             this.enabled=true;
             this.runToUp=true;
      }
      if(event.keyCode==cc.KEY.s){
             this.sprite.spriteFrame=this.left;
             this.enabled=false;
             this.enabled=true;
             this.runToDown=true;
      }
    },
    onKeyUp:function(event){
         
      //switch(event.keyCode){
      //             case cc.KEY.a:
      //               this.sprite.spriteFrame=this.idle;
      //               this.enabled=false;
      //               this.enabled=true;
      //               this.runToLeft=false;
      //               break;
      //             case cc.KEY.d:
      //               this.sprite.spriteFrame=this.idle;
      //               this.enabled=false;
      //               this.enabled=true;
      //               this.runToRight=false;
      //             case cc.KEY.w:
      //               this.runToUp=false;
      //             case cc.KEY.s:
      //               this.runToDown=false;
      //         }

      if(event.keyCode==cc.KEY.a){
            this.sprite.spriteFrame=this.idle;
            this.enabled=false;
            this.enabled=true;
            this.runToLeft=false;
      }
      if(event.keyCode==cc.KEY.d){
            this.sprite.spriteFrame=this.idle;
            this.enabled=false;
            this.enabled=true;
            this.runToRight=false;
      }
      if(event.keyCode==cc.KEY.w){
            this.runToUp=false;
      }
      if(event.keyCode==cc.KEY.s){
            this.runToDown=false;
      }
    },
    // setInputControl:function(){
    //   var self=this;
    //   cc.eventManager.addListener({
    //         event:cc.EventListener.KEYBOARD,
    //         onKeyPressed:function(keyCode,event){
    //             switch(keyCode){
    //               case cc.KEY.a:
    //                     self.sprite.spriteFrame=self.left;
    //                     self.enabled=false;
    //                     self.enabled=true;
    //                     self.runToLeft=true;
    //                     break;
    //               case cc.KEY.d:
    //                     self.sprite.spriteFrame=self.right;
    //                     self.enabled=false;
    //                     self.enabled=true;
    //                     self.runToRight=true;
    //                     break;
    //               case cc.KEY.w:
    //                     self.sprite.spriteFrame=self.idle;
    //                     self.enabled=false;
    //                     self.enabled=true;
    //                     self.runToUp=true;
    //                     break;
    //               case cc.KEY.s:
    //                     self.sprite.spriteFrame=self.idle;
    //                     self.enabled=false;
    //                     self.enabled=true;
    //                     self.runToDown=true;
    //                     break;
    //             }
    //         },
    //         onKeyReleased:function(keyCode,event){
    //             switch(keyCode){
    //               case cc.KEY.a:
    //                     self.runToLeft=false;
    //                     break;
    //               case cc.KEY.d:
    //                     self.runToRight=false;
    //               case cc.KEY.w:
    //                     self.runToUp=false;
    //               case cc.KEY.s:
    //                     self.runToDown=false;
    //             }
    //         }
    //   },self.node);
    // },
    // called every frame, uncomment this function to activate update callback
   update: function (dt) {
      //this.SpriteFrame=this.left;
      if(this.runToLeft){
            this.node.x -=this.moveSpeed*dt;
      }
      if(this.runToRight){
            this.node.x+=this.moveSpeed*dt;
      }
      if(this.runToUp){
            this.node.y+=this.moveSpeed*dt;
      }
      if(this.runToDown){
            this.node.y-=this.moveSpeed*dt;
      }
   },
});
同样的,注释掉的不用看,是另一种尝试,不过效果不太好。(才发现,代码缩进怎么没了?)
这里着重讲一下下面这段代码:
this.sprite=this.getComponent(cc.Sprite);
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;一行一行解读:
获取当前结点的Sprite类型的组件,赋值给全局变量sprite
将idle赋值给sprite的spriteFrame属性(idle是spriteFrame类型,可以看上面那段代码的properties区域)
禁用当前结点
启用当前结点
我之前在替换精灵的图片的时候只写了前两行,没有效果。
加上后面两行之后效果正常,我也不太懂这是为什么,如果有谁知道请务必给我讲解一下。猜测是更换图片之后需要刷新一下?而后两行代码起到了刷新的作用?
顺便附上用到的角色图片
是从rpgmaker里截出来的



角色的移动大概就这样了。之后试着做一下敌机和自机的攻击。
---------------------------------
2016年11月17日14:59:55
感觉弹幕游戏中,向左向右移动的时候角色脸也跟着变好像有点奇怪,先不改了,等代码写完再说素材的事。
顺便加了慢速移动的代码,按住shift的时候移动速度降低50%,松开恢复。
代码很简单,不贴了
从文档里找到了KEY的枚举值,把wasd换成了上下左右,更正常了。
下面是目前进度


右侧黑条预计用来做计分板,现在还没开工。
左侧的滚动图片是自己用PS做的
用起来效果还可以
------------------------------------------
2016年11月17日15:09:48
接下来准备做自机的武器,还在考虑要怎么升级。要不先做敌机?(好的)
好了,敌人画好了。

……………
至少大家知道它是敌人了,对吧------------------------------
2016年11月17日16:26:31
设置好了碰撞体还有碰撞分组,边界处理还是没做,懒癌发作,休息一会儿

------------------------------

flakedzz 发表于 2016-11-18 21:07:14

本帖最后由 flakedzz 于 2016-11-19 18:03 编辑

2016年11月18日21:07:12
今天感冒了,一行没写,罪过2016年11月19日17:59:58
休息了快两天,继续做。
先来想一下敌机自机的武器和自机武器的升级
击毁一架敌机掉落P点?
直接得分的话感觉太容易了一点,做个击毁敌机会掉落P点的小功能好了。
当敌机被击毁,会在一定范围内随机选取几个坐标生成P点对象。
和自机接触后自机获得得分。
这个放后面做。

那先做自机的弹幕好了。
把得分暴露出来,用来在没有收集P点的时候可以手动调整数值来测试弹幕的武器等级。


坡忒头 发表于 2016-11-22 19:27:36

ccc这引擎的界面和东方正作的好像啊
{:39:}
最近看了一篇基于unity2d的制作打飞机教程,挺有趣
http://space.bilibili.com/32264724/#!/video/0//1

flakedzz 发表于 2016-11-22 20:03:55

坡忒头 发表于 2016-11-22 19:27
ccc这引擎的界面和东方正作的好像啊

最近看了一篇基于unity2d的制作打飞机教程,挺有趣


之前做过unity的,但是感觉太臃肿,这次顺便学学ccc怎么用,就用这个做了

flakedzz 发表于 2016-11-26 19:54:29

2016年11月26日19:44:22
摸了几天鱼,今天着手写了自机弹幕的发射。
大概思路如下:
创建一个精灵节点,调整好材质之后,挂上去一个让它自动向前移动和在几秒后销毁的脚本。然后把这个精灵节点设置成Prefab
然后在触发按键事件之后按照一定速率重复生成该Prefab。【注意:这种方式有重大缺陷,重复的创建销毁对象,对性能的影响非常大。这里只做演示,完成后会改成用数组存储弹幕缓存的方式来进行优化】要注意的是,生成新的对象Prefab之后,将其加入到角色的子节点。这样设置坐标的时候就可以以角色本身的坐标系来进行。

这样就完成了子弹的发射。

如果要完成子弹和敌机的碰撞功能,需要在子弹节点和敌机上添加碰撞体。然后将碰撞进行分组。我设置的是playerBullet碰撞enemy。
这样当带有这两个标签的,带有碰撞体的节点相互碰撞,就会触发 onCollisionEnter()onCollisionStay()onCollisionExit()三个方法。
需要注意的是,碰撞管理默认情况下是关闭的,所以需要用代码手动启用。

子弹脚本如下:
cc.Class({
    extends: cc.Component,

    properties: {
      // foo: {
      //    default: null,      // The default value will be used only when the component attaching
      //                           to a node for the first time
      //    url: cc.Texture2D,// optional, default is typeof default
      //    serializable: true, // optional, default is true
      //    visible: true,      // optional, default is true
      //    displayName: 'Foo', // optional
      //    readonly: false,    // optional, default is false
      // },
      // ...
      shootSpeed:0,
      
    },
    onCollisionEnter: function (other, self) {
      console.log('on collision enter');
      self.node.destroy();
    },
    onCollisionStay: function (other, self) {
      console.log('on collision stay');
    },
    onCollisionExit: function (other, self) {
      console.log('on collision exit');
      
      other.node.destroy();
      
    },
    // use this for initialization
    onLoad: function () {
       this.counter=0;
       var manager = cc.director.getCollisionManager();
       manager.enabled = true;
    },
    move:function(dt){
      this.node.y+=this.shootSpeed*dt;
    },
   
    // called every frame, uncomment this function to activate update callback
    update: function (dt) {
      this.counter++;
            this.move(dt);
      
      //if(this.node.y>this.game.height*0.5||this.node.y<0-game.height*0.5||this.node.x<0-this.game.width*0.5||this.node.x>this.game.width*0.174)
      if(this.counter>=100)   
            this.node.destroy();
    },
});


添加了发射功能的角色脚本如下:
cc.Class({
    extends: cc.Component,

    properties: {
      // foo: {
      //    default: null,      // The default value will be used only when the component attaching
      //                           to a node for the first time
      //    url: cc.Texture2D,// optional, default is typeof default
      //    serializable: true, // optional, default is true
      //    visible: true,      // optional, default is true
      //    displayName: 'Foo', // optional
      //    readonly: false,    // optional, default is false
      // },
      // ...
      idle:{
            default:null,
            type:cc.SpriteFrame
      },
      left:{
            default:null,
            type:cc.SpriteFrame
      },
      right:{
            default:null,
            type:cc.SpriteFrame
      },
      normalBullet:{
            default:null,
            type:cc.Prefab
      },
      game:{
            default:null,
            type:cc.Node
      },
      
      moveSpeed:0,
      fireSpeed:0,//普通攻击速率
      specialBomb:0,//特殊攻击可用次数
      weaponLevel:0,//武器等级

      weaponPosX:0,
      weaponPosY:0,
    },

    // use this for initialization
    onLoad: function () {
      this.counter=0;
      this.sprite=this.getComponent(cc.Sprite);
      this.setInputControl();
      var manager = cc.director.getCollisionManager();
      manager.enabled = true;
    },
   
    cannonControl:function(){//直接在自机前方生成子弹

    },
    setInputControl:function(){
      cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN,
      this.onKeyDown, this);
      cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP,
      this.onKeyUp, this);
    },
    onKeyDown:function(event){
      if(event.keyCode==cc.KEY.z){
            this.cannonFire=true;
      }
      
      if(event.keyCode==cc.KEY.left){
             this.sprite.spriteFrame=this.left;
             this.enabled=false;
             this.enabled=true;
             this.runToLeft=true;
      }
      if(event.keyCode==cc.KEY.right){
             this.sprite.spriteFrame=this.right;
             this.enabled=false;
             this.enabled=true;
             this.runToRight=true;
      }
      if(event.keyCode==cc.KEY.up){
             this.sprite.spriteFrame=this.idle;
             this.enabled=false;
             this.enabled=true;
             this.runToUp=true;
      }
      if(event.keyCode==cc.KEY.down){
             this.sprite.spriteFrame=this.idle;
             this.enabled=false;
             this.enabled=true;
             this.runToDown=true;
      }
      if(event.keyCode==cc.KEY.shift){
            this.moveSpeed=this.moveSpeed*0.5;
      }
      
      
    },
    onKeyUp:function(event){
         


      if(event.keyCode==cc.KEY.left){
            this.sprite.spriteFrame=this.idle;
            this.enabled=false;
            this.enabled=true;
            this.runToLeft=false;
      }
      if(event.keyCode==cc.KEY.right){
            this.sprite.spriteFrame=this.idle;
            this.enabled=false;
            this.enabled=true;
            this.runToRight=false;
      }
      if(event.keyCode==cc.KEY.up){
            this.runToUp=false;
      }
      if(event.keyCode==cc.KEY.down){
            this.runToDown=false;
      }
      if(event.keyCode==cc.KEY.shift){
            this.moveSpeed=this.moveSpeed*2;
      }
      if(event.keyCode==cc.KEY.z){
            this.cannonFire=false;
      }
    },
   
    normalAttackLv1:function(dt){
      
      if(this.counter>=this.fireSpeed){
            var bullet=cc.instantiate(this.normalBullet);
            this.node.addChild(bullet);
            bullet.setPosition(0,0);
            bullet.getComponent("NormalBulletScript").game=this.game;
         
            
            this.counter=0;
      }
      else{
         this.counter++;
      }
      //cc.log(this.node.x+"x"+this.node.y);
    },
    normalAttackLv2:function(dt){

    },
    normalAttackLv3:function(dt){

    },
   boardCheck:function(){
      
      var screenWidth=this.game.width;
      var screenHeight=this.game.height;
      if(this.node.x<0-0.5*screenWidth)
            this.node.x=0-0.5*screenWidth;
      if(this.node.x>0.174*screenWidth)
            this.node.x=0.174*screenWidth;
      if(this.node.y<0-0.5*screenHeight)
            this.node.y=0-0.5*screenHeight;
      if(this.node.y>0.5*screenHeight)
            this.node.y=0.5*screenHeight;
      
    },
    moveControl:function(dt){
      if(this.cannonFire){
            this.normalAttackLv1(dt);
      }
      if(this.runToLeft){
            this.node.x -=this.moveSpeed*dt;
      }
      if(this.runToRight){
            this.node.x+=this.moveSpeed*dt;
      }
      if(this.runToUp){
            this.node.y+=this.moveSpeed*dt;
      }
      if(this.runToDown){
            this.node.y-=this.moveSpeed*dt;
      }
      
    },
    // called every frame, uncomment this function to activate update callback
   update: function (dt) {
         if(this.cannonFire){
             this.normalAttackLv1(dt);
         }
      this.moveControl(dt);
      this.boardCheck();
      
            
   },
});
我的命名不规范,还请不要学习我。

如果仔细看代码的话会发现我创建了三个类似的攻击函数,并只实现了一个。
剩下的两个是为了给拾取道具增强火力做出的预留。【并非最终版本,设计随时可能会变更】

接下来要实现至少一种的敌人和敌人的弹幕设计。
敌人和敌人的弹幕设计都做好之后,会进行第一阶段的优化。
将弹幕以及敌人进行缓存处理。
这次更新就到这里。

tinyAdapter 发表于 2016-11-28 15:21:34

一般这种js的大项目我都是建议用TypeScript,有静态类型检查和完整的面向对象,写完以后再转成js(

flakedzz 发表于 2016-11-28 15:49:41

tinyAdapter 发表于 2016-11-28 15:21
一般这种js的大项目我都是建议用TypeScript,有静态类型检查和完整的面向对象,写完以后再转成js(

也不算大型了,js没有类是挺困扰,不过ccc用js u3d用C#,已经习惯了

flakedzz 发表于 2016-12-12 22:22:31

2016年12月12日22:22:03
A了A了,我要摸鱼去了。更日志哪有摸鱼爽啊。

卵石之美 发表于 2017-4-13 16:00:39

楼楼好厉害……Orz(仰望

wsc010317 发表于 2017-4-27 23:31:32

谢谢dalao
萌新正在努力肝。。。。。。
页: [1] 2
查看完整版本: cocos creator 笔记——做一个弹幕游戏