Where can I find training/tutorial to become an advanced user?

Hi there,

I have a beginner level understanding of JS and have been trying to learn more advanced concepts of JS in order to work with PlayCanvas.

I might have to build an interactive app (not a game) for work in the near future and they want it built with the PlayCanvas engine. I have been over the tutorials and get the basics but when I try to move onto the specifics of things I need to achieve, I get stuck for hours on end without understanding much what I am doing wrong.

I could ask a million questions very specific to what I need to learn but I don’t want to come across as abusing the help everybody so willingly provide and would be good to understand the principles and techniques used, otherwise I will never be able to troubleshoot my own things.

I am going over JS tutorials as well as the PlayCanvas ones but was wondering if there is some sort of advanced JS training specific for playcanvas.

The process to becoming ‘advanced’ is the same as it is anywhere.

I’m a newbie here at PlayCanvas as well, and I plan to become advanced the same way I became advanced in many other peices of software/tools/etc.

  1. Use as few tutorials as possible. By presenting you all the information they take away a lot of the thinking that would allow you to actually learn. Instead reverse engineer other peoples examples. This way you have to put in the thought yourself and things you learn ‘stick’ better.
  2. Read the documentation. Get familiar with it. I wish there was more engine-only documentation as this is how I’m using the engine.
  3. Muck around. The more you play with it, the more weird cases you come across, and the better you become at solving problems you’re likely to encounter.

Hey sdfgoff,

Nice to have someone chime in. I see your points and agree with them. I do try to follow those points already, it is just that sometimes, it gets very hard to understand some things because there are certain concepts you don’t know or understand fully. There is a great deal of that that will come with experience, of course.

I guess I wasn’t clear enough on what I was after in my initial post. Here is an example:

I am doing some “mucking around” and wrote the following a script, that is attached to a bunch of planes:

pc.script.create('CoverSprite', function (app) {
        // Creates a new CoverSprite instance
        var CoverSprite = function (entity) {
            this.entity = entity;
        };
        
        CoverSprite.prototype = {
            // Called once after all resources are loaded and before the first update
            initialize: function () {
                var pos = this.entity.getLocalPosition();
                var target = this.entity;
                
                // Instantiate a tween
                this.tween_mouseOver = TweenMax.to(pos, 1, { 
                                       y:10, 
                                       ease:Power1.easeInOut,
                                       onUpdate: function(){
                                              target.setLocalPosition(pos);
                                       },
                                   
                                       onReverseComplete: function(){
                                               var cam = app.root.findByName("Camera");
                                               cam.script.CameraScript.enableMouse();
                                      }
            });
                
                 // Pause the tween immediately
                this.tween_mouseOver.pause();
            },
            
            grow: function () {
                // Tween the parent cover
                this.tween_mouseOver.play();
            },
            
            shrink: function () {
                // Tween the parent cover back to its original position
                this.tween_mouseOver.reverse();
            },

            setPos: function () {
                this.entity.setLocalPosition(pos);
            },
            
            enableMouse: function () {
               // Grab the camera entity
               var cam = app.root.findByName("Camera");
               cam.script.CameraScript.enableMouse();
            },
    
            // Called every frame, dt is time in seconds since last update
            update: function (dt) {     
            }
        };
        return CoverSprite;
    });

It took me a very long time to understand that the TweenMax bits were function objects themselves and, as such, any this contained inside either onUpdate or onComplete referred to the TweenMax itself not to the Entity in question.

