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

创建角色

到目前为止的项目

Assets/

Scenes/

Arena.unity

KnightMageArcherCharacterControllerCameraTarget

Scripts/

SimpleMover.csCharacterSwitcher.csCharacterStats.cs

Prefabs/

Knight prefab

Models/

Knight model (FBX)

你已经有的这一课新增

打开 tab 01 的 Arena.unity。地板上站着三个 capsule —— Knight、Mage、Archer —— 用按键 1 / 2 / 3 切换。本 tab 的所有操作都在 Knight 身上。

Mage 和 Archer 全程保持 capsule。故意留着它们:每课学完按一下 2,把它们和 Knight 摆在一起,一眼就看出 Knight 进步了多少。

一个游戏角色是同一个 GameObject 上的四层:用来看的 model、用来做动画的骨架、负责选 clip 的 Animator,以及和世界碰撞的 collider。

动态图解

模型、骨架、Animator、controller —— 四样凑齐,角色才能迈步。

去哪里弄一个角色

  • Mixamo(免费,需要 Adobe 账号):选角色、选动画、下载 FBX。最快的起步方式。

  • Unity Asset Store:搜索 “free character”,再按价格 0 筛选。

  • 或者继续用 tab 01 的 Knight capsule —— 下面的每个脚本在 capsule 上同样能跑,model 以后随时替换。

要紧的导入设置

  1. 1

    把 .fbx 拖进 Assets/Models。选中它,打开 Rig tab。

  2. 2

    Animation Type → Humanoid,然后 Apply。这一步把模型的骨骼映射到 Unity 的标准骨架上。

  3. 3

    选了 Humanoid,任何 Mixamo 动画都能用在任何 Mixamo 角色上 —— clip 可以互相通用。

  4. 4

    在 Materials tab 里选 Extract Textures / Extract Materials,这样才编辑得了。

拼装角色对象

  1. 1

    在 Hierarchy 里选中 Knight。删掉 capsule 的 MeshFilter、MeshRenderer 和 Capsule Collider —— 下面的 Character Controller 会顶替 collider —— 但保留 GameObject 和它的名字。

  2. 2

    把 model 拖进来,作为 Knight 的子物体,本地位置 0, 0, 0。父物体保留自己的名字、自己的脚本,也保留在 switcher 列表里的位置。

  3. 3

    在 Knight 本体上,添加 Component▸Physics▸Character Controller。

  4. 4

    把 Height 设为 1.8、Radius 0.3、Center Y 0.9,让 capsule 正好贴住身体、脚在 Y = 0。然后把 Knight 的 Position Y 调回 0 —— 现在负责把人抬起来的是 collider,不是 transform。

  5. 5

    在 Y = 1.5 创建一个空子物体,叫 CameraTarget —— 相机以后对着它,而不是对着脚。Tab 03 会用到。

  6. 6

    最后把 Knight 拖进 Assets/Prefabs。从此它是 prefab —— Knight 作为自己的文件被保存下来,而不只是这个场景里的一个 object。

用 CharacterController 还是 Rigidbody?

✓ CharacterController

控制直接、可预测,不用跟物理引擎较劲。几乎每个第三人称游戏用的都是它。

✓ Rigidbody

真实的物理:受力、反弹、布娃娃。拿来做木箱很棒,当玩家角色就别扭了。

一个可以往上搭的数值组件

CharacterStats.cs — attach to Knight, and later to every enemy
using UnityEngine;

public class CharacterStats : MonoBehaviour
{
    [Header("Base")]
    public float maxHealth = 100f;
    public float attack = 20f;
    public float defense = 10f;
    public float moveSpeed = 5f;

    [Header("Runtime")]
    public float currentHealth;

    public bool IsAlive => currentHealth > 0f;

    void Awake()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(float rawDamage)
    {
        if (!IsAlive) return;

        float reduced = Mathf.Max(1f, rawDamage - defense);   // always at least 1
        currentHealth = Mathf.Max(0f, currentHealth - reduced);

        if (!IsAlive) Die();
    }

    void Die()
    {
        Debug.Log(name + " died");
        // animator.SetTrigger("Die");   next lesson
    }
}