3D Arcade Simplex Game: Object Refactoring 1 of 2

INTRODUCTION

The next level of refactoring is to repackage code as objects. This will make the code not only easier to read, but easier to change.  And change is a good thing especially if you want to change hero, monster, and missile movements & appearances.  But first let’s review the code we refactored for consistency:

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);
window.addEventListener("keydown", herocontrol);
function init()
{
    canvas=document.getElementById("cvGame");
    engine=new BABYLON.Engine(canvas);

    scene=new BABYLON.Scene(engine);
    light=new BABYLON.HemisphericLight("sun", new BABYLON.Vector3(0,1,0), scene);    
    ground=BABYLON.MeshBuilder.CreateGround("mGround", {width:1000,height:1000}, scene);   
    ground.material = new BABYLON.StandardMaterial("gMat", scene);
    ground.material.diffuseColor=new BABYLON.Color3(0,.5,0);

    hero = BABYLON.MeshBuilder.CreateCylinder("cHero", {height:10,diameterBottom:10,diameterTop:0}, scene);
    hero.material=new BABYLON.StandardMaterial("mHero", scene);
    hero.material.diffuseColor=new BABYLON.Color3(0,0,1);
    hero.position=new BABYLON.Vector3(-20, 5, 0);
    hero.rotation.z=-Math.PI/2;
    herodx=herodz=0;
    herospd=1;
    herorotspd=5;
    herohit=herodead=false; 
    hero.computeWorldMatrix(true);

    mons= BABYLON.MeshBuilder.CreateCylinder("cMons", {height:10,diameterBottom:10,diameterTop:0}, scene);
    mons.material=new BABYLON.StandardMaterial("mMons", scene);
    mons.material.diffuseColor=new BABYLON.Color3(1,0,0);
    mons.position=new BABYLON.Vector3(20, 5, 0);
    mons.rotation.z=-Math.PI/2;    
    monsdx=monsdz=0;
    monsspd=0.05;
    monsrotspd=1; 
    monshit=monsdead=false; 
    mons.computeWorldMatrix(true);

    boom = BABYLON.MeshBuilder.CreateCylinder("cBoom", {height:10,diameterBottom:4,diameterTop:0}, scene);
    boom.material=new BABYLON.StandardMaterial("mBoom", scene);
    boom.material.diffuseColor=new BABYLON.Color3(1,.5,0);
    boom.position=new BABYLON.Vector3(0,5,20);
    boom.rotation.z=-Math.PI/2;
    boomdx=boomdz=0;
    boomspd=boomrotspd=1; 
    boomhit=boomdead=false; 
    boom.computeWorldMatrix(true);

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover=false;
    engine.runRenderLoop(gameloop);
}
function gameloop()
{
    if (!gameover) {
    monsdx=hero.position.x-mons.position.x;
    monsdz=hero.position.z-mons.position.z;
    monsang=Math.atan2(monsdz,monsdx); 
    
    monsdx=Math.cos(monsang); 
    monsdz=Math.sin(monsang); 
    mons.position.x+=monsdx*monsspd; 
    mons.position.z+=monsdz*monsspd; 
    mons.rotation.y=-monsang;
    if (mons.intersectsMesh(hero,true)) {
        gameover=true;
        spWinLose.innerHTML="YOU DIED!";
    }

    boom.position.x+=boomdx*boomspd; 
    boom.position.z+=boomdz*boomspd; 
    if (boom.intersectsPoint(mons.position)) {
        gameover=true;
        spWinLose.innerHTML="YOU WIN";
    }
    }
    scene.render();
}
function herocontrol(event)
{
    switch (event.key) {
        case 'a':
            hero.rotation.y-=Math.PI/180*herorotspd; 
        break;
        case 'd':
            hero.rotation.y+=Math.PI/180*herorotspd; 
        break;
        case 'w':
            heroang=-hero.rotation.y;
            herospd=1;
            herodx=Math.cos(heroang); 
            herodz=Math.sin(heroang); 
            hero.position.x+=herodx*herospd; 
            hero.position.z+=herodz*herospd; 
        break;
        case 's':
            heroang=-hero.rotation.y;
            herospd=1;
            herodx=Math.cos(heroang); 
            herodz=Math.sin(heroang); 
            hero.position.x-=herodx*herospd; 
            hero.position.z-=herodz*herospd; 
        break;
        case 't':
            camera.position.y=200;
            camera.position.x=camera.position.z=0;
            camera.rotation.x=Math.PI/2;
        break;
        case 'Enter':
            boom.position.x=hero.position.x;
            boom.position.z=hero.position.z;
            boom.rotation.y=hero.rotation.y;
            boomang=-boom.rotation.y;
            boomdx=Math.cos(boomang); 
            boomdz=Math.sin(boomang); 
        break;
    }
}
</script>
<style>
body    {width:100%; height:100%; padding:0; margin:0; background:red;}
#cvGame {position:absolute;width:100%; height:100%; padding:0; margin:0; background:skyblue;}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<span id="spWinLose" style="position:absolute;bottom:0;right:0;">SIMPLEX</span>
</body>
</html>

