Erratic behaviour with AssetReference.instantiate()

Iā€™m getting highly inconsistent behaviour when trying to instantiate prefabs. I assume something is wrong with fetching the assets

I have the following behaviour:

import { AssetReference, Behaviour, InstantiateOptions, serializable } from "@needle-tools/engine";


export class InstantiateTest2 extends Behaviour
{

    @serializable(AssetReference) 
    myObjectReference: AssetReference | null = null;

    @serializable(AssetReference) 
    myObjectReference2: AssetReference | null = null;

    @serializable(AssetReference) 
    myObjectReference3: AssetReference | null = null;
    

    async start()
    {
        const opts = new InstantiateOptions();
        opts.position = this.worldPosition;

        this.myObjectReference?.instantiate(opts)

        opts.position.setX(5);
        this.myObjectReference2?.instantiate(opts)

        opts.position.setX(10);
        this.myObjectReference3?.instantiate(opts)
    }
}

I have created some basic cube and sphere prefabs and have referenced them in Unity editor. (when they are placed in the scene directly, they always render)

Sometimes the prefab spawns, sometimes not. I am always saving Unity and entering play mode before each script edit.

Only 2 prefabs max will appear on screen even though more are being instantiated

Eventually Unity starts hanging and crashes (see screenshot)

Original Post on Discord

by user 285903863339548673

Hello there ā€“ we have an experimental AI bot :robot: that might be able to help you with your question. Would you like to try this out?

How it works:
Reply to the messages of the bot that end with a thread_id to interact with it and ask follow-up questions. You can also mention the bot directly in this channel to get help.

Help requested: please wait a momentā€¦

Here is what I found for you :brain: Discord

The behavior youā€™re observing with inconsistent prefab instantiation and the subsequent crash of Unity might be related to the way youā€™re instantiating the objects asynchronously without waiting for each instantiation to complete before starting another. Since the start method in a behavior runs asynchronously, itā€™s important to ensure each asynchronous operation completes before moving onto the next, especially if these operations can have side effects like creating new objects in the scene.

Letā€™s modify your start method to wait for each instantiation to finish before proceeding to the next one:

import { AssetReference, Behaviour, InstantiateOptions, serializable, Vector3 } from "@needle-tools/engine";

export class InstantiateTest2 extends Behaviour {

    @serializable(AssetReference) 
    myObjectReference: AssetReference | null = null;

    @serializable(AssetReference) 
    myObjectReference2: AssetReference | null = null;

    @serializable(AssetReference) 
    myObjectReference3: AssetReference | null = null;

    async start() {
        const opts = new InstantiateOptions();
        opts.position = this.worldPosition;

        if (this.myObjectReference) {
            await this.myObjectReference.instantiate(opts);
        }

        opts.position = new Vector3(5, opts.position.y, opts.position.z);
        if (this.myObjectReference2) {
            await this.myObjectReference2.instantiate(opts);
        }

        opts.position = new Vector3(10, opts.position.y, opts.position.z);
        if (this.myObjectReference3) {
            await this.myObjectReference3.instantiate(opts);
        }
    }
}

thread_k1JRBxb4YqkhTGYGwufEebZl

Could you report a bug with this scene? I havent seen this behaviour and would need to understand the setup

Also: which engine version are you using? I think it was 3.25.x? Could you try updating to the latest stable version before sending a bugreport?

This just gives this error:
Uncaught (in promise) TypeError: opts.position is undefined

by user 285903863339548673

Here is what I found for you :brain: Discord

I understand, letā€™s check the instantiation process, and define the position before each instantiation. We will also perform error checks to ensure the positions are not undefined:

import { AssetReference, Behaviour, InstantiateOptions, serializable, Vector3 } from "@needle-tools/engine";

export class InstantiateTest2 extends Behaviour {

    @serializable(AssetReference) 
    myObjectReference: AssetReference | null = null;

    @serializable(AssetReference) 
    myObjectReference2: AssetReference | null = null;

    @serializable(AssetReference) 
    myObjectReference3: AssetReference | null = null;

    async start() {
        if (!this.myObjectReference || !this.myObjectReference2 || !this.myObjectReference3) {
            console.error("One or more AssetReferences are not set.");
            return;
        }

        const opts = new InstantiateOptions();
        opts.position = this.worldPosition;
        const newObj = await this.myObjectReference.instantiate(opts);

        if (newObj) {
            opts.position = new Vector3(5, opts.position.y, opts.position.z);
            await this.myObjectReference2.instantiate(opts);

            opts.position = new Vector3(10, opts.position.y, opts.position.z);
            await this.myObjectReference3.instantiate(opts);
        } else {
            console.error("Error instantiating the first object.");
        }
    }
}

This code now includes error checking to ensure that the AssetReferences are not null or undefined before attempting to instantiate objects. It also includes a check for the newly instantiated object to ensure that the second and third instantiations only occur if the first instantiation was successful. This should help prevent errors related to undefined positions and improve error handling during the instantiation process.

0 messages left GPT 3.5
thread_k1JRBxb4YqkhTGYGwufEebZl
Thanks for your feedback!

Confused with using the report bug feature and how to add the script to the steps to reproduce.

To reproduce:

  • New Project 2022.3
  • Install Needle 3.32.5-exp
  • New Minimal Needle scene
  • New .ts script (copy & paste my ā€˜InstantiateTest2ā€™ scriptā€™)
  • Add ā€˜InstantiateTest2ā€™ component an empty GameObject in the scene
  • Create a Prefab, add meshFilter (reference cube mesh), meshRenderer components
  • Add prefab to ā€˜myObjectReferrenceā€™ field in ā€˜InstantiateTest2 componentā€™ (repeat for all three reference fields)
  • Start server + Play

Result: Only 2 of objects are instantiated

by user 285903863339548673

Hi, the reason is that the async callback for instantiate is not awaited and the Instantiate options that are passed in is immediately modified. You can fix this by creating new instantiate options per ā€œinstantiateā€ call.
I think it might be better if internally the options would be copied (to make your code above work as well) - but right now thatā€™s how you have to do it

@BOTBOT in case you didnt see it: I changed that addressable instantiate behaviour to copy the passed in options immediatly to make your life easer and also backported it to the latest stable version Discord

Ok cool, so does the instantiate options get destroyed after it is consumed by instantiate() ?

I also got it to work by awaiting the instantiate call

await this.myObjectReference?.instantiate(opts)

by user 285903863339548673

Nice, I will also try that. I suppose that is better for the expected behaviour

by user 285903863339548673

that shouldnt be the case, did you have issues with that?

in any case: now it gets cloned so your original object should stay untouched

Sorry misunderstood - I understand what the issue was now.

On slight tangent, are instantiated objects the children of the gameObject that instantiated them?

Is it possible to get a reference to the instantiate gameObject as we are instantiating it? e.g.

const k : GameObject = this.myPrefabRefernece.instantiate(opts)
k.GetComponent<>

instantiate() returns a Promise<GameObject - can I type cast that to GameObject??

by user 285903863339548673

the promise has to be awaited - then you get the GameObject/Object3D

Thatā€™s because of the assetreference which might need to load first

Cool, got it.

Iā€™ve just experimented with adding the instantiated objects as children to the gameobject that instantiated them like this:

    const newGameObject = await element?.instantiate(opts);
    newGameObject?.attach(this.gameObject);

I think itā€™s worked - however when moving the parent GameObject within Unity (with Needle liveSync), the children donā€™t move as well (like typical parent - child relations)

by user 285903863339548673