aircraft-war(一)

aircraft-war(一)

Game Scene 布局

首先制作场景中的部件,早cocos creator中创建,然后放到适当的位置:


image.png

接下来将组件绑定到脚本上,先创建一个main脚本作为Game场景的,首先要考虑的是界面中的固定布局元素,比如:分数,暂停按钮,炸弹等。所以脚本中先构造这些布局元素:

properties: {
      pause: cc.Button,
      scoreDisplay: cc.Label,
      bombAmount: cc.Label,
      bombDisplay: cc.Node
    },
image.png

接下来分别给这些组件添加widge布局组件进行布局处理。举一个例子,其余的也差不多类似:

image.png

接下来处理暂停按钮,要让暂停按钮按下后替换成开始的按钮,实现的思路很多,例如切换两个button组件,这里使用替换图片的方式。首先要在main脚本中添加按钮图片组:

let pause = false;

cc.Class({
    extends: cc.Component,

    properties: {
        pause: cc.Button,
        scoreDisplay: cc.Label,
        bombAmount: cc.Label,
        bombDisplay: cc.Node,
        pauseSprite: {
          default: [],
          type: cc.SpriteFrame,
          tooltip:'暂停按钮图片组',
        },
    },

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

    },

    handlePause: function () {
        if (pause) {
            this.pause.normalSprite = this.pauseSprite[0];
            this.pause.pressedSprite = this.pauseSprite[1];
            this.pause.hoverSprite = this.pauseSprite[1];
            return pause = !pause
        }
        this.pause.normalSprite = this.pauseSprite[2];
        this.pause.pressedSprite = this.pauseSprite[3];
        this.pause.hoverSprite = this.pauseSprite[3];
        return pause = !pause;
    }


    // called every frame, uncomment this function to activate update callback
    // update: function (dt) {

    // },
});

这里需要注意
handlePause中使用了ES6的箭头函数语法,但是this找不到上下文,有可能是兼容上还有些问题,保持function写法即可。

image.png

这时可以启动游戏试试看是否如预期的结果那样。

Hero移动

接下来来制作Hero。首先想一想可能面临的问题都有哪些?
首先要处理玩家拖动Hero移动,接下来Hero是可以发射子弹的,当然,Hero被敌机撞击是会爆炸的,爆炸涉及的是碰撞检测后播放爆炸动画。
那么先从Hero移动开始做起,先创建Hero精灵。


image.png

创建好Hero精灵后,需要将hero图片添加到here节点上的Sprite属性中的Sprite Frame中。然后还要添加一个动画组件,并且在左下角的资源区创建Animation资源。然后将其添加到hero节点上的Animation组件中。
编辑动画如下所示:

image.png

接下来处理Hero移动,游戏中按住Hero来进行拖动,所以需要监听“触摸事件”类型。CCC系统事件类型创建hero脚本来处理Hero相关的事物。

cc.Class({
    extends: cc.Component,
    properties: {

    },
    // use this for initialization
    onLoad: function () {
        // 监听拖动事件
        this.node.on('touchmove', this.onHandleHeroMove, this);
    },
    
    onHandleHeroMove: function (event) {
        // touchmove事件中 event.getLocation() 获取当前已左下角为锚点的触点位置(world point)
        let position = event.getLocation();
        // 实际hero是background的子元素,所以坐标应该是随自己的父元素进行的,所以要将“world point”转化为“node point”
        let location = this.node.parent.convertToNodeSpaceAR(position);
        this.node.setPosition(location);
    }
});

接下来将脚本绑定到Hero精灵上就可以启动看看Hero已经可以被拖动了。

Hero发射子弹

先想一下发射子弹这个动作,需要怎么去实现?不知道大家有没有注意过大街上的LED广告牌,横向移动的字会让你觉得“字”是在移动的。而原理和帧动画差不多,就是每个亮起的显示单元不停的在变化,从而造成“移动的错觉”。

