Sự kiện C# trong Unity
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 12 trên 64.
Khi điểm số thay đổi, có một số thành phần cần biết: dòng chữ điểm số, âm thanh, thành tích. Nếu ScoreKeeper gọi từng thành phần, nó cần một trường cho mỗi thành phần, và mỗi tính năng mới lại buộc bạn phải chỉnh sửa nó. Sự kiện đảo ngược cách làm đó: ScoreKeeper thông báo về thay đổi, còn những thành phần cần biết sẽ đăng ký nhận thông báo.
Có hai từ trong C# giúp làm việc này. Action<int> là kiểu của một phương thức nhận một int và không trả về gì. Một event có kiểu đó là danh sách các phương thức như vậy: code khác thêm phương thức của mình bằng +=, còn đối tượng sở hữu sự kiện gọi tất cả các phương thức đó bằng Invoke. Trong C# thông thường:
using System;
class Alarm
{
public event Action<int> Rang;
public void Ring(int hour)
{
Rang?.Invoke(hour);
}
}
class Sleeper
{
public void WakeUp(int hour)
{
Console.WriteLine("Up at " + hour);
}
}
// Ở chỗ khác
Alarm alarm = new Alarm();
Sleeper sleeper = new Sleeper();
alarm.Rang += sleeper.WakeUp;
alarm.Ring(7); // Dậy lúc 7 giờTrong Unity, bên phát sự kiện và các bên lắng nghe là những component, thường nằm trên các GameObject khác nhau:
using System;
using UnityEngine;
public class ScoreKeeper : MonoBehaviour
{
public event Action<int> OnScoreChanged;
private int score;
public void AddPoints(int points)
{
score += points;
OnScoreChanged?.Invoke(score);
}
}// ScoreLabel.cs, trên phần chữ của HUD
public ScoreKeeper keeper;
public TMP_Text label;
void Start()
{
keeper.OnScoreChanged += ShowScore;
}
void ShowScore(int score)
{
label.text = "Score: " + score;
}Action<int> nghĩa là mỗi bên lắng nghe là một phương thức nhận một int, trong trường hợp này là điểm số mới. Invoke gọi từng phương thức đã đăng ký, theo thứ tự đăng ký. Khi không có bên lắng nghe nào, sự kiện có giá trị null, vì vậy lời gọi thông thường như OnScoreChanged(score) sẽ gây ra NullReferenceException, còn ?. sẽ bỏ qua lời gọi đó. Từ khóa event chỉ cho phép các script khác dùng += và -=: trong ScoreLabel, keeper.OnScoreChanged(5) hoặc keeper.OnScoreChanged = null sẽ không biên dịch được.
ScoreKeeper không có trường dành cho dòng chữ điểm số và cũng không biết dòng chữ đó tồn tại. Bộ đếm combo hoặc trình phát âm thanh có thể đăng ký theo cách tương tự, còn ScoreKeeper vẫn giữ nguyên, và cảnh kiểm thử không có HUD vẫn chạy mà không gặp lỗi. Bên lắng nghe tìm bên phát sự kiện thông qua tham chiếu trong Inspector như ở đây, hoặc bằng bất kỳ cách nào bạn biết, chẳng hạn như GetComponent hoặc singleton. Việc xóa một bên lắng nghe bằng -= rất quan trọng khi các đối tượng bị hủy, nội dung này sẽ có trong bài học tiếp theo.
Thử thách
DễCho đấu sĩ thông báo chuỗi combo của nó.
- Trong
Fighter.cs, thêmusing System;vàpublic event Action<int> OnComboChanged;. - Thay các dòng dùng
comboTextvàflamebằngOnComboChanged?.Invoke(combo);, rồi xóa cả hai trường. - Trong
Start()củaComboTextvàComboFlame, viếtfighter.OnComboChanged += Show;và+= Grow;.
Nhấn Chơi rồi nhấn Space vài lần: combo tăng lên, sau đó được xóa. Tiếp theo, nhấn Kiểm tra.
Tự làm thử
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class Fighter : MonoBehaviour
{
public float comboWindow = 1f;
public TMP_Text comboText;
public Transform flame;
private int combo;
private float sinceHit;
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
combo++;
sinceHit = 0f;
comboText.text = "Combo x" + combo;
flame.localScale = Vector3.one * (0.5f + 0.25f * combo);
}
else if (combo > 0)
{
sinceHit += Time.deltaTime;
if (sinceHit > comboWindow)
{
combo = 0;
comboText.text = "";
flame.localScale = Vector3.one * 0.5f;
}
}
}
}
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