更新日期:2025年6月25日。
项目源码:获取项目源码
索引
飞机大战【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);
//如果碰到了增益 Buff
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();
}
}
}
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%概率生成一个buff
if (Utility.IsTriggerProbability(10)) GenerateBuff();
//80%概率生成一个敌人1
if (Utility.IsTriggerProbability(80)) GenerateEnemy1();
//50%概率生成一个敌人2
if (Utility.IsTriggerProbability(50)) GenerateEnemy2();
//10%概率生成一个敌人3
if (Utility.IsTriggerProbability(10)) GenerateEnemy3();
//10%概率生成一个敌人4
if (Utility.IsTriggerProbability(10)) GenerateEnemy4();
}
}
至此,一个简单的飞机大战小游戏就完成了,试玩效果如下:飞机大战【AirplaneWars】。
14.暂停游戏、退出游戏
同俄罗斯方块。