【Unity】MiniGame编辑器小游戏(六)飞机大战【AirplaneWars】

发布于:2025-06-26 ⋅ 阅读:(19) ⋅ 点赞:(0)

更新日期: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为模拟帧循环回调方法,类似于MonoBehaviourUpdate方法,只不过由于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.暂停游戏、退出游戏

俄罗斯方块


网站公告

今日签到

点亮在社区的每一天
去签到