AppleyAppley Docs
Docs/Lighting

Dynamic Seasons

Create seasons that transform terrain, vegetation, and gameplay over time.

29 min read Advanced Updated 2026-07-01 v2.0 ~120 min to complete

Introduction

Games like Animal Crossing and Medieval Dynasty feature seasons that transform the entire world. This guide explains how professional games swap terrain textures, change vegetation, and affect gameplay per season — then builds a dynamic seasons system in Appley.

Advanced Guide 15 of 25

This is part of the Advanced Documentation collection. Category: Lighting. Estimated completion: 120 minutes. Difficulty: Advanced/Professional.

Screenshot

Dynamic Seasons
Dynamic Seasons

How Professional Games Do It

1

D

e

2

E

a

3

T

r

4

S

e

5

M

o

6

S

w

7

C

h

8

T

r

9

A

f

10

W

i

11

P

e

12

S

a

FluxScript Implementation

Here is how to build this system in Appley using FluxScript (Appley Scripting):

Dynamic Seasons — FluxScript
// Dynamic seasons system with terrain and gameplay changes
const SEASONS = {
  spring: {
    terrainTextures: { grass: "spring_grass", dirt: "spring_dirt", rock: "mossy_rock" },
    treeVariant: "spring_bloom",
    lightColor: color(255, 245, 220),
    lightIntensity: 0.9,
    ambientColor: color(180, 200, 180),
    fogColor: color(200, 220, 200),
    fogDensity: 0.02,
    moveSpeedMul: 1.0,
    cropGrowthRate: 1.2,
    waterFreeze: false,
    precipitation: "light_rain"
  },
  summer: {
    terrainTextures: { grass: "summer_grass", dirt: "dry_dirt", rock: "rock" },
    treeVariant: "summer_full",
    lightColor: color(255, 240, 200),
    lightIntensity: 1.2,
    ambientColor: color(200, 200, 180),
    fogColor: color(220, 220, 200),
    fogDensity: 0.005,
    moveSpeedMul: 1.0,
    cropGrowthRate: 1.5,
    waterFreeze: false,
    precipitation: "clear"
  },
  autumn: {
    terrainTextures: { grass: "autumn_grass", dirt: "dry_dirt", rock: "rock" },
    treeVariant: "autumn_colors",
    lightColor: color(255, 200, 150),
    lightIntensity: 0.8,
    ambientColor: color(180, 150, 120),
    fogColor: color(200, 180, 150),
    fogDensity: 0.03,
    moveSpeedMul: 0.95,
    cropGrowthRate: 0.5,
    waterFreeze: false,
    precipitation: "cloudy"
  },
  winter: {
    terrainTextures: { grass: "snow", dirt: "snow_dirt", rock: "snow_rock" },
    treeVariant: "winter_bare",
    lightColor: color(200, 220, 255),
    lightIntensity: 0.5,
    ambientColor: color(150, 170, 200),
    fogColor: color(180, 190, 210),
    fogDensity: 0.05,
    moveSpeedMul: 0.6,
    cropGrowthRate: 0.0,
    waterFreeze: true,
    precipitation: "snow"
  }
}

seasonOrder = ["spring", "summer", "autumn", "winter"]
currentSeason = "spring"
seasonProgress = 0.0  // 0-1 within current season
SEASON_DURATION = 1800  // 30 minutes real time per season

on start {
  // Load saved season state
  saved = save.get("seasonState")
  if saved != null {
    currentSeason = saved.season
    seasonProgress = saved.progress
  }
  applySeason(currentSeason, 1.0)
}