Explanation. The main difference between this code and the previous tutorial is that I removed the comments.

I also bolded the code that we’ll make into an object in the next section. Specifically, we will start off by making monster into an object.  You should compare the code above, with the one below and make sure you understand the changes.

One thing to keep in mind for beginning programmers. Whenever you create a new object, it creates its own memory space, if you will, with its own variables. You access the objects variables by prefixing them with the keyword this. If you don’t use it, your object will use the program’s global variables.

[For more info. on objects, see Introduction to Object-Oriented JavaScript]

 

STEP 8: REFACTORING MONSTER CODE AS AN OBJECT

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);
window.addEventListener("keydown", herocontrol);
function init()
{
    canvas=document.getElementById("cvGame");
    engine=new BABYLON.Engine(canvas);

    scene=new BABYLON.Scene(engine);
    light=new BABYLON.HemisphericLight("sun", new BABYLON.Vector3(0,1,0), scene);    
    ground=BABYLON.MeshBuilder.CreateGround("mGround", {width:1000,height:1000}, scene);   
    ground.material = new BABYLON.StandardMaterial("gMat", scene);
    ground.material.diffuseColor=new BABYLON.Color3(0,.5,0);

    hero = BABYLON.MeshBuilder.CreateCylinder("cHero", {height:10,diameterBottom:10,diameterTop:0}, scene);
    hero.material=new BABYLON.StandardMaterial("mHero", scene);
    hero.material.diffuseColor=new BABYLON.Color3(0,0,1);
    hero.position=new BABYLON.Vector3(-20, 5, 0);
    hero.rotation.z=-Math.PI/2;
    herodx=herodz=0;
    herospd=1;
    herorotspd=5;
    herohit=herodead=false;
    hero.computeWorldMatrix(true);

    mons = new omons(hero);

    boom = BABYLON.MeshBuilder.CreateCylinder("cBoom", {height:10,diameterBottom:4,diameterTop:0}, scene);
    boom.material=new BABYLON.StandardMaterial("mBoom", scene);
    boom.material.diffuseColor=new BABYLON.Color3(1,.5,0);
    boom.position=new BABYLON.Vector3(0,5,20);
    boom.rotation.z=-Math.PI/2;
    boomdx=boomdz=0;
    boomspd=boomrotspd=1; 
    boomhit=boomdead=false; 
    boom.computeWorldMatrix(true);

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover=false;
    engine.runRenderLoop(gameloop);
}
function gameloop()
{
    if (!gameover) {
    mons.move();

    boom.position.x+=boomdx*boomspd;
    boom.position.z+=boomdz*boomspd;
    if (boom.intersectsPoint(mons.mesh.position)) { 
        gameover=true;
        spWinLose.innerHTML="YOU WIN";
    }
    }
    scene.render();
}
function herocontrol(event)
{
    switch (event.key) {
        case 'a':
            hero.rotation.y-=Math.PI/180*herorotspd; 
        break;
        case 'd':
            hero.rotation.y+=Math.PI/180*herorotspd; 
        break;
        case 'w':
            heroang=-hero.rotation.y;
            herospd=1;
            herodx=Math.cos(heroang); 
            herodz=Math.sin(heroang); 
            hero.position.x+=herodx*herospd; 
            hero.position.z+=herodz*herospd; 
        break;
        case 's':
            heroang=-hero.rotation.y;
            herospd=1;
            herodx=Math.cos(heroang); 
            herodz=Math.sin(heroang); 
            hero.position.x-=herodx*herospd; 
            hero.position.z-=herodz*herospd; 
        break;
        case 't':
            camera.position.y=200;
            camera.position.x=camera.position.z=0;
            camera.rotation.x=Math.PI/2;
        break;
        case 'Enter':
            boom.position.x=hero.position.x;
            boom.position.z=hero.position.z;
            boom.rotation.y=hero.rotation.y;
            boomang=-boom.rotation.y;
            boomdx=Math.cos(boomang); 
            boomdz=Math.sin(boomang); 
        break;
    }
}

