• 【Unity】U3D TD游戏制作实例(五)防御塔设计:对象排序、锁定敌人、攻击敌人、防御塔特色功能实现



    本章目标

    设计防御塔类型以及配置方式,选择几种防御塔做样例,实现防御塔的攻击相关功能,如:锁定敌人、攻击敌人、攻击动画、攻击特效等。

    防御塔策划

    虽然是个 Demo 游戏,但也要有基本的策划,我们暂定防御塔可以有以下几种类别:

    • 单体攻击类:箭塔,锁定单体攻击,攻击频率较快
    • 溅射攻击类:炮塔,攻击有溅射伤害
    • 超远程攻击类:导弹塔,单体锁定溅射攻击,锁定半径极大,攻击力较强,有溅射效果,攻击速度低。
    • 远程群体减速类:冰锥塔,锁定单体并溅射减速效果,降低范围内敌人移动力,攻击力极低。
    • 单体减速类:毒液塔,锁定单体攻击,降低单个敌人移动力,攻击力极低。
    • 周围群体减速类:荆棘塔,群体锁定攻击,降低半径范围内敌人移动力,攻击力极低。
    • 定身类:时间秩序塔,群体锁定攻击,限制半径范围内敌人移动,攻击力极低。
    • 群体攻击类:多重箭塔,最多可同时攻击6个单位,攻击力较弱。
    • 单体暴击类:剑神,基础攻击力较强,攻击产生暴击。
    • 持续攻击类:电塔,针对 Boss ,每帧都造成伤害,对 Boss 伤害有加成
    • 线性攻击类:激光塔,对一条射线路径上的所有敌人造成伤害。目标有距离限制,伤害无距离限制。
    • 弹射攻击类:折射炮塔,锁定单体攻击,击中后炮弹有两次弹射攻击。弹射距离有限制。

    四种防御塔

    我们在Demo中先做四种防御塔,分别是:

    1. 箭塔;
    2. 炮塔;
    3. 导弹塔;
    4. 冰锥塔。
      在这里插入图片描述
      因为是 Demo ,所以模型都是各个网站下载的,风格不太统一,先凑合一下,哈哈。

    防御塔配置文件

    在这里插入图片描述

    每关可选不同的防御塔

    为了增加游戏的可玩性,我们让不同的关卡拥有不同的可选防御塔,所以要在关卡配置文件增加一个配置项。
    在这里插入图片描述

    加载配置

    防御塔管理类(DefenseManager)代码:

    using Excel;
    using System;
    using System.Collections.Generic;
    using TDGameDemo.GameDefense;
    using TDGameDemo.GameLevel;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class DefenseManager : MonoBehaviour
    {
    
        private Dictionary<string, List<DefenseConfig>> _defenseConfigs;
    
        private void Start()
        {
            InitConfig();
        }
    
        /// 
        /// 初始化配置
        /// 
        private void InitConfig()
        {
            _defenseConfigs = new Dictionary<string, List<DefenseConfig>>();
            ConfigManager cm = new ConfigManager();
            IExcelDataReader excelReader = cm.LoadExcel(new string[] { "Configs", "DefenseConfig", "DefenseConfig_All.xlsx" });
            // 读取
            int index = 0;
            // 移动到第四行
            for (; index < 4; index++)
            {
                excelReader.Read();
            }
    
            while (true)
            {
                if (excelReader.GetString(1) == null) break;
                DefenseConfig defConfig = new DefenseConfig();
                defConfig.DefenseCode = excelReader.GetString(1);
                defConfig.DefenseType = excelReader.GetString(2);
                defConfig.DefenseTypeCode = excelReader.GetInt32(3);
                defConfig.LockTargetRange = excelReader.GetInt32(5);
                defConfig.LockTargetCount = excelReader.GetInt32(6);
                defConfig.AttackCooldownTime = excelReader.GetFloat(7);
                defConfig.BulletATK = excelReader.GetInt32(8);
                defConfig.RetardanceCoefficient = excelReader.GetFloat(9);
                defConfig.RetardanceDuration = excelReader.GetFloat(10);
                defConfig.RetardanceRange = excelReader.GetInt32(11);
    
                defConfig.IsTopLevel = false;
                if (!_defenseConfigs.ContainsKey(defConfig.DefenseCode))
                {
                    _defenseConfigs.Add(defConfig.DefenseCode, new List<DefenseConfig>());
                }
                _defenseConfigs[defConfig.DefenseCode].Add(defConfig);
                excelReader.Read();
                index++;
            }
    
            foreach (KeyValuePair<string, List<DefenseConfig>> items in _defenseConfigs)
            {
                // 让每个类别的炮塔按照等级重新排序
                items.Value.Sort();
                // 将每个类别中最高级的炮塔设置为顶级炮塔
                items.Value[items.Value.Count - 1].IsTopLevel = true;
                //foreach (DefenseConfig item in items.Value)
                //{
                //    Debug.Log(items.Key + "===========" + item.DefenseLevel);
                //}
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    防御塔配置模型类(DefenseConfig)代码:
    注意:代码中实现了 IComparable 接口,以便于在列表中实现按防御塔等级排序。关于排序的详细介绍可以参考我的另一篇文章:【Unity】Unity开发进阶(三)对象排序工具、减少使用foreach

    using System;
    
    namespace TDGameDemo.GameDefense
    {
        /// 
        /// 防御塔配置类
        /// 
        public class DefenseConfig : IComparable<DefenseConfig>
        {
    
            /// 
            /// 实现IComparable接口,让防御塔具备按照等级排序的能力
            /// 
            /// 
            /// 
            public int CompareTo(DefenseConfig other)
            {
                return DefenseLevel.CompareTo(other.DefenseLevel);
            }
    
            /// 
            /// 防御塔编号
            /// 
            public string DefenseCode { get; set; }
    
            /// 
            /// 防御塔类型
            /// 
            public string DefenseType { get; set; }
    
            /// 
            /// 防御塔类型编号
            /// 
            public int DefenseTypeCode { get; set; }
    
            /// 
            /// 防御塔等级
            /// 
            public int DefenseLevel { get; set; }
    
            /// 
            /// 是否顶级
            /// 
            public bool IsTopLevel { get; set; }
    
            /// 
            /// 锁定目标范围
            /// 
            public float LockTargetRange { get; set; }
    
            /// 
            /// 子弹速度
            /// 
            public float BulletSpeed { get; set; }
    
            /// 
            /// 目标数量
            /// 
            public int LockTargetCount { get; set; }
    
            /// 
            /// 攻击CD时间
            /// 
            public float AttackCooldownTime { get; set; }
    
            /// 
            /// 攻击力
            /// 
            public float BulletATK { get; set; }
    
            /// 
            /// 减速系数
            /// 
            public float RetardanceCoefficient { get; set; }
    
            /// 
            /// 减速持续时间
            /// 
            public float RetardanceDuration { get; set; }
    
            /// 
            /// 攻击影响范围
            /// 
            public float RetardanceRange { get; set; }
    
            /// 
            /// 定身时间
            /// 
            public float DizzyDuration { get; set; }
    
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    制作UI

    在这里插入图片描述
    在这里插入图片描述

    锁定敌人

    首先所有的防御塔都应该继承于一个基类:DefenseBase ,代码如下:

    using UnityEngine;
    
    namespace TDGameDemo.GameDefense
    {
        /// 
        /// 防御塔基类
        /// 
        public class DefenseBase : MonoBehaviour
        {
            /// 
            /// 敌人生成点的父节点
            /// 防御塔管理器创建防御塔时获得。 
            /// 
            [HideInInspector]
            public Transform EnemyGeneratePointParent;
    
            /// 
            /// 防御塔中需要旋转的物体
            /// 例如导弹发射器等需要旋转的炮台 
            /// 
            public Transform Rotater;
    
            /// 
            /// 防御塔配置
            /// 防御塔管理器创建防御塔时获得。 
            /// 
            public DefenseConfig _defenseConfig;
    
            /// 
            /// 防御塔目标
            /// 通过LockTarget锁定目标。 
            /// 
            protected Transform _target;// TODO 暂时为单个目标,后续需要改成列表。
    
            /// 
            /// 攻击偏移时间
            /// 当此变量超过防御塔的攻击冷却时间(AttackCooldownTime)时才可以进行下一次攻击。 
            /// 
            protected float _attackOffsetTime = 50f;
    
            /// 
            /// 子弹生成点
            /// 
            protected Transform _weaponGenPoint;
    
            /// 
            /// 子弹预制件文件路径前缀
            /// 
            public const string BULLET_PREFAB_PREFIX = "Defense/Prefab/";
    
            /// 
            /// 锁定敌人方法
            /// 
            /// 
            public virtual void LockTarget()
            {
                for (int i = 0; i < EnemyGeneratePointParent.childCount; i++)
                {
                    // 查找所有敌人
                    foreach (Transform child in EnemyGeneratePointParent.GetChild(i))
                    {
                        // 确认是否在射程范围内
                        if (Vector3.Distance(transform.position, child.position) < _defenseConfig.LockTargetRange)
                        {
                            _target = child;
                        }
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    在子类的 Update 中调用父类的 LockTarget 方法来锁定敌人。子类代码如下:

    using UnityEngine;
    
    namespace TDGameDemo.GameDefense
    {
    
        public class ArrowDefense : DefenseBase
        {
            void Update()
            {
                // 叠加攻击CD时间
                _attackOffsetTime += Time.deltaTime;
                // 如果失去目标,则重新锁定新的目标
                if (_target == null)
                {
                    LockTarget();
                }
                else // 如果有目标则攻击目标
                {
                    AttackTarget();
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    攻击敌人

    锁定敌人以后调用父类的 AttackTarget 方法即可实现攻击,代码如下:

    /// 
    /// 攻击目标方法
    /// 
    /// 
    public virtual void AttackTarget()
    {
        // 判断受击物体是否存在
        if (_target == null)
        {
            return;
        }
    
        // 判断是否可以攻击
        //if (_attackOffsetTime > _defenseConfig.AttackCooldownTime && _weaponGenPoint.childCount == 0)
        if (_attackOffsetTime > _defenseConfig.AttackCooldownTime)
        {
            // 能调用攻击,证明已经有目标了,要看目标是不是在攻击范围内,如果不在范围内,要更换新目标。
            // 计算玩家与目标敌人的距离
            if (Vector3.Distance(transform.position, _target.position) < _defenseConfig.LockTargetRange)
            {
                _weaponGenPoint.LookAt(_target);
                string path = BULLET_PREFAB_PREFIX + "Prefab_Defense_" + _defenseConfig.DefenseCode + "_Bullet";
                GameObject enemyPrefab = Resources.Load<GameObject>(path);
                GameObject bullet = Instantiate(enemyPrefab, _weaponGenPoint.position, _weaponGenPoint.rotation, _weaponGenPoint);
                bullet.GetComponent<Bullet>().Target = _target;
                bullet.GetComponent<Bullet>().Speed = _defenseConfig.BulletSpeed;
                _attackOffsetTime = 0f;
            }
            else
            {
                // 目标离开攻击范围,失去目标
                _target = null;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    将此方法放到 DefenseBase 类中即可。

    防御塔个性化实现

    父类的锁定敌人方法 LockTarget 和攻击敌人方法 AttackTarget 是常规情况下的处理方式,有时候新的防御塔并不一定用同样的方式锁定敌人或者攻击敌人,此时可以在子类中增加个性化代码。

    个性化代码分为两种,一种是对父类方法进行扩充,另一种是完全替代父类方法。

    对父类方法进行扩充

    比如我们的加农炮台需要顶部炮台朝着敌人的位置旋转,此时可以使用扩充的方式,在子类代码中重写 AttackTarget 方法并调用父类方法(base.AttackTarget();),然后再进行扩充,代码如下:

    using UnityEngine;
    
    namespace TDGameDemo.GameDefense
    {
        public class CannonDefense : DefenseBase
        {
            private void Start()
            {
                _weaponGenPoint = transform.Find("WeaponGenPoint");
            }
    
            private void Update()
            {
                // 叠加攻击CD时间
                _attackOffsetTime += Time.deltaTime;
                // 如果失去目标,则重新锁定新的目标
                if (_target == null)
                {
                    LockTarget();
                }
                else // 如果有目标则攻击目标
                {
                    AttackTarget();
                }
            }
    
            /// 
            /// 攻击目标
            /// 
            public override void AttackTarget()
            {
                base.AttackTarget();
                if (_target != null)
                {
                    Rotater.LookAt(new Vector3(_target.position.x, Rotater.transform.position.y, _target.position.z));
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    替代父类方法

    导弹塔的发射轨迹与箭塔不同,导弹是先斜向上飞然后再飞向敌人。这需要进行一个斜抛运动的计算,此时就可以直接替代父类方法,也就是在子类方法中不去调用父类方法即可,代码大致为:

    /// 
    /// 攻击目标
    /// 
    public override void AttackTarget()
    {
        // TODO 斜抛运动攻击敌人
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    斜抛运动

    关于斜抛运动的计算方式我将在下一章中讲解,欢迎关注,大家共同进步。

    效果演示

    Unity制作炮台防守游戏(3)防御塔攻击


    更多内容请查看总目录【Unity】Unity学习笔记目录整理

  • 相关阅读:
    赶紧进来!!!带你认识C语言基本数据类型
    Spring项目-前端问题:Can‘t find variable:$
    潘爱民:计算机程序的演进——我的程序人生三十年
    计算机组成原理 | 第一章
    算法之美阅读笔记
    Chapter 7 XGBoost
    C++ 循环截取字符串
    阿里三面:什么是循环依赖?你说一下Spring解决循环依赖的流程
    数据分析-费米问题
    如何解决 npm ERR! Cannot read properties of null (reading ‘pickAlgorithm‘)报错问题
  • 原文地址:https://blog.csdn.net/xiaoyaoACi/article/details/127672738