Refactor scoreboard to use GLB mesh and improve ship mechanics
All checks were successful
Build / build (push) Successful in 1m19s

- Move scoreboard ownership from Level1 to Ship class for better encapsulation
- Refactor scoreboard.initialize() to accept GLB submesh (Screen material mesh)
- Dispose original material when applying AdvancedDynamicTexture to mesh
- Change scoreboard background to green for visibility testing
- Increase ship velocities: MAX_LINEAR_VELOCITY to 200, LINEAR_FORCE_MULTIPLIER to 1200
- Adjust ammo spawn position (y: 0.5, z: 7.1) and velocity (200000)
- Update sight reticle position to match new ammo spawn point (y: 0.5)
- Fix sight circle rotation and rendering group assignment
- Update ship2.glb, ship1.glb, and base.glb models
- Comment out ship position override in level initialization

🤖 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-04 13:41:17 -06:00
parent 37128d8fbd
commit f11005fdb6
7 changed files with 54 additions and 25 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -27,7 +27,6 @@ export class Level1 implements Level {
private _initialized: boolean = false;
private _startBase: AbstractMesh;
private _endBase: AbstractMesh;
private _scoreboard: Scoreboard;
private _levelConfig: LevelConfig;
private _audioEngine: AudioEngineV2;
private _deserializer: LevelDeserializer;
@ -38,7 +37,7 @@ export class Level1 implements Level {
this._audioEngine = audioEngine;
this._deserializer = new LevelDeserializer(levelConfig);
this._ship = new Ship(audioEngine);
this._scoreboard = new Scoreboard();
const xr = DefaultScene.XR;
debugLog('Level1 constructor - Setting up XR observables');
@ -105,18 +104,18 @@ export class Level1 implements Level {
setLoadingMessage("Loading level from configuration...");
// Use deserializer to create all entities from config
const entities = await this._deserializer.deserialize(this._scoreboard.onScoreObservable);
const entities = await this._deserializer.deserialize(this._ship.scoreboard.onScoreObservable);
this._startBase = entities.startBase;
// sun and planets are already created by deserializer
// Initialize scoreboard with total asteroid count
this._scoreboard.setRemainingCount(entities.asteroids.length);
this._ship.scoreboard.setRemainingCount(entities.asteroids.length);
debugLog(`Initialized scoreboard with ${entities.asteroids.length} asteroids`);
// Position ship from config
const shipConfig = this._deserializer.getShipConfig();
this._ship.position = new Vector3(shipConfig.position[0], shipConfig.position[1], shipConfig.position[2]);
//this._ship.position = new Vector3(shipConfig.position[0], shipConfig.position[1], shipConfig.position[2]);
// Add distance constraints to asteroids (if physics enabled)
setLoadingMessage("Configuring physics constraints...");

View File

@ -1,6 +1,7 @@
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui";
import {DefaultScene} from "./defaultScene";
import {
AbstractMesh, Mesh,
MeshBuilder,
Observable, StandardMaterial,
Vector3,
@ -21,7 +22,7 @@ export class Scoreboard {
private _done = false;
public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
constructor() {
this.initialize();
}
public get done() {
return this._done;
@ -32,26 +33,40 @@ export class Scoreboard {
public setRemainingCount(count: number) {
this._remaining = count;
}
private initialize() {
public initialize(baseMesh: Mesh) {
const scene = DefaultScene.MainScene;
const parent = scene.getNodeById('ship');
debugLog('Scoreboard parent:', parent);
debugLog('Initializing scoreboard');
const scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
// scoreboard.renderingGroupId = 3;
const material = new StandardMaterial("scoreboard", scene);
let scoreboard = null;
if (baseMesh) {
scoreboard = baseMesh;
scoreboard.material.dispose();
//scoreboard.material = new StandardMaterial("scoreboard", scene);
} else {
scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
scoreboard.parent =parent;
scoreboard.position.y = 1.05;
scoreboard.position.z = 2.1;
scoreboard.visibility = .5;
scoreboard.scaling = new Vector3(.4, .4, .4);
}
// scoreboard.renderingGroupId = 3;
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512);
advancedTexture.background = "black";
advancedTexture.background = "green";
advancedTexture.hasAlpha = false;
const scoreText = this.createText();
@ -69,8 +84,8 @@ export class Scoreboard {
const panel = new StackPanel();
panel.isVertical = true;
panel.height = 1;
panel.isVertical = true;
//panel.height = .5;
//panel.isVertical = true;
panel.addControl(scoreText);
panel.addControl(remainingText);
panel.addControl(fpsText);
@ -92,6 +107,7 @@ export class Scoreboard {
this._score += score.score;
this._remaining += score.remaining;
});
this._active = true;
}
private createText(): TextBlock {

View File

@ -22,9 +22,10 @@ import {DefaultScene} from "./defaultScene";
import { GameConfig } from "./gameConfig";
import { Sight } from "./sight";
import debugLog from './debug';
const MAX_LINEAR_VELOCITY = 80;
import {Scoreboard} from "./scoreboard";
const MAX_LINEAR_VELOCITY = 200;
const MAX_ANGULAR_VELOCITY = 1.8;
const LINEAR_FORCE_MULTIPLIER = 800;
const LINEAR_FORCE_MULTIPLIER = 1200;
const ANGULAR_FORCE_MULTIPLIER = 20;
const controllerComponents = [
@ -52,6 +53,7 @@ type ControllerEvent = {
export class Ship {
private _ship: TransformNode;
private _scoreboard: Scoreboard;
private _controllerObservable: Observable<ControllerEvent> = new Observable<ControllerEvent>();
private _ammoMaterial: StandardMaterial;
private _primaryThrustVectorSound: StaticSound;
@ -87,7 +89,9 @@ export class Ship {
volume: 0.5
});
}
public get scoreboard(): Scoreboard {
return this._scoreboard;
}
private shoot() {
// Only allow shooting if physics is enabled
const config = GameConfig.getInstance();
@ -98,8 +102,9 @@ export class Ship {
this._shot?.play();
const ammo = new InstancedMesh("ammo", this._ammoBaseMesh as Mesh);
ammo.parent = this._ship;
ammo.position.y = 2;
ammo.rotation.x = Math.PI / 2;
ammo.position.y = .5;
ammo.position.z = 7.1;
//ammo.rotation.x = Math.PI / 2;
ammo.setParent(null);
const ammoAggregate = new PhysicsAggregate(ammo, PhysicsShapeType.SPHERE, {
mass: 1000,
@ -110,7 +115,7 @@ export class Ship {
ammoAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(100000))
ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(200000))
//.add(this._ship.physicsBody.getLinearVelocity()));
window.setTimeout(() => {
@ -170,7 +175,7 @@ export class Ship {
// Create sight reticle
this._sight = new Sight(DefaultScene.MainScene, this._ship, {
position: new Vector3(0, 2, 125),
position: new Vector3(0, .5, 125),
circleRadius: 2,
crosshairLength: 1.5,
lineThickness: 0.1,
@ -183,11 +188,16 @@ export class Ship {
private async initialize() {
this._scoreboard = new Scoreboard();
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "ship2.glb", DefaultScene.MainScene);
const shipMesh = importMesh.meshes[0];
shipMesh.id = "shipMesh";
shipMesh.name = "shipMesh";
debugLog(shipMesh.position);
shipMesh.parent = this._ship;
debugLog(shipMesh.position);
// Create physics aggregate based on the loaded mesh (if physics enabled)
const config = GameConfig.getInstance();
@ -242,6 +252,9 @@ export class Ship {
}
light.parent = this._ship;*/
//DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .4;
const screenMesh = DefaultScene.MainScene.getMaterialById('Screen')?.getBindedMeshes()[0];
this._scoreboard.initialize(screenMesh as Mesh);
//this._scoreboard.initialize(null);
}

View File

@ -78,8 +78,9 @@ export class Sight {
tessellation: 64
}, this.scene);
this.circle.parent = this.reticleGroup;
this.circle.rotation.x = -Math.PI / 2;
this.circle.material = material;
// this.circle.renderingGroupId = this.config.renderingGroupId;
this.circle.renderingGroupId = this.config.renderingGroupId;
// Create crosshair lines (4 lines extending from center gap)
this.createCrosshairLines(material);