Merge branch 'animal-bob' into main

Walking sway for animals (AnimalBobSystem) to make movement feel livelier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 23:29:15 +03:00
co-authored by Claude Opus 4.8
2 changed files with 47 additions and 0 deletions
+1
View File
@@ -323,6 +323,7 @@ public sealed class WorldScene : Scene
UpdateSystems.Add(new AnimalAppearanceSystem(_needs));
UpdateSystems.Add(new AnimalGrowthSystem(Context.Clock, SecondsPerDay, _animals, CellSize));
UpdateSystems.Add(new AnimalFacingSystem(_animals, CellSize));
UpdateSystems.Add(new AnimalBobSystem(Context.Clock));
UpdateSystems.Add(new AnimalMortalitySystem(Store, _animals, CellSize));
UpdateSystems.Add(new CorpseSystem(Store, Context.Clock, SecondsPerDay, climate));
UpdateSystems.Add(
+46
View File
@@ -1688,6 +1688,52 @@ public sealed class AnimalFacingSystem(AnimalSet animals, int cellSize)
}
}
/// <summary>
/// «Раскачка» при ходьбе (презентация): идущий зверь покачивается из стороны в сторону (наклон спрайта
/// через <see cref="Transform2D.Rotation"/>), стоящий — плавно выпрямляется. Делает движение живее, не
/// затрагивая симуляцию (она вращение не использует). Фаза покачивания идёт по игровому времени (стоп на
/// паузе, быстрее на ускорении) и сдвинута по id особи — звери качаются вразнобой. Граница sim/presentation
/// цела: система пишет только поворот трансформа.
/// </summary>
public sealed class AnimalBobSystem(GameClock clock) : QuerySystem<AnimalBrain, Transform2D>
{
private const float SwaySpeed = 9f; // скорость набега фазы (рад/сек игрового времени)
private const float SwayAmplitude = 0.08f; // амплитуда наклона (рад ≈ 4.6°)
private const float MinMoveSq = 0.5f * 0.5f; // ближе к цели — считаем стоящим
private const float UprightDecay = 0.85f; // как быстро выпрямляется стоя (за кадр)
private float _phase;
protected override void OnUpdate()
{
var dt = clock.DeltaTime;
if (dt <= 0f)
{
return; // пауза — не качаем и не выпрямляем
}
_phase += dt * SwaySpeed;
foreach (var (brains, transforms, entities) in Query.Chunks)
{
var b = brains.Span;
var t = transforms.Span;
for (var i = 0; i < b.Length; i++)
{
ref var rot = ref t[i].Rotation;
if ((b[i].Target - t[i].Position).LengthSquared() > MinMoveSq)
{
var offset = entities.EntityAt(i).Id * 0.7f; // десинхрон между особями
rot = MathF.Sin(_phase + offset) * SwayAmplitude;
}
else
{
rot *= UprightDecay; // у цели/стоя — плавно к вертикали
}
}
}
}
}
/// <summary>
/// Рост и стадии (фаза A3): копит возраст в игровых днях, переключает стадию (Baby→Juvenile→Adult→
/// Senior) по порогам из генов и при смене стадии меняет спрайт (детёныш/самка/самец) и размер.