本章目标
设计防御塔类型以及配置方式,选择几种防御塔做样例,实现防御塔的攻击相关功能,如:锁定敌人、攻击敌人、攻击动画、攻击特效等。
防御塔策划
虽然是个 Demo 游戏,但也要有基本的策划,我们暂定防御塔可以有以下几种类别:
- 单体攻击类:箭塔,锁定单体攻击,攻击频率较快。
- 溅射攻击类:炮塔,攻击有溅射伤害。
- 超远程攻击类:导弹塔,单体锁定溅射攻击,锁定半径极大,攻击力较强,有溅射效果,攻击速度低。
- 远程群体减速类:冰锥塔,锁定单体并溅射减速效果,降低范围内敌人移动力,攻击力极低。
- 单体减速类:毒液塔,锁定单体攻击,降低单个敌人移动力,攻击力极低。
- 周围群体减速类:荆棘塔,群体锁定攻击,降低半径范围内敌人移动力,攻击力极低。
- 定身类:时间秩序塔,群体锁定攻击,限制半径范围内敌人移动,攻击力极低。
- 群体攻击类:多重箭塔,最多可同时攻击6个单位,攻击力较弱。
- 单体暴击类:剑神,基础攻击力较强,攻击产生暴击。
- 持续攻击类:电塔,针对 Boss ,每帧都造成伤害,对 Boss 伤害有加成。
- 线性攻击类:激光塔,对一条射线路径上的所有敌人造成伤害。目标有距离限制,伤害无距离限制。
- 弹射攻击类:折射炮塔,锁定单体攻击,击中后炮弹有两次弹射攻击。弹射距离有限制。
四种防御塔
我们在Demo中先做四种防御塔,分别是:
- 箭塔;
- 炮塔;
- 导弹塔;
- 冰锥塔。
因为是 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学习笔记目录整理
转载:https://blog.csdn.net/xiaoyaoACi/article/details/127672738