Scope is not an easy thing to get you head around it when looking at a script setup like this. Specially if you’re not used to working on this sort of thing (I guess I should have started giving a bit more of a background on myself but too late now.

I am still trying to understand this whole prototyping thing done in JS - I am coming from a AS3 class based start and still can’t quite get how to extend functionality and share methods, to be honest.

Ok, here’s a question: Look at the script up here and see that I have a setPos and a enableMouse functions. I am trying to figure our how to call them from TweenMax’s onUpdate and onReverseComplete. Is there a way? Or the correct manner would be to cut these parts out from the script and have them placed on another script?

This is a long post and I am starting to go around in circles, talking about many things at once. The core question is: is there someplace where these concepts are written down in a more plain way? Over the past couple of days I started reading into the Mozilla Developers Network but the documentation here is still a bunch of terms that look more like greek to me.

Anyways, thank you. I guess I am just stressed out because I know I will need to show knowledge about this at work very, very soon.

I think the thing about javascript is that there are so many ways to do things that you have to pick a method and run with it.

I come from a python background, so I tend to think instead of ‘prototypes’ in terms of 'classes.'
The thing to realize is that they are objects.

Let’s say we have a Rocket ship. It may have engines.
Let’s make a class (or javascript prototype smash) of it. A rocket has some engines. The method I use is:

var Rocket = function () {
    //Creates a rocket with some fuel
    this.fuel = 100
}
Rocket.prototype.useEngines = function(time){
    this.fuel = this.fuel - time*BURN_RATE //burn_rate is the fuel per second
}

Using your method it would be (I think. I’m not familiar with your method):

var Rocket = function(){
    this.fuel = 100
    this.prototype = {
        useEngines: function(time){
            this.fuel = this.fuel - time*BURN_RATE
        }
    }
}

Now where am I going with this? The thing is that these are objects. So let’s create a rocket (or two):

rocket1 = new Rocket()
rocket2 = new Rocket()

These are now separate. If I go:

rocket1.useEngines(50)
console.log(rocket1.fuel)
console.log(rocket2.fuel)

They will be different. Only rocket1 will have used fuel because they are different objects.
From within a prototype/object/class you can access internal variables/functions by using "this.thingname"
From outside you can access them with “rocket.thingname” where rocket is an instance of the class.

So in your example you’re defining this ‘object’ called a CoverSprite (which inherits all the properties of pc.entity). Within that you’re adding a whole bunch of methods such as grow, shrink, setPos etc. I’m not familiar with tween, but I suspect you can access other class/prototype functions from within the onUpdate by passing in as the callback both the entity and the function, so if it were our Rockets, we would pass:

tween.onUpdate(rocket2.useEngines)

or if you are inside the object:

tween.onUpdate(this.useEngines)

(No idea what tween onUpdate does or how you use it, but hopefully you get the idea).



One of the great things about programming is that while syntax changes, core ideas do not. So if you can’t figure out javascript prototyping, have a shot at classes in python, or structs in C (ugh, C). They are all conceptually the same, just different implementations.

If you want an example of a prototype I made yesterday, here are some links to some code.
This is a map class/prototype. Within it it holds a representation of a map and some functions to work with it:
http://pastebin.com/2F6RWg4b

Then there is some more code that uses this map representation to build up a cellula-automata-like maze. Note in particular that I create an instance of the map (line 76) and then pass it around performing operations on it. (eg the randomize_map function uses the maps internal set_point function to randomize the map)
http://pastebin.com/6ySe0Gsw

The first stages of the project can be seen here:
HTML Only: http://sdfgeoff.totalh.net/cavex15/maptest.html
PlayCanvas Incomplete: http://sdfgeoff.totalh.net/cavex15/
(Press R for a new map in the playcanvas version. Be aware the playcanvas version is incomplete and will spout javascript errors left right and center until the assets have loaded. I need to figure out loading screens and physics…). For my second day working with playcanvas, I’m pretty pleased I got that far though.

Just to weigh in quickly about scope.

In Javascript, whenever you declare a function which internally uses this you need to think about what this will be when you call the function.

If you are declaring the function on a prototype, e.g.

var O = function () {
    this.value = 10;
};

O.prototype.myMethod = function () {
  console.log(this.value);
};

Then in normal circumstances this will be an instance of O. e.g.

var i = new O();
i.myMethod(); // prints 10

However, if you declare a function elsewhere and you still want to use this, then things get a little more complicated.

e.g.

var O = function () {
    this.value = 10;
};

O.prototype.myMethod = function () {
  var add = function() {
    return this.value + 10;
  }
  console.log(add());
};

The above example won’t work. this in the add function will be the window object.

There are two solutions:

bind()

var O = function () {
    this.value = 10;
};

O.prototype.myMethod = function () {
  var add = function() {
    return this.value + 10;
  }.bind(this);
  console.log(add());
};

Here we’ve call bind(scope) on the function. bind() returns a new function which has this set to be the value passed into bind(). In this case the correct value for this.

self

var O = function () {
    this.value = 10;
};

O.prototype.myMethod = function () {
  var self = this;
  var add = function() {
    return self.value + 10;
  }
  console.log(add());
};

In this case we’ve created a local variable called self (use whatever name you like, in PlayCanvas by convention we use self).

Inside the add() function we use self instead of this. When you declare the add function the local variables are captured and are available to use, so self is the instance of the object.

Hopefully that makes things a little clearer…

A couple of other things you might want to look into is function.call() and function.apply, these let you execute a function and supply the scope and arguments. See [MDN][1] for more details

1 Like

Hey guys,

There is a lot to respond to from both of your comments, I don’t think you want me to write a book to respond to each of the points, right? I did read all of what you wrote, did absorb some of it, some I didn’t.

Sdfgeoff, I get you. Although, I don’t know what Python is like (although I do want to get into it at some point - because of Blender and to try and do some statistical visualisation), my background is ActionScript, where it is all about classes and extending classes. Also it is an event based language. I am not quite sure just yet how JS is structured. I get it is Object Oriented but that’s pretty much it.

One of the things I still don’t get is how to replicate the extension of classes - I imagine it is possible because PlayCanvas seem to do tons of it.

What I did manage to work out was how to have the different methods(is that the correct definition?) talk to each other inside my CoverSprite object.

So I have [redacted, of course to save space]:

pc.script.create('CoverSprite', function (app) {
    // Creates a new CoverSprite instance
    var CoverSprite = function (entity) {
        this.entity = entity;
    };

CoverSprite.prototype = {
    initialize: function () {
      [... code stuff ...]
    },

    selected: function () {
      [... code stuff ...]
    },

    resetThis: function () {
      [... code stuff ...]
    }
};
    return CoverSprite;
});

I get the object is “CoverSprite” and everything inside the

var CoverSprite = function (entity) {...};

bit is created every time for each instance of my planes. And everything inside the

CoverSprite.prototype = {...};

is shared by all instances and only created once. If I want to call any of the methods inside this CoverSprite all I have to do is this.methodName BUT if I want to use the TweenMax and use any of its methods to call a method inside CoverSprite (I’ll explain that in a bit) I have to use the whole CoverSprite.prototype.methodName.

Dave, I followed your suggestion and had a read up on the function.call and function.apply but I will have to read it a couple more times to have it sink in.

So, TweenMax. I will not put links here about it because I am paranoid and don’t want people thinking I am spamming things around. What it is is a tweening library that I really enjoy using. It started out in flash and got ported to JS, now that I am moving into JS from flash, it made perfect sense to use it. So far, so good. I can explain what it does on my code if anybody wants but, I figure, for the lot of you, it is pretty much self explanatory. Most of the onSomething is calling a function when the Something happens.

And another comment, Dave, I am based in London and am quite often around Farrindgon. If you guys ever have an open day or one of those meetups about PlayCanvas, I would surely come.

To give a practical example of what I was talking about, take your tween code:

var target = this.entity;
this.tween_mouseOver = TweenMax.to(pos, 1, { 
   y:10, 
   ease:Power1.easeInOut,
   onUpdate: function(){
       target.setLocalPosition(pos);
   },
   //...         

You’ve saved the entity as target and you’re accessing that to update the position. An alternative would be this, using bind()

this.tween_mouseOver = TweenMax.to(pos, 1, { 
   y:10, 
   ease:Power1.easeInOut,
   onUpdate: function(){
       this.entity.setLocalPosition(pos);
   }.bind(this),
   //...         

or this using self (basically identical to your original but saving a different variable

var self = this;
this.tween_mouseOver = TweenMax.to(pos, 1, { 
   y:10, 
   ease:Power1.easeInOut,
   onUpdate: function(){
       self.entity.setLocalPosition(pos);
   },
   //...         

I see there is not much difference in case 1 and case 3. Case 2, although I understand what you are showing, it still is not natural for me to think with the .bind() - I think I get what is happening, you’re telling the function inside onUpdate that the scope is the same as the one of tween_mouseOver meaning that both this refer to the same thing.

I do try to say out loud (or write it back) what I understood to make sure I am not going on a different direction to what was intended.

Would you know if there are any performance differences between these three cases? Would one be more standard or preferred over the other?

I guess one of the important things about Javascript (and I’m not sure how this relates to AS) is that everything is an object. And that includes functions.

So you can do this, which is assigning the function to the variable add.

var add = function (a, b) {
   return a+b;
}
var sum = add(1,2);

So bind is doing this:

var add = function (a, b) {
   return a+b;
};
var bound_fn = add.bind(this); // bind() returns a new function
var sum = bound_fn(1, 2); 

(of course now I’m confusing things as add doesn’t use this but pretend it did…)

Anyway, it sounds like you’ve basically got it.

Regarding performance, I don’t think they’ll be any diffference. Though bind() does create a new function object. So it is allocating memory.

Yes, it does look like these concepts are slowly making their way into my brain. It is very nice to be able to discuss these things with someone else, rather than just bang the head on a wall. It does help a ton more when they know what they’re talking about. Thank you.

After four days of battling with the code and scouring the net for sources of information I have managed to write a script that does, more or less what I need for now. And I am quite satisfied with it.

If anybody hasn’t got anything better to do and would like a peek, I could post it up here to be taken apart by you lot. I can take criticism ( in another words, I am dying to find out if I did write some nice code or an aberration…) :wink:

Sure, post away. I’m happy to take a look.

There are two scripts working together. One at the camera and one at some planes.

The bellow is the script attached to the camera:

pc.script.create('CameraScript', function (app) {
    // Creates a new CameraScript instance
    var CameraScript = function (entity) {
        this.entity = entity;
    };
    

    CameraScript.prototype = {
        // Called once after all resources are loaded and before the first update
        initialize: function () {
            this.defaultState();
        },
        
        defaultState: function () {
            // Camera should move by default
            this.moveCamera(true);
            // Mouse event
            this.defaultMouse(true);
        },
        
         onSelect: function (e) {
            var from = this.entity.getPosition();
            var to = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.farClip);
            
            app.systems.rigidbody.raycastFirst(from, to, function (result) {
                var pickedEntity = result.entity;
               
                pickedEntity.script.CoverSprite.selected();

            }.bind(this));
        },
        
        resetSelected: function(e) {
            // Grab all the children
            var children = app.root.findByName("TL_Assets").getChildren();
            // Reset all children to its original position
            for (i = 0; i < children.length; i++) {
                children[i].script.CoverSprite.resetThis();
            }
        },
        
        defaultMouse: function (boolean) {
          if( boolean === true ) {
              app.mouse.on(pc.input.EVENT_MOUSEDOWN, this.onSelect, this);
              // If it exists, remove it
              app.mouse.off(pc.input.EVENT_MOUSEDOWN, this.resetSelected, this);
          }  else {
              // Remove the default mouse event
              app.mouse.off(pc.input.EVENT_MOUSEDOWN, this.onSelect, this);
              // Add the reset event listener
              app.mouse.on(pc.input.EVENT_MOUSEDOWN, this.resetSelected, this);
          }
        },
        
        moveCamera: function(boolean) {
            // Should the camera move
            if( boolean === true ) {
                // Yes
                // Enable the mouseEvent for the camera movement
                app.mouse.on(pc.input.EVENT_MOUSEMOVE, this.cameraMovement, this);
            } else {
                // No
                // Disable the mouseEvent for the camera movement
                app.mouse.off(pc.input.EVENT_MOUSEMOVE, this.cameraMovement, this);
            }
        },
        
        cameraMovement: function(e) {
            var w = window.innerWidth,
                h = window.innerHeight,
                thisW = (e.x - (w/2)) / 100,
                thisH = - (e.y - (h/2)) / 100;
            
            //TweenMax.to(pos, 1, {x:thisW, y:thisH, ease:Power1.easeOut, onUpdate: function(){ target.setLocalPosition(pos); }}); // This creates a smoother movement but also seems to leak memory and becomes slugish
            this.entity.setLocalPosition(thisW, thisH, 75);
        },

        // Called every frame, dt is time in seconds since last update
        update: function (dt) {
            
        }
    };

    return CameraScript;
});

And here is the one attached to the planes:

pc.script.create('CoverSprite', function (app) {
    // Creates a new CoverSprite instance
    var CoverSprite = function (entity) {
        this.entity = entity;
    };

    CoverSprite.prototype = {
        // Called once after all resources are loaded and before the first update
        initialize: function () {
            var target = this.entity;
            var pos = this.entity.getLocalPosition();
            
           
            this.cam = app.root.findByName("Camera");
            var camPos = this.cam.getLocalPosition();

            // Instantiate a partial tween, we will add extra x and y properties later on the code dynamically
            this.tween_selected = TweenMax.to(pos, 1, { z:30 } );
            // Pause the tween immediately
            this.tween_selected.pause();
        },
        
        selected: function () {
            // Grab the entity
            var target = this.entity;
            // Grab the Entity's current position
            var pos = this.entity.getLocalPosition();
            // Grab the camera's current position
            var camPos = app.root.findByName("Camera").getLocalPosition();
            // Add it to the x and y target positions of the tween
            this.tween_selected = TweenMax.to(pos, 1, {x:camPos.x, y:camPos.y, z:30, ease:Power1.easeInOut,
                                                                                    onUpdate: CoverSprite.prototype.setPos,
                                                                                    
                                                                                    onUpdateParams: [target, pos, camPos],
                                                                                    
                                                                                    onComplete: CoverSprite.prototype.hasFocused,
                                                                                    
                                                                                    onCompleteParams: [this, true],
                                                                                    
                                                                                    onReverseComplete: CoverSprite.prototype.hasFocused,
                                                                                    
                                                                                    onReverseCompleteParams: [this, false]
                                                                                  });
            // Tween the parent cover
            this.tween_selected.play();
        },
        
        resetThis: function () {
            // Set the click back to the default setting
            this.hasFocused(this, false);
            // Tween the parent cover back to its original position
            this.tween_selected.reverse();
        },
        
        
        hasFocused: function (scope, boolean) {
            // Find the camera
            var cam = app.root.findByName("Camera");
            // Have we focused on a cover?
            if( boolean === true ) {
                // Yes
                // the next click should take us back to the original state
                cam.script.CameraScript.defaultMouse(false);
                
            } else {
                // No
                // Set the scene back to its default settings
                cam.script.CameraScript.defaultState(true);
            }
        },
        
        matchCamera: function (e) {
           // Grab the camera's position
           var camPos = this.cam.getLocalPosition();
           // Grab the entity's position
           var pos = this.entity.getLocalPosition();
           // Lock the x position of this cover to the camera's
           this.entity.setLocalPosition(camPos.x, pos.y, pos.z);
        },
        
        setPos: function (target, pos, camPos) {
            // Update the entity's position
            target.setLocalPosition(pos);
        },
        
        // Called every frame, dt is time in seconds since last update
        update: function (dt) {
        }
    };

    return CoverSprite;
});

Meh! Already found an issue with my shinning code. The onSelect fires too many times after the first click. Got to look into that.

1 Like