Update gameplay mechanics, asteroid model, and star base physics
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:
Michael Mainguy 2025-11-03 12:26:25 -06:00
parent 3e36662031
commit 37128d8fbd
10 changed files with 78 additions and 53 deletions

BIN
public/asteroid4.glb Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,20 +1,18 @@
import {DefaultScene} from "./defaultScene";
import type {AudioEngineV2} from "@babylonjs/core";
import {
AbstractMesh,
Color3, DistanceConstraint, Engine, InstancedMesh, LinesMesh, Mesh,
Color3,
DistanceConstraint,
MeshBuilder,
Observable,
ParticleHelper,
PhysicsAggregate,
PhysicsMotionType,
PhysicsShapeType, PointsCloudSystem,
StandardMaterial, TransformNode,
PhysicsShapeType,
StandardMaterial,
Vector3
} from "@babylonjs/core";
import type {AudioEngineV2} from "@babylonjs/core";
import {Ship} from "./ship";
import {RockFactory} from "./rockFactory";
import Level from "./level";
import {Scoreboard} from "./scoreboard";
import setLoadingMessage from "./setLoadingMessage";
@ -55,12 +53,7 @@ export class Level1 implements Level {
this._ship.addController(controller);
});
});
//console.log('Controller observable registered, observer:', !!observer);
this.initialize();
}
getReadyObservable(): Observable<Level> {
@ -94,6 +87,7 @@ export class Level1 implements Level {
});
}, 2000);
}
public dispose() {
this._startBase.dispose();
this._endBase.dispose();
@ -101,6 +95,7 @@ export class Level1 implements Level {
this._backgroundStars.dispose();
}
}
public async initialize() {
debugLog('Initializing level from config:', this._levelConfig.difficulty);
if (this._initialized) {
@ -133,6 +128,7 @@ export class Level1 implements Level {
// Calculate distance from start base
const dist = Vector3.Distance(asteroidMesh.position, this._startBase.position);
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
// constraint.isCollisionsEnabled = true;
this._startBase.physicsBody.addConstraint(asteroidMesh.physicsBody, constraint);
}
}
@ -155,6 +151,7 @@ export class Level1 implements Level {
}
});
this._initialized = true;
// Notify that initialization is complete

View File

