Script does not recognize sub elements

Hi Friends, I am using a script to change the texture of the diffuse, this script works fine on main elements but not on sub elements, what is wrong with the code?

var Changemoons = pc.createScript('BEIGE');

// Reference a list of textures that we can cycle through
Changemoons.attributes.add("textures", {type: "asset", assetType: "texture", array: true, title: "Textures"});

// initialize code called once per entity
Changemoons.prototype.initialize = function() {
    var self = this;
    
    // Keep track of which texture in the array we are currently using
    this.textureIndex = 0;

    // Change textures on button press
  var myMoons = this.app.root.findByName('TopBaseA_Range_Beige');
    myMoons.element.on('mouseup', this.onMoonsBtnPressed, this);
    };
 

Changemoons.prototype.onMoonsBtnPressed = function(dt) {
    // Index the next texture in the list, wrapping around if we reach the end
    this.textureIndex = (this.textureIndex + 1) % this.textures.length;

    // Reference the texture
    var texture = this.textures[this.textureIndex].resource;        

    // Go through all the mesh instances of the model and change the diffuse texture on the
    // material to our new one
    var meshInstances = this.entity.model.meshInstances;
    for (var i = 0; i < meshInstances.length; ++i) { 
        var mesh = meshInstances[i];
        mesh.material.diffuseMap = texture;
        mesh.material.update();
    }
};

here I leave a screenshot to better understand the hierarchy of buttons and elements

This code finds the first entity in the rootā€™s children that is called that name. My guess is that you have multiple entities with the same name.

You can either make them all unique or instead of using root, reference the parent entity first and call findByName on the parent to find a child with that name.

https://developer.playcanvas.com/en/api/pc.Entity.html#findByName

The other alternative is to reference the entity directly with script attributes.

The names are all different, I think the best is what you suggest ā€œinstead of using root, reference the parent entity first and call findByName on the parent to find a child with that name.ā€

reference the parent: parent.findOne(ā€˜TopBaseA_Range_Beigeā€™ā€™);?? :thinking:

I really donā€™t understand how to tell the script to do this

Do you have a link to the project?

@yaustar Here the link
https://playcanvas.com/editor/scene/983214

I have added you to the project

@yaustar Were you able to enter the project? I put it as public.
I keep testing codes, but how I do this? ā€œreference the parent entity firstā€ and later ā€œcall findByName on the parent to find a child with that name.ā€ :thinking: :thinking:

Hi, @Ariel_Cancio

Donā€™t give up :slight_smile:

ā€˜.findByName()ā€™ method searches for children nodes from the one you call it on. And its grand-children. And its grand-grand-children. And so on. So, when you use this.app.root.findByName(), you are calling it on root. This means anything that is under root will be searched for, which is pretty much the whole scene.

Another example, if you do this.entity.findByName(), you will search among the children of the entity you are calling this script from.

So, when @yaustar said you can reference the parent first, it simply means you can search for the parent first, and then for the child:

var parent = this.app.root.findByName('Some Parent Unique Name');
var child = parent.findByName('Child Name Under the Parent');

This way parents may have children with the same name, and you would reference to the correct one. Otherwise, if you would search root for a child with a non-unique name, the search result may point to a child under some other parent.

1 Like

Hello @LeXXik Master Jedi :grin: :grin:

ok i tried to do this but i see an error

Line 16:

 myYellow.element.on('mouseup', this.onTopBaseA_Range_YellowBtnPressed, this);

var ChangeYellow = pc.createScript('TPYELLOW');

// Reference a list of textures that we can cycle through
ChangeYellow.attributes.add("textures", {type: "asset", assetType: "texture", array: true, title: "Textures"});

// initialize code called once per entity
ChangeYellow.prototype.initialize = function() {
    var self = this;
    
    // Keep track of which texture in the array we are currently using
    this.textureIndex = 0;

    // Change textures on button press
    var parent =  this.app.root.findByName('TopBaseA_Range_Beige');
    var child = parent.findByName('TopBaseA_Range_Yellow');
    myYellow.element.on('mouseup', this.onTopBaseA_Range_YellowBtnPressed, this);
    };
 

