U
13 / 22 · 8 phút
Hỏi đáp

Di chuyển nhân vật: ba cách, cách nào hợp với bạn

câu trả lời ngắn

Làm sao cho nhân vật di chuyển?

Chọn đúng một trong ba, đừng dùng cùng lúc hai cách: CharacterController cho nhân vật bạn điều khiển trực tiếp, Rigidbody cho vật để vật lý đẩy đi, và transform.Translate chỉ cho vật không va chạm. Mọi tốc độ đều phải nhân Time.deltaTime, không thì máy càng mạnh, game chạy càng nhanh.

Có ba cách để làm một vật di chuyển trong Unity, và chúng không trộn được với nhau. Chọn nhầm cách chính là lý do nhân vật lún vào tường, hoặc không nhúc nhích, hoặc chạy nhanh gấp đôi trên điện thoại 120 Hz.

sơ đồ động

Cùng một bức tường, ba cách gặp. Chỉ CharacterController trượt dọc theo nó, chỉ Rigidbody đẩy được cái thùng, còn transform.Translate thì không biết là có bức tường.

Chọn một — toàn bộ quyết định nằm ở đây

  • CharacterController — nhân vật bạn trực tiếp điều khiển. Nó trượt dọc tường, leo được bậc, và không bao giờ bị vật lý xô đẩy. Phần lớn game góc nhìn thứ nhất và thứ ba dùng cái này.

  • Rigidbody — mọi thứ mà vật lý nên nắm quyền: quả bóng lăn, cái thùng bị đẩy, ragdoll. Bạn yêu cầu nó di chuyển; engine vật lý quyết định chuyện gì thật sự xảy ra.

  • transform.Translate — chỉ cho thứ không bao giờ va chạm với cái gì: vật phẩm lơ lửng, đám mây trôi, phần tử giao diện. Nó dịch chuyển tức thời, không đẩy.

CharacterController

Thêm component vào, rồi di chuyển bằng Move(). Nó nhận quãng đường của khung hình này, không phải tốc độ — đó là lý do có deltaTime trong đó.

PlayerMove.cs — attach to the object that has the CharacterController
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerMove : MonoBehaviour
{
    public float speed = 6f;
    public float gravity = -20f;

    CharacterController cc;
    float verticalSpeed;

    void Awake()
    {
        cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        // Input là một hướng, độ dài từ 0 tới 1. Không bao giờ là quãng đường.
        Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
        if (input.sqrMagnitude > 1f) input.Normalize();

        // Trọng lực phải tự áp dụng: CharacterController không chịu tác động của vật lý.
        if (cc.isGrounded && verticalSpeed < 0f) verticalSpeed = -2f;
        verticalSpeed += gravity * Time.deltaTime;

        Vector3 move = input * speed + Vector3.up * verticalSpeed;
        cc.Move(move * Time.deltaTime);
    }
}

Rigidbody

Chỉ cần nhớ hai luật, phần còn lại tự khắc theo sau: ghi vào Rigidbody chứ đừng ghi vào transform, và làm trong FixedUpdate chứ không phải Update. Vật lý chạy theo đồng hồ riêng của nó, còn ghi vào transform thì engine vật lý không hề biết — nó sẽ vui vẻ kéo vật về chỗ cũ.

BallMove.cs — attach to the object that has the Rigidbody
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class BallMove : MonoBehaviour
{
    public float speed = 8f;

    Rigidbody rb;
    Vector3 input;

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        // Đọc input ở đây: Update chạy mỗi khung hình, nên không bỏ sót phím nào.
        input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
        if (input.sqrMagnitude > 1f) input.Normalize();
    }

    void FixedUpdate()
    {
        // Di chuyển ở đây: FixedUpdate chạy theo đồng hồ vật lý.
        rb.MovePosition(rb.position + input * speed * Time.fixedDeltaTime);
    }
}

Vì sao đọc input ở Update mà dùng ở FixedUpdate

  • FixedUpdate mặc định chạy 50 lần mỗi giây; game của bạn có thể vẽ 120 khung hình mỗi giây. Nên đọc Input.GetKeyDown trong FixedUpdate là bỏ sót những lần bấm rơi vào giữa hai nhịp vật lý.

  • Trong FixedUpdate dùng Time.fixedDeltaTime, trong Update dùng Time.deltaTime. Thật ra Unity trả về đúng giá trị ở cả hai chỗ, nhưng viết đúng tên giúp người đọc hiểu ý.

  • Bật Interpolate cho Rigidbody nếu camera bám theo nó, không thì chuyển động trông như đang rung ở 50 Hz.

transform.Translate

Drifter.cs — for something with no collider, or one set to trigger
void Update()
{
    transform.Translate(Vector3.forward * speed * Time.deltaTime);
}

bài học làm ra thứ này

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

Bài này là một công thức đứng riêng. Trong khoá học, cùng thứ đó được dựng như một phần của dự án xuyên suốt năm tab — Nhân vật & Chiến đấu, bài 02.