AssetReferences and non-components with hierarchy references

Hey again,

after an afternoon of debugging it finally dawned on me my problem is actually related to the other question I asked earlier today.

I have an objectC with a component spawning an instance of a AssetReference to objectB.
objectB in turn has a reference to a child objectA which it wishes to manipulate. For testing purposes I want objectB to deactivate object A.

So, recap: objectC spawns objectB deactivates child objectA.

Here’s the catch though: The component on objectB does not directly contain the reference to objectA. Instead, objectB contains a serialized plain object which contains the reference.

And that’s where it breaks :cry: Because whenever I fetch the reference to objectA, it’s not the actual instance’s objectA but the prefab’s objectA. (Prefab in the sense of the loaded instance under AssetReference.asset.)

I can work around it for now but it will become a hard problem pretty soon: I need a way to store a collection of sets of references and further data. There is no way of putting it all flat on the component’s level. So I need nested (plain serialized) objects containing references.

Happy to hear from you. But first: Happy weekend! :wink:

Hi krizzl0r, below is one approach to working around nested plain serialized objects that contain hierarchy references. Since Needle Engine only rebinding references automatically on top‐level (e.g. component) fields during instantiation, any nested object’s reference (like your objectA) won’t be automatically re‑mapped from the prefab. You need to rebind it manually once the instance is created.

Step‑by‑step pseudocode:

  1. Define a plain serialized helper class (e.g. ChildData) containing a reference field to objectA.
  2. In your objectB component, add a field of type ChildData.
  3. In the onEnable (or awake) method of objectB, use the instance’s hierarchy (e.g. using gameObject.getObjectByName(ā€œobjectAā€)) to locate the instance’s child.
  4. Update the nested object’s reference with the instance’s child rather than the prefab’s.
  5. Now you can safely manipulate objectA (for example, deactivating it).

Below is an example in TypeScript:

import { Behaviour, serializable } from "@needle-tools/engine";
import { Object3D } from "three";

// Helper class to store nested reference
class ChildData {
  // This field stores a reference to objectA
  @serializable(Object3D)
  childRef?: Object3D;
}

// Component on objectB
export class ObjectBBehaviour extends Behaviour {
  // Nested plain object holding a reference to objectA
  @serializable(ChildData)
  nestedData: ChildData = new ChildData();

  onEnable(): void {
    // Find objectA in the instance’s hierarchy (ensure it has the expected name)
    const instanceChildA = this.gameObject.getObjectByName("objectA");
    if (!instanceChildA) {
      console.warn("objectA not found in the instance hierarchy");
      return;
    }
    // Manually update the nested reference to point to the correct instance
    this.nestedData.childRef = instanceChildA;

    // For example, deactivating objectA (or adjust as needed)
    instanceChildA.visible = false;
  }
}

In this example the manual rebind ensures that after objectB is instantiated from objectC’s AssetReference, the nested object inside objectB properly points to its own child (objectA) rather than the prefab’s objectA. This workaround is necessary because plain serialized nested objects are not automatically updated during instantiation, as you discovered.

This approach follows similar principles as the automated reference resolution seen in Needle Engine’s instantiation routines (see the reference mapping in the instantiation code at engine_gameobject.ts).

Hope this helps, and happy coding!

1 replies left - Become PRO for more replies.
thread_J2iMfXSJ4VDUmk5u2OiW2Zfq
Reply to this post directly if you need further help (bottom right button)

Hi @krizzl0r

Could you perhaps provide a minimal reproducible example?

Yep, was doing just that and it currently uploads. Upload of 2.8MB took about 2 minutes :rocket: :smiley:

Sorry to bother you again but could you tell me if this is to be addressed soon(ish)?

Hi @krizzl0r this is unfortunately a change we can not do easily to avoid compatibility issues with existing projects and needs some more consideration.

The issue is caused by object references currently not being deep cloned during instantiate calls (when cloning components). In your case it means the reference of your custom class is shared by all instances, that’s why the wrong object is being disabled when you try to access the assigned Object3D.

I can see, thanks for the swift reply!

Rolled my own semi-manual solution using new_scripts_pre_setup_callbacks for now.

That’s one option - you could probably also implement __internalNewInstanceCreated() in your component to resolve children, just make sure to call the super method:

export class MyComp extends Behaviour {
  __internalNewInstanceCreated() {
    super.__internalNewInstanceCreated();
    console.log("TODO");
  }
}