0

Hi! If you want to save time and still help please read this section and the last one to get glimpse of my problem (part 1 and 6). So much code was needed to fully present problem

Using Unity 2019.3.0b2

1.

Im creating WebGL application that allows you to customize your character via assets from downloaded asset bundles. So far I got downloading and instantiating work, but I also want to change downloaded gameobject material to custom color from color-picker. In this case I need to refer to adequate Renderer. I've got function that sends request and it goes like so:

private IEnumerator SendRequestCoroutine(UnityWebRequest request, UnityAction<UnityWebRequest> OnDownloadCompleteHandler, UnityAction<float> OnDownloadProgressHandler = null)
    {
        request.SendWebRequest();
        while(!request.isDone)
        {
            if(OnDownloadProgressHandler != null)
                OnDownloadProgressHandler.Invoke(request.downloadProgress);
            yield return null;
        }
        // Has to fire once more because progress stops at around 0.87,
        // never returning 1 unless download is finished.
        if (OnDownloadProgressHandler != null)
            OnDownloadProgressHandler.Invoke(1);
        OnDownloadCompleteHandler.Invoke(request);
    }

2. I fire this coroutine like that:

public void DownloadAssetBundle(string url, ProgressBar bar = null)
{
    if (isRequestSend)
    {
        Alerter.ShowMessage("Request has been already send, please wait untill complete.");
        return;
    }
    UnityWebRequest request = HttpService.Instance.GetAssetBundleRequest(url);

    if(bar != null)
    {
        HttpService.Instance.SendDownloadRequest
        (
            request,
            (rq) => { OnDownloadAssetBundleCompleteHandler(rq); },
            (rq) => OnDownloadProgressHandler(rq, bar)
        );
        isRequestSend = true;
    }
    else
    {
        HttpService.Instance.SendDownloadRequest
        (
            request,
            (rq) => { OnDownloadAssetBundleCompleteHandler(rq); }
        );
        isRequestSend = true;
    }
}

3. OnDownloadAssetBundleCompleteHandler looks like this:

//Function that will handle asset bundle when completed.
private void OnDownloadAssetBundleCompleteHandler(UnityWebRequest request)
{
    isRequestSend = false;
    if(request.isHttpError || request.isNetworkError)
    {
        //Handle downloading error
        Alerter.ShowMessage("Seems like there was a problem with downloading, try again.");
    }
    else
    {
        AssetBundle bundle;
        //Handle content update
        bundle = DownloadHandlerAssetBundle.GetContent(request);
        AssetBundleInfo assetBundleInfo = bundle.LoadAllAssets<AssetBundleInfo>().FirstOrDefault();

        if (assetBundleInfo == null)
        {
            //Handle error
            Alerter.ShowMessage("Couldn't read information about this Character Part. AssetBundleInfo null exception.");
            bundle.Unload(false);
            return;
        }
        GameObject goToLoad = null;

        goToLoad = bundle.LoadAsset<GameObject>(assetBundleInfo.ObjectName);

        if (goToLoad == null)
        {
            Alerter.ShowMessage("Couldn't read information about this Character Part. Downloaded asset's gameobject null exception.");
            bundle.Unload(false);
            return;
        }
        Sticher.ConnectComponent(goToLoad, assetBundleInfo.PartType);
        ColorSetter.Instance.ChangeSelectedBodyPart(assetBundleInfo.PartType);
        bundle.Unload(false);
    }
}

4. Now the final step is to set adequate transform so my script will search for component of type Renderer, get its material of index 0 as current material to modify, class that contain ChangeSelectedBodyPart function looks like so:

