【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();
    }

    /// <summary>
    /// 初始化配置
    /// </summary>
    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);
            //}
        }
    }
}

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

using System;

namespace TDGameDemo.GameDefense
{
    /// <summary>
    /// 防御塔配置类
    /// </summary>
    public class DefenseConfig : IComparable<DefenseConfig>
    {

        /// <summary>
        /// 实现IComparable接口,让防御塔具备按照等级排序的能力
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(DefenseConfig other)
        {
            return DefenseLevel.CompareTo(other.DefenseLevel);
        }

        /// <summary>
        /// 防御塔编号
        /// </summary>
        public string DefenseCode { get; set; }

        /// <summary>
        /// 防御塔类型
        /// </summary>
        public string DefenseType { get; set; }

        /// <summary>
        /// 防御塔类型编号
        /// </summary>
        public int DefenseTypeCode { get; set; }

        /// <summary>
        /// 防御塔等级
        /// </summary>
        public int DefenseLevel { get; set; }

        /// <summary>
        /// 是否顶级
        /// </summary>
        public bool IsTopLevel { get; set; }

        /// <summary>
        /// 锁定目标范围
        /// </summary>
        public float LockTargetRange { get; set; }

        /// <summary>
        /// 子弹速度
        /// </summary>
        public float BulletSpeed { get; set; }

        /// <summary>
        /// 目标数量
        /// </summary>
        public int LockTargetCount { get; set; }

        /// <summary>
        /// 攻击CD时间
        /// </summary>
        public float AttackCooldownTime { get; set; }

        /// <summary>
        /// 攻击力
        /// </summary>
        public float BulletATK { get; set; }

        /// <summary>
        /// 减速系数
        /// </summary>
        public float RetardanceCoefficient { get; set; }

        /// <summary>
        /// 减速持续时间
        /// </summary>
        public float RetardanceDuration { get; set; }

        /// <summary>
        /// 攻击影响范围
        /// </summary>
        public float RetardanceRange { get; set; }

        /// <summary>
        /// 定身时间
        /// </summary>
        public float DizzyDuration { get; set; }

    }
}


制作UI

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

锁定敌人

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

using UnityEngine;

namespace TDGameDemo.GameDefense
{
    /// <summary>
    /// 防御塔基类
    /// </summary>
    public class DefenseBase : MonoBehaviour
    {
        /// <summary>
        /// 敌人生成点的父节点
        /// <para>防御塔管理器创建防御塔时获得。</para> 
        /// </summary>
        [HideInInspector]
        public Transform EnemyGeneratePointParent;

        /// <summary>
        /// 防御塔中需要旋转的物体
        /// <para>例如导弹发射器等需要旋转的炮台</para> 
        /// </summary>
        public Transform Rotater;

        /// <summary>
        /// 防御塔配置
        /// <para>防御塔管理器创建防御塔时获得。</para> 
        /// </summary>
        public DefenseConfig _defenseConfig;

        /// <summary>
        /// 防御塔目标
        /// <para>通过LockTarget锁定目标。</para> 
        /// </summary>
        protected Transform _target;// TODO 暂时为单个目标,后续需要改成列表。

        /// <summary>
        /// 攻击偏移时间
        /// <para>当此变量超过防御塔的攻击冷却时间(AttackCooldownTime)时才可以进行下一次攻击。</para> 
        /// </summary>
        protected float _attackOffsetTime = 50f;

        /// <summary>
        /// 子弹生成点
        /// </summary>
        protected Transform _weaponGenPoint;

        /// <summary>
        /// 子弹预制件文件路径前缀
        /// </summary>
        public const string BULLET_PREFAB_PREFIX = "Defense/Prefab/";

        /// <summary>
        /// 锁定敌人方法
        /// </summary>
        /// <returns></returns>
        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;
                    }
                }
            }
        }
    }
}

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

using UnityEngine;

namespace TDGameDemo.GameDefense
{

    public class ArrowDefense : DefenseBase
    {
        void Update()
        {
            // 叠加攻击CD时间
            _attackOffsetTime += Time.deltaTime;
            // 如果失去目标,则重新锁定新的目标
            if (_target == null)
            {
                LockTarget();
            }
            else // 如果有目标则攻击目标
            {
                AttackTarget();
            }
        }
    }
}

攻击敌人

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

/// <summary>
/// 攻击目标方法
/// </summary>
/// <returns></returns>
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;
        }
    }
}

将此方法放到 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();
            }
        }

        /// <summary>
        /// 攻击目标
        /// </summary>
        public override void AttackTarget()
        {
            base.AttackTarget();
            if (_target != null)
            {
                Rotater.LookAt(new Vector3(_target.position.x, Rotater.transform.position.y, _target.position.z));
            }
        }
    }
}

替代父类方法

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

/// <summary>
/// 攻击目标
/// </summary>
public override void AttackTarget()
{
    // TODO 斜抛运动攻击敌人
}

斜抛运动

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

效果演示

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


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

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的防御游戏的代码示例,包括防御自动攻击敌人的逻辑: ```csharp public class Tower : MonoBehaviour { public float range = 10f; // 攻击范围 public float fireRate = 1f; // 发射速率 public GameObject bulletPrefab; // 子弹预制体 private Transform target; // 目标敌人 private float fireCountdown = 0f; // 发射倒计时 void Update() { if (target == null) // 如果没有目标敌人,则查找敌人 { FindTarget(); return; } if (fireCountdown <= 0f) // 如果发射倒计时结束,则发射子弹 { Shoot(); fireCountdown = 1f / fireRate; } fireCountdown -= Time.deltaTime; // 更新发射倒计时 } void FindTarget() { GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); // 查找所有敌人 float shortestDistance = Mathf.Infinity; GameObject nearestEnemy = null; foreach (GameObject enemy in enemies) // 遍历所有敌人,找到最近的敌人 { float distanceToEnemy = Vector3.Distance(transform.position, enemy.transform.position); if (distanceToEnemy < shortestDistance && distanceToEnemy <= range) { shortestDistance = distanceToEnemy; nearestEnemy = enemy; } } if (nearestEnemy != null) // 如果有找到最近的敌人,则设为目标敌人 { target = nearestEnemy.transform; } } void Shoot() { GameObject bulletGO = Instantiate(bulletPrefab, transform.position, Quaternion.identity); // 实例化子弹 Bullet bullet = bulletGO.GetComponent<Bullet>(); // 获取子弹组件 if (bullet != null) // 如果子弹组件存在,则设置目标敌人 { bullet.Seek(target); } } void OnDrawGizmosSelected() // 在编辑器中显示攻击范围 { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, range); } } ``` 该代码包含以下要点: 1. 使用 `range` 属性定义防御攻击范围; 2. 使用 `fireRate` 属性定义防御发射速率; 3. 使用 `bulletPrefab` 属性定义子弹预制体; 4. 使用 `target` 变量存储目标敌人; 5. 在 `Update` 方法中,先查找目标敌人,如果有目标敌人则更新发射倒计时并发射子弹; 6. 在 `FindTarget` 方法中,查找所有敌人,找到最近的敌人并设为目标敌人; 7. 在 `Shoot` 方法中,实例化子弹并设置目标敌人; 8. 在 `OnDrawGizmosSelected` 方法中,在编辑器中显示攻击范围。 需要注意的是,该代码仅为示例代码,实际使用中可能需要根据具体需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值