GameObject.findObjectOfType with NPN def class

Using needleEngine 4.3.2-beta.4, unity 2022.3.43f1 and Node 18.20.3. i’m having an issue with GameObject.findObjectOfType, using it with an NPN def class like this:
_camController = GameObject.findObjectOfType(CameraController);
I’m getting these 2 errors:

Type ‘IComponent | null’ is not assignable to type ‘CameraController | null’.
Type ‘IComponent’ is missing the following properties from type ‘CameraController’: viewPositions, _onEnterView, selectView, tweenToGameObject, and 24 more.

Argument of type ‘typeof CameraController’ is not assignable to parameter of type ‘Constructor’.
Types of construct signatures are incompatible.
Type ‘new (init?: Partial<NoInternalNeedleEngineState<FilterStartingWith<FilterTypes<Component, Function | null | undefined>, “">>> | undefined) => CameraController’ is not assignable to type ‘abstract new (
args: any[]) => IComponent’.
Construct signature return types ‘CameraController’ and ‘IComponent’ are incompatible.
The types of ‘gameObject.addNewComponent’ are incompatible between these types.
Type '(type: ConstructorConcrete, init?: Partial<NoInternalNeedleEngineState<FilterStartingWith<FilterTypes<T, Function | null | undefined>, "
”>>> | undefined) => T’ is not assignable to type ‘(type: Constructor, init?: Partial<NoInternalNeedleEngineState<FilterStartingWith<FilterTypes<T, Function | null | undefined>, “_”>>> | undefined) => T’.
Types of parameters ‘type’ and ‘type’ are incompatible.
Type ‘Constructor’ is not assignable to type ‘ConstructorConcrete’.
Cannot assign an abstract constructor type to a non-abstract constructor type.

It works without forcing the implementation but i gotta use @ts-ignore

Hi TomĂĄs,

The issue you’re facing seems to be related to TypeScript’s type system and how it interprets the types involved in your usage of GameObject.findObjectOfType.

Plan

  1. Ensure you’re using the correct import statement for findObjectOfType.
  2. Create a custom type guard to safely assert that the found object is indeed a CameraController.
  3. Use this type guard to avoid requiring @ts-ignore.

Code

Here’s how you can implement it:

import { GameObject, IComponent } from "@needle-tools/engine";
import { CameraController } from "./path/to/CameraController"; // Adjust the import path accordingly

// Type guard to check if the object is a CameraController
function isCameraController(object: IComponent | null): object is CameraController {
    return object !== null && (object as CameraController).viewPositions !== undefined; // Check for a specific property from CameraController
}

// Usage
const foundObject = GameObject.findObjectOfType(CameraController);
if (isCameraController(foundObject)) {
    _camController = foundObject; // Now TypeScript knows this is a CameraController
} else {
    _camController = null; // Handle the null case appropriately
}

Explanation

  1. Type Guard: The isCameraController function checks if the object is not null and has a property specific to CameraController. This allows TypeScript to infer the correct type when you set _camController.

  2. Error Handling: By checking the type before assignment, you can avoid the need for @ts-ignore.

With this approach, you maintain type safety without suppressing the TypeScript errors. If you have further questions or need additional help, feel free to ask!

1 replies left - Get a PRO license for more AI replies.
thread_0PSVH3OMd55mukDjPcK1gPzE
Reply to this message to continue the conversation

Hi Tomas,

can you share a bit of the code that you’re using and which CameraController you’re using? It sounds like this type is not a Needle Engine component and can thus not be search for using this function. Make sure you pass in a component type (deriving from Component or Behaviour in Needle Engine).

Hello marwie

As you can see next the camera controller is getting extended as behaviour, it takes the camera where the gameObject is, we control its position in unity by putting empty objects with the world transform needed to point the camera to another object in the scene. This script was made from scratch and we began to use it as a dependency importing it from the folder “camera-Systems”, we used it like this for another project a month ago and it worked just fine, but it wasn’t in the version mentioned before.

Here’s the code:

import { Tween, Easing } from "@tweenjs/tween.js";
import { Quaternion, Vector3 } from "three";

/*
# CHANGE LOG:

# unreleased
- converts positions and rotation from local to world space before tweening.

# v2.0.0
- function tweenToGameObject uses a single tween to move the camera to a target position and rotation.

# v1.0.0
- function tweenToGameObject uses 2 tweens to move the camera to a target position and rotation.
*/

export class CameraController extends Behaviour {
    //Lista de posiciones.
    @serializable(GameObject)
    viewPositions: GameObject[] = [];

    private _onEnterView: Function[] = [];
    private _combinedTween?: Tween<{position: Vector3, rotation: Quaternion}> = undefined;

