AgentSkillsCN

weather-and-daynight

在 2D&D 中实现天气效果与昼夜循环机制

SKILL.md
--- frontmatter
name: weather-and-daynight
description: Implement weather effects and day/night cycle mechanics in 2D&D
license: MIT

Weather & Day/Night Systems

Two interrelated systems that affect encounters, combat, visuals, and audio.

Day/Night Cycle (src/systems/daynight.ts)

Cycle Structure

  • Total steps: 360 (one step per player movement)
  • ~9 minutes per full cycle at sustained walking speed
PeriodStepsDurationTint Color
Dawn0–4445 steps0xffd5a0 (warm orange)
Day45–219175 steps0xffffff (no tint)
Dusk220–26445 steps0xffa570 (deep orange)
Night265–35995 steps0x6688cc (cool blue)

Key Exports

typescript
import {
  TimePeriod,           // enum: Dawn, Day, Dusk, Night
  CYCLE_LENGTH,         // 360
  getTimePeriod,        // (step: number) → TimePeriod
  getEncounterMultiplier, // Dawn/Day=1.0, Dusk=1.25, Night=1.5
  isNightTime,          // true during Dusk and Night
  PERIOD_TINT,          // Record<TimePeriod, 0xRRGGBB>
  PERIOD_LABEL,         // Record<TimePeriod, "🌅 Dawn" etc.>
} from "../systems/daynight";

Visual Effects

  • Overworld: Tint applied to every tile sprite via applyDayNightTint()
  • Battle: Background image tinted + celestial body drawn (sun/moon)
    • Dawn: sun low-left (rising)
    • Day: sun upper-left with rays
    • Dusk: sun mid-left (setting, redder)
    • Night: crescent moon upper-left + scattered stars
  • Music: Night mode shifts major scales to relative minor, darker tone

Gameplay Effects

  • Encounter rate increases at dusk (+25%) and night (+50%)
  • Night-exclusive monsters spawn during Dusk and Night periods
  • timeStep is persisted in save data and passed through all scene transitions

Weather System (src/systems/weather.ts)

Weather Types

TypeVisualAudioCombat Effect
ClearNoneNoneNone
RainBlue rain particlesLowpass filtered noiseAccuracy penalty
SnowWhite slow particlesSoft highpass noiseAccuracy penalty
SandstormTan fast horizontalBandpass midrange noiseAccuracy penalty
StormHeavy rain + lightningHeavy noise + thunder rumbleAccuracy + monster boost
FogLarge slow blobsLow sine droneAccuracy penalty

Weather State

typescript
interface WeatherState {
  current: WeatherType;
  stepsUntilChange: number; // Countdown to next weather check
}

// Create fresh state (starts Clear, 40 steps until first check)
const state = createWeatherState();

Biome-Weighted Probabilities

Each chunk name maps to weather weights. Examples:

  • Mountain chunks: high Snow/Fog probability
  • Desert chunks: high Sandstorm probability
  • Swamp chunks: high Fog/Rain probability
  • Tundra chunks: Snow dominant

Key Functions

typescript
import {
  WeatherType,
  createWeatherState,
  advanceWeather,         // Step-based countdown, may change weather
  changeZoneWeather,      // Called on chunk transition
  getWeatherAccuracyPenalty,  // Accuracy penalty for combat
  getWeatherEncounterMultiplier, // Encounter rate modifier
  getMonsterWeatherBoost, // Per-monster AC/ATK/DMG bonuses
  WEATHER_TINT,           // Tint colors per weather type
  WEATHER_LABEL,          // HUD labels per weather type
} from "../systems/weather";

Combat Effects

typescript
// In combat, apply weather penalties:
const weatherPenalty = getWeatherAccuracyPenalty(weatherState.current);
const boost = getMonsterWeatherBoost(monster.id, weatherState.current);
// boost.acBonus, boost.attackBonus, boost.damageBonus

Adding a New Weather Type

  1. Add to WeatherType enum
  2. Add probability weights in BIOME_WEATHER records
  3. Add accuracy penalty in WEATHER_ACCURACY_PENALTY
  4. Add encounter multiplier in WEATHER_ENCOUNTER_MULT
  5. Add tint color in WEATHER_TINT
  6. Add label in WEATHER_LABEL
  7. Add particle config in OverworldScene.updateWeatherParticles() and BattleScene.createWeatherParticles()
  8. Add ambient SFX in audioEngine.playWeatherSFX()

Scene Data Flow

Both systems' state must be passed through every scene transition:

typescript
this.scene.start("NextScene", {
  player: this.player,
  defeatedBosses: this.defeatedBosses,
  bestiary: this.bestiary,
  timeStep: this.timeStep,           // Day/night position
  weatherState: this.weatherState,   // Current weather + countdown
});

Common Pitfalls

  • ❌ Don't forget to pass weatherState in scene transitions — it resets to Clear otherwise
  • ❌ Don't apply weather in dungeons/cities — they force WeatherType.Clear
  • ❌ Don't forget to call rerollWeather() on chunk transitions
  • ❌ Don't use hardcoded step numbers — use getTimePeriod() and constants