[SOLVED] Question on variable scope and functions, why is this not working?

I am having an issue with scope, I think?

I am unsure why this.wheel_on_ground always returns a FALSE when I check it in the update_position function. The raytrace is showing TRUE, as the log code shows, but checking the value in the other function or the main update just returns FALSE no matter what.

Ok, here’s my code, I’ve cut out some unrelated bits to get to the source issue:

    pc.script.create('Robot_movement', function (app) {
        // Creates a new Robot_movement instance
        var Robot_movement = function (entity) {
            this.entity = entity;
            //////////////////////////////////////////////////
            this.wheel_on_ground = false; //is the wheel of the player on the ground?
            this.groundCheckRay = new pc.Vec3(0,-this.ground_check_distance,0);
            this.rayEnd = new pc.Vec3();
        };
    
        Robot_movement.prototype = {
            // Called once after all resources are loaded and before the first update
            initialize: function () {

            },
            /////////////////////////////////////

            update_wheel_on_ground: function() {
                //var po = this.entity.getPosition();
                this.groundCheckRay.x = 0;
                this.groundCheckRay.y = -this.ground_check_distance;
                this.groundCheckRay.z = 0;
                this.rayEnd.add2(this.entity.getPosition(), this.groundCheckRay);
                //console.log("rayEnd is " + this.rayEnd);
                this.wheel_on_ground = false;
                // Fire a ray straight down to just below the bottom of the rigid body, 
                // if it hits something then the character is standing on something.
    app.systems.rigidbody.raycastFirst(this.entity.getPosition(), this.rayEnd, function (result) {
                    this.wheel_on_ground = true;
                    console.log("Raytrace returned a target, wheel_on_ground is set to " + this.wheel_on_ground);  //<-- This is returning TRUE in the console.
                });
                           
            },
            
            update_position: function() {
                console.log("update_position the wheel_on_ground is equal to " + this.wheel_on_ground);  //<-- Yet this is always returning FALSE, despite running later
                //if the wheel is touching the ground
                if(this.wheel_on_ground){
                    
                //move robot forwards
                 //I removed code here, but it's basically pushing the object forwards if the wheel_on_ground is TRUE.
                } //end of if wheel is on ground brackets
        },
            
            // Called every frame, dt is time in seconds since last update
            update: function (dt) {          
                this.update_wheel_on_ground();
                this.update_position(); //push the player object based on speed.
            }                
        };
    
        return Robot_movement;
    });

I’m sure I’m just missing something very basic, but shouldn’t this.wheel_on_ground always refer to the entity’s property I defined at the beginning? According to this it should, the paused, amplitude, and timer variables are described as working that way in the example: http://developer.playcanvas.com/en/user-manual/scripting/anatomy/

But somehow I’m losing the value in my setup. Is it because “this” changes target somewhere along the way? If so, why?

This is all about understanding the ‘this’ keyword in JavaScript. I recommend reading up on the topic. e.g.:smile:

Let’s look at a bit of your code:

            update_wheel_on_ground: function() {
                this.groundCheckRay.x = 0;
                this.groundCheckRay.y = -this.ground_check_distance;
                this.groundCheckRay.z = 0;
                this.rayEnd.add2(this.entity.getPosition(), this.groundCheckRay);
                //console.log("rayEnd is " + this.rayEnd);
                this.wheel_on_ground = false;
                // Fire a ray straight down to just below the bottom of the rigid body, 
                // if it hits something then the character is standing on something.
                app.systems.rigidbody.raycastFirst(this.entity.getPosition(), this.rayEnd, function (result) {
                    this.wheel_on_ground = true;
                    console.log("Raytrace returned a target, wheel_on_ground is set to " + this.wheel_on_ground);  //<-- This is returning TRUE in the console.
                });

The problem here is that ‘this’ is not what you think it is inside the raycast callback. You should really do:

            update_wheel_on_ground: function() {
                var self = this;
                this.groundCheckRay.x = 0;
                this.groundCheckRay.y = -this.ground_check_distance;
                this.groundCheckRay.z = 0;
                this.rayEnd.add2(this.entity.getPosition(), this.groundCheckRay);
                //console.log("rayEnd is " + this.rayEnd);
                this.wheel_on_ground = false;
                // Fire a ray straight down to just below the bottom of the rigid body, 
                // if it hits something then the character is standing on something.
                app.systems.rigidbody.raycastFirst(this.entity.getPosition(), this.rayEnd, function (result) {
                    self.wheel_on_ground = true;
                    console.log("Raytrace returned a target, wheel_on_ground is set to " + self.wheel_on_ground);  //<-- This is returning TRUE in the console.
                });

See how we are caching the this reference and using the cached value inside the function instead.

1 Like

Ok, I think I get it now. My code is working at least.

But I want to confirm my reasoning, just to make sure I understand this, so I’m not confused later. :wink:

When the code is in the app.systems.rigidbody.raycastFirst section of code, which is all just a fancy way of saying “if(collision), do {these things}”, the keyword this inside the “do these things” brackets now refers to the app.systems.rigidbody object, not the entity I was running the code from.

That is what creates the problem, because the keyword “this” is no longer defined in the new section of code, and so when first accessed “this” gets defined as the new scope’s “parent object”. The object app.systems.rigidbody, which the new function is part of. Instead of the entity reference I was hoping to get, which is now in the parent scope.

The self reference works because the new “scope” layer can still get variables from the parent scope, such as the var “self” defined before the new scope started. So by storing the expected value of this when it’s still valid, any code in sub-scope levels can then access the correct object.

Am I even close? :stuck_out_tongue:

Here is a good read on JS Scopes: http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/

1 Like