3D Arcade Simplex Game: More Variations

INTRODUCTION

Even more variations, all based on Arcade Simplex Game: Final. Remember to run these you must:

  1. Copy and paste the code into any text editor and save file with a .html extension.
  2. Have babylon.max.js (right-click save as babylon.max.js) in the same folder as your files.
  3. Double click on the file (in folder view), to run it.

 

THREE MISSILES

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

var things;
//
// INITIALIZATION
//
function init()
{
    things = new 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);
    maxmons = orgmons = 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;
    starttime = Date.now();
    engine.runRenderLoop(gameloop);
}
//
// GAME LOOP
//
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();
}
//
// OBJECTS
//
omons=function(h, scene) {
    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;
        }
        if (this.hit) {
            this.monsindex = things.indexOf(this);
            this.mesh.dispose();
            things.splice(this.monsindex, 1);     
            maxmons = maxmons - 1; 
        }
    }

    this.draw = function () {
    }
}

oboom = function (m, h, scene) {
    this.mons = m.mesh; 
    this.sx = h.mesh.position.x; 
    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); 
            things.splice(this.boomindex, 1);      
            boom = null;
            this.mesh.dispose();
        }

        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; 
                this.boomindex = things.indexOf(this); 
                things.splice(this.boomindex, 1);      
                boom = null;
                this.mesh.dispose();
            }
        }
    }

    this.draw = function () {
    }
}

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 = this.dz = 0;
    this.spd = 1;
    this.rotspd = 5;
    this.hit = this.dead = 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) {
                    spread=15;
                    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);

                    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+spread*Math.PI/180);                     
                    boom.dx = Math.cos(boom.ang);
                    boom.dz = Math.sin(boom.ang);
                    boom.mesh.rotation.y = -boom.ang;
                   

                    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-spread*Math.PI/180); 
                    boom.dx = Math.cos(boom.ang);
                    boom.dz = Math.sin(boom.ang);
                    boom.mesh.rotation.y = -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. Firing three missiles is quite straightforward. In the hero.move(), modify the key case for fire, to send three missiles out instead of one—each missile rotated by some angle controlled by the variable spread.

 

SPLIT-HIT MISSILES

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

var things;
//
// INITIALIZATION
//
function init()
{
    things = new 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);
    maxmons = orgmons = 20; 
    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);
}
//
// GAME LOOP
//
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();
}
//
// OBJECTS
//
omons=function(h, scene) {
    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;
        }
        if (this.hit) {
     
            this.monsindex = things.indexOf(this);
            things.splice(this.monsindex, 1);
            explode_monster(this);     
            this.mesh.dispose();
            maxmons = maxmons - 1;             
        }
    }

    this.draw = function () {
    }
}

oboom = function (m, h, scene) {
    this.mons = m.mesh; 
    this.sx = h.mesh.position.x; 
    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); 
            things.splice(this.boomindex, 1);      
            boom = null;
            this.mesh.dispose();
        }

        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; 
                this.boomindex = things.indexOf(this); 
                things.splice(this.boomindex, 1);      
                boom = null;
                this.mesh.dispose();
            }
        }
    }

    this.draw = function () {
    }
}

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 = this.dz = 0;
    this.spd = 1;
    this.rotspd = 5;
    this.hit = this.dead = 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 () {
    }
}

function explode_monster(mons)
{
    var a, dang, maxd;
    
    dang = 45; // dang*n=360; examples: 30,45,90,180
    maxd = 25;
    for (a=0;a<360;a+=dang) {        
        b = new oboom(mons, hero, scene);
        b.mesh.position.x = mons.mesh.position.x;
        b.mesh.position.z = mons.mesh.position.z;
        b.mesh.rotation.y = mons.mesh.rotation.y;
        b.ang = a*Math.PI/180;
        b.mesh.rotation.y=-b.ang;
        b.dx = Math.cos(b.ang);
        b.dz = Math.sin(b.ang);
        b.sx=mons.mesh.position.x; // reset starting missile position to monster
        b.sz=mons.mesh.position.z; 
        b.maxd=maxd; // limit exploding missiles
        things.push(b);
    }

}
</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. Once again, we reuse the functionality of the missile object (oboom).  The changes were as following:

  1. In init(), I upped the default monsters from 3 to 20. This was so I had more monsters to cascade-hit.
  2. In the omons.move(), when the monster is hit, I call a function explode_monster, passing the mons as a parameter.
  3. The function explode_monster(…), sprays bullets in centered around the monster. The number of bullets sprayed depends on dang (default: 45, for 8 sprayed/spawned bullets), and the distance these sprayed bullets travels depends on maxd (default: 25).

Note: Yes, there’s a bug where it sometimes leaves a missile or bullet. I suspect I will have to refactor oboom to not accept a monster as a parameter so I can call explode_monster with just a position.

 

NORMAL EXPLOSION

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

var things;
//
// INITIALIZATION
//
function init()
{
    things = new 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);
    maxmons = orgmons = 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;
    starttime = Date.now();
    engine.runRenderLoop(gameloop);
}
//
// GAME LOOP
//
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();
}
//
// OBJECTS
//
omons=function(h, scene) {
    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;
        }
        if (this.hit) {
     
            this.monsindex = things.indexOf(this);
            things.splice(this.monsindex, 1);
            explode_monster(this);     
            this.mesh.dispose();
            maxmons = maxmons - 1;             
        }
    }

    this.draw = function () {
    }
}