    start(): void {
        // console.log(`Posiciones de la camara: ${this.viewPositions.length}`);

        //Fijamos la posicion y rotacion inicial de la camara.
        if (this.viewPositions.length > 0) {
            //Convertimos la posicion y rotacion en valores globales.
            const position = new Vector3();
            const rotation = new Quaternion();

            const viewTransform = this.viewPositions[0];
            viewTransform.getWorldPosition(position);
            viewTransform.getWorldQuaternion(rotation);

            this.gameObject.position.set(position.x, position.y, position.z);
            this.gameObject.quaternion.set(rotation.x, rotation.y, rotation.z, rotation.w);
        }

        //Evaluar si esto merece estar en esta parte.
        this._onEnterView = [
            () => {},
            () => {
                // console.log("Vista 1");
            },
            () => {
                // console.log("Vista 2"); //Esta es la del video.
                //Buscar el componente del video y darle play manualmente (para evitar lo del renderizado).
            },
        ];
    }


    update(): void {
        if(this._combinedTween?.isPlaying())
            this._combinedTween?.update();
    }

    /**
     * Sets a tween that moves the camera to a predefined view position.
     * @param id The id of the view to select.
     * @returns nothing.
     */
    selectView(id: number) : void {
        // console.log("Seleccionando vista: " + id);

        if (this.viewPositions.length == 0) {
            console.info(`El id (${id}) de la vista no existe. No hay posiciones de camara definidas.`);
            return;
        }
        if (id < 0 || id >= this.viewPositions.length) {
            console.error(`El id (${id}) de la vista esta fuera de rango.`);
            return;
        }

        //Cada view tiene asociado una posicion.
        if (this.viewPositions.length > 0) {
            this.tweenToGameObject(this.viewPositions[id]);
        }

        //Llamamos a la funcion asociada a la vista.
        const ev = this._onEnterView[id];
        if(ev) ev();
    }

    tweenToGameObject(target: GameObject) {
        //Convertimos la posicion y rotacion en world space.
        const targetTransform = target;
        const targetWorldPosition = new Vector3();
        const targetWorldRotation = new Quaternion();
        targetTransform.getWorldPosition(targetWorldPosition);
        targetTransform.getWorldQuaternion(targetWorldRotation);

        this._combinedTween = new Tween({position: this.gameObject.position, rotation: this.gameObject.quaternion})
        .to({ position: targetWorldPosition, rotation: targetWorldRotation }, 1000)
        .easing(Easing.Cubic.InOut)
        .onUpdate(() => {
            this.gameObject.position.set(this.gameObject.position.x, this.gameObject.position.y, this.gameObject.position.z);
            this.gameObject.quaternion.set(this.gameObject.quaternion.x, this.gameObject.quaternion.y, this.gameObject.quaternion.z, this.gameObject.quaternion.w);
        })
        .start();
        // Could be: Activar y reactivar un componente look at, al comienzo y al final de la animacion.
    }

   /*  onLeaveXR(){
        this.selectView(0);
    }*/
} ```

Hi TomĂĄs,

Thank you for providing more context about your CameraController. Given that you are extending Behaviour from Needle Engine and using the viewPositions array, I can see how the issue might relate to the interaction between TypeScript’s type definitions and your class structure.

Troubleshooting the Type Issue

  1. Check Type Inheritance: Ensure that CameraController properly extends Behaviour. Check that your imports are correctly pointing to the expected versions of the classes.

  2. Ensure ‘IComponent’ Compliance: Confirm that your CameraController class implements all necessary properties/functions that IComponent expects. If it is missing any, TypeScript will raise errors.

  3. Version Compatibility: Since you mentioned this worked in a previous project with a different version of Needle Engine, there might be breaking changes in the beta version you’re using. Reviewing the changelog for the version (4.3.2-beta.4) could provide insight into any updates that may affect your implementation.

Suggestions

You can also refine your type check using TypeScript features to narrow down the errors:

function isCameraController(object: IComponent | null): object is CameraController {
    return object !== null && object instanceof CameraController;
}

Usage Example

Here’s how you can use this modified type guard with your existing code:

const foundObject = GameObject.findObjectOfType(CameraController);
if (isCameraController(foundObject)) {
    _camController = foundObject;
} else {
    console.error('CameraController not found or is of incorrect type');
    _camController = null;
}

Conclusion

If these suggestions don’t solve the problem, double-check any updates or migrations to Needle Engine that could affect the type definitions or other breaking changes. If you continue to face issues, feel free to share the complete error messages or any additional details that might help.

If you need further assistance or have specific questions about parts of your code, let me know!

1 replies left - Get a PRO license for more AI replies.
thread_0PSVH3OMd55mukDjPcK1gPzE
Reply to this message to continue the conversation

Hi, it looks like you’re missing imports from Needle Engine

import { Behaviour, serializable, GameObject } from "@needle-tools/engine"

Which (code) editor are you using? You should normally get lots of typescript errors in vscode for things like these. Make sure to don’t have any errors in your code :slight_smile:

The imports are there, i just copied the code wrong and missed the top line, what the bot said is pretty much what answered my question

1 Like

Great good to know!

This topic was automatically closed 13 days after the last reply. New replies are no longer allowed.