AB.maxSteps =1000000;
AB.drawRunControls =false;ABWorld.drawCameraControls =false;// set constantsconst skycolour ="lightblue";const boxcolor ="#bbbbbb";const movespeed =1;const delayTime =750;const OBJPATH ="/uploads/skellyc4/";// path of OBJ and MTLconst OBJNAME ="catMod.obj";const MTLNAME ="catMod-3.mtl";const SCALE_HERO =0.1;// set global variables
let moveForward =false;
let moveBackward =false;
let moveLeft =false;
let moveRight =false;
let redOn =false;
let hostStarted =false;
let actualChange =false;
let overallTime =0;
let consecutiveSameLightChanges =0;
let host;
let lightTime;
let startTime = performance.now();
let player;// create both the red and green light audios and set their volume, to be played when the lights changeconst greenLightChangeSound =newAudio("/uploads/adrian28/green-light-sound.wav");
greenLightChangeSound.volume =0.35;
greenLightChangeSound.loop =false;const redLightChangeSound =newAudio("/uploads/adrian28/red-light-sound.mp3");
redLightChangeSound.volume =0.15;
redLightChangeSound.loop =false;//loading in grass texturesconst textureLoader =new THREE.TextureLoader();//load base colour mapconst grassBaseColour = textureLoader.load("/uploads/skellyc4/Stylized_Grass_002_basecolor.jpg");
grassBaseColour.wrapS = THREE.RepeatWrapping;
grassBaseColour.wrapT = THREE.RepeatWrapping;
grassBaseColour.repeat.set(8,16);//load ambient occlusion mapconst grassAmbientOcclusionMap = textureLoader.load("/uploads/skellyc4/Stylized_Grass_002_ambientOcclusion.jpg");
grassAmbientOcclusionMap.wrapS = THREE.RepeatWrapping;
grassAmbientOcclusionMap.wrapT = THREE.RepeatWrapping;
grassAmbientOcclusionMap.repeat.set(8,16);//load displacement mapconst grassHeightMap = textureLoader.load("/uploads/skellyc4/Stylized_Grass_002_height.png");
grassHeightMap.wrapS = THREE.RepeatWrapping;
grassHeightMap.wrapT = THREE.RepeatWrapping;
grassHeightMap.repeat.set(8,16);//load normal mapconst grassNormalMap = textureLoader.load("/uploads/skellyc4/Stylized_Grass_002_normal.jpg");
grassNormalMap.wrapS = THREE.RepeatWrapping;
grassNormalMap.wrapT = THREE.RepeatWrapping;
grassNormalMap.repeat.set(8,16);//load roughness mapconst grassRoughnessMap = textureLoader.load("/uploads/skellyc4/Stylized_Grass_002_roughness.jpg");
grassRoughnessMap.wrapS = THREE.RepeatWrapping;
grassRoughnessMap.wrapT = THREE.RepeatWrapping;
grassRoughnessMap.repeat.set(8,16);//create light source (for textures)const directionalLight =new THREE.DirectionalLight(0xffffff,1);
directionalLight.position.x +=20;
directionalLight.position.y +=20;
directionalLight.position.z +=20;// create the floorconst floorGeometry =new THREE.PlaneGeometry(100,200,1024,2048);
floorGeometry.rotateX(-Math.PI /2);
floorGeometry.attributes.uv2 = floorGeometry.attributes.uv;// create the floor's materialconst floorMaterial =new THREE.MeshStandardMaterial({
map: grassBaseColour,
normalMap: grassNormalMap,
displacementMap: grassHeightMap,
displacementScale:0.4,
roughnessMap: grassRoughnessMap,
roughness:0.5,
aoMap: grassAmbientOcclusionMap
});// create the floorconst floor =new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.set(0,-20,-99);// create the red light and make it invisibleconst redLightGeometry =new THREE.BoxGeometry(20,20,5);const lightMaterial =new THREE.MeshBasicMaterial({ color:"red"});const redLight =new THREE.Mesh(redLightGeometry, lightMaterial);
redLight.position.set(0,-10,-196);
redLight.visible =false;// create the green lightconst greenLightGeometry =new THREE.BoxGeometry(20,20,5);const greenLightMaterial =new THREE.MeshBasicMaterial({ color:"green"});const greenLight =new THREE.Mesh(greenLightGeometry, greenLightMaterial);
greenLight.position.set(0,-10,-196);// create the finish lineconst finishLineGeometry =new THREE.BoxGeometry(100,0.8,1);const finishLineMaterial =new THREE.MeshBasicMaterial({ color:"blue"});const finishLine =new THREE.Mesh(finishLineGeometry, finishLineMaterial);
finishLine.position.set(0,-19.7,-175);
let m =new THREE.MTLLoader();
m.setResourcePath ( OBJPATH );
m.setPath ( OBJPATH );// create the player model as a cat then initialise the rest of the scene once we have the cat loaded infunction loadPlayer(){
m.load ( MTLNAME,function( materials ){
materials.preload();
let o =new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( OBJPATH );
o.load ( OBJNAME,function( object ){
theCat = object;
theCat.bumpScale =0.015;if( theCat ){player = theCat; initScene()}else(console.log("No cat :("));});});}// initialise the full scene once the cat is loaded infunction initScene(){// initialise a 3d worldABWorld.init3d(150,1500000, skycolour);// add the floor, red light, green light, finish line, player and a light source to the worldABWorld.scene.add(floor);ABWorld.scene.add(redLight);ABWorld.scene.add(greenLight);ABWorld.scene.add(finishLine);
player.position.x =0;
player.position.y =-18;
player.position.z =0;
player.rotation.y +=3.14159;
player.rotation.x +=1.5708;
player.scale.multiplyScalar ( SCALE_HERO );ABWorld.scene.add(player);ABWorld.scene.add(directionalLight);// set the camera position behind the player for use in MoveWith modeABWorld.lookat.copy(player.position);ABWorld.follow =new THREE.Vector3(player.position.x, player.position.y +6, player.position.z +10);// add event listeners for key presses and releases
document.addEventListener("keydown", onKeyDown,false);
document.addEventListener("keyup", onKeyUp,false);// set the default camera mode to MoveWith so we follow the playerABWorld.cameraMove();// remove the loading screen once everything has been added to the scene
AB.removeLoading();}// handle key pressesfunction onKeyDown(event){switch(event.code){case"KeyW":
moveForward =true;break;case"KeyA":
moveLeft =true;break;case"KeyS":
moveBackward =true;break;case"KeyD":
moveRight =true;break;}}// handle key releasesfunction onKeyUp(event){switch(event.code){case"KeyW":
moveForward =false;break;case"KeyA":
moveLeft =false;break;case"KeyS":
moveBackward =false;break;case"KeyD":
moveRight =false;break;}}// handle player movementfunction movementHandler(){if((moveForward || moveBackward || moveLeft || moveRight)&& redOn){// if the player is moving and the red light is on, reset the player's position
player.position.set(0,-18,-2);ABWorld.follow.set(player.position.x, player.position.y +6, player.position.z +10);}else{// otherwise, move the player in the direction they are pressing and update the camera's positionif(moveForward ===true){ABWorld.follow.z -= movespeed;
player.position.z -= movespeed;}if(moveBackward ===true&& player.position.z + movespeed <0){ABWorld.follow.z += movespeed;
player.position.z += movespeed;}if(moveLeft ===true&& player.position.x - movespeed >=-49){ABWorld.follow.x -= movespeed;
player.position.x -= movespeed;}if(moveRight ===true&& player.position.x + movespeed <=49){ABWorld.follow.x += movespeed;
player.position.x += movespeed;}}}// change the light visibility so that the red light is visible and the green light is invisiblefunction changeToRed(){
redLight.visible =true;// make the red light visible
greenLight.visible =false;// make the green light invisible}// change the light visibility so that the green light is visible and the red light is invisiblefunction changeToGreen(){
redLight.visible =false;// make the red light invisible
greenLight.visible =true;// make the green light visible}// handle the red lightfunction redLightFunction(){
AB.socketOut({"name":"lightChange","data":{"redOn":true,"actualChange": actualChange }});
redLightChangeSound.play();
setTimeout(function(){ changeToRed(); redOn =true;}, delayTime);}// handle the green lightfunction greenLightFunction(){
AB.socketOut({"name":"lightChange","data":{"redOn":false,"actualChange": actualChange }});
greenLightChangeSound.play();
setTimeout(function(){ changeToGreen(); redOn =false;}, delayTime);}// handle the light changesfunction changeLight(){
let currentTime = performance.now();
let occuranceDelta =(currentTime - lightTime)/1000;// get the time since the last light change// if the random boolean is true and the time since the last light change is greater than 3 seconds, change the lightif(AB.randomBoolean()&& occuranceDelta >=3){
let newColour = AB.randomPick("red","green");// randomly pick a colour to change the light to// based on the randomly chosen colour change the light to that colour either if it is not already that colour or if it has been that colour more than 3 times in a rowif(newColour ==="red"){if(redLight.visible ===false){
consecutiveSameLightChanges =0;
actualChange =true;
redLightFunction();}else{// if the red light is already visible
consecutiveSameLightChanges++;
actualChange =false;if(consecutiveSameLightChanges >=3){
consecutiveSameLightChanges =0;
actualChange =true;
greenLightFunction();}}}elseif(newColour ==="green"){if(greenLight.visible ===false){
consecutiveSameLightChanges =0;
actualChange =true;
greenLightFunction();}else{
consecutiveSameLightChanges++;
actualChange =false;if(consecutiveSameLightChanges >=3){
consecutiveSameLightChanges =0;
actualChange =true;
redLightFunction();}}}
lightTime = currentTime;// update the time of the last light change}}// check if the player has reached the finish linefunction checkLocation(){if(player.position.z <=-175){
AB.msg("<br><font color=red><B>Congratulations! You Win!</B></font>",2);
AB.abortRun =true;const runTime =Math.floor((performance.now()- startTime)/1000);
AB.newSplash();
AB.splashHtml("<h1>You Won</h1>"+`<p>It took you ${runTime} seconds to cross the finish line.</p>`+"<p>If you want to play again, click the button below.</p>"+"<button onclick='document.location.reload();' class='ab-largenormbutton'>Play Again</button>");
AB.socketOut({"name":"finishLineCrossed","data":{"runTime": runTime }});}}
AB.socketStart();// start the socket connection// everytime a new player joins the game, ensure that the new player is able to click the start game button
AB.socketUserlist =function(array){switch(array.length){case1:
host = AB.socket.id;
AB.msg("<p>You are the Host\n</p>",1);if($("#splashScreenStartMessage p:contains(You are the host)")!==1){
$("#splashScreenStartMessage").append("<p>You are the host, you must start the game so that others can play too.</p>");}break;default:// the host will send a socket message to the new player to tell them if the game has started or notif(host !==undefined){
AB.socketOut({"name":"updateHostStarted","data":{"hostStarted": hostStarted }});}else{if($("#splashScreenStartMessage p:contains(You must wait until the host)")!==1){
$("#splashScreenStartMessage").append("<p>You must wait until the host starts the game.</p>");}}
$("#splashbutton").html("Click here to start the game");
$("#splashbutton").prop("disabled",false);break;}// update the number of players message in game
AB.msg(`<p>Current number of players = ${array.length}\n</p>`,5);};
AB.socketIn =function(data){if(data.name ==="finishLineCrossed"){
AB.newSplash();
AB.splashHtml("<h1>You Lose</h1>"+"<p>The player who won the game crossed the finish line in "+ data.data.runTime +" seconds.</p>"+"<p>If you want to play again, click the button below.</p>"+"<button onclick='document.location.reload();' class='ab-largenormbutton'>Play Again</button>");}if(data.name ==="lightChange"){if(data.data.redOn){if(data.data.actualChange){
redLightChangeSound.play();}
setTimeout(function(){ changeToRed(); redOn =true;}, delayTime);}else{if(data.data.actualChange){
greenLightChangeSound.play();}
setTimeout(function(){ changeToGreen(); redOn =false;}, delayTime);}}if(data.name ==="hostPressedStart"){
hostStarted =true;
$("#splashbutton").html("Click here to start the game");}if(data.name ==="updateHostStarted"){
hostStarted = data.data.hostStarted;
$("#splashbutton").html("Click here to start the game");}if(data.name ==="timer"){
AB.msg(`<p>Time: ${data.data.time}\n</p>`,4);
overallTime = data.data.time;}};// message to show the user when they join the gameconst splashScreenMessage =('<div id="splashScreenStartMessage" style="text-align:center; font-family:Arial, Helvetica, sans-serif;">'+'<p>The objective of the game is to get to the <font color=blue><B>blue</B></font> finish line at the other end of the playing area.</p>'+'<p style="color: red">When the light is <B>red</B>, you must stop, if you move while the light is red, <B>you will be sent back to the start.</B></p>'+'<p style="color: green">When the light is <B>green</B>, you are able to freely move around.</p>'+'<p>A <B>sound</B> will play just before the light changes colour.</p>'+'<p style="color: blue">Use <B>W</B>, <B>A</B>, <B>S</B>, or <B>D</B> to move around.</p>'+'</div>');
AB.newSplash(splashScreenMessage);// display the start game message
$("#splashbutton").html("Click here to start the game");// change the text on the start game button// when the start game button is clicked, start the game based on if the host has already started the game or not
AB.splashClick(function(){if(host !==undefined){// if the player is the host, start the gameif(AB.socket && AB.socket.connected){
lightTime = performance.now();
AB.runReady =true;
AB.removeSplash();
hostStarted =true;
AB.socketOut({"name":"hostPressedStart"});}}elseif(host ===undefined&& hostStarted){// if the player is not the host and the game has started, start the gameif(AB.socket && AB.socket.connected){
lightTime = performance.now();
AB.runReady =true;
AB.removeSplash();}}else{// if the player is not the host and the game has not started, change the button text to tell the player to wait for the host to start the game
$("#splashbutton").html("Host has not started the game yet");}});// on initialisation of the world this function is ran
AB.world.newRun =function(){// while everything is being added to the scene add a loading screen
AB.loadingScreen();// set the runReady variable to false so that the game does not start until the host has started the game
AB.runReady =false;
loadPlayer();};// on each step of the world this function is ran
AB.world.nextStep =function(){
movementHandler();// set the camera to look at the playerABWorld.lookat.x = player.position.x;ABWorld.lookat.y = player.position.y;ABWorld.lookat.z = player.position.z;
checkLocation();if(AB.socket.id === host){// only the host will run this code// to make sure that every player sees the same light colour, only the host will change the light colour and will send the new colour to the other players
changeLight();
overallTime =Math.floor((performance.now()- startTime)/1000);
AB.msg(`<p>Time: ${overallTime}\n</p>`,4);
AB.socketOut({"name":"timer","data":{"time": overallTime }});}};
AB.world.endRun =function(){};