omons=function(hm) { // hero's MESH as input
    this.hero = hm;
    this.mesh= BABYLON.MeshBuilder.CreateCylinder("cMons", {height:10,diameterBottom:10,diameterTop:0}, scene);
    this.mesh.material=new BABYLON.StandardMaterial("mMons", scene);
    this.mesh.material.diffuseColor=new BABYLON.Color3(1,0,0);
    this.mesh.position=new BABYLON.Vector3(20, 5, 0);
    this.mesh.rotation.z=-Math.PI/2;    
    this.dx=this.dz=0;
    this.spd=0.05;
    this.rotspd=1;
    this.hit=this.dead=false;
    this.mesh.computeWorldMatrix(true);
    this.move = function() {
            this.dx=this.hero.position.x-this.mesh.position.x;
            this.dz=this.hero.position.z-this.mesh.position.z;
            this.ang=Math.atan2(this.dz,this.dx); 
    
            this.dx=Math.cos(this.ang); 
            this.dz=Math.sin(this.ang); 
            this.mesh.position.x+=this.dx*this.spd;  
            this.mesh.position.z+=this.dz*this.spd; 
            this.mesh.rotation.y=-this.ang;
            if (this.mesh.intersectsMesh(this.hero,true)) {
                gameover=true;
                spWinLose.innerHTML="YOU DIED!";
            }
    }
    this.draw = function() {
        // for future animations
    }
}
</script>
<style>
body    {width:100%; height:100%; padding:0; margin:0; background:red;}
#cvGame {position:absolute;width:100%; height:100%; padding:0; margin:0; background:skyblue;}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<span id="spWinLose" style="position:absolute;bottom:0;right:0;">SIMPLEX</span>
</body>
</html>

Explanation: init() changes. All the monster initialization code in the init() function has been replaced by the single line:

mons = new omons(hero);

Much cleaner!

Now omons is an object of type monster, and mons is a variable holding that object. Originally mons was a cylinder mesh created by Babylon, now it’s an object of type monster. Remember that. We’ll get to the object explanation last. For now, note that you pass the hero mesh (this is important, hero is a mesh, not an object) so that the monster’s AI can point and move towards the hero properly.

Explanation: gameloop() changes. All the monster move and collision detection code has been replaced by a the single line:

mons.move();

There is one other important difference. The missile now checks for collision with the mons.mesh.position instead of just mons.position. This is because mons is now an object, and it’s position is stored in a variable named mesh.

But where did all the code go?  The answer is that it is now in the omons object.

Explanation: omons = function (hm) {…}. Objects in javascript are functions. We created a function and assigned it to omons. Now, whenever we want to create a new monster we just use new omons() as we did in init().

So where did the code go? All the monster init code has been moved into the start of omons. And all the monster move code that used to be in the gameloop() has been moved to a function named move(). The gameloop simply needs to call mons.move() to access this code.

I added one another function draw(). This is for future enhancements. It will allow us to change the appearance of the monster real-time.

Next let’s make an object out of the missile.

 

STEP 9: REFACTORING THE MISSILE CODE (BOOM) AS AN OBJECT

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);
window.addEventListener("keydown", herocontrol);
function init()
{
    canvas=document.getElementById("cvGame");
    engine=new BABYLON.Engine(canvas);

    scene=new BABYLON.Scene(engine);
    light=new BABYLON.HemisphericLight("sun", new BABYLON.Vector3(0,1,0), scene);
    ground=BABYLON.MeshBuilder.CreateGround("mGround", {width:1000,height:1000}, scene);
    ground.material = new BABYLON.StandardMaterial("gMat", scene);
    ground.material.diffuseColor=new BABYLON.Color3(0,.5,0);

    hero = BABYLON.MeshBuilder.CreateCylinder("cHero", {height:10,diameterBottom:10,diameterTop:0}, scene);
    hero.material=new BABYLON.StandardMaterial("mHero", scene);
    hero.material.diffuseColor=new BABYLON.Color3(0,0,1);
    hero.position=new BABYLON.Vector3(-20, 5, 0);
    hero.rotation.z=-Math.PI/2;
    herodx=herodz=0;
    herospd=1;
    herorotspd=5;
    herohit=herodead=false;
    hero.computeWorldMatrix(true);

    mons = new omons(hero);
    boom = new oboom(mons.mesh);

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover=false;
    engine.runRenderLoop(gameloop);
}
function gameloop()
{
    if (!gameover) {
        mons.move();
        boom.move();
    }
    scene.render();
}
function herocontrol(event)
{
    switch (event.key) {
        case 'a':
            hero.rotation.y-=Math.PI/180*herorotspd; 
        break;
        case 'd':
            hero.rotation.y+=Math.PI/180*herorotspd; 
        break;
        case 'w':
            heroang=-hero.rotation.y;
            herospd=1;
            herodx=Math.cos(heroang); 
            herodz=Math.sin(heroang); 
            hero.position.x+=herodx*herospd; 
            hero.position.z+=herodz*herospd; 
        break;
        case 's':
            heroang=-hero.rotation.y;
            herospd=1;
            herodx=Math.cos(heroang); 
            herodz=Math.sin(heroang); 
            hero.position.x-=herodx*herospd; 
            hero.position.z-=herodz*herospd; 
        break;
        case 't':
            camera.position.y=200;
            camera.position.x=camera.position.z=0;
            camera.rotation.x=Math.PI/2;
        break;
        case 'Enter':
            boom.mesh.position.x = hero.position.x;
            boom.mesh.position.z = hero.position.z;
            boom.mesh.rotation.y = hero.rotation.y;
            boom.ang=-boom.mesh.rotation.y;
            boom.dx=Math.cos(boom.ang); 
            boom.dz=Math.sin(boom.ang); 
        break;
    }
}