using Assets.Scripts.Models.Enums;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColorSetter : MonoBehaviour
{
public Renderer rend;
public ColorPicker picker;
public static ColorSetter Instance;

private void Awake()
{
    if(Instance != null)
    {
        Destroy(this);
    }
    else
    {
        Instance = this;
        DontDestroyOnLoad(this);
    }
}

// Start is called before the first frame update
void Start()
{
    picker.onValueChanged.AddListener(color => 
    {
        if (rend == null)
            return;
        rend.material.SetColor("_BaseColor", color);
    }
    );
}

public void ChangeSelectedBodyPart(AvatarPartType p)
{
    switch(p)
    {
        case AvatarPartType.ClothesUpper:
            SetActiveMaterial(Sticher.Instance.Clothes_Upper);
            break;
        case AvatarPartType.ClothesLower:
            SetActiveMaterial(Sticher.Instance.Clothes_Lower);
            break;
        case AvatarPartType.Shoes:
            SetActiveMaterial(Sticher.Instance.Shoes);
            break;
        case AvatarPartType.Hair:
            SetActiveMaterial(Sticher.Instance.Hair);
            break;
        case AvatarPartType.Skin:
            SetActiveMaterial(Sticher.Instance.Skin);
            break;
    }
}

private void SetActiveMaterial(Transform parent)
{
    rend = parent.GetComponentInChildren<Renderer>();
}
}

5. PS. parent has only one child that contains Renderer component Now, finally, problem is that I don't get proper material reference, I got the old one that is being set via toggle button, simply as that:

    public void OnValueChanged(bool value)
{
    if(value)
    {
        ColorSetter.Instance.ChangeSelectedBodyPart(PartType);
        ButtonManager.Instance.RemoveAllButtonsFromPartsWindow();
        ButtonManager.Instance.PopulatePartsPanelWithAvatarPartsOfType(PartType);
    }
}

6. So in conclusion when I press on "toggle button" that represents some avatar body/clothing part it sets its parent and material properly via function, even after asset bundle has been downloaded (but I have to click the same toggle again to make it work), but when I fire the same function in OnDownloadAssetBundleCompleteHandler just after asset been downloaded it doesn't work :S Why? Is it related with asset unloading speed? Any tips on fixing this?

In Game View it behave like that: enter image description here

Seoner
  • 101
  • 8
  • TL;DR but, what exactly is called when you click the button, what does debugging show if you only click once and wait, are you sure its not just a simple logic issue.. are you downloading all say "tops" in one go or individually? – BugFinder Oct 08 '19 at 13:00
  • We don't have EndPoints implemented yet so Im currently using DropBox direct download links, those buttons are generated based on amount of Scriptable Object Assets inside resources folder, each of has enum called partType that tell what kind of part is it, it also contain URL and sprite to be shown. So download process begin when I press one of the "tops". When I press "tops" toggle script from part 5 is being called, while I press on individual top and it is finished downloading "ColorSetter.Instance.ChangeSelectedBodyPart(PartType);" is being called from part 4. Both use same function. – Seoner Oct 08 '19 at 13:43
  • if you add debug messages along each of the stages where does it get stuck on? – BugFinder Oct 08 '19 at 13:49
  • It doesn't get stuck neither console show error, the thing is when download is finished and it jumps into OnDownloadAssetBundleCompleteHandler function from part 3, importatnt are 2 first of 3 last lines of the code where former instantiate gameobject and set its parent base d on partType and later tries to get its material, debbuging line rend = parent.GetComponentInChildren(); from part 4 shows old Instantiated GameObject somehow. – Seoner Oct 08 '19 at 13:57
  • Alright... writting comment above gave me an idea. I've used Destroy(); to remove old GameObjects before loading new one. I assume that Garbage Collector takes care of object being Destroyed via Destroy() method and I've been receiving old references, however using DestroyImmidiate fixed my problem. – Seoner Oct 08 '19 at 14:22
  • `You are strongly recommended to use Destroy instead. This function should only be used when writing editor code since the delayed destruction will never be invoked in edit mode. In game code you should use Object.Destroy instead.` – derHugo Oct 08 '19 at 15:21
  • Alright, so I can't use this function but using Object.Destroy form unity namespace doesn't fix issue. – Seoner Oct 09 '19 at 05:43

1 Answers1

0

I fixed it. Since you can't use DestoyImmediate like @derHugo said because it can cause reference errors or even it could destroy assets permamently I had to use Destroy() instead before Instantiating new gameobject from assetbundle, however Destroy() will delete given object at the end of the frame while I try to access Renderer component on freshly instantiated GameObject just at the same frame, before old one is acutally destoryed. I fixed it yielding one frame using yield return new WaitForEndOfFrame(); just after function that takes care of destroying old GameObjects before trying to access new GameObject.

Seoner
  • 101
  • 8