# Game Development Problems

Các vấn đề gặp phải trong quá trình phát triển game với Unity

# Các khớp bị cắt khi sử dụng Spine Animation.

#### Vấn đề

- Lỗi bị viền tại các khớp khi dùng spine animation.
- [![image.png](https://knowledge.thanhdv.com/uploads/images/gallery/2025-05/scaled-1680-/hWoimage.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-05/hWoimage.png)

#### Lý do

- Do config export file không trùng với config trong Unity.

#### Giải pháp

- Sử dụng color space Gamma. (để trùng với export mặc định của Unity).
- <span style="white-space: pre-wrap;">Đọc thêm tại </span>[spine-unity Assets](https://esotericsoftware.com/spine-unity-assets#Advanced-Premultiplied-vs-Straight-Alpha-Export).

# Không thể using một namespace.

#### Vấn đề

- Không thể sử dụng một namespace dù đã thêm package chính xác và có thể sử dụng ở các flie khác.

#### Trường hợp cụ thể

- SoundsGood by MelenitasDev không thể using UnityEngine.AddressableAssets.

#### Lý do

- <span style="white-space: pre-wrap;">Các scripts được biên dịch thành một Assembly riêng biệt. Nếu namespace không được </span>****Assembly Definition Files (asmdef)****<span style="white-space: pre-wrap;"> tham chiếu thì sẽ không sử dụng được.</span>

#### Giải pháp

- Mở file Assembly Definition Files và thêm tham chiếu đến asmdef cần sử dụng (VD: Unity.Addressables…)[![image.png](https://knowledge.thanhdv.com/uploads/images/gallery/2025-05/scaled-1680-/PFoimage.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-05/PFoimage.png)

# Copy một List

#### Vấn đề:

- <span style="white-space: pre-wrap;">Trong C# khi tạo một list mới bằng các </span>`<span class="editor-theme-code">List<T> list = new(otherList)</span>`<span style="white-space: pre-wrap;"> thì các phần tử của </span>`<span class="editor-theme-code">list</span>`<span style="white-space: pre-wrap;"> vẫn được tham chiếu đến các phần tử của </span>`<span class="editor-theme-code">otherList</span>`

#### Nguyên nhân:

- <span style="white-space: pre-wrap;">Trong C# khi sử dụng lệnh </span>`<span class="editor-theme-code">List<T> list = new(otherList)</span>`<span style="white-space: pre-wrap;"> chỉ tạo ra được một shallow copy (copy nông). Các phần tử vẫn được tham chiếu đến một vị trí.</span>

#### Giải pháp

- Tạo ra một deep copy (copy sâu)
    - Sử dụng vòng for duyệt và tạo mới từng phần tử sau đó thêm vào list mới.
    - Sử dụng Linq để tạo phần từ mới
        - `<span class="editor-theme-code">List<T> list = otherList.Select(e => new T()).ToList();</span>`
        - `<span class="editor-theme-code">List<T> list = otherList.ConvertAll(e => new T());</span>`

# Release sai cách khi sử dụng Addressables

#### Vấn đề.

Release sai cách khi sử dụng Addressables

Case 1: Early-Release. Với những assets cần sử dụng nhiều lần. Việc Release ngay khi load xong khiến cho mỗi lần cần sử dụng. Asset sẽ phải load từ ổ cứng lên.

```c#
public async UniTask<ICharacter> CreateCharacter(CharacterSheet.Row characterData)
{
    var handle = Addressables.LoadAssetAsync<GameObject>(characterData.PrefabAddr);
    await handle.Task;

    GameObject characterObj = resolver.Instantiate(handle.Result);

    handle.Release();
    return characterObj.GetComponent<ICharacter>();
}
```

<span style="white-space: pre-wrap;">Case 2: Cache chưa chính xác. Trong trường hơp này handle.Result đã được cache vào Dictionary nhưng handle lại bị release ngay sau đó. Tiềm ẩn vấn đề Value này sẽ được tham chiếu đến một "zombie object" gây ra lỗi </span>`<span class="editor-theme-code">NullReferenceException</span>`<span style="white-space: pre-wrap;"> hoặc </span>`<span class="editor-theme-code">ArgumentNullException</span>`

```c#
private Dictionary<string, GameObject> projectileCaches = new();

public async UniTask<IProjectile> CreateProjectile(ProjectileSheet.Row projectileData)
{
    if (!projectileCaches.TryGetValue(projectileData.Id, out var projectilePrefab))
    {
        var handle = Addressables.LoadAssetAsync<GameObject>(projectileData.PrefabAddr);
        await handle.Task;

        projectileCaches.TryAdd(projectileData.Id, handle.Result);
        projectilePrefab = handle.Result;

        handle.Release();

        DebugExtension.Log("Load from disk!!!", Color.red);
    }

    DebugExtension.Log("Load from cache!!!", Color.green);
    GameObject projectileObj = resolver.Instantiate(projectilePrefab);
    return projectileObj.GetComponent<IProjectile>();
}
```

#### Cơ chế đếm tham chiếu của Addressables

<span style="white-space: pre-wrap;">Addressables không hoạt động theo kiểu bật/tắt đơn giản mà sử dụng một cơ chế thông minh gọi là </span>****Reference Counting.****

Mỗi khi asset được load lên RAM bộ đếm tham chiếu sẽ tăng lên. Và ngược lại mỗi khi release bộ đếm tham chiếu sẽ giảm đi. Asset sẽ chỉ được unload khỏi ram khi ref-count trở về 0.

##### Ví dụ

`<span class="editor-theme-code">A.cs</span>`<span style="white-space: pre-wrap;"> gọi </span>`<span class="editor-theme-code">handleA = LoadAssetAsync(mySprite)</span>`

- `<span class="editor-theme-code">mySprite</span>`<span style="white-space: pre-wrap;"> được load vào RAM.</span>
- Ref-Count = 1

`<span class="editor-theme-code">B.cs</span>`<span style="white-space: pre-wrap;"> gọi </span>`<span class="editor-theme-code">handleA = LoadAssetAsync(mySprite)</span>`

- `<span class="editor-theme-code">mySprite</span>`<span style="white-space: pre-wrap;"> được lấy từ RAM</span>
- Ref-Count = 2

`<span class="editor-theme-code">A.cs</span>`<span style="white-space: pre-wrap;"> gọi </span>`<span class="editor-theme-code">handleA.Release()</span>`

- <span style="white-space: pre-wrap;">Ref-Count = 1 → </span>`<span class="editor-theme-code">mySprite</span>`<span style="white-space: pre-wrap;"> vẫn còn trên RAM</span>

<span style="white-space: pre-wrap;">Lúc này, nếu có </span>`<span class="editor-theme-code">C.cs</span>`<span style="white-space: pre-wrap;"> gọi </span>`<span class="editor-theme-code">handleC = LoadAssetAsync(mySprite)</span>`<span style="white-space: pre-wrap;"> thì </span>

- `<span class="editor-theme-code">mySprite</span>`<span style="white-space: pre-wrap;"> vẫn được lấy từ RAM.</span>
- Ref-Count = 2

<span style="white-space: pre-wrap;">Hoặc nếu </span>`<span class="editor-theme-code">B.cs</span>`<span style="white-space: pre-wrap;"> gọi </span>`<span class="editor-theme-code">handleB.Release()</span>`<span style="white-space: pre-wrap;"> thì </span>

- <span style="white-space: pre-wrap;">Ref-Count = 0. </span>
- `<span class="editor-theme-code">mySprite</span>`<span style="white-space: pre-wrap;"> bị unload khỏi RAM.</span>

<span style="white-space: pre-wrap;">Tiếp theo, nếu có bất kỳ scripts nào gọi gọi </span>`<span class="editor-theme-code">LoadAssetAsync(mySprite)</span>`<span style="white-space: pre-wrap;"> thì </span>`<span class="editor-theme-code">mySprite</span>`<span style="white-space: pre-wrap;"> sẽ được tải lại từ ổ cứng.</span>

#### Giải pháp

Với những object chỉ cần sử dụng một lần. Hoàn toàn có thể release ngay sau khi load.

<span style="white-space: pre-wrap;">Với những object sử dụng nhiều lần. Có thể sử dụng </span>****Addressables.InstantiateAsync()****<span style="white-space: pre-wrap;"> hoặc </span>****cache handle****<span style="white-space: pre-wrap;"> để có thể release khi không còn sử dụng đến nữa.</span>

```c#
// Cache handles
public class ProjectileFactory
{
    private Dictionary<string, AsyncOperationHandle<GameObject>> loadedPrefabs = new();

    public async UniTask PreloadProjectiles(List<ProjectileSheet.Row> allProjectileData)
    {
        foreach (var data in allProjectileData)
        {
            if (!loadedPrefabs.ContainsKey(data.Id))
            {
                var handle = Addressables.LoadAssetAsync<GameObject>(data.PrefabAddr);
                loadedPrefabs.Add(data.Id, handle);
            }
        }

        await UniTask.WhenAll(loadedPrefabs.Values.Select(h => h.AsUniTask()));
    }

    public IProjectile CreateProjectile(string projectileId)
    {
        if (loadedPrefabs.TryGetValue(projectileId, out var handle))
        {
            GameObject projectileObj = resolver.Instantiate(handle.Result);
            return projectileObj.GetComponent<IProjectile>();
        }
        return null;
    }
    
    public void UnloadAll()
    {
        foreach(var handle in loadedPrefabs.Values) Addressables.Release(handle);
        loadedPrefabs.Clear();
    }
}
```