diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index 4412c46..6529dd8 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -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( diff --git a/src/LittleSim/Sim/AnimalSystems.cs b/src/LittleSim/Sim/AnimalSystems.cs index 965f1ca..595d782 100644 --- a/src/LittleSim/Sim/AnimalSystems.cs +++ b/src/LittleSim/Sim/AnimalSystems.cs @@ -1688,6 +1688,52 @@ public sealed class AnimalFacingSystem(AnimalSet animals, int cellSize) } } +/// +/// «Раскачка» при ходьбе (презентация): идущий зверь покачивается из стороны в сторону (наклон спрайта +/// через ), стоящий — плавно выпрямляется. Делает движение живее, не +/// затрагивая симуляцию (она вращение не использует). Фаза покачивания идёт по игровому времени (стоп на +/// паузе, быстрее на ускорении) и сдвинута по id особи — звери качаются вразнобой. Граница sim/presentation +/// цела: система пишет только поворот трансформа. +/// +public sealed class AnimalBobSystem(GameClock clock) : QuerySystem +{ + 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; // у цели/стоя — плавно к вертикали + } + } + } + } +} + /// /// Рост и стадии (фаза A3): копит возраст в игровых днях, переключает стадию (Baby→Juvenile→Adult→ /// Senior) по порогам из генов и при смене стадии меняет спрайт (детёныш/самка/самец) и размер.