on update(dt) {
  seasonProgress += dt / SEASON_DURATION

  // Transition to next season
  if seasonProgress >= 1.0 {
    seasonProgress = 0.0
    currentIdx = seasonOrder.indexOf(currentSeason)
    nextIdx = (currentIdx + 1) % seasonOrder.length
    currentSeason = seasonOrder[nextIdx]
    startSeasonTransition(currentSeason)
  }

  // During last 10% of season, start transitioning to next
  if seasonProgress > 0.9 {
    nextIdx = (seasonOrder.indexOf(currentSeason) + 1) % seasonOrder.length
    nextSeason = seasonOrder[nextIdx]
    transitionT = (seasonProgress - 0.9) / 0.1  // 0-1 over last 10%
    blendSeasons(currentSeason, nextSeason, transitionT)
  }
}

func startSeasonTransition(newSeason) {
  // Swap terrain textures (crossfade over 60 seconds)
  from = SEASONS[currentSeason]
  to = SEASONS[newSeason]

  // Terrain texture crossfade
  for layer in ["grass", "dirt", "rock"] {
    terrain.crossfadeTexture(layer, from.terrainTextures[layer], to.terrainTextures[layer], 60)
  }

  // Swap tree variants
  trees = world.findAll("tree")
  for tree in trees {
    tree.transitionToVariant(to.treeVariant, 60)
  }

  // Lighting transition
  sun = scene.find("SunLight")
  sun.transitionColor(from.lightColor, to.lightColor, 60)
  sun.transitionIntensity(from.lightIntensity, to.lightIntensity, 60)

  // Gameplay modifiers
  player.setMoveSpeedMul(to.moveSpeedMul)
  farming.setGrowthRate(to.cropGrowthRate)

  // Water freeze/unfreeze
  if to.waterFreeze {
    rivers = world.findAll("water")
    for river in rivers {
      river.setFrozen(true)  // Walkable surface
    }
  }

  // Precipitation change
  weather.setBasePrecipitation(to.precipitation)

  print("Season transition: " + currentSeason + " -> " + newSeason)
}

// Save season state periodically
on timer("saveSeason", 60) {
  save.set("seasonState", { season: currentSeason, progress: seasonProgress })
}

Node Graph Equivalent

The same system implemented as a Node Graph in Appley Node Scripting:

Dynamic Seasons — Node Graph
[On Start] --> [Load: saved season state]
   --> [Apply: current season fully]

[On Update]
   --> [seasonProgress += dt / DURATION]
   --> [Branch: progress >= 1.0]
        +-- True --> [Advance: to next season] --> [startSeasonTransition]
   --> [Branch: progress > 0.9 (last 10%)]
        +-- True --> [blendSeasons: current + next]

[Function: startSeasonTransition]
   --> [Crossfade: terrain textures (60s)]
   --> [Swap: tree variants (60s transition)]
   --> [Transition: lighting color + intensity]
   --> [Set: gameplay modifiers (speed, crop growth)]
   --> [Branch: waterFreeze]
        +-- True --> [Freeze: rivers walkable]
   --> [Set: base precipitation type]

[On Timer "saveSeason" 60s]
   --> [Save: current season + progress to cloud]

Professional Optimization Tips

How AAA studios optimize these systems — applicable to your Appley project:

  • Crossfade terrain textures over 60 seconds. Instant texture swaps look jarring. A 60-second crossfade between snow and grass textures feels like natural seasonal change.
  • Affect gameplay per season. Winter: snow slows movement, rivers freeze (new paths open), crops do not grow. Summer: full speed, crops thrive. This creates strategic seasonal planning.
  • Use tree variant swaps, not material color changes. Bare winter trees are fundamentally different from summer trees (different geometry). Swap models with a 60-second crossfade, not just leaf color.
  • Persist season state to cloud. Players expect the world to be in the same season when they return. Save season + progress every 60 seconds and on game close.

Common Mistakes

Instant season changes

Swapping snow for grass instantly is jarring. Crossfade terrain textures, lerp lighting, and transition tree variants over 60 seconds.

Seasons do not affect gameplay

If winter is just white textures, it is decoration. Snow should slow movement, frozen rivers should open paths, crops should stop growing. Seasons must matter.