Refactor sight to crosshair reticle and rename starfield to rockFactory
Some checks failed
Build / build (push) Failing after 19s

Created new Sight class:
- Crosshair design with circle, lines, and center dot
- Configurable radius, line length, thickness, and colors
- Green reticle color (traditional gun sight)
- Center gap and proper rendering group
- Dispose method for cleanup

Refactored Ship class:
- Replaced disc sight with new Sight class
- Changed ammo mesh to IcoSphere for better performance
- Added dispose method to clean up sight resources
- Integrated sight with configuration options

Renamed starfield.ts to rockFactory.ts:
- Better reflects the class purpose (RockFactory)
- Updated all imports across codebase
- Updated CLAUDE.md documentation

Updated asteroid model:
- Changed from asteroid2.glb to asteroid3.glb
- Added new asteroid3.blend and asteroid3.glb assets

Fixed background stars material:
- Added proper material type casting for StandardMaterial
- Fixed emissiveColor setting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-10-30 10:21:27 -05:00
parent 9df64b7dd9
commit cb96b4ea6c
10 changed files with 221 additions and 20 deletions

View File

@ -119,7 +119,7 @@ src/
level.ts - Level interface
level1.ts - Main game level implementation
ship.ts - Player ship, controls, weapons
starfield.ts - Rock factory and collision handling
rockFactory.ts - Rock factory and collision handling
scoreboard.ts - In-cockpit HUD display
createSun.ts - Sun mesh generation
createPlanets.ts - Procedural planet generation

BIN
public/asteroid3.blend Normal file

Binary file not shown.