oboom = function (m, h, scene) {
    this.mons = m.mesh; 
    this.sx = h.mesh.position.x; 
    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); 
            things.splice(this.boomindex, 1);      
            boom = null;
            this.mesh.dispose();
        }

        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; 
                this.boomindex = things.indexOf(this); 
                things.splice(this.boomindex, 1);      
                boom = null;
                this.mesh.dispose();
            }
        }
    }

    this.draw = function () {
    }
}

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 = this.dz = 0;
    this.spd = 1;
    this.rotspd = 5;
    this.hit = this.dead = 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 () {
    }
}

oball = function (m, h, scene) {
    this.mons = m.mesh; 
    this.sx = m.mesh.position.x;  // note: explosions start at monster
    this.sz = m.mesh.position.z;  //       unlike missiles at hero
    
    this.h = 5;
    this.maxw = this.maxd = 10;
    this.mesh = BABYLON.MeshBuilder.CreateCylinder("cBall", { height: this.h, diameterBottom: this.h/2, diameterTop: this.h/2 }, scene);
    this.mesh.material = new BABYLON.StandardMaterial("mBall", 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.dist= Math.sqrt(
                   Math.pow(this.mesh.position.x - this.sx, 2) 
                 + Math.pow(this.mesh.position.z - this.sz, 2)); 
        if (this.dist< this.maxd) {
            this.mesh.position.x += this.dx * this.spd;
            this.mesh.position.z += this.dz * this.spd;
        } else {
            this.ballindex = things.indexOf(this); 
            things.splice(this.ballindex, 1);      
            boom = null;
            this.mesh.dispose();
        }
    }

    this.draw = function () {
    }
}

function explode_monster(mons)
{
    var a;

    for (a=0;a<360;a+=45) {        
        b = new oball(mons, hero, scene);
        b.mesh.position.x = mons.mesh.position.x;
        b.mesh.position.z = mons.mesh.position.z;
        b.mesh.rotation.y = mons.mesh.rotation.y;
        b.ang = a*Math.PI/180;
        b.mesh.rotation.y=-b.ang;
        b.dx = Math.cos(b.ang);
        b.dz = Math.sin(b.ang);
        things.push(b);
    }
}
</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. Like the hit-split missile variation, when a monster is hit, it calls an explode_monster function.  However, this time, instead of reusing the oboom object, explode_monster creates an oball object. It originally was a sphere, hence oball, but the final implementation turned out to be a thin cylinder. The oball object behaves like a missile (oboom), but it starts at the monster, has no collision detection with monsters, and disappears after it travels maxd units (default: 10).

 

INFINITE MONSTERS (UNTIL YOU DIE)

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

var things;
function init()
{
    things = new 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=null;
    orgmons = 1;
    deadmon = 0;
    init_level();
    
    camera =new BABYLON.FreeCamera("mCam", new BABYLON.Vector3(0,6,-50), scene);
    camera.attachControl(canvas);

    gameover = false;
    starttime = Date.now();
    engine.runRenderLoop(gameloop);
}

function init_level()
{
    for (var i=0;i<things.length;i++) { // clear previous level
        tp=things.pop();
        if (tp instanceof ohero)
            things.push(tp); // push the hero back!
        else
            tp.mesh.dispose();
    }

    if (hero==null) {
        hero = new ohero(scene);
        things.push(hero);
    }
    
    orgmons=orgmons*2;
    maxmons = orgmons; 
    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;
    
    gameover = false;
}

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

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

        if (maxmons == 0 || hero.hit) {
            gameover = true;
            if (maxmons == 0) {
                spWinLose.innerHTML = "YOU WIN";
                init_level();
            } else
                spWinLose.innerHTML = "YOU LOSE";
        }        
    }
    scene.render();
}

omons=function(h, scene) {
    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;
        }
        if (this.hit) {
            this.monsindex = things.indexOf(this);
            things.splice(this.monsindex, 1);     
            this.mesh.dispose();
            maxmons = maxmons - 1; 
            deadmon = deadmon + 1;
        }
    }

    this.draw = function () {
    }
}

oboom = function (m, h, scene) {
    this.mons = m.mesh; 
    this.sx = h.mesh.position.x; 
    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); 
            things.splice(this.boomindex, 1);      
            boom = null;
            this.mesh.dispose();
        }

        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; 
                this.boomindex = things.indexOf(this); 
                things.splice(this.boomindex, 1);      
                boom = null;
                this.mesh.dispose();
            }
        }
    }

    this.draw = function () {
    }
}

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. The general idea is to take out the creation of the hero and monster in init(), and put it in a function name init_level(). The first thing init_level() tries to do is loop through and remove any left-over object from the previous level that is not an object of type hero (ohero). Technically there should be no leftover objects in the simplex game BUT if you added the multiple missile variations, you may have leftover objects. The other changes are as follows:

  • In init(), must set hero to null initially so that init_level() creates it. The variable orgmon and new variable deadmon are also initialized. The variable deadmon counts the number of monsters killed now.
  • In init_level(), the number of monsters doubles each time this function is called.
  • In gameloop(), if the player clears out all the monsters in the current level, the code calls init_level() to double the number of monsters.  The spMons div tag is set to the number of monsters killed, instead of the monsters remaining, like it used to be.
  • In omons().move(), deadmon is incremented by 1 if a monster dies.