Update gameplay mechanics, asteroid model, and star base physics
All checks were successful
Build / build (push) Successful in 1m18s
All checks were successful
Build / build (push) Successful in 1m18s
- Switch to asteroid4.glb model with updated material handling - Adjust difficulty parameters: increased spawn distances (220-450m range), updated force multipliers, varied asteroid sizes - Fix scoreboard timer display (was showing frames instead of actual seconds) - Refactor star base to use asset container with mesh merging for better performance - Change star base physics from STATIC to ANIMATED with collision detection enabled - Add directional lighting in level deserializer for improved scene lighting - Clean up commented code and optimize debug logging - Update base.glb 3D model 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3e36662031
commit
37128d8fbd
BIN
public/asteroid4.glb
Normal file
BIN
public/asteroid4.glb
Normal file
Binary file not shown.
BIN
public/base.glb
BIN
public/base.glb
Binary file not shown.
@ -1,20 +1,18 @@
|
|||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import type {AudioEngineV2} from "@babylonjs/core";
|
||||||
import {
|
import {
|
||||||
AbstractMesh,
|
AbstractMesh,
|
||||||
Color3, DistanceConstraint, Engine, InstancedMesh, LinesMesh, Mesh,
|
Color3,
|
||||||
|
DistanceConstraint,
|
||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable,
|
Observable,
|
||||||
ParticleHelper,
|
|
||||||
PhysicsAggregate,
|
PhysicsAggregate,
|
||||||
PhysicsMotionType,
|
PhysicsMotionType,
|
||||||
PhysicsShapeType, PointsCloudSystem,
|
PhysicsShapeType,
|
||||||
StandardMaterial, TransformNode,
|
StandardMaterial,
|
||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import type {AudioEngineV2} from "@babylonjs/core";
|
|
||||||
import {Ship} from "./ship";
|
import {Ship} from "./ship";
|
||||||
|
|
||||||
import {RockFactory} from "./rockFactory";
|
|
||||||
import Level from "./level";
|
import Level from "./level";
|
||||||
import {Scoreboard} from "./scoreboard";
|
import {Scoreboard} from "./scoreboard";
|
||||||
import setLoadingMessage from "./setLoadingMessage";
|
import setLoadingMessage from "./setLoadingMessage";
|
||||||
@ -55,12 +53,7 @@ export class Level1 implements Level {
|
|||||||
this._ship.addController(controller);
|
this._ship.addController(controller);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//console.log('Controller observable registered, observer:', !!observer);
|
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadyObservable(): Observable<Level> {
|
getReadyObservable(): Observable<Level> {
|
||||||
@ -94,6 +87,7 @@ export class Level1 implements Level {
|
|||||||
});
|
});
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this._startBase.dispose();
|
this._startBase.dispose();
|
||||||
this._endBase.dispose();
|
this._endBase.dispose();
|
||||||
@ -101,6 +95,7 @@ export class Level1 implements Level {
|
|||||||
this._backgroundStars.dispose();
|
this._backgroundStars.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initialize() {
|
public async initialize() {
|
||||||
debugLog('Initializing level from config:', this._levelConfig.difficulty);
|
debugLog('Initializing level from config:', this._levelConfig.difficulty);
|
||||||
if (this._initialized) {
|
if (this._initialized) {
|
||||||
@ -133,6 +128,7 @@ export class Level1 implements Level {
|
|||||||
// Calculate distance from start base
|
// Calculate distance from start base
|
||||||
const dist = Vector3.Distance(asteroidMesh.position, this._startBase.position);
|
const dist = Vector3.Distance(asteroidMesh.position, this._startBase.position);
|
||||||
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
|
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
|
||||||
|
// constraint.isCollisionsEnabled = true;
|
||||||
this._startBase.physicsBody.addConstraint(asteroidMesh.physicsBody, constraint);
|
this._startBase.physicsBody.addConstraint(asteroidMesh.physicsBody, constraint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +151,7 @@ export class Level1 implements Level {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
|
|
||||||
// Notify that initialization is complete
|
// Notify that initialization is complete
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AbstractMesh,
|
AbstractMesh,
|
||||||
Color3,
|
Color3, DirectionalLight,
|
||||||
GlowLayer,
|
GlowLayer,
|
||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable,
|
Observable,
|
||||||
@ -66,6 +66,10 @@ export class LevelDeserializer {
|
|||||||
const planets = this.createPlanets();
|
const planets = this.createPlanets();
|
||||||
const asteroids = await this.createAsteroids(scoreObservable);
|
const asteroids = await this.createAsteroids(scoreObservable);
|
||||||
|
|
||||||
|
const dir = new Vector3(-1,-2,-1)
|
||||||
|
const light = new DirectionalLight("dirLight", dir, DefaultScene.MainScene);
|
||||||
|
const light2 = new DirectionalLight("dirLight2", dir.negate(), DefaultScene.MainScene);
|
||||||
|
light2.intensity = .5;
|
||||||
return {
|
return {
|
||||||
startBase,
|
startBase,
|
||||||
sun,
|
sun,
|
||||||
|
|||||||
@ -214,29 +214,29 @@ export class LevelGenerator {
|
|||||||
case 'recruit':
|
case 'recruit':
|
||||||
return {
|
return {
|
||||||
rockCount: 5,
|
rockCount: 5,
|
||||||
forceMultiplier: .5,
|
forceMultiplier: .8,
|
||||||
rockSizeMin: 10,
|
rockSizeMin: 10,
|
||||||
rockSizeMax: 15,
|
rockSizeMax: 15,
|
||||||
distanceMin: 80,
|
distanceMin: 220,
|
||||||
distanceMax: 100
|
distanceMax: 250
|
||||||
};
|
};
|
||||||
case 'pilot':
|
case 'pilot':
|
||||||
return {
|
return {
|
||||||
rockCount: 10,
|
rockCount: 10,
|
||||||
forceMultiplier: 1,
|
forceMultiplier: 1,
|
||||||
rockSizeMin: 8,
|
rockSizeMin: 8,
|
||||||
rockSizeMax: 12,
|
rockSizeMax: 20,
|
||||||
distanceMin: 80,
|
distanceMin: 225,
|
||||||
distanceMax: 150
|
distanceMax: 300
|
||||||
};
|
};
|
||||||
case 'captain':
|
case 'captain':
|
||||||
return {
|
return {
|
||||||
rockCount: 20,
|
rockCount: 20,
|
||||||
forceMultiplier: 1.2,
|
forceMultiplier: 1.2,
|
||||||
rockSizeMin: 2,
|
rockSizeMin: 5,
|
||||||
rockSizeMax: 7,
|
rockSizeMax: 40,
|
||||||
distanceMin: 100,
|
distanceMin: 230,
|
||||||
distanceMax: 250
|
distanceMax: 450
|
||||||
};
|
};
|
||||||
case 'commander':
|
case 'commander':
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
CreateAudioEngineAsync,
|
CreateAudioEngineAsync,
|
||||||
DirectionalLight,
|
DirectionalLight,
|
||||||
Engine,
|
Engine,
|
||||||
HavokPlugin,
|
HavokPlugin, HemisphericLight,
|
||||||
ParticleHelper,
|
ParticleHelper,
|
||||||
Scene,
|
Scene,
|
||||||
ScenePerformancePriority,
|
ScenePerformancePriority,
|
||||||
@ -255,7 +255,12 @@ export class Main {
|
|||||||
const havok = await HavokPhysics();
|
const havok = await HavokPhysics();
|
||||||
const havokPlugin = new HavokPlugin(true, havok);
|
const havokPlugin = new HavokPlugin(true, havok);
|
||||||
//DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1);
|
//DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1);
|
||||||
const light = new DirectionalLight("dirLight", new Vector3(-1, -2, -1), DefaultScene.MainScene);
|
|
||||||
|
//const light = new HemisphericLight("mainlight", new Vector3(-1, -1, 0), DefaultScene.MainScene);
|
||||||
|
//light.diffuse = new Color3(.4, .4, .3);
|
||||||
|
//light.groundColor = new Color3(.2, .2, .1);
|
||||||
|
//light.intensity = .5;
|
||||||
|
//light.specular = new Color3(0,0,0);
|
||||||
DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin);
|
DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin);
|
||||||
DefaultScene.MainScene.getPhysicsEngine().setTimeStep(1/60);
|
DefaultScene.MainScene.getPhysicsEngine().setTimeStep(1/60);
|
||||||
DefaultScene.MainScene.getPhysicsEngine().setSubTimeStep(5);
|
DefaultScene.MainScene.getPhysicsEngine().setSubTimeStep(5);
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export class RockFactory {
|
|||||||
}
|
}
|
||||||
private static async loadMesh() {
|
private static async loadMesh() {
|
||||||
debugLog('loading mesh');
|
debugLog('loading mesh');
|
||||||
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid3.glb", DefaultScene.MainScene);
|
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid4.glb", DefaultScene.MainScene);
|
||||||
this._rockMesh = importMesh.meshes[1].clone("asteroid", null, false);
|
this._rockMesh = importMesh.meshes[1].clone("asteroid", null, false);
|
||||||
this._rockMesh.setParent(null);
|
this._rockMesh.setParent(null);
|
||||||
this._rockMesh.setEnabled(false);
|
this._rockMesh.setEnabled(false);
|
||||||
@ -62,16 +62,17 @@ export class RockFactory {
|
|||||||
if (!this._rockMaterial) {
|
if (!this._rockMaterial) {
|
||||||
// Clone the original material from GLB to preserve all textures
|
// Clone the original material from GLB to preserve all textures
|
||||||
this._originalMaterial = this._rockMesh.material.clone("asteroid-original") as PBRMaterial;
|
this._originalMaterial = this._rockMesh.material.clone("asteroid-original") as PBRMaterial;
|
||||||
|
this._rockMaterial = this._rockMesh.material.clone("asteroid-original") as PBRMaterial;
|
||||||
debugLog('Cloned original material from GLB:', this._originalMaterial);
|
debugLog('Cloned original material from GLB:', this._originalMaterial);
|
||||||
|
|
||||||
// Create material using GameConfig texture level
|
// Create material using GameConfig texture level
|
||||||
const config = GameConfig.getInstance();
|
/*const config = GameConfig.getInstance();
|
||||||
this._rockMaterial = MaterialFactory.createAsteroidMaterial(
|
this._rockMaterial = MaterialFactory.createAsteroidMaterial(
|
||||||
'asteroid-material',
|
'asteroid-material',
|
||||||
config.asteroidTextureLevel,
|
config.asteroidTextureLevel,
|
||||||
DefaultScene.MainScene,
|
DefaultScene.MainScene,
|
||||||
this._originalMaterial
|
this._originalMaterial
|
||||||
) as PBRMaterial;
|
) as PBRMaterial;*/
|
||||||
this._rockMaterial.freeze();
|
this._rockMaterial.freeze();
|
||||||
|
|
||||||
this._rockMesh.material = this._rockMaterial;
|
this._rockMesh.material = this._rockMaterial;
|
||||||
@ -113,12 +114,13 @@ export class RockFactory {
|
|||||||
body.setLinearDamping(0)
|
body.setLinearDamping(0)
|
||||||
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
body.setCollisionCallbackEnabled(true);
|
body.setCollisionCallbackEnabled(true);
|
||||||
|
|
||||||
body.getCollisionObservable().add((eventData) => {
|
body.getCollisionObservable().add((eventData) => {
|
||||||
if (eventData.type == 'COLLISION_STARTED') {
|
if (eventData.type == 'COLLISION_STARTED') {
|
||||||
debugLog('[RockFactory] Collision detected:', {
|
/*debugLog('[RockFactory] Collision detected:', {
|
||||||
collidedWith: eventData.collidedAgainst.transformNode.id,
|
collidedWith: eventData.collidedAgainst.transformNode.id,
|
||||||
asteroidName: eventData.collider.transformNode.name
|
asteroidName: eventData.collider.transformNode.name
|
||||||
});
|
});*/
|
||||||
|
|
||||||
if ( eventData.collidedAgainst.transformNode.id == 'ammo') {
|
if ( eventData.collidedAgainst.transformNode.id == 'ammo') {
|
||||||
debugLog('[RockFactory] ASTEROID HIT! Triggering explosion...');
|
debugLog('[RockFactory] ASTEROID HIT! Triggering explosion...');
|
||||||
@ -147,8 +149,15 @@ export class RockFactory {
|
|||||||
eventData.collidedAgainst.dispose();
|
eventData.collidedAgainst.dispose();
|
||||||
debugLog('[RockFactory] Disposal complete');
|
debugLog('[RockFactory] Disposal complete');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
/*debugLog('[RockFactory] Collision ended between:', {
|
||||||
|
collider: eventData.collider.transformNode.id,
|
||||||
|
collidedWith: eventData.collidedAgainst.transformNode.id
|
||||||
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
||||||
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,8 +82,8 @@ export class Scoreboard {
|
|||||||
scoreText.text = `Score: ${this.calculateScore()}`;
|
scoreText.text = `Score: ${this.calculateScore()}`;
|
||||||
remainingText.text = `Remaining: ${this._remaining}`;
|
remainingText.text = `Remaining: ${this._remaining}`;
|
||||||
const elapsed = Date.now() - this._startTime;
|
const elapsed = Date.now() - this._startTime;
|
||||||
if (this._active && i++%40 == 0) {
|
if (this._active && i++%30 == 0) {
|
||||||
timeRemainingText.text = `Time: ${Math.floor(elapsed/60).toString().padStart(2,"0")}:${(elapsed%60).toString().padStart(2,"0")}`;
|
timeRemainingText.text = `Time: ${Math.floor(elapsed/60000).toString().padStart(2,"0")}:${(Math.floor(elapsed/1000)%60).toString().padStart(2,"0")}`;
|
||||||
fpsText.text = `FPS: ${Math.floor(scene.getEngine().getFps())}`;
|
fpsText.text = `FPS: ${Math.floor(scene.getEngine().getFps())}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -231,16 +231,16 @@ export class Ship {
|
|||||||
//shipMesh.position.y = 1;
|
//shipMesh.position.y = 1;
|
||||||
shipMesh.position.z = -1;
|
shipMesh.position.z = -1;
|
||||||
// shipMesh.renderingGroupId = 3;
|
// shipMesh.renderingGroupId = 3;
|
||||||
const light = new PointLight("ship.light", new Vector3(0, .5, .1), DefaultScene.MainScene);
|
//const light = new PointLight("ship.light", new Vector3(0, .5, .1), DefaultScene.MainScene);
|
||||||
light.intensity = 4;
|
//light.intensity = 4;
|
||||||
light.includedOnlyMeshes = [shipMesh];
|
/*light.includedOnlyMeshes = [shipMesh];
|
||||||
for (const mesh of shipMesh.getChildMeshes()) {
|
for (const mesh of shipMesh.getChildMeshes()) {
|
||||||
// mesh.renderingGroupId = 3;
|
// mesh.renderingGroupId = 3;
|
||||||
if (mesh.material.id.indexOf('glass') === -1) {
|
if (mesh.material.id.indexOf('glass') === -1) {
|
||||||
light.includedOnlyMeshes.push(mesh);
|
light.includedOnlyMeshes.push(mesh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
light.parent = this._ship;
|
light.parent = this._ship;*/
|
||||||
//DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .4;
|
//DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
AbstractMesh,
|
AbstractMesh, LoadAssetContainerAsync, Mesh,
|
||||||
PhysicsAggregate,
|
PhysicsAggregate,
|
||||||
PhysicsMotionType,
|
PhysicsMotionType,
|
||||||
PhysicsShapeType,
|
PhysicsShapeType, Scene,
|
||||||
SceneLoader,
|
|
||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
import {GameConfig} from "./gameConfig";
|
import {GameConfig} from "./gameConfig";
|
||||||
import {debug} from "openai/core";
|
|
||||||
import debugLog from "./debug";
|
import debugLog from "./debug";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,21 +18,33 @@ export default async function buildStarBase(position: Vector3): Promise<Abstract
|
|||||||
const scene = DefaultScene.MainScene;
|
const scene = DefaultScene.MainScene;
|
||||||
|
|
||||||
// Load the base model
|
// Load the base model
|
||||||
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "base.glb", scene);
|
const importMesh = await LoadAssetContainerAsync('/base.glb', DefaultScene.MainScene,
|
||||||
const baseMesh = importMesh.meshes[0].getChildMeshes()[0];
|
{
|
||||||
debugLog('Star base mesh loaded:', baseMesh);
|
pluginOptions: {
|
||||||
baseMesh.id = "starBase";
|
gltf: {
|
||||||
baseMesh.name = "starBase";
|
enabled: true,
|
||||||
baseMesh.position = position;
|
}
|
||||||
debugLog('Ship Bounds radius', baseMesh.getBoundingInfo().boundingSphere.radiusWorld);
|
}
|
||||||
// Create physics if enabled
|
});
|
||||||
|
importMesh.addAllToScene();
|
||||||
|
const starBase = Mesh.MergeMeshes(importMesh.rootNodes[0].getChildMeshes(false), true, false, null, false, true);
|
||||||
|
starBase.id = 'starBase';
|
||||||
|
starBase.name = 'starBase';
|
||||||
|
DefaultScene.MainScene.addMesh(starBase);
|
||||||
|
debugLog('imported base mesh', importMesh.meshes[0]);
|
||||||
|
|
||||||
|
starBase.position = position;
|
||||||
const config = GameConfig.getInstance();
|
const config = GameConfig.getInstance();
|
||||||
if (config.physicsEnabled) {
|
if (config.physicsEnabled) {
|
||||||
const agg = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, {
|
const agg = new PhysicsAggregate(starBase, PhysicsShapeType.MESH, {
|
||||||
mass: 0
|
mass: 10000
|
||||||
}, scene);
|
}, scene);
|
||||||
agg.body.setMotionType(PhysicsMotionType.STATIC);
|
agg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||||
|
agg.body.setCollisionCallbackEnabled(true);
|
||||||
|
agg.body.getCollisionObservable().add((collidedBody) => {
|
||||||
|
debugLog('collidedBody', collidedBody);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
importMesh.rootNodes[0].dispose();
|
||||||
return baseMesh;
|
return starBase;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user