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ý do

Giải pháp

Không thể using một namespace.

Vấn đề

Trường hợp cụ thể

Lý do

Giải pháp

Copy một List

Vấn đề:

Nguyên nhân:

Giải pháp

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.

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>();
}

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 NullReferenceException hoặc ArgumentNullException

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

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à 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ụ

A.cs gọi handleA = LoadAssetAsync(mySprite)

B.cs gọi handleA = LoadAssetAsync(mySprite)

A.cs gọi handleA.Release()

Lúc này, nếu có C.cs gọi handleC = LoadAssetAsync(mySprite) thì

Hoặc nếu B.cs gọi handleB.Release() thì

Tiếp theo, nếu có bất kỳ scripts nào gọi gọi LoadAssetAsync(mySprite) thì mySprite sẽ được tải lại từ ổ cứng.

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.

Với những object sử dụng nhiều lần. Có thể sử dụng Addressables.InstantiateAsync() hoặc cache handle để có thể release khi không còn sử dụng đến nữa.

// 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();
    }
}