【Unity】MiniGame编辑器小游戏(六)飞机大战【AirplaneWars】
更新日期:2025年6月25日。
项目源码:获取项目源码
索引
- 飞机大战【AirplaneWars】
- 一、游戏最终效果
- 二、玩法简介
- 三、正式开始
- 1.定义游戏窗口类
- 2.规划游戏窗口、视口区域
- 3.飞机 Airplane
- ①.定义飞机类
- ②.飞机类绘制方法 OnGUI
- ③.飞机的移动
- ④.飞机的子弹发射口
- ⑤.飞机发射子弹
- 4.子弹 Bullet
- ①.定义子弹类
- ②.子弹类绘制方法 OnGUI
- ③.子弹的移动
- ④.子弹的生成
- ⑤.子弹的销毁
- 5.增益 Buff
- ①.定义增益类
- ②.增益类型
- ③.增益类绘制方法 OnGUI
- ④.增益的移动
- ⑤.增益的生成
- ⑥.增益的销毁
- ⑦.飞机获得增益
- 6.敌人 Enemy
- ①.定义敌人基类
- ②.敌人类型1
- ③.敌人类型2
- ③.敌人类型3
- ④.敌人类型3发射的子弹
- ③.敌人类型4
- 7.碰撞检测配置
- ①.游戏物体设置为碰撞器
- ②.游戏窗口启用碰撞检测
- ③.添加碰撞矩阵
- 8.飞机的子弹攻击敌人
- 9.飞机被敌人或敌人的子弹攻击
- 10.获得分数
- 11.绘制移动的背景板
- 12.飞机开火
- 13.生成敌人的策略
- 14.暂停游戏、退出游戏
飞机大战【AirplaneWars】
本篇的目标是开发一个飞机大战【AirplaneWars】
小游戏。
一、游戏最终效果
Unity编辑器小游戏:飞机大战
二、玩法简介
玩家使用鼠标控制飞机移动,躲避从上方飞来的敌人
或子弹
,同时按鼠标左键
或A
键,可以发射子弹消灭敌人。
游戏过程中还会出现一些武器Buff
,飞机碰撞即可拾取,拾取后可相应获得对应的增强武器效果
(比如双发弹、三发弹、加速弹等)。
飞机大战
是第一个开始使用到简易物理系统(比如碰撞检测)
的小游戏。
三、正式开始
1.定义游戏窗口类
首先,定义飞机大战的游戏窗口类MiniGame_AirplaneWars
,其继承至MiniGameWindow【小游戏窗口基类】
:
/// <summary>/// 飞机大战/// </summary>public class MiniGame_AirplaneWars : MiniGameWindow{}
2.规划游戏窗口、视口区域
通过覆写虚属性
实现规划游戏视口区域大小:
/// <summary>/// 游戏名称/// </summary>public override string Name => "飞机大战 [AirplaneWars]";/// <summary>/// 游戏窗体大小/// </summary>public override Vector2 WindowSize => new Vector2(700, 530);/// <summary>/// 游戏视口区域/// </summary>public override Rect ViewportRect => new Rect(5, 25, 500, 500);/// <summary>/// 游戏视口区域鼠标指针类型/// </summary>public override MouseCursor ViewportCursor => MouseCursor.MoveArrow;
注意:游戏窗体大小必须 > 游戏视口区域。
然后通过代码打开此游戏窗口:
[MenuItem("MiniGame/飞机大战 [AirplaneWars]", priority = 11)]private static void Open_MiniGame_AirplaneWars(){MiniGameWindow.OpenWindow<MiniGame_AirplaneWars>();}
便可以看到游戏的窗口、视口区域如下(左侧深色凹陷区域为视口
区域):
3.飞机 Airplane
不同于之前的几个游戏,游戏场景几乎都是清一色的方格板,飞机大战
开始引入MiniGameObject
对象了,其是EditorWindow
的小游戏通用对象(类似于运行时的GameObject
)。
MiniGameObject
对象会自动绘制到游戏视口
(只要是激活的,也即是activeSelf = true
),其同样存在一个transform
属性用于描述其所在位置与尺寸大小,只不过transform的类型为Rect
。
①.定义飞机类
飞机类Airplane
继承至MiniGameObject
:
/// <summary>/// 飞机/// </summary>public class Airplane : MiniGameObject{//默认构造函数public Airplane(MiniGameWindow window, Rect rect) : base(window, rect){//不允许此对象的位置超出游戏视口范围(飞机不能飞到屏幕外)isAllowOutOfViewport = false;}}
②.飞机类绘制方法 OnGUI
在OnGUI
方法中,基于飞机所在坐标transform
偏移一定值,以绘制出飞机机头、翅膀等:
OnGUI
方法用于描述如何绘制此MiniGameObject
。
/// <summary>/// 飞机/// </summary>public class Airplane : MiniGameObject{public override void OnGUI(){base.OnGUI();//机身Rect rect = new Rect();rect.Set(transform.x + 20, transform.y, 10, 40);GUI.Box(rect, "", "button");//左翼rect.Set(transform.x, transform.y + 20, 20, 10);GUI.Box(rect, "", "button");//右翼rect.Set(transform.x + 30, transform.y + 20, 20, 10);GUI.Box(rect, "", "button");//尾翼rect.Set(transform.x + 15, transform.y + 40, 20, 10);GUI.Box(rect, "", "button");}}
然后我们在游戏开始时创建一个飞机对象,飞机就能出现在场景中了:
private Airplane _airplane;/// <summary>/// 开始游戏/// </summary>private void StartGame(){//飞机_airplane = new Airplane(this, new Rect(ViewportRect.width * 0.5f - 25, ViewportRect.height - 55, 50, 50));}
虽然有点丑,但也可以理解为一种风格化。
③.飞机的移动
这里采用飞机跟随鼠标位置
进行移动的方式,同样的,需要在OnGamePlayingEvent
方法中编写事件相关逻辑:
protected override void OnGamePlayingEvent(Event e, Vector2 mousePosition){base.OnGamePlayingEvent(e, mousePosition);if (e.type == EventType.Repaint){//获取当前飞机位置Rect rect = _airplane.transform;//将飞机位置限制在视口底部 -30 至 -80 之间rect.center = new Vector2(mousePosition.x, Mathf.Clamp(mousePosition.y, ViewportRect.height - 80, ViewportRect.height - 30));//设置新的飞机位置_airplane.transform = rect;//飞机每次移动后,执行碰撞检测CollisionDetection(_airplane);}}
可以看到这里飞机每次移动后都会执行碰撞检测
,主动调用CollisionDetection
是执行碰撞检测的一种方式。
理论上,碰撞检测存在于
移动物体
与其他物体
之间,所以在某个物体移动后
才进行碰撞检测,可以有效节省性能开销。
④.飞机的子弹发射口
定义飞机的3个子弹发射口位置:
/// <summary>/// 机头子弹发射口/// </summary>public Vector2 HeadFirePort{get{return new Vector2(transform.x + 25, transform.y);}}/// <summary>/// 左翼子弹发射口/// </summary>public Vector2 LeftFirePort{get{return new Vector2(transform.x, transform.y);}}/// <summary>/// 右翼子弹发射口/// </summary>public Vector2 RightFirePort{get{return new Vector2(transform.x + 50, transform.y);}}
这三个位置如下:
默认情况下,只有机头发射口可以发射子弹,通过拾取Buff
(双发弹、三发弹)从而激活全部发射口。
⑤.飞机发射子弹
/// <summary>/// 是否拥有加速弹Buff/// </summary>public bool HasSpeedUp { get; private set; } = false;/// <summary>/// 同时发射子弹数量(1,2,3)(双发弹、三发弹)/// </summary>public int BulletNumber { get; private set; } = 1;/// <summary>/// 发射子弹/// </summary>public void Fire(){if (BulletNumber == 1){GenerateBullet(HeadFirePort);}else if (BulletNumber == 2){GenerateBullet(LeftFirePort);GenerateBullet(RightFirePort);}else if (BulletNumber == 3){GenerateBullet(HeadFirePort);GenerateBullet(LeftFirePort);GenerateBullet(RightFirePort);}}
GenerateBullet
为生成子弹的方法,这里我们先跳过,后续引入子弹类
后再填充。
4.子弹 Bullet
①.定义子弹类
子弹类Bullet
继承至MiniGameObject
:
/// <summary>/// 子弹/// </summary>public class Bullet : MiniGameObject{/// <summary>/// 能量/// </summary>public int Power { get; private set; }/// <summary>/// 飞行速度/// </summary>public float Speed { get; set; }public Bullet(MiniGameWindow window, Rect rect, int power, float speed) : base(window, rect){isAllowOutOfViewport = false;Power = power;Speed = speed;}}
②.子弹类绘制方法 OnGUI
GUI风格horizontalsliderthumb
能够直接绘制一个圆形,看起来正好像一个子弹:
public override void OnGUI(){base.OnGUI();GUI.Box(transform, "", "horizontalsliderthumb");}
③.子弹的移动
子弹一经发射后,自身便会不停向前移动(在这里即是向上),所以直接在OnUpdate
中调用MiniGameObject
的移动方法即可:
OnUpdate
为模拟帧循环回调方法,类似于MonoBehaviour
的Update
方法,只不过由于EditorWindow
的刷新频率很不固定,所以这个方法回调时机不太稳定。
/// <summary>/// 子弹/// </summary>public class Bullet : MiniGameObject{//模拟帧循环public override void OnUpdate(){base.OnUpdate();MoveVertical(-Speed, true);}}
MoveVertical
为垂直移动方法,第二个参数true
,将在移动过程中进行碰撞检测,这也是进行碰撞检测的另一种方法。
④.子弹的生成
子弹采用对象池
模式生成:
private Queue<Bullet> _bulletPool = new Queue<Bullet>();private const int _bulletPower = 100;private const float _bulletSpeed = 0.3f;/// <summary>/// 生成子弹/// </summary>/// <param name="pos">子弹生成位置</param>private void GenerateBullet(Vector2 pos){Rect rect = new Rect(0, 0, 10, 10);rect.center = pos;Bullet bullet = null;if (_bulletPool.Count > 0){bullet = _bulletPool.Dequeue();bullet.transform = rect;//飞机拥有加速弹buff,则速度加倍bullet.Speed = _bulletSpeed * (_airplane.HasSpeedUp ? 2 : 1);}else{bullet = new Bullet(this, rect, _bulletPower, _bulletSpeed * (_airplane.HasSpeedUp ? 2 : 1));}//激活子弹bullet.activeSelf = true;}
⑤.子弹的销毁
销毁子弹时会回收到对象池
:
/// <summary>/// 回收子弹/// </summary>/// <param name="bullet">子弹</param>private void RecycleBullet(Bullet bullet){bullet.activeSelf = false;_bulletPool.Enqueue(bullet);}
同时,子弹是往上移动的,所以子弹在碰到上边界后会主动销毁:
OnCollisionUpSide
为物体自身碰到游戏视口上边界时回调方法,同理分别有碰到下边界、左边界、右边界。
protected override void OnCollisionUpSide(){base.OnCollisionUpSide();RecycleBullet(this);}
5.增益 Buff
①.定义增益类
增益类Buff
继承至MiniGameObject
:
/// <summary>/// 增益/// </summary>public class Buff : MiniGameObject{/// <summary>/// 增益类型/// </summary>public int BuffType { get; private set; }/// <summary>/// 飞行速度/// </summary>public float Speed { get; private set; }public Buff(MiniGameWindow window, Rect rect, float speed) : base(window, rect){Speed = speed;}}
②.增益类型
我们暂时定义3种增益类型:
增益类型 | 增强功效 |
---|---|
加速弹 | 子弹飞行速度加倍 |
双发弹 | 可以同时发射2颗子弹 |
三发弹 | 可以同时发射3颗子弹 |
在增益物品诞生后,便随机生成增益类型:
/// <summary>/// 随机生成增益类型(1:加速弹,2:双发弹,3:三发弹)/// </summary>public void RandomType(){BuffType = Random.Range(1, 4);}
③.增益类绘制方法 OnGUI
分别根据增益的类型,在OnGUI
绘制增益物品:
public override void OnGUI(){base.OnGUI();GUI.color = Color.green;switch (BuffType){case 1:GUI.Box(transform, "", "SoloToggle");break;case 2:GUI.Box(transform, "", "BypassToggle");break;case 3:GUI.Box(transform, "", "MuteToggle");break;}GUI.color = Color.white;}
加速弹
绘制出来如下:
双发弹
绘制出来如下:
三发弹
绘制出来如下:
④.增益的移动
增益物品在出现后会从上方掉落下来:
public override void OnUpdate(){base.OnUpdate();MoveVertical(Speed);}
⑤.增益的生成
全局同时只能存在一个增益物品,所以只需要创建一个对象重复使用即可:
private Buff _buff;private const float _buffSpeed = 0.1f;/// <summary>/// 开始游戏/// </summary>private void StartGame(){ //增益_buff = new Buff(this, new Rect(10, 10, 20, 20), _buffSpeed);_buff.activeSelf = false;}/// <summary>/// 生成增益/// </summary>private void GenerateBuff(){if (!_buff.activeSelf){_buff.transform = new Rect(Random.Range(0, ViewportRect.width - 20), -20, 20, 20);_buff.activeSelf = true;_buff.RandomType();}}
⑥.增益的销毁
销毁增益物品时,只是将其设置为activeSelf = false
:
/// <summary>/// 销毁增益/// </summary>private void DestroyBuff(){_buff.activeSelf = false;}
同时,增益物品是往下移动的,所以在碰到下边界后会主动销毁:
protected override void OnCollisionDownSide(){base.OnCollisionDownSide();DestroyBuff();}
⑦.飞机获得增益
我们可以看到增益的移动过程是不带有碰撞检测
的,因为我们的飞机在移动时会主动进行碰撞检测
,所以增益的碰撞检测就省了,以降低开销:
OnCollisionEnter
方法为MiniGameObject
物体在碰撞到其他物体
时回调,从未碰撞状态
进入到碰撞状态
时回调1
次,也即是碰撞开始。
/// <summary>/// 飞机/// </summary>public class Airplane : MiniGameObject{public override void OnCollisionEnter(MiniGameObject other){base.OnCollisionEnter(other);//如果碰到了增益 Buffif (other is Buff){//根据增益类型,获得增强,同时销毁增益物品Buff buff = other as Buff;if (buff.BuffType == 1){HasSpeedUp = true;}else if (buff.BuffType == 2){BulletNumber = 2;}else if (buff.BuffType == 3){BulletNumber = 3;}DestroyBuff();}}}
6.敌人 Enemy
①.定义敌人基类
敌人基类Enemy
继承至MiniGameObject
,由于我们会设计多种敌人,所以这里先设计敌人的基类
,以归纳不同敌人的相似机制。
敌人相似机制如下:
1.自动向下移动(也可能不);
2.抵达下边界后死亡(也可能不);
3.拥有指定血量,血量见底后死亡;
/// <summary>/// 敌人基类/// </summary>public abstract class Enemy : MiniGameObject{private int _hp = 0;/// <summary>/// 自动向下移动/// </summary>public virtual bool IsAutoMove { get; } = true;/// <summary>/// 抵达下边界后死亡/// </summary>public virtual bool IsDeadToSide { get; } = true;/// <summary>/// 移动速度/// </summary>public float Speed { get; protected set; }/// <summary>/// 血量/// </summary>public int HP{get{return _hp;}set{_hp = value;if (_hp <= 0){//回收敌人RecycleEnemy(this);}}}public Enemy(MiniGameWindow window, Rect rect, float speed, int hp) : base(window, rect){Speed = speed;HP = hp;}public override void OnUpdate(){base.OnUpdate();//自动向下移动//不主动进行碰撞检测//因为敌人对象较多,主动进行碰撞检测开销过大,让飞机来进行针对敌人的碰撞检测即可if (IsAutoMove) MoveVertical(Speed);}protected override void OnCollisionDownSide(){base.OnCollisionDownSide();if (IsDeadToSide) HP = 0;}}
②.敌人类型1
敌人类型1我们决定就做成一个方块,简单且脆弱,一击必杀:
/// <summary>/// 敌人1/// </summary>public class Enemy1 : Enemy{public Enemy1(MiniGameWindow window, Rect rect, float speed, int hp) : base(window, rect, speed, hp){}public override void OnGUI(){base.OnGUI();GUI.color = Color.yellow;GUI.Box(transform, "", "button");GUI.color = Color.white;}}
将敌人统一绘制为
黄色
,以进行视觉上的区分。
生成、回收敌人1(均采用对象池
):
private Queue<Enemy1> _enemy1Pool = new Queue<Enemy1>();private const float _enemy1Speed = 0.05f;//敌人1血量100,与飞机子弹的攻击力100相等private const int _enemy1HP = 100;/// <summary>/// 生成敌人1/// </summary>private void GenerateEnemy1(){Rect rect = new Rect(Random.Range(0, ViewportRect.width - 20), -20, 20, 20);Enemy1 enemy = null;if (_enemy1Pool.Count > 0){enemy = _enemy1Pool.Dequeue();enemy.transform = rect;enemy.HP = _enemy1HP;}else{enemy = new Enemy1(this, rect, _enemy1Speed, _enemy1HP);}enemy.activeSelf = true;}/// <summary>/// 回收敌人/// </summary>/// <param name="enemy">敌人</param>private void RecycleEnemy(Enemy enemy){enemy.activeSelf = false;if (enemy is Enemy1){_enemy1Pool.Enqueue(enemy as Enemy1);}}
③.敌人类型2
敌人类型2我们稍作加强,绘制3个方块,看起来像个飞行器:
/// <summary>/// 敌人2/// </summary>public class Enemy2 : Enemy{public Enemy2(MiniGameWindow window, Rect rect, float speed, int hp) : base(window, rect, speed, hp){}public override void OnGUI(){base.OnGUI();GUI.color = Color.yellow;Rect rect = new Rect();rect.Set(transform.x, transform.y, 20, 20);GUI.Box(rect, "", "button");rect.Set(transform.x + 20, transform.y, 20, 20);GUI.Box(rect, "", "button");rect.Set(transform.x + 10, transform.y + 20, 20, 20);GUI.Box(rect, "", "button");GUI.color = Color.white;}}
生成、回收敌人2与敌人1同理,这里不做赘述。
只是敌人2的血量
获得了增强,同时移速
降低:
private const float _enemy2Speed = 0.03f;private const int _enemy2HP = 300;
③.敌人类型3
敌人类型3我们继续加强,其将能够发射子弹
攻击主角飞机:
/// <summary>/// 敌人3/// </summary>public class Enemy3 : Enemy{private Vector2 _ballDis = new Vector2(0, -35);private float _ball1Rot = 0;private float _ball2Rot = 90;private float _ball3Rot = 180;private float _ball4Rot = 270;private float _timer = 0;public Enemy3(MiniGameWindow window, Rect rect, float speed, int hp) : base(window, rect, speed, hp){}public override void OnGUI(){base.OnGUI();GUI.color = Color.yellow;Rect rect = new Rect();rect.width = 20;rect.height = 20;rect.center = transform.center;GUI.Box(rect, "", "button");//周围围绕飞行着4个球体Vector2 ballPos = Quaternion.Euler(0, 0, _ball1Rot) * _ballDis;rect.width = 10;rect.height = 10;rect.center = transform.center + ballPos;GUI.Box(rect, "", "horizontalsliderthumb");ballPos = Quaternion.Euler(0, 0, _ball2Rot) * _ballDis;rect.width = 10;rect.height = 10;rect.center = transform.center + ballPos;GUI.Box(rect, "", "horizontalsliderthumb");ballPos = Quaternion.Euler(0, 0, _ball3Rot) * _ballDis;rect.width = 10;rect.height = 10;rect.center = transform.center + ballPos;GUI.Box(rect, "", "horizontalsliderthumb");ballPos = Quaternion.Euler(0, 0, _ball4Rot) * _ballDis;rect.width = 10;rect.height = 10;rect.center = transform.center + ballPos;GUI.Box(rect, "", "horizontalsliderthumb");GUI.color = Color.white;}public override void OnUpdate(){base.OnUpdate();//4个球体旋转围绕飞行_ball1Rot += 0.3f;if (_ball1Rot > 360) _ball1Rot = 0;_ball2Rot += 0.3f;if (_ball2Rot > 360) _ball2Rot = 0;_ball3Rot += 0.3f;if (_ball3Rot > 360) _ball3Rot = 0;_ball4Rot += 0.3f;if (_ball4Rot > 360) _ball4Rot = 0;if (_timer > 0){_timer -= gameWindow.DeltaTime;}else{//每隔20个时间单位(不等于秒),发射一次子弹攻击飞机_timer = 20;GenerateEnemyBullet(transform.center + new Vector2(0, 40));}}}
gameWindow.DeltaTime
类似于Time.deltaTime
,只不过前者只是模拟帧流逝时间,后者是运行时的精确的帧流逝时间。
生成、回收敌人3与敌人1同理,这里不做赘述。
只是敌人3的血量
获得了增强,同时移速
降低:
private const float _enemy3Speed = 0.03f;private const int _enemy3HP = 1000;
④.敌人类型3发射的子弹
同样的,敌人3发射的子弹有自身的逻辑,所以其也需要单独定义:
/// <summary>/// 敌人的子弹/// </summary>public class EnemyBullet : MiniGameObject{public float Speed { get; set; }private Vector2 _dir;public EnemyBullet(MiniGameWindow window, Rect rect, float speed) : base(window, rect){isAllowOutOfViewport = false;Speed = speed;}public override void OnGUI(){base.OnGUI();GUI.color = Color.yellow;GUI.Box(transform, "", "horizontalsliderthumb");GUI.color = Color.white;}public override void OnUpdate(){base.OnUpdate();Move(_dir);}}
Move
方法为向指定方向移动,同样不进行碰撞检测。
在子弹发射之初纠正一下发射方向,使其瞄准飞机
:
/// <summary>/// 生成敌人子弹/// </summary>/// <param name="pos">子弹生成位置</param>private void GenerateEnemyBullet(Vector2 pos){Rect rect = new Rect(0, 0, 10, 10);rect.center = pos;EnemyBullet bullet = null;if (_enemyBulletPool.Count > 0){bullet = _enemyBulletPool.Dequeue();bullet.transform = rect;}else{bullet = new EnemyBullet(this, rect, _enemyBulletSpeed);}bullet.activeSelf = true;bullet.CorrectDirection(_airplane, pos);}/// <summary>/// 纠正子弹方向,以瞄准飞机/// </summary>public void CorrectDirection(Airplane airplane, Vector2 startPos){_dir = airplane.transform.center - startPos;_dir = _dir.normalized * Speed;}
敌人子弹在碰到下、左、右边界时,都会主动销毁:
protected override void OnCollisionLeftSide(){base.OnCollisionLeftSide();RecycleEnemyBullet(this);}protected override void OnCollisionRightSide(){base.OnCollisionRightSide();RecycleEnemyBullet(this);}protected override void OnCollisionDownSide(){base.OnCollisionDownSide();RecycleEnemyBullet(this);}
③.敌人类型4
敌人类型4做出一些改变:
/// <summary>/// 敌人4/// </summary>public class Enemy4 : Enemy{//不自动向下移动public override bool IsAutoMove => false;//抵达下边界也不死亡public override bool IsDeadToSide => false;public Enemy4(MiniGameWindow window, Rect rect, float speed, int hp) : base(window, rect, speed, hp){}}
敌人4的行为就像一个激光柱一样,从上方出现后,喷发至窗口底部,然后自动消失:
private bool _isExplosion = false;private float _expCountdown = 0;private float _expExpand = 0;private float _expNarrowing = 0;public override void OnGUI(){base.OnGUI();GUI.color = Color.red;//喷发中if (_isExplosion){Rect rect = transform;//高度不断拉长if (_expExpand < gameWindow.ViewportRect.height){rect.height = _expExpand;}else{Vector2 center = rect.center;rect.width = _expNarrowing;rect.center = center;}transform = rect;}GUI.Box(transform, "", "WhiteBackground");GUI.color = Color.white;}public override void OnUpdate(){base.OnUpdate();//喷发中if (_isExplosion){//高度拉长中if (_expExpand < gameWindow.ViewportRect.height){_expExpand += gameWindow.DeltaTime * 60;}//抵达底部,一段时间后自身死亡else{_expNarrowing -= gameWindow.DeltaTime * 20;if (_expNarrowing <= 0){HP = 0;}}}else{//未喷发中,一段时间后开始喷发if (_expCountdown > 0){_expCountdown -= gameWindow.DeltaTime;}else{_isExplosion = true;_expExpand = transform.height;_expNarrowing = 40;}}}
由于碰撞检测是基于物体的
transform
进行的(无需添加额外的碰撞盒),所以这里敌人4在拉高自身高度时,是设置的transform
属性,使得碰撞范围同步增大。
敌人4的血量
更多,同时不需要移动:
private const int _enemy4HP = 2000;
7.碰撞检测配置
想要游戏物体间的碰撞检测生效,还需要进行如下几步配置。
①.游戏物体设置为碰撞器
/// <summary>/// 小游戏的游戏对象/// </summary>public abstract class MiniGameObject{/// <summary>/// 是否为碰撞器/// </summary>public bool collider { get; set; } = true;}
这个属性默认为true
,不需要碰撞检测的可以关闭。
②.游戏窗口启用碰撞检测
每个游戏窗口有一个变量,控制是否启用碰撞检测:
/// <summary>/// 开始游戏/// </summary>private void StartGame(){//启用碰撞检测IsEnableCollisionDetection = true;}
③.添加碰撞矩阵
同样为了性能考虑(碰撞检测在EditorWindow
中比较昂贵),只有事先已指定的碰撞层(碰撞类型)
之间才支持碰撞检测:
/// <summary>/// 开始游戏/// </summary>private void StartGame(){//启用碰撞检测IsEnableCollisionDetection = true;AddCollisionMatrix("Bullet", "Enemy");AddCollisionMatrix("Airplane", "Buff");AddCollisionMatrix("Airplane", "Enemy");AddCollisionMatrix("Airplane", "EnemyBullet");}
AddCollisionMatrix
为添加碰撞层到碰撞矩阵
,比如AddCollisionMatrix("Bullet", "Enemy")
,将启用Bullet
层与Enemy
层之间的碰撞检测。
为此,每个MiniGameObject
均可以设置其所在的碰撞层
,默认为其类型全称:
/// <summary>/// 小游戏的游戏对象/// </summary>public abstract class MiniGameObject{private string _collisionType;/// <summary>/// 碰撞类型(默认为类型全称)/// </summary>public virtual string CollisionType{get{if (string.IsNullOrEmpty(_collisionType)){_collisionType = GetType().FullName;}return _collisionType;}}}
8.飞机的子弹攻击敌人
飞机的子弹能够攻击敌人,但我们的敌人定义了多个类,所以为了方便统一处理,所有敌人的碰撞层
均设置为Enemy
:
/// <summary>/// 敌人基类/// </summary>public abstract class Enemy : MiniGameObject{public override string CollisionType => "Enemy";}
子弹的碰撞层为Bullet
:
/// <summary>/// 子弹/// </summary>public class Bullet : MiniGameObject{public override string CollisionType => "Bullet";}
AddCollisionMatrix("Bullet", "Enemy")
将使得子弹能够碰撞到敌人,所以在子弹的碰撞回调方法中处理:
public override void OnCollisionEnter(MiniGameObject other){base.OnCollisionEnter(other);if (other is Enemy){//敌人扣血(other as Enemy).HP -= Power;//回收子弹RecycleBullet(this);}}
9.飞机被敌人或敌人的子弹攻击
同理,定义飞机的碰撞层为Airplane
:
/// <summary>/// 飞机/// </summary>public class Airplane : MiniGameObject{public override string CollisionType => "Airplane";}
敌人子弹的碰撞层为EnemyBullet
:
/// <summary>/// 敌人的子弹/// </summary>public class EnemyBullet : MiniGameObject{public override string CollisionType => "EnemyBullet";}
在飞机的碰撞回调方法中处理:
public override void OnCollisionEnter(MiniGameObject other){base.OnCollisionEnter(other);if (other is Buff){Buff buff = other as Buff;if (buff.BuffType == 1){HasSpeedUp = true;}else if (buff.BuffType == 2){BulletNumber = 2;}else if (buff.BuffType == 3){BulletNumber = 3;}DestroyBuff();}else if (other is Enemy){//碰到敌人即游戏失败gameWindow.IsGameOvered = true;}else if (other is EnemyBullet){//碰到敌人子弹即游戏失败gameWindow.IsGameOvered = true;}}
10.获得分数
当敌人死亡时获得分数:
/// <summary>/// 回收敌人/// </summary>/// <param name="enemy">敌人</param>private void RecycleEnemy(Enemy enemy){enemy.activeSelf = false;if (enemy is Enemy1){_enemy1Pool.Enqueue(enemy as Enemy1);_score += 1;}else if (enemy is Enemy2){_enemy2Pool.Enqueue(enemy as Enemy2);_score += 3;}else if (enemy is Enemy3){_enemy3Pool.Enqueue(enemy as Enemy3);_score += 10;}else if (enemy is Enemy4){enemy.activeSelf = false;_score += 10;}}
11.绘制移动的背景板
绘制一个向下移动
的背景板,以突出飞机正在向上飞行
:
protected override void OnInit(){base.OnInit();_background = EditorGUIUtility.IconContent("StateMachineEditor.Background").image;_background.wrapMode = TextureWrapMode.Repeat;}protected override void OnGameViewportGUI(){//背景板向下移动_backPos += DeltaTime * 0.02f;GUI.DrawTextureWithTexCoords(new Rect(0, 0, ViewportRect.width, ViewportRect.height), _background, new Rect(0, _backPos, ViewportRect.width / 256, ViewportRect.height / 128));base.OnGameViewportGUI();}
12.飞机开火
在OnGamePlayingEvent
方法中,按下鼠标左键
或A
键为开火:
protected override void OnGamePlayingEvent(Event e, Vector2 mousePosition){base.OnGamePlayingEvent(e, mousePosition);if (e.type == EventType.Repaint){Rect rect = _airplane.transform;rect.center = new Vector2(mousePosition.x, Mathf.Clamp(mousePosition.y, ViewportRect.height - 80, ViewportRect.height - 30));_airplane.transform = rect;CollisionDetection(_airplane);}//_fireCooling 开火冷却时间else if (e.type == EventType.MouseDown){if (e.button == 0 && _fireCooling <= 0){_airplane.Fire();_fireCooling = 1;}}else if (e.type == EventType.KeyDown){if (e.keyCode == KeyCode.A && _fireCooling <= 0){_airplane.Fire();_fireCooling = 1;}}}
13.生成敌人的策略
生成敌人的策略采用固定时间轮询的形式,每隔一段时间有几率生成指定敌人:
private float _emitEnemyCooling = 10;protected override void OnGamePlayingUpdate(){base.OnGamePlayingUpdate();if (_timer > 0){_timer -= DeltaTime;}else{//每隔10个单位时间生成一次敌人_timer = _emitEnemyCooling;//10%概率生成一个buffif (Utility.IsTriggerProbability(10)) GenerateBuff();//80%概率生成一个敌人1if (Utility.IsTriggerProbability(80)) GenerateEnemy1();//50%概率生成一个敌人2if (Utility.IsTriggerProbability(50)) GenerateEnemy2();//10%概率生成一个敌人3if (Utility.IsTriggerProbability(10)) GenerateEnemy3();//10%概率生成一个敌人4if (Utility.IsTriggerProbability(10)) GenerateEnemy4();}}
至此,一个简单的飞机大战小游戏就完成了,试玩效果如下:飞机大战【AirplaneWars】。
14.暂停游戏、退出游戏
同俄罗斯方块。