
Tách biệt dữ liệu trò chơi và logic bằng ScriptableObjects
Trang web này đã được dịch bằng máy để thuận tiện cho bạn. Chúng tôi không thể đảm bảo tính chính xác hoặc độ tin cậy của nội dung được dịch. Nếu bạn có thắc mắc về tính chính xác của nội dung được dịch, vui lòng tham khảo phiên bản tiếng Anh chính thức của trang web.
Trang này giải thích cách sử dụng ScriptableObjects như các vùng chứa dữ liệu giúp tách biệt dữ liệu khỏi logic trong mã trò chơi của bạn.
Đây là bài thứ hai trong chuỗi sáu bài hướng dẫn ngắn được tạo ra để hỗ trợ các nhà phát triển Unity với bản demo đi kèm sách điện tử " Tạo kiến trúc trò chơi mô-đun trong Unity với ScriptableObjects" .
Bản demo này lấy cảm hứng từ cơ chế trò chơi điện tử arcade kinh điển với bóng và vợt, và cho thấy cách ScriptableObjects có thể giúp bạn tạo các thành phần có thể kiểm thử, có khả năng mở rộng và thân thiện với nhà thiết kế.
Cả sách điện tử, dự án minh họa và các hướng dẫn ngắn này cùng nhau cung cấp những phương pháp tốt nhất để sử dụng các mẫu thiết kế lập trình với lớp ScriptableObject trong dự án Unity của bạn. Những mẹo này có thể giúp bạn đơn giản hóa mã, giảm mức sử dụng bộ nhớ và thúc đẩy khả năng tái sử dụng mã.
Loạt bài này bao gồm các bài viết sau:
Lưu ý quan trọng trước khi bắt đầu
Trước khi bạn bắt đầu tìm hiểu dự án demo ScriptableObject và loạt bài hướng dẫn ngắn này, hãy nhớ rằng, về bản chất, các mẫu thiết kế chỉ là những ý tưởng. Những điều này không áp dụng cho mọi tình huống. Những kỹ thuật này có thể giúp bạn học được những cách làm việc mới với Unity và ScriptableObjects.
Mỗi kiểu mẫu đều có ưu điểm và nhược điểm riêng. Chỉ chọn những giải pháp mang lại lợi ích thiết thực cho dự án cụ thể của bạn. Các nhà thiết kế của bạn có phụ thuộc nhiều vào Unity Editor không? Một mô hình dựa trên ScriptableObject có thể là lựa chọn tốt để giúp họ cộng tác với các nhà phát triển của bạn.
Tóm lại, kiến trúc mã nguồn tốt nhất là kiến trúc phù hợp với dự án và nhóm của bạn.

Các vùng chứa dữ liệu
Các nhà phát triển phần mềm thường quan tâm đến tính mô-đun – chia nhỏ ứng dụng thành các đơn vị nhỏ hơn, độc lập. Mỗi mô-đun sẽ chịu trách nhiệm về một khía cạnh cụ thể trong chức năng của ứng dụng.
Trong Unity, ScriptableObjects có thể giúp tách biệt dữ liệu khỏi logic.
ScriptableObjects rất hiệu quả trong việc lưu trữ dữ liệu, đặc biệt là dữ liệu tĩnh. Điều này khiến chúng trở nên lý tưởng cho việc thống kê trò chơi, giá trị cấu hình cho vật phẩm hoặc NPC, hội thoại nhân vật và nhiều hơn nữa.
Việc tách biệt dữ liệu lối chơi khỏi logic hành vi có thể giúp việc kiểm thử và bảo trì từng phần độc lập của dự án trở nên dễ dàng hơn. Việc “tách biệt các mối quan tâm” này có thể giảm thiểu các tác dụng phụ không mong muốn khi bạn thực hiện những thay đổi cần thiết.

