文章目录
- 先看本次实现的最终效果
- 前言
- 绘制炮塔UI
- 炮塔转向敌人
- 生成炮弹
- 旋转我们的子弹
- 对敌人造成伤害,回收子弹
- 自动发射子弹
- 添加攻击间隔
- 显示伤害字体
- 设计通用泛型单例
- 创建更多炮塔
- 升级增加伤害
- 升级缩短攻击间隔
- 添加货币
- 杀死敌人获取金币
- 源码
- 完结
先看本次实现的最终效果
前言
本期紧接着上一篇,主要内容主要是实现不同炮塔、攻击行为逻辑和伤害飘字效果。
绘制炮塔UI
新增炮塔脚本,配置炮塔攻击范围
[RequireComponent(typeof(CircleCollider2D))]
public class Turret : MonoBehaviour
{
[SerializeField] private float attackRange = 3f; // 视为攻击范围
private bool _gameStarted; // 游戏是否已经开始
private void Start()
{
_gameStarted = true; // 设置游戏为已开始状态
}
private void OnDrawGizmos()
{
if (!_gameStarted)
{
// 在Scene视图中绘制攻击范围的可视化表示
GetComponent<CircleCollider2D>().radius = attackRange; // 设置CircleCollider2D的半径为攻击范围
Gizmos.DrawWireSphere(transform.position, attackRange); // 在指定位置绘制表示攻击范围的线框球体
}
}
}
炮塔转向敌人
修改Turret
public class Turret : MonoBehaviour
{
//...
public Enemy CurrentEnemyTarget { get; set; }
private List<Enemy> _enemies;
private void Start()
{
_gameStarted = true; // 设置游戏为已开始状态
_enemies = new List<Enemy>();
}
private void Update()
{
GetCurrentEnemyTarget();
RotateTowardsTarget();
}
//获取当前敌人目标
private void GetCurrentEnemyTarget()
{
if (_enemies.Count <= 0)
{
CurrentEnemyTarget = null;
return;
}
CurrentEnemyTarget = _enemies[0];
}
//面向敌人
private void RotateTowardsTarget()
{
if (CurrentEnemyTarget == null) return;
Vector3 targetPos = CurrentEnemyTarget.transform.position - transform.position;
float angle = Vector3.SignedAngle(transform.up, targetPos, transform.forward);
transform.Rotate(0f, 0f, angle);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Enemy"))
{
Enemy newEnemy = other.GetComponent<Enemy>();
_enemies.Add(newEnemy);
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Enemy"))
{
Enemy enemy = other.GetComponent<Enemy>();
if (_enemies.Contains(enemy))
{
_enemies.Remove(enemy);
}
};
}
//...
}
记得修改敌人标签,添加刚体和碰撞体
效果,进入范围获取了目标,敌人超出范围,不再跟随
生成炮弹
新增Projectile,子弹控制脚本
public class Projectile : MonoBehaviour
{
[SerializeField, Header("子弹移动速度")]
private float moveSpeed = 10f;
private Enemy _enemyTarget; // 目标敌人
private void Update()
{
if (_enemyTarget != null)
{
MoveProjectile();
}
}
private void MoveProjectile()
{
// 将子弹向目标位置移动
transform.position = Vector2.MoveTowards(transform.position,
_enemyTarget.transform.position,
moveSpeed * Time.deltaTime);
}
public void SetEnemy(Enemy enemy)
{
// 设置目标敌人
_enemyTarget = enemy;
}
}
制作子弹预制体,并挂载Projectile 脚本
新增TurretProjectile,控制子弹生成脚本
public class TurretProjectile : MonoBehaviour
{
[SerializeField] private Transform projectileSpawnPosition; // 发射子弹的位置
private ObjectPooler pooler; // 对象池管理器
private Turret _turret;// 炮塔组件
private Projectile _currentProjectileLoaded;// 当前装载的子弹
private void Start()
{
pooler = GetComponent<ObjectPooler>(); // 获取对象池管理器组件
_turret = GetComponent<Turret>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.G))
{
LoadProjectile(); // 测试按下G键时加载子弹
}
// 如果有敌人目标、已加载子弹且敌人目标的健康值大于0
if (_turret.CurrentEnemyTarget != null && _currentProjectileLoaded != null && _turret.CurrentEnemyTarget._enemyHealth.CurrentHealth > 0f)
{
// 解除子弹的父节点关系
_currentProjectileLoaded.transform.parent = null;
// 设置子弹的目标敌人
_currentProjectileLoaded.SetEnemy(_turret.CurrentEnemyTarget);
}
}
private void LoadProjectile()
{
GameObject newInstance = pooler.GetInstanceFromPool(); // 从对象池获取一个新的子弹实例
newInstance.transform.localPosition = projectileSpawnPosition.position; // 设置子弹的位置为发射位置
newInstance.transform.SetParent(projectileSpawnPosition); // 设置子弹的父节点为发射位置
_currentProjectileLoaded = newInstance.GetComponent<Projectile>();// 获取子弹组件
newInstance.SetActive(true); // 激活子弹
}
}
挂载脚本,并配置参数
效果
旋转我们的子弹
修改Projectile
private void Update()
{
if (_enemyTarget != null)
{
MoveProjectile();
RotateProjectile();
}
}
//控制子弹自动旋转
private void RotateProjectile()
{
Vector3 enemyPos = _enemyTarget.transform.position - transform.position; // 计算目标敌人相对于子弹的位置向量
float angle = Vector3.SignedAngle(transform.up, enemyPos, transform.forward); // 计算旋转角度
transform.Rotate(0f, 0f, angle); // 绕Z轴旋转子弹
}
效果
对敌人造成伤害,回收子弹
修改Projectile
[SerializeField, Header("子弹造成的伤害值")]
private float damage = 2f;
[SerializeField, Header("造成伤害的距离")]
private float minDistanceToDealDamage = 0.1f;
private void MoveProjectile()
{
// 将子弹向目标位置移动
//...
// 计算子弹与目标的距离
float distanceToTarget = (_enemyTarget.transform.position - transform.position).magnitude;
// 如果子弹与目标的距离小于最小距离,造成伤害并回收子弹
if (distanceToTarget < minDistanceToDealDamage)
{
_enemyTarget._enemyHealth.DealDamage(damage);
ObjectPooler.ReturnToPool(gameObject);
}
}
效果
自动发射子弹
修改Projectile
public TurretProjectile Turretowner { get; set; }
private void MoveProjectile()
{
// ...
// 如果子弹与目标的距离小于最小距离,造成伤害并回收子弹
if (distanceToTarget < minDistanceToDealDamage)
{
Turretowner.ResetTurretProjectile(); //重置装载的子弹
//...
}
}
修改TurretProjectile
private void Update()
{
if(IsTurretEmpty() && _turret.CurrentEnemyTarget != null){
LoadProjectile();
}
//...
}
//加载子弹
private void LoadProjectile()
{
//...
_currentProjectileLoaded.Turretowner = this;
}
//判断是否没有装载子弹
private bool IsTurretEmpty()
{
return _currentProjectileLoaded == null;
}
//重置装载的子弹
public void ResetTurretProjectile()
{
_currentProjectileLoaded = null;
}
添加攻击间隔
修改TurretProjectile
[SerializeField, Header("攻击间隔")]
private float delayBtwAttacks = 2f;//攻击间隔
private float nextAttackTime;//下一次攻击时间
private void Update()
{
if (Time.time > nextAttackTime)
{
// 如果有敌人目标、已加载子弹且敌人目标的健康值大于0
if (_turret.CurrentEnemyTarget != null && _turret.CurrentEnemyTarget._enemyHealth.CurrentHealth > 0f)
{
LoadProjectile();
// 解除子弹的父节点关系
_currentProjectileLoaded.transform.parent = null;
// 设置子弹的目标敌人
_currentProjectileLoaded.SetEnemy(_turret.CurrentEnemyTarget);
}
nextAttackTime = Time.time + delayBtwAttacks;
}
//加载子弹
private void LoadProjectile()
{
//。。。
_currentProjectileLoaded.ResetProjectile();//重置炮弹
}
}
修改Projectile
//重置炮弹
public void ResetProjectile()
{
_enemyTarget = null;
transform.localRotation = Quaternion.identity;
}
效果,每两秒发动一次攻击
显示伤害字体
绘制字体UI
配置动画,这个动画就大家自由发挥吧,怎么放大缩小或者上飘
新增脚本DamageText
public class DamageText : MonoBehaviour
{
public TextMeshPro DmgText => GetComponent<TextMeshPro>();
// 回收飘字
public void ReturnTextToPool(){
transform.SetParent(null);
ObjectPooler.ReturnToPool(gameObject);
}
}
挂载脚本到字体,动画完成时添加事件
新增DamageTextManager
public class DamageTextManager : MonoBehaviour
{
public ObjectPooler Pooler { get; set; }
public DamageText damageText;//飘字特效预制体
public static DamageTextManager Instance;
void Awake()
{
Instance = this;
}
private void Start()
{
Pooler = GetComponent<ObjectPooler>();
}
}
挂载脚本,配置参数
修改Projectile,添加敌人受伤委托
public static Action<Enemy, float> OnEnemyHit;//敌人受伤委托
private void MoveProjectile()
{
// 。。。
// 如果子弹与目标的距离小于最小距离,造成伤害并回收子弹
if (distanceToTarget < minDistanceToDealDamage)
{
OnEnemyHit?.Invoke(_enemyTarget, damage);
//。。。
}
}
新增EnemyFX
public class EnemyFX : MonoBehaviour
{
[SerializeField] private Transform textDamageSpawnPosition; // 伤害数字显示位置
private Enemy _enemy; // 敌人对象
private void Awake()
{
_enemy = GetComponent<Enemy>(); // 获取敌人组件
}
// 敌人受到攻击时的处理方法
private void EnemyHit(Enemy enemy, float damage)
{
if (_enemy == enemy) // 判断是否为当前敌人
{
GameObject newInstance = DamageTextManager.Instance.Pooler.GetInstanceFromPool(); // 从对象池中获取伤害数字的实例
TextMeshPro damageText = newInstance.GetComponent<DamageText>().DmgText; // 获取伤害数字的Text组件
damageText.text = damage.ToString(); // 设置显示的伤害数值
newInstance.transform.SetParent(textDamageSpawnPosition); // 设置父物体为伤害数字显示位置
newInstance.transform.position = textDamageSpawnPosition.position; // 设置显示位置
newInstance.SetActive(true); // 激活显示
}
}
private void OnEnable()
{
Projectile.OnEnemyHit += EnemyHit; // 注册敌人被攻击事件的监听
}
private void OnDisable()
{
Projectile.OnEnemyHit -= EnemyHit; // 取消注册敌人被攻击事件的监听
}
}
挂载脚本
效果
设计通用泛型单例
新增泛型单例
using UnityEngine;
//简单的泛型单例
public class Singleton<T> : MonoBehaviour where T : Component
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
GameObject newInstance = new GameObject();
instance = newInstance.AddComponent<T>();
}
}
return instance;
}
}
public void Awake()
{
instance = this as T;
}
}
修改DamageTextManager ,简化代码
public class DamageTextManager : Singleton<DamageTextManager>
{
public ObjectPooler Pooler { get; set; }
public DamageText damageText;//飘字特效预制体
private void Start()
{
Pooler = GetComponent<ObjectPooler>();
}
}
创建更多炮塔
效果
升级增加伤害
修改TurretProjectile
[SerializeField, Header("造成的伤害值")]
protected float damage = 2f;
public float Damge { get; set; }
private void Start()
{
//。。。
Damge = damage;
}
//加载子弹
protected virtual void LoadProjectile()
{
//。。。
_currentProjectileLoaded.damage = Damge;
}
新增TurretUpgrade
public class TurretUpgrade : MonoBehaviour
{
[SerializeField]
private int upgradeInitialCost; // 升级初始费用
[SerializeField]
private int upgradeCostIncremental; // 升级费用递增值
[SerializeField]
private float damageIncremental; // 伤害递增值
private TurretProjectile _turretProjectile;
private void Start()
{
_turretProjectile = GetComponent<TurretProjectile>(); // 获取 TurretProjectile 组件
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
UpgradeTurret(); // 测试升级炮塔
}
}
private void UpgradeTurret()
{
_turretProjectile.Damge += damageIncremental; // 增加炮塔的伤害值
}
}
配置参数测试
效果,按一次D,伤害增加2
升级缩短攻击间隔
修改TurretProjectile
public float DelayPerShot { get; set; }
DelayPerShot = delayBtwAttacks;
protected virtual void Update()
{
if (Time.time > _nextAttackTime)
{
// 。。。
_nextAttackTime = Time.time + DelayPerShot;
}
}
修改TurretUpgrade
[SerializeField, Header("攻击间隔递减值")]
private float delayReduce;
private void UpgradeTurret()
{
_turretProjectile.Damge += damageIncremental; // 增加炮塔的伤害值
_turretProjectile.DelayPerShot -= delayReduce;// 减少炮塔的攻击间隔
}
测试配置
效果
添加货币
添加
public class CurrencySystem : Singleton<CurrencySystem>
{
[SerializeField]
private int coinTest; // 测试用的初始金币数量
private string CURRENCY_SAVE_KEY = "MYGAME_CURRENCY";
public int TotalCoins { get; set; } // 总金币数量
private void Start()
{
PlayerPrefs.DeleteKey(CURRENCY_SAVE_KEY);//开始清空金币数据
LoadCoins(); // 加载金币数量
}
private void LoadCoins()
{
TotalCoins = PlayerPrefs.GetInt(CURRENCY_SAVE_KEY, coinTest); // 从 PlayerPrefs 中读取金币数量,默认为 coinTest 的值
}
public void AddCoins(int amount)
{
TotalCoins += amount; // 增加金币数量
PlayerPrefs.SetInt(CURRENCY_SAVE_KEY, TotalCoins); // 将金币数量保存到 PlayerPrefs
PlayerPrefs.Save(); // 保存 PlayerPrefs 的修改
}
public void RemoveCoins(int amount)
{
if (TotalCoins >= amount)
{
TotalCoins -= amount; // 减少金币数量
PlayerPrefs.SetInt(CURRENCY_SAVE_KEY, TotalCoins); // 将金币数量保存到 PlayerPrefs
PlayerPrefs.Save(); // 保存 PlayerPrefs 的修改
}
}
}
修改TurretUpgrade
public int UpgradeCost { get; set; }
private void Start()
{
//。。。
UpgradeCost = upgradeInitialCost;
}
private void UpgradeTurret()
{
if (CurrencySystem.Instance.TotalCoins >= UpgradeCost)
{
_turretProjectile.Damge += damageIncremental; // 增加炮塔的伤害值
_turretProjectile.DelayPerShot -= delayReduce;// 减少炮塔的攻击间隔
UpdateUpgrade();
}
}
private void UpdateUpgrade()
{
CurrencySystem.Instance.RemoveCoins(UpgradeCost);//扣除金额
UpgradeCost += upgradeCostIncremental;//加下次升级金额
}
配置,升级消耗10,每次升级消耗+20
效果,升级扣除了金额
杀死敌人获取金币
修改CurrencySystem,每杀一个敌人加一金币
private void AddCoins(Enemy enemy)
{
AddCoins(1);
}
private void OnEnable()
{
EnemyHealth.OnEnemyKilled += AddCoins;
}
private void OnDisable()
{
EnemyHealth.OnEnemyKilled -= AddCoins;
}
效果,杀死敌人金额增加
源码
见本项目最后一节
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~