3D Arcade Simplex Game: Object Refactoring 2 of 2

INTRODUCTION

Let’s finish this! We have a nice example of 1 hero, 1 monster, and 1 missile. How do we extend to multiple monsters and multiple bullets (sorry multiple heroes is beyond the scope of this tutorial). Let’s review the previous tutorial’s code, and then we’ll refactor it to use a list to hold our objects. That will allows us to have multiple monsters & missiles.

<!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>

Review. When we left off, with one hero, one monster, and one missile—all separate objects declared near the bottom of the code, which makes it easy to modify.  The missile (boom) is now created dynamically in the hero’s move function. The missile now travels a short distance from the hero before disappearing, and the hero can only fire one missile on the screen at a time.

So with that brief review. Let’s see how to create multiple monsters.

 

STEP 13: REFACTORING USING LISTS FOR OBJECTS

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);

var things; // This will be a global array of all objects (things) in our game
function init()
{
    things = new Array(); // create an empty array
    
    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);
    things.push(hero); // everytime you create an object, push it onto the list (array)
    mons = new omons(hero, scene);
    things.push(mons); // again, push any object if you want to see it
    
    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(); // THIS CODE IS NOT NEEDED NOW THAT OBJECTS ARE IN A LIST
        //if (boom != null)
        //    boom.move();
        for (i = 0; i < things.length; i++) {
            if ((things[i] instanceof ohero)==false)
                things[i].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 {
            this.boomindex = things.indexOf(boom); // find boom in list
            things.splice(this.boomindex, 1);       // remove from list
            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);
                    things.push(boom);
                    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. Going roughly top to bottom

  • var things; first you declare a global array (list) of things accessible to every function
  • things.push(OBJECT); anytime you create an object, push it onto the list
  • In the gameloop(); Loop through everything in the list, and move.  The exception is the hero object, which has it’s own move function based on keyboard control.
  • In oboom; When a missile reaches its maximum discuss you also need to add code to remove it from the list. These two lines find the index of the missile on the list, and remove it using the splice function.

 

STEP 14: EXTENDING TO MULTIPLE MONSTERS USING LISTS

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);

var things; // This will be a global array of all objects (things) in our game
function init()
{
    things = new Array(); // creat an empty array
    
    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);
    things.push(hero); // everytime you create an object, push it onto the list (array)

    maxmons = 3;
    for (i = 0; i < maxmons; i++) {
        mons = new omons(hero, scene);
        things.push(mons); // again, push any object if you want to see it
    }

    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();
        for (i = 0; i < things.length; i++) {
            if ((things[i] instanceof ohero)==false)
                things[i].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!";
        }
        if (this.hit) {
            this.monsindex = things.indexOf(this); // find mons in list
            things.splice(this.monsindex, 1);       // remove mons from list
            this.mesh.dispose();
            maxmons = maxmons - 1; // game over condition is now here
            if (maxmons == 0) {
                gameover = true;
                spWinLose.innerHTML = "YOU WIN";
            }
        }
    }

    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 {
            this.boomindex = things.indexOf(this); // find boom in list
            things.splice(this.boomindex, 1);       // remove from list
            boom = null;
            this.mesh.dispose();
        }

        //if (this.mesh.intersectsPoint(this.mons.position)) {
        //    gameover = true;
        //    spWinLose.innerHTML = "YOU WIN";
        //}
        for (this.i = 0; this.i < things.length; this.i++) {
            if ((things[this.i] instanceof omons) &&
                this.mesh.intersectsPoint(things[this.i].mesh.position)) {
                things[this.i].hit = true; // let the monster remove itself
                this.boomindex = things.indexOf(this); // find boom in list
                things.splice(this.boomindex, 1);       // remove from list
                boom = null;
                this.mesh.dispose();
            }
        }
    }

    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);
                    things.push(boom);
                    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.

  • In init(): First create a variable describing how many monsters you want on the screen (maxmons). I choose 3 to start off.
  • In oboom(): You can’t just check for a single mons. Now the code loops through all the things in the list, looking for monsters. If a monster is found, the missile checks to see if it intersects the monster. If a collision is detected, the missile is removed from the list, and the monster is flagged as hit.
  • Important note in oboom(): Note we do not end the game once the missile hits the monster because with multiple monsters the game should end only if all monsters are dead. Nor does the missile remove the monster from the list. Objects by convention, should NOT remove other objects from the list, only themselves. So the game ending code has been moved to the monster object.
  • In omonster(): I added a new code chunk to check if the monster’s hit flag is set. If it is, then the monster is removed from the thing list, and disposed of.  Once disposed, maxmons is reduced by 1. If maxmons hits zero, it means end of game.
  • Finally, in oboom, I refactored the code to use indexOf(this) versus indexOf(boom). This tiny change will allow me to fire multiple missiles in a future revision.

 

 