ChangeYellow.prototype.onTopBaseA_Range_YellowBtnPressed = function(dt) {
    // Index the next texture in the list, wrapping around if we reach the end
    this.textureIndex = (this.textureIndex + 1) % this.textures.length;

    // Reference the texture
    var texture = this.textures[this.textureIndex].resource;        

    // Go through all the mesh instances of the model and change the diffuse texture on the
    // material to our new one
    var meshInstances = this.entity.model.meshInstances;
    for (var i = 0; i < meshInstances.length; ++i) { 
        var mesh = meshInstances[i];
        mesh.material.diffuseMap = texture;
        mesh.material.update();
    }
};

Yes, check what the error says. The very first line on it.
ReferenceError: myYellow is not defined

You know its on lilne 16, so go there and see whatā€™s happening. You tell the engine that you want to use an element property of myYellow variable. But you never told what that variable is, so it tells you myYellow is not defined, tell me what that is first. Well, not word by word, by you got the point.

HInt: you did define child variable, which you probably wanted to use instead.

1 Like

Iā€™m not making it @LeKKik
I feel like youā€™re almost telling me, but I canā€™t figure it out:
ā€œyou did define child variable, which you probably wanted to use instead.ā€

The child variable:

 var child = parent.findByName('TopBaseA_Range_Yellow');

The line 16

 myYellow.element.on('mouseup', this.onTopBaseA_Range_YellowBtnPressed, this);

I donā€™t understand what it is the Yellow Variable

This is related to what I was talking about in the other thread: Change material properties with a button

myYellow has no value and not referencing an object. So you either have define it as a variable in this function and give it a value/reference an object or use another variable that has the reference to the object that you need to get the element from.

To further explain this, this line of code is saying:

On the object that myYellow is referencing, get the element object and call the on function.

1 Like

Ok, I think this is closer, but it still doesnā€™t work. Follow the same error

var parent =  this.app.root.findByName('TopBaseA_Range_Beige');
    var child = parent.findByName('TopBaseA_Range_Yellow');
    myYellow.element.on = parent.findByName('TopBaseA_Range_Yellow');

I feel like Iā€™m shooting blindfolded, if I succeed it will be pure luck :persevere: :persevere:

You give up too early :wink: I will give an answer tomorrow, if you are still stuck by then.

Hint: myYellow is not defined, but child is

Yeap no problem @LeXXik, we continue tomorrow. Iā€™ll keep trying. I have noticed that if I erase the line

myYellow.element.on = parent.findByName ('TopBaseA_Range_Yellow');

the error disappears but does not change color, letā€™s say it does not work. From what I deduce that deleting is not the solution but to correctly complete the code.

Hi @LeXXik, Iā€™m still stuck, Iā€™ve tried many things and searched the API guide but I canā€™t find how to define myYellow as a variable in this function, therefore I havenā€™t achieved what @yaustar suggests "give it a value / reference an object ", I donā€™t know if I should add a line of code, if I should change some values in what is written, etc.

@LeXXik I have added this line to give myYellow a value but it doesnā€™t work, obviously itā€™s wrong and I donā€™t understand why

    var parent =  this.app.root.findByName('TopBaseA_Range_Beige');
    var child = parent.findByName('TopBaseA_Range_Yellow');
    myYellow.element.on('mouseup', this.onTopBaseA_Range_YellowBtnPressed, this);
    this.myYellow = this.parent.findByName('TopBaseA_Range_Yellow');

Howdy :slight_smile:

First of all, letā€™s get rid of your early bad habbits in syntax:

  1. Donā€™t use underscores _ in the names of your methods. Javascript developers use camelCase (google it) for naming convention:
    .onTopBaseA_Range_YellowBtnPressed()
    vs
    .onTopBaseARangeYellowBtnPressed()
  2. Try to keep the method names short. The name should only tell what the method does or when it is called. For example:
    .onClick()
    .moveForward()
    vs
    onRedButtonWithYellowStripesClickedTwoTimes()

