+2

Script Serialization trong Unity3D là gì?

Phuongne, Th10 22, 2020


Giới thiệu

Bài viết này giới thiệu về cách mà Unity serialize và hơi lý thuyết, tuy nhiên nó sẽ giúp các bạn tiếp cận về bản chất hơn, hiểu rõ hơn một chút về engine này.

Qua bài này các bạn có thể trả lời một số câu hỏi:

  • Serialization là gì?
  • Làm cách nào Unity có thể lưu trữ các giá trị chỉnh sửa của component trên Inspector?;
  • Những biến như thế nào thì sẽ được unity lưu lại?
  • Unity lưu trữ các biến đã qua chỉnh sửa này ở đâu?

1. Serialization là gì?

Theo Unity Mannual Document, “Serialization là quá trình chuyển trạng thái (dữ liệu) của một object sang một format mà unity có thể lưu trữ và cấu trúc lại”

Mình sẽ lấy ví dụ ở trên Inspector, với các biến public của component khi chỉnh sửa các giá trị của nó:

image.png

Chỉnh sửa các biến trên inspector

Vậy các giá trị của 2 biến FullnameFirstname sẽ lưu ở đâu?

Được lưu trữ ở memory, database hoặc file (.unity, .prefab,…), có thể xem đoạn mã ở dưới đây

  m_Name: SchoolManager
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
--- !u!114 &1104590625
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 1104590624}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: fcee7be6700abe546af43a1352f39c6b, type: 3}
  m_Name:
  m_EditorClassIdentifier:
  principal:
    fullname:
    firstName:

Phía trên là một đoạn mã sau khi Unity đã serialize về GameObject có tên là SchoolManager, mình lấy ra từ file MyScene.unity, được định dạng với format kiểu YAML, hay chính xác hơn là UnityYAML.

Tuy nhiên UnityYAML không hỗ trợ đầy đủ chức năng của YAML nên có thể xem UnityYAML như là một phiên bản custom-optimized YAML library.

“các biến nằm trên inspector đều có khả năng Serialize“, tuy nhiên điều ngược lại thì ko đúng vì attribute [HideInInspector] phá vỡ điều này, mình sẽ giải thích ở mục 2.

Để đọc về attribute [HideInInspector], các bạn có thể tham khảo: [SerializeField, Header và một số attributes trong Unity3D (P1)](https://phuongne.com/serializefield-header-attributes-unity3d-p1/(opens in a new tab))

Bởi fullnamefirstname mình đang để trống nên khi serialize thành UnityYAML nó cũng trống, các bạn có thể tự thay đổi, save scene lại và kiểm tra file MyScene.unity (file scene mà các bạn đang thực hành)

“Ngoài file .unity lưu tthông tin đã serialize của scene ra thì còn file nào khác không?”

Các bạn có thể xem file .prefab

“Cái này có cần học không?”

Không, như mình đã nói ở đầu, bài này giúp các bạn rõ hơn về unity.

2. Các quy định về Serialization field

Mình sẽ đưa ví dụ về serialization field trước:

Person.cs – class non-monobehaviour

public class Person
{
    public string fullname;
    [SerializeField] string firstName;
    private string lastName;
}

School.cs – class MonoBehaviour

public class School : MonoBehaviour
{
    public Person principal;
    public int publicCount = 1;
    [HideInInspector] public int hidePublicCount = 1;
    private int privateCount = 2;
    [SerializeField] private int privateSerializeCount = 2;
    public static int staticCount = 3;
    public const int constCount = 4;
    public readonly int readonlyCount = 5;
    public Dictionary<string, int> dictionary = new Dictionary<string, int>();
}

image.png

School component trên Inspector

Có rất nhiều biến, tuy nhiên Unity chỉ hiện mỗi publicCountprivateSerializeCount, điều này giúp chúng ta có thể rút ra một số luật về serialization, Unity engine chỉ serialize field nếu thỏa mãn:

  1. Nó là public hoặc có gắn thuộc tính [SerializeField]
  2. Không phải là static
  3. Không phải là const
  4. Không phải là readonly
  5. kiểu dữ liệu có thể mà Unity có khả năng serialize (ở ví dụ trên, Dictionary không được hỗ trợ)

“Ủa sao public Person principal không xuất hiện trên Inspector?”

class Person là chúng ta tự tạo, vì vậy cần phải đánh dấu cho Unity biết nó có thể serialize bằng attribute [Serializable]

Và chúng ta cũng có thể đoán được rằng: Unity sẽ chỉ lưu 3 giá trị của Component này dưới dạng UnityYAML, còn các biến khác thì không.

“Sao lại 3, mình thấy chỉ có 2 biến xuất hiện trên inspector mà?”

Như mình đã nói ở phần 1, “…attribute [HideInInspector] phá vỡ điều này”, biến hidePublicCount không xuất hiện trên inspector nhưng cũng được Unity serialize và lưu lại dưới dạng UnityYAML:

MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 980147355}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 4d41c37069812cb4dabc420a408b6178, type: 3}
  m_Name:
  m_EditorClassIdentifier:
  publicCount: 1
  hidePublicCount: 1
  privateSerializeCount: 2
