Intro
ScriptableObject là một class kế thừa trực tiếp từ class Object, là nơi chứa dữ liệu của game chúng ta. Trong phạm vi bài này mình sẽ không đi sâu về lý thuyết cơ bản của ScriptableObject, mà là cách vận dụng nó sao cho hiệu quả và dùng hết khả năng của ScriptableObject vào game architecture
Để đọc được bài này mọi người nên tìm hiểu ScriptableObject trong Unity và cách dùng nó cơ bản.Học tại đây
Chúng ta có thể hiểu trách nhiệm của ScriptableObject là lưu data ,nhưng theo mình thì cách dùng ScriptableObject không chỉ đơn giản là như vậy mình thấy mọi người thường đặt nghi vấn: "Tại sao không khai báo biến thẳng vào class mà chúng ta nên xài ScritptableObject?". Như mọi người đều biết trong team chúng ta luôn có Game Designer và làm sao để GD có thể tham gia vào quá trình config game của chúng ta như chỉnh sửa thông số máu của nhân vật qua mỗi level, damage của boss, exp lên level,.. Và chúng ta có ScripTableObject để thực hiện điều này.
Làm việc hiệu quả hơn với Game Designer
Tiếp tục vấn đề trên, bạn sẽ tiếp tục đặt ra câu hỏi: "Thằng GD có thể config trực tiếp trong prefab mà tại sao là dùng ScriptableObject?". Khi một team làm việc với designer, designer muốn chỉnh sửa thông số của enemy, player hay map thường sẽ phải vào file prefab và chỉnh => mỗi object lại phải tìm 1 file trong prefab. Trong khi đó 1 prefab có thể đặt " chằn chịt " các con số public dẫn đến rổi rắm và hiệu suất công việc giảm mạnh.
Đó là lý do mình khi làm việc với Game Deisnger nên dùng ScriptableObject nhé. Thằng nào còn thắc mắc đấm vỡ mồm.
Reduce Project's memory
Trong docs của Unity đã rất rõ ràng điều này, nhưng mình sẽ lấy ví dụ cho mấy bạn hiểu. Nôm na là một Enemy Object có 10 thông số float trong nó và trong game sẽ có rất nhiều loại Enemy giống nó, giả sử đó là 5 enemy => game sẽ cần bộ nhớ để lưu trữ 5*10 float field đó. Thay vào đó ta sẽ dùng ScriptTableObject là 1 file config duy nhất, định nghĩa các field có thể có và tất cả enemy sẽ tham chiều tới file Config đó và lấy ra các thuộc tinh.
Điều này rất hữu dụng khi dùng một số dữ liệu không thay đổi trong game.
Giảm dependency
Theo dõi đoạn code sau:
Ta có Player với chỉ số HP = 100 khi bắt đầu game
public class Player : MonoBehaviour { public float HP; private void Start() { HP = 100; } }
và PlayerUI sẽ display chỉ số HP đó lên màn hình cho người chơi dưới dạng thanh cuộn ngang
public class PlayerUI : MonoBehaviour { public Player player; private void Update() { transform.localScale = new Vector3((player.HP / 100),transform.localScale.y,transform.localScale.z); } }
Lưu ý: đây là cách làm không tốt, mình chỉ demo nhanh cho các bạn xem và cách làm tốt hơn là dụng Event ở một bài của mình Tại đây , đó cũng chính là giải pháp tối ưu cho bài toán này, tuy nhiên trong khuôn khổ bài này mình sẽ chỉ nói về ScriptableObject
Ví dụ: Khi một player bị tấn công bơi enemy khiến player bị giảm HP -> HP UI sẽ tham chiếu đến HP Player và display nó khi có sự thay đổi.Tiếp tục, khi máu của player dưới 20% thì Audio sẽ thay đổ nhịp độ -> tiếp tục khiến Audio phải tham chiều đến Player tạo ra nhiều sự phụ thuộc cứng.Tồi tệ hơn khi có nhiều EnemyAI tham chiều tới Player để xác định và tấn công
Ở ví dụ trên ta thấy ở mỗi Component ta đều tham chiếu đến Player dẫn đến 1 só trường hợp sau
- Disable Player, hư cả game
- Thay đổi nhẹ cấu hình trong Player, hư cả game
- Chúng ta tham chiếu đến Player nhưng không dùng hết sức mạnh của Player Component
Vậy giải pháp là gì?
Như những gì chúng ta đã phân tích thì ScriptableObject là một object lưu trữ dữ liệu độc lập với code base. Vậy thì chỉ cần các object khác như UI , Audio, EnemyAI khác tham chiếu đến ScriptableObject này là được chả việc gì chúng ta phải reference cứng Player cả, ngoài ra ScriptableObject một khi đã khởi tạo thì luôn tồn tại => không sợ break game.
Vậy chúng ta sẽ sửa code bên trên 1 tí lại như sau
[CreateAssetMenu(fileName ="PlayerConfig" , menuName = "GameConfiguration/PlayerConfig",order =1)] public class PlayerConfig : ScriptableObject { public float HP; }
Tại những nơi chúng ta cần tham chiếu đến HP, khái báo public PlayerConfig ví dụ như PlayerUI
public class PlayerUI : MonoBehaviour { public PlayerConfig player; private void Update() { transform.localScale = new Vector3((player.HP / 100),transform.localScale.y,transform.localScale.z); } }
Như vậy, code của chúng ta đã hết bị tightly coupling và chuyển về loose coupling



0 nhận xét:
Đăng nhận xét