BIN
public/asteroid3.glb Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
import {Color3, Color4, PointsCloudSystem, Scene, Vector3} from "@babylonjs/core";
import {Color3, Color4, PointsCloudSystem, Scene, StandardMaterial, Vector3} from "@babylonjs/core";
/**
* Configuration options for background stars
@ -95,11 +95,12 @@ export class BackgroundStars {
const mesh = this.pcs.mesh;
if (mesh) {
// Stars should not receive lighting
mesh.material.disableLighting = true;
mesh.material.emissiveColor = new Color3(1,1,1);
const mat = (mesh.material as StandardMaterial)
mat.disableLighting = true;
mat.emissiveColor = new Color3(1,1,1);
// Disable depth write so stars don't occlude other objects
mesh.material.disableDepthWrite = true;
mat.disableDepthWrite = true;
// Stars should be in the background
mesh.renderingGroupId = 0;

View File

@ -14,7 +14,7 @@ import {
import type {AudioEngineV2} from "@babylonjs/core";
import {Ship} from "./ship";
import {RockFactory} from "./starfield";
import {RockFactory} from "./rockFactory";
import Level from "./level";
import {Scoreboard} from "./scoreboard";
import setLoadingMessage from "./setLoadingMessage";

View File

@ -13,7 +13,7 @@ import {
Vector3
} from "@babylonjs/core";
import { DefaultScene } from "./defaultScene";
import { RockFactory } from "./starfield";
import { RockFactory } from "./rockFactory";
import { ScoreEvent } from "./scoreboard";
import {
LevelConfig,

View File

@ -19,7 +19,7 @@ import {Level1} from "./level1";
import Demo from "./demo";
import Level from "./level";
import setLoadingMessage from "./setLoadingMessage";
import {RockFactory} from "./starfield";
import {RockFactory} from "./rockFactory";
import {ControllerDebug} from "./controllerDebug";
import {router, showView} from "./router";
import {populateLevelSelector, hasSavedLevels} from "./levelSelector";

View File

@ -59,7 +59,7 @@ export class RockFactory {
}
private static async loadMesh() {
console.log('loading mesh');
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid2.glb", DefaultScene.MainScene);
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid3.glb", DefaultScene.MainScene);
this._rockMesh = importMesh.meshes[1].clone("asteroid", null, false);
this._rockMesh.setParent(null);
this._rockMesh.setEnabled(false);

View File

@ -22,6 +22,7 @@ import {
import type {AudioEngineV2, StaticSound} from "@babylonjs/core";
import {DefaultScene} from "./defaultScene";
import { GameConfig } from "./gameConfig";
import { Sight } from "./sight";
const MAX_FORWARD_THRUST = 40;
const controllerComponents = [
@ -69,6 +70,7 @@ export class Ship {
private _controllerMode: ControllerStickMode;
private _active = false;
private _audioEngine: AudioEngineV2;
private _sight: Sight;
constructor(mode: ControllerStickMode = ControllerStickMode.BEGINNER, audioEngine?: AudioEngineV2) {
this._controllerMode = mode;
this._audioEngine = audioEngine;
@ -154,7 +156,7 @@ export class Ship {
}
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0);
this._ammoBaseMesh = MeshBuilder.CreateSphere("bullet", {diameter: .2}, DefaultScene.MainScene);
this._ammoBaseMesh = MeshBuilder.CreateIcoSphere("bullet", {radius: .1, subdivisions: 2}, DefaultScene.MainScene);
this._ammoBaseMesh.material = this._ammoMaterial;
this._ammoBaseMesh.setEnabled(false);
@ -180,17 +182,17 @@ export class Ship {
DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
//const sightPos = this._forwardNode.position.scale(30);
const sight = MeshBuilder.CreateDisc("sight", {radius: 2 }, DefaultScene.MainScene);
// Create sight reticle
this._sight = new Sight(DefaultScene.MainScene, this._ship, {
position: new Vector3(0, 2, 125),
circleRadius: 2,
crosshairLength: 1.5,
lineThickness: 0.1,
color: Color3.Green(),
renderingGroupId: 3,
centerGap: 0.5
});
sight.parent = this._ship
//sight.rotation.x = -Math.PI / 2;
const signtMaterial = new StandardMaterial("sightMaterial", DefaultScene.MainScene);
signtMaterial.emissiveColor = Color3.Yellow();
signtMaterial.ambientColor = Color3.Yellow();
sight.material = signtMaterial;
sight.position = new Vector3(0, 2, 125);
sight.renderingGroupId = 3;
let i = 0;
DefaultScene.MainScene.onBeforeRenderObservable.add(() => {
if (i++ % 10 == 0) {
@ -527,6 +529,16 @@ export class Ship {
}
});
}
/**
* Dispose of ship resources
*/
public dispose(): void {
if (this._sight) {
this._sight.dispose();
}
// Add other cleanup as needed
}
}
function decrementValue(value: number, increment: number = .8): number {
if (Math.abs(value) < .01) {

188
src/sight.ts Normal file
View File

@ -0,0 +1,188 @@
import {
Color3,
Mesh,
MeshBuilder,
Scene,
StandardMaterial,
TransformNode,
Vector3
} from "@babylonjs/core";
/**
* Configuration options for the sight reticle
*/
export interface SightConfig {
/** Position relative to parent */
position?: Vector3;
/** Circle radius */
circleRadius?: number;
/** Crosshair line length */
crosshairLength?: number;
/** Line thickness */
lineThickness?: number;
/** Reticle color */
color?: Color3;
/** Rendering group ID */
renderingGroupId?: number;
/** Gap size in the center of the crosshair */
centerGap?: number;
}
/**
* Gun sight reticle with crosshair and circle
*/
export class Sight {
private reticleGroup: TransformNode;
private circle: Mesh;
private crosshairLines: Mesh[] = [];
private scene: Scene;
private config: Required<SightConfig>;
// Default configuration
private static readonly DEFAULT_CONFIG: Required<SightConfig> = {
position: new Vector3(0, 2, 125),
circleRadius: 2,
crosshairLength: 1.5,
lineThickness: 0.1,
color: Color3.Green(),
renderingGroupId: 3,
centerGap: 0.5
};
constructor(scene: Scene, parent: TransformNode, config?: SightConfig) {
this.scene = scene;
this.config = { ...Sight.DEFAULT_CONFIG, ...config };
this.createReticle(parent);
}
/**
* Create the reticle (circle + crosshair)
*/
private createReticle(parent: TransformNode): void {
// Create a parent node for the entire reticle
this.reticleGroup = new TransformNode("sightReticle", this.scene);
this.reticleGroup.parent = parent;
this.reticleGroup.position = this.config.position;
// Create material
const material = new StandardMaterial("sightMaterial", this.scene);
material.emissiveColor = this.config.color;
material.disableLighting = true;
material.alpha = 0.8;
// Create outer circle
this.circle = MeshBuilder.CreateTorus("sightCircle", {
diameter: this.config.circleRadius * 2,
thickness: this.config.lineThickness,
tessellation: 64
}, this.scene);
this.circle.parent = this.reticleGroup;
this.circle.material = material;
this.circle.renderingGroupId = this.config.renderingGroupId;
// Create crosshair lines (4 lines extending from center gap)
this.createCrosshairLines(material);
}
/**
* Create the crosshair lines (top, bottom, left, right)
*/
private createCrosshairLines(material: StandardMaterial): void {
const gap = this.config.centerGap;
const length = this.config.crosshairLength;
const thickness = this.config.lineThickness;
// Top line
const topLine = MeshBuilder.CreateBox("crosshairTop", {
width: thickness,
height: length,
depth: thickness
}, this.scene);
topLine.parent = this.reticleGroup;
topLine.position.y = gap + length / 2;
topLine.material = material;
topLine.renderingGroupId = this.config.renderingGroupId;
this.crosshairLines.push(topLine);
// Bottom line
const bottomLine = MeshBuilder.CreateBox("crosshairBottom", {
width: thickness,
height: length,
depth: thickness
}, this.scene);
bottomLine.parent = this.reticleGroup;
bottomLine.position.y = -(gap + length / 2);
bottomLine.material = material;
bottomLine.renderingGroupId = this.config.renderingGroupId;
this.crosshairLines.push(bottomLine);
// Left line
const leftLine = MeshBuilder.CreateBox("crosshairLeft", {
width: length,
height: thickness,
depth: thickness
}, this.scene);
leftLine.parent = this.reticleGroup;
leftLine.position.x = -(gap + length / 2);
leftLine.material = material;
leftLine.renderingGroupId = this.config.renderingGroupId;
this.crosshairLines.push(leftLine);
// Right line
const rightLine = MeshBuilder.CreateBox("crosshairRight", {
width: length,
height: thickness,
depth: thickness
}, this.scene);
rightLine.parent = this.reticleGroup;
rightLine.position.x = gap + length / 2;
rightLine.material = material;
rightLine.renderingGroupId = this.config.renderingGroupId;
this.crosshairLines.push(rightLine);
// Center dot (optional, very small)
const centerDot = MeshBuilder.CreateSphere("crosshairCenter", {
diameter: thickness * 1.5
}, this.scene);
centerDot.parent = this.reticleGroup;
centerDot.material = material;
centerDot.renderingGroupId = this.config.renderingGroupId;
this.crosshairLines.push(centerDot);
}
/**
* Set visibility of the sight
*/
public setVisible(visible: boolean): void {
this.circle.isVisible = visible;
this.crosshairLines.forEach(line => line.isVisible = visible);
}
/**
* Change the sight color
*/
public setColor(color: Color3): void {
this.config.color = color;
const material = this.circle.material as StandardMaterial;
if (material) {
material.emissiveColor = color;
}
}
/**
* Get the reticle group transform node
*/
public getTransformNode(): TransformNode {
return this.reticleGroup;
}
/**
* Dispose of the sight
*/
public dispose(): void {
this.circle.dispose();
this.crosshairLines.forEach(line => line.dispose());
this.reticleGroup.dispose();
}
}