--- !u!4 &980147357

Đó cũng là lý do khi chúng ta tắt đi mở lại engine Unity, các giá trị này vẫn nằm trên Scene, không thay đổi.

Hoặc khi kéo thả prefab lên hierachy để tạo bản copy của prefab (prefab’s instance), Unity sẽ tạo một object hoàn toàn mới, sau đó mới đẩy các dữ liệu đã serialize của prefab vào instance đó.

3. Serialization ứng dụng trong Instantiate()

Phần này mình sẽ đi vào phân tích ví dụ về việc khi mình instantiate một GameObject B từ GameObject A, những giá trị nào sẽ được copy từ component của gameObject A sang component của GameObject B?

public class School : MonoBehaviour
{
    public int publicCount = 1;
    [HideInInspector] public int hidePublicCount = 1;
    private int privateCount = 2;
    [SerializeField] private int privateSerializeCount = 2;
    public static int staticCount = 3;

    private void Start()
    {
        Debug.Log("----------" + gameObject.name + "------------");
        Debug.Log("publicCount: " + publicCount);
        Debug.Log("hidePublicCount: " + hidePublicCount);
        Debug.Log("privateCount: " + privateCount);
        Debug.Log("privateSerializeCount: " + privateSerializeCount);
        Debug.Log("staticCount: " + staticCount);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            publicCount *= 10;
            hidePublicCount *= 10;
            privateCount *= 10;
            privateSerializeCount *= 10;
            staticCount *= 10;
        }

        else if (Input.GetKeyDown(KeyCode.B))
        {
            Instantiate(this.gameObject);
        }
    }
}
  • Khi mình nhấn A, giá trị của các biến sẽ đồng loạt x10
  • Khi nhấn B, sẽ tạo ra một bản copy của gameObject này

Có 2 trường hợp xảy ra khi nhấn A -> B, GameObject thứ 2 có giá trị các biến sẽ giống hoàn toàn GameObject sinh ra nó HOẶC chỉ có giá trị của các biến serialize mới được copy?

Đáp án chính xác là đáp án 2

image.png

LOG của ví dụ trên

Như mình đã nói ở cuối mục 2, chỉ biến có khả năng serialize mới được đổ vào bản copy, bao gồm publicCount, hidePublicCountprivateSerializeCount.

“Ủa sao biến static staticCount không được serialize nhưng nó vẫn thay đổi?”

Static không serialize, staticCount thay đổi bởi vì nó là biến chung của toàn bộ instance, hay là biến của class.

“Vậy các biến không serialize thì nó sẽ nhận giá trị nào?”

Nhận giá trị default trong script, nếu bạn khai báo int i = 10, hoặc Person abc; thì abc == null

Giá trị default của các biến không serializable này là không đổi, nếu bạn cố gắng thay đổi bằng cách sử dụng [ContextMenu] thì sau khi script compile, sau khi đóng mở project, sau khi load lại scene, sau khi nhấn Play trên Editor,…. giá trị này sẽ về lại giá trị default trong code, bởi Unity không lưu giá trị này ở đâu cả.

Như vậy “Chỉ có các biến serializable mới có khả năng lưu lại giá trị đã chỉnh sửa ở trên Unity Editor.”

Disclaimer

Bài viết được đăng vào 2020, chỉ có giá trị tham khảo các bạn nhé 😉

Nguồn tham khảo

Nguồn tham khảo:

Gamasutra – Introduction to Unity Serialization and Game Data

Unity Document – Script Serialization


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí