3D Arcade Simplex Game: Base

screenshot-275 screenshot-276

INTRODUCTION

Here is the final base code for the 3D Arcade Simplex Game. Most of the knowledge of the ancient arcade game developers (lol) is contained in this code. You really should go back and read the previous pages to understand how the code works.  Make your goal be: to generate this code from scratch, without looking. This will force you to understand the code at a conceptual level.

Note: this is teaching, not production code. The goal is directness and clarity. So, for example, I referenced html tags directly instead of document.GetElementById.

INSTALLATION

But if you just want to play with the code and noodle around, the steps are:

  1. Create a folder on your computer
  2. Right-click & download Babylon.max.js into this folder *** IMPORTANT THIS IS THE GAME ENGINE ***
  3. Open a text editor and copy the code into the text editor
  4. Save the file as simplex.html. This file MUST BE IN THE SAME FOLDER as babylon.max.js
  5. Double click on the file to open it up in a browser; Or, start up your favorite browser, type Ctrl-O, and open file to play the game.

Don’t skip any steps!

Controls

  • W,A,S,D: Forward, Rotate counter-clockwise, Backwards, Rotate clockwise
  • Enter: Fire
  • T: Top View
  • Arrow Keys—Up, Down, Left Right: Zoom In,Out; Pan Left, Right
  • Left-mouse click & drag in window: Orbit
  • Ctrl-R: Restart the game

 

<!doctype html>
<html>
<head>
<script src="babylon.max.js"></script>
<script>
/*
 * Copyright (c) Nick V. Flor, 2014-2017, All rights reserved
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * If you use this code for research, you must cite me in your paper references.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This material is based partly upon work supported by the National Science Foundation (NSF)
 * under both ECCS - 1231046 and CMMI - 1635334. Any opinions, findings, and conclusions or 
 * recommendations expressed in this material are those of the author and do not necessarily
 * reflect the views of the NSF.
 */
</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);     
            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 () {
    }
}
</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>