Животные A3: жизненный цикл, стадии и пол
Компонент AnimalGrowth (стадия + возраст). Ген пола GeneSex (локус XX/XY; генерация основателя задаёт пол явно через AnimalSet.GenerateGenome, исключая невозможный YY) и GeneMaturityAge. Стадии Baby->Juvenile->Adult->Senior с порогами из генов. AnimalGrowthSystem меняет спрайт (детёныш/самец/самка) и размер по стадии; AnimalMortalitySystem — смерть от старости и истощения (деспавн), замыкает контур ёмкости среды. Стадо спавнится разновозрастным. Сборка чистая, --check-content (30 генов, 29 признаков). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e35e620655
commit
355ced4937
@@ -9,6 +9,8 @@
|
|||||||
"label": "pawn.deer",
|
"label": "pawn.deer",
|
||||||
"kind": "animal",
|
"kind": "animal",
|
||||||
"texture": "things/pawn/animal/deer/DeerFemale_east",
|
"texture": "things/pawn/animal/deer/DeerFemale_east",
|
||||||
|
"maleTexture": "things/pawn/animal/deer/DeerMale_east",
|
||||||
|
"babyTexture": "things/pawn/animal/deer/DeerBaby_east",
|
||||||
"genome": {
|
"genome": {
|
||||||
"bodySize": 1.5,
|
"bodySize": 1.5,
|
||||||
"metabolism": 1.0,
|
"metabolism": 1.0,
|
||||||
@@ -18,6 +20,7 @@
|
|||||||
"brainSize": 0.45,
|
"brainSize": 0.45,
|
||||||
"insulation": 0.55,
|
"insulation": 0.55,
|
||||||
"furColor": 0.45,
|
"furColor": 0.45,
|
||||||
|
"maturityAge": 90,
|
||||||
"lifespan": 360,
|
"lifespan": 360,
|
||||||
"spread": 0.08
|
"spread": 0.08
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,15 @@
|
|||||||
{ "defName": "GeneFurColor", "parent": "BaseNumericGene", "label": "gene.furColor",
|
{ "defName": "GeneFurColor", "parent": "BaseNumericGene", "label": "gene.furColor",
|
||||||
"default": 0.5, "min": 0.0, "max": 1.0, "spread": 0.06, "tags": ["animal", "morphology", "color"],
|
"default": 0.5, "min": 0.0, "max": 1.0, "spread": 0.06, "tags": ["animal", "morphology", "color"],
|
||||||
"effects": { "furHue": "value" } },
|
"effects": { "furHue": "value" } },
|
||||||
|
{ "defName": "GeneMaturityAge", "parent": "BaseNumericGene", "label": "gene.maturityAge",
|
||||||
|
"default": 90, "min": 1, "max": 400, "tags": ["animal", "lifecycle"],
|
||||||
|
"effects": { "maturityAge": "value" } },
|
||||||
|
|
||||||
|
// Пол как локус (фаза A3): аллели {X=0, Y=1}; самка XX, самец XY. Эффекта нет — пол читается из
|
||||||
|
// самих аллелей (наличие Y), не из выраженного значения. mutationChance 0 (X не мутирует в Y).
|
||||||
|
// Генерация особи-основателя задаёт пол явно (XX или XY), чтобы не возник невозможный YY.
|
||||||
|
{ "defName": "GeneSex", "kind": "Discrete", "label": "gene.sex",
|
||||||
|
"variants": 2, "variantWeights": [0.5, 0.5], "mutationChance": 0, "tags": ["animal", "sex"] },
|
||||||
|
|
||||||
// Дискретный ген морфы: вариант 0 доминирует, 1 (рецессивный) виден только в гомозиготе.
|
// Дискретный ген морфы: вариант 0 доминирует, 1 (рецессивный) виден только в гомозиготе.
|
||||||
{ "defName": "GeneMorph", "kind": "Discrete", "label": "gene.morph",
|
{ "defName": "GeneMorph", "kind": "Discrete", "label": "gene.morph",
|
||||||
|
|||||||
+9
-1
@@ -1,8 +1,16 @@
|
|||||||
# Животные (зоология)
|
# Животные (зоология)
|
||||||
|
|
||||||
> Статус: **A1–A2 реализованы**, остальное по плану. Это живой план — правится по мере
|
> Статус: **A1–A3 реализованы**, остальное по плану. Это живой план — правится по мере
|
||||||
> реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md).
|
> реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md).
|
||||||
>
|
>
|
||||||
|
> **A3 (готово):** компонент `AnimalGrowth` (стадия + возраст); ген пола `GeneSex` (XX/XY,
|
||||||
|
> генерация основателя без YY через `AnimalSet.GenerateGenome`) и ген `GeneMaturityAge`;
|
||||||
|
> стадии Baby→Juvenile→Adult→Senior с порогами из генов; `AnimalGrowthSystem` (смена
|
||||||
|
> спрайта детёныш/самец/самка и размера по стадии) + `AnimalMortalitySystem` (смерть от
|
||||||
|
> старости и от истощения → деспавн, замыкает контур ёмкости среды). Спавн стада —
|
||||||
|
> разновозрастный; команда `animal` показывает пол/созревание. Сборка чистая, `--check-content`
|
||||||
|
> (30 генов).
|
||||||
|
>
|
||||||
> **A1 (готово):** тип дефа `Animal` (`AnimalDef : PawnDef`) + `animals.json` (олень);
|
> **A1 (готово):** тип дефа `Animal` (`AnimalDef : PawnDef`) + `animals.json` (олень);
|
||||||
> 8 животных генов в `genes.json`; `AnimalPhenotype`/`AnimalOrganism`; `AnimalSet`
|
> 8 животных генов в `genes.json`; `AnimalPhenotype`/`AnimalOrganism`; `AnimalSet`
|
||||||
> (шаблон генома вида) + `AnimalFactory`; детерминированный спавн стада оленей по суше в
|
> (шаблон генома вида) + `AnimalFactory`; детерминированный спавн стада оленей по суше в
|
||||||
|
|||||||
@@ -12,14 +12,20 @@ namespace LittleSim.Content;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AnimalSet
|
public sealed class AnimalSet
|
||||||
{
|
{
|
||||||
/// <summary>Вид животного: его деф, спрайт и набор генов для генерации особи.</summary>
|
/// <summary>Вид животного: его деф, спрайты по полу/стадии и набор генов для генерации особи.</summary>
|
||||||
public sealed class Species
|
public sealed class Species
|
||||||
{
|
{
|
||||||
/// <summary>Исходный деф животного.</summary>
|
/// <summary>Исходный деф животного.</summary>
|
||||||
public required AnimalDef Def { get; init; }
|
public required AnimalDef Def { get; init; }
|
||||||
|
|
||||||
/// <summary>Спрайт вида (фаза A1 — единый; стадии/пол позже).</summary>
|
/// <summary>Спрайт взрослой самки (он же общий/fallback).</summary>
|
||||||
public required Texture2DRegion Region { get; init; }
|
public required Texture2DRegion Female { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Спрайт взрослого самца (fallback — самка).</summary>
|
||||||
|
public required Texture2DRegion Male { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Спрайт детёныша/подростка (fallback — самка).</summary>
|
||||||
|
public required Texture2DRegion Baby { get; init; }
|
||||||
|
|
||||||
/// <summary>Набор генов вида: базовые значения для генерации особи.</summary>
|
/// <summary>Набор генов вида: базовые значения для генерации особи.</summary>
|
||||||
public required GenomeTemplate Template { get; init; }
|
public required GenomeTemplate Template { get; init; }
|
||||||
@@ -44,10 +50,17 @@ public sealed class AnimalSet
|
|||||||
for (var i = 0; i < defs.Length; i++)
|
for (var i = 0; i < defs.Length; i++)
|
||||||
{
|
{
|
||||||
var def = defs[i];
|
var def = defs[i];
|
||||||
|
var female = atlases.GetRegion(device, def.Texture);
|
||||||
_species[i] = new Species
|
_species[i] = new Species
|
||||||
{
|
{
|
||||||
Def = def,
|
Def = def,
|
||||||
Region = atlases.GetRegion(device, def.Texture),
|
Female = female,
|
||||||
|
Male = string.IsNullOrEmpty(def.MaleTexture)
|
||||||
|
? female
|
||||||
|
: atlases.GetRegion(device, def.MaleTexture),
|
||||||
|
Baby = string.IsNullOrEmpty(def.BabyTexture)
|
||||||
|
? female
|
||||||
|
: atlases.GetRegion(device, def.BabyTexture),
|
||||||
Template = BuildTemplate(def.Genome, genes),
|
Template = BuildTemplate(def.Genome, genes),
|
||||||
};
|
};
|
||||||
_index[def] = i;
|
_index[def] = i;
|
||||||
@@ -63,6 +76,18 @@ public sealed class AnimalSet
|
|||||||
/// <summary>Индекс вида по дефу.</summary>
|
/// <summary>Индекс вида по дефу.</summary>
|
||||||
public int IndexOf(AnimalDef def) => _index[def];
|
public int IndexOf(AnimalDef def) => _index[def];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Генерирует геном новой особи-основателя вида: аллели из шаблона + явно назначенный пол
|
||||||
|
/// (XX самка / XY самец, 50/50) — чтобы не возник невозможный YY (см. genes.json GeneSex).
|
||||||
|
/// Скрещивание (A4) наследует пол само (мать XX отдаёт только X).
|
||||||
|
/// </summary>
|
||||||
|
public Genome GenerateGenome(int species, Random random)
|
||||||
|
{
|
||||||
|
var genome = _species[species].Template.Generate(random);
|
||||||
|
genome["GeneSex"] = random.NextSingle() < 0.5f ? new Allele(0f, 0f) : new Allele(0f, 1f);
|
||||||
|
return genome;
|
||||||
|
}
|
||||||
|
|
||||||
// Переводит базовый геном вида (числа из animals.json) в набор генов: каждое число — центр
|
// Переводит базовый геном вида (числа из animals.json) в набор генов: каждое число — центр
|
||||||
// аллелей соответствующего общего GeneDef. Зеркало PlantSet.BuildTemplate.
|
// аллелей соответствующего общего GeneDef. Зеркало PlantSet.BuildTemplate.
|
||||||
private static GenomeTemplate BuildTemplate(
|
private static GenomeTemplate BuildTemplate(
|
||||||
@@ -90,6 +115,7 @@ public sealed class AnimalSet
|
|||||||
new GenomeTemplate.Entry(Gene("GeneBrainSize"), g.BrainSize, 0.03f),
|
new GenomeTemplate.Entry(Gene("GeneBrainSize"), g.BrainSize, 0.03f),
|
||||||
Numeric("GeneInsulation", g.Insulation),
|
Numeric("GeneInsulation", g.Insulation),
|
||||||
new GenomeTemplate.Entry(Gene("GeneFurColor"), g.FurColor, 0.06f),
|
new GenomeTemplate.Entry(Gene("GeneFurColor"), g.FurColor, 0.06f),
|
||||||
|
Numeric("GeneMaturityAge", g.MaturityAge),
|
||||||
Numeric("GeneLifespan", g.Lifespan),
|
Numeric("GeneLifespan", g.Lifespan),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -241,6 +241,9 @@ public sealed class AnimalGenomeDef
|
|||||||
/// <summary>Оттенок меха (0..1) — сдвиг тинта спрайта (как leafHue у растений).</summary>
|
/// <summary>Оттенок меха (0..1) — сдвиг тинта спрайта (как leafHue у растений).</summary>
|
||||||
public float FurColor { get; init; } = 0.5f;
|
public float FurColor { get; init; } = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>Возраст полового созревания (игровых дней) — порог стадии Adult.</summary>
|
||||||
|
public float MaturityAge { get; init; } = 90f;
|
||||||
|
|
||||||
/// <summary>Продолжительность жизни (игровых дней) до смерти от старости.</summary>
|
/// <summary>Продолжительность жизни (игровых дней) до смерти от старости.</summary>
|
||||||
public float Lifespan { get; init; } = 160f;
|
public float Lifespan { get; init; } = 160f;
|
||||||
|
|
||||||
@@ -255,6 +258,12 @@ public sealed class AnimalGenomeDef
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AnimalDef : PawnDef
|
public sealed class AnimalDef : PawnDef
|
||||||
{
|
{
|
||||||
|
/// <summary>Текстура взрослого самца (фаза A3); пусто — берётся <see cref="PawnDef.Texture"/> (самка/общая).</summary>
|
||||||
|
public string MaleTexture { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>Текстура детёныша/подростка (фаза A3); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
|
||||||
|
public string BabyTexture { get; init; } = "";
|
||||||
|
|
||||||
/// <summary>Базовый геном вида: центры аллелей животных генов.</summary>
|
/// <summary>Базовый геном вида: центры аллелей животных генов.</summary>
|
||||||
public AnimalGenomeDef Genome { get; init; } = new();
|
public AnimalGenomeDef Genome { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,6 +220,8 @@ public sealed class WorldScene : Scene
|
|||||||
new AnimalActionSystem(Store, _plants, Context.Clock, SecondsPerDay, _bounds)
|
new AnimalActionSystem(Store, _plants, Context.Clock, SecondsPerDay, _bounds)
|
||||||
);
|
);
|
||||||
UpdateSystems.Add(new AnimalAppearanceSystem());
|
UpdateSystems.Add(new AnimalAppearanceSystem());
|
||||||
|
UpdateSystems.Add(new AnimalGrowthSystem(Context.Clock, SecondsPerDay, _animals, CellSize));
|
||||||
|
UpdateSystems.Add(new AnimalMortalitySystem(Store));
|
||||||
|
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||||
@@ -482,7 +484,7 @@ public sealed class WorldScene : Scene
|
|||||||
}
|
}
|
||||||
|
|
||||||
var species = _animals.IndexOf(deerDef);
|
var species = _animals.IndexOf(deerDef);
|
||||||
var template = _animals[species].Template;
|
var maxAge = deerDef.Genome.Lifespan * 0.85f; // разновозрастное стадо, но не у самой смерти
|
||||||
var random = new Random(_config.Seed + 0x5EED);
|
var random = new Random(_config.Seed + 0x5EED);
|
||||||
|
|
||||||
var landCells = 0;
|
var landCells = 0;
|
||||||
@@ -520,7 +522,8 @@ public sealed class WorldScene : Scene
|
|||||||
_animals,
|
_animals,
|
||||||
species,
|
species,
|
||||||
new Vector2(px, py),
|
new Vector2(px, py),
|
||||||
template.Generate(random),
|
ageDays: random.NextSingle() * maxAge,
|
||||||
|
_animals.GenerateGenome(species, random),
|
||||||
generation: 0,
|
generation: 0,
|
||||||
CellSize
|
CellSize
|
||||||
);
|
);
|
||||||
@@ -794,13 +797,14 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
var index = _animals.IndexOf(def);
|
var index = _animals.IndexOf(def);
|
||||||
var seed = args.Length > 1 ? int.Parse(args[1]) : Random.Shared.Next();
|
var seed = args.Length > 1 ? int.Parse(args[1]) : Random.Shared.Next();
|
||||||
var genome = _animals[index].Template.Generate(new Random(seed));
|
var genome = _animals.GenerateGenome(index, new Random(seed));
|
||||||
var traits = AnimalPhenotype.FromTraits(Phenotype.Compute(genome, _animals.GeneRegistry));
|
var traits = AnimalPhenotype.FromTraits(Phenotype.Compute(genome, _animals.GeneRegistry));
|
||||||
|
var sex = AnimalFactory.IsMaleGenome(genome) ? "male" : "female";
|
||||||
|
|
||||||
console.WriteLine($"{def.DefName} (seed {seed}):");
|
console.WriteLine($"{def.DefName} ({sex}, seed {seed}):");
|
||||||
console.WriteLine(
|
console.WriteLine(
|
||||||
$" body {traits.BodySize:0.##} cells, lifespan {traits.Lifespan:0} d, "
|
$" body {traits.BodySize:0.##} cells, maturity {traits.MaturityAge:0} d, "
|
||||||
+ $"brain {traits.BrainSize:0.##}, metabolism {traits.Metabolism:0.##}"
|
+ $"lifespan {traits.Lifespan:0} d, brain {traits.BrainSize:0.##}, metabolism {traits.Metabolism:0.##}"
|
||||||
);
|
);
|
||||||
console.WriteLine(
|
console.WriteLine(
|
||||||
$" move {traits.MoveSpeed:0.##}, vision {traits.Vision:0.##}, blood {traits.BloodVolume:0.##}, "
|
$" move {traits.MoveSpeed:0.##}, vision {traits.Vision:0.##}, blood {traits.BloodVolume:0.##}, "
|
||||||
|
|||||||
@@ -18,12 +18,22 @@ public static class AnimalFactory
|
|||||||
private static readonly Color FurWarm = new(255, 226, 188);
|
private static readonly Color FurWarm = new(255, 226, 188);
|
||||||
private static readonly Color FurCool = new(206, 222, 235);
|
private static readonly Color FurCool = new(206, 222, 235);
|
||||||
|
|
||||||
/// <summary>Создаёт животное вида <paramref name="species"/> с заданным геномом и поколением.</summary>
|
/// <summary>Стадии роста животного (фаза A3) по индексам, как в <see cref="AnimalGrowth.Stage"/>.</summary>
|
||||||
|
public const int StageBaby = 0;
|
||||||
|
public const int StageJuvenile = 1;
|
||||||
|
public const int StageAdult = 2;
|
||||||
|
public const int StageSenior = 3;
|
||||||
|
|
||||||
|
// Масштаб спрайта по стадии (множитель размера тела): детёныш мельче, старик чуть усох.
|
||||||
|
private static readonly float[] StageScales = [0.45f, 0.7f, 1f, 0.92f];
|
||||||
|
|
||||||
|
/// <summary>Создаёт животное вида <paramref name="species"/> заданного возраста, генома и поколения.</summary>
|
||||||
public static Entity Create(
|
public static Entity Create(
|
||||||
EntityStore store,
|
EntityStore store,
|
||||||
AnimalSet animals,
|
AnimalSet animals,
|
||||||
int species,
|
int species,
|
||||||
Vector2 position,
|
Vector2 position,
|
||||||
|
float ageDays,
|
||||||
Genome genome,
|
Genome genome,
|
||||||
int generation,
|
int generation,
|
||||||
int cellSize
|
int cellSize
|
||||||
@@ -31,24 +41,26 @@ public static class AnimalFactory
|
|||||||
{
|
{
|
||||||
var sp = animals[species];
|
var sp = animals[species];
|
||||||
var traits = AnimalPhenotype.FromTraits(Phenotype.Compute(genome, animals.GeneRegistry));
|
var traits = AnimalPhenotype.FromTraits(Phenotype.Compute(genome, animals.GeneRegistry));
|
||||||
|
var isMale = IsMaleGenome(genome);
|
||||||
|
var stage = StageAt(ageDays, traits.MaturityAge, traits.Lifespan);
|
||||||
|
var region = RegionFor(sp, stage, isMale);
|
||||||
|
|
||||||
var sprite = new Sprite(sp.Region, GameLayers.Beings);
|
var sprite = new Sprite(region, GameLayers.Beings);
|
||||||
sprite.CenterOrigin();
|
sprite.CenterOrigin();
|
||||||
sprite.Color = FurTint(traits);
|
sprite.Color = FurTint(traits);
|
||||||
|
|
||||||
// Размер спрайта задаёт ген размера тела; деф-fallback — на случай отсутствия гена.
|
|
||||||
var sizeCells = traits.BodySize > 0f ? traits.BodySize : sp.Def.SizeCells;
|
|
||||||
|
|
||||||
return store.CreateEntity(
|
return store.CreateEntity(
|
||||||
new Transform2D(position, scale: new Vector2(cellSize * sizeCells / sp.Region.Width)),
|
new Transform2D(position, scale: new Vector2(Scale(sp, traits, stage, region, cellSize))),
|
||||||
sprite,
|
sprite,
|
||||||
new AnimalOrganism
|
new AnimalOrganism
|
||||||
{
|
{
|
||||||
Genome = genome,
|
Genome = genome,
|
||||||
Traits = traits,
|
Traits = traits,
|
||||||
Species = species,
|
Species = species,
|
||||||
|
IsMale = isMale,
|
||||||
Generation = generation,
|
Generation = generation,
|
||||||
},
|
},
|
||||||
|
new AnimalGrowth { Stage = stage, AgeDays = ageDays },
|
||||||
// Нужды стартуют полными; «мозг» решает действие сразу (DecideIn=0).
|
// Нужды стартуют полными; «мозг» решает действие сразу (DecideIn=0).
|
||||||
new AnimalNeeds
|
new AnimalNeeds
|
||||||
{
|
{
|
||||||
@@ -60,6 +72,48 @@ public static class AnimalFactory
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Пол из локуса GeneSex: наличие аллеля Y (значение ≈1) → самец; иначе самка (XX).</summary>
|
||||||
|
public static bool IsMaleGenome(Genome genome) =>
|
||||||
|
genome.Has("GeneSex") && (genome["GeneSex"].A > 0.5f || genome["GeneSex"].B > 0.5f);
|
||||||
|
|
||||||
|
/// <summary>Стадия по возрасту: Baby < 0.2·созревание, Juvenile < созревание, Adult < 0.8·жизнь, иначе Senior.</summary>
|
||||||
|
public static int StageAt(float ageDays, float maturity, float lifespan)
|
||||||
|
{
|
||||||
|
var babyEnd = MathF.Max(1f, maturity * 0.2f);
|
||||||
|
var juvEnd = MathF.Max(babyEnd + 1f, maturity);
|
||||||
|
var adultEnd = MathF.Max(juvEnd + 1f, lifespan * 0.8f);
|
||||||
|
if (ageDays < babyEnd)
|
||||||
|
{
|
||||||
|
return StageBaby;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ageDays < juvEnd)
|
||||||
|
{
|
||||||
|
return StageJuvenile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ageDays < adultEnd ? StageAdult : StageSenior;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Спрайт по стадии и полу: детёныш/подросток — baby-текстура; взрослые — по полу.</summary>
|
||||||
|
public static Texture2DRegion RegionFor(AnimalSet.Species sp, int stage, bool isMale) =>
|
||||||
|
stage <= StageJuvenile ? sp.Baby
|
||||||
|
: isMale ? sp.Male
|
||||||
|
: sp.Female;
|
||||||
|
|
||||||
|
/// <summary>Масштаб Transform2D для спрайта стадии (размер тела × множитель стадии / ширина региона).</summary>
|
||||||
|
public static float Scale(
|
||||||
|
AnimalSet.Species sp,
|
||||||
|
in AnimalPhenotype traits,
|
||||||
|
int stage,
|
||||||
|
Texture2DRegion region,
|
||||||
|
int cellSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var sizeCells = (traits.BodySize > 0f ? traits.BodySize : sp.Def.SizeCells) * StageScales[stage];
|
||||||
|
return cellSize * sizeCells / region.Width;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Цвет животного из генов: лёгкий тёплый/холодный сдвиг меха (ген furHue) поверх текстуры —
|
/// Цвет животного из генов: лёгкий тёплый/холодный сдвиг меха (ген furHue) поверх текстуры —
|
||||||
/// субтильный, чтобы не перекрашивать уже цветной спрайт зверя (как leafHue у растений).
|
/// субтильный, чтобы не перекрашивать уже цветной спрайт зверя (как leafHue у растений).
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ public struct AnimalPhenotype
|
|||||||
/// <summary>Оттенок меха (0..1) — сдвиг тинта спрайта.</summary>
|
/// <summary>Оттенок меха (0..1) — сдвиг тинта спрайта.</summary>
|
||||||
public float FurHue;
|
public float FurHue;
|
||||||
|
|
||||||
|
/// <summary>Возраст созревания (игровых дней) — порог стадии Adult.</summary>
|
||||||
|
public float MaturityAge;
|
||||||
|
|
||||||
/// <summary>Продолжительность жизни (игровых дней).</summary>
|
/// <summary>Продолжительность жизни (игровых дней).</summary>
|
||||||
public float Lifespan;
|
public float Lifespan;
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ public struct AnimalPhenotype
|
|||||||
BrainSize = T("brainSize"),
|
BrainSize = T("brainSize"),
|
||||||
Insulation = T("insulation"),
|
Insulation = T("insulation"),
|
||||||
FurHue = T("furHue"),
|
FurHue = T("furHue"),
|
||||||
|
MaturityAge = T("maturityAge"),
|
||||||
Lifespan = T("lifespan"),
|
Lifespan = T("lifespan"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -74,6 +78,24 @@ public struct AnimalOrganism : IComponent
|
|||||||
/// <summary>Индекс вида в <see cref="Content.AnimalSet"/>.</summary>
|
/// <summary>Индекс вида в <see cref="Content.AnimalSet"/>.</summary>
|
||||||
public int Species;
|
public int Species;
|
||||||
|
|
||||||
|
/// <summary>Пол: самец (XY) или самка (XX) — выведен из локуса GeneSex при создании.</summary>
|
||||||
|
public bool IsMale;
|
||||||
|
|
||||||
/// <summary>Номер поколения: max(родителей) + 1; у основателей 0.</summary>
|
/// <summary>Номер поколения: max(родителей) + 1; у основателей 0.</summary>
|
||||||
public int Generation;
|
public int Generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Состояние роста животного (фаза A3): текущая стадия и накопленный возраст в игровых днях. Плоские
|
||||||
|
/// данные — переход между стадиями (Baby→Juvenile→Adult→Senior), смену спрайта и размера выполняет
|
||||||
|
/// <see cref="AnimalGrowthSystem"/>, читая пороги из генов (возраст созревания, продолжительность жизни).
|
||||||
|
/// Зеркало <see cref="PlantGrowth"/>.
|
||||||
|
/// </summary>
|
||||||
|
public struct AnimalGrowth : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Текущая стадия: 0 Baby, 1 Juvenile, 2 Adult, 3 Senior.</summary>
|
||||||
|
public int Stage;
|
||||||
|
|
||||||
|
/// <summary>Накопленный возраст в игровых днях.</summary>
|
||||||
|
public float AgeDays;
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ public struct AnimalNeeds : IComponent
|
|||||||
/// <summary>Отдых/бодрость [0,1].</summary>
|
/// <summary>Отдых/бодрость [0,1].</summary>
|
||||||
public float Rest;
|
public float Rest;
|
||||||
|
|
||||||
|
/// <summary>Накоплено игровых дней истощения (голод/жажда на нуле); порог — смерть (A3).</summary>
|
||||||
|
public float Starve;
|
||||||
|
|
||||||
/// <summary>Наименьшая (самая острая) нужда — для выбора и внешнего вида.</summary>
|
/// <summary>Наименьшая (самая острая) нужда — для выбора и внешнего вида.</summary>
|
||||||
public readonly float Worst() => MathF.Min(Hunger, MathF.Min(Thirst, Rest));
|
public readonly float Worst() => MathF.Min(Hunger, MathF.Min(Thirst, Rest));
|
||||||
}
|
}
|
||||||
@@ -99,6 +102,12 @@ public sealed class AnimalNeedsSystem(GameClock clock, float secondsPerDay)
|
|||||||
need.Hunger = Math.Clamp(need.Hunger - HungerPerDay * metabolism * days, 0f, 1f);
|
need.Hunger = Math.Clamp(need.Hunger - HungerPerDay * metabolism * days, 0f, 1f);
|
||||||
need.Thirst = Math.Clamp(need.Thirst - ThirstPerDay * metabolism * days, 0f, 1f);
|
need.Thirst = Math.Clamp(need.Thirst - ThirstPerDay * metabolism * days, 0f, 1f);
|
||||||
need.Rest = Math.Clamp(need.Rest - RestPerDay * metabolism * days, 0f, 1f);
|
need.Rest = Math.Clamp(need.Rest - RestPerDay * metabolism * days, 0f, 1f);
|
||||||
|
|
||||||
|
// Истощение: голод/жажда на нуле копят счётчик смерти (A3), иначе он рассасывается вдвое быстрее.
|
||||||
|
need.Starve =
|
||||||
|
need.Hunger <= 0f || need.Thirst <= 0f
|
||||||
|
? need.Starve + days
|
||||||
|
: MathF.Max(0f, need.Starve - days * 2f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,3 +450,97 @@ public sealed class AnimalAppearanceSystem : QuerySystem<AnimalNeeds, AnimalOrga
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Рост и стадии (фаза A3): копит возраст в игровых днях, переключает стадию (Baby→Juvenile→Adult→
|
||||||
|
/// Senior) по порогам из генов (возраст созревания, продолжительность жизни) и при смене стадии меняет
|
||||||
|
/// спрайт (детёныш/самка/самец) и размер. Зеркало <see cref="PlantGrowthSystem"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AnimalGrowthSystem(
|
||||||
|
GameClock clock,
|
||||||
|
float secondsPerDay,
|
||||||
|
AnimalSet animals,
|
||||||
|
int cellSize
|
||||||
|
) : QuerySystem<AnimalGrowth, AnimalOrganism, Transform2D, Sprite>
|
||||||
|
{
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
var days = clock.DeltaTime / secondsPerDay;
|
||||||
|
if (days <= 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (growths, organisms, transforms, sprites, _) in Query.Chunks)
|
||||||
|
{
|
||||||
|
var g = growths.Span;
|
||||||
|
var o = organisms.Span;
|
||||||
|
var t = transforms.Span;
|
||||||
|
var s = sprites.Span;
|
||||||
|
for (var i = 0; i < g.Length; i++)
|
||||||
|
{
|
||||||
|
ref var grow = ref g[i];
|
||||||
|
grow.AgeDays += days;
|
||||||
|
ref readonly var org = ref o[i];
|
||||||
|
var stage = AnimalFactory.StageAt(
|
||||||
|
grow.AgeDays,
|
||||||
|
org.Traits.MaturityAge,
|
||||||
|
org.Traits.Lifespan
|
||||||
|
);
|
||||||
|
if (stage == grow.Stage)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
grow.Stage = stage;
|
||||||
|
var sp = animals[org.Species];
|
||||||
|
var region = AnimalFactory.RegionFor(sp, stage, org.IsMale);
|
||||||
|
ref var sprite = ref s[i];
|
||||||
|
sprite.Region = region;
|
||||||
|
sprite.Origin = new Vector2(region.Width / 2f, region.Height / 2f);
|
||||||
|
t[i].Scale = new Vector2(AnimalFactory.Scale(sp, org.Traits, stage, region, cellSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Смертность (фаза A3): простая гибель с деспавном — от старости (возраст превысил продолжительность
|
||||||
|
/// жизни) или от истощения (счётчик голода/жажды превысил порог). Замыкает контур ёмкости среды.
|
||||||
|
/// На A5 «апгрейдится» в смерть через здоровье и оставит труп вместо деспавна.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AnimalMortalitySystem : BaseSystem
|
||||||
|
{
|
||||||
|
private const float LethalStarveDays = 3f;
|
||||||
|
|
||||||
|
private readonly ArchetypeQuery<AnimalGrowth, AnimalOrganism, AnimalNeeds> _query;
|
||||||
|
private readonly List<Entity> _deaths = [];
|
||||||
|
|
||||||
|
public AnimalMortalitySystem(EntityStore store)
|
||||||
|
{
|
||||||
|
_query = store.Query<AnimalGrowth, AnimalOrganism, AnimalNeeds>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdateGroup()
|
||||||
|
{
|
||||||
|
_deaths.Clear();
|
||||||
|
foreach (var (growths, organisms, needs, entities) in _query.Chunks)
|
||||||
|
{
|
||||||
|
var g = growths.Span;
|
||||||
|
var o = organisms.Span;
|
||||||
|
var n = needs.Span;
|
||||||
|
for (var i = 0; i < g.Length; i++)
|
||||||
|
{
|
||||||
|
if (g[i].AgeDays > o[i].Traits.Lifespan || n[i].Starve >= LethalStarveDays)
|
||||||
|
{
|
||||||
|
_deaths.Add(entities.EntityAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entity in _deaths)
|
||||||
|
{
|
||||||
|
entity.DeleteEntity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user