omons=function(hm) { // hero's MESH as input
    this.hero = hm;
    this.mesh= BABYLON.MeshBuilder.CreateCylinder("cMons", {height:10,diameterBottom:10,diameterTop:0}, scene);
    this.mesh.material=new BABYLON.StandardMaterial("mMons", scene);
    this.mesh.material.diffuseColor=new BABYLON.Color3(1,0,0);
    this.mesh.position=new BABYLON.Vector3(20, 5, 0);
    this.mesh.rotation.z=-Math.PI/2;
    this.dx=this.dz=0;
    this.spd=0.05;
    this.rotspd=1;
    this.hit=this.dead=false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.dx=this.hero.position.x-this.mesh.position.x;
        this.dz=this.hero.position.z-this.mesh.position.z;
        this.ang=Math.atan2(this.dz,this.dx);

        this.dx = Math.cos(this.ang);
        this.dz = Math.sin(this.ang); 
        this.mesh.position.x+=this.dx*this.spd;
        this.mesh.position.z+=this.dz*this.spd; 
        this.mesh.rotation.y=-this.ang;
        if (this.mesh.intersectsMesh(this.hero,true)) {
            gameover=true;
            spWinLose.innerHTML="YOU DIED!";
        }
    }

    this.draw = function () {
    // for future animations
    }
}

oboom = function (mm) { // pass the monster mesh
    this.mons = mm; // mons is a mesh
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cBoom", { height: 10, diameterBottom: 4, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mBoom", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(1, .5, 0);
    this.mesh.position = new BABYLON.Vector3(0, 5, 20);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = this.dz = 0;
    this.spd = this.rotspd = 1;
    this.hit = this.dead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.mesh.position.x += this.dx * this.spd;
        this.mesh.position.z += this.dz * this.spd;
        if (this.mesh.intersectsPoint(this.mons.position)) {
            gameover = true;
            spWinLose.innerHTML = "YOU WIN";
        }
    }

    this.draw = function () {
    // for future enchancements
    }
}
</script>
<style>
body    {width:100%; height:100%; padding:0; margin:0; background:red;}
#cvGame {position:absolute;width:100%; height:100%; padding:0; margin:0; background:skyblue;}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<span id="spWinLose" style="position:absolute;bottom:0;right:0;">SIMPLEX</span>
</body>
</html>

Explanation. Like the monster object, the missile object has been refactored into an object named oboom. The init() code for boom has been moved to the start of the oboom, and the gameloop() code for boom has been moved into the function move().

Now init() there is just the single line:

boom = new oboom(mons.mesh);

And in gameloop() there is just the singe line:

boom.move();

The function herocontrol() also has changes that reflect the fact that we are now accessing variables in the boom object. Hence, boom.dx versus boomdx, and boom.mesh.rotation.y versus boom.rotation.y.

 

