Menu
CoddyTech

Anh hùng

Thuộc phần Kiến trúc trò chơi trong Unity trong hành trình Unity của Coddy. Bài học 50 trên 64.

Chương này xây dựng Arena Survivor, trò chơi cuối cùng của khóa học. Các đợt kẻ địch tràn vào đấu trường có tường bao, và bạn chống trả chúng bằng kiếm: những tên lính chân đất đi về phía bạn, cùng những quả cầu chờ đợi rồi lao tới. Khi đánh bại một đợt, bạn sẽ nhận được vật phẩm tăng sức mạnh; HUD hiển thị máu và đợt hiện tại. Khi hết máu, lượt chơi kết thúc và lượt chơi tốt nhất của bạn được lưu lại. Trò chơi sử dụng các công cụ trong phần này khi phù hợp: một lớp trừu tượng Enemy với hai loại, IDamageable dành cho mọi thứ có thể bị gây sát thương, các đợt được lưu dưới dạng tài nguyên ScriptableObject, vật phẩm nhặt được thông qua một interface, HUD chỉ lắng nghe các sự kiện C#, cùng một bản lưu JSON. Cả mười hai script đều có sẵn trong dự án ngay từ đầu. Hiện tại chỉ có Hero hoạt động; các script còn lại chứa những trường mà bài học sau cần, và mỗi bài học sẽ bổ sung thêm.

Kiếm của anh hùng đánh trúng những gì ở phía trước, nên cú vung kiếm cần kiểm tra xem mục tiêu có ở “phía trước” hay không. Vector3.Dot(a, b) nhân các thành phần tương ứng của hai vector rồi cộng chúng lại: a.x * b.x + a.y * b.y + a.z * b.z. Với hai hướng có độ dài 1, kết quả là cosin của góc giữa chúng: 1 khi chúng cùng hướng, 0.866 ở góc 30 độ, 0.707 ở góc 45 độ, 0 ở góc vuông và -1 khi chúng ngược hướng. Vì vậy, “nằm trong phạm vi 30 độ so với hướng đang nhìn” tương đương với “tích vô hướng ít nhất là 0.866”, không cần tính góc. Robot gác này sẽ phát báo động khi người chơi ở trong phạm vi 10 đơn vị và nằm trong tầm nhìn 30 độ của nó:

public Transform player;
public float range = 10f;

void Update()
{
    Vector3 toPlayer = player.position - transform.position;
    toPlayer.y = 0f;
    bool near = toPlayer.magnitude <= range;
    bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
    if (near && inView)
    {
        Debug.Log("Alarm!");
    }
}

Cả hai hướng đều cần có độ dài 1. transform.forward luôn có độ dài đó, nhưng toPlayer dài bằng khoảng cách, nên cần chuẩn hóa trước; nếu không, người chơi ở xa gấp đôi sẽ tạo ra tích vô hướng lớn gấp đôi. Thành phần y được đặt thành 0 trước bước đó, nên vị trí của người chơi trên lối đi cao hơn được tính theo vị trí trên bản đồ, không phải theo độ cao. Dấu của kết quả cho biết mục tiêu ở phía trước hay phía sau: tích vô hướng lớn hơn 0 nghĩa là ở bất kỳ vị trí nào trong nửa phía trước, nhỏ hơn 0 nghĩa là ở bất kỳ vị trí nào phía sau.

challenge icon

Thử thách

Trung bình

Trang bị cho nhân vật chính một thanh kiếm.

  1. Trong Update(), khi nhấn phím cách hoặc nhấp chuột trái và Time.time >= nextSwing, hãy đặt nextSwing = Time.time + swingCooldown; rồi gọi Swing().
  2. Trong Swing(), lặp qua Physics.OverlapSphere(transform.position, reach).
  3. Phá hủy từng mục tiêu trúng đòn có thẻ Enemy mà hướng nằm ngang toHit (đặt y thành 0) cho kết quả Vector3.Dot(transform.forward, toHit.normalized) >= 0.5f.

Nhấn Chơi rồi vung kiếm: các hình nộm phía trước sẽ biến mất. Sau đó nhấn Kiểm tra.

Tự làm thử

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;

public class Hero : MonoBehaviour
{
    [SerializeField] private float speed = 5f;
    [SerializeField] private float reach = 2f;
    [SerializeField] private float swingCooldown = 0.5f;
    [SerializeField] private GameObject slash;

    private CharacterController controller;
    private InputAction moveAction;
    private float nextSwing;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        moveAction = InputSystem.actions.FindAction("Move");
    }

    void Update()
    {
        Vector2 input = moveAction.ReadValue<Vector2>();
        Vector3 move = new Vector3(input.x, 0f, input.y);
        controller.SimpleMove(move * speed);
        if (move != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(move);
        }

        // Nhấn Space hoặc nhấp chuột trái để vung vũ khí, tối đa một lần mỗi swingCooldown giây
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // Tiêu diệt mọi đối tượng mang thẻ Enemy trong tầm với và ở phía trước nhân vật
    }

    IEnumerator ShowSlash()
    {
        slash.SetActive(true);
        yield return new WaitForSeconds(0.1f);
        slash.SetActive(false);
    }
}

Tất cả bài học trong Kiến trúc trò chơi trong Unity

Tự luyện tập: Playground Unity