子弹是否可以像这样的实现思路去做呢?把子弹依次排开,铺满整个屏幕,Hero有一个出发点去触发亮起的子弹,然后依次亮起该列向上所有的子弹。我觉得是可以的,说实话第一次看到这个游戏,第一时间想到的就是LED显示牌。

那么还是换一种更“cocos”的方式来做,和刚刚实现Hero移动的方式一样。子弹是自动发射,所以要处理的是获取到当前Hero的位置,然后从当前位置,不断累加“positionY”的值来实现向上移动。

无限子弹(基础版)

首先要明确一下游戏规则,游戏中分为两种类型的子弹,普通单道子弹和道具双道子弹,现在先来制作普通单道子弹。
有点需要注意,ccc中不断重复创建的组件做成Prefab这个是很有必要的,虽然不用Prefab也是可以达到目的。

首先把图片资源从资源管理器中拖到层级管理器中,在属性检查器中调整好大小等参数后,将其拖拽到资源管理器相对应的目录下即可,然后删除层级管理器中的原资源即可。(图中属性是随意拖拽显示的,具体请自己尝试。)


image.png

接下来开始编写脚本,首先需要知道的参数应该有位置、速度这两个参数目前就够了,位置需要通过获取Hero的位置原点,速度可以由自己给出,所以脚本如下:

// 提供思路参考用的代码
cc.Class({
    extends: cc.Component,
    properties: {
        speed: cc.Integer,
        bullet: cc.Prefab
    },
    // use this for initialization
    onLoad: function () {
        // cc.instantiate() 克隆指定的任意类型的对象,或者从 Prefab 实例化出新节点。
        this.newNode = cc.instantiate(this.bullet);
        this.node.addChild(this.newNode);
        this.newNode.setPosition({x: 0, y: 0});
    },
    // called every frame, uncomment this function to activate update callback
    update: function (dt) {
        this.newNode.y += dt * this.speed;
    },
});

起初我的想法是,将上述脚本绑在Hero上,这样{x: 0, y: 0}就是子弹发出的起始点,但是实际运行中,出现的问题是,Hero是需要移动的,子弹Prefab也就作为了Hero的子元素,会随着Hero移动而移动,如下GIF所以:

image.png

所以子弹应该单独作为一层去处理,或者将bullet脚本绑在“background”层上,然后将Hero的位置通过传参的形式传过来即可。参考代码是单独用子弹层处理所有子弹的事件,所以也参考这种做法,用单独的层级去处理这个层级的所有相关事物。

在层级管理器中创建空节点并命名为bulletGroup,这个节点需要做的事就是处理Hero与Bullet的发射位置与发射频率。所以首先需要的就是HeroBullet-Prefab,发射频率的话,需要用到ccc中的定时器来实现固定间隔创建bullet节点并发射炮弹的功能,所以需要一个cc.Integer类型的变量。
有一个非常值得注意的性能问题:

在运行时进行节点的创建(cc.instantiate)和销毁(node.destroy)操作是非常耗费性能的,因此在比较复杂的场景中,通常只有在场景初始化逻辑(onLoad)中才会进行节点的创建,在切换场景时才会进行节点的销毁。

所以,要实现不间断发射子弹,除了定时器,还需要引入对象池(cc.NodePool)

无限子弹(进阶版)

下面开始构建脚本:
首先修改脚本bullet.js,bullet作为Prefab,只需要完成自己作为子弹的使命,那就是发射,销毁,碰撞检测。所以先来做一个只有发射的基础子弹脚本。

cc.Class({
    extends: cc.Component,

    properties: {
        speed: cc.Integer,
    },

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

    },
    // called every frame, uncomment this function to activate update callback
    update: function (dt) {
        this.node.y += dt * this.speed;
    },
});

然后将脚本添加到bullet的Prefab上,设定速度为1500。


image.png

接着开始编写bulletGroup的脚本:

cc.Class({
    extends: cc.Component,

    properties: {
        bullet: cc.Prefab,
        hero: cc.Node,
        rate: cc.Integer
    },

    onLoad: function () {
            // 创建子弹对象池
        this.genBulletPool();
            // 设置定时器,每个0.2s创建一个新的bullet
        this.schedule(function () {
            this.startShoot(this.bulletPool)
        }.bind(this), this.rate);
    },

    genBulletPool: function () {
        this.bulletPool = new cc.NodePool();
        let initCount = 100;
        for (let i = 0; i < initCount; ++i) {
            let newBullet = cc.instantiate(this.bullet); // 创建节点
            this.bulletPool.put(newBullet); // 通过 putInPool 接口放入对象池
        }
    },
    //获取子弹位置
    getBulletPosition: function(){
        let heroP = this.hero.getPosition();
        let newV2_x = heroP.x;
        let newV2_y = heroP.y;
        return cc.p(newV2_x, newV2_y);
    },
      // 发射子弹
    startShoot: function (pool) {
        let newNode = null;
        if (pool.size() > 0) {
            newNode = pool.get();
            this.node.addChild(newNode);
            let p = this.getBulletPosition();
            newNode.setPosition(p);
        }
    },
    //销毁子弹
    destroyBullet: function (bullet) {
    }

    // called every frame, uncomment this function to activate update callback
    // update: function (dt) {

    // },
});

然后将组建绑到脚本上:


image.png

bulletGroup脚本中,直接给对象池中放了一百发子弹,打完了却没有回收,这是不合理的,接下来要处理的就是回收资源。要注意的是对象池中的数量与发射子弹的关系,如果对象池中的对象用完了,而这时却没有及时补充,就会“延迟发货”。可以试着调整bulletCount来验证效果。如果对象池中的对象太多,每次最多只能用10个,之后就会被补充进来,那么剩下的就会浪费了。
如下bulletGroup.js:

cc.Class({
    extends: cc.Component,

    properties: {
        bullet: cc.Prefab,
        hero: cc.Node,
        rate: cc.Integer,
        bulletCount: {
            default: 10,
            type: cc.Integer
        }
    },

    onLoad: function () {
        this.genBulletPool();
        this.schedule(function () {
            this.startShoot(this.bulletPool)
        }.bind(this), this.rate);
           // 将对象池添加到window对象中,方便浏览器查看对象池状态
        window.pool = this.bulletPool;
    },

    genBulletPool: function () {
        this.bulletPool = new cc.NodePool();
        for (let i = 0; i < this.bulletCount; ++i) {
            let newBullet = cc.instantiate(this.bullet); // 创建节点
            this.bulletPool.put(newBullet); // 通过 putInPool 接口放入对象池
        }
    },
    //获取子弹位置
    getBulletPosition: function(){
        let heroP = this.hero.getPosition();
        let newV2_x = heroP.x;
        let newV2_y = heroP.y;
        return cc.p(newV2_x, newV2_y);
    },
    startShoot: function (pool) {
        let newNode = null;
        if (pool.size() > 0) {
            newNode = pool.get();
            this.node.addChild(newNode);
            let p = this.getBulletPosition();
            newNode.setPosition(p);
            newNode.getComponent('bullet').bulletGroup = this;
        }
    },

    // called every frame, uncomment this function to activate update callback
    // update: function (dt) {

    // },
});
image.png

子弹的销毁就是当子弹飞出屏幕之后,将其重新放回对象池中,这样一直都是对象池中的对象在被使用,而没有不断创建新的对象。目前先在bullet脚本中去处理对象销毁。

cc.Class({
    extends: cc.Component,

    properties: {
        speed: cc.Integer,
    },

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

    },
    // called every frame, uncomment this function to activate update callback
    update: function (dt) {
        this.node.y += dt * this.speed;
        if (this.node.y > this.node.parent.height){
            this.bulletGroup.bulletPool.put(this.node);
        }
    },
});

目前无限子弹类型已经差不多完成了,但是只是实现了功能,接下来做双弹道的子弹。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容