上一章把飞机添加到屏幕上,但是飞机要发射子弹对吧?那么这一章我们就来实现一下发射子弹,并实现一个简单的子弹对象池
先来捋一捋思路
1.创建一个子弹对象
2.然后添加一个bitmap,显示子弹贴图
3.判断子弹类型(我们这里是一个子弹是敌人和主角都在使用,根据不同的状态类型,显示不同的图和往不同的方向飞行)
4.子弹回收(回收子弹的意思就是把子弹状态还原,并且从界面中移除)
5.子弹对象池的设计(综合上面四点,设计一个简单的对象池)
6.主角发射子弹
首先,我们先新建一个IdentityType.ts的文件,这是一个标识身份的枚举。用来区别是主角发射的子弹还是敌人发射的子弹
/** * 子弹类型 */ enum IdentityType { /** * 敌人 */ ENEMY, /** * 主角 */ HERO }
然后创建一个BulletObject.ts,顾名思义就是子弹对象的意思。子弹的一切操作,都在这个类里面完成
class BulletObject extends egret.DisplayObjectContainer { _bullet: egret.Bitmap; public btype: IdentityType; /** * 是否使用 */ public IsUse: boolean = false; _main: Main; _speed = 5; public constructor(main: Main) { super(); this.width = 9; this.height = 21; this._main = main; this._bullet = new egret.Bitmap(); this.addChild(this._bullet) } frame() { // console.log("Bullet Frame") if (this.IsUse) { if (this.btype == IdentityType.ENEMY) { this.y += this._speed; } if (this.btype == IdentityType.HERO) { this.y -= this._speed; if (this.y <= 0) { //从父节点中移除 if (this.parent) { this.parent.removeChild(this); this.Recycle(); } } } } } /** * 使用 */ public Use(type: IdentityType, x: number, y: number) { this.IsUse = true; this.x = x; this.y = y; this.btype = type; if (type == IdentityType.ENEMY) { this._bullet.texture = RES.getRes("bullet1_png") } else { this._bullet.texture = RES.getRes("bullet2_png") } this._main.addChildAt(this, 10) this.addEventListener(egret.Event.ENTER_FRAME, this.frame, this) } /** * 回收 */ public Recycle() { console.log("回收子弹:" + this.btype) this.IsUse = false; this.removeEventListener(egret.Event.ENTER_FRAME, this.frame, this) } }
在构造方法中,我们初始化了子弹对象的宽高和图片显示对象,但是这里不给bitmap的texture赋值,需要再使用的时候赋值
Use和Recycle方法都是在外部调用。
Use是使用这个对象,做的操作是根据外部传递进来的值来确定自己的主角的子弹还是敌人的子弹,并给texture赋值一个对应身份状态的图片对象,并且监听Enter_frame事件,在每一帧,对应移动当前子弹的坐标
Recycle方法是回收当前子弹,把子弹状态都还原,并且移除ENTER_FRAME事件的监听
然后实现一个简单的对象池
/** * 初始化对象池 */ InitPool() { for (var i = 0; i < 50; i++) { var bullet = new BulletObject(this); this._bullet.push(bullet) } } /** * 从对象池获取一个Bullet */ public GetBullet(): BulletObject { for (var i = 0; i < this._bullet.length; i++) { if (this._bullet[i].IsUse == false) { return this._bullet[i]; } } console.log("对象池已经用光了,可能是没有回收") }
对象池这个东西按我个人的理解就是先初始化一堆东西放到一个篮子里,然后需要的时候,就去篮子里拿,用完再放回去,实现资源的重复利用,避免频繁的对象new的操作。
子弹和对象池都搞定了,然后来实现主角发射子弹
玩过微信打飞机游戏的都知道,主角飞机是不停的发射子弹。飞机移动位置,子弹也对应从飞机的头部发射子弹
我的思路就是,在飞机对象里面放一个Timer定时器,500毫秒发射一个子弹。然后自定义一个子弹发射的事件,飞机发射子弹的时候,只触发发射事件,并不实际发射子弹,我们在飞机的外部容器,也就是我们的Main这个容器中,监听对象,把子弹对象添加到Main中
先新建一个文件OpenFireEvent.ts
class OpenFireEvent extends egret.Event { public static EventString = "开火"; /** * 事件类型默认是Hero */ public Btype: IdentityType = IdentityType.HERO; public constructor(type: string, bubbles: boolean = false, cancelable: boolean = false) { super(type, bubbles, cancelable); } }
然后再HeroObject.ts的ADDED_TO_STAGE事件里面添加一个timer
this._heroOpenFireEvent = new OpenFireEvent(OpenFireEvent.EventString); this._heroOpenFireEvent.Btype = IdentityType.HERO; this.addEventListener(egret.Event.ENTER_FRAME, this.frame, this); this._timer = new egret.Timer(GameConfig.HeroOpenFireTime); this._timer.addEventListener(egret.TimerEvent.TIMER, this.timerFunc, this); this._timer.addEventListener(egret.TimerEvent.COMPLETE, this.timerComplete, this); this._timer.start(); ..... public timerFunc(e: egret.TimerEvent) { // console.log("定时开火--------------------") this.dispatchEvent(this._heroOpenFireEvent); }
在Timer的每次调用中,我们触发开火事件。
这里触发了开火的事件,需要让这个事件生效,我们就需要在其他的地方监听这个事件
所以在Main的添加主角对象的地方
this._Hero.addEventListener(OpenFireEvent.EventString, (e: OpenFireEvent) => { var b = this.GetBullet(); if (b == undefined) { console.log("对象池中没有对象") return; } var x = this._Hero.x + this._Hero.width / 2 - 5; var y = this._Hero.y - 18; b.Use(IdentityType.HERO, x, y) }, this)
监听到事件之后,从对象池中取出一个子弹对象,然后计算这个子弹应该出现的坐标x=(英雄的X坐标+英雄的宽度/2),y=(英雄的Y坐标-子弹的高度) ,不要问我为啥我还有一个-5在后面,估计是我的图片大小不标准,然后我对坐标进行微调了一下。。这样看起来正常一点。。。
需要源码的,可以加QQ群来问我要哈,等这一系列学习笔记写完了,我再上传到github去