Quy trình làm việc chung
Nếu bạn muốn ôn lại quy trình làm việc với ScriptableObject, bài viết này Unity Learn có thể giúp ích. Nói tóm lại, đây là lời giải thích ngắn gọn:
Định nghĩa một ScriptableObject: Để tạo một đối tượng như vậy, hãy định nghĩa một lớp C# kế thừa từ lớp cơ sở ScriptableObject với các trường và thuộc tính cho dữ liệu bạn muốn lưu trữ. ScriptableObjects có thể lưu trữ các kiểu dữ liệu tương tự như MonoBehaviours, khiến chúng trở thành các thùng chứa dữ liệu đa năng. Thêm thuộc tính CreateAssetMenuAttribute từ trình chỉnh sửa để việc tạo tài nguyên trong dự án trở nên dễ dàng hơn.
Tạo một tài nguyên: Sau khi đã định nghĩa một lớp ScriptableObject, bạn có thể tạo một thể hiện của ScriptableObject đó trong dự án. Điều này xuất hiện dưới dạng một tài nguyên được lưu vào ổ đĩa mà bạn có thể sử dụng lại trong nhiều GameObjects và cảnh khác nhau.
Đặt giá trị: Sau khi tạo tài nguyên, hãy điền dữ liệu vào đó bằng cách thiết lập giá trị cho các trường và thuộc tính của nó trong Trình kiểm tra.
Sử dụng tài nguyên này: Khi tài tài nguyên đã chứa dữ liệu, hãy tham chiếu dữ liệu đó từ một biến hoặc trường. Mọi thay đổi được thực hiện đối với tài nguyên ScriptableObject sẽ được phản ánh trên toàn bộ dự án.
Bạn có thể tái sử dụng ScriptableObjects làm các vùng chứa dữ liệu ở nhiều phần khác nhau trong trò chơi của mình. Ví dụ, bạn có thể định nghĩa các thuộc tính của vũ khí hoặc nhân vật trong một ScriptableObject, và sau đó tham chiếu đến tài nguyên đó từ bất kỳ đâu trong dự án.
Ghi chú: Bạn cũng có thể tạo ScriptableObjects trong thời gian chạy thông qua phương thức CreateInstance . Tuy nhiên, để lưu trữ dữ liệu, bạn thường sẽ tạo trước các tài sản ScriptableObject bằng cách sử dụng CreateAssetMenuAttribute .

So sánh ScriptableObjects và MonoBehaviours
Để hiểu rõ hơn lý do tại sao ScriptableObjects là lựa chọn phù hợp hơn MonoBehaviours để lưu trữ dữ liệu, hãy so sánh các phiên bản trống của mỗi loại. Hãy đảm bảo thiết lập Serialization tài sản của bạn thành Cách thức: Văn bản bắt buộc Trong phần Cài đặt Dự án, bạn có thể xem mã YAML dưới dạng văn bản.
Tạo một GameObject mới với MonoBehaviour trống rỗng. Sau đó, hãy so sánh điều đó với một tài nguyên ScriptableObject trống. Khi đặt chúng cạnh nhau, chúng sẽ trông giống như hình so sánh ở trên.
So với MonoBehaviours, ScriptableObjects nhẹ hơn và không có những chi phí phát sinh như thành phần Transform. Điều này giúp ScriptableObjects chiếm ít bộ nhớ hơn và tối ưu hóa việc lưu trữ dữ liệu.

