Phase D: spatial lighting with shadows; local light drives growth
Bump the engine submodule to 79d406a (2D lightmap). The world scene now uses UseLighting instead of the per-sprite ambient: a lightmap multiplies day/night × occlusion over the world, so forests cast soft shade and the world darkens spatially toward night. Occluders are mountains (new TerrainDef.BlocksLight) plus mature trees, rebuilt from the live population. PlantGrowthSystem samples the local light per plant (Lighting.SampleAt), so undergrowth under canopy grows slower. A dev-console 'light' command places a point light at the cursor to show cast shadows at night. Completes the plant ecosystem (A–D). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a04e867e87
commit
49936a1de3
@@ -17,6 +17,6 @@
|
|||||||
{ "chance": 0.35, "options": ["TreeOakA", "TreeOakB", "TreeBirchA", "TreeGrayPineA"] }
|
{ "chance": 0.35, "options": ["TreeOakA", "TreeOakB", "TreeBirchA", "TreeGrayPineA"] }
|
||||||
] },
|
] },
|
||||||
{ "defName": "Mountain", "label": "terrain.mountain", "maxHeight": 1.01, "color": [136, 132, 128],
|
{ "defName": "Mountain", "label": "terrain.mountain", "maxHeight": 1.01, "color": [136, 132, 128],
|
||||||
"surface": "terrain/surfaces/roughhewnrock", "fertility": 0.2 }
|
"surface": "terrain/surfaces/roughhewnrock", "fertility": 0.2, "blocksLight": true }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule engine updated: d0104df304...79d406a9f4
@@ -27,6 +27,9 @@ public sealed class TerrainDef : Def
|
|||||||
/// <summary>Суша: здесь появляются жители, животные и растения.</summary>
|
/// <summary>Суша: здесь появляются жители, животные и растения.</summary>
|
||||||
public bool IsLand { get; init; }
|
public bool IsLand { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Загораживает свет (горы) — базовый окклюдер для лайтмапа/теней.</summary>
|
||||||
|
public bool BlocksLight { get; init; }
|
||||||
|
|
||||||
/// <summary>Ключ текстуры поверхности для тайловой сцены; null — тонированный тайл.</summary>
|
/// <summary>Ключ текстуры поверхности для тайловой сцены; null — тонированный тайл.</summary>
|
||||||
public string? Surface { get; init; }
|
public string? Surface { get; init; }
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ public sealed class WorldScene : Scene
|
|||||||
private PlantSet _plants = null!;
|
private PlantSet _plants = null!;
|
||||||
private float[] _cellFertility = [];
|
private float[] _cellFertility = [];
|
||||||
private bool[] _cellLand = [];
|
private bool[] _cellLand = [];
|
||||||
|
private bool[] _cellOccluderBase = []; // горы (статично из террейна)
|
||||||
|
private bool[] _cellOccluder = []; // горы + зрелые деревья (пересобирается лайтмапом)
|
||||||
|
|
||||||
/// <summary>Новый мир из конфига.</summary>
|
/// <summary>Новый мир из конфига.</summary>
|
||||||
public WorldScene(WorldConfig config)
|
public WorldScene(WorldConfig config)
|
||||||
@@ -84,7 +86,7 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
var calendar = Context.UseCalendar(SecondsPerDay);
|
var calendar = Context.UseCalendar(SecondsPerDay);
|
||||||
var climate = Context.UseClimate(ClimateSettings.Default);
|
var climate = Context.UseClimate(ClimateSettings.Default);
|
||||||
var dayNight = this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
|
var dayNight = new DayNight(calendar, DayNightSettings.Default);
|
||||||
|
|
||||||
// Рельеф детерминирован сидом. Растения: новый мир — скаттер из сида; загрузка — из сейва.
|
// Рельеф детерминирован сидом. Растения: новый мир — скаттер из сида; загрузка — из сейва.
|
||||||
_plants = new PlantSet(content, atlases, device);
|
_plants = new PlantSet(content, atlases, device);
|
||||||
@@ -101,6 +103,18 @@ public sealed class WorldScene : Scene
|
|||||||
RestorePlants(content);
|
RestorePlants(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Освещение: лайтмап (день/ночь × окклюзия от гор/крон + точечные) множится поверх мира,
|
||||||
|
// а Lighting.SampleAt даёт локальный свет системе роста (подлесок под кронами растёт хуже).
|
||||||
|
var lighting = this.UseLighting(
|
||||||
|
renderer,
|
||||||
|
dayNight,
|
||||||
|
_config.Width,
|
||||||
|
_config.Height,
|
||||||
|
CellSize,
|
||||||
|
Vector2.Zero,
|
||||||
|
BuildOccluders
|
||||||
|
);
|
||||||
|
|
||||||
var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));
|
var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));
|
||||||
|
|
||||||
// HUD, полоса скорости и меню-пауза в одной корневой панели.
|
// HUD, полоса скорости и меню-пауза в одной корневой панели.
|
||||||
@@ -120,8 +134,31 @@ public sealed class WorldScene : Scene
|
|||||||
this.UseInspector(renderer);
|
this.UseInspector(renderer);
|
||||||
var console = this.UseDevConsole();
|
var console = this.UseDevConsole();
|
||||||
RegisterCommands(console, content, atlases);
|
RegisterCommands(console, content, atlases);
|
||||||
|
console.Register(
|
||||||
|
"light",
|
||||||
|
"light [radius] — place a point light at the cursor (night shadows demo)",
|
||||||
|
(c, args) =>
|
||||||
|
{
|
||||||
|
var radius =
|
||||||
|
args.Length > 0
|
||||||
|
? float.Parse(args[0], System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: 120f;
|
||||||
|
var mouse = Mouse.GetState();
|
||||||
|
var world = renderer.ScreenToWorld(new Vector2(mouse.X, mouse.Y));
|
||||||
|
Store.CreateEntity(
|
||||||
|
Transform2D.At(world),
|
||||||
|
new PointLight
|
||||||
|
{
|
||||||
|
Radius = radius,
|
||||||
|
Color = Color.White,
|
||||||
|
Intensity = 0.9f,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
c.WriteLine($"light at {world.X:0},{world.Y:0} r{radius:0}");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
UpdateSystems.Add(new PlantGrowthSystem(_plants, calendar, climate, dayNight, CellSize));
|
UpdateSystems.Add(new PlantGrowthSystem(_plants, calendar, climate, lighting, CellSize));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new PlantLifecycleSystem(
|
new PlantLifecycleSystem(
|
||||||
Store,
|
Store,
|
||||||
@@ -193,8 +230,11 @@ public sealed class WorldScene : Scene
|
|||||||
_config.SmoothPasses
|
_config.SmoothPasses
|
||||||
);
|
);
|
||||||
var grid = new TileGrid(_config.Width, _config.Height);
|
var grid = new TileGrid(_config.Width, _config.Height);
|
||||||
_cellFertility = new float[_config.Width * _config.Height];
|
var cells = _config.Width * _config.Height;
|
||||||
_cellLand = new bool[_config.Width * _config.Height];
|
_cellFertility = new float[cells];
|
||||||
|
_cellLand = new bool[cells];
|
||||||
|
_cellOccluderBase = new bool[cells];
|
||||||
|
_cellOccluder = new bool[cells];
|
||||||
for (var x = 0; x < _config.Width; x++)
|
for (var x = 0; x < _config.Width; x++)
|
||||||
{
|
{
|
||||||
for (var y = 0; y < _config.Height; y++)
|
for (var y = 0; y < _config.Height; y++)
|
||||||
@@ -204,6 +244,7 @@ public sealed class WorldScene : Scene
|
|||||||
var cell = y * _config.Width + x;
|
var cell = y * _config.Width + x;
|
||||||
_cellFertility[cell] = terrain.Fertility;
|
_cellFertility[cell] = terrain.Fertility;
|
||||||
_cellLand[cell] = terrain.IsLand;
|
_cellLand[cell] = terrain.IsLand;
|
||||||
|
_cellOccluderBase[cell] = terrain.BlocksLight;
|
||||||
if (scatterRandom is not null)
|
if (scatterRandom is not null)
|
||||||
{
|
{
|
||||||
ScatterSpawner.Spawn(
|
ScatterSpawner.Spawn(
|
||||||
@@ -355,6 +396,40 @@ public sealed class WorldScene : Scene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Текущая сетка окклюдеров для лайтмапа: статичные горы + клетки со зрелыми деревьями.
|
||||||
|
private bool[] BuildOccluders()
|
||||||
|
{
|
||||||
|
Array.Copy(_cellOccluderBase, _cellOccluder, _cellOccluder.Length);
|
||||||
|
Store
|
||||||
|
.Query<PlantGrowth, Transform2D>()
|
||||||
|
.ForEachEntity(
|
||||||
|
(ref PlantGrowth grow, ref Transform2D transform, Entity _) =>
|
||||||
|
{
|
||||||
|
var species = _plants[grow.Species];
|
||||||
|
if (
|
||||||
|
species.Def.TrunkRadiusCells <= 0f
|
||||||
|
|| grow.Stage < species.Stages.Length - 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return; // затеняют только зрелые деревья (со стволом)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cx = Math.Clamp(
|
||||||
|
(int)(transform.Position.X / CellSize),
|
||||||
|
0,
|
||||||
|
_config.Width - 1
|
||||||
|
);
|
||||||
|
var cy = Math.Clamp(
|
||||||
|
(int)(transform.Position.Y / CellSize),
|
||||||
|
0,
|
||||||
|
_config.Height - 1
|
||||||
|
);
|
||||||
|
_cellOccluder[cy * _config.Width + cx] = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return _cellOccluder;
|
||||||
|
}
|
||||||
|
|
||||||
private void Switch(Scene scene)
|
private void Switch(Scene scene)
|
||||||
{
|
{
|
||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public sealed class PlantGrowthSystem(
|
|||||||
PlantSet plants,
|
PlantSet plants,
|
||||||
Calendar calendar,
|
Calendar calendar,
|
||||||
Climate climate,
|
Climate climate,
|
||||||
DayNight dayNight,
|
Lighting lighting,
|
||||||
int cellSize
|
int cellSize
|
||||||
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
||||||
{
|
{
|
||||||
@@ -31,8 +31,7 @@ public sealed class PlantGrowthSystem(
|
|||||||
return; // пауза — рост стоит
|
return; // пауза — рост стоит
|
||||||
}
|
}
|
||||||
|
|
||||||
// В фазе B свет и температура глобальные — считаем раз за кадр (фаза D даст локальный свет).
|
// Температура глобальная; свет — локальный (лайтмап: подлесок под кронами темнее).
|
||||||
var light = dayNight.Intensity;
|
|
||||||
var temperature = climate.Temperature;
|
var temperature = climate.Temperature;
|
||||||
|
|
||||||
foreach (var (growths, genomes, sprites, transforms, _) in Query.Chunks)
|
foreach (var (growths, genomes, sprites, transforms, _) in Query.Chunks)
|
||||||
@@ -52,6 +51,7 @@ public sealed class PlantGrowthSystem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ref var gene = ref dna[i];
|
ref var gene = ref dna[i];
|
||||||
|
var light = lighting.SampleAt(t[i].Position);
|
||||||
var rate =
|
var rate =
|
||||||
gene.Vigor
|
gene.Vigor
|
||||||
* Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance)
|
* Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance)
|
||||||
|
|||||||
Reference in New Issue
Block a user