练习:一场你可能输的战斗
Assets/
Scripts/
everything from tab 01–02
你已经有的这一课新增
继续待在 Arena.unity 里,把本 tab 的所有东西同时跑起来:Knight 会移动带动画,三个技能有冷却,三个 Dummy 有血条,两件神器 —— Mage 和 Archer 也还在场上,用 2 和 3 键切换。
复用的东西
Tab 02 的所有东西,在同一个场景里同时运行。
分四遍来搭
第 1 遍 —— 角色先动起来
Knight 身上已经有本 tab 第 01 课给的模型、CharacterController 和 CameraTarget。去 Assets/Prefabs 确认它还是个 prefab。
把 CharacterController 调到和身体相称,PlayerMovement 要有走 / 跑 / 跳和重力。
Animator 里放一个由 Speed 驱动的 Locomotion blend tree,再加上 Jump 和 Attack。
给 Knight 挂上 CharacterStats:maxHealth 200、attack 25、defense 8、moveSpeed 5。Mage 和 Archer 保留 SimpleMover、不加属性 —— 它们现在只是背景板。
第 2 遍 —— 三个手感完全不同的技能
三个 ScriptableObject 资源,三种真正不同的活。要是三个都只是在球形范围内砸伤害,那你其实只有一个技能、三张名字。
Skill_Slash Q cooldown 1.5 mana 5 damage 30 range 3 melee sphere in front
Skill_Bolt E cooldown 4.0 mana 20 damage 55 projectile prefab
Skill_Nova R cooldown 12 mana 45 damage 90 range 6 hits everything around// in SkillData.cs
public enum SkillShape { MeleeArc, Projectile, Nova }
public SkillShape shape = SkillShape.MeleeArc;
// in SkillCaster.TryCast, replace the single ApplyDamage call
switch (s.shape)
{
case SkillShape.MeleeArc:
HitSphere(transform.position + transform.forward * s.range * 0.5f, s.range, s);
break;
case SkillShape.Projectile:
GameObject go = Instantiate(s.projectilePrefab, castPoint.position, castPoint.rotation);
go.GetComponent<Projectile>().Launch(gameObject, s.damage);
break;
case SkillShape.Nova:
HitSphere(transform.position, s.range, s);
break;
}
void HitSphere(Vector3 center, float radius, SkillData s)
{
foreach (Collider hit in Physics.OverlapSphere(center, radius, enemyLayers))
if (hit.TryGetComponent(out CharacterStats target))
target.TakeDamage(s.damage);
}第 3 遍 —— 让 Dummy 稍微还还手
三个 Dummy 放在 Enemy 层,每个都有 CharacterStats、世界空间血条和 HitFlash。
给它们不同的 maxHealth:80、120、200 —— 好让你对伤害数字有实感。
死后把 Dummy 关掉,3 秒后重生,让你能接着测。
using System.Collections;
using UnityEngine;
public class Respawner : MonoBehaviour
{
[SerializeField] private CharacterStats stats;
[SerializeField] private float delay = 3f;
[SerializeField] private GameObject[] toHide;
void OnEnable() => stats.OnDied += HandleDeath;
void OnDisable() => stats.OnDied -= HandleDeath;
void HandleDeath() => StartCoroutine(Revive());
IEnumerator Revive()
{
foreach (GameObject g in toHide) g.SetActive(false);
yield return new WaitForSeconds(delay);
stats.currentHealth = stats.finalMaxHp;
foreach (GameObject g in toHide) g.SetActive(true);
}
}第 4 遍 —— 两件见效看得见的神器
Artifact_Ember:Attack 直接 +40。Artifact_Swift:MoveSpeed +25%、Attack +15%。
把 7 和 8 键绑到 Equip / Unequip,玩的时候也能随时切换。
技能必须读 finalAttack 而不是基础的 attack —— 否则装备了也白装。
// in SkillCaster
float outgoing = s.damage * (myStats.finalAttack / 100f + 1f);
target.TakeDamage(outgoing);何时算完成
- ✓Knight 会走、按 Shift 会跑、会跳,动画和它正在做的事对得上 —— 而且 2、3 键仍然能把控制权交给 Mage 和 Archer。
- ✓Q、E、R 做的三件事一眼可辨,每个在 UI 上都有自己的冷却圈。
- ✓蓝不够或正在冷却时施法会被拒绝 —— 玩家也能看出原因。
- ✓每次命中都有伤害数字、白色闪光,血条往下掉。
- ✓血归 0 的 Dummy 会消失,3 秒后满血回来。
- ✓装备神器会让 Inspector 里的 finalAttack 变化,屏幕上的伤害数字也跟着变。
- ✓卸下来后每个数字都精确回到原样 —— 不会越用越偏。
- ✓把每个键狂按两分钟后,Console 里没有报错。
需要达标的数字
| 指标 | 目标值 | 原因 |
|---|---|---|
| 击杀 120 HP 的 Dummy 用时 | 4–8 s | 低于 4 秒技能显得没意义,超过 8 秒就拖沓了。 |
| 输入 → 看得见的反馈 | < 100 ms | 再慢玩家就会以为按钮坏了。 |
| 神器对伤害的改变幅度 | ≥ 20% | 看不出来的加成算不上奖励。 |
| 每次施法产生的 GC 分配 | 0 B | 子弹和伤害数字都要走对象池,再用 Profiler 检查。 |
想再加练的话
给一个 Dummy 加 Chase 状态:你靠近到 8 m 内,它就朝你走来。
加第四个回血而非伤害的技能 —— 复用同一个 SkillData,把伤害设成负数。
让 Nova 用 AddExplosionForce 把 Dummy 击退。
鼠标悬停在神器图标上时,显示 BuildTooltip 生成的 tooltip。