// Based from the starter world, can be found here: https://ancientbrain.com/world.php?world=4606438712// ==== Starter World =================================================================================================// This code is designed for use on the Ancient Brain site.// This code may be freely copied and edited by anyone on the Ancient Brain site.// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.// ====================================================================================================================var hitCount =0;
AB.clockTick =100;// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps =10000;// Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep =100;// Take screenshot on this step. (All resources should have finished loading.) Default 50.
AB.drawRunControls =false;// Scrap the Run/Step/Pause controls// Global Variables and Constants.var zombieAmount =3;// number of Zombies in the world.const zombieObject ='/uploads/goobert/zombie.obj';// object for the zombie model, can be found here: https://3dexport.com/free-3dmodel-minecraft-zombie-406441.htmconst floorTextureFile ="/uploads/goobert/grass2.png";// texture for the floor texture. can be found here: https://www.filterforge.com/filters/11635-v7.htmlconst steveScale =70;// multiply model sizes by some amount:const zombieTexture ='/uploads/goobert/zombie.png';// material texture for the zombie model, can be found here: https://3dexport.com/free-3dmodel-minecraft-zombie-406441.htmvar zombieScale = AB.randomFloatAtoB (0,0);// sets the zombie scale, not random just 0,0.const backgroundMusic ='/uploads/liam/mc.mp3';// the soundtrack for our game. can be found here:const maxPos =3500;// length of one side of the arena const skyColor =0xffffcc;// a number, not a string const lightColor =0xffffff;const startRadiusConst = maxPos ;// distance from centre to start the camera atconst maxRadiusConst = maxPos *10;// maximum distance from camera we will render things var steveSpeed =0;// how much agent moves each step var zombieSpeed =0;// how much enemy moves each step const closeLimit =25;// this is as close as models come to other modelsconst mixers =[];const clock =new THREE.Clock();const timeClock =new THREE.Clock();// clock that tracks the time.var worldSize =10000;// sets the world size.const followY = steveScale *4;// going high up (or forward/back) means we get it out of agent's hair (makes it first person)const lookAtY = zombieScale *40;const cameraShift =- steveScale *40;// shift camera behind agent, looking past agent along line of sight function mainMenu(){// function to display the main screen splash screen.
AB.newSplash();// new splash menu created.
let splash = document.getElementById("splash-inner");
let play = document.getElementById("splashbutton");var howToPlay = document.createElement("p");// creates an element for the how to play title.
howToPlay.innerHTML ="HOW TO PLAY";// the text for the element.
howToPlay.style.fontSize ="25px";// changing the font size for the element.
howToPlay.style.font ="bold 20px helvetica";// changing the font.
howToPlay.style.color ="white";// changing the text color.var gameDescription = document.createElement("p");// creates an element for the game description
gameDescription.innerHTML ="Compete with another player to see who can survive the longest!<br> Select a difficulty to spawn the zombies and survive from the zombies for as long as you can! <br> The Zombies are bigger and faster depending on the difficulty you select.";
gameDescription.style.color ="white";
gameDescription.style.font =" 20px helvetica";
gameDescription.style.fontSize ="18px";var controlsTitle = document.createElement("p");
controlsTitle.innerHTML ="CONTROLS";
controlsTitle.style.fontSize ="25px";
controlsTitle.style.font ="bold 20px helvetica";
controlsTitle.style.color ="white";var controlsText = document.createElement("p");
controlsText.innerHTML ="Move around with either the <b>W, S, A, D</b> keys or the <b>UP, DOWN, LEFT, RIGHT </b> arrow keys.<br> Select one of the difficulties: <font color=green><b>EASY</b></font>, <font color=orange><b>NORMAL</b></font>, <font color=red><b>HARD</b></font>, each setting is more difficult then the last!<br> Try switching between camera modes while playing to change the experience!";
controlsText.style.color ="white";
controlsText.style.font =" 20px helvetica";
controlsText.style.fontSize ="18px";
splash.appendChild(howToPlay);
splash.appendChild(gameDescription);
splash.appendChild(controlsTitle);
splash.appendChild(controlsText);
play.addEventListener("click", beginGame);// when the button is clicked, the beginGame function is called.
splash.appendChild(play);// adds the button start button to the splash page.if(AB.socket){if(AB.socket.connected){
console.log("Socket has been connected successfully.");// Shows in the console when the sockets have connected.}}
document.getElementById("splash").style.backgroundImage ="url('https://64.media.tumblr.com/cd1fd504e7e1a49662e5375422c873b2/tumblr_pq62oludPv1y7ei2ho1_540.gif')";// adds background image the main menu.
document.getElementById("splash").style.backgroundSize ="cover";// sets the background image to cover the entire splash.}function death(){// this function is called when the player has no more lives.// /uploads/goobert/minecrafthit.mp3var deathSound =newAudio('/uploads/liam/damage.mp3');// play damage sound everytime hitcounter is 4.
deathSound.play();
steve.position.y =0;// steve's player model is modev back to the middle.
steve.position.x =0;
steve.position.z =0;
steveSpeed =0// steve's speed is set to 0 so the player cannot move while the splash screen is on screen.
AB.newSplash();// new splash screen created.
let splash = document.getElementById("splash-inner");
let span = document.getElementById("splashbutton");
span.innerHTML ="Play again";// button to play again.
document.getElementById("splash").style.backgroundImage ="url('https://media3.giphy.com/media/8d45jqPbtUcyA/giphy.gif?cid=ecf05e47w67psxanmothzr8t2b79ki3ffgx40lo0y6o3qw57&rid=giphy.gif&ct=g')";// adds background image with entire splash death screen
document.getElementById("splash").style.backgroundSize ="cover";// sets the background image to cover the entire splash.
span.addEventListener("click", beginGame);// when the restart button is clicked the beginGame function is executed.var deathMessage = document.createElement("p");
deathMessage.innerHTML ="YOU DIED!";
deathMessage.style.color ="red";
deathMessage.style.font ="bold 20px helvetica";
deathMessage.style.fontSize ="50px";
splash.append(deathMessage);var deathText = document.createElement("p");
deathText.innerHTML ="You have ran out of lives and lost. Play again by clicking the 'Play Again' button above.";
deathText.style.color ="white";
deathText.style.font ="bold 20px helvetica";
deathText.style.fontSize ="25px";
splash.appendChild(deathText);
zombieSpeed =0// freezes the zombies in place until updated when game is restarted.
steve.rotation.z =Math.PI /1// faces steve model in the default direction (forward).for(var i =0; i < zombieAmount ; i++)// loops for all zombies and changes their scale.{
zombieArray[i].scale.set(0,0,0);// sets the zombies to 0 so invisible.}}ABHandler.MAXCAMERAPOS = maxRadiusConst ;ABHandler.GROUNDZERO =true;// "ground" exists at altitude zeroconst SKYBOX_ARRAY =[// array for the skybox "/uploads/starter/sky_pos_z.jpg","/uploads/starter/sky_neg_z.jpg","/uploads/starter/sky_pos_y.jpg","/uploads/starter/sky_neg_y.jpg","/uploads/starter/sky_pos_x.jpg","/uploads/starter/sky_neg_x.jpg"];var zombieArray =newArray( zombieAmount );var steve, zombie;var steveRotation =0;var enemyRotation =0;var ZOMBIE_texture;function loadResources()// asynchronous file loads - call initScene() when all finished {var loader =new THREE.OBJLoader(new THREE.LoadingManager());// load ZOMBIE - OBJ that we will paint with a texture
loader.load ( zombieObject,function( object ){
zombie = object;// canonical enemy object - may be copied multiple times if( asynchFinished()) initScene();});
THREE.DefaultLoadingManager.addHandler (/\.tga$/i,new THREE.TGALoader());// load textures var loader2 =new THREE.TextureLoader();
loader2.load ( zombieTexture,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
ZOMBIE_texture = thetexture;if( asynchFinished()) initScene();});}var difficulty ="<p align=\"center\"><button id='easyButton', style=\"height:50px;width:100px;\"><b>EASY</b></button><button id='normalButton', style=\"height:50px;width:100px\"><b>NORMAL</b></button><button id='hardButton', style=\"height:50px;width:100px\"><b>HARD</b></button></p>"// buttons are created for the run menu, IDs are used to add style.var chooseDifficulty ="<p align=\"center\"><div id='chooseDifficulty', <b>CHOOSE A DIFFICULTY TO PLAY:</b></div></p>"function setDifficulty()// function to change the difficulty of the game.{
$("#user_span4").html( chooseDifficulty );// adds to the run menu.
$("#user_span5").html( difficulty );
easyButton.style.background ='#4CAF63';// color for the easy button
normalButton.style.background ='#FA9E2A';// color for the normal button
hardButton.style.background ='#C43737';// color for the hard button
document.getElementById("chooseDifficulty").style.font ="20px Helvetica ";
document.getElementById("chooseDifficulty").style.fontStyle ="bold";
document.getElementById("easyButton").style.font ="17px Helvetica ";
document.getElementById("normalButton").style.font ="17px Helvetica ";
document.getElementById("hardButton").style.font ="17px Helvetica ";
document.getElementById("easyButton").addEventListener("click", easyMode);// checks if the button with the ID of easyButton is pressed, if so the easyMode is set (function is called)
document.getElementById("normalButton").addEventListener("click", normalMode);// checks if the button with the ID of normalButton is pressed, if so the normalMode is set (function is called)
document.getElementById("hardButton").addEventListener("click", hardMode);// checks if the button with the ID of hardButton is pressed, if so the hardMode is set (function is called)}function loadModels()// this function loads models.{const loader =new THREE.GLTFLoader();// gltf loader is used to load animated model.const onLoad =( gltf, position )=>{var model = gltf.scene.children[0];// the model is the first child of the scene
model.position.copy( position );// set the position of the modelvar animation = gltf.animations[1];var animationIdle = gltf.animations[0];// get the animation from the gltf objectconst mixer =new THREE.AnimationMixer( model );
mixers.push( mixer );const action = mixer.clipAction( animation );
action.play();ABWorld.scene.add( model );
steve = model;
model.scale.set(5,5,5);
initScene();
lighting();};var stevePosition =new THREE.Vector3(0,0,0);const mutantPosition =new THREE.Vector3(10,0,-60);
loader.load ('/uploads/liam/steve.glb', glb => onLoad ( glb, stevePosition ));}function asynchFinished()// all file loads returned {if( ZOMBIE_texture && zombie && steve )returntrue;elsereturnfalse;}function initScene()// all file loads have returned {
setLookatFollow();// set up lookat and follow in direction steve is looking in
zombie.traverse (function( child ){if( child instanceof THREE.Mesh)
child.material.map = ZOMBIE_texture ;});
zombie.scale.multiplyScalar ( zombieScale );for(var i =0; i < zombieAmount ; i++){var object = zombie.clone();// zombie model is cloned.
object.position.y =0;// level with the floor x and z randomized for randomized spawning locations.
object.position.x = AB.randomPick ( AB.randomIntAtoB (-maxPos/2,-maxPos/3), AB.randomIntAtoB ( maxPos/3, maxPos/2));
object.position.z = AB.randomPick ( AB.randomIntAtoB (-maxPos/2,-maxPos/3), AB.randomIntAtoB ( maxPos/3, maxPos/2));ABWorld.scene.add( object );// adds the ZOMBIE enemies
zombieArray[i]= object;// save in array for later}}function lighting()// this function is called on the first new run of the game.{
steve.scale.multiplyScalar ( steveScale );// sets steve's scaleABWorld.scene.background =new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY,function()// skybox{ABWorld.render();// skybox
AB.removeLoading();// removes loading splash
AB.runReady =true;});}// --- lookat and follow -----------------------------------function setLookatFollow()// set up lookat and follow {// follow - camera position // start with agent centre, then adjust it by small amount ABWorld.follow.copy ( steve.position );ABWorld.follow.y = followY ;// shifted forward/back along line linking agent with direction it is facing ABWorld.follow.x =ABWorld.follow.x +( cameraShift *Math.sin(steveRotation));ABWorld.follow.z =ABWorld.follow.z +( cameraShift *Math.cos(steveRotation));// lookat - look at point in distance along line we are facing// start with agent centre, then adjust it along line by huge amount ABWorld.lookat.copy ( steve.position );ABWorld.lookat.y = lookAtY ;ABWorld.lookat.x =ABWorld.lookat.x +((startRadiusConst *3)*Math.sin(steveRotation));ABWorld.lookat.z =ABWorld.lookat.z +((startRadiusConst *3)*Math.cos(steveRotation));}// --- enemy move -----------------------------------function angleTwoPoints ( x1, y1, x2, y2 ){returnMath.atan2 ( x2 - x1, y2 - y1 );}function dist ( a, b )// distance between them when a,b are Three vectors {return( a.distanceTo ( b ));}var lives =5;var lives2 =0;function collision ( proposedMove, k )// proposed move FOR ZOMBIE k{if( dist ( proposedMove, steve.position )< closeLimit )
hitCount +=1;// updates every tick the enemey is inside the range of the close limit// console.log(hitCount);if( hitCount ==2)// if the player has been hit 4 times, the game is over (around being hit for 0.5 second){
lives -=1;
hitCount =0;// hit count is resit back to zerovar hitSound =newAudio('/uploads/goobert/minecrafthit.mp3');// play damage sound everytime hitcounter is 4.
hitSound.play();}if( lives ===0)// if the player has no lives left, the death menu is displayed.{
death();// calling the death function
lives =1000;}for(var i=0; i < zombieAmount ; i++)if( i != k )if( dist ( proposedMove, zombieArray[i].position )< closeLimit )returntrue;// else returnfalse;}function moveEnemy(){for(var i =0; i < zombieAmount ; i++){var e = zombieArray[i];
enemyRotation = angleTwoPoints ( e.position.x, e.position.z, steve.position.x, steve.position.z );// rotate enemy to face agent
e.rotation.set(0, enemyRotation +85,0);// move along that line, if no collision with any other model var proposedMove =new THREE.Vector3(
e.position.x +( zombieSpeed *Math.sin(enemyRotation)),
e.position.y,
e.position.z +( zombieSpeed *Math.cos(enemyRotation)));if(! collision ( proposedMove, i ))
e.position.copy ( proposedMove );// else enemy i just misses a turn}}//--- key control of agent -------------------------------------------------------------const KEY_UP =38;const KEY_LEFT =37;const KEY_RIGHT =39;const KEY_DOWN =40;const SPACEBAR =32;const KEY_W =87;const KEY_A =65;const KEY_S =83;const KEY_D =68;var pressedcount =0;// counts how long the key is pressed for.var OURKEYS =[ KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_W, KEY_A, KEY_S, KEY_D ];function ourKeys ( event ){return( OURKEYS.includes ( event.keyCode ));}function keyHandler ( event ){if(! AB.runReady )returntrue;// not ready yet // if not handling this key, send it to default: if(! ourKeys ( event ))returntrue;// else handle it and prevent default:if( event.keyCode == KEY_UP || event.keyCode == KEY_W)// if the Up arrow or W key is pressed.{
pressedcount = pressedcount +1;// updates the pressed count each time the W key is pressed
console.log(pressedcount)// logs the pressing to console.if( pressedcount >8)// only play the sound every 8 key presses to avoid the sound effect overlapping.{
pressedcount =0;// resets the pressedcountvar forwardSound =newAudio('/uploads/liam/walk_forward.mp3');// play the walking forward sound effect
forwardSound.setLoop =(false);
forwardSound.setVolume =(0.1);
forwardSound.play();// audio is played}if( steve.position.z >-(worldSize /2)){// console.log("up", (worldSize / 2) );
steve.rotation.z =0;// face the agent backwards
steveRotation = steve.rotation.z;
steve.position.z = steve.position.z -( steveSpeed *Math.cos(steveRotation));// moves the agent in the direction its facing (backwards)}}if( event.keyCode == KEY_DOWN || event.keyCode == KEY_S)// if the down arrow or S key is pressed.{
pressedcount = pressedcount +1;// updates the pressed count each time the W key is pressed
console.log(pressedcount)// logs the pressing to console.if( pressedcount >8)// only play the sound every 8 key presses to avoid the sound effect overlapping.{
pressedcount =0;// resets the pressedcountvar backwardsSound =newAudio('/uploads/liam/walk_backward.mp3');// play the walking forward sound effect
backwardsSound.setLoop =(false);
backwardsSound.setVolume =(0.1);
backwardsSound.play();// audio is played}if( steve.position.z <(worldSize /2)){// console.log("down", steve.position.z);
steve.rotation.z =Math.PI /1;// face the agent forward
steveRotation = steve.rotation.z;
steve.position.z = steve.position.z -( steveSpeed *Math.cos(steveRotation));// moves the agent in the direction its facing (forward)}}if( event.keyCode == KEY_LEFT || event.keyCode == KEY_A)// if the left arrow or A key is pressed.{
pressedcount = pressedcount +1;// updates the pressed count each time the W key is pressed
console.log(pressedcount)// logs the pressing to console.if( pressedcount >8)// only play the sound every 8 key presses to avoid the sound effect overlapping.{
pressedcount =0;// resets the pressedcountvar leftSound =newAudio('/uploads/liam/walk_left.mp3');// play the walking forward sound effect
leftSound.setLoop =(false);
leftSound.setVolume =(0.1);
leftSound.play();// audio is played}if(steve.position.x >-(worldSize /2)){// console.log("left", steve.position.x);
steve.rotation.z =Math.PI /2;// face the agent left
steveRotation = steve.rotation.z;
steve.position.x = steve.position.x -( steveSpeed *Math.sin(steveRotation));// moves the agent in the direction its facing (left)}}if( event.keyCode == KEY_RIGHT || event.keyCode == KEY_D)// if the right arrow or D key is pressed.{
pressedcount = pressedcount +1;// updates the pressed count each time the W key is pressed
console.log(pressedcount)// logs the pressing to console.if( pressedcount >8)// only play the sound every 8 key presses to avoid the sound effect overlapping.{
pressedcount =0;// resets the pressedcountvar rightSound =newAudio('/uploads/liam/walk_right.mp3');// play the walking forward sound effect
rightSound.setLoop =(false);
rightSound.setVolume =(0.1);
rightSound.play();// audio is played}if(steve.position.x <(worldSize /2)){// console.log("right", steve.position.x);
steve.rotation.z =Math.PI /-2;// face the agent right
steveRotation = steve.rotation.z;
steve.position.x = steve.position.x -( steveSpeed *Math.sin(steveRotation));// moves the agent in the direction its facing (left) }}// if ( event.keyCode == SPACEBAR ) // {// steve.rotation.z = Math.PI / -2; // face the agent right// steveRotation = steve.rotation.z;// steve.position.y = steve.position.y - ( steveSpeed * Math.sin(steveRotation) ); // moves the agent in the direction its facing (left)// steve.position.y = steve.position.y + ( steveSpeed * Math.sin(steveRotation) ); // moves the agent in the direction its facing (left)// }
setLookatFollow();// lookat/follow depend on change in agent position/rotation: M.H
event.stopPropagation(); event.preventDefault();returnfalse;}function easyMode()// this function is called when Easy difficulty is selected.{
lives =10
steveSpeed =100
zombieSpeed =50;// makes the enemy speed 50% slower.for(var i =0; i < zombieAmount ; i++)// loops for all zombies and increases their size to 700x700x700{
zombieArray[i].scale.set(300,300,300);// initial zombie size is set.}}function normalMode()// this function is called when Normal difficulty is selected.{
lives =3
steveSpeed =100
zombieSpeed =80;// increased zombie movement speed.for(var i =0; i < zombieAmount ; i++)// loops for all zombies and increases their size to 700x700x700{
zombieArray[i].scale.set(500,500,500);}}function hardMode()// this function is called when Easy difficulty is selected.{
lives =1
steveSpeed =100
zombieSpeed =200;// makes the enemy speed much fasterfor(var i =0; i < zombieAmount ; i++){
zombieArray[i].scale.set(700,700,700);// zombie is larger then normal mode.}}
AB.world.nextStep =function(){
time_elapsed =Math.trunc(timeClock.getElapsedTime());// tracks the play time of the game.var playtime ="<p align=\"center\"><b>Playtime: "+ time_elapsed +"s</p>";
$("#user_span6").html( playtime );// inserts to the run menu.
moveEnemy();// enemies moves on their own clockconst delta = clock.getDelta();
mixers.forEach(( mixer )=>{ mixer.update( delta );});if(AB.socket){if(AB.socket.connected){if( lives2 >100){// if the users currently lives are over 100 that means the user is dead. as we set it to 1000 between rounds.
AB.msg("<p align=\"center\"> <font color=red> <B> Your lives: "+ lives +"<p align=\"center\"> <font color=blue> <B> Opponent's Lives: "+" Dead </B>");}if( lives2 <100){// if the user lives are not bigger then 100, that means real lives are in play and the value can be displayed.
AB.socketOut(lives)
AB.msg("<p align=\"center\"> <font color=red> <B> Your lives: </B>"+ lives +"<p align=\"center\"> <font color=blue> <B> Opponent's Lives: </B> "+ lives2);}}}};function beginGame(){// removes the splash menu when button is pressedvar clickSound =newAudio('/uploads/goobert/minecraft-click.mp3');// play minecraft click sound
clickSound.play();
steve.position.y =0;
steve.position.x =0;
steve.position.z =0;
initScene();
AB.removeSplash();
lives =5;}
AB.world.newRun =function()// function executed on first run.{
AB.socketStart();// sockets started
mainMenu();// main menu splash called.
AB.loadingScreen();
AB.runReady =false;ABWorld.init3d ( startRadiusConst, maxRadiusConst, skyColor );
loadResources();// aynch file loads // calls initScene() when it returns
AB.backgroundMusic ( backgroundMusic );// background music played
loadModels();
setDifficulty();// difficult options// lightvar ambient =new THREE.AmbientLight();ABWorld.scene.add( ambient );var thelight =new THREE.DirectionalLight( lightColor,3);
thelight.position.set( startRadiusConst, startRadiusConst, startRadiusConst );ABWorld.scene.add(thelight);varBlockSize=500;var floorGeometry =new THREE.PlaneBufferGeometry( worldSize, worldSize );
floorGeometry.rotateX(-Math.PI /2);// loading the floor texturevar floorTexture =new THREE.ImageUtils.loadTexture ( floorTextureFile );
floorTexture.minFilter = THREE.LinearFilter;
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;// world floor texture size and location
floorTexture.offset.set(0,0);
floorTexture.repeat.set( worldSize /BlockSize, worldSize /BlockSize);var floor =new THREE.Mesh(floorGeometry,new THREE.MeshBasicMaterial({map : floorTexture}));
floor.position.set(0,0,0);
threeworld.scene.add(floor);
document.onkeydown = keyHandler;};
AB.socketUserlist =function( array ){if(array.length >2){
AB.newSplash();
let splash = document.getElementById("splash-inner");
let tryagain = document.getElementById("splashbutton");var gameFull = document.createElement("p");
gameFull.innerHTML ="GAME HAS 2/2 PLAYERS PLEASE WAIT...";
gameFull.style.fontSize ="25px";
gameFull.style.font ="bold 20px helvetica";
gameFull.style.color ="white";
splash.appendChild(gameFull);
document.getElementById("splashbutton").innerHTML ="Try Again";
document.getElementById("splashbutton").onclick =function(){var clickSound =newAudio('/uploads/goobert/minecraft-click.mp3');// play minecraft click sound
clickSound.play();
location.reload();};}elseif(array.length <2){
AB.newSplash ();
let splash = document.getElementById("splash-inner");
let play = document.getElementById("splashbutton");var waitingTitle = document.createElement("p");
waitingTitle.innerHTML ="WAITING FOR ANOTHER PLAYER TO JOIN...";
waitingTitle.style.fontSize ="25px";
waitingTitle.style.font ="bold 20px helvetica";
waitingTitle.style.color ="white";
splash.appendChild(waitingTitle);// update the button from "start" to "Try Again"
document.getElementById("splashbutton").innerHTML ="Try Again";// reload the page when the button is clicked
document.getElementById("splashbutton").onclick =function(){var clickSound =newAudio('/uploads/goobert/minecraft-click.mp3');// play minecraft click sound
clickSound.play();
location.reload();};}
AB.socketUserlist =function(){};};
AB.socketIn =function(score){
lives2 = score;}