❌ BUG FIX #3 - Runtime Error: **Problem:** - MasterWeatherSystem.js:460 - rainEmitter.setSpeedX is not a function - No null check before accessing particle emitter methods - Crashed when entering GameScene (New Game) **Root Cause:** - Particle emitters not initialized yet when update() called - Missing method existence check **Solution:** - Added null checks: this.rainEmitter && this.snowEmitter - Added method existence check: typeof setSpeedX === 'function' - Prevents crash if emitters not ready **Changes:** - Line 458: Added null + method check for rainEmitter - Line 463: Added null + method check for snowEmitter ✅ Game now loads past menu into GameScene ✅ Ready for TestVisualAudioScene test **Test:** game.scene.start('TestVisualAudioScene')
488 lines
14 KiB
JavaScript
488 lines
14 KiB
JavaScript
/**
|
|
* MasterWeatherSystem.js
|
|
*
|
|
* Complete weather & environmental particle system for DolinaSmrti
|
|
* Phase 1/2/Demo CORE SYSTEM - All biomes, all weather types
|
|
*
|
|
* Features:
|
|
* - Wind (grass, trees, hair movement)
|
|
* - Rain (drops, puddles, wetness)
|
|
* - Snow (flakes, accumulation, footprints)
|
|
* - Fire (flames, smoke, heat distortion)
|
|
* - Water (ripples, displacement, caustics)
|
|
*
|
|
* Style 32 Dark-Chibi Noir compatible
|
|
* Performance optimized for 60 FPS
|
|
*/
|
|
|
|
import WindFoliageSystem from './WindFoliageSystem.js';
|
|
|
|
export default class MasterWeatherSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Sub-systems
|
|
this.windSystem = null;
|
|
this.rainSystem = null;
|
|
this.snowSystem = null;
|
|
this.fireSystem = null;
|
|
this.waterSystem = null;
|
|
|
|
// Current weather state
|
|
this.currentWeather = 'clear'; // clear, rain, snow, storm
|
|
this.weatherIntensity = 0; // 0.0 - 1.0
|
|
this.transitionTime = 0;
|
|
|
|
// Biome-specific settings
|
|
this.biomeWeatherSettings = {
|
|
'grassland': {
|
|
allowedWeather: ['clear', 'rain', 'storm'],
|
|
defaultWind: 1.0
|
|
},
|
|
'desert': {
|
|
allowedWeather: ['clear', 'sandstorm'],
|
|
defaultWind: 1.5
|
|
},
|
|
'snow': {
|
|
allowedWeather: ['clear', 'snow', 'blizzard'],
|
|
defaultWind: 1.8
|
|
},
|
|
'swamp': {
|
|
allowedWeather: ['rain', 'fog'],
|
|
defaultWind: 0.3
|
|
},
|
|
'mountains': {
|
|
allowedWeather: ['clear', 'snow', 'storm'],
|
|
defaultWind: 2.0
|
|
},
|
|
'forest': {
|
|
allowedWeather: ['clear', 'rain'],
|
|
defaultWind: 0.8
|
|
},
|
|
'volcanic': {
|
|
allowedWeather: ['clear', 'ash_rain'],
|
|
defaultWind: 1.2,
|
|
constantFire: true
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Initialize all weather systems
|
|
*/
|
|
init() {
|
|
console.log('🌦️ MasterWeatherSystem: Initializing...');
|
|
|
|
// Initialize Wind System (already implemented!)
|
|
this.windSystem = new WindFoliageSystem(this.scene);
|
|
this.windSystem.init();
|
|
|
|
// Initialize Rain System
|
|
this.initRainSystem();
|
|
|
|
// Initialize Snow System
|
|
this.initSnowSystem();
|
|
|
|
// Initialize Fire System
|
|
this.initFireSystem();
|
|
|
|
// Initialize Water Effects
|
|
this.initWaterSystem();
|
|
|
|
console.log('✅ MasterWeatherSystem: All systems ready!');
|
|
}
|
|
|
|
/**
|
|
* RAIN SYSTEM - Realistic rain with puddles
|
|
*/
|
|
initRainSystem() {
|
|
// Create rain drop texture (Style 32 noir)
|
|
const graphics = this.scene.add.graphics();
|
|
graphics.fillStyle(0xffffff, 0.6);
|
|
graphics.fillRect(0, 0, 2, 8); // Thin vertical drop
|
|
graphics.generateTexture('rainDrop', 2, 8);
|
|
graphics.destroy();
|
|
|
|
// Rain particle emitter
|
|
this.rainEmitter = this.scene.add.particles(0, 0, 'rainDrop', {
|
|
x: { min: 0, max: this.scene.cameras.main.width },
|
|
y: -20,
|
|
lifespan: 2000,
|
|
speedY: { min: 300, max: 500 },
|
|
speedX: { min: -20, max: 20 }, // Wind affects rain
|
|
scale: { start: 1, end: 0.8 },
|
|
alpha: { start: 0.6, end: 0.3 },
|
|
quantity: 2,
|
|
frequency: 20,
|
|
blendMode: 'ADD'
|
|
});
|
|
|
|
this.rainEmitter.stop(); // Start inactive
|
|
|
|
// Puddle system (appears after rain)
|
|
this.puddles = [];
|
|
|
|
console.log('💧 Rain system initialized');
|
|
}
|
|
|
|
/**
|
|
* SNOW SYSTEM - Falling snow with accumulation
|
|
*/
|
|
initSnowSystem() {
|
|
// Create snowflake texture (Style 32 - simple white dot with black outline)
|
|
const graphics = this.scene.add.graphics();
|
|
graphics.lineStyle(1, 0x000000, 1);
|
|
graphics.fillStyle(0xffffff, 0.9);
|
|
graphics.fillCircle(4, 4, 3);
|
|
graphics.strokeCircle(4, 4, 3);
|
|
graphics.generateTexture('snowflake', 8, 8);
|
|
graphics.destroy();
|
|
|
|
// Snow particle emitter
|
|
this.snowEmitter = this.scene.add.particles(0, 0, 'snowflake', {
|
|
x: { min: 0, max: this.scene.cameras.main.width },
|
|
y: -20,
|
|
lifespan: 8000,
|
|
speedY: { min: 20, max: 50 },
|
|
speedX: { min: -30, max: 30 }, // Wind drift
|
|
scale: { min: 0.5, max: 1.2 },
|
|
alpha: { start: 0.9, end: 0.6 },
|
|
rotate: { min: 0, max: 360 },
|
|
frequency: 100,
|
|
quantity: 1
|
|
});
|
|
|
|
this.snowEmitter.stop();
|
|
|
|
// Snow accumulation layer (white overlay on ground)
|
|
this.snowAccumulation = this.scene.add.rectangle(
|
|
0, 0,
|
|
this.scene.cameras.main.width,
|
|
this.scene.cameras.main.height,
|
|
0xffffff, 0
|
|
).setOrigin(0, 0).setDepth(-1);
|
|
|
|
console.log('❄️ Snow system initialized');
|
|
}
|
|
|
|
/**
|
|
* FIRE SYSTEM - Flames, smoke, heat distortion
|
|
*/
|
|
initFireSystem() {
|
|
// Create flame texture (orange-red gradient)
|
|
const graphics = this.scene.add.graphics();
|
|
graphics.fillStyle(0xff4500, 1);
|
|
graphics.fillCircle(8, 8, 6);
|
|
graphics.fillStyle(0xffaa00, 0.8);
|
|
graphics.fillCircle(8, 8, 4);
|
|
graphics.generateTexture('flame', 16, 16);
|
|
graphics.destroy();
|
|
|
|
// Create smoke texture (dark grey)
|
|
const smokeGraphics = this.scene.add.graphics();
|
|
smokeGraphics.fillStyle(0x333333, 0.4);
|
|
smokeGraphics.fillCircle(12, 12, 10);
|
|
smokeGraphics.generateTexture('smoke', 24, 24);
|
|
smokeGraphics.destroy();
|
|
|
|
// Fire sources (campfires, torches, etc.)
|
|
this.fireSources = [];
|
|
|
|
console.log('🔥 Fire system initialized');
|
|
}
|
|
|
|
/**
|
|
* Create fire source (campfire, torch, etc.)
|
|
* @param {number} x - X position
|
|
* @param {number} y - Y position
|
|
* @param {number} size - Fire size multiplier (0.5 - 2.0)
|
|
*/
|
|
createFireSource(x, y, size = 1.0) {
|
|
// Flame particles
|
|
const flameEmitter = this.scene.add.particles(x, y, 'flame', {
|
|
lifespan: 800,
|
|
speed: { min: 20, max: 60 },
|
|
angle: { min: 250, max: 290 }, // Upward
|
|
scale: { start: size, end: 0.1 },
|
|
alpha: { start: 1, end: 0 },
|
|
blendMode: 'ADD',
|
|
frequency: 50,
|
|
tint: [0xff4500, 0xff6600, 0xffaa00]
|
|
});
|
|
|
|
// Smoke particles
|
|
const smokeEmitter = this.scene.add.particles(x, y - 10, 'smoke', {
|
|
lifespan: 2000,
|
|
speed: { min: 10, max: 30 },
|
|
angle: { min: 260, max: 280 },
|
|
scale: { start: size * 0.5, end: size * 2 },
|
|
alpha: { start: 0.5, end: 0 },
|
|
frequency: 200
|
|
});
|
|
|
|
this.fireSources.push({
|
|
x, y, size,
|
|
flameEmitter,
|
|
smokeEmitter
|
|
});
|
|
|
|
return { flameEmitter, smokeEmitter };
|
|
}
|
|
|
|
/**
|
|
* WATER SYSTEM - Ripples, displacement, caustics
|
|
* (Integration with planned WaterPhysicsSystem)
|
|
*/
|
|
initWaterSystem() {
|
|
// Water ripple texture (Style 32 - concentric circles)
|
|
const graphics = this.scene.add.graphics();
|
|
graphics.lineStyle(2, 0x000000, 1);
|
|
graphics.strokeCircle(32, 32, 28);
|
|
graphics.lineStyle(1, 0x000000, 0.6);
|
|
graphics.strokeCircle(32, 32, 24);
|
|
graphics.generateTexture('waterRipple', 64, 64);
|
|
graphics.destroy();
|
|
|
|
// Ripple particle manager
|
|
this.rippleParticles = this.scene.add.particles(0, 0, 'waterRipple');
|
|
|
|
// Water zones (areas with water effects)
|
|
this.waterZones = [];
|
|
|
|
console.log('🌊 Water system initialized');
|
|
}
|
|
|
|
/**
|
|
* Create water ripple effect
|
|
* @param {number} x - X position
|
|
* @param {number} y - Y position
|
|
* @param {number} size - Ripple size
|
|
*/
|
|
createRipple(x, y, size = 1.0) {
|
|
const emitter = this.rippleParticles.createEmitter({
|
|
x, y,
|
|
lifespan: 1500,
|
|
speed: 0,
|
|
scale: { start: 0.1 * size, end: 2.0 * size },
|
|
alpha: { start: 0.7, end: 0 },
|
|
frequency: -1,
|
|
quantity: 1
|
|
});
|
|
|
|
emitter.explode(1);
|
|
|
|
this.scene.time.delayedCall(1500, () => {
|
|
emitter.stop();
|
|
this.rippleParticles.removeEmitter(emitter);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set weather type
|
|
* @param {string} type - Weather type: 'clear', 'rain', 'snow', 'storm', 'blizzard'
|
|
* @param {number} intensity - Intensity 0.0 - 1.0
|
|
* @param {number} transitionTime - Transition duration in ms
|
|
*/
|
|
setWeather(type, intensity = 0.5, transitionTime = 2000) {
|
|
console.log(`🌦️ Weather changing to: ${type} (intensity: ${intensity})`);
|
|
|
|
this.currentWeather = type;
|
|
this.weatherIntensity = intensity;
|
|
this.transitionTime = transitionTime;
|
|
|
|
// Stop all current weather
|
|
this.rainEmitter.stop();
|
|
this.snowEmitter.stop();
|
|
|
|
// Start new weather
|
|
switch (type) {
|
|
case 'rain':
|
|
this.startRain(intensity);
|
|
break;
|
|
case 'snow':
|
|
this.startSnow(intensity);
|
|
break;
|
|
case 'storm':
|
|
this.startStorm(intensity);
|
|
break;
|
|
case 'blizzard':
|
|
this.startBlizzard(intensity);
|
|
break;
|
|
case 'clear':
|
|
this.clearWeather();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start rain weather
|
|
*/
|
|
startRain(intensity) {
|
|
this.rainEmitter.setFrequency(100 / intensity); // More intense = more frequent
|
|
this.rainEmitter.setQuantity(Math.ceil(intensity * 3));
|
|
this.rainEmitter.start();
|
|
|
|
// Adjust wind
|
|
this.windSystem.setWindStrength(0.8 + intensity * 0.5);
|
|
|
|
// Create puddles over time
|
|
this.scene.time.addEvent({
|
|
delay: 5000,
|
|
callback: () => this.createPuddle(),
|
|
loop: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Start snow weather
|
|
*/
|
|
startSnow(intensity) {
|
|
this.snowEmitter.setFrequency(200 / intensity);
|
|
this.snowEmitter.setQuantity(Math.ceil(intensity * 2));
|
|
this.snowEmitter.start();
|
|
|
|
// Adjust wind (snow drifts more)
|
|
this.windSystem.setWindStrength(intensity * 1.2);
|
|
|
|
// Gradual snow accumulation
|
|
this.scene.tweens.add({
|
|
targets: this.snowAccumulation,
|
|
alpha: intensity * 0.3,
|
|
duration: 10000,
|
|
ease: 'Linear'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Start storm (heavy rain + strong wind)
|
|
*/
|
|
startStorm(intensity) {
|
|
this.startRain(intensity);
|
|
this.windSystem.setWindStrength(intensity * 2.0);
|
|
|
|
// Lightning flashes (TODO: add later)
|
|
}
|
|
|
|
/**
|
|
* Start blizzard (heavy snow + very strong wind)
|
|
*/
|
|
startBlizzard(intensity) {
|
|
this.startSnow(intensity);
|
|
this.windSystem.setWindStrength(intensity * 2.5);
|
|
|
|
// Reduce visibility
|
|
this.scene.tweens.add({
|
|
targets: this.scene.cameras.main,
|
|
alpha: 0.7,
|
|
duration: 2000,
|
|
yoyo: true,
|
|
repeat: -1
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clear all weather
|
|
*/
|
|
clearWeather() {
|
|
this.rainEmitter.stop();
|
|
this.snowEmitter.stop();
|
|
this.windSystem.setWindStrength(1.0);
|
|
|
|
// Fade out snow accumulation
|
|
this.scene.tweens.add({
|
|
targets: this.snowAccumulation,
|
|
alpha: 0,
|
|
duration: 5000
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create puddle after rain
|
|
*/
|
|
createPuddle() {
|
|
const x = Phaser.Math.Between(0, this.scene.cameras.main.width);
|
|
const y = Phaser.Math.Between(0, this.scene.cameras.main.height);
|
|
|
|
const puddle = this.scene.add.ellipse(x, y, 60, 30, 0x4682B4, 0.3);
|
|
puddle.setDepth(-2);
|
|
|
|
this.puddles.push(puddle);
|
|
|
|
// Puddles evaporate over time
|
|
this.scene.tweens.add({
|
|
targets: puddle,
|
|
alpha: 0,
|
|
duration: 30000,
|
|
onComplete: () => {
|
|
puddle.destroy();
|
|
this.puddles = this.puddles.filter(p => p !== puddle);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set biome-specific weather
|
|
* @param {string} biomeName - Biome name
|
|
*/
|
|
setBiomeWeather(biomeName) {
|
|
const settings = this.biomeWeatherSettings[biomeName.toLowerCase()];
|
|
|
|
if (!settings) {
|
|
console.warn(`⚠️ Unknown biome: ${biomeName}`);
|
|
return;
|
|
}
|
|
|
|
// Set default wind for biome
|
|
this.windSystem.setBiomeWind(biomeName);
|
|
|
|
// Volcanic biome always has fire
|
|
if (settings.constantFire) {
|
|
this.createFireSource(100, 200, 1.5);
|
|
this.createFireSource(300, 250, 1.2);
|
|
}
|
|
|
|
console.log(`🌍 Biome weather set: ${biomeName}`);
|
|
}
|
|
|
|
/**
|
|
* Update all weather systems
|
|
* @param {number} delta - Time delta in ms
|
|
*/
|
|
update(delta) {
|
|
// Update wind system
|
|
if (this.windSystem) {
|
|
this.windSystem.update(delta);
|
|
}
|
|
|
|
// Update rain/snow particle positions based on wind
|
|
// ✅ NULL CHECK: Ensure emitter exists and has setSpeedX method
|
|
if (this.rainEmitter && this.rainEmitter.active && typeof this.rainEmitter.setSpeedX === 'function') {
|
|
const windInfluence = this.windSystem.wind.strength * 20;
|
|
this.rainEmitter.setSpeedX({ min: -windInfluence, max: windInfluence });
|
|
}
|
|
|
|
if (this.snowEmitter && this.snowEmitter.active && typeof this.snowEmitter.setSpeedX === 'function') {
|
|
const windInfluence = this.windSystem.wind.strength * 30;
|
|
this.snowEmitter.setSpeedX({ min: -windInfluence, max: windInfluence });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup
|
|
*/
|
|
destroy() {
|
|
if (this.windSystem) this.windSystem.destroy();
|
|
if (this.rainEmitter) this.rainEmitter.destroy();
|
|
if (this.snowEmitter) this.snowEmitter.destroy();
|
|
|
|
this.fireSources.forEach(fire => {
|
|
fire.flameEmitter.destroy();
|
|
fire.smokeEmitter.destroy();
|
|
});
|
|
|
|
this.puddles.forEach(puddle => puddle.destroy());
|
|
|
|
if (this.rippleParticles) this.rippleParticles.destroy();
|
|
}
|
|
}
|