U
tab 02Nhân vật & Chiến đấu9 bài — chạm để mở
02 / 09 · 12 phút
Nhân vật & Chiến đấu

Di chuyển, nhảy và đáp lên khối

dự án của bạn tới lúc này

Assets/

Scenes/

Arena.unity

KnightCharacterController

Scripts/

CharacterStats.csCharacterSwitcher.csSimpleMover.cs (on Knight)→PlayerMovement.cs

đã có sẵnbài này thêmbài này thay thế

SimpleMover ở tab 01 đã cho Knight đi được, nhưng nó trượt xuyên tường, không có trọng lực, và luôn đi theo trục của thế giới. Bài này thay thế nó — chỉ trên Knight.

Mage và Archer vẫn giữ SimpleMover. Đó chính là chủ ý: học xong bài này, bấm phím 1 rồi phím 2 là cảm nhận được ngay khác biệt giữa hai kiểu di chuyển, trong cùng một cảnh.

Di chuyển luôn là ba bước giống nhau: đọc phím, đổi thành hướng, rồi áp dụng kèm Time.deltaTime.

sơ đồ động

Phím thành Vector3, vector thành mét trên giây.

Script di chuyển đầy đủ

gỡ SimpleMover khỏi Knight trước
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{
    [Header("di chuyển")]
    [SerializeField] private float walkSpeed = 4f;
    [SerializeField] private float runSpeed = 8f;
    [SerializeField] private float turnSmooth = 0.08f;

    [Header("nhảy và trọng lực")]
    [SerializeField] private float jumpHeight = 1.4f;
    [SerializeField] private float gravity = -20f;

    private CharacterController cc;
    private Camera cam;
    private float verticalVelocity;
    private float turnVelocity;

    void Awake()
    {
        cc = GetComponent<CharacterController>();
        cam = Camera.main;
    }

    void Update()
    {
        //đọc phím ----
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        Vector3 input = new Vector3(h, 0f, v).normalized;

        //di chuyển theo hướng camera ----
        Vector3 move = Vector3.zero;
        if (input.sqrMagnitude > 0.01f)
        {
            float targetAngle = Mathf.Atan2(input.x, input.z) * Mathf.Rad2Deg
                              + cam.transform.eulerAngles.y;

            float angle = Mathf.SmoothDampAngle(
                transform.eulerAngles.y, targetAngle, ref turnVelocity, turnSmooth);
            transform.rotation = Quaternion.Euler(0f, angle, 0f);

            move = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
        }

        float speed = Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed;

        //trọng lực và nhảy ----
        if (cc.isGrounded && verticalVelocity < 0f)
            verticalVelocity = -2f;                       // bám sàn

        if (cc.isGrounded && Input.GetButtonDown("Jump"))
            verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);

        verticalVelocity += gravity * Time.deltaTime;

        //gộp lại một lệnh Move duy nhất ----
        Vector3 velocity = move * speed + Vector3.up * verticalVelocity;
        cc.Move(velocity * Time.deltaTime);
    }
}

Gắn nó lên Knight

  1. 1

    Chọn Knight trong Hierarchy. Ở component SimpleMover, bấm dấu ba chấm → Remove Component. Knight sẽ dùng script mới; Mage và Archer vẫn giữ cái cũ.

  2. 2

    Trong Project, ở Assets/Scripts: Assets▸Create▸C# Script, đặt tên PlayerMovement — tên file phải trùng y hệt tên class, không thì Unity không cho gắn.

  3. 3

    Mở ra, dán toàn bộ script ở trên đè lên nội dung cũ, lưu lại.

  4. 4

    Kéo PlayerMovement từ Project thả lên Knight. Một Character Controller tự hiện ra — đó là việc của [RequireComponent] ở đầu script.

  5. 5

    Chỉnh controller cho khớp model, không thì capsule lún nửa dưới sàn: Center 0, 1, 0 · Radius 0.3 · Height 2. Capsule xanh trong Scene view phải ôm vừa Knight.

Bốn ý tưởng bên trong

  • Di chuyển theo camera: bấm W nghĩa là 'đi ra xa camera', không phải 'đi về hướng +Z của thế giới'. Cộng góc Y của camera vào là điều khiển thấy đúng ở mọi góc quay.

  • SmoothDampAngle biến góc đổi đột ngột thành cú xoay mượt, và xử lý đúng chỗ nhảy từ 359° về 0°.

  • Trọng lực được cộng dồn vào verticalVelocity mỗi khung hình; đó là lý do rơi thì nhanh dần chứ không trượt đều.

  • Công thức Sqrt(h * -2 * g) cho ra 'nhảy cao đúng h mét' — nên giá trị trong Inspector tính bằng mét, chỉnh rất sướng.

isGrounded cứ nhấp nháy

  • Đặt verticalVelocity thành số âm nhỏ (-2) khi đang chạm đất để controller luôn tì xuống sàn.

  • Kiểm tra Slope Limit và Step Offset trên CharacterController — Step Offset 0.3 cho phép bước lên bậc thấp.

  • Thêm chút coyote time (rời mép 0.15 giây vẫn nhảy được). Người chơi không nhận ra, nhưng sẽ thấy game 'công bằng' hơn hẳn.

Giữ cho bộ chuyển nhân vật vẫn chạy

CharacterSwitcher đang bật tắt SimpleMover. Knight không còn SimpleMover nữa, nên danh sách phải giữ thứ gì đó mà cả hai script cùng có.

CharacterSwitcher.cs — one word changes
//trước đây
[SerializeField] private List<SimpleMover> characters = new List<SimpleMover>();

// sau khi sửa — MonoBehaviour là class cha của cả hai script
[SerializeField] private List<MonoBehaviour> characters = new List<MonoBehaviour>();

// phần còn lại giữ nguyên, vì .enabled nằm sẵn trên MonoBehaviour
characters[i].enabled = (i == index);
thêm vào Update
private float coyote;

// ...
if (cc.isGrounded) coyote = 0.15f;
else coyote -= Time.deltaTime;

if (coyote > 0f && Input.GetButtonDown("Jump"))
{
    verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
    coyote = 0f;
}