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.
Thử thách
Trung bìnhTrang bị cho nhân vật chính một thanh kiếm.
- Trong
Update(), khi nhấn phím cách hoặc nhấp chuột trái vàTime.time >= nextSwing, hãy đặtnextSwing = Time.time + swingCooldown;rồi gọiSwing(). - Trong
Swing(), lặp quaPhysics.OverlapSphere(transform.position, reach). - Phá hủy từng mục tiêu trúng đòn có thẻ
Enemymà hướng nằm ngangtoHit(đặ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
1Thành phần, không phải hệ thống phân cấp
Thành phần nhỏTập lệnh kẻ địch cơ sởThành phần trừu tượngCác thành phần phối hợp với nhauÔn tập: tháp pháo mô-đunÔn tập: pháo nòng đôi4Dữ liệu tuần tự hóa
Lớp có thể tuần tự hóaDanh sách trong InspectorKiểu liệt kê trong InspectorÔn tập: Bảng vật phẩm rơiÔn tập: Quay vật phẩm2Giao diện trong Unity
IDamageableGiao diện và TryGetComponentGetComponentsÔn tập: phím tương tácÔn tập: đối tượng gần nhất trong tầm với5ScriptableObjects
Tài sản dữ liệuChỉ số vũ khíCài đặt dùng chungLưu gióSóng dưới dạng dữ liệuÔn tập: Cơ sở dữ liệu vật phẩmÔn tập: Mua hàng từ cửa hàng8Code có thể tái sử dụng
Phương thức mở rộngSingleton tổng quátCác hàm hỗ trợ tổng quátÔn tập: FindNearest3Sự kiện
Sự kiện C# trong UnityĐăng ký trong OnEnableCác trường UnityEventSự kiện điều khiển UIÔn tập: Cửa sổ bật lên thành tíchÔn tập: Bộ hẹn giờ cửa sổ bật lên6Lưu dữ liệu bằng JSON
JsonUtilityLưu trò chơiTải dữ liệu và giá trị mặc địnhÔn tập: các ô lưuChọn và xóa một ô lưu9Tái sử dụng đối tượng bằng pool
Vì sao cần pool đối tượngPool dùng hàng đợiObjectPool của UnityÔn tập: pool đạnTự luyện tập: Playground Unity