diff --git a/client/src/app/routes/hangar.js b/client/src/app/routes/hangar.js index 9bf37e1f..a4849973 100644 --- a/client/src/app/routes/hangar.js +++ b/client/src/app/routes/hangar.js @@ -36,16 +36,16 @@ export default class hangarRoute { loadHangar() { l.current_scene.scene.add(l.scenograph.objects.structures.hangar.mesh); - //this.targetStructure.visible = false; + l.scenograph.objects.structures.hangar.mesh.visible = true; - console.log(this.targetStructure.userData.config.hangars[0].position); + l.scenograph.objects.structures.hangar.mesh.position.x = this.targetStructure.userData.config.hangars[0].position.x; l.scenograph.objects.structures.hangar.mesh.position.y = this.targetStructure.userData.config.hangars[0].position.y; l.scenograph.objects.structures.hangar.mesh.position.z = this.targetStructure.userData.config.hangars[0].position.z; - l.scenograph.actors.player.vehicle.mesh.userData.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; - l.scenograph.actors.player.vehicle.mesh.userData.object.position.z = - 2.5 + this.targetStructure.userData.config.hangars[0].position.z; - l.scenograph.actors.player.vehicle.mesh.userData.object.position.y = l.scenograph.objects.structures.hangar.mesh.position.y - 7.5; + l.scenograph.actors.player.vehicle.game.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; + l.scenograph.actors.player.vehicle.game.object.position.z = - 2.5 + this.targetStructure.userData.config.hangars[0].position.z; + l.scenograph.actors.player.vehicle.game.object.position.y = l.scenograph.objects.structures.hangar.mesh.position.y - 7.5; l.scenograph.actors.player.actorInstance.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; l.scenograph.actors.player.actorInstance.object.position.z = 10 + this.targetStructure.userData.config.hangars[0].position.z; diff --git a/client/src/app/routes/singleplayer.js b/client/src/app/routes/singleplayer.js index ef3e383f..e5c2b667 100644 --- a/client/src/app/routes/singleplayer.js +++ b/client/src/app/routes/singleplayer.js @@ -25,7 +25,8 @@ export default class singlePlayerRoute { // Set client mode. l.mode = 'single_player'; - l.scenograph.actors.map.get('Player One').setMode('vehicle'); + l.scenograph.actors.player = l.scenograph.actors.get('Player One'); + l.scenograph.actors.player.setMode('vehicle'); } diff --git a/client/src/app/scenograph/actors/player.js b/client/src/app/scenograph/actors/player.js index f3a8e113..d93c0153 100644 --- a/client/src/app/scenograph/actors/player.js +++ b/client/src/app/scenograph/actors/player.js @@ -53,7 +53,7 @@ export default class Player { async load() { // Setup aircraft, used for the intro sequence. - this.vehicle = new l.scenograph.objects.vehicles.valiant(); + this.vehicle = new l.scenograph.objects.vehicles.valiant(this.actorInstance); await this.vehicle.load(); l.current_scene.scene.add( this.vehicle.mesh diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index 73a01759..46f81e66 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -17,7 +17,7 @@ import l from '@/helpers/l.js'; /** * World Simulation */ -import World from '#/game/src/world'; +import World from '#/game/ecs/world'; /** * Scene controllers @@ -231,9 +231,12 @@ export default class Director { // Load world instance from game classes. load( sceneName ) { - this.world = new World( sceneName ); + this.world = new World(sceneName); - return this; + console.log(this.world); + debugger; + + return this; } // Load the objects in world instance to the current scene. diff --git a/client/src/app/scenograph/objects/vehicles/person.js b/client/src/app/scenograph/objects/vehicles/person.js index d013233e..8c7f8ff3 100644 --- a/client/src/app/scenograph/objects/vehicles/person.js +++ b/client/src/app/scenograph/objects/vehicles/person.js @@ -178,5 +178,4 @@ export default class Person { } } - } diff --git a/client/src/app/scenograph/objects/vehicles/raven.js b/client/src/app/scenograph/objects/vehicles/raven.js index e1d12771..31e0159d 100644 --- a/client/src/app/scenograph/objects/vehicles/raven.js +++ b/client/src/app/scenograph/objects/vehicles/raven.js @@ -1,6 +1,6 @@ /** * Enemy bot. - * + * * Currently hardcoded to use the Raven aircraft. */ import * as THREE from 'three'; @@ -11,7 +11,7 @@ import * as THREE from 'three'; import l from '@/helpers/l.js'; import { brightenMaterial, proceduralMetalMaterial } from '@/scenograph/materials.js'; import PirateActor from '#/game/src/actors/pirate'; -import RavenObject from '#/game/src/objects/aircraft/raven'; +import RavenObject from '#/game/src/objects/vehicles/raven'; export default class Raven { @@ -140,10 +140,10 @@ export default class Raven { /** * Animate hook. - * + * * This method is called within the main animation loop and * therefore must only reference global objects or properties. - * + * * @method animate * @memberof Raven * @global @@ -158,5 +158,5 @@ export default class Raven { } } - + } diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 4179b4a2..175bceed 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -14,7 +14,6 @@ import * as THREE from 'three'; import l from '@/helpers/l.js'; import { brightenMaterial, proceduralMetalMaterial } from '@/scenograph/materials.js'; import Player from '#/game/src/actors/player'; -import ValiantObject from '#/game/src/objects/aircraft/valiant'; export default class Valiant { @@ -45,13 +44,17 @@ export default class Valiant { // TrailRenderer effect showing a trailing effect on the thruster. trail; - constructor() { + constructor(actorInstance) { + // Set internal game accessor to the game world actor instance. + this.game = actorInstance; + this.default_camera_distance = -35; this.trail_position_y = 1.2; this.trail_position_z = 1.5; this.camera_distance = 0; this.ready = false; + console.log(this); } @@ -122,7 +125,6 @@ export default class Valiant { this.trail = l.current_scene.effects.trail.createTrail( this.mesh, 0, this.trail_position_y, this.trail_position_z ); - this.mesh.userData.object = new ValiantObject( this.mesh ); } createThrusterMesh( options ) { @@ -309,9 +311,9 @@ export default class Valiant { // Set the ship as ready. l.current_scene.objects.demoShip.ready = true; l.current_scene.objects.demoShip.camera_distance = l.current_scene.objects.demoShip.default_camera_distance + ( l.current_scene.room_depth / 2 ); - l.current_scene.objects.demoShip.mesh.userData.object.position.x = l.current_scene.objects.demoShip.mesh.position.x; - l.current_scene.objects.demoShip.mesh.userData.object.position.y = l.current_scene.objects.demoShip.mesh.position.y; - l.current_scene.objects.demoShip.mesh.userData.object.position.z = l.current_scene.objects.demoShip.mesh.position.z; + // l.current_scene.objects.demoShip.mesh.userData.object.position.x = l.current_scene.objects.demoShip.mesh.position.x; + // l.current_scene.objects.demoShip.mesh.userData.object.position.y = l.current_scene.objects.demoShip.mesh.position.y; + // l.current_scene.objects.demoShip.mesh.userData.object.position.z = l.current_scene.objects.demoShip.mesh.position.z; } ); } @@ -328,12 +330,12 @@ export default class Valiant { let changing = false; for ( const [ controlName, keyMapping ] of Object.entries( mappings ) ) { if ( l.scenograph.controls.keyboard.pressed( keyMapping ) ) { - this.mesh.userData.object.controls[ controlName ] = true; + this.game.actor.controls[ controlName ] = true; changing = true; } else { - this.mesh.userData.object.controls[ controlName ] = false; + this.game.actor.controls[ controlName ] = false; if ( l.scenograph.controls.touch ) { // Check if any touchpad controls are being pressed @@ -347,29 +349,29 @@ export default class Valiant { ) { changing = true; if ( l.scenograph.controls.touch.controls.moveUp ) { - this.mesh.userData.object.controls.moveUp = true; + this.game.actor.controls.moveUp = true; } if ( l.scenograph.controls.touch.controls.moveDown ) { - this.mesh.userData.object.controls.moveDown = true; + this.game.actor.controls.moveDown = true; } if ( l.scenograph.controls.touch.controls.moveForward ) { - this.mesh.userData.object.controls.throttleUp = true; + this.game.actor.controls.throttleUp = true; } if ( l.scenograph.controls.touch.controls.moveBackward ) { - this.mesh.userData.object.controls.throttleDown = true; + this.game.actor.controls.throttleDown = true; } if ( l.scenograph.controls.touch.controls.moveLeft ) { - this.mesh.userData.object.controls.moveLeft = true; + this.game.actor.controls.moveLeft = true; } if ( l.scenograph.controls.touch.controls.moveRight ) { - this.mesh.userData.object.controls.moveRight = true; + this.game.actor.controls.moveRight = true; } } } } } - this.mesh.userData.object.controls.changing = changing; + this.game.actor.controls.changing = changing; } @@ -378,52 +380,54 @@ export default class Valiant { this.mixer.update( delta ); } - // Rock the ship forward and back when moving horizontally - if ( this.mesh.userData.object.controls.throttleDown || this.mesh.userData.object.controls.throttleUp ) { - let pitchChange = this.mesh.userData.object.controls.throttleUp ? -1 : 1; - if ( Math.abs( this.mesh.rotation.x ) < 1 / 4 ) { - this.mesh.rotation.x += pitchChange / 10 / 180; + if ( this.game ) { + // Rock the ship forward and back when moving horizontally + if ( this.game.actor.controls.throttleDown || this.game.actor.controls.throttleUp ) { + let pitchChange = this.game.actor.controls.throttleUp ? -1 : 1; + if ( Math.abs( this.mesh.rotation.x ) < 1 / 4 ) { + this.mesh.rotation.x += pitchChange / 10 / 180; + } } - } - // Rock the ship forward and back when moving vertically - if ( - this.mesh.userData.object.controls.moveDown - || - this.mesh.userData.object.controls.moveUp - ) { - let elevationChange = this.mesh.userData.object.controls.moveDown ? -1 : 1; - if ( Math.abs( this.mesh.rotation.x ) < 1 / 8 ) { - this.mesh.rotation.x += elevationChange / 10 / 180; - } + // Rock the ship forward and back when moving vertically + if ( + this.game.actor.controls.moveDown + || + this.game.actor.controls.moveUp + ) { + let elevationChange = this.game.actor.controls.moveDown ? -1 : 1; + if ( Math.abs( this.mesh.rotation.x ) < 1 / 8 ) { + this.mesh.rotation.x += elevationChange / 10 / 180; + } - if ( Math.abs( l.scenograph.cameras.player.rotation.x ) < 1 / 8 ) { - let radian = ( Math.PI / 180 ); - l.scenograph.cameras.player.rotation.x += elevationChange * radian / 10; + if ( Math.abs( l.scenograph.cameras.player.rotation.x ) < 1 / 8 ) { + let radian = ( Math.PI / 180 ); + l.scenograph.cameras.player.rotation.x += elevationChange * radian / 10; + } + } + else { + if ( l.scenograph.controls.touch && !l.scenograph.controls.touch.controls.rotationPad.mouseDown ) + l.scenograph.cameras.player.rotation.x *= .9; } - } - else { - if ( l.scenograph.controls.touch && !l.scenograph.controls.touch.controls.rotationPad.mouseDown ) - l.scenograph.cameras.player.rotation.x *= .9; } } // Update the position of the aircraft to spot determined by game logic. - updateMesh() { - this.mesh.position.x = this.mesh.userData.object.position.x; - this.mesh.position.y = this.mesh.userData.object.position.y; - this.mesh.position.z = this.mesh.userData.object.position.z; - this.mesh.rotation.x = this.mesh.userData.object.rotation.x; - this.mesh.rotation.y = this.mesh.userData.object.rotation.y; - this.mesh.rotation.z = this.mesh.userData.object.rotation.z; + sync() { + this.mesh.position.x = this.game.object.position.x; + this.mesh.position.y = this.game.object.position.y; + this.mesh.position.z = this.game.object.position.z; + this.mesh.rotation.x = this.game.object.rotation.x; + this.mesh.rotation.y = this.game.object.rotation.y; + this.mesh.rotation.z = this.game.object.rotation.z; } updateCamera( rY, tY, tZ ) { var radian = ( Math.PI / 180 ); this.camera_distance = this.default_camera_distance + ( l.current_scene.room_depth / 2 ); - if ( this.mesh.userData.object.airSpeed < 0 ) { - this.camera_distance -= this.mesh.userData.object.airSpeed * 4; + if ( this.game.object.airSpeed < 0 ) { + this.camera_distance -= this.game.object.airSpeed * 4; } let xDiff = this.mesh.position.x; @@ -432,8 +436,7 @@ export default class Valiant { l.scenograph.cameras.player.position.x = xDiff + this.camera_distance * Math.sin( this.mesh.rotation.y ); l.scenograph.cameras.player.position.z = zDiff + this.camera_distance * Math.cos( this.mesh.rotation.y ); - if ( rY != 0 ) { - + if ( rY != 0 && Math.abs(l.scenograph.cameras.player.rotation.y) < .3925 ) { l.scenograph.cameras.player.rotation.y += rY; } else { @@ -488,7 +491,7 @@ export default class Valiant { if ( l.current_scene.objects.demoShip.ready ) { - if ( l.current_scene.settings.game_controls ) { + if ( l.current_scene.settings.game_controls && this.game ) { if ( l.scenograph.actors.player.mode == 'vehicle' ) { // Detect keyboard input and pass it to the ship state model. @@ -496,7 +499,7 @@ export default class Valiant { } if ( l.scenograph.modes.multiplayer.connected ) { - l.scenograph.modes.multiplayer.socket.emit( 'input', this.mesh.userData.object.controls ); + l.scenograph.modes.multiplayer.socket.emit( 'input', this.game.actor.controls ); } this.mesh.userData.actor.animate( delta ); @@ -506,13 +509,12 @@ export default class Valiant { if ( l.mode != 'hangar') this.updateAnimation( delta ); - // Update the ships state model. - let [ rY, tY, tZ ] = this.mesh.userData.object.move( l.current_scene.stats.currentTime - l.current_scene.stats.lastTime ); - this.updateMesh(); - - this.updateCamera( rY, tY, tZ ); + if (this.game) { + this.sync(); + this.updateCamera( this.game.object.rY, this.game.object.tY, this.game.object.tZ ); - this.animateTrail( rY ); + this.animateTrail( this.game.object.rY ); + } } } @@ -524,21 +526,21 @@ export default class Valiant { let trailOffset = 0; // Only offset the trail effect if we are going forward which is (z-1) in numerical terms - if ( this.mesh.userData.object.airSpeed < 0 ) { + if ( this.game.object.airSpeed < 0 ) { // Update ship thruster - this.animateThruster( this.mesh.userData.object.airSpeed, this.thruster.centralConeBurner, .5 ); - this.animateThruster( this.mesh.userData.object.airSpeed, this.thruster.outerCylBurner, .5 ); + this.animateThruster( this.game.object.airSpeed, this.thruster.centralConeBurner, .5 ); + this.animateThruster( this.game.object.airSpeed, this.thruster.outerCylBurner, .5 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.rearConeBurner, -1 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.centralConeBurner, 1 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.outerCylBurner, -1 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.innerCylBurner, 1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.rearConeBurner, -1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.centralConeBurner, 1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.outerCylBurner, -1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.innerCylBurner, 1 ); // Limit playback rate to 5x as large values freak out the browser. - this.thruster.videoElement.playbackRate = Math.min( 5, 0.25 + Math.abs( this.mesh.userData.object.airSpeed ) ); + this.thruster.videoElement.playbackRate = Math.min( 5, 0.25 + Math.abs( this.game.object.airSpeed ) ); - trailOffset += this.trail_position_z - Math.abs( this.mesh.userData.object.airSpeed ); + trailOffset += this.trail_position_z - Math.abs( this.game.object.airSpeed ); this.trail.mesh.material.uniforms.headColor.value.set( 255 / 255, 212 / 255, 148 / 255, .8 ); // RGBA. } @@ -547,11 +549,11 @@ export default class Valiant { } // Update the trail position based on above calculations. - this.trail.targetObject.position.y = this.trail_position_y + this.mesh.userData.object.verticalSpeed; + this.trail.targetObject.position.y = this.trail_position_y + this.game.object.verticalSpeed; this.trail.targetObject.position.z = trailOffset; if ( rY != 0 ) { - this.trail.targetObject.position.x = rY * this.mesh.userData.object.airSpeed; + this.trail.targetObject.position.x = rY * this.game.object.airSpeed; this.trail.targetObject.position.y += Math.abs( this.trail.targetObject.position.x ) / 4; } else { diff --git a/client/src/app/scenograph/overlays/heads-up-display.js b/client/src/app/scenograph/overlays/heads-up-display.js index 4d282ebc..955f74e4 100644 --- a/client/src/app/scenograph/overlays/heads-up-display.js +++ b/client/src/app/scenograph/overlays/heads-up-display.js @@ -77,10 +77,10 @@ export default class HeadsUpDisplay { l.scenograph.overlays.hud.container.classList.remove('portrait'); } - let aspd = -l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.mesh.userData.object.airSpeed); + let aspd = -l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.object.airSpeed); l.scenograph.overlays.hud.aspdElement.innerHTML = `AIRSPEED: ${aspd}km/h`; - let vspd = l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.mesh.userData.object.verticalSpeed); + let vspd = l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.object.verticalSpeed); l.scenograph.overlays.hud.vspdElement.innerHTML = `VERT. SPD: ${vspd}km/h`; let heading = THREE.MathUtils.radToDeg( l.scenograph.actors.player.vehicle.mesh.rotation.y ); diff --git a/client/src/app/ui/menus/main_menu.js b/client/src/app/ui/menus/main_menu.js index 2b2ab675..c60047fd 100644 --- a/client/src/app/ui/menus/main_menu.js +++ b/client/src/app/ui/menus/main_menu.js @@ -41,7 +41,8 @@ export default class Main_Menu { //l.ui.hide_flight_instruments(); // Show game mode buttons. - this.buttons.single_player.hidden = false; + this.buttons.player_one.hidden = false; + this.buttons.player_two.hidden = false; this.buttons.multi_player.hidden = false; // Hide game exit button to return to main menu. @@ -70,14 +71,15 @@ export default class Main_Menu { l.ui.score_table.show(); }); - this.buttons.test = this.pane.addButton( { - title: 'Test - Hangar', + this.buttons.player_one = this.pane.addButton( { + title: 'P1: Overworld', } ); - this.buttons.test.on( 'click', () => { - new l.routes.hangar(); + this.buttons.player_one.on( 'click', () => { + new l.routes.singlePlayer(); // Hide game mode buttons. - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; // Hide main menu and change it's title @@ -89,17 +91,18 @@ export default class Main_Menu { // Show game scores button this.buttons.scores.hidden = false; - + } ); - this.buttons.single_player = this.pane.addButton( { - title: 'Single Player', + this.buttons.player_two = this.pane.addButton( { + title: 'P2: Hangar', } ); - this.buttons.single_player.on( 'click', () => { - new l.routes.singlePlayer(); + this.buttons.player_two.on( 'click', () => { + new l.routes.hangar(); // Hide game mode buttons. - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; // Hide main menu and change it's title @@ -111,7 +114,7 @@ export default class Main_Menu { // Show game scores button this.buttons.scores.hidden = false; - + } ); this.buttons.multi_player = this.pane.addButton( { @@ -122,7 +125,8 @@ export default class Main_Menu { new l.routes.multiPlayer(); // Hide game mode buttons. - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; // Hide main menu @@ -147,7 +151,8 @@ export default class Main_Menu { // Hide all the other buttons. this.buttons.scores.hidden = true; this.buttons.exit_game.hidden = true; - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; this.buttons.settings.hidden = true; this.buttons.help.hidden = true; @@ -173,7 +178,8 @@ export default class Main_Menu { // Show all the other buttons. this.buttons.scores.hidden = l.mode !== 'home' ? false : true; this.buttons.exit_game.hidden = l.mode !== 'home' ? false : true; - this.buttons.single_player.hidden = l.mode !== 'home' ? true : false; + this.buttons.player_one.hidden = l.mode !== 'home' ? true : false; + this.buttons.player_two.hidden = l.mode !== 'home' ? true : false; this.buttons.multi_player.hidden = l.mode !== 'home' ? true : false; this.buttons.settings.hidden = false; this.buttons.help.hidden = l.mode !== 'home' ? true : false; @@ -228,4 +234,4 @@ export default class Main_Menu { return this; } -} \ No newline at end of file +} diff --git a/game/ecs/components/name.ts b/game/ecs/components/name.ts new file mode 100644 index 00000000..fbc467a1 --- /dev/null +++ b/game/ecs/components/name.ts @@ -0,0 +1,2 @@ +// components/Name.ts +export type Name = string; diff --git a/game/ecs/components/transform.ts b/game/ecs/components/transform.ts new file mode 100644 index 00000000..9195245f --- /dev/null +++ b/game/ecs/components/transform.ts @@ -0,0 +1,8 @@ +// components/Transform.ts + +import { Vec3 } from "../types"; + +export interface Transform { + position: Vec3 + rotation: Vec3 +} diff --git a/game/ecs/scenes/overworld.yml b/game/ecs/scenes/overworld.yml new file mode 100644 index 00000000..de4b20b3 --- /dev/null +++ b/game/ecs/scenes/overworld.yml @@ -0,0 +1,166 @@ +entities: + - id: player1 + components: + Name: Player One + Transform: + position: { x: -65000, y: -35000, z: 1500 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + primaryColor: FF0000 + secondaryColor: FFFF00 + object: valiant + PlayerInput: {} + FactionStanding: { union: 0.5, winthrom: 1.0, zaar: 0.0 } + AI: defensive + Health: + current: 100 + max: 100 + + - id: player2 + components: + Name: Player Two + Transform: + position: { x: -65000, y: -35000, z: 1500 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + primaryColor: 0000FF + secondaryColor: 00FFFF + object: person + PlayerInput: {} + HangarAssigned: + structureId: lambda_city + hangarId: hangar_1 + FactionStanding: { union: 0.5, winthrom: 1.0, zaar: 0.0 } + AI: defensive + Health: + current: 100 + max: 100 + + - id: pirate_1 + components: + Name: Pirate One + Transform: + position: { x: -65000, y: -35000, z: 1500 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + primaryColor: 00FF00 + secondaryColor: FF00FF + object: raven + AI: aggressive + FactionStanding: { union: 0.5, winthrom: 0.0, zaar: 1.0 } + Health: + current: 100 + max: 100 + + - id: cargo_1 + components: + Name: Capy One + Transform: + position: { x: -35000, y: -2000, z: 10000 } + Renderable: + object: cargoShip + AI: passive + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + Health: + current: 100 + max: 100 + + - id: cargo_2 + components: + Name: Capy Two + Transform: + position: { x: -34000, y: -1500, z: 10000 } + Renderable: + object: cargoShip + AI: passive + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + Health: + current: 100 + max: 100 + + - id: cargo_3 + components: + Name: Capy Three + Transform: + position: { x: -36000, y: -1500, z: 10000 } + Renderable: + object: cargoShip + AI: passive + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + Health: + current: 100 + max: 100 + + # Static objects + - id: einstein_well + components: + Name: Einstein Well + Transform: + position: { x: 0, y: -7450, z: -70000 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: newton_well + components: + Name: Newton Well + Transform: + position: { x: 0, y: -7450, z: 70000 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: galileo_well + components: + Name: Galileo Well + Transform: + position: { x: -70000, y: -7450, z: 0 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: planck_well + components: + Name: Planck Well + Transform: + position: { x: 70000, y: -7450, z: 0 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: lambda_city + components: + Name: Lambda City + Transform: + position: { x: -35000, y: 1500, z: -65000 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + object: platform + Hangars: + - id: hangar_1 + name: Hangar 1 + position: { x: 0, y: 6100, z: -8620 } + rotation: { x: 0, y: 0, z: 0 } + - id: hangar_2 + name: Hangar 2 + position: { x: 0, y: 505, z: 0 } + rotation: { x: 0, y: 3.14159, z: 0 } + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: refinery_91 + components: + Name: Refinery 91 + Transform: + position: { x: 20000, y: 153, z: -20000 } + Renderable: + object: refinery + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: refinery_92 + components: + Name: Refinery 92 + Transform: + position: { x: -20000, y: 153, z: 20000 } + Renderable: + object: refinery + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } diff --git a/game/ecs/systems/movement.ts b/game/ecs/systems/movement.ts new file mode 100644 index 00000000..c3dfd135 --- /dev/null +++ b/game/ecs/systems/movement.ts @@ -0,0 +1,25 @@ +import { Transform } from "../components/Transform"; +import { Velocity } from "../components/Velocity"; + +export function movementSystem(entities: Map, deltaMs: number) { + const dt = deltaMs / 1000; // convert ms to seconds + + for (const entity of entities.values()) { + const transform = entity.components.Transform as Transform | undefined; + const velocity = entity.components.Velocity as Velocity | undefined; + + if (transform && velocity) { + // Linear movement + transform.position.x += velocity.linear.x * dt; + transform.position.y += velocity.linear.y * dt; + transform.position.z += velocity.linear.z * dt; + + // Angular movement + if (velocity.angular) { + transform.rotation.x += velocity.angular.x * dt; + transform.rotation.y += velocity.angular.y * dt; + transform.rotation.z += velocity.angular.z * dt; + } + } + } +} diff --git a/game/ecs/types.ts b/game/ecs/types.ts new file mode 100644 index 00000000..acffdfaa --- /dev/null +++ b/game/ecs/types.ts @@ -0,0 +1,11 @@ +// types.ts + +export interface Vec3 { + x: number + y: number + z: number +} + +export interface AABB { + halfSize: Vec3; +} diff --git a/game/ecs/world.ts b/game/ecs/world.ts new file mode 100644 index 00000000..4f36e953 --- /dev/null +++ b/game/ecs/world.ts @@ -0,0 +1,124 @@ +/** + * Game world simulation + * + * - Loads a scene definition + * - Builds entities using components + * - Advances simulation on a fixed timestep + * + */ + +import Overworld from "./scenes/overworld.yml"; + +import { Vec3 } from "./types"; + +import { Name } from "./components/name"; +import { Transform } from "./components/Transform"; + +import { movementSystem } from "./systems/movement"; + +interface WorldConfig { + entities: Record; +} + +interface WorldInstance { + entities: Record; +} + +export default class World { + + public config: WorldConfig; + public instance: WorldInstance; + + public fixedDelta: number; + public lastUpdateTime: number; + + private accumulator = 0; + private running = false; + + /** + * Construct the game world instance. + * + * Parses config and builds a self updating virtual world simulation. + * + * @todo: + * - load abstract scene definition file overworld.yml and parse it + * - loop over config to load game world simulation in here and scenograph in the client + */ + constructor( sceneName: string ) { + if ( sceneName === 'Overworld' ) { + this.config = { + entities: Overworld.entities, + } + this.instance = { + entities: new Map(), + }; + + this.lastUpdateTime = performance.now(); + this.fixedDelta = 16; // ~60 FPS for logic + + this.load(); + this.start(); + } + } + + load() { + // Load entities. + for (const entityConfig of this.config.entities.values()) { + const entityInstance: any = { components: {}, config: entityConfig }; + + // Attach each component + for (const [componentName, componentData] of Object.entries(entityConfig.components)) { + switch (componentName) { + case 'Name': + entityInstance.components.Name = componentData as Name; + break; + case 'Transform': + entityInstance.components.Transform = { + position: componentData.position, + rotation: componentData.rotation + } as Transform; + break; + } + } + + this.instance.entities.set(entityConfig.id, entityInstance); + } + + } + + start() { + this.running = true; + this.lastUpdateTime = performance.now(); + + const loop = () => { + if (!this.running) return; + + const now = performance.now(); + const frameTime = now - this.lastUpdateTime; + this.lastUpdateTime = now; + + // Prevent spiral of death + this.accumulator += Math.min(frameTime, 250); + + while (this.accumulator >= this.fixedDelta) { + this.update(); + this.accumulator -= this.fixedDelta; + } + + setTimeout(loop, 0); + }; + + loop(); + } + + stop() { + this.running = false; + } + + update() { + movementSystem(this.instance.entities, this.fixedDelta); + // Later: call other systems here, e.g., AI, collision, rendering + } + + +} diff --git a/game/src/objects/aircraft/valiant.ts b/game/src/objects/aircraft/valiant.ts deleted file mode 100644 index ad75b337..00000000 --- a/game/src/objects/aircraft/valiant.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Valiant Aircraft, default ship and Kingdom of Winthrom main vehicle - */ - -import BaseAircraft from './base'; - -class Valiant extends BaseAircraft { - - constructor( mesh ) { - super( mesh ); // Call the constructor of the base class - } - -} - -module.exports = Valiant; diff --git a/game/src/objects/person.ts b/game/src/objects/person.ts deleted file mode 100644 index 6e8fc02f..00000000 --- a/game/src/objects/person.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Base Aircraft class - * - * @todo: - * - Add weight and wind resistance - */ - -import { normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../helpers'; - -export default class Person { - public score: { kills: number; deaths: number } = { kills: 0, deaths: 0 }; - public standing: number = 0; - public hitPoints: number = 100; - public airSpeed: number = 0; - public verticalSpeed: number = 0; - public maxForward: number = 16 / 60; // 8 km/h @ 60 FPS - public maxBackward: number = 16 / 60; - public maxUp: number = 4 / 60; - public maxDown: number = 16 / 60; // gravity? - - public position: { x: number; y: number; z: number } = { x: 0, y: 8.5, z: 0 }; - public startPosition: { x: number; y: number; z: number } = { x: 0, y: 8.5, z: 0 }; - public rotation: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 }; - - public controls: { - changing: boolean, - forward: boolean; - back: boolean; - jump: boolean; - crouch: boolean; - turnLeft: boolean; - turnRight: boolean; - } = { - changing: false, - forward: false, - back: false, - jump: false, - crouch: false, - turnLeft: false, - turnRight: false - }; - - constructor() { - } - - /** - * Change aircraft velocity based on current and what buttons are pushed by the player. - * - * @param currentVelocity - * @param increasePushed - * @param decreasePushed - */ - private _changeVelocity(stepIncrease, stepDecrease, currentVelocity, increasePushed, decreasePushed, increaseMax, decreaseMax, dragFactor): number { - let newVelocity = currentVelocity; - - if (increasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity - stepIncrease; - if (Math.abs(increaseMax) >= Math.abs(tempVelocity)) - newVelocity = tempVelocity; - } - else { - if (decreasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity + stepDecrease; - if (Math.abs(decreaseMax) >= tempVelocity) - newVelocity = tempVelocity; - } - else { - if (newVelocity != 0) { - - if (Math.abs(newVelocity) > 0.1) { - // Ease out the velocity exponentially to simulate drag - newVelocity *= dragFactor; - } - else { - newVelocity = 0; - } - } - - } - } - - return newVelocity; - } - - /** - * Move the aircraft based on velocity, direction and time delta between frames. - * - * @param time_delta - */ - public move( time_delta: number ): object { - let stepSize: number = .025 * normaliseSpeedDelta( time_delta ), - rY: number = 0, - tZ: number = 0, - tY: number = 0, - radian: number = - (Math.PI / 180) * stepSize * 100; - - if ( this.controls.forward || this.controls.back ){ - // Update Airspeed (horizontal velocity) - this.airSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.airSpeed ) / this.maxForward ) ), - stepSize, - this.airSpeed, - this.controls.forward, - this.controls.back, - this.maxForward, - this.maxBackward, - easeOutExpo( 0.987 ) - ); - } - else { - this.airSpeed = 0; - } - - - // Update Vertical Speed (velocity) - this.verticalSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxUp ) ), - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxDown ) ), - this.verticalSpeed, - this.controls.crouch, // Note: Move Down/Up is reversed by design. - this.controls.jump, - this.maxDown, - this.maxUp, - easeInQuad( 0.321 ) - ); - - // Check the vertical speed exceeds minimum threshold for change in vertical position - if (Math.abs(this.verticalSpeed) > 0.01) { - tY = this.verticalSpeed; - } - - // Turning - if (this.controls.turnRight) { - rY += radian; - } - else { - if (this.controls.turnLeft) { - rY -= radian; - } - } - - // Check if we have significant airspeed - if (Math.abs(this.airSpeed) > 0.01) { - - // Set change in Z position based on airspeed - tZ = this.airSpeed; - - } - - if (rY != 0) { - if (Math.abs(this.rotation.z) < Math.PI / 4) { - this.rotation.z += rY / Math.PI; - } - - this.rotation.y += rY; - } - - let xDiff = tZ * Math.sin(this.rotation.y), - zDiff = tZ * Math.cos(this.rotation.y); - - // "1" is the floor limit as it's the ocean surface and the camera clips through the water any lower. - if (this.position.y + tY >= 1 ) { - this.position.y += tY; - } else { - this.verticalSpeed = 0; - } - - this.position.x += xDiff; - this.position.z += zDiff; - - return [ rY, tY, tZ ]; - - } - -} diff --git a/game/src/objects/aircraft/base.ts b/game/src/objects/vehicles/base.ts similarity index 54% rename from game/src/objects/aircraft/base.ts rename to game/src/objects/vehicles/base.ts index c8e617c3..d85f5786 100644 --- a/game/src/objects/aircraft/base.ts +++ b/game/src/objects/vehicles/base.ts @@ -149,142 +149,5 @@ export default class BaseAircraft { return [ damage, targetDestroyed ]; } - /** - * Change aircraft velocity based on current and what buttons are pushed by the player. - * - * @param currentVelocity - * @param increasePushed - * @param decreasePushed - */ - private _changeVelocity(stepIncrease, stepDecrease, currentVelocity, increasePushed, decreasePushed, increaseMax, decreaseMax, dragFactor): number { - let newVelocity = currentVelocity; - - if (increasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity - stepIncrease; - if (Math.abs(increaseMax) >= Math.abs(tempVelocity)) - newVelocity = tempVelocity; - } - else { - if (decreasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity + stepDecrease; - if (Math.abs(decreaseMax) >= tempVelocity) - newVelocity = tempVelocity; - } - else { - if (newVelocity != 0) { - - if (Math.abs(newVelocity) > 0.1) { - // Ease out the velocity exponentially to simulate drag - newVelocity *= dragFactor; - } - else { - newVelocity = 0; - } - } - - } - } - - return newVelocity; - } - - /** - * Move the aircraft based on velocity, direction and time delta between frames. - * - * @param time_delta - */ - public move( time_delta: number ): object { - let stepSize: number = .05 * normaliseSpeedDelta( time_delta ), - rY: number = 0, - tZ: number = 0, - tY: number = 0, - radian: number = (Math.PI / 180); - - // Update Airspeed (horizontal velocity) - this.airSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.airSpeed ) / this.maxForward ) ), - stepSize, - this.airSpeed, - this.controls.throttleUp, - this.controls.throttleDown, - this.maxForward, - this.maxBackward, - easeOutExpo( 0.987 ) - ); - - // Update Vertical Speed (velocity) - this.verticalSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxUp ) ), - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxDown ) ), - this.verticalSpeed, - this.controls.moveDown, // Note: Move Down/Up is reversed by design. - this.controls.moveUp, - this.maxDown, - this.maxUp, - easeInQuad( 0.321 ) - ); - - // Check the vertical speed exceeds minimum threshold for change in vertical position - if (Math.abs(this.verticalSpeed) > 0.01) { - tY = this.verticalSpeed; - } - - // Turning - if (this.controls.moveLeft) { - rY += radian; - } - else { - if (this.controls.moveRight) { - rY -= radian; - } - } - - // Check if we have significant airspeed - if (Math.abs(this.airSpeed) > 0.01) { - - // Set change in Z position based on airspeed - tZ = this.airSpeed; - - } - - // Animate the ship's rotation in the game client based on controls. - if ( - !(this.controls.throttleDown || this.controls.throttleUp) && - !(this.controls.moveDown || this.controls.moveUp) - ) { - this.rotation.x *= .9; - } - - if (rY != 0) { - if (Math.abs(this.rotation.z) < Math.PI / 4) { - this.rotation.z += rY / Math.PI; - } - - this.rotation.y += rY; - } - else { - this.rotation.z *= .9; - } - - let xDiff = tZ * Math.sin(this.rotation.y), - zDiff = tZ * Math.cos(this.rotation.y); - - // "1" is the floor limit as it's the ocean surface and the camera clips through the water any lower. - if (this.position.y + tY >= 1 ) { - this.position.y += tY; - } else { - this.verticalSpeed = 0; - } - - this.position.x += xDiff; - this.position.z += zDiff; - - return [ rY, tY, tZ ]; - - } } diff --git a/game/src/objects/person2.ts b/game/src/objects/vehicles/person.ts similarity index 98% rename from game/src/objects/person2.ts rename to game/src/objects/vehicles/person.ts index a804f7b0..2108c566 100644 --- a/game/src/objects/person2.ts +++ b/game/src/objects/vehicles/person.ts @@ -2,9 +2,9 @@ * Person class */ -import ObjectBase from './base'; +import ObjectBase from '../base'; import BaseActor from '../actors/base2'; -import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../helpers'; +import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../../helpers'; import { Vec3 } from '../types'; diff --git a/game/src/objects/aircraft/raven.ts b/game/src/objects/vehicles/raven.ts similarity index 94% rename from game/src/objects/aircraft/raven.ts rename to game/src/objects/vehicles/raven.ts index cb20fe78..be37ea3a 100644 --- a/game/src/objects/aircraft/raven.ts +++ b/game/src/objects/vehicles/raven.ts @@ -2,7 +2,7 @@ * Raven Aircraft, default ship and Los Zaar main vehicle. */ -import BaseAircraft from './base'; +import BaseAircraft from '../base'; class Raven extends BaseAircraft { diff --git a/game/src/objects/vehicles/valiant.ts b/game/src/objects/vehicles/valiant.ts new file mode 100644 index 00000000..8ff652ae --- /dev/null +++ b/game/src/objects/vehicles/valiant.ts @@ -0,0 +1,128 @@ +/** + * Valiant Aircraft, default ship and Kingdom of Winthrom main vehicle + */ + +import ObjectBase from '../base'; +import ActorBase from '../actors/base2'; + +import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../../helpers'; + +class Valiant extends ObjectBase { + + // Actor that controls this object. + public actor?: ActorBase; + + // Object world parameters + public hitPoints: number = 100; + public airSpeed: number = 0; + public verticalSpeed: number = 0; + public maxForward: number = 16 / 60; // 8 km/h @ 60 FPS + public maxBackward: number = 16 / 60; + public maxUp: number = 4 / 60; + public maxDown: number = 16 / 60; // gravity? + + constructor( mesh ) { + super( mesh ); // Call the constructor of the base class + } + + /** + * Move the aircraft based on velocity, direction and time delta between frames. + * + * @param time_delta + */ + public update( time_delta: number ): object { + if (!this.actor) return; + let stepSize: number = .05 * normaliseSpeedDelta( time_delta ), + rY: number = 0, + tZ: number = 0, + tY: number = 0, + radian: number = (Math.PI / 180); + + // Update Airspeed (horizontal velocity) + this.airSpeed = changeVelocity( + stepSize * easeInOutExpo( 1 - ( Math.abs ( this.airSpeed ) / this.maxForward ) ), + stepSize, + this.airSpeed, + this.actor.controls.throttleUp, + this.actor.controls.throttleDown, + this.maxForward, + this.maxBackward, + easeOutExpo( 0.987 ) + ); + + // Update Vertical Speed (velocity) + this.verticalSpeed = changeVelocity( + stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxUp ) ), + stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxDown ) ), + this.verticalSpeed, + this.actor.controls.moveDown, // Note: Move Down/Up is reversed by design. + this.actor.controls.moveUp, + this.maxDown, + this.maxUp, + easeInQuad( 0.321 ) + ); + + // Check the vertical speed exceeds minimum threshold for change in vertical position + if (Math.abs(this.verticalSpeed) > 0.01) { + tY = this.verticalSpeed; + } + + // Turning + if (this.actor.controls.moveLeft) { + rY += radian; + } + else { + if (this.actor.controls.moveRight) { + rY -= radian; + } + } + + // Check if we have significant airspeed + if (Math.abs(this.airSpeed) > 0.01) { + + // Set change in Z position based on airspeed + tZ = this.airSpeed; + + } + + // Animate the ship's rotation in the game client based on controls. + if ( + !(this.actor.controls.throttleDown || this.actor.controls.throttleUp) && + !(this.actor.controls.moveDown || this.actor.controls.moveUp) + ) { + this.rotation.x *= .9; + } + + if (rY != 0) { + if (Math.abs(this.rotation.z) < Math.PI / 4) { + this.rotation.z += rY / Math.PI; + } + + this.rotation.y += rY; + } + else { + this.rotation.z *= .9; + } + + let xDiff = tZ * Math.sin(this.rotation.y), + zDiff = tZ * Math.cos(this.rotation.y); + + // "1" is the floor limit as it's the ocean surface and the camera clips through the water any lower. + if (this.position.y + tY >= 1 ) { + this.position.y += tY; + } else { + this.verticalSpeed = 0; + } + + this.position.x += xDiff; + this.position.z += zDiff; + + this.rY = rY; + this.tY = tY; + this.tZ = tZ; + + } + +} + +module.exports = Valiant; diff --git a/game/src/systems/weapons.ts b/game/src/systems/weapons.ts index a0af9edc..a28eae99 100644 --- a/game/src/systems/weapons.ts +++ b/game/src/systems/weapons.ts @@ -1,6 +1,6 @@ /** * Weapons Systems - * + * * Defines an interface that fires a vehicles weapons. */ @@ -48,10 +48,14 @@ export default class Weapons extends BaseSystem { target.locked && this.ready() ) { + if (! this.mesh.userData.object ) { + debugger; + } + let negativeStanding = false; // Check if the object has an object class game object and do a standing check. - if ( + if ( target.mesh.userData.hasOwnProperty('object') && target.mesh.userData.object.hasOwnProperty('standing') && target.mesh.userData.object.standing != this.mesh.userData.object.standing diff --git a/game/src/world.ts b/game/src/world.ts index 3c329cb9..9fb88493 100644 --- a/game/src/world.ts +++ b/game/src/world.ts @@ -10,7 +10,8 @@ import Overworld from "./scenes/overworld.yml"; import ActorPlayer from './actors/player2'; import ObjectHangar from './objects/structures/hangar'; -import ObjectPerson from './objects/person2'; +import ObjectPerson from './objects/vehicles/person'; +import ObjectValiant from './objects/vehicles/valiant'; interface WorldConfig { actors: Record; @@ -127,7 +128,11 @@ export default class World { else if (config.model == 'hangar') { return new ObjectHangar(config); } + else if (config.model == 'valiant') { + return new ObjectValiant(config); + } else { + console.log('Unknown object model:', config.model); return false; } } @@ -176,6 +181,7 @@ export default class World { } checkHangarCollisions(actorInstance: any) { + // Get object's proposed AABB at next position const actorAABB = actorInstance.object.getAABBNext();