PlayCanvas cant find model components even though they exist

The script does have mouse event callbacks. Maybe that’s whats keeping the reference? How would I completely destroy everything?

Script…PS. The findByName(“Plane”); is at the bottom of the script

var Hotspot = pc.createScript('hotspot');

//Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"});//OLD from demo
Hotspot.attributes.add("id", {type: "number", title: "ID"});

Hotspot.attributes.add("radius", {type: "number", title: "Radius"});
Hotspot.attributes.add("fadeDropOff", {
    type: "number", 
    default: 0.4, 
    title: "Fade Drop Off", 
    description: "When to start fading out hotspot relative to the camera direction. 1 for when hotspot is directly inline with the camera. 0 for never."

// initialize code called once per entity
Hotspot.prototype.initialize = function() {
    hotspot_highlightMaterial ="Hotspot_Highlight");
     //get the app manager script
    if( this.cameraEntity===null)
        {console.log("HOTSPOT CANT FIND CAMERA");}
        {console.log("HOTSPOT FOUND CAMERA: ";} //Returns Camera correctly
    // Create a hit area using a bounding sphere
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius);
    // More information about pc.ray:
    this.ray = new pc.Ray();
    this.defaultForwardDirection = this.entity.forward.clone();
    this.directionToCamera = new pc.Vec3();
    this.sprite = this.entity.children[0];
    //set the text on this hotspot to match the id
    this.hotspotText=this.entity.findByName("Text").element;//you can only reference objects;
    // Register the mouse down and touch start event so we know when the user has clicked, this.onMouseDown, this);
    if ( {, this.onTouchStart, this);
    //reference to all other cutaway hotspots"cutaway_hotspots");
    console.log("INIT HOTSPOT");
   // this.hotspot_model = this.entity.findByName("Plane").model;
    this.hotspot_plane_mesh = this.entity.findByName("Plane").model;

Hotspot.prototype.postInitialize = function() {

// update code called every frame
Hotspot.prototype.update = function(dt) {
        var cameraPosition = this.cameraEntity.getPosition();

        // Always face the camera

        // Get the current direction to the camera
        this.directionToCamera.sub2(cameraPosition, this.entity.getPosition());

        // Get the dot product of the direction to the camera and the original direction of the hotspot
        // which is relative to the angle between the two vectors
        // Start fading out the hotspot if the dot product is below fadeDropOff
        var dot =;
        if (dot < 0) {
            if (this.sprite.enabled) {
                this.sprite.enabled = false;
        } else {
            if (!this.sprite.enabled) {
                this.sprite.enabled = true;

            var meshInstances = this.sprite.model.meshInstances; 
            var alpha = pc.math.clamp(dot / this.fadeDropOff, 0, 1);
            for(var i = 0; i < meshInstances.length; ++i) {
                meshInstances[i].setParameter("material_opacity", alpha);


Hotspot.prototype.doRayCast = function (screenPosition) {
    // Only do the raycast if the sprite is showing
    if (this.sprite.enabled) { 
//          if( this.cameraEntity===null)
//         {console.log("HOTSPOT CANT FIND CAMERA");}
//     else
//         {console.log("HOTSPOT FOUND CAMERA: ";}//Returns fine
//          if(
//         {console.log("NO COMPONENT ON CAMERA ENTITY");}
//          else
//         {console.log("COMPONENT ON CAMERA FOUND : ";}//Returns but its called Undefined
       "Camera_MV");//This works if a I put it here. But maybe its slow. Its probably finding the cam from previous scene so maybe quick fix is to rename camera
                // Initialise the ray and work out the direction of the ray from the a screen position
      , screenPosition.y,, this.ray.direction); 


                // If the hotspot is clicked on, then send a event to start the 'pulse' effect
                if (this.hitArea.intersectsRay(this.ray)) 

Hotspot.prototype.onMouseDown = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {

Hotspot.prototype.onTouchStart = function (event) {
    // On perform the raycast logic if the user has one finger on the screen
    if (event.touches.length == 1) {
        // Prevent the default mouse down event from triggering

Hotspot.prototype.show_hotspot_information = function () 
    console.log("Showing Hotspot Info");
    //populate left hand window with info from the JSON data file
    //hide some text"Text_Cutaway_Title").element;//you can only reference objects
    //Hotspot Name"Text_Cutaway_SubTitle").element;//you can only reference objects
    this.modelNameText.text=this.appManagerScript.mattressData.brands[this.appManagerScript.currentBrand].products[this.appManagerScript.currentMattress].hotspots.cutaway[].info_1;"Text_Cutaway_Body").element;//you can only reference objects
    //change the hotspot image.."Mattress_Main_Image").element;//you can only reference objects
    this.hotspot_image_1_name = this.appManagerScript.mattressData.brands[this.appManagerScript.currentBrand].products[this.appManagerScript.currentMattress].hotspots.cutaway[].image;
    var hotspot_imageAsset =;
    console.log ("Trying to load "+this.hotspot_image_1_name);
    if( hotspot_imageAsset && hotspot_imageAsset.loaded)
       this.hotspot_image_1.texture= hotspot_imageAsset.resource;
    //highlight this hotspot and unhighlight others
    var hotspot_normalMaterial="Hotspot_Normal");
    //var hotspot_highlightMaterial ="Hotspot_Highlight");
    console.log("This hospot id is:";
     console.log("this.cutaway_hotspots.length is :"+this.cutaway_hotspots.length );
        for(x=0;x<this.cutaway_hotspots.length > 0;x++)
            //set texture to normal
             this.hotspot_plane_mesh = this.cutaway_hotspots[x].findByName("Plane");
            this.hotspot_plane_mesh = this.cutaway_hotspots[x].findByName("Plane").model;// when the scene gets reloaded it cant find this mesh..why not???
            this.hotspot_plane_mesh.material = hotspot_normalMaterial.resource;

            console.log("SETTING DEFAULT MAT ON id "+x);
        for(x=0;x<this.cutaway_hotspots.length > 0;x++)
            //set texture to normal
            //but set this hotspot the highlighted
                     console.log("CHANGING MATERIAL on ";
                    this.hotspot_plane_mesh = this.cutaway_hotspots[x].findByName("Plane").model;
                    this.hotspot_plane_mesh.material= hotspot_highlightMaterial.resource;
                    //this.hotspot_model.material= hotspot_highlightMaterial.resource;


Does this have to be on the root object?

Where are these entities in relation to the entity that this script is on? Are they children of this entity or elsewhere?

Can you show a screenshot of the hierarchy and show where the entities tagged cutaway_hotspots are and also the entity where this script is attached to?

The plane is on a template (Hotspot) which is inside a parent template(Test_Cutaway). This ‘parent template’ is instantiated into the Root>Scene> node of the new scene. Here is the Test_Cutaway template…
And the script is a component of each of the Hotspot entities

Which entities are tagged ‘cutaway_hotspots’?

And the ‘Hotspot’ script is on Hotspot 0/1/2 etc ?

Hotspot 0,Hotspot 1, Hotpot 2, Hotspot 3 etc are the ones with the script AND the tag.

In which case, what I would do is change the scene root entity to something unique to the whole project:

Eg: ‘Scene1’

And change the code to:'Scene1').findByTag("cutaway_hotspots");

The name of the entity (‘Scene1’) could be a script attribute on the hotspot script.

The issue here is the same reason here: Hotspots not drawn - #14 by yaustar

At the point that initialise is called during your loadScene logic, both scenes are loaded and the root.findByTag will find the scene you are switch from entities too.


I would suggest using loadSceneData to load the JSON from the server, destroy the current scene hierarchy and then add the new scene hierarchy. This means that the JSON data can be loaded first with adding the new scene so you can destroy the old scene and add the new scene in the same frame.

An example of use load scene data in this context: PlayCanvas 3D HTML5 Game Engine (see console log for printing out of the model components on the tagged entities.

ChangingScenes.prototype.loadScene = function (sceneName) {
    // Get a reference to the scene's root object
    var oldHierarchy = ('Root');

    // Get the path to the scene
    var scene =;, function (err, sceneItem) {
        if (err) {
        } else {
  , function(err, parent) {
                if (err) {

Using the exact function above, but replacing:

var oldHierarchy = ('Root');
var oldHierarchy = ('Scene');

(because I don’t want to destroy everything at root level because that includes my app manager entity)

I get exactly the same error when returning to the scene.(Uncaught TypeError: Cannot read property ‘model’ of null) Although the entities themselves exist.

This looks like it would need a reproducible project to look at properly

I think you are already shared on it here…PlayCanvas 3D HTML5 Game Engine

To run the project you will need to run it from the init scene.

The hotspot script is in script/Misc/hotspot.js
and the opencene is in script/Misc/openScene.js

To repro in app choose Purple>Purple 4 > At this point choosing the cutaway option (bottom right) and clicking on hotspots will work fine.
Now go back to the previous scene with the back/up arrow…then return (Purple 4) and do the same again and you will see the error.

From the repro steps and a quick look at the code, it looks like the issue I mentioned earlier in the thread: PlayCanvas cant find model components even though they exist - #4 by yaustar

You need to unsubscribe from the touch and mouse events when the script is destroyed:

You have the following code in hotspot:

    // Register the mouse down and touch start event so we know when the user has clicked, this.onMouseDown, this);
    if ( {, this.onTouchStart, this);

But there isn’t anywhere where it unsubscribes.

So when you load the scene the second time, the old callback is still registered to the mouse and touch events and all the related data is still in memory due to the callback reference.

Add the following to the initialise function:

this.on('destroy', function()  {, this.onMouseDown, this);
    if ( {, this.onTouchStart, this);

}, this);

OKay great, that works perfectly. I still don’t quite understand why the destroy function doesn’t do this by default on an entity when it gets destroyed (why would I want to keep events available for entities that don’t exist? Is there a reason?) but I guess I know now to bear this in mind.
Thanks very much.

The event system is generic, it’s not always a script instance that subscribes to the event so it has no way to know if the object that is subscribing to the event is PlayCanvas script instance or a generic JS object.

At the same time, the script has no idea what event’s it is subscribing to (as it’s an object outside the ownership of the script instance) or whether the developer may want to persist beyond the lifetime of the script instance.

The rule of thumb is that if it are subscribing to something, it is also responsible for unsubscribing.

1 Like

That said, we cooooould write a wrapper function on the script type API that does this or you could write your own :thinking:

Yes, do that :rofl:


To be honest though, I cant see any occasion when anyone would actually need to keep the events of an entity that no longer exists - especially if it just causes errors.

Don’t forget about engine only users or those that listen to these events in code that lives outside the lifetime of an Entity.

Eg: at master · playcanvas/ · GitHub

Sure, but surely that’s a far less common occurrence than for regular users. Maybe an optional argument in the destroy function would be great for everyone :slight_smile:

It can’t be on the destroy function because the event that is being listened to is on, not an Entity.

Ok. You know far more than me of course, but just from and end user point of view I think someway of automatically unsubscribing would be great.

Untested, but you can use a utility function to make this easier for you


function listen(scriptInstance, target, event, callback) {
    target.on(event, callback, scriptInstance);
    scriptInstance.on('destroy', function() {, callback, scriptInstance);

// usage in a script
listen(this, app, pc.EVENT_MOUSEDOWN, function() {});
1 Like

Thanks, i tried this, but couldnt get it to work. The main listen function ran and the scriptInstance.on(‘destroy’) but it didnt seem to do the job and I get the same issue as before. I’ll probably just go with the original suggestion:)