Bring plants back as data-driven growth stages over the calendar
Bump the engine submodule to f791ee6 (Calendar) and use it to drive plant growth. PlantDef gains a Stages list (texture/size/growDays per stage, inheritable via abstract parents in plants.json); a resolved PlantSet table turns those into atlas regions and day thresholds at scene load. New PlantGrowth component plus PlantGrowthSystem advance each plant by in-game days from the Calendar and swap its sprite/scale at stage borders. WorldScene scatters plants again with a seed-deterministic, varied initial maturity, registers the calendar and growth system, and the HUD now shows the current day. Stage durations and visuals are pure data in plants.json. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
923644dd19
commit
1030f27c50
@@ -1,15 +1,28 @@
|
|||||||
{
|
{
|
||||||
"type": "Plant",
|
"type": "Plant",
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28 },
|
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
||||||
|
"stages": [
|
||||||
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" },
|
||||||
|
{ "sizeCells": 1.2, "growDays": 9, "label": "plant.stage.sapling" },
|
||||||
|
{ "sizeCells": 2.0, "label": "plant.stage.mature" }
|
||||||
|
] },
|
||||||
{ "defName": "TreeOakA", "parent": "BaseTree", "label": "plant.oak", "texture": "things/plant/treeoak/TreeOakA" },
|
{ "defName": "TreeOakA", "parent": "BaseTree", "label": "plant.oak", "texture": "things/plant/treeoak/TreeOakA" },
|
||||||
{ "defName": "TreeOakB", "parent": "BaseTree", "label": "plant.oak", "texture": "things/plant/treeoak/TreeOakB" },
|
{ "defName": "TreeOakB", "parent": "BaseTree", "label": "plant.oak", "texture": "things/plant/treeoak/TreeOakB" },
|
||||||
{ "defName": "TreeBirchA", "parent": "BaseTree", "label": "plant.birch", "texture": "things/plant/treebirch/TreeBirchA" },
|
{ "defName": "TreeBirchA", "parent": "BaseTree", "label": "plant.birch", "texture": "things/plant/treebirch/TreeBirchA" },
|
||||||
{ "defName": "TreeGrayPineA", "parent": "BaseTree", "label": "plant.pine", "texture": "things/plant/treegraypine/TreeGrayPineA" },
|
{ "defName": "TreeGrayPineA", "parent": "BaseTree", "label": "plant.pine", "texture": "things/plant/treegraypine/TreeGrayPineA" },
|
||||||
|
|
||||||
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.2 },
|
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.2,
|
||||||
|
"stages": [
|
||||||
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 2, "label": "plant.stage.sprout" },
|
||||||
|
{ "sizeCells": 1.2, "label": "plant.stage.mature" }
|
||||||
|
] },
|
||||||
|
|
||||||
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4 },
|
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
|
||||||
|
"stages": [
|
||||||
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" },
|
||||||
|
{ "sizeCells": 1.4, "label": "plant.stage.mature" }
|
||||||
|
] },
|
||||||
{ "defName": "BushA", "parent": "BaseBush", "texture": "things/plant/bush/BushA" },
|
{ "defName": "BushA", "parent": "BaseBush", "texture": "things/plant/bush/BushA" },
|
||||||
{ "defName": "BushB", "parent": "BaseBush", "texture": "things/plant/bush/BushB" },
|
{ "defName": "BushB", "parent": "BaseBush", "texture": "things/plant/bush/BushB" },
|
||||||
{ "defName": "BushC", "parent": "BaseBush", "texture": "things/plant/bush/BushC" }
|
{ "defName": "BushC", "parent": "BaseBush", "texture": "things/plant/bush/BushC" }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hud.world": "LittleSim | seed {0} | {2} FPS",
|
"hud.world": "LittleSim | seed {0} | day {1} | {2} FPS",
|
||||||
"hud.controls": "WASD — camera, wheel — zoom, ` — console ('help' lists commands)",
|
"hud.controls": "WASD — camera, wheel — zoom, ` — console ('help' lists commands)",
|
||||||
"hud.paused": "PAUSED",
|
"hud.paused": "PAUSED",
|
||||||
|
|
||||||
@@ -67,6 +67,10 @@
|
|||||||
"plant.pine": "pine",
|
"plant.pine": "pine",
|
||||||
"plant.grass": "grass tuft",
|
"plant.grass": "grass tuft",
|
||||||
"plant.bush": "bush",
|
"plant.bush": "bush",
|
||||||
|
"plant.stage.seedling": "seedling",
|
||||||
|
"plant.stage.sprout": "sprout",
|
||||||
|
"plant.stage.sapling": "sapling",
|
||||||
|
"plant.stage.mature": "mature",
|
||||||
|
|
||||||
"pawn.being": "being",
|
"pawn.being": "being",
|
||||||
"pawn.bear": "bear",
|
"pawn.bear": "bear",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hud.world": "LittleSim | сид {0} | {2} FPS",
|
"hud.world": "LittleSim | сид {0} | день {1} | {2} FPS",
|
||||||
"hud.controls": "WASD — камера, колесо — зум, ` — консоль ('help' — список команд)",
|
"hud.controls": "WASD — камера, колесо — зум, ` — консоль ('help' — список команд)",
|
||||||
"hud.paused": "ПАУЗА",
|
"hud.paused": "ПАУЗА",
|
||||||
|
|
||||||
@@ -67,6 +67,10 @@
|
|||||||
"plant.pine": "сосна",
|
"plant.pine": "сосна",
|
||||||
"plant.grass": "пучок травы",
|
"plant.grass": "пучок травы",
|
||||||
"plant.bush": "куст",
|
"plant.bush": "куст",
|
||||||
|
"plant.stage.seedling": "росток",
|
||||||
|
"plant.stage.sprout": "всходы",
|
||||||
|
"plant.stage.sapling": "саженец",
|
||||||
|
"plant.stage.mature": "взрослое",
|
||||||
|
|
||||||
"pawn.being": "житель",
|
"pawn.being": "житель",
|
||||||
"pawn.bear": "медведь",
|
"pawn.bear": "медведь",
|
||||||
|
|||||||
+1
-1
Submodule engine updated: 53659450a6...f791ee6c95
@@ -37,17 +37,40 @@ public sealed class TerrainDef : Def
|
|||||||
public Color Tint => Rgb is [var r, var g, var b, ..] ? new(r, g, b) : Color.Magenta;
|
public Color Tint => Rgb is [var r, var g, var b, ..] ? new(r, g, b) : Color.Magenta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие.</summary>
|
/// <summary>
|
||||||
|
/// Одна стадия роста растения (вложенный объект <see cref="PlantDef.Stages"/>): свой спрайт и
|
||||||
|
/// размер, и сколько игровых дней растение проводит на этой стадии до перехода к следующей.
|
||||||
|
/// Растёт на месте — последняя стадия терминальная (её <see cref="GrowDays"/> игнорируется).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PlantGrowthStage
|
||||||
|
{
|
||||||
|
/// <summary>Ключ текстуры стадии; null — берётся базовая <see cref="PlantDef.Texture"/>.</summary>
|
||||||
|
public string? Texture { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Размер спрайта в клетках на этой стадии; 0 — берётся базовый <see cref="PlantDef.SizeCells"/>.</summary>
|
||||||
|
public float SizeCells { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Сколько игровых дней расти на этой стадии до перехода к следующей.</summary>
|
||||||
|
public float GrowDays { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Ключ локализации названия стадии (опционально, для будущих тултипов).</summary>
|
||||||
|
public string? Label { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста.</summary>
|
||||||
public sealed class PlantDef : Def
|
public sealed class PlantDef : Def
|
||||||
{
|
{
|
||||||
/// <summary>Ключ текстуры (путь региона в атласах).</summary>
|
/// <summary>Ключ текстуры (путь региона в атласах); база для стадий без своей текстуры.</summary>
|
||||||
public string Texture { get; init; } = "";
|
public string Texture { get; init; } = "";
|
||||||
|
|
||||||
/// <summary>Размер спрайта в клетках.</summary>
|
/// <summary>Базовый размер спрайта в клетках; база для стадий без своего размера.</summary>
|
||||||
public float SizeCells { get; init; } = 1f;
|
public float SizeCells { get; init; } = 1f;
|
||||||
|
|
||||||
/// <summary>Радиус коллайдера ствола в клетках; 0 — без коллайдера (трава, кусты).</summary>
|
/// <summary>Радиус коллайдера ствола в клетках; 0 — без коллайдера (трава, кусты).</summary>
|
||||||
public float TrunkRadiusCells { get; init; }
|
public float TrunkRadiusCells { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Стадии роста по порядку; пусто — одна терминальная стадия из базовой текстуры.</summary>
|
||||||
|
public List<PlantGrowthStage> Stages { get; init; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
|
namespace LittleSim.Content;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Готовая (resolved) таблица растений для сцены: по каждому <see cref="PlantDef"/> — массив
|
||||||
|
/// стадий с уже загруженными регионами атласа, размерами и порогами входа в днях. Строится один
|
||||||
|
/// раз при загрузке сцены (как <see cref="MrGameEng.Tilemaps.TileSet"/>, потому что нужны атлас и
|
||||||
|
/// устройство). <see cref="Sim.PlantGrowthSystem"/> индексируется в неё без словарей и аллокаций.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PlantSet
|
||||||
|
{
|
||||||
|
/// <summary>Разрешённая стадия: спрайт, размер в клетках и день, с которого стадия активна.</summary>
|
||||||
|
public readonly record struct Stage(Texture2DRegion Region, float SizeCells, float EnterDay);
|
||||||
|
|
||||||
|
/// <summary>Вид растения: его деф и стадии роста по возрастанию <see cref="Stage.EnterDay"/>.</summary>
|
||||||
|
public sealed class Species
|
||||||
|
{
|
||||||
|
/// <summary>Исходный деф растения.</summary>
|
||||||
|
public required PlantDef Def { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Стадии роста по порядку; последняя — терминальная.</summary>
|
||||||
|
public required Stage[] Stages { get; init; }
|
||||||
|
|
||||||
|
/// <summary>День, с которого растение достигает последней (зрелой) стадии.</summary>
|
||||||
|
public float MaturityDays => Stages[^1].EnterDay;
|
||||||
|
|
||||||
|
/// <summary>Индекс стадии для данного возраста в днях.</summary>
|
||||||
|
public int StageAt(float ageDays)
|
||||||
|
{
|
||||||
|
var stage = 0;
|
||||||
|
while (stage + 1 < Stages.Length && ageDays >= Stages[stage + 1].EnterDay)
|
||||||
|
{
|
||||||
|
stage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Species[] _species;
|
||||||
|
private readonly Dictionary<PlantDef, int> _index = new();
|
||||||
|
|
||||||
|
/// <summary>Строит таблицу из всех (не-abstract) <see cref="PlantDef"/> мода.</summary>
|
||||||
|
public PlantSet(GameContent content, ModAtlases atlases, GraphicsDevice device)
|
||||||
|
{
|
||||||
|
var defs = content.Defs.All<PlantDef>().ToArray();
|
||||||
|
_species = new Species[defs.Length];
|
||||||
|
for (var i = 0; i < defs.Length; i++)
|
||||||
|
{
|
||||||
|
var def = defs[i];
|
||||||
|
_species[i] = new Species { Def = def, Stages = BuildStages(def, atlases, device) };
|
||||||
|
_index[def] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Число видов в таблице.</summary>
|
||||||
|
public int Count => _species.Length;
|
||||||
|
|
||||||
|
/// <summary>Вид по индексу.</summary>
|
||||||
|
public Species this[int index] => _species[index];
|
||||||
|
|
||||||
|
/// <summary>Индекс вида по дефу.</summary>
|
||||||
|
public int IndexOf(PlantDef def) => _index[def];
|
||||||
|
|
||||||
|
private static Stage[] BuildStages(PlantDef def, ModAtlases atlases, GraphicsDevice device)
|
||||||
|
{
|
||||||
|
// Без явных стадий растение существует как одна терминальная стадия из базовой текстуры.
|
||||||
|
if (def.Stages.Count == 0)
|
||||||
|
{
|
||||||
|
var region = atlases.GetRegion(device, def.Texture);
|
||||||
|
return [new Stage(region, def.SizeCells, 0f)];
|
||||||
|
}
|
||||||
|
|
||||||
|
var stages = new Stage[def.Stages.Count];
|
||||||
|
var enterDay = 0f;
|
||||||
|
for (var i = 0; i < def.Stages.Count; i++)
|
||||||
|
{
|
||||||
|
var stage = def.Stages[i];
|
||||||
|
var region = atlases.GetRegion(device, stage.Texture ?? def.Texture);
|
||||||
|
var size = stage.SizeCells > 0f ? stage.SizeCells : def.SizeCells;
|
||||||
|
stages[i] = new Stage(region, size, enterDay);
|
||||||
|
enterDay += stage.GrowDays; // следующая стадия входит после GrowDays этой
|
||||||
|
}
|
||||||
|
|
||||||
|
return stages;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,30 @@
|
|||||||
using Friflo.Engine.ECS;
|
using Friflo.Engine.ECS;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
|
using LittleSim.Sim;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.DevConsole;
|
using MrGameEng.DevConsole;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Scenes;
|
namespace LittleSim.Scenes;
|
||||||
|
|
||||||
/// <summary>Спавн растительности по скаттеру дефов террейна (общий для обеих сцен).</summary>
|
/// <summary>Спавн растительности по скаттеру дефов террейна.</summary>
|
||||||
internal static class ScatterSpawner
|
internal static class ScatterSpawner
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Бросает каждую запись скаттера клетки независимо; при успехе ставит одно растение
|
/// Бросает каждую запись скаттера клетки независимо; при успехе ставит одно растение со
|
||||||
/// со случайным смещением. Деревья (ствол > 0) получают коллайдер-препятствие,
|
/// случайным смещением и случайной (детерминированной сидом) зрелостью — мир после генерации
|
||||||
/// когда заданы слои коллизий.
|
/// разновозрастный. Стадия и спрайт берутся из <paramref name="plants"/>, дальше растением
|
||||||
|
/// управляет <see cref="PlantGrowthSystem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Spawn(
|
public static void Spawn(
|
||||||
Scene scene,
|
Scene scene,
|
||||||
GameContent content,
|
GameContent content,
|
||||||
ModAtlases atlases,
|
PlantSet plants,
|
||||||
GraphicsDevice device,
|
|
||||||
TerrainDef terrain,
|
TerrainDef terrain,
|
||||||
Point cell,
|
Point cell,
|
||||||
int cellSize,
|
int cellSize,
|
||||||
Random random,
|
Random random
|
||||||
uint obstacleLayer = 0,
|
|
||||||
uint collidesWith = 0
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
foreach (var entry in terrain.Scatter)
|
foreach (var entry in terrain.Scatter)
|
||||||
@@ -36,28 +34,30 @@ internal static class ScatterSpawner
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var plant = content.Defs.Get<PlantDef>(entry.Options[random.Next(entry.Options.Count)]);
|
var def = content.Defs.Get<PlantDef>(entry.Options[random.Next(entry.Options.Count)]);
|
||||||
var region = atlases.GetRegion(device, plant.Texture);
|
var index = plants.IndexOf(def);
|
||||||
var sprite = new Sprite(region, GameLayers.Beings);
|
var species = plants[index];
|
||||||
sprite.CenterOrigin();
|
var px = (cell.X + 0.3f + random.NextSingle() * 0.4f) * cellSize;
|
||||||
var entity = scene.Store.CreateEntity(
|
var py = (cell.Y + 0.3f + random.NextSingle() * 0.4f) * cellSize;
|
||||||
new Transform2D(
|
var age = random.NextSingle() * species.MaturityDays;
|
||||||
new Vector2(
|
var stage = species.StageAt(age);
|
||||||
(cell.X + 0.3f + random.NextSingle() * 0.4f) * cellSize,
|
var resolved = species.Stages[stage];
|
||||||
(cell.Y + 0.3f + random.NextSingle() * 0.4f) * cellSize
|
|
||||||
),
|
|
||||||
scale: new Vector2(cellSize * plant.SizeCells / region.Width)
|
|
||||||
),
|
|
||||||
sprite
|
|
||||||
);
|
|
||||||
|
|
||||||
if (obstacleLayer != 0 && plant.TrunkRadiusCells > 0f)
|
var sprite = new Sprite(resolved.Region, GameLayers.Beings);
|
||||||
{
|
sprite.CenterOrigin();
|
||||||
var trunk = MrGameEng.Collisions.Collider.Circle(cellSize * plant.TrunkRadiusCells);
|
scene.Store.CreateEntity(
|
||||||
trunk.Layer = obstacleLayer;
|
new Transform2D(
|
||||||
trunk.CollidesWith = collidesWith;
|
new Vector2(px, py),
|
||||||
entity.AddComponent(trunk);
|
scale: new Vector2(cellSize * resolved.SizeCells / resolved.Region.Width)
|
||||||
}
|
),
|
||||||
|
sprite,
|
||||||
|
new PlantGrowth
|
||||||
|
{
|
||||||
|
Species = index,
|
||||||
|
Stage = stage,
|
||||||
|
AgeDays = age,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ public sealed class WorldScene : Scene
|
|||||||
{
|
{
|
||||||
public const int CellSize = 16;
|
public const int CellSize = 16;
|
||||||
|
|
||||||
|
/// <summary>Сколько секунд масштабированного времени длится один игровой день.</summary>
|
||||||
|
public const float SecondsPerDay = 30f;
|
||||||
|
|
||||||
private readonly WorldConfig _config;
|
private readonly WorldConfig _config;
|
||||||
private readonly WorldSave? _save;
|
private readonly WorldSave? _save;
|
||||||
private readonly RectF _bounds;
|
private readonly RectF _bounds;
|
||||||
@@ -66,7 +69,12 @@ public sealed class WorldScene : Scene
|
|||||||
GameLayers.EnsureRegistered(renderer);
|
GameLayers.EnsureRegistered(renderer);
|
||||||
this.UseTilemaps();
|
this.UseTilemaps();
|
||||||
|
|
||||||
BuildTerrain(content, atlases, device, assets);
|
var calendar = Context.UseCalendar(SecondsPerDay);
|
||||||
|
|
||||||
|
// Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed).
|
||||||
|
var plants = new PlantSet(content, atlases, device);
|
||||||
|
var random = new Random(_config.Seed);
|
||||||
|
BuildTerrain(content, atlases, device, assets, plants, random);
|
||||||
|
|
||||||
var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));
|
var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));
|
||||||
|
|
||||||
@@ -87,9 +95,10 @@ public sealed class WorldScene : Scene
|
|||||||
var console = this.UseDevConsole();
|
var console = this.UseDevConsole();
|
||||||
RegisterCommands(console, content, atlases);
|
RegisterCommands(console, content, atlases);
|
||||||
|
|
||||||
|
UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, CellSize));
|
||||||
UpdateSystems.Add(new GodCameraSystem(camera, input, console, () => _pause.IsOpen));
|
UpdateSystems.Add(new GodCameraSystem(camera, input, console, () => _pause.IsOpen));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new HudSystem(Context, content.Languages, hudLabel, "hud.world", _config.Seed, 0)
|
new HudSystem(Context, content.Languages, hudLabel, "hud.world", _config.Seed, calendar)
|
||||||
);
|
);
|
||||||
UpdateSystems.Add(new CallbackSystem(() => Hotkeys(input)));
|
UpdateSystems.Add(new CallbackSystem(() => Hotkeys(input)));
|
||||||
|
|
||||||
@@ -101,13 +110,16 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Строит одну сущность-<see cref="Tilemap"/>: тайлсет из дефов рельефа (поверхность из
|
/// Строит одну сущность-<see cref="Tilemap"/>: тайлсет из дефов рельефа (поверхность из
|
||||||
/// атласа либо тонированный тайл) и сетка тайлов по карте высот процедурной генерации.
|
/// атласа либо тонированный тайл) и сетка тайлов по карте высот процедурной генерации, а
|
||||||
|
/// поверх — растительность по скаттеру дефов (с компонентом роста, разной зрелости).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BuildTerrain(
|
private void BuildTerrain(
|
||||||
GameContent content,
|
GameContent content,
|
||||||
ModAtlases atlases,
|
ModAtlases atlases,
|
||||||
Microsoft.Xna.Framework.Graphics.GraphicsDevice device,
|
Microsoft.Xna.Framework.Graphics.GraphicsDevice device,
|
||||||
AssetManager assets
|
AssetManager assets,
|
||||||
|
PlantSet plants,
|
||||||
|
Random random
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White));
|
var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White));
|
||||||
@@ -134,7 +146,17 @@ public sealed class WorldScene : Scene
|
|||||||
{
|
{
|
||||||
for (var y = 0; y < _config.Height; y++)
|
for (var y = 0; y < _config.Height; y++)
|
||||||
{
|
{
|
||||||
grid[x, y] = tileByDef[content.Terrains.Classify(heights[x, y])];
|
var terrain = content.Terrains.Classify(heights[x, y]);
|
||||||
|
grid[x, y] = tileByDef[terrain];
|
||||||
|
ScatterSpawner.Spawn(
|
||||||
|
this,
|
||||||
|
content,
|
||||||
|
plants,
|
||||||
|
terrain,
|
||||||
|
new Point(x, y),
|
||||||
|
CellSize,
|
||||||
|
random
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Friflo.Engine.ECS;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Состояние роста растения: индекс вида в <see cref="Content.PlantSet"/>, текущая стадия и
|
||||||
|
/// накопленный возраст в игровых днях. Плоские данные — переход между стадиями и смену спрайта
|
||||||
|
/// выполняет <see cref="PlantGrowthSystem"/>, читая пороги стадий из таблицы вида.
|
||||||
|
/// </summary>
|
||||||
|
public struct PlantGrowth : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Индекс вида растения в <see cref="Content.PlantSet"/>.</summary>
|
||||||
|
public int Species;
|
||||||
|
|
||||||
|
/// <summary>Текущая стадия роста (индекс в стадиях вида).</summary>
|
||||||
|
public int Stage;
|
||||||
|
|
||||||
|
/// <summary>Накопленный возраст в игровых днях.</summary>
|
||||||
|
public float AgeDays;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Friflo.Engine.ECS.Systems;
|
||||||
|
using LittleSim.Content;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Двигает рост растений: копит возраст по игровому календарю и при пересечении порога стадии
|
||||||
|
/// переключает спрайт на текстуру новой стадии, переотцентровывает и масштабирует его. Время
|
||||||
|
/// идёт по <see cref="Calendar"/> (пауза/скорость учитываются через <c>Tick.deltaTime</c>),
|
||||||
|
/// данные стадий — из <see cref="PlantSet"/>. Терминальную стадию пропускает (рост окончен).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int cellSize)
|
||||||
|
: QuerySystem<PlantGrowth, Sprite, Transform2D>
|
||||||
|
{
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
var deltaDays = Tick.deltaTime / calendar.SecondsPerDay;
|
||||||
|
if (deltaDays <= 0f)
|
||||||
|
{
|
||||||
|
return; // пауза — рост стоит
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (growths, sprites, transforms, _) in Query.Chunks)
|
||||||
|
{
|
||||||
|
var g = growths.Span;
|
||||||
|
var s = sprites.Span;
|
||||||
|
var t = transforms.Span;
|
||||||
|
for (var i = 0; i < g.Length; i++)
|
||||||
|
{
|
||||||
|
ref var grow = ref g[i];
|
||||||
|
var stages = plants[grow.Species].Stages;
|
||||||
|
if (grow.Stage >= stages.Length - 1)
|
||||||
|
{
|
||||||
|
continue; // уже зрелое — рост окончен
|
||||||
|
}
|
||||||
|
|
||||||
|
grow.AgeDays += deltaDays;
|
||||||
|
var changed = false;
|
||||||
|
while (
|
||||||
|
grow.Stage < stages.Length - 1
|
||||||
|
&& grow.AgeDays >= stages[grow.Stage + 1].EnterDay
|
||||||
|
)
|
||||||
|
{
|
||||||
|
grow.Stage++;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
Apply(ref s[i], ref t[i], stages[grow.Stage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Apply(ref Sprite sprite, ref Transform2D transform, in PlantSet.Stage stage)
|
||||||
|
{
|
||||||
|
sprite.Region = stage.Region;
|
||||||
|
sprite.Origin = new Vector2(stage.Region.Width / 2f, stage.Region.Height / 2f);
|
||||||
|
transform.Scale = new Vector2(cellSize * stage.SizeCells / stage.Region.Width);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -161,7 +161,7 @@ public sealed class GodCameraSystem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Строка статуса в HUD: заголовок сцены (ключ локализации с сидом, населением и FPS)
|
/// Строка статуса в HUD: заголовок сцены (ключ локализации с сидом, днём календаря и FPS)
|
||||||
/// и подсказка управления. Обновляется 4 раза в секунду; смена языка подхватывается сама.
|
/// и подсказка управления. Обновляется 4 раза в секунду; смена языка подхватывается сама.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HudSystem(
|
public sealed class HudSystem(
|
||||||
@@ -170,7 +170,7 @@ public sealed class HudSystem(
|
|||||||
Myra.Graphics2D.UI.Label label,
|
Myra.Graphics2D.UI.Label label,
|
||||||
string titleKey,
|
string titleKey,
|
||||||
int seed,
|
int seed,
|
||||||
int population
|
MrGameEng.Core.Calendar? calendar = null
|
||||||
) : BaseSystem
|
) : BaseSystem
|
||||||
{
|
{
|
||||||
private float _accumulated;
|
private float _accumulated;
|
||||||
@@ -189,7 +189,7 @@ public sealed class HudSystem(
|
|||||||
_accumulated = 0f;
|
_accumulated = 0f;
|
||||||
_frames = 0;
|
_frames = 0;
|
||||||
label.Text =
|
label.Text =
|
||||||
languages.Format(titleKey, seed, population, fps)
|
languages.Format(titleKey, seed, calendar?.Day ?? 0, fps)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ languages.Get("hud.controls");
|
+ languages.Get("hud.controls");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user