Add GameConfig system with texture quality levels and physics toggle
Some checks failed
Build / build (push) Failing after 18s
Some checks failed
Build / build (push) Failing after 18s
Created GameConfig singleton class with localStorage persistence for game settings: - Texture quality levels: WIREFRAME, SIMPLE_MATERIAL, FULL_TEXTURE, PBR_TEXTURE - Physics enable/disable toggle for performance optimization Created MaterialFactory for quality-level-based material generation: - Planet materials with dynamic sun-oriented lightmaps - Asteroid materials preserving GLB bump textures - Sun materials with procedural fire textures Integrated GameConfig throughout game entities: - Conditional physics creation in asteroids, ship, start base - Material creation respects texture quality settings - Physics constraints only applied when physics enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
03f170e150
commit
181a427875
87
src/gameConfig.ts
Normal file
87
src/gameConfig.ts
Normal file
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Texture detail levels for game objects
|
||||
*/
|
||||
export enum TextureLevel {
|
||||
WIREFRAME = 'WIREFRAME',
|
||||
SIMPLE_MATERIAL = 'SIMPLE_MATERIAL',
|
||||
FULL_TEXTURE = 'FULL_TEXTURE',
|
||||
PBR_TEXTURE = 'PBR_TEXTURE'
|
||||
}
|
||||
|
||||
/**
|
||||
* Global game configuration settings
|
||||
* Singleton class for managing game-wide settings
|
||||
*/
|
||||
export class GameConfig {
|
||||
private static _instance: GameConfig;
|
||||
|
||||
// Texture detail settings
|
||||
public planetTextureLevel: TextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
public asteroidTextureLevel: TextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
public sunTextureLevel: TextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
|
||||
// Physics settings
|
||||
public physicsEnabled: boolean = true;
|
||||
|
||||
/**
|
||||
* Private constructor for singleton pattern
|
||||
*/
|
||||
private constructor() {
|
||||
// Load settings from localStorage if available
|
||||
this.loadFromStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*/
|
||||
public static getInstance(): GameConfig {
|
||||
if (!GameConfig._instance) {
|
||||
GameConfig._instance = new GameConfig();
|
||||
}
|
||||
return GameConfig._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current configuration to localStorage
|
||||
*/
|
||||
public save(): void {
|
||||
const config = {
|
||||
planetTextureLevel: this.planetTextureLevel,
|
||||
asteroidTextureLevel: this.asteroidTextureLevel,
|
||||
sunTextureLevel: this.sunTextureLevel,
|
||||
physicsEnabled: this.physicsEnabled
|
||||
};
|
||||
localStorage.setItem('game-config', JSON.stringify(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from localStorage
|
||||
*/
|
||||
private loadFromStorage(): void {
|
||||
try {
|
||||
const stored = localStorage.getItem('game-config');
|
||||
if (stored) {
|
||||
const config = JSON.parse(stored);
|
||||
this.planetTextureLevel = config.planetTextureLevel ?? TextureLevel.FULL_TEXTURE;
|
||||
this.asteroidTextureLevel = config.asteroidTextureLevel ?? TextureLevel.FULL_TEXTURE;
|
||||
this.sunTextureLevel = config.sunTextureLevel ?? TextureLevel.FULL_TEXTURE;
|
||||
this.physicsEnabled = config.physicsEnabled ?? true;
|
||||
} else {
|
||||
this.save();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load game config from localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to default settings
|
||||
*/
|
||||
public reset(): void {
|
||||
this.planetTextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
this.asteroidTextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
this.sunTextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
this.physicsEnabled = true;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
@ -117,16 +117,18 @@ export class Level1 implements Level {
|
||||
const shipConfig = this._deserializer.getShipConfig();
|
||||
this._ship.position = new Vector3(shipConfig.position[0], shipConfig.position[1], shipConfig.position[2]);
|
||||
|
||||
// Add distance constraints to asteroids
|
||||
// Add distance constraints to asteroids (if physics enabled)
|
||||
setLoadingMessage("Configuring physics constraints...");
|
||||
const asteroidMeshes = entities.asteroids;
|
||||
for (let i = 0; i < asteroidMeshes.length; i++) {
|
||||
const asteroidMesh = asteroidMeshes[i];
|
||||
if (asteroidMesh.physicsBody) {
|
||||
// Calculate distance from start base
|
||||
const dist = Vector3.Distance(asteroidMesh.position, this._startBase.position);
|
||||
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
|
||||
this._startBase.physicsBody.addConstraint(asteroidMesh.physicsBody, constraint);
|
||||
if (this._startBase.physicsBody) {
|
||||
for (let i = 0; i < asteroidMeshes.length; i++) {
|
||||
const asteroidMesh = asteroidMeshes[i];
|
||||
if (asteroidMesh.physicsBody) {
|
||||
// Calculate distance from start base
|
||||
const dist = Vector3.Distance(asteroidMesh.position, this._startBase.position);
|
||||
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
|
||||
this._startBase.physicsBody.addConstraint(asteroidMesh.physicsBody, constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,8 @@ import {
|
||||
} from "./levelConfig";
|
||||
import { FireProceduralTexture } from "@babylonjs/procedural-textures";
|
||||
import {createSphereLightmap} from "./sphereLightmap";
|
||||
import { GameConfig } from "./gameConfig";
|
||||
import { MaterialFactory } from "./materialFactory";
|
||||
|
||||
/**
|
||||
* Deserializes a LevelConfig JSON object and creates all entities in the scene
|
||||
@ -92,8 +94,12 @@ export class LevelDeserializer {
|
||||
}
|
||||
mesh.material = material;
|
||||
|
||||
const agg = new PhysicsAggregate(mesh, PhysicsShapeType.CONVEX_HULL, { mass: 0 }, this.scene);
|
||||
agg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
// Only create physics if enabled in config
|
||||
const gameConfig = GameConfig.getInstance();
|
||||
if (gameConfig.physicsEnabled) {
|
||||
const agg = new PhysicsAggregate(mesh, PhysicsShapeType.CONVEX_HULL, { mass: 0 }, this.scene);
|
||||
agg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
@ -116,11 +122,13 @@ export class LevelDeserializer {
|
||||
|
||||
sun.position = this.arrayToVector3(config.position);
|
||||
|
||||
// Create material with procedural fire texture
|
||||
const material = new StandardMaterial("sunMaterial", this.scene);
|
||||
material.emissiveTexture = new FireProceduralTexture("fire", 1024, this.scene);
|
||||
material.emissiveColor = new Color3(0.5, 0.5, 0.1);
|
||||
material.disableLighting = true;
|
||||
// Create material using GameConfig texture level
|
||||
const gameConfig = GameConfig.getInstance();
|
||||
const material = MaterialFactory.createSunMaterial(
|
||||
"sunMaterial",
|
||||
gameConfig.sunTextureLevel,
|
||||
this.scene
|
||||
);
|
||||
sun.material = material;
|
||||
|
||||
// Create glow layer
|
||||
@ -151,33 +159,16 @@ export class LevelDeserializer {
|
||||
// Calculate direction from planet to sun
|
||||
const toSun = sunPosition.subtract(planetPosition).normalize();
|
||||
|
||||
// Apply texture
|
||||
const material = new StandardMaterial(planetConfig.name + "-material", this.scene);
|
||||
const texture = new Texture(planetConfig.texturePath, this.scene);
|
||||
|
||||
// Create lightmap with bright light pointing toward sun
|
||||
const lightmap = createSphereLightmap(
|
||||
planetConfig.name + "-lightmap",
|
||||
256, // texture size
|
||||
DefaultScene.MainScene,
|
||||
toSun, // bright light from sun direction
|
||||
1, // bright intensity
|
||||
toSun.negate(), // dim light from opposite direction
|
||||
0.3, // dim intensity
|
||||
0.3 // ambient
|
||||
// Create material using GameConfig texture level
|
||||
const config = GameConfig.getInstance();
|
||||
const material = MaterialFactory.createPlanetMaterial(
|
||||
planetConfig.name + "-material",
|
||||
planetConfig.texturePath,
|
||||
config.planetTextureLevel,
|
||||
this.scene,
|
||||
toSun
|
||||
);
|
||||
|
||||
// Apply to material
|
||||
// Use emissiveTexture (self-lit) instead of diffuseTexture when lighting is disabled
|
||||
material.emissiveTexture = texture;
|
||||
material.lightmapTexture = lightmap;
|
||||
material.useLightmapAsShadowmap = true;
|
||||
|
||||
// Disable standard lighting since we're using baked lightmap
|
||||
material.disableLighting = true;
|
||||
|
||||
material.roughness = 1;
|
||||
material.specularColor = Color3.Black();
|
||||
planet.material = material;
|
||||
|
||||
planets.push(planet);
|
||||
|
||||
304
src/materialFactory.ts
Normal file
304
src/materialFactory.ts
Normal file
@ -0,0 +1,304 @@
|
||||
import {
|
||||
Color3,
|
||||
DynamicTexture,
|
||||
NoiseProceduralTexture,
|
||||
PBRMaterial,
|
||||
Scene,
|
||||
StandardMaterial,
|
||||
Texture,
|
||||
Vector3
|
||||
} from "@babylonjs/core";
|
||||
import { TextureLevel } from "./gameConfig";
|
||||
import { FireProceduralTexture } from "@babylonjs/procedural-textures";
|
||||
import { createSphereLightmap } from "./sphereLightmap";
|
||||
|
||||
/**
|
||||
* Factory for creating materials at different quality levels
|
||||
*/
|
||||
export class MaterialFactory {
|
||||
/**
|
||||
* Create a planet material based on texture level
|
||||
*/
|
||||
public static createPlanetMaterial(
|
||||
name: string,
|
||||
texturePath: string,
|
||||
textureLevel: TextureLevel,
|
||||
scene: Scene,
|
||||
sunDirection: Vector3
|
||||
): StandardMaterial | PBRMaterial {
|
||||
switch (textureLevel) {
|
||||
case TextureLevel.WIREFRAME:
|
||||
return this.createWireframeMaterial(name, scene, new Color3(0.5, 0.5, 0.8));
|
||||
|
||||
case TextureLevel.SIMPLE_MATERIAL:
|
||||
return this.createSimplePlanetMaterial(name, scene);
|
||||
|
||||
case TextureLevel.FULL_TEXTURE:
|
||||
return this.createFullTexturePlanetMaterial(name, texturePath, scene, sunDirection);
|
||||
|
||||
case TextureLevel.PBR_TEXTURE:
|
||||
return this.createPBRPlanetMaterial(name, texturePath, scene, sunDirection);
|
||||
|
||||
default:
|
||||
return this.createFullTexturePlanetMaterial(name, texturePath, scene, sunDirection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an asteroid material based on texture level
|
||||
*/
|
||||
public static createAsteroidMaterial(
|
||||
name: string,
|
||||
textureLevel: TextureLevel,
|
||||
scene: Scene,
|
||||
originalMaterial?: PBRMaterial
|
||||
): StandardMaterial | PBRMaterial {
|
||||
switch (textureLevel) {
|
||||
case TextureLevel.WIREFRAME:
|
||||
return this.createWireframeMaterial(name, scene, new Color3(0.5, 0.5, 0.5));
|
||||
|
||||
case TextureLevel.SIMPLE_MATERIAL:
|
||||
return this.createSimpleAsteroidMaterial(name, scene);
|
||||
|
||||
case TextureLevel.FULL_TEXTURE:
|
||||
return this.createFullTextureAsteroidMaterial(name, scene, originalMaterial);
|
||||
|
||||
case TextureLevel.PBR_TEXTURE:
|
||||
return this.createPBRAsteroidMaterial(name, scene, originalMaterial);
|
||||
|
||||
default:
|
||||
return this.createFullTextureAsteroidMaterial(name, scene, originalMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sun material based on texture level
|
||||
*/
|
||||
public static createSunMaterial(
|
||||
name: string,
|
||||
textureLevel: TextureLevel,
|
||||
scene: Scene
|
||||
): StandardMaterial | PBRMaterial {
|
||||
switch (textureLevel) {
|
||||
case TextureLevel.WIREFRAME:
|
||||
return this.createWireframeMaterial(name, scene, new Color3(1, 1, 0));
|
||||
|
||||
case TextureLevel.SIMPLE_MATERIAL:
|
||||
return this.createSimpleSunMaterial(name, scene);
|
||||
|
||||
case TextureLevel.FULL_TEXTURE:
|
||||
return this.createFullTextureSunMaterial(name, scene);
|
||||
|
||||
case TextureLevel.PBR_TEXTURE:
|
||||
return this.createPBRSunMaterial(name, scene);
|
||||
|
||||
default:
|
||||
return this.createFullTextureSunMaterial(name, scene);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Private helper methods ==========
|
||||
|
||||
/**
|
||||
* Create wireframe material
|
||||
*/
|
||||
private static createWireframeMaterial(
|
||||
name: string,
|
||||
scene: Scene,
|
||||
color: Color3
|
||||
): StandardMaterial {
|
||||
const material = new StandardMaterial(name, scene);
|
||||
material.wireframe = true;
|
||||
material.emissiveColor = color;
|
||||
material.disableLighting = true;
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create simple planet material with solid color
|
||||
*/
|
||||
private static createSimplePlanetMaterial(name: string, scene: Scene): StandardMaterial {
|
||||
const material = new StandardMaterial(name, scene);
|
||||
material.diffuseColor = new Color3(0.4, 0.6, 0.8);
|
||||
material.specularColor = Color3.Black();
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create full texture planet material (current implementation)
|
||||
*/
|
||||
private static createFullTexturePlanetMaterial(
|
||||
name: string,
|
||||
texturePath: string,
|
||||
scene: Scene,
|
||||
sunDirection: Vector3
|
||||
): StandardMaterial {
|
||||
const material = new StandardMaterial(name, scene);
|
||||
const texture = new Texture(texturePath, scene);
|
||||
|
||||
// Create lightmap with bright light pointing toward sun
|
||||
const lightmap = createSphereLightmap(
|
||||
name + "-lightmap",
|
||||
256,
|
||||
scene,
|
||||
sunDirection,
|
||||
1,
|
||||
sunDirection.negate(),
|
||||
0.3,
|
||||
0.3
|
||||
);
|
||||
|
||||
material.emissiveTexture = texture;
|
||||
material.lightmapTexture = lightmap;
|
||||
material.useLightmapAsShadowmap = true;
|
||||
material.disableLighting = true;
|
||||
material.roughness = 1;
|
||||
material.specularColor = Color3.Black();
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create PBR planet material
|
||||
*/
|
||||
private static createPBRPlanetMaterial(
|
||||
name: string,
|
||||
texturePath: string,
|
||||
scene: Scene,
|
||||
sunDirection: Vector3
|
||||
): PBRMaterial {
|
||||
const material = new PBRMaterial(name, scene);
|
||||
const texture = new Texture(texturePath, scene);
|
||||
|
||||
// Create lightmap with bright light pointing toward sun
|
||||
const lightmap = createSphereLightmap(
|
||||
name + "-lightmap",
|
||||
256,
|
||||
scene,
|
||||
sunDirection,
|
||||
1,
|
||||
sunDirection.negate(),
|
||||
0.3,
|
||||
0.3
|
||||
);
|
||||
|
||||
material.albedoTexture = texture;
|
||||
material.lightmapTexture = lightmap;
|
||||
material.useLightmapAsShadowmap = true;
|
||||
material.roughness = 0.8;
|
||||
material.metallic = 0;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create simple asteroid material with solid color
|
||||
*/
|
||||
private static createSimpleAsteroidMaterial(name: string, scene: Scene): StandardMaterial {
|
||||
const material = new StandardMaterial(name, scene);
|
||||
material.diffuseColor = new Color3(0.4, 0.4, 0.4);
|
||||
material.specularColor = Color3.Black();
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create full texture asteroid material (current implementation)
|
||||
*/
|
||||
private static createFullTextureAsteroidMaterial(name: string, scene: Scene, originalMaterial?: PBRMaterial): StandardMaterial {
|
||||
// If we have the original material from GLB, use it as a base
|
||||
if (originalMaterial) {
|
||||
// Clone the original material to preserve bump texture and other properties
|
||||
const material = originalMaterial.clone(name) as PBRMaterial;
|
||||
|
||||
// Create noise texture for color variation
|
||||
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
|
||||
noiseTexture.brightness = 0.6;
|
||||
noiseTexture.octaves = 4;
|
||||
|
||||
// Replace only the albedo texture, keeping bump and other textures
|
||||
material.albedoTexture = noiseTexture;
|
||||
material.roughness = 1;
|
||||
|
||||
return material as any as StandardMaterial;
|
||||
}
|
||||
|
||||
// Fallback if no original material
|
||||
const material = new StandardMaterial(name, scene);
|
||||
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
|
||||
noiseTexture.brightness = 0.6;
|
||||
noiseTexture.octaves = 4;
|
||||
|
||||
material.ambientTexture = noiseTexture;
|
||||
material.diffuseTexture = noiseTexture;
|
||||
material.roughness = 1;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create PBR asteroid material
|
||||
*/
|
||||
private static createPBRAsteroidMaterial(name: string, scene: Scene, originalMaterial?: PBRMaterial): PBRMaterial {
|
||||
// If we have the original material from GLB, use it as a base
|
||||
if (originalMaterial) {
|
||||
// Clone the original material to preserve bump texture and other properties
|
||||
const material = originalMaterial.clone(name) as PBRMaterial;
|
||||
|
||||
// Create noise texture for color variation
|
||||
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
|
||||
noiseTexture.brightness = 0.6;
|
||||
noiseTexture.octaves = 4;
|
||||
|
||||
// Replace only the albedo texture, keeping bump and other textures
|
||||
material.albedoTexture = noiseTexture;
|
||||
material.roughness = 1;
|
||||
material.metallic = 0;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
// Fallback if no original material
|
||||
const material = new PBRMaterial(name, scene);
|
||||
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
|
||||
noiseTexture.brightness = 0.6;
|
||||
noiseTexture.octaves = 4;
|
||||
|
||||
material.albedoTexture = noiseTexture;
|
||||
material.roughness = 1;
|
||||
material.metallic = 0;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create simple sun material with solid color
|
||||
*/
|
||||
private static createSimpleSunMaterial(name: string, scene: Scene): StandardMaterial {
|
||||
const material = new StandardMaterial(name, scene);
|
||||
material.emissiveColor = new Color3(1, 0.9, 0.2);
|
||||
material.disableLighting = true;
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create full texture sun material (current implementation)
|
||||
*/
|
||||
private static createFullTextureSunMaterial(name: string, scene: Scene): StandardMaterial {
|
||||
const material = new StandardMaterial(name, scene);
|
||||
material.emissiveTexture = new FireProceduralTexture("fire", 1024, scene);
|
||||
material.emissiveColor = new Color3(0.5, 0.5, 0.1);
|
||||
material.disableLighting = true;
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create PBR sun material
|
||||
*/
|
||||
private static createPBRSunMaterial(name: string, scene: Scene): PBRMaterial {
|
||||
const material = new PBRMaterial(name, scene);
|
||||
material.emissiveTexture = new FireProceduralTexture("fire", 1024, scene);
|
||||
material.emissiveColor = new Color3(0.5, 0.5, 0.1);
|
||||
material.unlit = true;
|
||||
return material;
|
||||
}
|
||||
}
|
||||
66
src/ship.ts
66
src/ship.ts
@ -21,6 +21,7 @@ import {
|
||||
} from "@babylonjs/core";
|
||||
import type {AudioEngineV2, StaticSound} from "@babylonjs/core";
|
||||
import {DefaultScene} from "./defaultScene";
|
||||
import { GameConfig } from "./gameConfig";
|
||||
const MAX_FORWARD_THRUST = 40;
|
||||
|
||||
const controllerComponents = [
|
||||
@ -97,6 +98,12 @@ export class Ship {
|
||||
}
|
||||
|
||||
private shoot() {
|
||||
// Only allow shooting if physics is enabled
|
||||
const config = GameConfig.getInstance();
|
||||
if (!config.physicsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._shot?.play();
|
||||
const ammo = new InstancedMesh("ammo", this._ammoBaseMesh as Mesh);
|
||||
ammo.parent = this._ship;
|
||||
@ -202,38 +209,41 @@ export class Ship {
|
||||
shipMesh.name = "shipMesh";
|
||||
shipMesh.parent = this._ship;
|
||||
|
||||
// Create physics aggregate based on the loaded mesh
|
||||
// Find the actual geometry mesh (usually meshes[1] or a child)
|
||||
//const geometryMesh = importMesh.meshes.find(m => m instanceof Mesh && m.getTotalVertices() > 0) as Mesh;
|
||||
const geo = shipMesh.getChildMeshes()[0]
|
||||
if (geo) {
|
||||
// Create physics aggregate based on the loaded mesh (if physics enabled)
|
||||
const config = GameConfig.getInstance();
|
||||
if (config.physicsEnabled) {
|
||||
// Find the actual geometry mesh (usually meshes[1] or a child)
|
||||
//const geometryMesh = importMesh.meshes.find(m => m instanceof Mesh && m.getTotalVertices() > 0) as Mesh;
|
||||
const geo = shipMesh.getChildMeshes()[0]
|
||||
if (geo) {
|
||||
|
||||
|
||||
// Create physics aggregate on the ship TransformNode using the mesh shape
|
||||
const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.CONVEX_HULL, {
|
||||
mass: 100,
|
||||
mesh: (geo as Mesh) // Use the actual ship geometry
|
||||
}, DefaultScene.MainScene);
|
||||
// Create physics aggregate on the ship TransformNode using the mesh shape
|
||||
const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.CONVEX_HULL, {
|
||||
mass: 100,
|
||||
mesh: (geo as Mesh) // Use the actual ship geometry
|
||||
}, DefaultScene.MainScene);
|
||||
|
||||
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
agg.body.setLinearDamping(.1);
|
||||
agg.body.setAngularDamping(.2);
|
||||
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
|
||||
agg.body.setCollisionCallbackEnabled(true);
|
||||
} else {
|
||||
console.warn("No geometry mesh found in ship1.glb, falling back to box shape");
|
||||
// Fallback to box shape if mesh not found
|
||||
const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.BOX, {
|
||||
mass: 100,
|
||||
extents: new Vector3(4, 4, 7.4),
|
||||
center: new Vector3(0, 1, 1.8)
|
||||
}, DefaultScene.MainScene);
|
||||
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
agg.body.setLinearDamping(.1);
|
||||
agg.body.setAngularDamping(.2);
|
||||
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
|
||||
agg.body.setCollisionCallbackEnabled(true);
|
||||
} else {
|
||||
console.warn("No geometry mesh found in ship1.glb, falling back to box shape");
|
||||
// Fallback to box shape if mesh not found
|
||||
const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.BOX, {
|
||||
mass: 100,
|
||||
extents: new Vector3(4, 4, 7.4),
|
||||
center: new Vector3(0, 1, 1.8)
|
||||
}, DefaultScene.MainScene);
|
||||
|
||||
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
agg.body.setLinearDamping(.1);
|
||||
agg.body.setAngularDamping(.2);
|
||||
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
|
||||
agg.body.setCollisionCallbackEnabled(true);
|
||||
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
agg.body.setLinearDamping(.1);
|
||||
agg.body.setAngularDamping(.2);
|
||||
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
|
||||
agg.body.setCollisionCallbackEnabled(true);
|
||||
}
|
||||
}
|
||||
//shipMesh.rotation.y = Angle.FromDegrees(90).radians();
|
||||
//shipMesh.rotation.y = Math.PI;
|
||||
|
||||
162
src/starfield.ts
162
src/starfield.ts
@ -17,6 +17,8 @@ import {DefaultScene} from "./defaultScene";
|
||||
import {ScoreEvent} from "./scoreboard";
|
||||
import {Debug} from "@babylonjs/core/Legacy/legacy";
|
||||
import {createSphereLightmap} from "./sphereLightmap";
|
||||
import { GameConfig } from "./gameConfig";
|
||||
import { MaterialFactory } from "./materialFactory";
|
||||
let _particleData: any = null;
|
||||
|
||||
export class Rock {
|
||||
@ -24,8 +26,8 @@ export class Rock {
|
||||
constructor(mesh: AbstractMesh) {
|
||||
this._rockMesh = mesh;
|
||||
}
|
||||
public get physicsBody(): PhysicsBody {
|
||||
return this._rockMesh.physicsBody;
|
||||
public get physicsBody(): PhysicsBody | null {
|
||||
return this._rockMesh.physicsBody || null;
|
||||
}
|
||||
public get position(): Vector3 {
|
||||
return this._rockMesh.getAbsolutePosition();
|
||||
@ -35,6 +37,7 @@ export class Rock {
|
||||
export class RockFactory {
|
||||
private static _rockMesh: AbstractMesh;
|
||||
private static _rockMaterial: PBRMaterial;
|
||||
private static _originalMaterial: PBRMaterial = null;
|
||||
private static _explosionPool: ParticleSystemSet[] = [];
|
||||
private static _poolSize: number = 10;
|
||||
private static _viewer: PhysicsViewer = null;
|
||||
@ -64,18 +67,20 @@ export class RockFactory {
|
||||
//importMesh.meshes[1].dispose();
|
||||
console.log(importMesh.meshes);
|
||||
if (!this._rockMaterial) {
|
||||
this._rockMaterial = this._rockMesh.material.clone("asteroid") as PBRMaterial;
|
||||
// Clone the original material from GLB to preserve all textures
|
||||
this._originalMaterial = this._rockMesh.material.clone("asteroid-original") as PBRMaterial;
|
||||
console.log('Cloned original material from GLB:', this._originalMaterial);
|
||||
|
||||
this._rockMaterial.name = 'asteroid-material';
|
||||
this._rockMaterial.id = 'asteroid-material';
|
||||
const material = (this._rockMaterial as PBRMaterial)
|
||||
const noiseTexture = new NoiseProceduralTexture("asteroid-noise", 256, DefaultScene.MainScene);
|
||||
noiseTexture.brightness = 0.6; // Brighter base color
|
||||
noiseTexture.octaves = 4; // More detaila
|
||||
material.albedoTexture = noiseTexture;
|
||||
material.roughness = 1;
|
||||
// Create material using GameConfig texture level
|
||||
const config = GameConfig.getInstance();
|
||||
this._rockMaterial = MaterialFactory.createAsteroidMaterial(
|
||||
'asteroid-material',
|
||||
config.asteroidTextureLevel,
|
||||
DefaultScene.MainScene,
|
||||
this._originalMaterial
|
||||
) as PBRMaterial;
|
||||
|
||||
this._rockMesh.material = material;
|
||||
this._rockMesh.material = this._rockMaterial;
|
||||
importMesh.meshes[1].dispose(false, true);
|
||||
importMesh.meshes[0].dispose();
|
||||
}
|
||||
@ -104,84 +109,89 @@ export class RockFactory {
|
||||
rock.metadata = {type: 'asteroid'};
|
||||
rock.setEnabled(true);
|
||||
|
||||
// PhysicsAggregate will automatically compute sphere size from mesh bounding info
|
||||
// The mesh scaling is already applied, so Babylon will create correctly sized physics shape
|
||||
const agg = new PhysicsAggregate(rock, PhysicsShapeType.SPHERE, {
|
||||
mass: 10000,
|
||||
restitution: .5
|
||||
// Don't pass radius - let Babylon compute from scaled mesh bounds
|
||||
}, DefaultScene.MainScene);
|
||||
const body = agg.body;
|
||||
// Only create physics if enabled in config
|
||||
const config = GameConfig.getInstance();
|
||||
if (config.physicsEnabled) {
|
||||
// PhysicsAggregate will automatically compute sphere size from mesh bounding info
|
||||
// The mesh scaling is already applied, so Babylon will create correctly sized physics shape
|
||||
const agg = new PhysicsAggregate(rock, PhysicsShapeType.SPHERE, {
|
||||
mass: 10000,
|
||||
restitution: .5
|
||||
// Don't pass radius - let Babylon compute from scaled mesh bounds
|
||||
}, DefaultScene.MainScene);
|
||||
const body = agg.body;
|
||||
|
||||
if (!this._viewer) {
|
||||
// this._viewer = new PhysicsViewer(DefaultScene.MainScene);
|
||||
}
|
||||
if (!this._viewer) {
|
||||
// this._viewer = new PhysicsViewer(DefaultScene.MainScene);
|
||||
}
|
||||
|
||||
// this._viewer.showBody(body);
|
||||
body.setLinearDamping(0)
|
||||
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
body.setCollisionCallbackEnabled(true);
|
||||
let scaling = Vector3.One();
|
||||
body.getCollisionObservable().add((eventData) => {
|
||||
if (eventData.type == 'COLLISION_STARTED') {
|
||||
if ( eventData.collidedAgainst.transformNode.id == 'ammo') {
|
||||
score.notifyObservers({score: 1, remaining: -1, message: "Asteroid Destroyed"});
|
||||
const position = eventData.point;
|
||||
// this._viewer.showBody(body);
|
||||
body.setLinearDamping(0)
|
||||
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
body.setCollisionCallbackEnabled(true);
|
||||
let scaling = Vector3.One();
|
||||
body.getCollisionObservable().add((eventData) => {
|
||||
if (eventData.type == 'COLLISION_STARTED') {
|
||||
if ( eventData.collidedAgainst.transformNode.id == 'ammo') {
|
||||
score.notifyObservers({score: 1, remaining: -1, message: "Asteroid Destroyed"});
|
||||
const position = eventData.point;
|
||||
|
||||
eventData.collider.shape.dispose();
|
||||
eventData.collider.transformNode.dispose();
|
||||
eventData.collider.dispose();
|
||||
scaling = eventData.collider.transformNode.scaling.clone();
|
||||
console.log(scaling);
|
||||
eventData.collidedAgainst.shape.dispose();
|
||||
eventData.collidedAgainst.transformNode.dispose();
|
||||
eventData.collidedAgainst.dispose();
|
||||
eventData.collider.shape.dispose();
|
||||
eventData.collider.transformNode.dispose();
|
||||
eventData.collider.dispose();
|
||||
scaling = eventData.collider.transformNode.scaling.clone();
|
||||
console.log(scaling);
|
||||
eventData.collidedAgainst.shape.dispose();
|
||||
eventData.collidedAgainst.transformNode.dispose();
|
||||
eventData.collidedAgainst.dispose();
|
||||
|
||||
// Get explosion from pool (or create new if pool empty)
|
||||
let explosion = RockFactory.getExplosionFromPool();
|
||||
// Get explosion from pool (or create new if pool empty)
|
||||
let explosion = RockFactory.getExplosionFromPool();
|
||||
|
||||
if (!explosion) {
|
||||
console.log("Pool empty, creating new explosion");
|
||||
ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene).then((set) => {
|
||||
const point = MeshBuilder.CreateSphere("point", {diameter: 0.1}, DefaultScene.MainScene);
|
||||
if (!explosion) {
|
||||
console.log("Pool empty, creating new explosion");
|
||||
ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene).then((set) => {
|
||||
const point = MeshBuilder.CreateSphere("point", {diameter: 0.1}, DefaultScene.MainScene);
|
||||
point.position = position.clone();
|
||||
point.isVisible = false;
|
||||
|
||||
set.start(point);
|
||||
|
||||
setTimeout(() => {
|
||||
set.dispose();
|
||||
point.dispose();
|
||||
}, 2000);
|
||||
});
|
||||
} else {
|
||||
// Use pooled explosion
|
||||
const point = MeshBuilder.CreateSphere("point", {diameter: 10}, DefaultScene.MainScene);
|
||||
point.position = position.clone();
|
||||
point.isVisible = false;
|
||||
point.scaling = scaling.multiplyByFloats(.2,.3,.2);
|
||||
console.log("Using pooled explosion with", explosion.systems.length, "systems at", position);
|
||||
|
||||
set.start(point);
|
||||
// Set emitter and start each system individually
|
||||
explosion.systems.forEach((system: ParticleSystem, idx: number) => {
|
||||
system.emitter = point; // Set emitter to the collision point
|
||||
system.start(); // Start this specific system
|
||||
console.log(` System ${idx}: emitter set to`, system.emitter, "activeCount=", system.getActiveCount());
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
set.dispose();
|
||||
explosion.systems.forEach((system: ParticleSystem) => {
|
||||
system.stop();
|
||||
});
|
||||
RockFactory.returnExplosionToPool(explosion);
|
||||
point.dispose();
|
||||
}, 2000);
|
||||
});
|
||||
} else {
|
||||
// Use pooled explosion
|
||||
const point = MeshBuilder.CreateSphere("point", {diameter: 10}, DefaultScene.MainScene);
|
||||
point.position = position.clone();
|
||||
point.isVisible = false;
|
||||
point.scaling = scaling.multiplyByFloats(.2,.3,.2);
|
||||
console.log("Using pooled explosion with", explosion.systems.length, "systems at", position);
|
||||
|
||||
// Set emitter and start each system individually
|
||||
explosion.systems.forEach((system: ParticleSystem, idx: number) => {
|
||||
system.emitter = point; // Set emitter to the collision point
|
||||
system.start(); // Start this specific system
|
||||
console.log(` System ${idx}: emitter set to`, system.emitter, "activeCount=", system.getActiveCount());
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
explosion.systems.forEach((system: ParticleSystem) => {
|
||||
system.stop();
|
||||
});
|
||||
RockFactory.returnExplosionToPool(explosion);
|
||||
point.dispose();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
||||
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
||||
});
|
||||
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
||||
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
||||
}
|
||||
|
||||
return new Rock(rock);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user