changed game dynamics.

This commit is contained in:
Michael Mainguy 2025-02-17 16:51:06 -06:00
parent b85d1f5b09
commit 5b7d04bd39
8 changed files with 200 additions and 184 deletions

View File

@ -12,10 +12,12 @@
} }
}); });
</script> </script>
<link rel="prefetch" href="/background.mp3"/>
<link rel="prefetch" href="/8192.webp"/>
</head> </head>
<body> <body>
<canvas id="gameCanvas"></canvas> <canvas id="gameCanvas"></canvas>
<div id="startGame"> <div id="mainDiv">
<div id="loadingDiv">Loading...</div> <div id="loadingDiv">Loading...</div>
<button id="startButton">Start Game</button> <button id="startButton">Start Game</button>
</div> </div>

View File

@ -8,31 +8,7 @@ body {
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;
font-size: large; 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 { #startButton {
background-color: #000; background-color: #000;
color: #fff; color: #fff;
@ -41,11 +17,25 @@ body {
cursor: pointer; cursor: pointer;
font-size: xxx-large; font-size: xxx-large;
display: none; display: none;
z-index: 1000;
} }
#startButton.ready { #startButton.ready {
display: block; display: block;
background-color: red; 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 { #gameCanvas {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -2,5 +2,6 @@ import {Scene, WebXRDefaultExperience} from "@babylonjs/core";
export class DefaultScene { export class DefaultScene {
public static MainScene: Scene; public static MainScene: Scene;
public static DemoScene: Scene;
public static XR: WebXRDefaultExperience; public static XR: WebXRDefaultExperience;
} }

18
src/demo.ts Normal file
View File

@ -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);
}
}

8
src/level.ts Normal file
View File

@ -0,0 +1,8 @@
import {Observable} from "@babylonjs/core";
export default interface Level {
initialize(): void;
dispose(): void;
play(): void;
getReadyObservable(): Observable<Level>;
}

View File

