// ==== 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.// ====================================================================================================================// Putting it all together - How to make a touch game for mobile// Based on Complex World and Touch World // touch drag of objects, touch hit of objects // mouse drag of objects, mouse click on objects // Splash screen (so click can auto-start audio) // Behind the splash screen, it is loading resources while waiting for you to click // Mobile:// Touch drag to move agent // Touch tap to "zap" baby// Touch pinch camera (built in)// Desktop:// Mouse drag to move agent // Mouse click to "zap" baby// Mouse scroll camera (built in)// ============================================================================================= // To do my own mouse/touch: // Over-ride certain "ABHandler" functions. // =============================================================================================// Name inspired by "Sonic Death Monkey" in "High Fidelity"// ===================================================================================================================// === Start of tweaker's box ======================================================================================== // ===================================================================================================================// The easiest things to modify are in this box.// You should be able to change things in this box without being a JavaScript programmer.// Go ahead and change some of these. What's the worst that could happen?
AB.clockTick =100;// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps =1000000;// make it so run in practice never ends until user wants// if do put in a maximum run, 1000 is a pretty long run // Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep =200;// Take screenshot on this step. (All resources should have finished loading.) Default 50.
AB.drawRunControls =false;// Scrap the Run/Step/Pause controls//---- global constants: -------------------------------------------------------// Switch between 3d and 2d viewconst show3d =false;const TEXTURE_WALL ='/uploads/starter/door.jpg';const TEXTURE_MAZE ='/uploads/starter/latin.jpg';const TEXTURE_AGENT ='/uploads/starter/pacman.jpg';// credits:// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons// http://en.wikipedia.org/wiki/File:Inscription_displaying_apices_(from_the_shrine_of_the_Augustales_at_Herculaneum).jpgconst TEXTURE_BABY ='/uploads/starter/baby.1.png';const TEXTURE_DEAD ='/uploads/starter/ghost.3.png';// https://commons.wikimedia.org/wiki/Category:Baby_icons// https://commons.wikimedia.org/wiki/File:Emojione_1F476.svg// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_iconsconst SOUND_MAMA ='/uploads/starter/mama.mp3';const SOUND_SCREAM ='/uploads/starter/scream.2.mp3';// http://soundbible.com/544-Girl-Saying-Mommy.html// http://soundbible.com/tags-scream.html// http://soundbible.com/700-Screaming.html// http://soundbible.com/1102-Child-Scream.htmlconst MUSIC_BACK ='/uploads/starter/nursery.2.mp3';// https://www.dl-sounds.com/royalty-free/category/children/// https://www.dl-sounds.com/royalty-free/abc-song/// https://www.dl-sounds.com/royalty-free/carolans-dream/ const SOUND_ALARM ='/uploads/starter/air.horn.mp3';const gridsize =20;// number of squares along side of world const NOBOXES =Math.trunc ((gridsize * gridsize)/10);// density of maze - number of internal boxesconst squaresize =100;// size of square in pixelsconst MAXPOS = gridsize * squaresize;// length of one side in pixels const SKYCOLOR =0xddffdd;// a number, not a string const startRadiusConst = MAXPOS *1;// distance from centre to start the camera atconst maxRadiusConst = MAXPOS *10;// maximum distance from camera we will render things // CSS style used for score output:const scoreStyle ="font-weight: bold; font-family: Segoe Print, Comic Sans, Impact, Bolton;";const NONEWBABIES =2;// how many babies to make when I kill one const SCORE_KILL =500;// add this to score for every kill of a baby const SCORE_CLOSE =-10;// add this to score for every step close to a baby //--- change ABWorld defaults: -------------------------------ABHandler.MAXCAMERAPOS = maxRadiusConst ;ABHandler.GROUNDZERO =true;// "ground" exists at altitude zeroABWorld.drawCameraControls =false;//--- skybox: -------------------------------// mountain skybox, credit:// http://stemkoski.github.io/Three.js/Skybox.html/*
const SKYBOX_ARRAY = [
"/uploads/starter/dawnmountain-xpos.png",
"/uploads/starter/dawnmountain-xneg.png",
"/uploads/starter/dawnmountain-ypos.png",
"/uploads/starter/dawnmountain-yneg.png",
"/uploads/starter/dawnmountain-zpos.png",
"/uploads/starter/dawnmountain-zneg.png"
];
*/const SKYBOX_ARRAY =["/uploads/starter/nursery.1.jpg","/uploads/starter/nursery.1.jpg","/uploads/starter/nursery.1.jpg","/uploads/starter/nursery.1.jpg","/uploads/starter/nursery.1.jpg","/uploads/starter/nursery.1.jpg"];// nursery wallpaper credit:// https://www.arthouse.com/shop/wallpaper/colour/neutral/bear-hugs-mint/// https://commons.wikimedia.org/wiki/Category:Children%27s_book_illustrations // ===================================================================================================================// === End of tweaker's box ==========================================================================================// ===================================================================================================================// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.// can write CSS to page // document.write (of CSS at least) is good at the moment we include the JS, not later
document.write (`<style>.mybutton
{
border-radius:0px;
background-color: darkslategrey;}.mybutton:hover { background-color: darkcyan;}</style>`);// we have a splash screen// behind the splash screen, newRun is running, loading resources // do not start the run loop until resources ready AND splash screen is dismissed var resourcesLoaded =false;var splashClicked =false;const ACTION_LEFT =0;const ACTION_RIGHT =1;const ACTION_UP =2;const ACTION_DOWN =3;const ACTION_STAYSTILL =4;// in initial view, (smaller-larger) on i axis is aligned with (left-right)// in initial view, (smaller-larger) on j axis is aligned with (away from you - towards you)// contents of a grid squareconst GRID_BLANK =0;const GRID_WALL =1;const GRID_MAZE =2;const GRID_AGENT =3;const GRID_BABY =4;const GRID_DEAD =5;var BOXHEIGHT;// 3d or 2d box height var GRID =newArray( gridsize );// will in fact be a 2D array, see later var theagent;var BABIES =newArray(1);// start at 1 and increase using array.push()var wall_texture, agent_texture, baby_texture, dead_texture, maze_texture;// agent position var ai, aj;var score;function loadResources()// asynchronous file loads - call initScene() when all finished {var loader1 =new THREE.TextureLoader();var loader2 =new THREE.TextureLoader();var loader3 =new THREE.TextureLoader();var loader4 =new THREE.TextureLoader();var loader5 =new THREE.TextureLoader();
loader1.load ( TEXTURE_WALL,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
wall_texture = thetexture;if( asynchFinished()) initScene();// if all file loads have returned });
loader2.load ( TEXTURE_AGENT,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
agent_texture = thetexture;if( asynchFinished()) initScene();});
loader3.load ( TEXTURE_BABY,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
baby_texture = thetexture;if( asynchFinished()) initScene();});
loader4.load ( TEXTURE_MAZE,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
maze_texture = thetexture;if( asynchFinished()) initScene();});
loader5.load ( TEXTURE_DEAD,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
dead_texture = thetexture;if( asynchFinished()) initScene();});}function asynchFinished()// all file loads returned {if( wall_texture && agent_texture && baby_texture && dead_texture && maze_texture )returntrue;elsereturnfalse;}//--- grid system -------------------------------------------------------------------------------// my numbering is 0 to gridsize-1function occupied ( i, j )// is this square occupied{return( GRID[i][j]!= GRID_BLANK );}// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates// logically, coordinates are: y=0, x and z all positive (no negative) // logically my dimensions are all positive 0 to MAXPOS// to centre everything on origin, subtract (MAXPOS/2) from all dimensions function translate ( i, j ){var v =new THREE.Vector3();
v.y =0;
v.x =( i * squaresize )-( MAXPOS/2);
v.z =( j * squaresize )-( MAXPOS/2);return v;}function initScene()// all file loads have returned {var i,j, shape, thecube;// set up GRID as 2D arrayfor( i =0; i < gridsize ; i++)
GRID[i]=newArray(gridsize);// set up wallsfor( i =0; i < gridsize ; i++)for( j =0; j < gridsize ; j++)if(( i==0)||( i==gridsize-1)||( j==0)||( j==gridsize-1)){
GRID[i][j]= GRID_WALL;
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: wall_texture });
thecube.position.copy ( translate(i,j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube);}else
GRID[i][j]= GRID_BLANK;// set up maze for(var c=1; c <= NOBOXES ; c++){
i = AB.randomIntAtoB(1,gridsize-2);// inner squares are 1 to gridsize-2
j = AB.randomIntAtoB(1,gridsize-2);
GRID[i][j]= GRID_MAZE ;
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: maze_texture });
thecube.position.copy ( translate(i,j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube);}// set up first baby
BABIES[0]= newBaby();// set up agent // start in random locationdo{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
ai = i;
aj = j;
GRID[i][j]= GRID_AGENT;
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theagent =new THREE.Mesh( shape );
theagent.material =new THREE.MeshBasicMaterial({ map: agent_texture });
theagent.position.copy ( translate(ai,aj));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(theagent);// finally skybox ABWorld.scene.background =new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY,function(){ABWorld.render();
console.log ("Resources loaded.");
resourcesLoaded =true;if( resourcesLoaded && splashClicked )
AB.runReady =true;// start run loop });}// --- babies -----------------------------------function newBaby(){var i, j;do{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
GRID[i][j]= GRID_BABY;var shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );var thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: baby_texture });
thecube.position.copy ( translate(i,j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube);return(newArray( thecube, i, j ));// save it for later, array, object plus i,j position}function newBabies()// make NONEWBABIES new babies {for(var c=1; c <= NONEWBABIES ; c++){var baby = newBaby();
BABIES.push ( baby );// add to array }}function moveBabies(){for(var c =0; c < BABIES.length; c++){var thebaby = BABIES[c][0];var ei = BABIES[c][1];var ej = BABIES[c][2];var i, j;// move towards agent // put some randomness in so it won't get stuck with barriers if( ei < ai ) i = AB.randomIntAtoB(ei, ei+1);if( ei == ai ) i = ei;if( ei > ai ) i = AB.randomIntAtoB(ei-1, ei);if( ej < aj ) j = AB.randomIntAtoB(ej, ej+1);if( ej == aj ) j = ej;if( ej > aj ) j = AB.randomIntAtoB(ej-1, ej);if(! occupied(i,j))// if no obstacle then move, else just miss a turn for this baby {
GRID[ei][ej]= GRID_BLANK;
GRID[i][j]= GRID_BABY;
thebaby.position.copy ( translate(i,j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
BABIES[c]=newArray( thebaby, i, j );// update list of positions }}}// --- agent -----------------------------------// movement caused by user actions function moveAgent( a ){var i = ai;var j = aj;if( a == ACTION_LEFT ) i--;elseif( a == ACTION_RIGHT ) i++;elseif( a == ACTION_UP ) j++;elseif( a == ACTION_DOWN ) j--;if(! occupied(i,j))// else just miss a turn {
GRID[ai][aj]= GRID_BLANK;
GRID[i][j]= GRID_AGENT;
ai = i;
aj = j;
theagent.position.copy ( translate(ai,aj));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates }}// --- user actions - keyboard and touch handling functions: ----------------------------------------var OURKEYS =[37,38,39,40];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 ==37) moveAgent ( ACTION_LEFT );if( event.keyCode ==38) moveAgent ( ACTION_DOWN );if( event.keyCode ==39) moveAgent ( ACTION_RIGHT );if( event.keyCode ==40) moveAgent ( ACTION_UP );
event.stopPropagation(); event.preventDefault();returnfalse;}var startX, startY;var dragevents;// number of events in the current drag function initDrag ( x, y )// x,y position on screen{if(! AB.runReady )returntrue;// not ready yet
startX = x;
startY = y;
dragevents =0;
tryHitBabies ( x, y );// check if this is a tap not a drag - and if it hits a baby };function drag ( x, y )// compare with previous x,y position on screen to get direction of drag{if(! AB.runReady )returntrue;// not ready yet if(( dragevents %4)==0)// slow it down to respond to every nth event - too many events{if( x > startX ) moveAgent ( ACTION_RIGHT );elseif( x < startX ) moveAgent ( ACTION_LEFT );if( y > startY ) moveAgent ( ACTION_UP );elseif( y < startY ) moveAgent ( ACTION_DOWN );}
dragevents++;
startX = x;
startY = y;};// --- kill babies and make new ones ------------------------------------------------------------------- function tryHitBabies ( x, y )// we did an x,y touch/click, did it hit a baby {for(var c =0; c < BABIES.length; c++){var thebaby = BABIES[c][0];var ei = BABIES[c][1];var ej = BABIES[c][2];if(ABWorld.hitsObject ( x, y, thebaby ))// detect hit object {
soundScream();// make a noise
score = score + SCORE_KILL ;// increase score
GRID[ei][ej]= GRID_DEAD ;// stationary obstacle - basically part of the maze now
thebaby.material =new THREE.MeshBasicMaterial({ map: dead_texture });// remove thebaby from array, so it will no longer move
BABIES.splice(c,1);// make new baby/babies:
newBabies();return;// assume only can hit one baby }}}// --- score and status: -----------------------------------function babyClose()// is a baby close to (within one square of) the agent// note because of the wall, co-ordinates at +1 and -1 always exist {if( GRID[ai-1][aj-1]== GRID_BABY )returntrue;if( GRID[ai-1][aj]== GRID_BABY )returntrue;if( GRID[ai-1][aj+1]== GRID_BABY )returntrue;if( GRID[ai][aj-1]== GRID_BABY )returntrue;if( GRID[ai][aj]== GRID_BABY )returntrue;if( GRID[ai][aj+1]== GRID_BABY )returntrue;if( GRID[ai+1][aj-1]== GRID_BABY )returntrue;if( GRID[ai+1][aj]== GRID_BABY )returntrue;if( GRID[ai+1][aj+1]== GRID_BABY )returntrue;returnfalse;}function agentBlocked()// agent is blocked on the 4 compass sides (not diagonal){return( occupied (ai-1,aj)&&
occupied (ai+1,aj)&&
occupied ( ai,aj+1)&&
occupied ( ai,aj-1));}
AB.world.newRun =function(){
score =0;if( show3d ){
BOXHEIGHT = squaresize;ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );}else{
BOXHEIGHT =1;ABWorld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR );}// newRun can run behind splash screen // do not start run loop until resources ready AND splash screen is dismissed
AB.runReady =false;
loadResources();// aynch file loads // calls initScene() when it returns // redirect keyboard and touch event handling to my own functions:
document.onkeydown = keyHandler;// override ABHandler default (which is camera control) to use my own functions:
myControls();// Toggle between my mouse/touch controls and default mouse/touch controls var buttons =" <p> <button onclick='myControls();' class='ab-normbutton mybutton' >My controls </button> "+" <button onclick='ABHandler.defaultTouch(); ABHandler.defaultMouse();' class='ab-normbutton mybutton' >Default controls </button> </p> ";
AB.msg ( buttons,3);};function myControls(){ABHandler.initTouchDrag = initDrag;ABHandler.touchDrag = drag
ABHandler.initMouseDrag = initDrag;ABHandler.mouseDrag = drag
}
AB.world.nextStep =function(){for(var c =0; c < BABIES.length; c++)if(Math.random()<0.05)
soundMama();// baby noises now and then, increasing as no. of babies increases if(( AB.step %2)==0) moveBabies();// slow babies down to every nth stepif( babyClose()) score = score + SCORE_CLOSE;// lose points every step you are close to a baby
AB.msg (" Step: "+ AB.step +" Score: <span style='"+ scoreStyle +"'>"+ score +"</span>");if( agentBlocked())// if agent blocked in, run over {
AB.abortRun =true;
musicPause();
soundAlarm();}};
AB.world.endRun =function(){
musicPause();if( AB.abortRun ) AB.msg (" <br> <font color=red> <B> You are trapped. You lose! </B> </font> ",2);else AB.msg (" <br> <font color=green> <B> Run over. You survived! </B> </font> ",2);};// --- background music, play once ----------------------------------------// note mute button only mutes the background music // would need to design my own mute button to mute the sound effects var backmusic;function initMusic()// called by user interaction {
backmusic = AB.backgroundMusic ( MUSIC_BACK );}function musicPlay(){ backmusic.play();}function musicPause(){ backmusic.pause();}// --- sound effects, called randomly by JS ----------------------------------------var thealarm =newAudio( SOUND_ALARM );var mama1 =newAudio( SOUND_MAMA );var mama2 =newAudio( SOUND_MAMA );var mama3 =newAudio( SOUND_MAMA );var mama4 =newAudio( SOUND_MAMA );var mama5 =newAudio( SOUND_MAMA );var scream1 =newAudio( SOUND_SCREAM );var scream2 =newAudio( SOUND_SCREAM );var scream3 =newAudio( SOUND_SCREAM );var scream4 =newAudio( SOUND_SCREAM );var scream5 =newAudio( SOUND_SCREAM );function soundAlarm(){
thealarm.play();}function soundMama()// allow multiple sounds at same time {if(! AB.audioIsPlaying ( mama1 )){ mama1.play();return;}if(! AB.audioIsPlaying ( mama2 )){ mama2.play();return;}if(! AB.audioIsPlaying ( mama3 )){ mama3.play();return;}if(! AB.audioIsPlaying ( mama4 )){ mama4.play();return;}if(! AB.audioIsPlaying ( mama5 )){ mama5.play();return;}// else var a =newAudio( SOUND_MAMA );
a.play();}function soundScream()// allow multiple sounds at same time{if(! AB.audioIsPlaying ( scream1 )){ scream1.play();return;}if(! AB.audioIsPlaying ( scream2 )){ scream2.play();return;}if(! AB.audioIsPlaying ( scream3 )){ scream3.play();return;}if(! AB.audioIsPlaying ( scream4 )){ scream4.play();return;}if(! AB.audioIsPlaying ( scream5 )){ scream5.play();return;}// else var a =newAudio( SOUND_SCREAM );
a.play();}// --- Splash screen --------------------------------------------------------------function instructionsHtml()// HTML format string of instructions for splash screen {var s ="The babies look cute, but beware, they are <font color=red><b>zombies!</b></font> Stop the babies before they kill you! ";if( AB.onDesktop()) s = s +" Desktop instructions: Arrow keys or mouse drag to move. Click baby to kill it. ";else s = s +" Mobile instructions: Touch drag to move. Touch baby to kill it. ";
s = s + SCORE_KILL +" points for killing a baby. "+ SCORE_CLOSE +" points for every step you are close to a baby.";return( s );}// display splash screen with instructions
AB.newSplash ( instructionsHtml());// touch on splash screen button will mark multiple audio objects as good for JS to call without further user interaction
AB.splashClick (function(){
thealarm.play(); thealarm.pause();
mama1.play(); mama1.pause();
mama2.play(); mama2.pause();
mama3.play(); mama3.pause();
mama4.play(); mama4.pause();
mama5.play(); mama5.pause();
scream1.play(); scream1.pause();
scream2.play(); scream2.pause();
scream3.play(); scream3.pause();
scream4.play(); scream4.pause();
scream5.play(); scream5.pause();
initMusic();
AB.removeSplash();// remove splash screen // ready to start run loop?
splashClicked =true;if( resourcesLoaded && splashClicked )
AB.runReady =true;// start run loop });