U
章 02角色与战斗9 课 — 点击打开
09 / 09 · 24 分钟
角色与战斗★ 练习

练习:一场你可能输的战斗

到目前为止的项目

Assets/

Scripts/

SkillShapeRespawner.cs

everything from tab 01–02

你已经有的这一课新增

练习3–4 小时

继续待在 Arena.unity 里,把本 tab 的所有东西同时跑起来:Knight 会移动带动画,三个技能有冷却,三个 Dummy 有血条,两件神器 —— Mage 和 Archer 也还在场上,用 2 和 3 键切换。

复用的东西

CharacterController移动Animator + blend treeAnimator 与混合树ScriptableObject skillsScriptableObject 技能Cooldown + mana冷却与法力Projectile + pool弹道与对象池Equipment → final stats装备 → 最终属性Events → health bar事件 → 血条
动态图解

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 资源,三种真正不同的活。要是三个都只是在球形范围内砸伤害,那你其实只有一个技能、三张名字。

the three assets you must create
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
one extra field makes the three behave differently
// 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 秒后重生,让你能接着测。

Respawner.cs — on each dummy
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 —— 否则装备了也白装。

C# 脚本
// 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 检查。

想再加练的话

加分项 1

给一个 Dummy 加 Chase 状态:你靠近到 8 m 内,它就朝你走来。

加分项 2

加第四个回血而非伤害的技能 —— 复用同一个 SkillData,把伤害设成负数。

加分项 3

让 Nova 用 AddExplosionForce 把 Dummy 击退。

加分项 4

鼠标悬停在神器图标上时,显示 BuildTooltip 生成的 tooltip。