diff --git a/Mods/Core/Defs/plants.json b/Mods/Core/Defs/plants.json index 0124e0c..1a83da0 100644 --- a/Mods/Core/Defs/plants.json +++ b/Mods/Core/Defs/plants.json @@ -2,6 +2,8 @@ "type": "Plant", "defs": [ { "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28, + "genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 16, + "optimalFertility": 1.4, "fertilityTolerance": 0.9, "vigor": 1.0, "spread": 0.08 }, "stages": [ { "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" }, { "sizeCells": 1.2, "growDays": 9, "label": "plant.stage.sapling" }, @@ -13,12 +15,16 @@ { "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, + "genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 20, "temperatureTolerance": 12, + "optimalFertility": 1.0, "fertilityTolerance": 1.0, "vigor": 1.3, "spread": 0.1 }, "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, + "genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 13, + "optimalFertility": 1.1, "fertilityTolerance": 1.0, "vigor": 1.0, "spread": 0.1 }, "stages": [ { "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" }, { "sizeCells": 1.4, "label": "plant.stage.mature" } diff --git a/engine b/engine index a684c23..d0104df 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit a684c23cd96249dc686c4747a3bc52b2c6361421 +Subproject commit d0104df3044961f74bda68d3e6cc97dd0d34b326 diff --git a/src/LittleSim/Content/GameDefs.cs b/src/LittleSim/Content/GameDefs.cs index 4407b37..a33e1d7 100644 --- a/src/LittleSim/Content/GameDefs.cs +++ b/src/LittleSim/Content/GameDefs.cs @@ -63,7 +63,39 @@ public sealed class PlantGrowthStage public string? Label { get; init; } } -/// Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста. +/// +/// Базовый геном вида (вложенный объект ): оптимумы и толерантности по +/// факторам среды + базовая бодрость роста. Особь при спавне получает аллели рядом с этими базами +/// (разброс — ); фенотип = среднее аллелей. +/// +public sealed class GenomeDef +{ + /// Оптимальная освещённость (0..1) и её толерантность. + public float OptimalLight { get; init; } = 0.7f; + + /// Ширина толерантности по свету. + public float LightTolerance { get; init; } = 0.4f; + + /// Оптимальная температура (°C) и её толерантность. + public float OptimalTemperature { get; init; } = 18f; + + /// Ширина толерантности по температуре (°C). + public float TemperatureTolerance { get; init; } = 12f; + + /// Оптимальная плодородность почвы и её толерантность. + public float OptimalFertility { get; init; } = 1f; + + /// Ширина толерантности по плодородности. + public float FertilityTolerance { get; init; } = 0.8f; + + /// Базовая бодрость роста (множитель скорости в идеальных условиях). + public float Vigor { get; init; } = 1f; + + /// Доля разброса аллелей вокруг базы при генерации особи. + public float Spread { get; init; } = 0.08f; +} + +/// Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста, геном. public sealed class PlantDef : Def { /// Ключ текстуры (путь региона в атласах); база для стадий без своей текстуры. @@ -77,6 +109,9 @@ public sealed class PlantDef : Def /// Стадии роста по порядку; пусто — одна терминальная стадия из базовой текстуры. public List Stages { get; init; } = []; + + /// Базовый геном вида: оптимумы/толерантности по среде и бодрость роста. + public GenomeDef Genome { get; init; } = new(); } /// diff --git a/src/LittleSim/Scenes/ContentHelpers.cs b/src/LittleSim/Scenes/ContentHelpers.cs index 77a4025..3f43486 100644 --- a/src/LittleSim/Scenes/ContentHelpers.cs +++ b/src/LittleSim/Scenes/ContentHelpers.cs @@ -56,8 +56,9 @@ internal static class ScatterSpawner Species = index, Stage = stage, AgeDays = age, - GrowthRate = terrain.Fertility, - } + CellFertility = terrain.Fertility, + }, + PlantGenome.FromDef(def.Genome, random) ); } } diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index f9b38c0..1afd90f 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -76,7 +76,7 @@ public sealed class WorldScene : Scene var calendar = Context.UseCalendar(SecondsPerDay); var climate = Context.UseClimate(ClimateSettings.Default); - this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер + var dayNight = this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер // Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed). var plants = new PlantSet(content, atlases, device); @@ -103,7 +103,7 @@ public sealed class WorldScene : Scene var console = this.UseDevConsole(); RegisterCommands(console, content, atlases); - UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, CellSize)); + UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, climate, dayNight, CellSize)); UpdateSystems.Add( new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen) ); diff --git a/src/LittleSim/Sim/PlantGenome.cs b/src/LittleSim/Sim/PlantGenome.cs new file mode 100644 index 0000000..a7971db --- /dev/null +++ b/src/LittleSim/Sim/PlantGenome.cs @@ -0,0 +1,90 @@ +using Friflo.Engine.ECS; +using LittleSim.Content; + +namespace LittleSim.Sim; + +/// +/// Диплоидный геном растения: по паре аллелей на ген. Для числовых генов экспрессия гибридная — +/// фенотип есть среднее двух аллелей (дискретные доминант/рецессивные гены добавятся с размножением). +/// Аллели — наследуемые данные (правятся в инспекторе); фенотип-свойства читает система роста. +/// +public struct PlantGenome : IComponent +{ + /// Аллели оптимума освещённости (0..1). + public float OptimalLightA; + public float OptimalLightB; + + /// Аллели толерантности по свету. + public float LightToleranceA; + public float LightToleranceB; + + /// Аллели оптимума температуры (°C). + public float OptimalTemperatureA; + public float OptimalTemperatureB; + + /// Аллели толерантности по температуре (°C). + public float TemperatureToleranceA; + public float TemperatureToleranceB; + + /// Аллели оптимума плодородности. + public float OptimalFertilityA; + public float OptimalFertilityB; + + /// Аллели толерантности по плодородности. + public float FertilityToleranceA; + public float FertilityToleranceB; + + /// Аллели бодрости роста. + public float VigorA; + public float VigorB; + + /// Фенотип: оптимальная освещённость. + public readonly float OptimalLight => (OptimalLightA + OptimalLightB) * 0.5f; + + /// Фенотип: толерантность по свету. + public readonly float LightTolerance => (LightToleranceA + LightToleranceB) * 0.5f; + + /// Фенотип: оптимальная температура. + public readonly float OptimalTemperature => (OptimalTemperatureA + OptimalTemperatureB) * 0.5f; + + /// Фенотип: толерантность по температуре. + public readonly float TemperatureTolerance => + (TemperatureToleranceA + TemperatureToleranceB) * 0.5f; + + /// Фенотип: оптимальная плодородность. + public readonly float OptimalFertility => (OptimalFertilityA + OptimalFertilityB) * 0.5f; + + /// Фенотип: толерантность по плодородности. + public readonly float FertilityTolerance => (FertilityToleranceA + FertilityToleranceB) * 0.5f; + + /// Фенотип: бодрость роста. + public readonly float Vigor => (VigorA + VigorB) * 0.5f; + + /// + /// Генерирует особь из базового генома вида: каждый аллель — база ± относительный разброс + /// (). Сидируется переданным — детерминизм. + /// + public static PlantGenome FromDef(GenomeDef def, Random random) + { + float Allele(float baseValue) => + baseValue + (random.NextSingle() * 2f - 1f) * def.Spread * baseValue; + + return new PlantGenome + { + OptimalLightA = Allele(def.OptimalLight), + OptimalLightB = Allele(def.OptimalLight), + LightToleranceA = Allele(def.LightTolerance), + LightToleranceB = Allele(def.LightTolerance), + OptimalTemperatureA = Allele(def.OptimalTemperature), + OptimalTemperatureB = Allele(def.OptimalTemperature), + TemperatureToleranceA = Allele(def.TemperatureTolerance), + TemperatureToleranceB = Allele(def.TemperatureTolerance), + OptimalFertilityA = Allele(def.OptimalFertility), + OptimalFertilityB = Allele(def.OptimalFertility), + FertilityToleranceA = Allele(def.FertilityTolerance), + FertilityToleranceB = Allele(def.FertilityTolerance), + VigorA = Allele(def.Vigor), + VigorB = Allele(def.Vigor), + }; + } +} diff --git a/src/LittleSim/Sim/PlantGrowth.cs b/src/LittleSim/Sim/PlantGrowth.cs index 2ac67d9..7ad2101 100644 --- a/src/LittleSim/Sim/PlantGrowth.cs +++ b/src/LittleSim/Sim/PlantGrowth.cs @@ -18,6 +18,6 @@ public struct PlantGrowth : IComponent /// Накопленный возраст в игровых днях. public float AgeDays; - /// Множитель скорости роста (плодородность клетки): 1 — норма, больше — быстрее. - public float GrowthRate; + /// Плодородность клетки, на которой растёт растение — вход пригодности(плодородность). + public float CellFertility; } diff --git a/src/LittleSim/Sim/PlantGrowthSystem.cs b/src/LittleSim/Sim/PlantGrowthSystem.cs index 4bac078..aafdc72 100644 --- a/src/LittleSim/Sim/PlantGrowthSystem.cs +++ b/src/LittleSim/Sim/PlantGrowthSystem.cs @@ -1,19 +1,27 @@ using Friflo.Engine.ECS.Systems; using LittleSim.Content; using Microsoft.Xna.Framework; +using MrGameEng.AI; using MrGameEng.Core; using MrGameEng.Graphics; +using MrGameEng.Lighting; namespace LittleSim.Sim; /// -/// Двигает рост растений: копит возраст по игровому календарю и при пересечении порога стадии -/// переключает спрайт на текстуру новой стадии, переотцентровывает и масштабирует его. Время -/// идёт по (пауза/скорость учитываются через Tick.deltaTime), -/// данные стадий — из . Терминальную стадию пропускает (рост окончен). +/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов +/// растения. Скорость = бодрость · пригодность(свет) · пригодность(температура) · +/// пригодность(плодородность), каждая пригодность — гауссов «колокол» вокруг оптимума из генов +/// (см. ). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии +/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает. /// -public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int cellSize) - : QuerySystem +public sealed class PlantGrowthSystem( + PlantSet plants, + Calendar calendar, + Climate climate, + DayNight dayNight, + int cellSize +) : QuerySystem { protected override void OnUpdate() { @@ -23,9 +31,14 @@ public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int ce return; // пауза — рост стоит } - foreach (var (growths, sprites, transforms, _) in Query.Chunks) + // В фазе B свет и температура глобальные — считаем раз за кадр (фаза D даст локальный свет). + var light = dayNight.Intensity; + var temperature = climate.Temperature; + + foreach (var (growths, genomes, sprites, transforms, _) in Query.Chunks) { var g = growths.Span; + var dna = genomes.Span; var s = sprites.Span; var t = transforms.Span; for (var i = 0; i < g.Length; i++) @@ -37,7 +50,22 @@ public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int ce continue; // уже зрелое — рост окончен } - grow.AgeDays += deltaDays * grow.GrowthRate; // плодородность ускоряет/замедляет рост + ref var gene = ref dna[i]; + var rate = + gene.Vigor + * Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance) + * Suitability.Gaussian( + temperature, + gene.OptimalTemperature, + gene.TemperatureTolerance + ) + * Suitability.Gaussian( + grow.CellFertility, + gene.OptimalFertility, + gene.FertilityTolerance + ); + + grow.AgeDays += deltaDays * rate; var changed = false; while ( grow.Stage < stages.Length - 1