STEP 10: REFACTORING THE HERO CODE AS AN OBJECT

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);
// window.addEventListener("keydown", herocontrol); // moved to AFTER ohero.move()
function init()
{
    canvas=document.getElementById("cvGame");
    engine=new BABYLON.Engine(canvas);

    scene=new BABYLON.Scene(engine);
    light=new BABYLON.HemisphericLight("sun", new BABYLON.Vector3(0,1,0), scene);
    ground=BABYLON.MeshBuilder.CreateGround("mGround", {width:1000,height:1000}, scene);
    ground.material = new BABYLON.StandardMaterial("gMat", scene);
    ground.material.diffuseColor=new BABYLON.Color3(0,.5,0);

    hero = new ohero();
    mons = new omons(hero.mesh); // now mesh, since hero is an object
    boom = new oboom(mons.mesh);

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover=false;
    engine.runRenderLoop(gameloop);
}
function gameloop()
{
    if (!gameover) {
        mons.move();
        boom.move();
    }
    scene.render();
}

function herocontrol(event)
{
    // all moved to ohero.move this function can be deleted now
}

omons=function(hm) { // hero's MESH as input
    this.hero = hm;
    this.mesh= BABYLON.MeshBuilder.CreateCylinder("cMons", {height:10,diameterBottom:10,diameterTop:0}, scene);
    this.mesh.material=new BABYLON.StandardMaterial("mMons", scene);
    this.mesh.material.diffuseColor=new BABYLON.Color3(1,0,0);
    this.mesh.position=new BABYLON.Vector3(20, 5, 0);
    this.mesh.rotation.z=-Math.PI/2;
    this.dx=this.dz=0;
    this.spd=0.05;
    this.rotspd=1;
    this.hit=this.dead=false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.dx=this.hero.position.x-this.mesh.position.x;
        this.dz=this.hero.position.z-this.mesh.position.z;
        this.ang=Math.atan2(this.dz,this.dx);

        this.dx = Math.cos(this.ang);
        this.dz = Math.sin(this.ang); 
        this.mesh.position.x+=this.dx*this.spd;
        this.mesh.position.z+=this.dz*this.spd; 
        this.mesh.rotation.y=-this.ang;
        if (this.mesh.intersectsMesh(this.hero,true)) {
            gameover=true;
            spWinLose.innerHTML="YOU DIED!";
        }
    }

    this.draw = function () {
    // for future animations
    }
}

oboom = function (mm) { // pass the monster mesh
    this.mons = mm; // mons is a mesh
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cBoom", { height: 10, diameterBottom: 4, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mBoom", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(1, .5, 0);
    this.mesh.position = new BABYLON.Vector3(0, 5, 20);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = this.dz = 0;
    this.spd = this.rotspd = 1;
    this.hit = this.dead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.mesh.position.x += this.dx * this.spd;
        this.mesh.position.z += this.dz * this.spd;
        if (this.mesh.intersectsPoint(this.mons.position)) {
            gameover = true;
            spWinLose.innerHTML = "YOU WIN";
        }
    }

    this.draw = function () {
    // for future enchancements
    }
}

ohero = function () {
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cHero", { height: 10, diameterBottom: 10, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mHero", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(0, 0, 1);
    this.mesh.position = new BABYLON.Vector3(-20, 5, 0);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = herodz = 0;
    this.spd = 1;
    this.rotspd = 5;
    this.hit = herodead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function (event) {
        switch (event.key) {
            case 'a':
                this.mesh.rotation.y -= Math.PI / 180 * this.rotspd;
                break;
            case 'd':
                this.mesh.rotation.y += Math.PI / 180 * this.rotspd;
                break;
            case 'w':
                this.ang = -this.mesh.rotation.y;
                this.spd = 1;
                this.dx = Math.cos(this.ang);
                this.dz = Math.sin(this.ang);
                this.mesh.position.x += this.dx * this.spd;
                this.mesh.position.z += this.dz * this.spd;
                break;
            case 's':
                this.ang = -this.mesh.rotation.y;
                this.spd = 1;
                this.dx = Math.cos(this.ang);
                this.dz = Math.sin(this.ang);
                this.mesh.position.x -= this.dx * this.spd;
                this.mesh.position.z -= this.dz * this.spd;
                break;
            case 't':
                camera.position.y = 200;
                camera.position.x = camera.position.z = 0;
                camera.rotation.x = Math.PI / 2;
                break;
            case 'Enter':
                boom.mesh.position.x = this.mesh.position.x;
                boom.mesh.position.z = this.mesh.position.z;
                boom.mesh.rotation.y = this.mesh.rotation.y;
                boom.ang = -boom.mesh.rotation.y;
                boom.dx = Math.cos(boom.ang);
                boom.dz = Math.sin(boom.ang);
                break;
        }
    }

    window.addEventListener("keydown", this.move.bind(this)); // this is magic

    this.draw = function () {
    }
}
</script>
<style>
body    {width:100%; height:100%; padding:0; margin:0; background:red;}
#cvGame {position:absolute;width:100%; height:100%; padding:0; margin:0; background:skyblue;}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<span id="spWinLose" style="position:absolute;bottom:0;right:0;">SIMPLEX</span>
</body>
</html>
Explanation. I saved the hero for last because the movement is controlled by the keyboard, and not automatic.  Just like monster and missile, I created an object for the hero (ohero). I moved the old init() code to the start of ohero, and the old herocontrol() code to the function move() inside of ohero.  Like monster and missile, I adjusted variable references to use the variables in the object (this.etc).
The trick to making the keyboard work, is this line:
    window.addEventListener(“keydown”, this.move.bind(this));
