U
15 / 22 · 11 phút
Hỏi đáp

Xoay vật: góc Euler, Quaternion, và cái nào bạn được đụng vào

câu trả lời ngắn

Làm sao xoay một vật, và Quaternion là gì?

Dùng Quaternion.Euler(x, y, z) khi bạn biết góc mình muốn, và Quaternion.LookRotation kèm RotateTowards hoặc Slerp để quay dần về phía một vật nào đó. Đừng bao giờ cộng thẳng vào transform.rotation: nó chứa bốn con số không phải là góc, ghi góc vào đó chỉ ra kết quả vô nghĩa.

Vị trí và tỉ lệ là ba con số bạn cộng vào được. Góc xoay trông cũng như ba con số trong Inspector nhưng không phải, và người mới nào cũng phát hiện ra điều đó theo cùng một cách: chép transform.rotation ra một biến, cộng 1 vào y, ghi ngược lại, rồi nhìn vật thể làm một chuyện không tả nổi. (C# thậm chí không cho viết transform.rotation.y += 1 trên một dòng — property đó chỉ trả về một bản sao.)

Thứ Inspector hiện ra không phải thứ được lưu

Unity lưu góc xoay dưới dạng Quaternion: bốn con số x, y, z và w, gộp lại mô tả một trục và một lượng quay quanh trục đó. Không con số nào trong bốn cái là góc tính bằng độ. Inspector đổi chúng thành ba góc Euler dễ nhìn để hiện ra, và đổi ngược lại khi bạn gõ vào.

Đặt một góc xoay bạn đã biết

Quaternion.Euler — degrees in, Quaternion out
// Quay 90° sang phải, không ngẩng, không nghiêng.
transform.rotation = Quaternion.Euler(0f, 90f, 0f);

// Xoay cục bộ: tính theo vật cha, không theo thế giới.
transform.localRotation = Quaternion.Euler(0f, 90f, 0f);

// Cộng thêm 90° vào góc hiện tại. Quaternion nhân với nhau, không cộng.
transform.rotation *= Quaternion.Euler(0f, 90f, 0f);

Quay về phía thứ gì đó

Đây mới là tình huống thật sự hay gặp: kẻ địch phải nhìn về phía người chơi, tháp súng phải bám mục tiêu, nhân vật phải quay theo hướng đang đi. Nó gồm hai bước — tính ra góc muốn đạt tới, rồi mỗi khung hình tiến về phía đó một chút.

FaceTarget.cs
using UnityEngine;

public class FaceTarget : MonoBehaviour
{
    public Transform target;
    public float turnSpeed = 360f;   // độ mỗi giây

    void Update()
    {
        if (target == null) return;

        Vector3 to = target.position - transform.position;

        // Ép phẳng lại, không thì kẻ địch chúi đầu xuống nhìn nhân vật thấp hơn.
        to.y = 0f;

        // LookRotation báo lỗi khi gặp vector bằng 0: tức là khi đang đứng đúng ngay mục tiêu.
        if (to.sqrMagnitude < 0.0001f) return;

        Quaternion wanted = Quaternion.LookRotation(to);
        transform.rotation = Quaternion.RotateTowards(
            transform.rotation, wanted, turnSpeed * Time.deltaTime);
    }
}

Dùng RotateTowards hay Slerp?

  • RotateTowards quay với số độ mỗi giây cố định và có lúc tới nơi. Dùng khi tốc độ quay là một phần của thiết kế — tháp súng không được phép xoay tức thì.

  • Slerp quay chậm dần, không bao giờ tới hẳn nơi, và tốc độ phụ thuộc quãng còn lại. Dùng cho camera hoặc thứ cần cảm giác mềm chứ không máy móc.

  • Slerp viết kiểu Slerp(a, b, speed * Time.deltaTime) phụ thuộc khung hình. Cách sửa giống hệt với vị trí: 1f - Mathf.Exp(-speed * Time.deltaTime).

Cho vật quay liên tục

transform.Rotate — the one case where you do not touch rotation directly
// Quanh trục lên của chính nó: đồng xu xoay tại chỗ.
transform.Rotate(Vector3.up * 90f * Time.deltaTime, Space.Self);

// Quanh trục lên của thế giới: kiểu quay quỹ đạo, không bị ảnh hưởng bởi độ nghiêng.
transform.Rotate(Vector3.up * 90f * Time.deltaTime, Space.World);

Gimbal lock, và vì sao bạn nên tự giữ góc

Góc Euler mô tả phép xoay thành ba lần quay nối tiếp nhau. Khi lần quay ở giữa đạt 90°, lần thứ nhất và lần thứ ba rơi vào cùng một trục, và mất đi một bậc tự do. Đó là gimbal lock, và trên thực tế nó chính là thứ làm camera góc nhìn thứ nhất loạn lên khi bạn nhìn thẳng lên trời.

The pattern that avoids all of it: keep the angle yourself
using UnityEngine;

public class MouseLook : MonoBehaviour
{
    public float sensitivity = 2f;

    // Con số của riêng bạn, tính bằng độ, không bao giờ đọc ngược từ transform.
    float yaw, pitch;

    void Update()
    {
        yaw   += Input.GetAxis("Mouse X") * sensitivity;
        pitch -= Input.GetAxis("Mouse Y") * sensitivity;

        // Giới hạn góc chạy đúng vì pitch là một float thường do bạn quản lý.
        pitch = Mathf.Clamp(pitch, -80f, 80f);

        transform.rotation = Quaternion.Euler(pitch, yaw, 0f);
    }
}

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

Camera xoay góc nhìn thứ ba

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 — Camera & Điều khiển, bài 02.