@ -1,6 +1,6 @@
import {
AbstractMesh,
Color3,
Color3, DirectionalLight,
GlowLayer,
MeshBuilder,
Observable,
@ -66,6 +66,10 @@ export class LevelDeserializer {
const planets = this.createPlanets();
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 {
startBase,
sun,

View File

@ -214,29 +214,29 @@ export class LevelGenerator {
case 'recruit':
return {
rockCount: 5,
forceMultiplier: .5,
forceMultiplier: .8,
rockSizeMin: 10,
rockSizeMax: 15,
distanceMin: 80,
distanceMax: 100
distanceMin: 220,
distanceMax: 250
};
case 'pilot':
return {
rockCount: 10,
forceMultiplier: 1,
rockSizeMin: 8,
rockSizeMax: 12,
distanceMin: 80,
distanceMax: 150
rockSizeMax: 20,
distanceMin: 225,
distanceMax: 300
};
case 'captain':
return {
rockCount: 20,
forceMultiplier: 1.2,
rockSizeMin: 2,
rockSizeMax: 7,
distanceMin: 100,
distanceMax: 250
rockSizeMin: 5,
rockSizeMax: 40,
distanceMin: 230,
distanceMax: 450
};
case 'commander':
return {

View File

@ -4,7 +4,7 @@ import {
CreateAudioEngineAsync,
DirectionalLight,
Engine,
HavokPlugin,
HavokPlugin, HemisphericLight,
ParticleHelper,
Scene,
ScenePerformancePriority,
@ -255,7 +255,12 @@ export class Main {
const havok = await HavokPhysics();
const havokPlugin = new HavokPlugin(true, havok);
//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.getPhysicsEngine().setTimeStep(1/60);
DefaultScene.MainScene.getPhysicsEngine().setSubTimeStep(5);

View File

@ -52,7 +52,7 @@ export class RockFactory {
}
private static async loadMesh() {
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.setParent(null);
this._rockMesh.setEnabled(false);
@ -62,16 +62,17 @@ export class RockFactory {
if (!this._rockMaterial) {
// Clone the original material from GLB to preserve all textures
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);
// Create material using GameConfig texture level
const config = GameConfig.getInstance();
/*const config = GameConfig.getInstance();
this._rockMaterial = MaterialFactory.createAsteroidMaterial(
'asteroid-material',
config.asteroidTextureLevel,
DefaultScene.MainScene,
this._originalMaterial
) as PBRMaterial;
) as PBRMaterial;*/
this._rockMaterial.freeze();
this._rockMesh.material = this._rockMaterial;
@ -113,12 +114,13 @@ export class RockFactory {
body.setLinearDamping(0)
body.setMotionType(PhysicsMotionType.DYNAMIC);
body.setCollisionCallbackEnabled(true);
body.getCollisionObservable().add((eventData) => {
if (eventData.type == 'COLLISION_STARTED') {
debugLog('[RockFactory] Collision detected:', {
/*debugLog('[RockFactory] Collision detected:', {
collidedWith: eventData.collidedAgainst.transformNode.id,
asteroidName: eventData.collider.transformNode.name
});
});*/
if ( eventData.collidedAgainst.transformNode.id == 'ammo') {
debugLog('[RockFactory] ASTEROID HIT! Triggering explosion...');
@ -147,8 +149,15 @@ export class RockFactory {
eventData.collidedAgainst.dispose();
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.setLinearVelocity(Vector3.Random(-10, 10));
}

View File

@ -82,8 +82,8 @@ export class Scoreboard {
scoreText.text = `Score: ${this.calculateScore()}`;
remainingText.text = `Remaining: ${this._remaining}`;
const elapsed = Date.now() - this._startTime;
if (this._active && i++%40 == 0) {
timeRemainingText.text = `Time: ${Math.floor(elapsed/60).toString().padStart(2,"0")}:${(elapsed%60).toString().padStart(2,"0")}`;
if (this._active && i++%30 == 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())}`;
}
});

View File

@ -231,16 +231,16 @@ export class Ship {
//shipMesh.position.y = 1;
shipMesh.position.z = -1;
// shipMesh.renderingGroupId = 3;
const light = new PointLight("ship.light", new Vector3(0, .5, .1), DefaultScene.MainScene);
light.intensity = 4;
light.includedOnlyMeshes = [shipMesh];
//const light = new PointLight("ship.light", new Vector3(0, .5, .1), DefaultScene.MainScene);
//light.intensity = 4;
/*light.includedOnlyMeshes = [shipMesh];
for (const mesh of shipMesh.getChildMeshes()) {
// mesh.renderingGroupId = 3;
if (mesh.material.id.indexOf('glass') === -1) {
light.includedOnlyMeshes.push(mesh);
}
}
light.parent = this._ship;
light.parent = this._ship;*/
//DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .4;
}

View File

@ -1,14 +1,12 @@
import {
AbstractMesh,
AbstractMesh, LoadAssetContainerAsync, Mesh,
PhysicsAggregate,
PhysicsMotionType,
PhysicsShapeType,
SceneLoader,
PhysicsShapeType, Scene,
Vector3
} from "@babylonjs/core";
import {DefaultScene} from "./defaultScene";
import {GameConfig} from "./gameConfig";
import {debug} from "openai/core";
import debugLog from "./debug";
/**
@ -20,21 +18,33 @@ export default async function buildStarBase(position: Vector3): Promise<Abstract
const scene = DefaultScene.MainScene;
// Load the base model
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "base.glb", scene);
const baseMesh = importMesh.meshes[0].getChildMeshes()[0];
debugLog('Star base mesh loaded:', baseMesh);
baseMesh.id = "starBase";
baseMesh.name = "starBase";
baseMesh.position = position;
debugLog('Ship Bounds radius', baseMesh.getBoundingInfo().boundingSphere.radiusWorld);
// Create physics if enabled
const importMesh = await LoadAssetContainerAsync('/base.glb', DefaultScene.MainScene,
{
pluginOptions: {
gltf: {
enabled: true,
}
}
});
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();
if (config.physicsEnabled) {
const agg = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, {
mass: 0
const agg = new PhysicsAggregate(starBase, PhysicsShapeType.MESH, {
mass: 10000
}, scene);
agg.body.setMotionType(PhysicsMotionType.STATIC);
agg.body.setMotionType(PhysicsMotionType.ANIMATED);
agg.body.setCollisionCallbackEnabled(true);
agg.body.getCollisionObservable().add((collidedBody) => {
debugLog('collidedBody', collidedBody);
})
}
return baseMesh;
importMesh.rootNodes[0].dispose();
return starBase;
}