Here’s why you need it. The key down event has it’s own memory context, which gets stored in this, whenever move() is called back in response to a keydown. This callback context has none of your variables. So, you have to force the correct context to be put in this. That’s what bind does.
[For more info on bind, read Function.prototype.bind()]

 

STEP 11: REFACTORING TO ELIMINATE SOME GLOBALS & CONSTANTS

Okay so let’s do yet another refactoring to eliminate or minimize the use of globals and constants. The most obvious global in the objects is scene. The most obvious constant is the height of the cone. The positions of the objects are also hard coded. We can randomize position to some extent, but we will have to hardcode the range of random variables.
<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);
function init()
{
    canvas=document.getElementById("cvGame");
    engine=new BABYLON.Engine(canvas);

    scene=new BABYLON.Scene(engine);
    light=new BABYLON.HemisphericLight("sun", new BABYLON.Vector3(0,1,0), scene);
    ground=BABYLON.MeshBuilder.CreateGround("mGround", {width:1000,height:1000}, scene);
    ground.material = new BABYLON.StandardMaterial("gMat", scene);
    ground.material.diffuseColor=new BABYLON.Color3(0,.5,0);

    hero = new ohero(scene);
    mons = new omons(hero,scene); // now mesh, since hero is an object
    boom = new oboom(mons,scene);

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover=false;
    engine.runRenderLoop(gameloop);
}
function gameloop()
{
    if (!gameover) {
        mons.move();
        boom.move();
    }
    scene.render();
}

function herocontrol(event)
{
    // all moved to ohero.move
}

omons=function(h, scene) { // hero's object as input
    this.hero = h.mesh;
    this.h = 10;
    this.maxw = this.maxd = 200;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cMons", { height: this.h, diameterBottom: this.h, diameterTop: 0 }, scene);
    this.mesh.material=new BABYLON.StandardMaterial("mMons", scene);
    this.mesh.material.diffuseColor=new BABYLON.Color3(1,0,0);
    this.mesh.position = new BABYLON.Vector3(Math.random()*this.maxw-this.maxw/2, this.h/2, Math.random()*this.maxd-this.maxd/2);
    this.mesh.rotation.z=-Math.PI/2;
    this.dx=this.dz=0;
    this.spd=0.05;
    this.rotspd=1;
    this.hit=this.dead=false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.dx=this.hero.position.x-this.mesh.position.x;
        this.dz=this.hero.position.z-this.mesh.position.z;
        this.ang=Math.atan2(this.dz,this.dx);

        this.dx = Math.cos(this.ang);
        this.dz = Math.sin(this.ang); 
        this.mesh.position.x+=this.dx*this.spd;
        this.mesh.position.z+=this.dz*this.spd; 
        this.mesh.rotation.y=-this.ang;
        if (this.mesh.intersectsMesh(this.hero,true)) {
            gameover=true;
            spWinLose.innerHTML="YOU DIED!";
        }
    }

    this.draw = function () {
    // for future animations
    }
}

oboom = function (m, scene) { // pass the monster 
    this.mons = m.mesh; // mons is a mesh
    this.h = 10;
    this.maxw = this.maxd = 100;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cBoom", { height: this.h, diameterBottom: this.h/2.5, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mBoom", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(1, .5, 0);
    this.mesh.position = new BABYLON.Vector3(20, this.h/2, 20);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = this.dz = 0;
    this.spd = this.rotspd = 1;
    this.hit = this.dead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.mesh.position.x += this.dx * this.spd;
        this.mesh.position.z += this.dz * this.spd;
        if (this.mesh.intersectsPoint(this.mons.position)) {
            gameover = true;
            spWinLose.innerHTML = "YOU WIN";
        }
    }

    this.draw = function () {
    // for future enchancements
    }
}