@ -1,35 +1,59 @@
import {DefaultScene} from "./defaultScene"; import {DefaultScene} from "./defaultScene";
import { import {
AbstractMesh, AbstractMesh,
Color3, DistanceConstraint, InstancedMesh, Mesh, Color3, DistanceConstraint, Engine, InstancedMesh, Mesh,
MeshBuilder, MeshBuilder,
Observable, Observable,
ParticleHelper, ParticleHelper,
PhysicsAggregate, PhysicsAggregate,
PhysicsMotionType, PhysicsMotionType,
PhysicsShapeType, PhysicsShapeType, Sound,
StandardMaterial, TransformNode, StandardMaterial, TransformNode,
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import {Ship} from "./ship"; import {Ship} from "./ship";
import {ScoreEvent} from "./scoreEvent"; import {ScoreEvent} from "./scoreEvent";
import {RockFactory} from "./starfield"; import {RockFactory} from "./starfield";
import Level from "./level";
export class Level1 { export class Level1 implements Level {
private _ship: Ship; private _ship: Ship;
private _onReadyObservable: Observable<Level> = new Observable<Level>();
private _initialized: boolean = false; private _initialized: boolean = false;
private _startBase: AbstractMesh; private _startBase: AbstractMesh;
private _endBase: AbstractMesh; private _endBase: AbstractMesh;
public onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>(); public onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
constructor(ship: Ship) { constructor() {
this._ship = ship; 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.createStartBase();
this.createEndBase();
this.initialize();
}
getReadyObservable(): Observable<Level> {
return this._onReadyObservable;
} }
private scored: Set<string> = new Set<string>(); private scored: Set<string> = new Set<string>();
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() { public async initialize() {
if (this._initialized) { if (this._initialized) {
return; return;
@ -37,43 +61,18 @@ export class Level1 {
this._initialized = true; this._initialized = true;
ParticleHelper.BaseAssetsUrl = window.location.href; ParticleHelper.BaseAssetsUrl = window.location.href;
this._ship.position = new Vector3(0, 1, 0); this._ship.position = new Vector3(0, 1, 0);
await RockFactory.init(); await RockFactory.init();
const distance = Vector3.Distance(this._startBase.getAbsolutePosition(), this._endBase.getAbsolutePosition());
const baseTransform = new TransformNode("baseTransform", DefaultScene.MainScene); const baseTransform = new TransformNode("baseTransform", DefaultScene.MainScene);
baseTransform.position = this._endBase.getAbsolutePosition(); baseTransform.position = this._startBase.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();
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); const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
rock.physicsBody.addConstraint(this._endBase.physicsBody, constraint); //rock.physicsBody.addConstraint(this._endBase.physicsBody, constraint);
rock.physicsBody.applyImpulse(Vector3.Random(-1, 1).scale(1000), rock.getAbsolutePosition()); this._startBase.physicsBody.addConstraint(rock.physicsBody, constraint);
rock.physicsBody.setAngularVelocity(Vector3.Random(-.5, .5)); rock.physicsBody.applyForce(Vector3.Random(-1, 1).scale(50000000), rock.getAbsolutePosition());
/* const material = new StandardMaterial("material", DefaultScene.MainScene); //rock.physicsBody.setAngularVelocity(Vector3.Random(-.5, .5));
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);
*/
} }
} }
@ -97,7 +96,7 @@ export class Level1 {
height: 1, height: 1,
tessellation: 72 tessellation: 72
}, DefaultScene.MainScene); }, DefaultScene.MainScene);
mesh.position = new Vector3(0, 5, 200); mesh.position = new Vector3(0, 5, 500);
const material = new StandardMaterial("material", DefaultScene.MainScene); const material = new StandardMaterial("material", DefaultScene.MainScene);
material.diffuseColor = new Color3(0, 1, 0); material.diffuseColor = new Color3(0, 1, 0);
mesh.material = material; mesh.material = material;

View File

@ -6,21 +6,42 @@ import {DefaultScene} from "./defaultScene";
import {Ship} from "./ship"; import {Ship} from "./ship";
import {Level1} from "./level1"; import {Level1} from "./level1";
import {Scoreboard} from "./scoreboard"; import {Scoreboard} from "./scoreboard";
import Demo from "./demo";
import Level from "./level";
const webGpu = false; const webGpu = false;
const canvas = (document.querySelector('#gameCanvas') as HTMLCanvasElement); const canvas = (document.querySelector('#gameCanvas') as HTMLCanvasElement);
enum GameState {
export class Main { PLAY,
DEMO
constructor() {
this.initialize();
} }
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() { private async initialize() {
this._loadingDiv.innerText = "Initializing.";
await this.setupScene(); await this.setupScene();
const xr = await WebXRDefaultExperience.CreateAsync(DefaultScene.MainScene, { DefaultScene.XR = await WebXRDefaultExperience.CreateAsync(DefaultScene.MainScene, {
disablePointerSelection: true, disablePointerSelection: true,
disableTeleportation: true, disableTeleportation: true,
disableNearInteraction: true, disableNearInteraction: true,
@ -28,29 +49,17 @@ export class Main {
disableDefaultUI: true, disableDefaultUI: true,
}); });
const ship = new Ship(); this.setLoadingMessage("Get Ready!");
const scoreboard = new Scoreboard(); this.setLoadingMessage("Initializing Level...");
const level = new Level1(ship); this._currentLevel = new Level1();
this._currentLevel.getReadyObservable().add(() => {
});
const photoDome = new PhotoDome("testdome", '/8192.webp', {}, DefaultScene.MainScene); 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() { private async setupScene() {
let engine: WebGPUEngine | Engine = null; let engine: WebGPUEngine | Engine = null;
@ -64,30 +73,36 @@ export class Main {
window.onresize = () => { window.onresize = () => {
engine.resize(); engine.resize();
} }
DefaultScene.DemoScene = new Scene(engine);
DefaultScene.MainScene = new Scene(engine); DefaultScene.MainScene = new Scene(engine);
this.setLoadingMessage("Initializing Physics Engine..");
await this.setupPhysics(); await this.setupPhysics();
this.setupInspector(); this.setupInspector();
engine.runRenderLoop(() => { engine.runRenderLoop(() => {
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(); DefaultScene.MainScene.render();
} else {
DefaultScene.DemoScene.render();
}
}); });
} }
private async setupPhysics() { private async setupPhysics() {
const havok = await HavokPhysics(); const havok = await HavokPhysics();
const havokPlugin = new HavokPlugin(true, havok); const havokPlugin = new HavokPlugin(true, havok);
DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin); DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin);
DefaultScene.MainScene.collisionsEnabled = true; DefaultScene.MainScene.collisionsEnabled = true;
} }
private setupInspector() { private setupInspector() {
this.setLoadingMessage("Initializing Inspector...");
window.addEventListener("keydown", (ev) => { window.addEventListener("keydown", (ev) => {
if (ev.key == 'i') { if (ev.key == 'i') {
import ("@babylonjs/inspector").then((inspector) => { import ("@babylonjs/inspector").then((inspector) => {
@ -102,7 +117,7 @@ export class Main {
} }
const main = new Main(); const main = new Main();
const demo = new Demo(main);

View File

@ -22,7 +22,7 @@ import {
WebXRInputSource WebXRInputSource
} from "@babylonjs/core"; } from "@babylonjs/core";
import {DefaultScene} from "./defaultScene"; import {DefaultScene} from "./defaultScene";
import {Radar} from "./radar";
import {ShipEngine} from "./shipEngine"; import {ShipEngine} from "./shipEngine";
import {Level1} from "./level1"; import {Level1} from "./level1";
@ -71,6 +71,45 @@ export class Ship {
private _camera: FreeCamera; private _camera: FreeCamera;
constructor() { 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._ship = new TransformNode("ship", DefaultScene.MainScene);
this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene); this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene);
this._glowLayer.intensity = 1; this._glowLayer.intensity = 1;
@ -87,65 +126,12 @@ export class Ship {
{loop: false, autoplay: false, volume: .5}); {loop: false, autoplay: false, volume: .5});
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene); this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0); 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 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, mass: 100,
extents: new Vector3(4, 4, 7.4), extents: new Vector3(4, 4, 7.4),
center: new Vector3(0, 1, 1.8) center: new Vector3(0, 1, 1.8)
@ -155,8 +141,7 @@ export class Ship {
agg.body.setLinearDamping(.1); agg.body.setLinearDamping(.1);
agg.body.setAngularDamping(.2); agg.body.setAngularDamping(.2);
agg.body.setAngularVelocity(new Vector3(0, 0, 0)); agg.body.setAngularVelocity(new Vector3(0, 0, 0));
agg.body.setCollisionCallbackEnabled(true) agg.body.setCollisionCallbackEnabled(true);
DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
this.setupKeyboard(); this.setupKeyboard();
this.setupMouse(); this.setupMouse();
this._controllerObservable.add(this.controllerCallback); this._controllerObservable.add(this.controllerCallback);
@ -164,6 +149,13 @@ export class Ship {
this._rotationNode = new TransformNode("rotation", DefaultScene.MainScene); this._rotationNode = new TransformNode("rotation", DefaultScene.MainScene);
this._forwardNode.parent = this._ship; this._forwardNode.parent = this._ship;
this._rotationNode.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 sightPos = this._forwardNode.position.scale(30);
const sight = MeshBuilder.CreateSphere("sight", {diameter: 1}, DefaultScene.MainScene); const sight = MeshBuilder.CreateSphere("sight", {diameter: 1}, DefaultScene.MainScene);
sight.parent = this._ship sight.parent = this._ship
@ -171,30 +163,21 @@ export class Ship {
signtMaterial.emissiveColor = Color3.Yellow(); signtMaterial.emissiveColor = Color3.Yellow();
sight.material = signtMaterial; sight.material = signtMaterial;
sight.position = new Vector3(0, 2, 125); sight.position = new Vector3(0, 2, 125);
window.setInterval(() => { window.setInterval(() => {
this.applyForce(); this.applyForce();
}, 50); }, 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'); private async initialize() {
await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "cockpit3.glb", DefaultScene.MainScene);
await level.initialize(); const shipMesh = importMesh.meshes[0];
}); shipMesh.id = "shipMesh";
}, {loop: true, autoplay: true, volume: .2}); 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.play();
} }
this._thrust.setVolume(Math.abs(this._leftStickVector.y)); this._thrust.setVolume(Math.abs(this._leftStickVector.y));
this._forwardValue += this._leftStickVector.y * .4; this._forwardValue += this._leftStickVector.y * .8;
} else { } else {
if (this._thrust.isPlaying) { if (this._thrust.isPlaying) {
this._thrust.pause(); this._thrust.pause();