// Creates a new Scene
var scene = new THREE.Scene();
// =============================================================================================
// =============================================================================================
// AUDIO
// =============================================================================================
// =============================================================================================
const SPEED1 = "/uploads/duarte/swingx1.mp3";
const SPEED2 = "/uploads/duarte/swingx1.25.mp3";
const SPEED3 = "/uploads/duarte/swingx1.5.mp3";
const HONK = "/uploads/duarte/car-honk.mp3";
const SPEEDUP = "/uploads/duarte/speed-up.mp3";
var speed1 = new Audio(SPEED1);
var speed2 = new Audio(SPEED2);
var speed3 = new Audio(SPEED3);
var honk = new Audio(HONK);
var speedUp = new Audio(SPEEDUP);
var isMuted = false;
// Function to sets volume of each sound
function setVolume() {
if (isMuted) { // Mutes all sounds
speed1.volume = 0;
speed2.volume = 0;
speed3.volume = 0;
honk.volume = 0;
speedUp.volume = 0;
} else { // Unmutes all sounds
speed1.volume = 0.1;
speed2.volume = 0.1;
speed3.volume = 0.1;
honk.volume = 0.1;
speedUp.volume = 0.1;
}
}
// Calls function to set volumes
setVolume();
// =============================================================================================
// =============================================================================================
// VARIABLES
// =============================================================================================
// =============================================================================================
let password = "753";
// Scoreboard Variables
var score1 = 0;
var hscore1 = 0;
var score2 = 0;
var hscore2 = 0;
// Lanes variables
const laneTypes = ['car', 'forest'];
const vechicleColors = [0xFFFFFF, 0x000000, 0xFF0000, 0xFFFF00, 0x0000FF, 0x9400D3, 0xFFA500];
const laneSpeeds = [2, 3, 5, 6, 7];
const bushHeights = [15, 25, 35];
const rocksHeights = [5, 7, 12];
let lanes;
let currentLane;
let currentColumn;
// Movement Variable
const positionWidth = 42;
const columns = 17;
const boardWidth = positionWidth * columns;
const stepTime = 50;
const zoom = 2;
let previousTimestamp;
let startMoving;
let moves;
let stepStartTimestamp;
// Player settings
var player;
const playerSize = 15;
// Lights Settings
const initialDirLightPositionX = -100;
const initialDirLightPositionY = -100;
var d = 500;
// Websockets Variables
var host;
var playerJoined = false;
var playerLeft = false;
var lockControls = false;
var clock;
// Manages Sound Speed UPs
var onSpeedUp1 = true;
var onSpeedUp2 = true;
// =============================================================================================
// =============================================================================================
// CAMERA
// =============================================================================================
// =============================================================================================
const distance = 500; // Distance for the camera
// Creates camera and assigns position
const camera = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 0.1, 10000);
camera.rotation.x = 50 * Math.PI / 180;
camera.rotation.y = 20 * Math.PI / 180;
camera.rotation.z = 10 * Math.PI / 180;
const initialCameraPositionY = -Math.tan(camera.rotation.x) * distance;
const initialCameraPositionX = Math.tan(camera.rotation.y) * Math.sqrt(distance ** 2 + initialCameraPositionY ** 2);
camera.position.y = initialCameraPositionY;
camera.position.x = initialCameraPositionX;
camera.position.z = distance;
// =============================================================================================
// =============================================================================================
// INITIALISE VALUES
// =============================================================================================
// =============================================================================================
const initaliseValues = () => { // Function to initialise values
player = new Player(); // Creates player
scene.add(player); // Adds the player to the scene
hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6); // Creates a new Hemisphere Light
scene.add(hemiLight); // Adds the Hemisphere Light to the scene
dirLight = new THREE.DirectionalLight(0xffffff, 0.6); // Creates a new Directional Light
dirLight.position.set(initialDirLightPositionX, initialDirLightPositionY, 200); // Sets the Directional Light position
dirLight.target = player; // Sets the Directional Light target
scene.add(dirLight); // Adds the Directional Light to the scene
backLight = new THREE.DirectionalLight(0x000000, .4); // Creates a new Directional Light
backLight.position.set(200, 200, 50); // Sets the Directional Light position
scene.add(backLight); // Adds the Directional Light to the scene
lanes = generateLanes(); // Generates the lanes
currentLane = 0; // Sets the current lane to 0
currentColumn = Math.floor(columns / 2); // Sets the current column to the middle of the board
previousTimestamp = null; // Sets the previous timestamp to null
startMoving = false; // Sets start moving to false
moves = []; // Sets moves to an empty array
stepStartTimestamp; // Sets the step start timestamp to null
player.position.x = 0; // Sets the player position to 0
player.position.y = 0; // Sets the player position to 0
camera.position.y = initialCameraPositionY; // Sets the camera position to the initial camera position
camera.position.x = initialCameraPositionX; // Sets the camera position to the initial camera position
dirLight.position.x = initialDirLightPositionX; // Sets the Directional Light position to the initial Directional Light position
dirLight.position.y = initialDirLightPositionY; // reset lighting to follow player
requestAnimationFrame(animate); // Calls the animate function
updateHtml(); // Calls the update html function
speed1.play(); // Plays the speed 1 sound
}
// =============================================================================================
// =============================================================================================
// LOAD RESOURCES
// =============================================================================================
// =============================================================================================
function loadResources() { // Function to load resources
AB.socketStart(); // Calls the socket start function
AB.removeSplash(); // Removes the splash screen
if (playerJoined) { // If the player has joined
initaliseValues(); // Calls the initialise values function
} else { // If the player has not joined
waitingForPlayers(); // Calls the waiting for players function
}
}
// =============================================================================================
// =============================================================================================
// GAME
// =============================================================================================
// =============================================================================================
const renderer = new THREE.WebGLRenderer({ // Creates renderer
alpha: true,
antialias: true
});
renderer.shadowMap.enabled = true; // Enables shadows
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows
renderer.setSize(window.innerWidth, window.innerHeight); // Sets size of renderer
document.body.appendChild(renderer.domElement); // Adds renderer to the body of the document
// =============================================================================================
// =============================================================================================
// SOCKETS
// =============================================================================================
// =============================================================================================
// function to handle socket out to the other user
function socketOut(_name, _joined, _closed) {
AB.socketOut({
name: _name,
data: {
joined: _joined,
closed: _closed,
host: host,
clock: clock,
score2: score1,
hscore2: hscore1,
}
});
}
// =============================================================================================
// =============================================================================================
// ON USER LEAVE
// =============================================================================================
// =============================================================================================
window.onbeforeunload = function () {
socketOut("playerLeft", false, true); // Remove player from game when leaving the page
}
// =============================================================================================
// =============================================================================================
// SOCKET USER LIST
// =============================================================================================
// =============================================================================================
AB.socketUserlist = function (e) { // Function to handle socket user list
// console.log(e); // Logs the event
if (e.length % 2 === 0 && !playerJoined && host === undefined) { // If there are an even number of players and the player has not joined and the host is undefined
// Join game
host = Math.floor(Math.random() * 999999999); // Generate random host number
// console.log(host); // Set host
playerJoined = true; // Sets player joined to true
initaliseValues(); // Calls the initialise values function
AB.removeSplash(); // Removes the splash screen
clock = new Clock(); // Creates a new clock
clock.start(); // Starts the clock
socketOut("triggerJoin", true, false); // Send socket to other player
// console.log("Join game");
} else if (e.length % 2 === 1 && !playerJoined && host === undefined) { // If there is an odd number of players and the player has not joined and the host is undefined
// Create game
host = AB.socket.id; // Sets the host to the socket id
// console.log("Create game"); // Creates a new
}
switch (e.length) { // Switch statement to handle the number of players
default: // Default case
if (void 0 !== host) { // If the host is not undefined
socketOut("start", false, true); // Calls the socket out function
}
}
AB.msg("<p>Current number of players = " + e.length + "\n</p>", 2); // Display number of players
}
// =============================================================================================
// =============================================================================================
// SOCKET IN
// =============================================================================================
// =============================================================================================
AB.socketIn = function (element) {
// console.log("TRIGGER SOCKET IN ", element.name, element.data);
if (element.name === "playerLeft" && element.data.host === host && playerJoined) {
lockControls = true; // Lock controls
playerLeft = true; // Player left
AB.socket.destroy(); // Destroy socket
endGame(); // End game
resetPlayer(); // Reset player
} else if (element.name === "update" && element.data.host === host && playerJoined) {
score2 = element.data.score2; // Updates score
hscore2 = element.data.hscore2; // Updates highscore
updateHtml(); // update HTML side screen
} else if (element.name === "triggerJoin" && element.data.joined === true && !playerJoined) {
playerJoined = true; // sets playerJoined to true
clock = element.data.clock; // gets clock from host
host = element.data.host; // gets host id
initaliseValues(); // starts game
AB.removeSplash(); // removes splash screen
} else if (element.name === "clockUpdate" && element.data.host === host && playerJoined) {
clock = element.data.clock; // Update clock
AB.msg("<p>Time left: <strong>" + clock.currTime + "</strong> seconds</p>", 5); // Update time left screen
} else if (element.name === "endGame" && element.data.host === host && playerJoined) { // End game
score2 = element.data.score2; // Update score
hscore2 = element.data.hscore2; // Update highscore
lockControls = true; // Lock controls
AB.socket.destroy(); // Destroy socket
endGame(); // End game
resetPlayer(); // Reset player
}
};
// =============================================================================================
// =============================================================================================
// CLOCK
// =============================================================================================
// =============================================================================================
class Clock { // Clock class
constructor() { // Constructor
this.currTime = 60 // Current time
}
start() { // Start clock
var clock = this; // Set clock to this
function _time() { // Function to update time
clock.currTime--;
if (clock.currTime < 0) {
socketOut("endGame", false, false); // Send end game to other player
lockControls = true; // Lock controls
AB.socket.destroy(); // Destroy socket
endGame(); // End game
resetPlayer(); // Reset player
return; // Stop clock
}
AB.msg("<p>Time left: <strong>" + clock.currTime + "</strong> seconds</p>", 5); // Update time
socketOut("clockUpdate", false, false); // Send clock update to other player
}
setInterval(_time, 1000); // Set interval to 1 second
}
}
// =============================================================================================
// =============================================================================================
// PLAYER
// =============================================================================================
// =============================================================================================
function Player() { // Player class
const player = new THREE.Group(); // Create a group to hold the player
const body = new THREE.Mesh( // Create the body
new THREE.BoxBufferGeometry((playerSize - 1) * zoom, (playerSize - 1) * zoom, (playerSize - 1) * zoom),
new THREE.MeshPhongMaterial({ color: 0xF0B8A0 })
);
body.position.z = 10 * zoom; // Move the body up
body.position.z += 15; // Move the body up
player.add(body); // Adds the body to the group
const hair = new THREE.Mesh( // Create the hair
new THREE.BoxBufferGeometry((playerSize - 0.8) * zoom, (playerSize - 0.8) * zoom, 2 * zoom),
new THREE.MeshLambertMaterial({ color: 0x000000 })
);
hair.position.z = 21 * zoom; // Move the hair up
hair.position.z += 6; // Move the hair up
player.add(hair); // Add the hair to the group
const pants = new THREE.Mesh( // Create the pants
new THREE.BoxBufferGeometry(playerSize * zoom, playerSize * zoom, 20 * zoom),
new THREE.MeshLambertMaterial({ color: 0x435F9A })
);
pants.position.z = 10 * zoom / 2; // Move the pants up
player.add(pants); // Add the pants to the group
return player; // Returns the group
}
// =============================================================================================
// =============================================================================================
// CAR
// =============================================================================================
// =============================================================================================
function Wheel() { // Wheel
const wheel = new THREE.Mesh( // Creates a new mesh for the wheel
new THREE.BoxBufferGeometry(12 * zoom, 33 * zoom, 12 * zoom), // Creates a new box geometry for the wheel
new THREE.MeshLambertMaterial({ color: 0x333333 }) // Creates a new material for the wheel
);
wheel.position.z = 6 * zoom; // Sets the position of the wheel
return wheel; // Returns the wheel
}
function Car() {
const car = new THREE.Group(); // Creates a new group
const carFrontTexture = new Texture(40, 80, [{ x: 0, y: 10, w: 30, h: 60 }]); // Creates a new texture
const carBackTexture = new Texture(40, 80, [{ x: 10, y: 10, w: 30, h: 60 }]); // Creates a new texture
const carRightSideTexture = new Texture(110, 40, [{ x: 10, y: 0, w: 50, h: 30 }, { x: 70, y: 0, w: 30, h: 30 }]); // Creates a new texture
const carLeftSideTexture = new Texture(110, 40, [{ x: 10, y: 10, w: 50, h: 30 }, { x: 70, y: 10, w: 30, h: 30 }]); // Creates a new texture
const color = vechicleColors[Math.floor(Math.random() * vechicleColors.length)]; // Randomly selects a color from the array
const main = new THREE.Mesh( // Creates a new mesh
new THREE.BoxBufferGeometry(60 * zoom, 30 * zoom, 15 * zoom), // Creates a new box buffer geometry
new THREE.MeshPhongMaterial({ color }) // Creates a new material
); // Creates a new mesh
main.position.z = 12 * zoom; // Sets the position of the mesh
car.add(main) // Adds the mesh to the group
const cabin = new THREE.Mesh( // Creates a new mesh
new THREE.BoxBufferGeometry(33 * zoom, 24 * zoom, 12 * zoom), // Creates a new box
[
new THREE.MeshPhongMaterial({ color: 0xcccccc, map: carBackTexture }), // Creates a new material
new THREE.MeshPhongMaterial({ color: 0xcccccc, map: carFrontTexture }), // Creates a new material
new THREE.MeshPhongMaterial({ color: 0xcccccc, map: carRightSideTexture }), // Creates a new material
new THREE.MeshPhongMaterial({ color: 0xcccccc, map: carLeftSideTexture }), // Creates a new material
new THREE.MeshPhongMaterial({ color: 0xcccccc, }), // top
new THREE.MeshPhongMaterial({ color: 0xcccccc, }) // bottom
]
);
cabin.position.x = 6 * zoom; // Sets the position of the mesh
cabin.position.z = 25.5 * zoom; // Sets the position of the mesh
car.add(cabin); // Adds the mesh to the group
const frontWheel = new Wheel(); // Creates a new wheel
frontWheel.position.x = -18 * zoom; // Sets the position of the wheel
car.add(frontWheel); // Adds the wheel to the group
const backWheel = new Wheel(); // Creates a new wheel
backWheel.position.x = 18 * zoom; // Sets the position of the wheel
car.add(backWheel); // Adds the wheel to the group
return car; // Returns the group
}
// =============================================================================================
// =============================================================================================
// BUSH
// =============================================================================================
// =============================================================================================
function Bush() { // Creates a new bush object
const bush = new THREE.Group(); // Creates a new group
height = bushHeights[Math.floor(Math.random() * bushHeights.length)]; // Randomly selects a height for the bush
const leaf = new THREE.Mesh( // Creates a new mesh for the leaves
new THREE.BoxBufferGeometry(30 * zoom, 30 * zoom, height * zoom), // Creates a box with the size of the bush
new THREE.MeshLambertMaterial({ color: 0x37AE0F, }) // Creates a material for the leaves
);
leaf.position.z = (height / 2) * zoom; // Sets the position of the leaves
bush.add(leaf); // Adds the leaves to the bush
return bush; // Returns the bush
}
// =============================================================================================
// =============================================================================================
// ROCK
// =============================================================================================
// =============================================================================================
function Rock() { // Create a new rock object
const rocks = new THREE.Group(); // Create a new group for the rock
height = rocksHeights[Math.floor(Math.random() * bushHeights.length)]; // Get a random height for the rock
const rock = new THREE.Mesh( // Create the rock
new THREE.BoxBufferGeometry(12 * zoom, 12 * zoom, height * zoom), // Create a box with the random height
new THREE.MeshLambertMaterial({ color: 0x666a6c, }) // Set the color of the rock
);
rock.position.z = (height / 2) * zoom; // Set the position of the rock
rocks.add(rock); // Add the rock to the group
return rocks; // Return the group
}
// =============================================================================================
// =============================================================================================
// ROAD
// =============================================================================================
// =============================================================================================
function Road() { // Creates a new road object
const road = new THREE.Group(); // Creates a new group for the road
const createSection = color => new THREE.Mesh( // Creates a new section of the road
new THREE.PlaneBufferGeometry(boardWidth * zoom, positionWidth * zoom), // Creates a new plane with the width of the board and the height of the position
new THREE.MeshPhongMaterial({ color }) // Creates a new material with the color of the road
);
const middle = createSection(0x454A59); // Creates the middle section of the road
road.add(middle); // Adds the middle section to the road
const left = createSection(0x393D49); // Creates a new section
left.position.x = - boardWidth * zoom; // Moves the section to the left
road.add(left); // Adds the section to the road
const right = createSection(0x393D49); // Creates a new section
right.position.x = boardWidth * zoom; // Moves the section to the right
road.add(right); // Adds the section to the road
return road; // Returns the road
}
// =============================================================================================
// =============================================================================================
// GRASS
// =============================================================================================
// =============================================================================================
function Grass() { // Creates a new grass object
const grass = new THREE.Group(); // Creates a new group
const createSection = color => new THREE.Mesh( // Creates a new section
new THREE.BoxBufferGeometry(boardWidth * zoom, positionWidth * zoom, 3 * zoom), // Creates a new box geometry
new THREE.MeshPhongMaterial({ color }) // Creates a new material
); // Creates a new section
const middle = createSection(0xbaf455); // Creates a new section
middle.receiveShadow = true; // Sets the shadow to true
grass.add(middle); // Adds the section to the grass group
const left = createSection(0x99C846); // Creates a new section
left.position.x = - boardWidth * zoom; // Sets the position of the section
grass.add(left); // Adds the section to the grass group
const right = createSection(0x99C846); // Creates a new section
right.position.x = boardWidth * zoom; // Sets the position of the section
grass.add(right); // Adds the section to the grass group
grass.position.z = 1.5 * zoom; // Moves the grass up a bit
return grass; // Returns the grass object
}
// =============================================================================================
// =============================================================================================
// LANE
// =============================================================================================
// =============================================================================================
// Generates inital lanes
const generateLanes = () => [-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((index) => {
const lane = new Lane(index); // Creates a new Lane as an object
lane.mesh.position.y = index * positionWidth * zoom; // Edit lane y position
scene.add(lane.mesh); // Adds it to the scene
return lane;
}).filter((lane) => lane.index >= 0);
const addLane = () => { // Adds a new lane to the scene
const index = lanes.length; // Gets next Index to add lane
const lane = new Lane(index); // Creates a new Lane as an object
lane.mesh.position.y = index * positionWidth * zoom; // Edit lane y position
scene.add(lane.mesh); // Adds lane to the scene
lanes.push(lane); // Appends new Lane to current Lanes
}
function Lane(index) {
this.index = index; // Index of the lane
this.type = index <= 0 ? 'field' : laneTypes[Math.floor(Math.random() * laneTypes.length)]; // randomly chooses to either create a field or a forest or a car lane
switch (this.type) {
case 'field': { // Makes a new field lane
this.type = 'field';
this.mesh = new Grass(); // Makes a grass layer
break;
}
case 'forest': { // Makes a new forest lane
this.mesh = new Grass(); // Makes a grass layer
this.occupiedPositions = new Set(); // Saves the ocuppied spots
this.threes = [1, 2, 3, 4].map(() => { // Creates 4 bushes or rocks at random positions
var obstacle;
if (Math.random() > 0.7) { // Random to add either a bush or a rock
obstacle = new Bush(); // Creates a new bush
} else {
obstacle = new Rock(); // Creates a new rock
}
let position; // Saves the position of the obstacle
do {
position = Math.floor(Math.random() * columns); // Randomly chooses a position
} while (this.occupiedPositions.has(position)) // Checks if the position is already occupied
this.occupiedPositions.add(position); // Adds the position to the occupied positions
obstacle.position.x = (position * positionWidth + positionWidth / 2) * zoom - boardWidth * zoom / 2; // Sets the x position of the obstacle
this.mesh.add(obstacle); // Adds the obstacle to the mesh
return obstacle; // Returns the new made obstacle
}
)
break;
}
case 'car': { // Makes a new car lane
this.mesh = new Road(); // Makes a road layer
this.direction = Math.random() >= 0.5; // Randomly chooses the direction of the car lane
const occupiedPositions = new Set(); // Saves the ocuppied spots
this.vechicles = [1, 2, 3].map(() => { // Creates 3 cars
const vechicle = new Car(); // Creates a new car
let position; // Creates a new position
do {
position = Math.floor(Math.random() * columns / 2); // Randomly chooses a position
} while (occupiedPositions.has(position)) // Checks if the position is already taken
occupiedPositions.add(position); // Adds the position to the taken positions
vechicle.position.x = (position * positionWidth * 2 + positionWidth / 2) * zoom - boardWidth * zoom / 2; // Sets the position of the car
if (!this.direction) vechicle.rotation.z = Math.PI; // Rotates the car if the direction is false
this.mesh.add(vechicle); // Adds the car to the scene
return vechicle; // Returns the new made obstacle
})
this.speed = laneSpeeds[Math.floor(Math.random() * laneSpeeds.length)]; // Randomly chooses a speed for the lane
if (score1 >= 30) {
this.speed *= 1.7; // Increases speed of cars if score is greater than 30
} else if (score1 >= 10) {
this.speed *= 1.3; // Increases the speed of the cars
}
break;
}
}
}
// =============================================================================================
// =============================================================================================
// TEXTURE
// =============================================================================================
// =============================================================================================
function Texture(width, height, rects) { // Creates a new texture
const canvas = document.createElement("canvas"); // Creates a new canvas
canvas.width = width; canvas.height = height; // Sets the width and height of the canvas
const context = canvas.getContext("2d"); // Gets the context of the canvas
context.fillStyle = "#FFFFFF"; // Sets the fill color to white
context.fillRect(0, 0, width, height); // Fills the canvas with white
context.fillStyle = "rgba(0,0,0,0.6)"; // Sets the fill color to black with 60% opacity
rects.forEach(rect => { // Loops through all the rectangles
context.fillRect(rect.x, rect.y, rect.w, rect.h); // Fills the rectangle with the black color
});
return new THREE.CanvasTexture(canvas); // Returns the canvas texture
}
// =============================================================================================
// =============================================================================================
// MOVES
// =============================================================================================
// =============================================================================================
function move(direction) {
const finalPositions = moves.reduce((position, move) => { // Gets the final position of the player
if (move === 'forward') return { lane: position.lane + 1, column: position.column }; // If the move is forward, then the lane increases by 1
if (move === 'backward') return { lane: position.lane - 1, column: position.column }; // If the move is backward, then the lane decreases by 1
if (move === 'left') return { lane: position.lane, column: position.column - 1 }; // If the move is left, then the column decreases by 1
if (move === 'right') return { lane: position.lane, column: position.column + 1 }; // If the move is right, then the column increases by 1
}, { lane: currentLane, column: currentColumn }) // The initial position is the current position of the player
if (direction === 'forward') {
// If the player is trying to move into a bush, then don't move
if (lanes[finalPositions.lane + 1].type === 'forest' && lanes[finalPositions.lane + 1].occupiedPositions.has(finalPositions.column)) return;
if (!stepStartTimestamp) startMoving = true;
addLane();
}
else if (direction === 'backward') {
if (finalPositions.lane === 0) return;
// If the player is trying to move into a bush, then don't move
if (lanes[finalPositions.lane - 1].type === 'forest' && lanes[finalPositions.lane - 1].occupiedPositions.has(finalPositions.column)) return;
if (!stepStartTimestamp) startMoving = true;
}
else if (direction === 'left') {
if (finalPositions.column === 0) return; // If the player is at the left most column, then don't move
// If the player is trying to move into a bush, then don't move
if (lanes[finalPositions.lane].type === 'forest' && lanes[finalPositions.lane].occupiedPositions.has(finalPositions.column - 1)) return;
if (!stepStartTimestamp) startMoving = true;
}
else if (direction === 'right') {
if (finalPositions.column === columns - 1) return; // If the player is at the right most column, then don't move
// If the player is trying to move into a bush, then don't move
if (lanes[finalPositions.lane].type === 'forest' && lanes[finalPositions.lane].occupiedPositions.has(finalPositions.column + 1)) return;
if (!stepStartTimestamp) startMoving = true;
}
moves.push(direction); // Adds the direction to the moves array
}
// =============================================================================================
// =============================================================================================
// ANIMATION
// =============================================================================================
// =============================================================================================
function animate(timestamp) {
requestAnimationFrame(animate);
if (!previousTimestamp) previousTimestamp = timestamp; // Checks timestamp
const delta = timestamp - previousTimestamp; // edits timestamp with the new delta
previousTimestamp = timestamp; // sets previous timestamp to current timestamp
// Animate cars moving on the lane
lanes.forEach(lane => { // Loops through each lane and animates all cars
if (lane.type === 'car') { // if lane is a car animate it
const aBitBeforeTheBeginingOfLane = -boardWidth * zoom / 2 - positionWidth * 2 * zoom; // Sets the position of the car
const aBitAfterTheEndOFLane = boardWidth * zoom / 2 + positionWidth * 2 * zoom; // Sets the end of the lane
lane.vechicles.forEach(vechicle => { // Loops through each car and animates it
if (lane.direction) {
vechicle.position.x = vechicle.position.x < aBitBeforeTheBeginingOfLane ? aBitAfterTheEndOFLane : vechicle.position.x -= lane.speed / 16 * delta;
} else {
vechicle.position.x = vechicle.position.x > aBitAfterTheEndOFLane ? aBitBeforeTheBeginingOfLane : vechicle.position.x += lane.speed / 16 * delta;
}
});
}
});
if (startMoving) { // If start moving is true
stepStartTimestamp = timestamp; // sets the timestamp to the start of the step
startMoving = false; // sets start moving to false
}
if (stepStartTimestamp) { // If step start timestamp is true
// Moves player
const moveDeltaTime = timestamp - stepStartTimestamp; // sets the move delta time to the timestamp - the start of the step
const moveDeltaDistance = Math.min(moveDeltaTime / stepTime, 1) * positionWidth * zoom; // Sets the distance of the player
switch (moves[0]) {
case 'forward': { // If move is forward
const positionY = currentLane * positionWidth * zoom + moveDeltaDistance; // Sets the position of the player
camera.position.y = initialCameraPositionY + positionY; // Sets the position of the camera
dirLight.position.y = initialDirLightPositionY + positionY; // Sets the position of the light
player.position.y = positionY; // sets the position of the player
break;
}
case 'backward': { // If move is backward
positionY = currentLane * positionWidth * zoom - moveDeltaDistance; // Sets the position of the player
camera.position.y = initialCameraPositionY + positionY; // Sets camera position
dirLight.position.y = initialDirLightPositionY + positionY; // Sets the position of the light
player.position.y = positionY; // Sets the position of the player
break;
}
case 'left': { // If move is left
const positionX = (currentColumn * positionWidth + positionWidth / 2) * zoom - boardWidth * zoom / 2 - moveDeltaDistance; // Sets the position of the player
camera.position.x = initialCameraPositionX + positionX; // Sets camera position
dirLight.position.x = initialDirLightPositionX + positionX; // Sets the position of the light
player.position.x = positionX; // Sets the position of the player
break;
}
case 'right': { // If move is right
const positionX = (currentColumn * positionWidth + positionWidth / 2) * zoom - boardWidth * zoom / 2 + moveDeltaDistance; // Sets the position of the player
camera.position.x = initialCameraPositionX + positionX; // Sets camera position
dirLight.position.x = initialDirLightPositionX + positionX; // Sets the position of the light
player.position.x = positionX; // Sets the position of the player
break;
}
}
// Once a step has ended
if (moveDeltaTime > stepTime) {
switch (moves[0]) { // Switches through the moves
case 'forward': { // If move is forward
if (lanes[currentLane].type === 'car') { // If the lane is a car
score1++; // Add to score
}
currentLane++; // Add to current lane
break;
}
case 'backward': { // If move is backward
currentLane--; // Decreases the current lane
if (lanes[currentLane].type === 'car') { // If the lane is a car
score1--; // Decreases the current
}
break;
}
case 'left': { // If move is left
currentColumn--; // Decreases the current column
break;
}
case 'right': { // If move is right
currentColumn++; // Increases the current column
break;
}
}
if (hscore1 < score1) hscore1 = score1; // saves highscore
if (score1 >= 30 && onSpeedUp2) { // Updates sounds speed to level 2
speed2.pause();
speedUp.play();
onSpeedUp2 = false;
speed3.play();
} else if (score1 >= 10 && onSpeedUp1) { // Updates sounds speed to level 1
speed1.pause();
speedUp.play();
speed2.play();
onSpeedUp1 = false;
}
socketOut("update", false, false); // calls websockets
updateHtml(); // updates HTML sidebar
// removes the first element in moves
moves.shift();
// If more steps are to be taken then restart counter otherwise stop stepping
stepStartTimestamp = moves.length === 0 ? null : timestamp;
}
}
// Hit test for lanes
if (lanes[currentLane].type === 'car') {
const playerMinX = player.position.x - playerSize * zoom / 2; // sets player min x
const playerMaxX = player.position.x + playerSize * zoom / 2; // sets player min y
const vechicleLength = 60;
lanes[currentLane].vechicles.forEach(vechicle => { // Loops through each vechicle in the lane
const carMinX = vechicle.position.x - vechicleLength * zoom / 2; // sets car min x
const carMaxX = vechicle.position.x + vechicleLength * zoom / 2; // sets car max x
if (playerMaxX > carMinX && playerMinX < carMaxX) { // if player is in the same x as the car
if (hscore1 < score1) hscore1 = score1; // saves highscore
speed1.pause(); // pauses sound
speed2.pause(); // pauses sound
speed3.pause(); // pauses sound
honk.play(); // plays honk sound
onSpeedUp1 = true; // resets speed up
onSpeedUp2 = true; // resets speed up
score1 = 0; // resets score
resetPlayer(); // resets player
socketOut("update", false, false); // calls websockets
}
});
}
renderer.render(scene, camera); // Updates scene and camera
}
// =============================================================================================
// =============================================================================================
// RESET PLAYERS
// =============================================================================================
// =============================================================================================
function resetPlayer() {
currentLane = 0; // sets the current lane
currentColumn = Math.floor(columns / 2); // sets the current collumn
previousTimestamp = null; // reset timestamp
startMoving = false; // reset startMoving
moves = []; // reset moves
stepStartTimestamp; // reset stepStartTimestamp
player.position.x = 0; // reset players x position
player.position.y = 0; // reset players y position
camera.position.y = initialCameraPositionY; // reset cameras y position
camera.position.x = initialCameraPositionX; // reset cameras y position
dirLight.position.x = initialDirLightPositionX; // reset lighting to follow player
dirLight.position.y = initialDirLightPositionY; // reset lighting to follow player
updateHtml(); // update HTML
speed1.play(); // play speed1 song
}
// =============================================================================================
// =============================================================================================
// HANDLE KEY PRESSES
// =============================================================================================
// =============================================================================================
window.addEventListener("keyup", event => { // Handles key ups
if (lockControls) return;
if (event.keyCode == '38' || event.keyCode == '87') {
// up arrow OR W
move('forward'); // moves foward
}
else if (event.keyCode == '40' || event.keyCode == '83') {
// down arrow OR S
move('backward'); // moves backward
}
else if (event.keyCode == '37' || event.keyCode == '65') {
// left arrow OR A
move('left'); // moves to the left
}
else if (event.keyCode == '39' || event.keyCode == '68') {
// right arrow OR D
move('right'); // moves to the right
}
else if (event.keyCode == '77') {
// Mute or Unmute
isMuted = !isMuted; // changes to muted or unmuted
setVolume(); // changes volume of sounds
}
});
// =============================================================================================
// =============================================================================================
// SIDE SCREEN HTML INFO
// =============================================================================================
// =============================================================================================
function updateHtml() {
// Creates HTML for the side screen
AB.msg(`<h3> Welcome to our game!</h3>`, 1);
AB.msg(`<p><strong>Player 1</strong> Score: <strong>` + score1 + `</strong> | Highest: <strong>` + hscore1 + `</strong> </p>`, 3);
AB.msg(`<p><strong>Player 2</strong> Score: <strong>` + score2 + `</strong> | Highest: <strong>` + hscore2 + `</strong> </p>`, 4);
AB.msg(`<p><strong>Controls</strong><br>
<strong>Arrow Up/W</strong> - Move Up<br>
<strong>Arrow Left/A</strong> - Move Left<br>
<strong>Arrow Down/S</strong> - Move Down<br>
<strong>Arrow Right/D</strong> - Move Right</p>`, 7);
AB.msg(`<p>Press <strong>M</strong> to mute </p>`, 8);
}
// =============================================================================================
// =============================================================================================
// WAITING FOR PLAYERS
// =============================================================================================
// =============================================================================================
function endGame() {
speed1.pause(); // Pauses music
speed2.pause(); // Pauses music
speed3.pause(); // Pauses music
AB.removeSplash(); // Removes old splashed
AB.newSplash(); // Creates new splash
var txt;
var other = "";
if (playerLeft) {
// Other player left
txt = "Other Player has left.<br><br><span style='color: rgb(0, 255, 0);'>You Won!</span>";
} else if (hscore1 > hscore2) {
// You Win
txt = "<span style='color: rgb(0, 255, 0);'>You Won!</span>";
other = `Your opponent's highscore was: ` + hscore2 + ` <br>`;
} else if (hscore2 > hscore1) {
// Other Player Wins
txt = "<span style='color: rgb(255, 0, 0);'>You Lost!</span>";
other = `Your opponent's highscore was: ` + hscore2 + ` <br>`;
} else {
// Draw
txt = "It's a draw!";
other = `Your opponent's highscore was: ` + hscore2 + ` <br>`;
}
AB.socket.destroy(); // Destroy socket
// Creates HTML for the splash screen
AB.splashHtml(`
<h1> GAME OVER... </h1>
<h3>` + txt + `<br></h3>
Your highest score was: ` + hscore1 + `<br>
`+ other + `<br>
<button onclick='location.reload();' class=ab-largenormbutton >Play Again</button>` );
}
// =============================================================================================
// =============================================================================================
// WAITING FOR PLAYERS
// =============================================================================================
// =============================================================================================
function waitingForPlayers() {
AB.removeSplash(); // Removes old splashed
AB.newSplash(); // Creates new splash
// Creates HTML for the splash screen
AB.splashHtml(`
<h1> Waiting for opponent, please wait... </h1>
<img src="/uploads/wuk/loading-buffering.gif">
` );
}
// =============================================================================================
// =============================================================================================
// WELCOME
// =============================================================================================
// =============================================================================================
function welcome() {
AB.removeSplash(); // Removes old splashed
AB.newSplash(); // Creates new splash
// Creates HTML for the splash screen
AB.splashHtml(`
<h1 style="font-family:cooper"> RUN RUN RUN! </h1>
Run as far as you can in 60 seconds and beat your opponent's highscore to win! <br>
Avoid all the cars! If you get hit you will start again. <br>
<br>
<b>Controls:</b> <br>
Arrow Up/W - Move Up <br>
Arrow Left/A - Move Left <br>
Arrow Down/S - Move Down <br>
Arrow Right/D - Move Right <br>
<br>
<b>GOOD LUCK! <br>
<p>
<button onclick='loadResources();' class=ab-largenormbutton >Search for match</button>
<p>
<div id=errordiv name=errordiv> </div> ` );
}
welcome();