Now, about your problem. Lets refresh how we use variables first.

  • Rule number 1: Define your variable.
var myVariable;
  • Rule number 2: Assign some value to it (in other words, store something inside that variable)
myVariable = 5;
  • Rule number 3: Use the data stored inside the variable only after rules 1 and 2 are passed.
myVariable + 2; // 7

You must follow these rules with anything in Javascript, be it objects, properties, variables, libraries, JSON payloads. Remember them. If you skip rules 1 or 2 or both, and jump straght to number 3, the compiler will complain that the variable is undefined. You must start from rule 1.

Often it is verbose to write rules 1 and 2 on new lines, so Javascript allows to merge them into a single string:

var myVariable = 5;

But the order is still preserved - the variable is defined first, then it is assigned a value.

Can you tell by now where you problem is?

Perhaps, to fully open your issue, I could introduce you to a new concept - Javascript Objects. Well, its not really new. Youā€™ve actually been using them all this time.

Say you want to create a character:

var character;

To make it more than just an empty blob of nothingness, you want to add a name to it and a language it speaks:

var charName = "LeXXik";
var language = "Marsian";

But now, those character treats are stored in their own variables, so you canā€™t simply pass character variable to some function. You would also have to send charName and language. But what if the char has dozens of properties? You can see how it quickly becomes hard to handle. Javascript objects are the answer.

A Javascript object is a simple collection of key:value pairs. Where you can name keys anything you want, and assign them any value, another object or even a function that returns a value. Pretty much anything, really. The objects syntax is to enclose the pairs in curly brackets { }. So, our character would look something like this:

{
    name: "LeXXik",
    language: "Marsian"
}

Great, now we have a character, but how do we use it? How can I get that name, for example? Well, remember rule number 1:

var character = {
    name: "LeXXik",
    language: "Marsian"
};

We defined the variable and assigned a value to it. So, now our variable stores an object that describes our character. How do we get that name? Pretty simple - we use so called ā€œdot notationā€ to access object properties. This should be familiar to you:

var name = character.name;

We can even extend it further:

var character = {
    name: "LeXXik",
    language: {
        name: "Marsian",
        level: "pretty good"
    }
};
var languageLevel = character.language.level;

Here we added another object with properties that describe the language LeXXik may speak. This is a very common and powerful method, which gives you an ability to create rich descriptions about an object. Now, instead of passing a dozens of variables, you can simply send a single character variable that stores the object.

The answer.

Here is your code:

var child = parent.findByName(...);
myYellow.element.on(...)

You broke the rule number 1 and 2, and straight went to number 3 by using myYellow variable without defining it or giving it any value. The variable myYellow is empty, and has no object inside, that would have a key with a name element. However your child variable does, and should be used instead:

var child = parent.findByName(...);
child.element.on(...)

Your child was properly defined and stores an entity object that was found by the engine:

var child = {
    ...
    ...
    element: {
        ...
        ...
        on: function() {  ...  }
        ...
    }
};

So, when you run child.element.on(), the engine looks inside the object stored in the variable and using the dot notation finds the .on() method.

4 Likes

Thank you very much @LeXXik I am reading everything very carefully.

Hello @LeXXik I have reviewed everything and I think I have understood it, thank you very much, the error does not appear anymore but it still does not change color (I think it is a problem that the child button is linked to the parent button in root), basically when I press the yellow button ā€œI dragā€ the beige button I leave you some screenshots so you can visualize
(Can you notice that when I pose above the red button, the beige button is also activated?)


Jerarquia_01

The corrected code with your suggestions (I cross my fingers that it is correct) :

    var parent =  this.app.root.findByName('TopBaseA_Range_Beige');
    var child = parent.findByName('TopBaseA_Range_Yellow');
    child.element.on('mouseup', this.onTopBaseA_Range_YellowBtnPressed, this);

pd: Thanks for your CamelCase advice, Iā€™ll use it when rewriting this