ohero = function (scene) {
    this.h = 10;
    this.maxw = this.maxd = 100;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cHero", { height: this.h, diameterBottom: this.h, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mHero", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(0, 0, 1);
    this.mesh.position = new BABYLON.Vector3(0, this.h/2, 0);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = herodz = 0;
    this.spd = 1;
    this.rotspd = 5;
    this.hit = herodead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function (event) {
        switch (event.key) {
            case 'a':
                this.mesh.rotation.y -= Math.PI / 180 * this.rotspd;
                break;
            case 'd':
                this.mesh.rotation.y += Math.PI / 180 * this.rotspd;
                break;
            case 'w':
                this.ang = -this.mesh.rotation.y;
                this.spd = 1;
                this.dx = Math.cos(this.ang);
                this.dz = Math.sin(this.ang);
                this.mesh.position.x += this.dx * this.spd;
                this.mesh.position.z += this.dz * this.spd;
                break;
            case 's':
                this.ang = -this.mesh.rotation.y;
                this.spd = 1;
                this.dx = Math.cos(this.ang);
                this.dz = Math.sin(this.ang);
                this.mesh.position.x -= this.dx * this.spd;
                this.mesh.position.z -= this.dz * this.spd;
                break;
            case 't':
                camera.position.y = 200;
                camera.position.x = camera.position.z = 0;
                camera.rotation.x = Math.PI / 2;
                break;
            case 'Enter':
                boom.mesh.position.x = this.mesh.position.x;
                boom.mesh.position.z = this.mesh.position.z;
                boom.mesh.rotation.y = this.mesh.rotation.y;
                boom.ang = -boom.mesh.rotation.y;
                boom.dx = Math.cos(boom.ang);
                boom.dz = Math.sin(boom.ang);
                break;
        }
    }

    window.addEventListener("keydown", this.move.bind(this)); // this is magic

    this.draw = function () {
    }
}
</script>
<style>
body    {width:100%; height:100%; padding:0; margin:0; background:red;}
#cvGame {position:absolute;width:100%; height:100%; padding:0; margin:0; background:skyblue;}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<span id="spWinLose" style="position:absolute;bottom:0;right:0;">SIMPLEX</span>
</body>
</html>

 

STEP 12: AN EXAMPLE OF THE POWER OF OBJECTS: LIMITING BULLETS

One problem with our game is that missiles fly on forever until the player hits the fire key (Enter). With objects, fixing this is easy.
<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);
// window.addEventListener("keydown", herocontrol); // moved to AFTER ohero.move()
function init()
{
    canvas=document.getElementById("cvGame");
    engine=new BABYLON.Engine(canvas);

    scene=new BABYLON.Scene(engine);
    light=new BABYLON.HemisphericLight("sun", new BABYLON.Vector3(0,1,0), scene);
    ground=BABYLON.MeshBuilder.CreateGround("mGround", {width:1000,height:1000}, scene);
    ground.material = new BABYLON.StandardMaterial("gMat", scene);
    ground.material.diffuseColor=new BABYLON.Color3(0,.5,0);

    hero = new ohero(scene);
    mons = new omons(hero,scene); // now mesh, since hero is an object
    boom = null;

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover=false;
    engine.runRenderLoop(gameloop);
}
function gameloop()
{
    if (!gameover) {
        mons.move();
        if (boom != null)
            boom.move();
    }
    scene.render();
}

function herocontrol(event)
{
    // all moved to ohero.move
}

omons=function(h, scene) { // hero's object as input
    this.hero = h.mesh;
    this.h = 10;
    this.maxw = this.maxd = 200;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cMons", { height: this.h, diameterBottom: this.h, diameterTop: 0 }, scene);
    this.mesh.material=new BABYLON.StandardMaterial("mMons", scene);
    this.mesh.material.diffuseColor=new BABYLON.Color3(1,0,0);
    this.mesh.position = new BABYLON.Vector3(Math.random() * this.maxw - this.maxw / 2, this.h / 2, Math.random() * this.maxd - this.maxd / 2);
    this.mesh.rotation.z=-Math.PI/2;
    this.dx=this.dz=0;
    this.spd=0.05;
    this.rotspd=1;
    this.hit=this.dead=false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        this.dx=this.hero.position.x-this.mesh.position.x;
        this.dz=this.hero.position.z-this.mesh.position.z;
        this.ang=Math.atan2(this.dz,this.dx);

        this.dx = Math.cos(this.ang);
        this.dz = Math.sin(this.ang); 
        this.mesh.position.x+=this.dx*this.spd;
        this.mesh.position.z+=this.dz*this.spd; 
        this.mesh.rotation.y=-this.ang;
        if (this.mesh.intersectsMesh(this.hero,true)) {
            gameover=true;
            spWinLose.innerHTML="YOU DIED!";
        }
    }

    this.draw = function () {
    // for future animations
    }
}

