Files
moon-gaming/Exfiltration Template/scripts/project/main.js
T
2024-01-04 17:13:16 +00:00

407 lines
11 KiB
JavaScript

/*
* Made by Viridino Studios (@ViridinoStudios)
*
* Felipe Vaiano Calderan - Programmer
* Twitter: @fvcalderan
* E-mail: [email protected]
*
* Wesley Andrade - Artist
* Twitter: @andrart7
* E-mail: [email protected]
*
* Made with the support of patrons on https://www.patreon.com/viridinostudios
*/
//=============================================================================
// Camera view variables
let camLookTheta;
let camLookPhi;
let camLookX;
let camLookY;
let camLookZ;
// These variables store object instances that are referenced later.
let player;
let playerSpawn;
let playerEscapeZone;
let hudRadar;
let hudTextSuccess;
let hudTextFail;
let hudTextTutorial;
let hudFader;
// List of instances
let turrets;
let hudDots;
let playerSpotters;
let playerSafeZones;
// Global objects
let camera;
let mouse;
let keyboard;
// Special objects
let mouseLocker; // Canvas to request mouse lock
// Gameplay variables
let playerCrouching; // Is the player crouching?
let inputsDisabled; // Is the player frozen?
// Settings
const MOUSESENSITIVITY = 0.0025; // Mouse sensitivity
const CROUCHSPEED = 50; // Player crouch speed
const WALKSPEED = 100; // Player waking speed
const NUMTURRETS = 3; // Number of Turrets
const RADARDISTFACTOR = 0.05; // Radar distance factor
const RADARMAXDIST = 19; // Radar maximum scan distance
const TURRETANGLEFIX = 3.92699; // Fix turret angle
const RESETFADETIME = 3.25; // Time to fade-out-in the game
const TEXTBLINKSPEED = 0.25; // Success/Fail text blink speed
runOnStartup(async runtime => {
// Code to run on the loading screen.
runtime.addEventListener(
"beforeprojectstart", () => onBeforeProjectStart(runtime)
);
});
async function onBeforeProjectStart(runtime)
{
// Code to run just before 'On start of layout'
// Get instances
player = runtime.objects.Player.getFirstInstance();
playerSpawn = runtime.objects.PlayerSpawn.getFirstInstance();
playerEscapeZone = runtime.objects.PlayerEscapeZone.getFirstInstance();
hudRadar = runtime.objects.HUDRadar.getFirstInstance();
hudTextSuccess = runtime.objects.HUDTextSuccess.getFirstInstance();
hudTextFail = runtime.objects.HUDTextFail.getFirstInstance();
hudTextTutorial = runtime.objects.HUDTextTutorial.getFirstInstance();
hudFader = runtime.objects.HUDFader.getFirstInstance();
// Get list of instances
turrets = runtime.objects.TurretBody.getAllInstances();
playerSpotters = runtime.objects.PlayerSpotter.getAllInstances();
playerSafeZones = runtime.objects.PlayerSafeZone.getAllInstances();
// Get global objects
camera = runtime.objects.Camera3D;
mouse = runtime.mouse;
keyboard = runtime.keyboard;
// Configure initial parameters for the game
setupGame(runtime);
// Remove mouse cursor
runtime.mouse.setCursorStyle("None");
// Create a canvas to request mouse lock
mouseLocker = document.createElement("canvas");
document.body.appendChild(mouseLocker);
// Mouse events
runtime.addEventListener("mousedown", e => onMouseDown(e));
runtime.addEventListener("mousemove", e => onMouseMove(e));
// Keyboard events
runtime.addEventListener("keydown", e => onKeyDown(e));
// Start ticking
runtime.addEventListener("tick", () => onTick(runtime));
}
function setupGame(runtime) {
// Configure initial parameters for the game
// Disable player's input and show tutorial
inputsDisabled = true;
hudTextTutorial.behaviors.Tween.startTween(
"opacity", 1, 0.5, "in-out-sine"
);
// Set player spawn position
player.x = playerSpawn.x;
player.y = playerSpawn.y;
// Starting camera's spherical coordinates
camLookTheta = 0;
camLookPhi = Math.PI/2;
// Spherical coordinates converted to cartesian coordinates
setCameraCartesian(camLookTheta, camLookPhi);
// Set starting look position
camera.lookAtPosition(
player.x, player.y, player.zElevation + player.zHeight - 2,
camLookX, camLookY, camLookZ, 0, 0, 1
);
// Setup player crouching state (false at the start)
playerCrouching = false;
// Setup radar dots
hudDots = new Array(NUMTURRETS).fill(null);
for (const dot of runtime.objects.HUDDot.getAllInstances()) {
dot.destroy();
}
for (const turret of turrets) {
hudDots[turret.instVars.id] = runtime.objects.HUDDot.createInstance(
"HUD", hudRadar.x, hudRadar.y
);
}
// Setup mission success text
hudTextSuccess.isVisible = false;
hudTextSuccess.behaviors.Flash.flash(TEXTBLINKSPEED, TEXTBLINKSPEED, 0);
// Setup mission failed text
hudTextFail.isVisible = false;
hudTextFail.behaviors.Flash.flash(TEXTBLINKSPEED, TEXTBLINKSPEED, 0);
// Setup fader
hudFader.behaviors.Tween.startTween(
"opacity", 0, RESETFADETIME/2, "in-out-sine"
);
// Reset view bobbing speed
setViewBobbing(8, 4);
}
function onMouseDown(_) {
// If mouse is not locket yet, lock it
if (document.pointerLockElement !== mouseLocker) {
mouseLocker.requestPointerLock();
}
}
function onMouseMove(e) {
// Process mouse movement
// Set camera's spherical coordinates according to mouse movement
camLookTheta += e.movementX * MOUSESENSITIVITY;
camLookPhi = Math.max(
Math.min(
camLookPhi - e.movementY * MOUSESENSITIVITY, Math.PI - 0.1
), 0.1
);
}
function onKeyDown(e) {
// Check if a key has been pressed
// [Space] hides the tutorial and enables input
if (e.key == " " && inputsDisabled) {
inputsDisabled = false;
hudTextTutorial.behaviors.Tween.startTween(
"opacity", 0, 0.5, "in-out-sine"
);
}
// [C] toggles player's crouching state
if ((e.key == "c" || e.key == "C") && !inputsDisabled) {
playerCrouching = !playerCrouching;
}
}
function onTick(runtime) {
// Code to run every tick
setCamera3D(runtime);
getKeyboardInputs();
updateRadar();
spotPlayer(runtime);
playerEscape(runtime);
}
function setCamera3D(runtime) {
// Set camera position and rotation
const camX = player.x; // Camera X
const camY = player.y; // Camera Y
const camZ = lerp(
camera.getCameraPosition()[2],
player.zElevation + player.zHeight - (playerCrouching ? 16 : 2),
0.2 * 60 * runtime.dt
); // Camera Z (lerp is for smooth crouching/standing)
// Spherical coordinates converted to cartesian coordinates
setCameraCartesian(camLookTheta, camLookPhi);
// Apply camera settings
camera.lookAtPosition(
camX, camY, camZ,
camX + camLookX,camY + camLookY, camZ + camLookZ,
0, 0, 1
);
}
function getKeyboardInputs() {
// Get player's inputs
// If the tutorial is being displayed, ignore everything
if (inputsDisabled) {
return;
}
// Shorthand for player's 8 Direction behavior
const eightd = player.behaviors.EightDirection;
// Is the player crouching?
eightd.maxSpeed = playerCrouching ? CROUCHSPEED : WALKSPEED;
// Directional inputs
const inputRight = keyboard.isKeyDown("KeyD");
const inputLeft = keyboard.isKeyDown("KeyA");
const inputUp = keyboard.isKeyDown("KeyW");
const inputDown = keyboard.isKeyDown("KeyS");
// Compute axes
const horizontalAxis = inputRight - inputLeft;
const verticalAxis = inputUp - inputDown;
// Move the player's object and increase bobbing if an axis is active
if (horizontalAxis != 0 || verticalAxis != 0) {
// Get the angle to move
const axisAngle = Math.atan2(horizontalAxis, verticalAxis);
// Set 8Direction movement vector
eightd.setVector(
Math.cos(camLookTheta + axisAngle) * eightd.maxSpeed,
Math.sin(camLookTheta + axisAngle) * eightd.maxSpeed,
)
// Increase view bobbing speed
setViewBobbing(2, 1);
// Otherwise make the player stationary and view bobbing slow
} else {
eightd.setVector(0, 0);
setViewBobbing(8, 4);
}
}
function updateRadar() {
// Update radar dots
for (const turret of turrets) {
// Get angle and distance between player and turret
const params = [player.x, player.y, turret.x, turret.y]
const a = angleBetween(...params) - camLookTheta - Math.PI/2;
const d = distanceBetween(...params) * RADARDISTFACTOR;
// Update dots on the radar according to new angle and distance
const dot = hudDots[turret.instVars.id];
dot.x = hudRadar.x + Math.cos(a) * d;
dot.y = hudRadar.y + Math.sin(a) * d;
dot.angle = turret.angle - camLookTheta - Math.PI/2 - TURRETANGLEFIX;
dot.isVisible = d < RADARMAXDIST;
}
}
function spotPlayer(runtime) {
// Turrets spot player if they have them in sight
// For each spotter
for (const ps of playerSpotters) {
// Check if the player is overlapping this spotter
if (
player.testOverlap(ps) &&
!hudTextFail.behaviors.Flash.isFlashing
) {
// Assume that the player is not safe
let playerSafe = false;
// Check if the player is inside a safe zone
for (const sz of playerSafeZones) {
if (player.testOverlap(sz)) {
// Deal with player crouching state
playerSafe = (sz.instVars.crouchingOnly && playerCrouching)
|| !sz.instVars.crouchingOnly;
}
}
// If the player is not safe, then it is game over
if (!playerSafe) {
gameOver(runtime, hudTextFail);
}
}
}
}
function playerEscape(runtime) {
// Player escaped successfully
if (
player.testOverlap(playerEscapeZone) &&
!hudTextSuccess.behaviors.Flash.isFlashing
) {
gameOver(runtime, hudTextSuccess);
}
}
function gameOver(runtime, activeText) {
// Start game over procedures
// Show fail / success text
activeText.isVisible = true;
// Start flashing the text
activeText.behaviors.Flash.flash(0.33, 0.33, Number.MAX_SAFE_INTEGER);
hudFader.behaviors.Tween.startTween(
"opacity", 1, RESETFADETIME/2, "in-out-sine"
);
// Fix Z-Ordering
hudFader.moveToTop();
activeText.moveToTop();
// Disable player movement
inputsDisabled = true;
// Decrease view bobbing speed
setViewBobbing(8, 4);
// Call restart
setTimeout(() => setupGame(runtime), RESETFADETIME*1000);
}
function setCameraCartesian(theta, phi) {
// Convert spherical to cartesian and set camLooks
camLookX = Math.cos(theta) * Math.sin(phi);
camLookY = Math.sin(theta) * Math.sin(phi);
camLookZ = -Math.cos(phi);
}
function setViewBobbing(h, v) {
// Set view bobbing
hudRadar.behaviors.SineH.period = h;
hudRadar.behaviors.SineV.period = v;
}
function lerp(start, end, amt) {
// Simple helper function for linear interpolation
return (1 - amt) * start + amt * end;
}
function distanceBetween(x1, y1, x2, y2) {
// Calculate 2D distance between two coordinates
return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
}
function angleBetween(x1, y1, x2, y2) {
// Calculate 2D angle between two coordinates
return Math.atan2(y2 - y1, x2 - x1);
}