Initial Commit
This commit is contained in:
commit
a105e28333
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
/.env
|
||||||
25
index.html
Normal file
25
index.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta content="width=device-width, initial-scale=1, height=device-height" name="viewport">
|
||||||
|
<link href="/styles.css" rel="stylesheet">
|
||||||
|
<title>Game</title>
|
||||||
|
<script>
|
||||||
|
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||||
|
for (const registration of registrations) {
|
||||||
|
registration.unregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="gameCanvas"></canvas>
|
||||||
|
<div id="startGame">
|
||||||
|
<div id="loadingDiv">Loading...</div>
|
||||||
|
<button id="startButton">Start Game</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1227
package-lock.json
generated
Normal file
1227
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "space-game",
|
||||||
|
"private": false,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps",
|
||||||
|
"speech": "tsc && node ./dist/server/voices.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babylonjs/core": "7.13.1",
|
||||||
|
"@babylonjs/gui": "^7.13.1",
|
||||||
|
"@babylonjs/havok": "1.3.5",
|
||||||
|
"@babylonjs/inspector": "^7.13.1",
|
||||||
|
"@babylonjs/loaders": "^7.13.1",
|
||||||
|
"@babylonjs/materials": "^7.13.1",
|
||||||
|
"@babylonjs/serializers": "^7.13.1",
|
||||||
|
"@babylonjs/procedural-textures": "^7.13.1",
|
||||||
|
"openai": "4.52.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"vite": "^5.2.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/8192.webp
Normal file
BIN
public/8192.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
BIN
public/arrow.stl
Normal file
BIN
public/arrow.stl
Normal file
Binary file not shown.
BIN
public/asteroid.glb
Normal file
BIN
public/asteroid.glb
Normal file
Binary file not shown.
BIN
public/background.mp3
Normal file
BIN
public/background.mp3
Normal file
Binary file not shown.
BIN
public/cockpit.glb
Normal file
BIN
public/cockpit.glb
Normal file
Binary file not shown.
BIN
public/cockpit2.glb
Normal file
BIN
public/cockpit2.glb
Normal file
Binary file not shown.
BIN
public/cockpit3.glb
Normal file
BIN
public/cockpit3.glb
Normal file
Binary file not shown.
BIN
public/flare.png
Normal file
BIN
public/flare.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
public/ship1.stl
Normal file
BIN
public/ship1.stl
Normal file
Binary file not shown.
BIN
public/shot.mp3
Normal file
BIN
public/shot.mp3
Normal file
Binary file not shown.
55
public/styles.css
Normal file
55
public/styles.css
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
body {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #000;
|
||||||
|
aspect-ratio: auto;
|
||||||
|
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;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: xxx-large;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#startButton.ready {
|
||||||
|
display: block;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
#gameCanvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
1357
public/systems/explosion.json
Normal file
1357
public/systems/explosion.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/thrust.mp3
Normal file
BIN
public/thrust.mp3
Normal file
Binary file not shown.
BIN
public/thrust2.mp3
Normal file
BIN
public/thrust2.mp3
Normal file
Binary file not shown.
BIN
public/thust2.mp3
Normal file
BIN
public/thust2.mp3
Normal file
Binary file not shown.
BIN
public/yehrat.mp3
Normal file
BIN
public/yehrat.mp3
Normal file
Binary file not shown.
14
server/voices.ts
Normal file
14
server/voices.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import OpenAI from "openai";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
|
||||||
|
async function build() {
|
||||||
|
const client = new OpenAI({ apiKey: ""})
|
||||||
|
const mp3 = await client.audio.speech.create({
|
||||||
|
model: 'tts-1-hd',
|
||||||
|
voice: 'alloy',
|
||||||
|
input: 'test 1 2 3'
|
||||||
|
});
|
||||||
|
const buffer = Buffer.from(await mp3.arrayBuffer());
|
||||||
|
await fs.promises.writeFile('./output.mp3', buffer);
|
||||||
|
}
|
||||||
33
src/createSun.ts
Normal file
33
src/createSun.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
AbstractMesh,
|
||||||
|
Color3, GlowLayer,
|
||||||
|
MeshBuilder,
|
||||||
|
PhysicsAggregate,
|
||||||
|
PhysicsMotionType,
|
||||||
|
PhysicsShapeType,
|
||||||
|
PointLight,
|
||||||
|
StandardMaterial,
|
||||||
|
Vector3
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {FireProceduralTexture} from "@babylonjs/procedural-textures";
|
||||||
|
|
||||||
|
export function createSun() : AbstractMesh {
|
||||||
|
const light = new PointLight("light", new Vector3(0, 0, 0), DefaultScene.MainScene);
|
||||||
|
const sun = MeshBuilder.CreateSphere("sun", {diameter: 50, segments: 32}, DefaultScene.MainScene);
|
||||||
|
|
||||||
|
const sunAggregate = new PhysicsAggregate(sun, PhysicsShapeType.SPHERE, {mass: 0}, DefaultScene.MainScene);
|
||||||
|
sunAggregate.body.setMotionType(PhysicsMotionType.STATIC);
|
||||||
|
const material = new StandardMaterial("material", DefaultScene.MainScene);
|
||||||
|
material.emissiveTexture =new FireProceduralTexture("fire", 256, DefaultScene.MainScene);
|
||||||
|
material.emissiveColor = new Color3(.5, .5, .1);
|
||||||
|
material.disableLighting = true;
|
||||||
|
sun.material = material;
|
||||||
|
const gl = new GlowLayer("glow", DefaultScene.MainScene);
|
||||||
|
//gl.addIncludedOnlyMesh(sun);
|
||||||
|
gl.intensity = 5;
|
||||||
|
|
||||||
|
|
||||||
|
sun.position = new Vector3(0, 0, 0);
|
||||||
|
return sun;
|
||||||
|
}
|
||||||
6
src/defaultScene.ts
Normal file
6
src/defaultScene.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import {Scene, WebXRDefaultExperience} from "@babylonjs/core";
|
||||||
|
|
||||||
|
export class DefaultScene {
|
||||||
|
public static MainScene: Scene;
|
||||||
|
public static XR: WebXRDefaultExperience;
|
||||||
|
}
|
||||||
172
src/level1.ts
Normal file
172
src/level1.ts
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {
|
||||||
|
Color3,
|
||||||
|
HavokPlugin,
|
||||||
|
MeshBuilder, Observable, ParticleHelper, ParticleSystem, ParticleSystemSet,
|
||||||
|
PhysicsAggregate,
|
||||||
|
PhysicsMotionType,
|
||||||
|
PhysicsShapeType,
|
||||||
|
StandardMaterial, Texture,
|
||||||
|
Vector3
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
import {Ship} from "./ship";
|
||||||
|
import {ScoreEvent} from "./scoreEvent";
|
||||||
|
import {createRock} from "./starfield";
|
||||||
|
|
||||||
|
export class Level1 {
|
||||||
|
private _ship: Ship;
|
||||||
|
private _explosion: ParticleSystemSet
|
||||||
|
|
||||||
|
public onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
||||||
|
constructor(ship: Ship) {
|
||||||
|
this._ship = ship;
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
private scored: Set<string> = new Set<string>();
|
||||||
|
private async initialize() {
|
||||||
|
const phys = DefaultScene.MainScene.getPhysicsEngine().getPhysicsPlugin() as HavokPlugin;
|
||||||
|
ParticleHelper.BaseAssetsUrl = window.location.href;
|
||||||
|
|
||||||
|
//console.log(window.location.href);
|
||||||
|
DefaultScene.MainScene.onReadyObservable.addOnce(async () => {
|
||||||
|
this._explosion = await ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene);
|
||||||
|
});
|
||||||
|
//
|
||||||
|
/*phys.onTriggerCollisionObservable.add((eventData) => {
|
||||||
|
if (eventData.collider.transformNode.id.indexOf('star') > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.scored.has(eventData.collidedAgainst.transformNode.id)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.scored.add(eventData.collidedAgainst.transformNode.id);
|
||||||
|
//this.onScoreObservable.notifyObservers(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
phys.onCollisionObservable.add( (eventData) => {
|
||||||
|
this.onScoreObservable.notifyObservers({score: 0, message: eventData?.impulse?.toFixed(2)});
|
||||||
|
if ((eventData.collidedAgainst.transformNode.id == 'bullet' ||
|
||||||
|
eventData.collider.transformNode.id == 'bullet') &&
|
||||||
|
(eventData.collidedAgainst.transformNode.id.indexOf('asteroid') > -1 ||
|
||||||
|
eventData.collider.transformNode.id.indexOf('asteroid') > -1)
|
||||||
|
){
|
||||||
|
const point = eventData.point.clone();
|
||||||
|
if (this._explosion) {
|
||||||
|
/*this._explosion.systems.forEach((system) => {
|
||||||
|
system.stop();
|
||||||
|
});*/
|
||||||
|
this._explosion.emitterNode = point;
|
||||||
|
this._explosion.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventData.collider.transformNode.dispose();
|
||||||
|
eventData.collidedAgainst.transformNode.dispose()
|
||||||
|
|
||||||
|
eventData.collider.dispose();
|
||||||
|
eventData.collidedAgainst.dispose();
|
||||||
|
|
||||||
|
|
||||||
|
/*const myParticleSystem = new ParticleSystem("particles", 1000, DefaultScene.MainScene);
|
||||||
|
myParticleSystem.emitter = point;
|
||||||
|
myParticleSystem.emitRate = 100;
|
||||||
|
myParticleSystem.minEmitPower = 2;
|
||||||
|
myParticleSystem.maxEmitPower = 200;
|
||||||
|
const sphereEmitter = myParticleSystem.createSphereEmitter(10);
|
||||||
|
|
||||||
|
myParticleSystem.particleTexture = new Texture("./flare.png");
|
||||||
|
myParticleSystem.maxLifeTime = 10000;
|
||||||
|
|
||||||
|
//const coneEmitter = myParticleSystem.createConeEmitter(0.1, Math.PI / 9);
|
||||||
|
myParticleSystem.addSizeGradient(0, 2);
|
||||||
|
myParticleSystem.addSizeGradient(1, 4);
|
||||||
|
//myParticleSystem.isLocal = true;
|
||||||
|
|
||||||
|
myParticleSystem.start(); //S
|
||||||
|
console.log(eventData);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
this._ship.onReadyObservable.add(() => {
|
||||||
|
this._ship.position = new Vector3(0, 1, 0);
|
||||||
|
this.createStartBase();
|
||||||
|
this.createEndBase();
|
||||||
|
|
||||||
|
createRock(1, new Vector3(0,0, 70), new Vector3(10,10,10));
|
||||||
|
createRock(1, new Vector3(0,0, 100), new Vector3(10,10,10));
|
||||||
|
createRock(1, new Vector3(0,0, 130), new Vector3(10,10,10));
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
createRock(i , Vector3.Random(-200, 200), Vector3.Random(5,20))
|
||||||
|
.then((rock) => {
|
||||||
|
rock.physicsBody.setAngularVelocity(Vector3.Random(-1, 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private createStartBase() {
|
||||||
|
const mesh = MeshBuilder.CreateCylinder("startBase", {diameter: 10, height: 1, tessellation: 72}, DefaultScene.MainScene);
|
||||||
|
const material = new StandardMaterial("material", DefaultScene.MainScene);
|
||||||
|
material.diffuseColor = new Color3(1, 1, 0);
|
||||||
|
mesh.material = material;
|
||||||
|
const agg = new PhysicsAggregate(mesh, PhysicsShapeType.CONVEX_HULL, {mass: 0}, DefaultScene.MainScene);
|
||||||
|
agg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||||
|
|
||||||
|
}
|
||||||
|
private createEndBase() {
|
||||||
|
const mesh = MeshBuilder.CreateCylinder("endBase", {diameter: 10, height: 1, tessellation: 72}, DefaultScene.MainScene);
|
||||||
|
mesh.position = new Vector3(0, 5, 200);
|
||||||
|
const material = new StandardMaterial("material", DefaultScene.MainScene);
|
||||||
|
material.diffuseColor = new Color3(0, 1, 0);
|
||||||
|
mesh.material = material;
|
||||||
|
const agg = new PhysicsAggregate(mesh, PhysicsShapeType.CONVEX_HULL, {mass: 0}, DefaultScene.MainScene);
|
||||||
|
agg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||||
|
/*agg.body.setCollisionCallbackEnabled(true);
|
||||||
|
const collider = agg.body.getCollisionObservable().add((eventData) => {
|
||||||
|
if (eventData.collidedAgainst.transformNode.id == 'ship') {
|
||||||
|
console.log(eventData);
|
||||||
|
this.onScoreObservable.notifyObservers({score: 0,
|
||||||
|
message: eventData?.impulse?.toFixed(2)})
|
||||||
|
|
||||||
|
}
|
||||||
|
}); */
|
||||||
|
|
||||||
|
}
|
||||||
|
private createTarget(i: number) {
|
||||||
|
const target = MeshBuilder.CreateTorus("target" + i, {diameter: 10, tessellation: 72}, DefaultScene.MainScene);
|
||||||
|
|
||||||
|
const targetLOD = MeshBuilder.CreateTorus("target" + i, {diameter: 50, tessellation: 10}, DefaultScene.MainScene);
|
||||||
|
targetLOD.parent = target;
|
||||||
|
target.addLODLevel(300, targetLOD);
|
||||||
|
|
||||||
|
const material = new StandardMaterial("material", DefaultScene.MainScene);
|
||||||
|
material.diffuseColor = new Color3(1, 0, 0);
|
||||||
|
material.alpha = .9;
|
||||||
|
target.material = material;
|
||||||
|
target.position = Vector3.Random(-1000, 1000);
|
||||||
|
target.rotation = Vector3.Random(0, Math.PI*2);
|
||||||
|
const disc = MeshBuilder.CreateDisc("disc-"+i, {radius: 2, tessellation: 72}, DefaultScene.MainScene);
|
||||||
|
const discMaterial = new StandardMaterial("material", DefaultScene.MainScene);
|
||||||
|
discMaterial.ambientColor = new Color3(.1, 1, .1);
|
||||||
|
discMaterial.alpha = .2;
|
||||||
|
target.addLODLevel(200, null);
|
||||||
|
disc.material = discMaterial;
|
||||||
|
disc.parent = target;
|
||||||
|
disc.rotation.x = -Math.PI/2;
|
||||||
|
const agg = new PhysicsAggregate(disc, PhysicsShapeType.MESH, {mass: 0}, DefaultScene.MainScene);
|
||||||
|
agg.body.setMotionType(PhysicsMotionType.STATIC);
|
||||||
|
agg.shape.isTrigger = true;
|
||||||
|
//agg.shape.filterCollideMask = 2;
|
||||||
|
//agg.body.dispose();
|
||||||
|
|
||||||
|
//const body = new PhysicsShapeMesh(disc, DefaultScene.MainScene);
|
||||||
|
|
||||||
|
//agg.body.setCollisionCallbackEnabled(true);
|
||||||
|
/*agg.body.getCollisionObservable().add((eventData) => {
|
||||||
|
target.dispose(false, false);
|
||||||
|
agg.dispose();
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/main.ts
Normal file
111
src/main.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import {Engine, HavokPlugin, PhotoDome, Scene, Vector3, WebGPUEngine, WebXRDefaultExperience} from "@babylonjs/core";
|
||||||
|
import '@babylonjs/loaders';
|
||||||
|
import HavokPhysics from "@babylonjs/havok";
|
||||||
|
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {Ship} from "./ship";
|
||||||
|
import {Level1} from "./level1";
|
||||||
|
import {Scoreboard} from "./scoreboard";
|
||||||
|
|
||||||
|
const webGpu = false;
|
||||||
|
const canvas = (document.querySelector('#gameCanvas') as HTMLCanvasElement);
|
||||||
|
|
||||||
|
export class Main {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initialize() {
|
||||||
|
await this.setupScene();
|
||||||
|
|
||||||
|
const xr = await WebXRDefaultExperience.CreateAsync(DefaultScene.MainScene, {
|
||||||
|
disablePointerSelection: true,
|
||||||
|
disableTeleportation: true,
|
||||||
|
disableNearInteraction: true,
|
||||||
|
disableHandTracking: true,
|
||||||
|
disableDefaultUI: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
//const sun = createSun();
|
||||||
|
const ship = new Ship();
|
||||||
|
const scoreboard = new Scoreboard();
|
||||||
|
const level = new Level1(ship);
|
||||||
|
const photoDome = new PhotoDome("testdome", '/8192.webp', {}, DefaultScene.MainScene);
|
||||||
|
//starfield(sun);
|
||||||
|
//starfield(sun);
|
||||||
|
|
||||||
|
|
||||||
|
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 async setupScene() {
|
||||||
|
|
||||||
|
let engine: WebGPUEngine | Engine = null;
|
||||||
|
if (webGpu) {
|
||||||
|
engine = new WebGPUEngine(canvas);
|
||||||
|
await (engine as WebGPUEngine).initAsync();
|
||||||
|
} else {
|
||||||
|
engine = new Engine(canvas, true);
|
||||||
|
}
|
||||||
|
engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
|
||||||
|
//Engine.audioEngine.useCustomUnlockedButton = true;
|
||||||
|
window.onresize = () => {
|
||||||
|
engine.resize();
|
||||||
|
}
|
||||||
|
DefaultScene.MainScene = new Scene(engine);
|
||||||
|
|
||||||
|
|
||||||
|
await this.setupPhysics();
|
||||||
|
|
||||||
|
this.setupInspector();
|
||||||
|
|
||||||
|
engine.runRenderLoop(() => {
|
||||||
|
DefaultScene.MainScene.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() {
|
||||||
|
window.addEventListener("keydown", (ev) => {
|
||||||
|
if (ev.key == 'i') {
|
||||||
|
import ("@babylonjs/inspector").then((inspector) => {
|
||||||
|
inspector.Inspector.Show(DefaultScene.MainScene, {
|
||||||
|
overlay: true,
|
||||||
|
showExplorer: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const main = new Main();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
22
src/mirror.ts
Normal file
22
src/mirror.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {FreeCamera, MeshBuilder, RenderTargetTexture, StandardMaterial, TransformNode, Vector3} from "@babylonjs/core";
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
|
||||||
|
export class Mirror {
|
||||||
|
constructor(ship: TransformNode) {
|
||||||
|
const renderTargetTexture = new RenderTargetTexture('mirror', 512, DefaultScene.MainScene);
|
||||||
|
const camera = new FreeCamera("mirrorCamera", new Vector3(0, 0, -5), DefaultScene.MainScene);
|
||||||
|
camera.parent = ship;
|
||||||
|
//camera.rotation.y = Math.PI;
|
||||||
|
renderTargetTexture.activeCamera = camera;
|
||||||
|
renderTargetTexture.renderList.push(DefaultScene.MainScene.getMeshByName("shipMesh"));
|
||||||
|
const mirror = MeshBuilder.CreatePlane("mirrorMesh" , {width: 1, height: 1}, DefaultScene.MainScene);
|
||||||
|
mirror.parent = ship;
|
||||||
|
const mirrorMaterial = new StandardMaterial("mirrorMaterial", DefaultScene.MainScene);
|
||||||
|
|
||||||
|
mirrorMaterial.backFaceCulling = false;
|
||||||
|
mirrorMaterial.diffuseTexture = renderTargetTexture;
|
||||||
|
mirror.material = mirrorMaterial;
|
||||||
|
mirror.position = new Vector3(0, 1, 5);
|
||||||
|
mirror.rotation.y = Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/radar.ts
Normal file
81
src/radar.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {
|
||||||
|
AbstractMesh,
|
||||||
|
Color3,
|
||||||
|
HavokPlugin, InstancedMesh, Mesh,
|
||||||
|
MeshBuilder, Ray, SceneLoader,
|
||||||
|
StandardMaterial,
|
||||||
|
TransformNode,
|
||||||
|
Vector3
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
|
||||||
|
const DETECTED: Color3 = Color3.Blue();
|
||||||
|
const WARN: Color3 = Color3.Yellow();
|
||||||
|
const DANGER: Color3 = Color3.Red();
|
||||||
|
const DETECTED_DISTANCE = 100;
|
||||||
|
const WARN_DISTANCE = 50;
|
||||||
|
const DANGER_DISTANCE = 30;
|
||||||
|
export class Radar {
|
||||||
|
private _shipTransform: TransformNode;
|
||||||
|
private _radarTransform: TransformNode;
|
||||||
|
private _arrowMesh: AbstractMesh;
|
||||||
|
constructor(ship: TransformNode) {
|
||||||
|
this._shipTransform = ship;
|
||||||
|
this._radarTransform = new TransformNode('radar', DefaultScene.MainScene);
|
||||||
|
this._radarTransform.parent = ship;
|
||||||
|
const sphere = MeshBuilder.CreateSphere('radarSphere', {diameter: 1}, DefaultScene.MainScene);
|
||||||
|
sphere.parent = this._radarTransform;
|
||||||
|
const material = new StandardMaterial('radarMaterial', DefaultScene.MainScene);
|
||||||
|
material.diffuseColor = Color3.Yellow();
|
||||||
|
material.alpha = .5;
|
||||||
|
sphere.material = material;
|
||||||
|
// dmaterial.alpha = .1;
|
||||||
|
this._radarTransform.position.z = 4;
|
||||||
|
//this._radarTransform.scaling = new Vector3(.01, .01 ,.01);
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initialize() {
|
||||||
|
const scene = DefaultScene.MainScene;
|
||||||
|
const arrow = await SceneLoader.ImportMeshAsync(null, './', 'arrow.stl', scene);
|
||||||
|
//arrow.meshes[0].parent = this._radarTransform;
|
||||||
|
arrow.meshes[0].scaling = new Vector3(.05,.05,.05);
|
||||||
|
this._arrowMesh = arrow.meshes[0];
|
||||||
|
const material = new StandardMaterial('arrowMaterial', scene);
|
||||||
|
material.emissiveColor = Color3.White();
|
||||||
|
this._arrowMesh.material = material;
|
||||||
|
window.setInterval(() => {
|
||||||
|
const point = scene.getMeshById('endBase');
|
||||||
|
point.computeWorldMatrix(true)
|
||||||
|
this._arrowMesh.position = this._radarTransform.absolutePosition;
|
||||||
|
this._arrowMesh.lookAt(point.absolutePosition);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// arrow[0].parent = this._radarTransform;
|
||||||
|
/*window.setInterval(() => {
|
||||||
|
scene.meshes.forEach((mesh) => {
|
||||||
|
if (mesh.physicsBody) {
|
||||||
|
if (!this._radarMeshes.has(mesh.id)) {
|
||||||
|
const radarmesh = new InstancedMesh('radar-' + mesh.id, mesh as Mesh);
|
||||||
|
radarmesh.metadata = {source: mesh};
|
||||||
|
radarmesh.parent = this._radarTransform;
|
||||||
|
this._radarMeshes.set(mesh.id, radarmesh);
|
||||||
|
}
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private async update() {
|
||||||
|
/*this._radarMeshes.forEach((radarMesh, id) => {
|
||||||
|
const mesh = radarMesh.metadata.source as AbstractMesh;
|
||||||
|
radarMesh.position = mesh.absolutePosition.subtract(this._shipTransform.absolutePosition).scaleInPlace(1.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/scoreEvent.ts
Normal file
4
src/scoreEvent.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type ScoreEvent = {
|
||||||
|
score: number,
|
||||||
|
message: string
|
||||||
|
}
|
||||||
88
src/scoreboard.ts
Normal file
88
src/scoreboard.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui";
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {
|
||||||
|
AbstractMesh,
|
||||||
|
ActionManager,
|
||||||
|
Angle,
|
||||||
|
ExecuteCodeAction,
|
||||||
|
MeshBuilder,
|
||||||
|
Observable,
|
||||||
|
TransformNode,
|
||||||
|
Vector3,
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
import {ScoreEvent} from "./scoreEvent";
|
||||||
|
|
||||||
|
export class Scoreboard {
|
||||||
|
private _score: number = 0;
|
||||||
|
private _lastMessage: string = null;
|
||||||
|
public onscoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
||||||
|
constructor() {
|
||||||
|
DefaultScene.MainScene.onNewMeshAddedObservable.add((mesh) => {
|
||||||
|
if (mesh.id == 'RightUpperDisplay') {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
//mesh.material = null;
|
||||||
|
this.initialize();
|
||||||
|
},1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//this.initialize(camera);
|
||||||
|
}
|
||||||
|
private initialize() {
|
||||||
|
const scene = DefaultScene.MainScene;
|
||||||
|
|
||||||
|
const parent = scene.getMeshById('RightUpperDisplay');
|
||||||
|
const scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
|
||||||
|
scoreboard.parent =parent;
|
||||||
|
//DefaultScene.MainScene.onBeforeDrawPhaseObservable.add(() => {
|
||||||
|
|
||||||
|
//});
|
||||||
|
//scoreboard.parent = camera;
|
||||||
|
scoreboard.position.x = -.76;
|
||||||
|
scoreboard.position.y = 4.19;
|
||||||
|
scoreboard.position.z = .53;
|
||||||
|
scoreboard.rotation.x = Angle.FromDegrees(108).radians();
|
||||||
|
scoreboard.rotation.z = Math.PI;
|
||||||
|
scoreboard.scaling = new Vector3(.5, .5, .5);
|
||||||
|
//scoreboard.position = camera.getFrontPosition(1);
|
||||||
|
|
||||||
|
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard);
|
||||||
|
advancedTexture.background = "black";
|
||||||
|
const scoreText = this.createText();
|
||||||
|
advancedTexture.addControl(scoreText);
|
||||||
|
const fpsText = this.createText();
|
||||||
|
fpsText.top = '120px';
|
||||||
|
const panel = new StackPanel();
|
||||||
|
panel.isVertical = true;
|
||||||
|
advancedTexture.addControl(fpsText);
|
||||||
|
advancedTexture.addControl(scoreText);
|
||||||
|
advancedTexture.addControl(panel);
|
||||||
|
|
||||||
|
scene.onAfterRenderObservable.add(() => {
|
||||||
|
scoreText.text = `Score: ${this.calculateScore()}`;
|
||||||
|
if (this._lastMessage != null) {
|
||||||
|
fpsText.text = this._lastMessage;
|
||||||
|
} else {
|
||||||
|
fpsText.text = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onscoreObservable.add((score) => {
|
||||||
|
this._score += score.score;
|
||||||
|
this._lastMessage = score.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private createText(): TextBlock {
|
||||||
|
const text1 = new TextBlock();
|
||||||
|
|
||||||
|
text1.color = "white";
|
||||||
|
text1.fontSize = 90;
|
||||||
|
text1.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
|
||||||
|
text1.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
||||||
|
return text1;
|
||||||
|
}
|
||||||
|
private calculateScore() {
|
||||||
|
return Math.floor(this._score);
|
||||||
|
}
|
||||||
|
}
|
||||||
438
src/ship.ts
Normal file
438
src/ship.ts
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
import {
|
||||||
|
AbstractMesh, Color3,
|
||||||
|
DirectionalLight, Engine,
|
||||||
|
|
||||||
|
FreeCamera, GlowLayer,
|
||||||
|
Matrix, MeshBuilder,
|
||||||
|
Observable,
|
||||||
|
PhysicsAggregate,
|
||||||
|
PhysicsMotionType,
|
||||||
|
PhysicsShapeType,
|
||||||
|
SceneLoader, Sound,
|
||||||
|
SpotLight, StandardMaterial,
|
||||||
|
TransformNode,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
WebXRAbstractMotionController,
|
||||||
|
WebXRControllerComponent,
|
||||||
|
WebXRInputSource
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {Radar} from "./radar";
|
||||||
|
import {ShipEngine} from "./shipEngine";
|
||||||
|
import {Mirror} from "./mirror";
|
||||||
|
|
||||||
|
const controllerComponents = [
|
||||||
|
'a-button',
|
||||||
|
'b-button',
|
||||||
|
'x-button',
|
||||||
|
'y-button',
|
||||||
|
'thumbrest',
|
||||||
|
'xr-standard-squeeze',
|
||||||
|
'xr-standard-thumbstick',
|
||||||
|
'xr-standard-trigger',
|
||||||
|
]
|
||||||
|
type ControllerEvent = {
|
||||||
|
hand: 'right' | 'left' | 'none',
|
||||||
|
type: 'thumbstick' | 'button',
|
||||||
|
controller: WebXRAbstractMotionController,
|
||||||
|
component: WebXRControllerComponent,
|
||||||
|
value: number,
|
||||||
|
axisData: { x: number, y: number },
|
||||||
|
pressed: boolean,
|
||||||
|
touched: boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ControllerStickMode {
|
||||||
|
ARCADE,
|
||||||
|
REALISTIC
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Ship {
|
||||||
|
private _ship: TransformNode;
|
||||||
|
private _controllerObservable: Observable<ControllerEvent> = new Observable<ControllerEvent>();
|
||||||
|
public onReadyObservable: Observable<unknown> = new Observable<unknown>();
|
||||||
|
private _engine: ShipEngine;
|
||||||
|
|
||||||
|
private _forwardNode: TransformNode;
|
||||||
|
private _rotationNode: TransformNode;
|
||||||
|
private _onscore: Observable<number>;
|
||||||
|
private _ammo: Array<AbstractMesh> = [];
|
||||||
|
private _glowLayer: GlowLayer;
|
||||||
|
private _thrust: Sound;
|
||||||
|
private _thrust2: Sound;
|
||||||
|
private _shot: Sound;
|
||||||
|
private _shooting: boolean = false;
|
||||||
|
constructor() {
|
||||||
|
this._ship = new TransformNode("ship", DefaultScene.MainScene);
|
||||||
|
this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene);
|
||||||
|
this._glowLayer.intensity = 1;
|
||||||
|
this._thrust = new Sound("thrust", "/thrust.mp3", DefaultScene.MainScene, null, {loop: true, autoplay: false});
|
||||||
|
this._thrust2 = new Sound("thrust2", "/thrust2.mp3", DefaultScene.MainScene, null, {loop: true, autoplay: false});
|
||||||
|
this._shot = new Sound("shot", "/shot.mp3", DefaultScene.MainScene, null,
|
||||||
|
{loop: false, autoplay: false});
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
|
}
|
||||||
|
private shoot() {
|
||||||
|
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: 1}, DefaultScene.MainScene);
|
||||||
|
const material = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
|
||||||
|
material.emissiveColor = new Color3(1,1,0);
|
||||||
|
ammo.material = material;
|
||||||
|
ammoAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
|
ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(100).add(this._ship.physicsBody.getLinearVelocity()));
|
||||||
|
this._shot.play();
|
||||||
|
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 lightCode = MeshBuilder.CreateCylinder("lightCode", {diameterBottom: 0, diameterTop: .5, height: 1}, DefaultScene.MainScene);
|
||||||
|
//lightCode.parent = ship;
|
||||||
|
//lightCode.position.z = 5;
|
||||||
|
//this._engine = new ShipEngine(ship);
|
||||||
|
//ship.position = new Vector3(0, 0, -1000);
|
||||||
|
//const landingLight = new DirectionalLight("landingLight", new Vector3(0, -1, 0), DefaultScene.MainScene);
|
||||||
|
//landingLight.parent = ship;
|
||||||
|
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;
|
||||||
|
|
||||||
|
const camera = new FreeCamera("Flat Camera",
|
||||||
|
new Vector3(0, 0, -10),
|
||||||
|
DefaultScene.MainScene);
|
||||||
|
camera.parent = ship;
|
||||||
|
camera.position.y = .5;
|
||||||
|
camera.position.z = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//right.attachToMesh(rightTransform);
|
||||||
|
|
||||||
|
|
||||||
|
//camera.rotation.x = -Math.PI / 2;
|
||||||
|
const body = new PhysicsAggregate(ship, PhysicsShapeType.BOX, {
|
||||||
|
mass: 100,
|
||||||
|
extents: new Vector3(5, 2, 5)
|
||||||
|
}, DefaultScene.MainScene);
|
||||||
|
|
||||||
|
body.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
|
body.body.setLinearDamping(.1);
|
||||||
|
body.body.setAngularDamping(.2);
|
||||||
|
body.body.setAngularVelocity(new Vector3(0, 0, 0));
|
||||||
|
body.body.setCollisionCallbackEnabled(true)
|
||||||
|
//body.shape.filterCollideMask = 1;
|
||||||
|
/*const sight = MeshBuilder.CreateDisc("sight", {
|
||||||
|
radius: .1,
|
||||||
|
sideOrientation: Mesh.DOUBLESIDE
|
||||||
|
}, DefaultScene.MainScene);
|
||||||
|
const sightMaterial = new StandardMaterial("sightMaterial", DefaultScene.MainScene);
|
||||||
|
sightMaterial.ambientColor = new Color3(1, 1, 1);
|
||||||
|
sightMaterial.alpha = .5;
|
||||||
|
sight.material = sightMaterial;
|
||||||
|
sight.parent = ship;
|
||||||
|
sight.rotation.x = Math.PI;
|
||||||
|
sight.position.z = 10;
|
||||||
|
const line = MeshBuilder.CreateLines("line", {points: [Vector3.Zero(), new Vector3(0, 0, 10)]}, DefaultScene.MainScene);
|
||||||
|
line.parent = ship;
|
||||||
|
line.material = sightMaterial; */
|
||||||
|
DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
|
||||||
|
|
||||||
|
|
||||||
|
this.setupKeyboard();
|
||||||
|
this.setupMouse();
|
||||||
|
this._controllerObservable.add(this.controllerCallback);
|
||||||
|
this._forwardNode = new TransformNode("forward", DefaultScene.MainScene);
|
||||||
|
this._rotationNode = new TransformNode("rotation", DefaultScene.MainScene);
|
||||||
|
this._forwardNode.parent = this._ship;
|
||||||
|
this._rotationNode.parent = this._ship;
|
||||||
|
|
||||||
|
//const radar = new Radar(this._ship);
|
||||||
|
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 background = new Sound("background", "/background.mp3", DefaultScene.MainScene, () => {
|
||||||
|
const startButton = document.querySelector('#startButton');
|
||||||
|
startButton.classList.add('ready');
|
||||||
|
startButton.addEventListener('click', () => {
|
||||||
|
Engine.audioEngine.unlock();
|
||||||
|
background.play();
|
||||||
|
DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor');
|
||||||
|
});
|
||||||
|
}, {loop: true, autoplay: false, volume: .1});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _leftStickVector = Vector2.Zero().clone();
|
||||||
|
private _rightStickVector = Vector2.Zero().clone();
|
||||||
|
private _forwardValue = 0;
|
||||||
|
private _yawValue = 0;
|
||||||
|
private _rollValue = 0;
|
||||||
|
private _pitchValue = 0;
|
||||||
|
private _mouseDown = false;
|
||||||
|
private _mousePos = new Vector2(0, 0);
|
||||||
|
|
||||||
|
private scale(value: number) {
|
||||||
|
return value * .8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public get transformNode() {
|
||||||
|
return this._ship;
|
||||||
|
}
|
||||||
|
|
||||||
|
private adjust(value: number, increment: number = .8): number {
|
||||||
|
if (Math.abs(value) < .001) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return value * increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyForce() {
|
||||||
|
if (!this?._ship?.physicsBody) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const body = this._ship.physicsBody;
|
||||||
|
if (Math.abs(this._forwardValue) > 40) {
|
||||||
|
this._forwardValue = Math.sign(this._forwardValue) * 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(this._forwardValue) <= 40) {
|
||||||
|
if (Math.abs(this._leftStickVector.y) > .1) {
|
||||||
|
if (!this._thrust.isPlaying) {
|
||||||
|
this._thrust.play();
|
||||||
|
}
|
||||||
|
this._thrust.setVolume(Math.abs(this._leftStickVector.y));
|
||||||
|
this._forwardValue += this._leftStickVector.y * .4;
|
||||||
|
} else {
|
||||||
|
if (this._thrust.isPlaying) {
|
||||||
|
this._thrust.pause();
|
||||||
|
}
|
||||||
|
this._forwardValue = this.adjust(this._forwardValue, .98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(this._leftStickVector.x) > .1) {
|
||||||
|
this._yawValue += this._leftStickVector.x * .03;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this._yawValue = this.adjust(this._yawValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(this._rightStickVector.x) > .1) {
|
||||||
|
this._rollValue += this._rightStickVector.x * .03;
|
||||||
|
} else {
|
||||||
|
this._rollValue = this.adjust(this._rollValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(this._rightStickVector.y) > .1) {
|
||||||
|
this._pitchValue += this._rightStickVector.y * .03;
|
||||||
|
} else {
|
||||||
|
this._pitchValue = this.adjust(this._pitchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._forwardNode.position.z = this._forwardValue;
|
||||||
|
this._rotationNode.position.y = this._yawValue;
|
||||||
|
this._rotationNode.position.z = -this._rollValue;
|
||||||
|
this._rotationNode.position.x = -this._pitchValue;
|
||||||
|
|
||||||
|
const thrust2 = Math.abs(this._rightStickVector.y) +
|
||||||
|
Math.abs(this._rightStickVector.x) +
|
||||||
|
Math.abs(this._leftStickVector.x);
|
||||||
|
|
||||||
|
if (thrust2 > .01) {
|
||||||
|
if (!this._thrust2.isPlaying) {
|
||||||
|
this._thrust2.play();
|
||||||
|
}
|
||||||
|
this._thrust2.setVolume(thrust2 * .4);
|
||||||
|
} else {
|
||||||
|
if (this._thrust2.isPlaying) {
|
||||||
|
this._thrust2.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
body.setAngularVelocity(this._rotationNode.absolutePosition.subtract(this._ship.absolutePosition));
|
||||||
|
body.setLinearVelocity(this._forwardNode.absolutePosition.subtract(this._ship.absolutePosition).scale(-1));
|
||||||
|
//this._engine.forwardback(this._forwardValue);
|
||||||
|
}
|
||||||
|
private controllerCallback = (controllerEvent: ControllerEvent) => {
|
||||||
|
if (controllerEvent.type == 'thumbstick') {
|
||||||
|
if (controllerEvent.hand == 'left') {
|
||||||
|
this._leftStickVector.x = controllerEvent.axisData.x;
|
||||||
|
this._leftStickVector.y = controllerEvent.axisData.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controllerEvent.hand == 'right') {
|
||||||
|
this._rightStickVector.x = controllerEvent.axisData.x;
|
||||||
|
this._rightStickVector.y = controllerEvent.axisData.y;
|
||||||
|
|
||||||
|
}
|
||||||
|
this.applyForce();
|
||||||
|
}
|
||||||
|
if (controllerEvent.type == 'button') {
|
||||||
|
if (controllerEvent.component.type == 'trigger') {
|
||||||
|
if (controllerEvent.value > .9 && !this._shooting) {
|
||||||
|
this._shooting = true;
|
||||||
|
this.shoot();
|
||||||
|
}
|
||||||
|
if (controllerEvent.value < .1) {
|
||||||
|
this._shooting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private setupMouse() {
|
||||||
|
this._ship.getScene().onPointerDown = (evt, pickInfo, type) => {
|
||||||
|
this._mousePos.x = evt.x;
|
||||||
|
this._mousePos.y = evt.y;
|
||||||
|
this._mouseDown = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
this._ship.getScene().onPointerUp = (evt, pickInfo, type) => {
|
||||||
|
this._mouseDown = false;
|
||||||
|
}
|
||||||
|
this._ship.getScene().onPointerMove = (evt, pickInfo, type) => {
|
||||||
|
|
||||||
|
};
|
||||||
|
this._ship.getScene().onPointerMove = (ev, pickInfo, type) => {
|
||||||
|
if (!this._mouseDown) {return};
|
||||||
|
const xInc = this._rightStickVector.x = (ev.x - this._mousePos.x) / 100;
|
||||||
|
const yInc = this._rightStickVector.y = (ev.y - this._mousePos.y) / 100;
|
||||||
|
if (Math.abs(xInc) <= 1) {
|
||||||
|
this._rightStickVector.x = xInc;
|
||||||
|
} else {
|
||||||
|
this._rightStickVector.x = Math.sign(xInc);
|
||||||
|
}
|
||||||
|
if (Math.abs(yInc) <= 1) {
|
||||||
|
this._rightStickVector.y = yInc;
|
||||||
|
} else {
|
||||||
|
this._rightStickVector.y = Math.sign(yInc);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private setupKeyboard() {
|
||||||
|
document.onkeydown = (ev) => {
|
||||||
|
switch (ev.key) {
|
||||||
|
case ' ':
|
||||||
|
this.shoot();
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
this._leftStickVector.y =-1;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
this._leftStickVector.y =1;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
this._leftStickVector.x =-1;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
this._leftStickVector.x =1;
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _leftInputSource: WebXRInputSource;
|
||||||
|
private _rightInputSource: WebXRInputSource;
|
||||||
|
|
||||||
|
public addController(controller: WebXRInputSource) {
|
||||||
|
if (controller.inputSource.handedness == "left") {
|
||||||
|
this._leftInputSource = controller;
|
||||||
|
this._leftInputSource.onMotionControllerInitObservable.add((motionController) => {
|
||||||
|
console.log(motionController);
|
||||||
|
this.mapMotionController(motionController);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (controller.inputSource.handedness == "right") {
|
||||||
|
this._rightInputSource = controller;
|
||||||
|
this._rightInputSource.onMotionControllerInitObservable.add((motionController) => {
|
||||||
|
console.log(motionController);
|
||||||
|
this.mapMotionController(motionController);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapMotionController(controller: WebXRAbstractMotionController) {
|
||||||
|
controllerComponents.forEach((component) => {
|
||||||
|
const comp = controller.components[component];
|
||||||
|
const observable = this._controllerObservable;
|
||||||
|
|
||||||
|
if (comp && comp.onAxisValueChangedObservable) {
|
||||||
|
comp.onAxisValueChangedObservable.add((axisData) => {
|
||||||
|
observable.notifyObservers({
|
||||||
|
controller: controller,
|
||||||
|
hand: controller.handness,
|
||||||
|
type: 'thumbstick',
|
||||||
|
component: comp,
|
||||||
|
value: comp.value,
|
||||||
|
axisData: {x: axisData.x, y: axisData.y},
|
||||||
|
pressed: comp.pressed,
|
||||||
|
touched: comp.touched
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (comp && comp.onButtonStateChangedObservable) {
|
||||||
|
comp.onButtonStateChangedObservable.add((component) => {
|
||||||
|
observable.notifyObservers({
|
||||||
|
controller: controller,
|
||||||
|
hand: controller.handness,
|
||||||
|
type: 'button',
|
||||||
|
component: comp,
|
||||||
|
value: component.value,
|
||||||
|
axisData: {x: component.axes.x, y: component.axes.y},
|
||||||
|
pressed: component.pressed,
|
||||||
|
touched: component.touched
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/shipEngine.ts
Normal file
81
src/shipEngine.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
AbstractMesh, Color3, GlowLayer,
|
||||||
|
MeshBuilder,
|
||||||
|
ParticleSystem,
|
||||||
|
StandardMaterial,
|
||||||
|
Texture,
|
||||||
|
TransformNode,
|
||||||
|
Vector3
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
|
||||||
|
type MainEngine = {
|
||||||
|
transformNode: TransformNode;
|
||||||
|
particleSystem: ParticleSystem;
|
||||||
|
}
|
||||||
|
export class ShipEngine {
|
||||||
|
private _ship: TransformNode;
|
||||||
|
private _leftMainEngine: MainEngine;
|
||||||
|
private _rightMainEngine: MainEngine;
|
||||||
|
private _gl: GlowLayer;
|
||||||
|
constructor(ship: TransformNode) {
|
||||||
|
this._ship = ship;
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize() {
|
||||||
|
this._gl = new GlowLayer("glow", DefaultScene.MainScene);
|
||||||
|
this._gl.intensity =1;
|
||||||
|
this._leftMainEngine = this.createEngine(new Vector3(-.44, .37, -1.1));
|
||||||
|
this._rightMainEngine = this.createEngine(new Vector3(.44, .37, -1.1));
|
||||||
|
}
|
||||||
|
public idle() {
|
||||||
|
this._leftMainEngine.particleSystem.emitRate = 1;
|
||||||
|
this._rightMainEngine.particleSystem.emitRate = 1;
|
||||||
|
}
|
||||||
|
public forwardback(value: number) {
|
||||||
|
|
||||||
|
if (Math.sign(value) > 0) {
|
||||||
|
(this._leftMainEngine.particleSystem.emitter as AbstractMesh).rotation.y = 0;
|
||||||
|
(this._rightMainEngine.particleSystem.emitter as AbstractMesh).rotation.y = 0;
|
||||||
|
} else {
|
||||||
|
(this._leftMainEngine.particleSystem.emitter as AbstractMesh).rotation.y = Math.PI;
|
||||||
|
(this._rightMainEngine.particleSystem.emitter as AbstractMesh).rotation.y = Math.PI;
|
||||||
|
}
|
||||||
|
this._leftMainEngine.particleSystem.emitRate = Math.abs(value) * 10;
|
||||||
|
this._rightMainEngine.particleSystem.emitRate = Math.abs(value) * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createEngine(position: Vector3) : MainEngine{
|
||||||
|
const MAIN_ROTATION = Math.PI / 2;
|
||||||
|
const engine = new TransformNode("engine", DefaultScene.MainScene);
|
||||||
|
engine.parent = this._ship;
|
||||||
|
engine.position = position;
|
||||||
|
const leftDisc = MeshBuilder.CreateIcoSphere("engineSphere", {radius: .07}, DefaultScene.MainScene);
|
||||||
|
this._gl.addIncludedOnlyMesh(leftDisc);
|
||||||
|
const material = new StandardMaterial("material", DefaultScene.MainScene);
|
||||||
|
material.emissiveColor = new Color3(.5, .5, .1);
|
||||||
|
leftDisc.material = material;
|
||||||
|
leftDisc.parent = engine;
|
||||||
|
leftDisc.rotation.x = MAIN_ROTATION;
|
||||||
|
const particleSystem = this.createParticleSystem(leftDisc);
|
||||||
|
return {transformNode: engine, particleSystem: particleSystem};
|
||||||
|
}
|
||||||
|
private createParticleSystem(mesh: AbstractMesh): ParticleSystem {
|
||||||
|
const myParticleSystem = new ParticleSystem("particles", 1000, DefaultScene.MainScene);
|
||||||
|
myParticleSystem.emitRate = 1;
|
||||||
|
//myParticleSystem.minEmitPower = 2;
|
||||||
|
//myParticleSystem.maxEmitPower = 10;
|
||||||
|
|
||||||
|
myParticleSystem.particleTexture = new Texture("./flare.png");
|
||||||
|
myParticleSystem.emitter = mesh;
|
||||||
|
const coneEmitter = myParticleSystem.createConeEmitter(0.1, Math.PI / 9);
|
||||||
|
myParticleSystem.addSizeGradient(0, .01);
|
||||||
|
myParticleSystem.addSizeGradient(1, .3);
|
||||||
|
myParticleSystem.isLocal = true;
|
||||||
|
|
||||||
|
myParticleSystem.start(); //S
|
||||||
|
return myParticleSystem;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/starfield.ts
Normal file
30
src/starfield.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
AbstractMesh,
|
||||||
|
PhysicsAggregate,
|
||||||
|
PhysicsMotionType,
|
||||||
|
PhysicsShapeType,
|
||||||
|
SceneLoader,
|
||||||
|
Vector3
|
||||||
|
} from "@babylonjs/core";
|
||||||
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
|
||||||
|
export async function createRock(i: number, position: Vector3, size: Vector3): Promise<AbstractMesh> {
|
||||||
|
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid.glb", DefaultScene.MainScene);
|
||||||
|
const rock = importMesh.meshes[1];
|
||||||
|
|
||||||
|
rock.scaling = size;
|
||||||
|
rock.position = position;
|
||||||
|
rock.setParent(null);
|
||||||
|
importMesh.meshes[0].dispose();
|
||||||
|
rock.name = "asteroid-" + i;
|
||||||
|
rock.id = "asteroid-" + i;
|
||||||
|
const agg = new PhysicsAggregate(rock, PhysicsShapeType.CONVEX_HULL, {mass: 10000}, DefaultScene.MainScene);
|
||||||
|
const body =agg.body;
|
||||||
|
body.setLinearDamping(.001);
|
||||||
|
body.setAngularDamping(.00001);
|
||||||
|
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
|
body.setCollisionCallbackEnabled(true);
|
||||||
|
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
||||||
|
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
||||||
|
return rock;
|
||||||
|
}
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
44
tsconfig.json
Normal file
44
tsconfig.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"outDir": "./dist",
|
||||||
|
// choose our ECMA/JavaScript version (all modern browsers support ES6 so it's your best bet)
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"lib": [
|
||||||
|
// choose our default ECMA/libraries to import
|
||||||
|
"dom",
|
||||||
|
// mandatory for all browser-based apps
|
||||||
|
"es6"
|
||||||
|
// mandatory for targeting ES6
|
||||||
|
],
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
// enable latest ECMA runtime behavior with older ECMA/JavaScript versions (delete this line if target: "ESNext" or "ES2022"+)
|
||||||
|
"module": "ESNext",
|
||||||
|
// use the latest ECMA/JavaScript syntax for our import statements and such
|
||||||
|
"moduleResolution": "node",
|
||||||
|
// ensures we are using CommonJS for our npm packages
|
||||||
|
"noResolve": false,
|
||||||
|
// disable TypeScript from automatically detecting/adding files based on import statements and etc (it's less helpful than you think)
|
||||||
|
"isolatedModules": false,
|
||||||
|
// allows our code to be processed by other transpilers, such as preventing non-module TS files (you could delete this since we're only using base TypeScript)
|
||||||
|
"removeComments": true,
|
||||||
|
// remove comments from our outputted code to save on space (look into terser if you want to protect the outputted JS even more)
|
||||||
|
"esModuleInterop": true,
|
||||||
|
// treats non-ES6 modules separately from ES6 modules (helpful if module: "ESNext")
|
||||||
|
"noImplicitAny": false,
|
||||||
|
// usually prevents code from using "any" type fallbacks to prevent untraceable JS errors, but we'll need this disabled for our example code
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
// usually raises an error for any unused local variables, but we'll need this disabled for our example code
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
// raises an error for unused parameters
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
// raises an error for functions that return nothing
|
||||||
|
"skipLibCheck": true
|
||||||
|
// skip type-checking of .d.ts files (it speeds up transpiling)
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
// specify location(s) of .ts files
|
||||||
|
}
|
||||||
32
vite.config.ts
Normal file
32
vite.config.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {defineConfig} from "vite";
|
||||||
|
|
||||||
|
/** @type {import('vite').UserConfig} */
|
||||||
|
export default defineConfig({
|
||||||
|
test: {},
|
||||||
|
define: {},
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'babylon': ['@babylonjs/core']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
esbuildOptions: {
|
||||||
|
define: {
|
||||||
|
global: 'window',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3001,
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
port: 3001,
|
||||||
|
},
|
||||||
|
base: "/"
|
||||||
|
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user