oboom = function (m, h, scene) { // pass the monster 
    this.mons = m.mesh; // mons is a mesh
    this.sx = h.mesh.position.x; // starting x & z
    this.sz = h.mesh.position.z;
    
    this.h = 10;
    this.maxw = this.maxd = 100;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cBoom", { height: this.h, diameterBottom: this.h/2.5, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mBoom", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(1, .5, 0);
    this.mesh.position = new BABYLON.Vector3(20, this.h/2, 20);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = this.dz = 0;
    this.spd = this.rotspd = 1;
    this.hit = this.dead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function () {
        if (Math.sqrt(Math.pow(this.mesh.position.x - this.sx, 2) + Math.pow(this.mesh.position.z - this.sz, 2)) < this.maxd) {
            this.mesh.position.x += this.dx * this.spd;
            this.mesh.position.z += this.dz * this.spd;
        } else {
            boom = null;
            this.mesh.dispose();
        }

        if (this.mesh.intersectsPoint(this.mons.position)) {
            gameover = true;
            spWinLose.innerHTML = "YOU WIN";
        }
    }

    this.draw = function () {
    // for future enchancements
    }
}

ohero = function (scene) {
    this.h = 10;
    this.maxw = this.maxd = 100;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cHero", { height: this.h, diameterBottom: this.h, diameterTop: 0 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mHero", scene);
    this.mesh.material.diffuseColor = new BABYLON.Color3(0, 0, 1);
    this.mesh.position = new BABYLON.Vector3(0, this.h/2, 0);
    this.mesh.rotation.z = -Math.PI / 2;
    this.dx = herodz = 0;
    this.spd = 1;
    this.rotspd = 5;
    this.hit = herodead = false;
    this.mesh.computeWorldMatrix(true);

    this.move = function (event) {
        switch (event.key) {
            case 'a':
                this.mesh.rotation.y -= Math.PI / 180 * this.rotspd;
                break;
            case 'd':
                this.mesh.rotation.y += Math.PI / 180 * this.rotspd;
                break;
            case 'w':
                this.ang = -this.mesh.rotation.y;
                this.spd = 1;
                this.dx = Math.cos(this.ang);
                this.dz = Math.sin(this.ang);
                this.mesh.position.x += this.dx * this.spd;
                this.mesh.position.z += this.dz * this.spd;
                break;
            case 's':
                this.ang = -this.mesh.rotation.y;
                this.spd = 1;
                this.dx = Math.cos(this.ang);
                this.dz = Math.sin(this.ang);
                this.mesh.position.x -= this.dx * this.spd;
                this.mesh.position.z -= this.dz * this.spd;
                break;
            case 't':
                camera.position.y = 200;
                camera.position.x = camera.position.z = 0;
                camera.rotation.x = Math.PI / 2;
                break;
            case 'Enter':
                if (boom == null) {
                    boom = new oboom(mons, hero, scene);
                
                    boom.mesh.position.x = this.mesh.position.x;
                    boom.mesh.position.z = this.mesh.position.z;
                    boom.mesh.rotation.y = this.mesh.rotation.y;
                    boom.ang = -boom.mesh.rotation.y;
                    boom.dx = Math.cos(boom.ang);
                    boom.dz = Math.sin(boom.ang);
                }
                break;
        }
    }

    window.addEventListener("keydown", this.move.bind(this)); // this is magic

    this.draw = function () {
    }
}
</script>
<style>
body    {width:100%; height:100%; padding:0; margin:0; background:red;}
#cvGame {position:absolute;width:100%; height:100%; padding:0; margin:0; background:skyblue;}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<span id="spWinLose" style="position:absolute;bottom:0;right:0;">SIMPLEX</span>
</body>
</html>

Explanation. Instead of instantiating the missile in init(), we set boom to null, and in the gameloop(), boom.move is only called if boom is not null. The oboom object take a hero as input and remembers the starting position of the hero in new variables sx and sz. The distance the bullet travels will be controlled by maxd. If the missile is less than maxd units from sx and sz, it continues, otherwise the missile is disappears via dispose() and boom is set to null.  The missile is now instantiated in the hero’s move(), when the user hits the Enter key, but only if there is not currently a missile displayed (if (boom==null)…).

Thus, the player can only shoot one missile at a time, and must wait for the missile to disappear before shooting another one.