Refactor sight to crosshair reticle and rename starfield to rockFactory
Some checks failed
Build / build (push) Failing after 19s
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:
parent
9df64b7dd9
commit
cb96b4ea6c
@ -119,7 +119,7 @@ src/
|
|||||||
level.ts - Level interface
|
level.ts - Level interface
|
||||||
level1.ts - Main game level implementation
|
level1.ts - Main game level implementation
|
||||||
ship.ts - Player ship, controls, weapons
|
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
|
scoreboard.ts - In-cockpit HUD display
|
||||||
createSun.ts - Sun mesh generation
|
createSun.ts - Sun mesh generation
|
||||||
createPlanets.ts - Procedural planet generation
|
createPlanets.ts - Procedural planet generation
|
||||||
|
|||||||
BIN
public/asteroid3.blend
Normal file
BIN
public/asteroid3.blend
Normal file
Binary file not shown.
BIN
public/asteroid3.glb
Normal file
BIN
public/asteroid3.glb
Normal file
Binary file not shown.
@ -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
|
* Configuration options for background stars
|
||||||
@ -95,11 +95,12 @@ export class BackgroundStars {
|
|||||||
const mesh = this.pcs.mesh;
|
const mesh = this.pcs.mesh;
|
||||||
if (mesh) {
|
if (mesh) {
|
||||||
// Stars should not receive lighting
|
// Stars should not receive lighting
|
||||||
mesh.material.disableLighting = true;
|
const mat = (mesh.material as StandardMaterial)
|
||||||
mesh.material.emissiveColor = new Color3(1,1,1);
|
mat.disableLighting = true;
|
||||||
|
mat.emissiveColor = new Color3(1,1,1);
|
||||||
|
|
||||||
// Disable depth write so stars don't occlude other objects
|
// Disable depth write so stars don't occlude other objects
|
||||||
mesh.material.disableDepthWrite = true;
|
mat.disableDepthWrite = true;
|
||||||
|
|
||||||
// Stars should be in the background
|
// Stars should be in the background
|
||||||
mesh.renderingGroupId = 0;
|
mesh.renderingGroupId = 0;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
import type {AudioEngineV2} from "@babylonjs/core";
|
import type {AudioEngineV2} from "@babylonjs/core";
|
||||||
import {Ship} from "./ship";
|
import {Ship} from "./ship";
|
||||||
|
|
||||||
import {RockFactory} from "./starfield";
|
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";
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import { DefaultScene } from "./defaultScene";
|
import { DefaultScene } from "./defaultScene";
|
||||||
import { RockFactory } from "./starfield";
|
import { RockFactory } from "./rockFactory";
|
||||||
import { ScoreEvent } from "./scoreboard";
|
import { ScoreEvent } from "./scoreboard";
|
||||||
import {
|
import {
|
||||||
LevelConfig,
|
LevelConfig,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import {Level1} from "./level1";
|
|||||||
import Demo from "./demo";
|
import Demo from "./demo";
|
||||||
import Level from "./level";
|
import Level from "./level";
|
||||||
import setLoadingMessage from "./setLoadingMessage";
|
import setLoadingMessage from "./setLoadingMessage";
|
||||||
import {RockFactory} from "./starfield";
|
import {RockFactory} from "./rockFactory";
|
||||||
import {ControllerDebug} from "./controllerDebug";
|
import {ControllerDebug} from "./controllerDebug";
|
||||||
import {router, showView} from "./router";
|
import {router, showView} from "./router";
|
||||||
import {populateLevelSelector, hasSavedLevels} from "./levelSelector";
|
import {populateLevelSelector, hasSavedLevels} from "./levelSelector";
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export class RockFactory {
|
|||||||
}
|
}
|
||||||
private static async loadMesh() {
|
private static async loadMesh() {
|
||||||
console.log('loading mesh');
|
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 = importMesh.meshes[1].clone("asteroid", null, false);
|
||||||
this._rockMesh.setParent(null);
|
this._rockMesh.setParent(null);
|
||||||
this._rockMesh.setEnabled(false);
|
this._rockMesh.setEnabled(false);
|
||||||
34
src/ship.ts
34
src/ship.ts
@ -22,6 +22,7 @@ import {
|
|||||||
import type {AudioEngineV2, StaticSound} from "@babylonjs/core";
|
import type {AudioEngineV2, StaticSound} from "@babylonjs/core";
|
||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
import { GameConfig } from "./gameConfig";
|
import { GameConfig } from "./gameConfig";
|
||||||
|
import { Sight } from "./sight";
|
||||||
const MAX_FORWARD_THRUST = 40;
|
const MAX_FORWARD_THRUST = 40;
|
||||||
|
|
||||||
const controllerComponents = [
|
const controllerComponents = [
|
||||||
@ -69,6 +70,7 @@ export class Ship {
|
|||||||
private _controllerMode: ControllerStickMode;
|
private _controllerMode: ControllerStickMode;
|
||||||
private _active = false;
|
private _active = false;
|
||||||
private _audioEngine: AudioEngineV2;
|
private _audioEngine: AudioEngineV2;
|
||||||
|
private _sight: Sight;
|
||||||
constructor(mode: ControllerStickMode = ControllerStickMode.BEGINNER, audioEngine?: AudioEngineV2) {
|
constructor(mode: ControllerStickMode = ControllerStickMode.BEGINNER, audioEngine?: AudioEngineV2) {
|
||||||
this._controllerMode = mode;
|
this._controllerMode = mode;
|
||||||
this._audioEngine = audioEngine;
|
this._audioEngine = audioEngine;
|
||||||
@ -154,7 +156,7 @@ export class Ship {
|
|||||||
}
|
}
|
||||||
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
|
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
|
||||||
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0);
|
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.material = this._ammoMaterial;
|
||||||
this._ammoBaseMesh.setEnabled(false);
|
this._ammoBaseMesh.setEnabled(false);
|
||||||
|
|
||||||
@ -180,17 +182,17 @@ export class Ship {
|
|||||||
|
|
||||||
DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
|
DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
|
||||||
|
|
||||||
//const sightPos = this._forwardNode.position.scale(30);
|
// Create sight reticle
|
||||||
const sight = MeshBuilder.CreateDisc("sight", {radius: 2 }, DefaultScene.MainScene);
|
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;
|
let i = 0;
|
||||||
DefaultScene.MainScene.onBeforeRenderObservable.add(() => {
|
DefaultScene.MainScene.onBeforeRenderObservable.add(() => {
|
||||||
if (i++ % 10 == 0) {
|
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 {
|
function decrementValue(value: number, increment: number = .8): number {
|
||||||
if (Math.abs(value) < .01) {
|
if (Math.abs(value) < .01) {
|
||||||
|
|||||||
188
src/sight.ts
Normal file
188
src/sight.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user