using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Input;
using MrGameEng.Audio;
using MrGameEng.Core;
using MrGameEng.Graphics;
using MrGameEng.Input;
namespace MrGameEng.Sample;
/// Управление игроком (WASD/стрелки) и прыжок-писк на пробел.
public sealed class PlayerControlSystem(
Entity player, ActionMap actions, AudioManager audio, SoundEffect beep) : BaseSystem
{
protected override void OnUpdateGroup()
{
ref var transform = ref player.GetComponent();
var move = new Vector2(
actions.GetAxis(SampleAction.MoveLeft, SampleAction.MoveRight),
actions.GetAxis(SampleAction.MoveUp, SampleAction.MoveDown));
if (move != Vector2.Zero)
{
move.Normalize();
transform.Position += move * 260f * Tick.deltaTime;
}
if (actions.IsPressed(SampleAction.Jump))
{
audio.Play(beep);
}
}
}
/// Камера следует за игроком; колесо — зум, Q/E — поворот.
public sealed class CameraControlSystem(Entity cameraEntity, Entity player, InputManager input) : BaseSystem
{
protected override void OnUpdateGroup()
{
ref var camera = ref cameraEntity.GetComponent();
var target = player.GetComponent().Position;
camera.Position = Vector2.Lerp(camera.Position, target, Math.Min(1f, 6f * Tick.deltaTime));
camera.Zoom = Math.Clamp(camera.Zoom * (1f + input.WheelDelta * 0.001f), 0.2f, 5f);
var rotate = (input.IsKeyDown(Keys.E) ? 1f : 0f) - (input.IsKeyDown(Keys.Q) ? 1f : 0f);
camera.Rotation += rotate * 1.2f * Tick.deltaTime;
}
}
/// Отскок сущностей со скоростью от границ мира.
public sealed class BounceSystem(RectF bounds) : QuerySystem
{
protected override void OnUpdate()
{
var delta = Tick.deltaTime;
foreach (var (transforms, velocities, _) in Query.Chunks)
{
var t = transforms.Span;
var v = velocities.Span;
for (var i = 0; i < t.Length; i++)
{
ref var position = ref t[i].Position;
ref var velocity = ref v[i].Value;
position += velocity * delta;
if (position.X < bounds.Left || position.X > bounds.Right)
{
velocity.X = -velocity.X;
position.X = Math.Clamp(position.X, bounds.Left, bounds.Right);
}
if (position.Y < bounds.Top || position.Y > bounds.Bottom)
{
velocity.Y = -velocity.Y;
position.Y = Math.Clamp(position.Y, bounds.Top, bounds.Bottom);
}
}
}
}
}
/// Пауза (P), музыка (M), переключение сцены с переходом (Tab).
public sealed class SceneHotkeysSystem(
EngineContext context, ActionMap actions, Func nextScene, Transition transition) : BaseSystem
{
protected override void OnUpdateGroup()
{
if (actions.IsPressed(SampleAction.Pause))
{
context.Clock.TimeScale = context.Clock.TimeScale > 0f ? 0f : 1f;
}
if (actions.IsPressed(SampleAction.ToggleMusic))
{
var music = context.Services.Get().Music;
if (music.IsPlaying)
{
music.Pause();
}
else
{
music.Resume();
}
}
if (actions.IsPressed(SampleAction.SwitchScene) && !context.Scenes.IsTransitioning)
{
context.Scenes.Switch(nextScene(), transition);
}
}
}
/// FPS и статистика рендера в заголовке окна (обновляется 4 раза в секунду).
public sealed class TitleStatsSystem(EngineContext context, Renderer2D renderer, string sceneName) : BaseSystem
{
private float _accumulated;
private int _frames;
protected override void OnUpdateGroup()
{
_accumulated += context.Clock.UnscaledDeltaTime;
_frames++;
if (_accumulated < 0.25f)
{
return;
}
var fps = _frames / _accumulated;
_accumulated = 0f;
_frames = 0;
context.Services.Get().Title =
$"MrGameEng Sample — {sceneName} | {fps:F0} FPS | sprites: {renderer.SubmittedSprites} | culled: {renderer.CulledSprites} | draw calls: {renderer.DrawCalls}";
}
}
/// Общая настройка ввода для сцен сэмпла.
public static class SampleInput
{
public static ActionMap CreateActions(InputManager input) =>
new ActionMap(input)
.Bind(SampleAction.MoveLeft, Keys.A)
.Bind(SampleAction.MoveLeft, Keys.Left)
.Bind(SampleAction.MoveRight, Keys.D)
.Bind(SampleAction.MoveRight, Keys.Right)
.Bind(SampleAction.MoveUp, Keys.W)
.Bind(SampleAction.MoveUp, Keys.Up)
.Bind(SampleAction.MoveDown, Keys.S)
.Bind(SampleAction.MoveDown, Keys.Down)
.Bind(SampleAction.Jump, Keys.Space)
.Bind(SampleAction.Pause, Keys.P)
.Bind(SampleAction.ToggleMusic, Keys.M)
.Bind(SampleAction.SwitchScene, Keys.Tab);
}