STEP 15: SCORING AND ENDGAME REFACTORING

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
window.addEventListener("DOMContentLoaded", init);

var things; // This will be a global array of all objects (things) in our game
function init()
{
    things = new Array(); // creat an empty array
    
    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);
    things.push(hero); // everytime you create an object, push it onto the list (array)

    maxmons = orgmons = 3; // added orgmons to help w/ scoring 
    for (i = 0; i < maxmons; i++) {
        mons = new omons(hero, scene);
        things.push(mons); // again, push any object if you want to see it
    }

    boom = null;
   

    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);
    gameover = false;
    starttime = Date.now();
    engine.runRenderLoop(gameloop);
}

function gameloop()
{
    if (!gameover) {
        for (i = 0; i < things.length; i++) {
            if ((things[i] instanceof ohero)==false)
                things[i].move();
        }

        spMons.innerHTML = (orgmons-maxmons);
        elapsed = Date.now() - starttime;
        spTime.innerHTML = (elapsed / 1000).toFixed(2);

        if (maxmons == 0 || hero.hit) {
            gameover = true;
            if (maxmons == 0)
                spWinLose.innerHTML = "YOU WIN";
            else
                spWinLose.innerHTML = "YOU LOSE";
        }        
    }
    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)) {
            hero.hit = true;
            //gameover = true;
            //spWinLose.innerHTML="YOU DIED!";
        }
        if (this.hit) {
            this.monsindex = things.indexOf(this); // find mons in list
            things.splice(this.monsindex, 1);       // remove mons from list
            this.mesh.dispose();
            maxmons = maxmons - 1; // game over condition is now here
            //if (maxmons == 0) {
            //    gameover = true;
            //    spWinLose.innerHTML = "YOU WIN";
            //}
        }
    }

    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 {
            this.boomindex = things.indexOf(this); // find boom in list
            things.splice(this.boomindex, 1);       // remove from list
            boom = null;
            this.mesh.dispose();
            //    gameover = true;
            //    spWinLose.innerHTML = "YOU WIN";            
        }

        //if (this.mesh.intersectsPoint(this.mons.position)) {
        //    gameover = true;
        //    spWinLose.innerHTML = "YOU WIN";
        //}
        for (this.i = 0; this.i < things.length; this.i++) {
            if ((things[this.i] instanceof omons) &&
                this.mesh.intersectsPoint(things[this.i].mesh.position)) {
                things[this.i].hit = true; // let the monster remove itself
                this.boomindex = things.indexOf(this); // find boom in list
                things.splice(this.boomindex, 1);       // remove from list
                boom = null;
                this.mesh.dispose();
            }
        }
    }

    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);
                    things.push(boom);
                    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;}
.score  {position: absolute; top: 0; right: 0; width: 100%; font:bold 24pt Arial; color:white; text-shadow:2px 2px #000000}
</style>
</head>
<body>
<canvas id="cvGame"></canvas>
<div id="spWinLose" class="score" style="text-align:center;">SIMPLEX</div>
<div id="spTime" class="score" style="text-align:left;">TIME</div>
<div id="spMons" class="score" style="text-align:right;">MONSTERS</div>
</body>
</html>

Explanation. I refactored the code so that instead of the monster object displaying the win/lose message, and setting the gameover flag, the monster merely indicates if the hero is hit. The gameloop() now displays the win/lose message and sets the gameover flag.  As for scoring:

  • I created a score style, which places all scoring information at the top of the game screen
  • To display game information, there are three DIV tags: spWinLose, spTime, and spMons. These display information about Win/Lose, time elapsed, and number of monsters killed, respectively.
  • Time elapsed is calculated by first recording the starting time (starttime) in init(). Then in the gameloop() the current time is subtracted from the starttime. This gives the time in milliseconds. Divide by 1000 to get the seconds and display in spTime.

And that’s the code! You now have the fundamentals for developing almost every kind of old arcade-level game. Much of this knowledge extends to more sophisticated 3D games.  At the very least, you can prototype the logic and gameplay of your more complex game as a simplex, then add the more complicated 3D models in place of the terrain and other polygon objects.