Bản demo các mẫu
ScriptableObjects được lưu dưới dạng tài sản, vì vậy chúng vẫn tồn tại ngay cả khi không ở chế độ Chơi, điều này có thể hữu ích. Ví dụ, dữ liệu ScriptableObject có thể truy cập được từ bất kỳ đâu, ngay cả khi bạn tải một cảnh mới.
Ví dụ minh họa Patterns có màn hình hiển thị thông tin tác giả cơ bản mà bạn có thể tự kiểm tra. Chỉnh sửa ScriptableObject Credits_Data rồi nhấn Cập nhật để xem văn bản đã lưu hiển thị.
Nếu bạn có một trò chơi nhập vai (RPG) với lượng hội thoại khổng lồ hoặc một cảnh hướng dẫn với kịch bản được soạn sẵn, đây là một cách phổ biến để lưu trữ nhiều dữ liệu.
Mặc dù dữ liệu trong ScriptableObject được cập nhật ngay lập tức khi được sửa đổi, dự án của chúng tôi yêu cầu một nút Cập nhật để làm mới màn hình theo cách thủ công. Màn hình dựa trên UI Toolkit chỉ được xây dựng một lần và cần được thông báo khi dữ liệu bị thay đổi.
Hãy tạo một sự kiện bên trong ScriptableObject nếu bạn muốn tự động đồng bộ hóa các bản cập nhật. Ví dụ, đoạn mã ExampleSO này sẽ gọi sự kiện OnValueChanged mỗi khi ExampleValue thay đổi. Hãy xem ví dụ mã lệnh bên dưới.
Tiếp theo, hãy cho đối tượng giao diện người dùng (UI) lắng nghe sự kiện đăng ký OnValueChanged và cập nhật tương ứng.
[CreateAssetMenu(menuName = "ExampleSO")]
public class ExampleSO : ScriptableObject
{
Hành động công khai khi giá trị thay đổi;
[SerializeField] private int m_ExampleValue;
public int ExampleValue
{
get => m_ExampleValue;
bộ
{
m_ExampleValue = value;
OnValueChanged?.Invoke();
}
}
}

Lưu trữ dữ liệu trò chơi bằng mô hình flyweight.
ScriptableObjects phát huy tối đa hiệu quả khi nhiều đối tượng cùng chia sẻ dữ liệu. Ví dụ, nếu bạn đang xây dựng một trò chơi chiến thuật mà nhiều đơn vị có cùng tốc độ tấn công và máu tối đa, thì việc lưu trữ các giá trị đó riêng lẻ trên mỗi GameObject sẽ không hiệu quả.
Thay vào đó, bạn có thể tập trung dữ liệu dùng chung vào một vị trí trung tâm và cho phép mỗi đối tượng tham chiếu đến vị trí dùng chung đó. Trong thiết kế phần mềm, đây là một phương pháp tối ưu hóa được gọi là mô hình flyweight . Việc tái cấu trúc mã theo cách này giúp tránh việc sao chép nhiều giá trị và giảm thiểu lượng bộ nhớ sử dụng.
Trong PaddleBallSO , ScriptableObject GameDataSO đóng vai trò là nơi lưu trữ dữ liệu dùng chung.
Thay vì giữ một bản sao riêng biệt của các thiết lập chung (tốc độ, khối lượng, độ nảy vật lý, v.v.), các tập lệnh của Vợt và Bóng tham chiếu cùng một thể hiện GameDataSO bất cứ khi nào có thể. Mỗi yếu tố trong trò chơi duy trì dữ liệu riêng biệt như vị trí và sự kiện đầu vào, nhưng mặc định sẽ sử dụng dữ liệu chung khi có thể.
Mặc dù việc tiết kiệm bộ nhớ có thể không đáng kể với chỉ hai hoặc ba đối tượng, nhưng việc chỉnh sửa dữ liệu được chia sẻ nhanh hơn và ít xảy ra lỗi hơn so với việc chỉnh sửa từng đối tượng một cách thủ công.
Ví dụ, nếu bạn cần điều chỉnh tốc độ mái chèo, việc điều chỉnh ở một vị trí duy nhất sẽ cập nhật tốc độ của cả hai mái chèo trong mọi cảnh. Nếu bạn lưu trữ chúng dưới dạng các trường riêng biệt trong MonoBehaviours, chỉ cần một cú nhấp chuột sai cũng có thể dễ dàng làm cho hai giá trị không đồng bộ.
Việc chuyển dữ liệu sang ScriptableObjects cũng có thể giúp kiểm soát phiên bản và ngăn ngừa xung đột khi hợp nhất khi các thành viên nhóm cùng làm việc trên cùng một cảnh hoặc Prefab.

