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 и статистика рендера: в HUD-лейбл и в заголовок окна (4 раза в секунду). public sealed class StatsSystem( EngineContext context, Renderer2D renderer, string sceneName, Myra.Graphics2D.UI.Label? hudLabel = null) : 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; var stats = $"{fps:F0} FPS | sprites: {renderer.SubmittedSprites} | culled: {renderer.CulledSprites} | draw calls: {renderer.DrawCalls}"; context.Services.Get().Title = $"MrGameEng Sample — {sceneName} | {stats}"; if (hudLabel is not null) { hudLabel.Text = $"{sceneName}\n{stats}"; } } } /// Общая настройка ввода для сцен сэмпла. 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); }