From 5b7d04bd39d8403dab7dc1010468223cad2a4141 Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Mon, 17 Feb 2025 16:51:06 -0600 Subject: [PATCH] changed game dynamics. --- index.html | 4 +- public/styles.css | 40 +++++------- src/defaultScene.ts | 1 + src/demo.ts | 18 ++++++ src/level.ts | 8 +++ src/level1.ts | 81 ++++++++++++------------ src/main.ts | 85 ++++++++++++++----------- src/ship.ts | 147 ++++++++++++++++++++------------------------ 8 files changed, 200 insertions(+), 184 deletions(-) create mode 100644 src/demo.ts create mode 100644 src/level.ts diff --git a/index.html b/index.html index 2fe9e75..a0c3459 100644 --- a/index.html +++ b/index.html @@ -12,10 +12,12 @@ } }); + + -
+
Loading...
diff --git a/public/styles.css b/public/styles.css index c2c988f..6217ed7 100644 --- a/public/styles.css +++ b/public/styles.css @@ -8,31 +8,7 @@ body { font-family: Roboto, sans-serif; font-size: large; } -#startGame { - position: absolute; - display: block; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - padding: 48px; - border-radius: 12px; - background-color: #000; - color: #fff; - z-index: 1000; -} -#music { - position: absolute; - display: block; - top: 75%; - left: 50%; - width: 50%; - transform: translate(-50%, -50%); - padding: 48px; - border-radius: 12px; - background-color: #000; - color: #fff; - z-index: 1000; -} + #startButton { background-color: #000; color: #fff; @@ -41,11 +17,25 @@ body { cursor: pointer; font-size: xxx-large; display: none; + z-index: 1000; } #startButton.ready { display: block; background-color: red; } +#loadingDiv { + z-index: 1000; + color: #fff; +} +#mainDiv { + position: absolute; + display: block; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1000; +} + #gameCanvas { width: 100%; height: 100%; diff --git a/src/defaultScene.ts b/src/defaultScene.ts index 8136b4c..75b563d 100644 --- a/src/defaultScene.ts +++ b/src/defaultScene.ts @@ -2,5 +2,6 @@ import {Scene, WebXRDefaultExperience} from "@babylonjs/core"; export class DefaultScene { public static MainScene: Scene; + public static DemoScene: Scene; public static XR: WebXRDefaultExperience; } \ No newline at end of file diff --git a/src/demo.ts b/src/demo.ts new file mode 100644 index 0000000..e0ed967 --- /dev/null +++ b/src/demo.ts @@ -0,0 +1,18 @@ +import {DefaultScene} from "./defaultScene"; +import {ArcRotateCamera, MeshBuilder, PointerEventTypes, Vector3} from "@babylonjs/core"; +import {Main} from "./main"; + +export default class Demo { + private _main: Main; + constructor(main: Main) { + this._main = main; + this.initialize(); + } + private async initialize() { + if (!DefaultScene.DemoScene) { + return; + } + const scene = DefaultScene.DemoScene; + const camera = new ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2, 5, new Vector3(0, 0, 0), scene); + } +} \ No newline at end of file diff --git a/src/level.ts b/src/level.ts new file mode 100644 index 0000000..88d478b --- /dev/null +++ b/src/level.ts @@ -0,0 +1,8 @@ +import {Observable} from "@babylonjs/core"; + +export default interface Level { + initialize(): void; + dispose(): void; + play(): void; + getReadyObservable(): Observable; +} \ No newline at end of file diff --git a/src/level1.ts b/src/level1.ts index c6e4bde..d384e09 100644 --- a/src/level1.ts +++ b/src/level1.ts @@ -1,35 +1,59 @@ import {DefaultScene} from "./defaultScene"; import { AbstractMesh, - Color3, DistanceConstraint, InstancedMesh, Mesh, + Color3, DistanceConstraint, Engine, InstancedMesh, Mesh, MeshBuilder, Observable, ParticleHelper, PhysicsAggregate, PhysicsMotionType, - PhysicsShapeType, + PhysicsShapeType, Sound, StandardMaterial, TransformNode, Vector3 } from "@babylonjs/core"; import {Ship} from "./ship"; import {ScoreEvent} from "./scoreEvent"; import {RockFactory} from "./starfield"; +import Level from "./level"; -export class Level1 { +export class Level1 implements Level { private _ship: Ship; + private _onReadyObservable: Observable = new Observable(); private _initialized: boolean = false; private _startBase: AbstractMesh; private _endBase: AbstractMesh; public onScoreObservable: Observable = new Observable(); - constructor(ship: Ship) { - this._ship = ship; + constructor() { + this._ship = new Ship(); + const xr = DefaultScene.XR; + xr.baseExperience.onInitialXRPoseSetObservable.add(() => { + xr.baseExperience.camera.parent = this._ship.transformNode; + xr.baseExperience.camera.position = new Vector3(0, 0, 0); + }); + xr.input.onControllerAddedObservable.add((controller) => { + this._ship.addController(controller); + }); this.createStartBase(); - this.createEndBase(); + + this.initialize(); + + } + + getReadyObservable(): Observable { + return this._onReadyObservable; } private scored: Set = new Set(); - + public play() { + const background = new Sound("background", "/background.mp3", DefaultScene.MainScene, () => { + }, {loop: true, autoplay: true, volume: .2}); + DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); + } + public dispose() { + this._startBase.dispose(); + this._endBase.dispose(); + } public async initialize() { if (this._initialized) { return; @@ -37,43 +61,18 @@ export class Level1 { this._initialized = true; ParticleHelper.BaseAssetsUrl = window.location.href; this._ship.position = new Vector3(0, 1, 0); - await RockFactory.init(); - const distance = Vector3.Distance(this._startBase.getAbsolutePosition(), this._endBase.getAbsolutePosition()); const baseTransform = new TransformNode("baseTransform", DefaultScene.MainScene); - baseTransform.position = this._endBase.getAbsolutePosition(); - - for (let i = 0; i < 20; i++) { - //const constraintDistance = distance - 20; - const dist = (Math.random() * 80) + 20; - const startPos = this._endBase.getAbsolutePosition().add(new Vector3(dist,dist,dist)); - const startTrans = new TransformNode("startTransform", DefaultScene.MainScene); - startTrans.position = startPos; - startTrans.setParent(baseTransform); - baseTransform.rotation = Vector3.Random(0, Math.PI * 2); - const rock = await RockFactory.createRock(i, startTrans.getAbsolutePosition(), Vector3.Random(1, 5)) - startTrans.dispose(); - - - - + baseTransform.position = this._startBase.getAbsolutePosition(); + for (let i = 0; i < 50; i++) { + const dist = (Math.random() * 200) + 190; + const rock = await RockFactory.createRock(i, new Vector3(Math.random() * 200 +50 * Math.sign(Math.random() -.5),200,200), Vector3.Random(1, 5)) const constraint = new DistanceConstraint(dist, DefaultScene.MainScene); - rock.physicsBody.addConstraint(this._endBase.physicsBody, constraint); - rock.physicsBody.applyImpulse(Vector3.Random(-1, 1).scale(1000), rock.getAbsolutePosition()); - rock.physicsBody.setAngularVelocity(Vector3.Random(-.5, .5)); - /* const material = new StandardMaterial("material", DefaultScene.MainScene); - material.emissiveColor = Color3.Random(); - const sphere = MeshBuilder.CreateSphere("sphere", {diameter: 1}, DefaultScene.MainScene); - sphere.material = material; - - window.setInterval(() => { - const track = new InstancedMesh("track", sphere); - track.position = rock.physicsBody.transformNode.getAbsolutePosition(); - - }, 200); - - */ + //rock.physicsBody.addConstraint(this._endBase.physicsBody, constraint); + this._startBase.physicsBody.addConstraint(rock.physicsBody, constraint); + rock.physicsBody.applyForce(Vector3.Random(-1, 1).scale(50000000), rock.getAbsolutePosition()); + //rock.physicsBody.setAngularVelocity(Vector3.Random(-.5, .5)); } } @@ -97,7 +96,7 @@ export class Level1 { height: 1, tessellation: 72 }, DefaultScene.MainScene); - mesh.position = new Vector3(0, 5, 200); + mesh.position = new Vector3(0, 5, 500); const material = new StandardMaterial("material", DefaultScene.MainScene); material.diffuseColor = new Color3(0, 1, 0); mesh.material = material; diff --git a/src/main.ts b/src/main.ts index 501c861..7fcf961 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,21 +6,42 @@ import {DefaultScene} from "./defaultScene"; import {Ship} from "./ship"; import {Level1} from "./level1"; import {Scoreboard} from "./scoreboard"; +import Demo from "./demo"; +import Level from "./level"; const webGpu = false; const canvas = (document.querySelector('#gameCanvas') as HTMLCanvasElement); - +enum GameState { + PLAY, + DEMO +} export class Main { - + private _loadingDiv: HTMLElement; + private _currentLevel: Level; + private _gameState: GameState = GameState.DEMO; constructor() { - + this._loadingDiv = document.querySelector('#loadingDiv'); this.initialize(); - } + document.querySelector('#startButton').addEventListener('click', () => { + Engine.audioEngine.unlock(); + this.play(); + document.querySelector('#mainDiv').remove(); + }); + } + private _started = false; + public play() { + this._gameState = GameState.PLAY; + this._currentLevel.play(); + } + public demo() { + this._gameState = GameState.DEMO; + } private async initialize() { + this._loadingDiv.innerText = "Initializing."; await this.setupScene(); - const xr = await WebXRDefaultExperience.CreateAsync(DefaultScene.MainScene, { + DefaultScene.XR = await WebXRDefaultExperience.CreateAsync(DefaultScene.MainScene, { disablePointerSelection: true, disableTeleportation: true, disableNearInteraction: true, @@ -28,29 +49,17 @@ export class Main { disableDefaultUI: true, }); - const ship = new Ship(); - const scoreboard = new Scoreboard(); - const level = new Level1(ship); + this.setLoadingMessage("Get Ready!"); + this.setLoadingMessage("Initializing Level..."); + this._currentLevel = new Level1(); + this._currentLevel.getReadyObservable().add(() => { + + }); const photoDome = new PhotoDome("testdome", '/8192.webp', {}, DefaultScene.MainScene); - - - - xr.baseExperience.onInitialXRPoseSetObservable.add(() => { - xr.baseExperience.camera.parent = ship.transformNode; - xr.baseExperience.camera.position = new Vector3(0, 0, 0); - - level.onScoreObservable.add((score) => { - scoreboard.onscoreObservable.notifyObservers(score); - }); - - }); - xr.input.onControllerAddedObservable.add((controller) => { - ship.addController(controller); - }); - DefaultScene.XR = xr; - } - + private setLoadingMessage(message: string) { + this._loadingDiv.innerText = message; + } private async setupScene() { let engine: WebGPUEngine | Engine = null; @@ -64,30 +73,36 @@ export class Main { window.onresize = () => { engine.resize(); } + DefaultScene.DemoScene = new Scene(engine); DefaultScene.MainScene = new Scene(engine); - + this.setLoadingMessage("Initializing Physics Engine.."); await this.setupPhysics(); - this.setupInspector(); - engine.runRenderLoop(() => { - DefaultScene.MainScene.render(); + if (!this._started) { + this._started = true; + this._loadingDiv.remove(); + const start = document.querySelector('#startButton'); + start.classList.add('ready'); + } + if (this._gameState == GameState.PLAY) { + DefaultScene.MainScene.render(); + } else { + DefaultScene.DemoScene.render(); + } }); } private async setupPhysics() { const havok = await HavokPhysics(); - const havokPlugin = new HavokPlugin(true, havok); DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin); - DefaultScene.MainScene.collisionsEnabled = true; - - } private setupInspector() { + this.setLoadingMessage("Initializing Inspector..."); window.addEventListener("keydown", (ev) => { if (ev.key == 'i') { import ("@babylonjs/inspector").then((inspector) => { @@ -102,7 +117,7 @@ export class Main { } const main = new Main(); - +const demo = new Demo(main); diff --git a/src/ship.ts b/src/ship.ts index 8a8a2fc..4058851 100644 --- a/src/ship.ts +++ b/src/ship.ts @@ -22,7 +22,7 @@ import { WebXRInputSource } from "@babylonjs/core"; import {DefaultScene} from "./defaultScene"; -import {Radar} from "./radar"; + import {ShipEngine} from "./shipEngine"; import {Level1} from "./level1"; @@ -71,6 +71,45 @@ export class Ship { private _camera: FreeCamera; constructor() { + + this.setup(); + this.initialize(); + } + + private shoot() { + this._shot.play(); + const ammo = MeshBuilder.CreateCapsule("bullet", {radius: .1, height: 2.5}, DefaultScene.MainScene); + ammo.parent = this._ship + ammo.position.y = 2; + ammo.rotation.x = Math.PI / 2; + ammo.setParent(null); + const ammoAggregate = new PhysicsAggregate(ammo, PhysicsShapeType.CONVEX_HULL, { + mass: 1000, + restitution: 0 + }, DefaultScene.MainScene); + + + ammo.material = this._ammoMaterial; + ammoAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC); + + ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(200).add(this._ship.physicsBody.getLinearVelocity())); + + window.setTimeout(() => { + ammoAggregate.dispose(); + ammo.dispose() + }, 1500) + } + + public set position(newPosition: Vector3) { + const body = this._ship.physicsBody; + body.disablePreStep = false; + body.transformNode.position.copyFrom(newPosition); + DefaultScene.MainScene.onAfterRenderObservable.addOnce(() => { + body.disablePreStep = true; + }) + + } + private setup() { this._ship = new TransformNode("ship", DefaultScene.MainScene); this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene); this._glowLayer.intensity = 1; @@ -87,65 +126,12 @@ export class Ship { {loop: false, autoplay: false, volume: .5}); this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene); this._ammoMaterial.emissiveColor = new Color3(1, 1, 0); - this.initialize(); - - } - - private shoot() { - this._shot.play(); - const ammo = MeshBuilder.CreateCapsule("bullet", {radius: .05, height: 1.5}, DefaultScene.MainScene); - ammo.parent = this._ship - ammo.position.y = 2; - ammo.rotation.x = Math.PI / 2; - ammo.setParent(null); - const ammoAggregate = new PhysicsAggregate(ammo, PhysicsShapeType.CONVEX_HULL, { - mass: 1000, - restitution: 0 - }, DefaultScene.MainScene); - - - ammo.material = this._ammoMaterial; - ammoAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC); - - ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(150).add(this._ship.physicsBody.getLinearVelocity())); - - window.setTimeout(() => { - ammoAggregate.dispose(); - ammo.dispose() - }, 5000) - } - - public set position(newPosition: Vector3) { - const body = this._ship.physicsBody; - body.disablePreStep = false; - body.transformNode.position.copyFrom(newPosition); - DefaultScene.MainScene.onAfterRenderObservable.addOnce(() => { - body.disablePreStep = true; - }) - - } - - private async initialize() { const light = new DirectionalLight("light", new Vector3(.1, -1, 0), DefaultScene.MainScene); - const ship = this._ship; - const landingLight = new SpotLight("landingLight", new Vector3(0, 0, 0), new Vector3(0, -.5, .5), 1.5, .5, DefaultScene.MainScene); - landingLight.parent = ship; - landingLight.position.z = 5; - const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "cockpit3.glb", DefaultScene.MainScene); - const shipMesh = importMesh.meshes[0]; - shipMesh.id = "shipMesh"; - shipMesh.name = "shipMesh"; - shipMesh.parent = ship; - shipMesh.rotation.y = Math.PI; - shipMesh.position.y = 1; - shipMesh.position.z = -1; - DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .7; - this._camera = new FreeCamera("Flat Camera", - new Vector3(0, .5, 0), - DefaultScene.MainScene); - this._camera.parent = ship; - const agg = new PhysicsAggregate(ship, PhysicsShapeType.BOX, { + const landingLight = new SpotLight("landingLight", new Vector3(0, 0, 0), new Vector3(0, -.5, .5), 1.5, .5, DefaultScene.MainScene); + landingLight.parent = this._ship; + landingLight.position.z = 5; + const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.BOX, { mass: 100, extents: new Vector3(4, 4, 7.4), center: new Vector3(0, 1, 1.8) @@ -155,8 +141,7 @@ export class Ship { agg.body.setLinearDamping(.1); agg.body.setAngularDamping(.2); agg.body.setAngularVelocity(new Vector3(0, 0, 0)); - agg.body.setCollisionCallbackEnabled(true) - DefaultScene.MainScene.setActiveCameraByName("Flat Camera"); + agg.body.setCollisionCallbackEnabled(true); this.setupKeyboard(); this.setupMouse(); this._controllerObservable.add(this.controllerCallback); @@ -164,6 +149,13 @@ export class Ship { this._rotationNode = new TransformNode("rotation", DefaultScene.MainScene); this._forwardNode.parent = this._ship; this._rotationNode.parent = this._ship; + this._camera = new FreeCamera("Flat Camera", + new Vector3(0, .5, 0), + DefaultScene.MainScene); + this._camera.parent = this._ship; + + DefaultScene.MainScene.setActiveCameraByName("Flat Camera"); + //const sightPos = this._forwardNode.position.scale(30); const sight = MeshBuilder.CreateSphere("sight", {diameter: 1}, DefaultScene.MainScene); sight.parent = this._ship @@ -171,30 +163,21 @@ export class Ship { signtMaterial.emissiveColor = Color3.Yellow(); sight.material = signtMaterial; sight.position = new Vector3(0, 2, 125); + window.setInterval(() => { this.applyForce(); }, 50); - this.onReadyObservable.notifyObservers(true); - //const mirror = new Mirror(this._ship); - const radar = new Radar(this._ship); - - document.querySelector('#loadingDiv').remove(); - //const startButton = document.querySelector('#startButton'); - //startButton.classList.add('ready'); - const level = new Level1(this); - const background = new Sound("background", "/background.mp3", DefaultScene.MainScene, () => { - const startButton = document.querySelector('#startButton'); - startButton.classList.add('ready'); - startButton.addEventListener('click', async () => { - if (!Engine.audioEngine.unlocked) { - Engine.audioEngine.unlock(); - } - console.log('start background'); - await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); - await level.initialize(); - }); - }, {loop: true, autoplay: true, volume: .2}); - + } + private async initialize() { + const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "cockpit3.glb", DefaultScene.MainScene); + const shipMesh = importMesh.meshes[0]; + shipMesh.id = "shipMesh"; + shipMesh.name = "shipMesh"; + shipMesh.parent = this._ship; + shipMesh.rotation.y = Math.PI; + shipMesh.position.y = 1; + shipMesh.position.z = -1; + DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .7; } @@ -239,7 +222,7 @@ export class Ship { this._thrust.play(); } this._thrust.setVolume(Math.abs(this._leftStickVector.y)); - this._forwardValue += this._leftStickVector.y * .4; + this._forwardValue += this._leftStickVector.y * .8; } else { if (this._thrust.isPlaying) { this._thrust.pause();