Dữ liệu trò chơi PaddallSO
GameDataSO minh họa cách sử dụng ScriptableObject như một vùng chứa dữ liệu. Trong PaddleBallSO , điều này bao gồm nhiều thiết lập khác nhau để cấu hình lối chơi:
- Dữ liệu mái chèo: Các thuộc tính như tốc độ vợt, lực cản và khối lượng quyết định chuyển động và vật lý của vợt trong suốt quá trình chơi.
- Thông tin về quả bóng: Thông tin này lưu trữ tốc độ hiện tại, tốc độ tối đa và hệ số nảy của quả bóng, điều khiển hành vi của quả bóng khi tương tác với mô phỏng.
- Dữ liệu trận đấu: GameDataSO chứa thông tin về độ trễ giữa các thời điểm trong trận đấu, giúp kiểm soát nhịp độ của trò chơi.
- Mã định danh người chơi: Các ScriptableObject PlayerIDSO đóng vai trò là mã định danh đội cho mỗi người chơi (ví dụ: Player1 và Player2).
- Hình ảnh nhân vật người chơi: Các hình ảnh tùy chọn này cho phép người chơi tùy chỉnh hình đại diện của mình.
- Bố cục cấp độ: Đối tượng LevelLayoutSO xác định vị trí bắt đầu cho người chơi và các yếu tố trong trò chơi như khung thành và tường.
Với tất cả các cài đặt và dữ liệu được tập trung tại một vị trí duy nhất, GameDataSO cho phép bất kỳ đối tượng nào cũng có thể truy cập vào dữ liệu được chia sẻ này. Điều này giúp đơn giản hóa cách bạn quản lý các đối tượng đó và thúc đẩy tính nhất quán cao hơn trong toàn bộ dự án của bạn. Thay đổi cơ chế vật lý của mái chèo? Chỉ cần thực hiện một thay đổi ở đây thay vì chỉnh sửa nhiều đoạn mã.

Ví dụ về tuần tự hóa kép: Bố cục cấp độ
Đôi khi, bạn có thể vừa có bánh lại vừa được ăn bánh. Với tính năng tuần tự hóa kép, bạn có thể lưu trữ dữ liệu trong một ScriptableObject đồng thời duy trì dữ liệu đó ở một định dạng khác.
Đoạn mã LevelLayoutSO minh họa khái niệm này. Ngoài việc lưu giữ vị trí ban đầu của vợt và bóng, nó còn lưu trữ dữ liệu biến đổi của các bức tường và khung thành trong một cấu trúc tùy chỉnh.
Các giá trị này có thể được ghi vào đĩa thông qua phương thức ExportToJson . Các tệp JSON là văn bản dễ đọc đối với con người, cho phép chỉnh sửa dễ dàng bên ngoài Unity. Điều này cho phép bạn làm việc với ScriptableObjects trong Trình chỉnh sửa và sau đó lưu trữ dữ liệu của chúng ở một vị trí khác, chẳng hạn như tệp JSON hoặc XML.
Các định dạng tệp như JSON và XML có thể gây khó khăn khi làm việc trong Trình chỉnh sửa, nhưng chúng dễ dàng chỉnh sửa bên ngoài Unity bằng trình soạn thảo văn bản. Điều này mở ra khả năng tạo ra các màn chơi tùy chỉnh hoặc do người dùng chỉnh sửa.
Sau đó, tập lệnh GameSetup có thể sử dụng ScriptableObject của LevelLayout hoặc một tệp JSON bên ngoài để tạo cấp độ trò chơi.
Để tải một cấp độ được chỉnh sửa tùy chỉnh, tập lệnh thiết lập sẽ tạo ra một ScriptableObject trong quá trình chạy bằng CreateInstance. Sau đó, nó đọc văn bản từ tệp JSON để điền vào ScriptableObject.
Dữ liệu tùy chỉnh của bạn sẽ thay thế nội dung của ScriptableObject và cho phép bạn sử dụng cấp độ được chỉnh sửa từ bên ngoài này giống như bất kỳ cấp độ nào khác. Các chức năng còn lại của ứng dụng vẫn hoạt động bình thường, không hề hay biết về sự thay đổi này.

Các ứng dụng khác của bộ chứa dữ liệu ScriptableObject
Mặc dù trò chơi nhỏ dùng vợt bóng của chúng tôi không thể minh họa mọi trường hợp sử dụng của các container dữ liệu ScriptableObject, hãy xem xét những điều sau đây cho các ứng dụng của riêng bạn:
- Cấu hình trò chơi: Hãy nghĩ về các hằng số, luật chơi hoặc bất kỳ thông số thiết lập nào khác không cần thay đổi trong suốt quá trình chơi. Các thành phần khác sau đó có thể tham chiếu đến dữ liệu cấu hình này mà không cần sử dụng các giá trị được mã hóa cứng.
- Thuộc tính của nhân vật và kẻ thù: Sử dụng ScriptableObjects để định nghĩa các thuộc tính như máu, sức tấn công, tốc độ, v.v. Điều này cho phép các nhà thiết kế của bạn cân bằng và điều chỉnh các yếu tố lối chơi mà không cần đến sự hỗ trợ của nhà phát triển.
- Hệ thống quản lý kho và hàng hóa: Các định nghĩa và thuộc tính của mục, chẳng hạn như tên, mô tả và biểu tượng, rất phù hợp với ScriptableObjects. Bạn cũng có thể sử dụng chúng như một phần của hệ thống quản lý kho đồ để theo dõi các vật phẩm mà người chơi thu thập, sử dụng hoặc trang bị.
- Hệ thống đối thoại và tự sự: ScriptableObjects có thể lưu trữ văn bản hội thoại, tên nhân vật, các nhánh hội thoại và các dữ liệu liên quan đến cốt truyện khác. Chúng có thể đặt nền tảng cho các hệ thống đối thoại phức tạp.
- Dữ liệu về cấp độ và tiến trình: Bạn có thể sử dụng ScriptableObjects để định nghĩa bố cục màn chơi, điểm xuất hiện kẻ thù, mục tiêu và các thông tin khác liên quan đến màn chơi.
- Các đoạn âm thanh: Như đã thấy trong dự án PaddleBallSO , ScriptableObjects có thể lưu trữ một hoặc nhiều đoạn âm thanh. Những thiết lập này có thể xác định hiệu ứng âm thanh hoặc nhạc nền ở nhiều phần khác nhau trong trò chơi của bạn.
- Các đoạn phim hoạt hình : ScriptableObjects có thể được sử dụng để lưu trữ các đoạn hoạt ảnh, điều này rất hữu ích để định nghĩa các hoạt ảnh chung được chia sẻ giữa nhiều GameObjects hoặc nhân vật.
Khi bạn tìm hiểu sâu hơn về ScriptableObjects và tùy chỉnh chúng cho các dự án của riêng mình, bạn sẽ khám phá ra nhiều ứng dụng hơn nữa của chúng. Chúng đặc biệt hữu ích cho việc quản lý dữ liệu và giúp dễ dàng duy trì tính nhất quán giữa các yếu tố khác nhau trong trò chơi.

Thêm tài nguyên ScriptableObject
Tìm hiểu thêm về các mẫu thiết kế với ScriptableObjects trong sách điện tử "Tạo kiến trúc trò chơi mô-đun trong Unity với ScriptableObjects" . Bạn cũng có thể tìm hiểu thêm về các mẫu thiết kế phát triển Unity phổ biến trong bài viết "Nâng tầm mã nguồn của bạn với các mẫu lập trình game" .