chore: updated engine

This commit is contained in:
2025-04-11 20:02:29 +03:00
parent 73ae55e1d4
commit 67e46cdaf3
37 changed files with 842 additions and 803 deletions

View File

@@ -0,0 +1,81 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Abstract;
namespace Pong.Behaviours;
public class BallBehaviour : Behaviour2D
{
public Vector2D StartDirection { get; private set; } = Vector2D.Zero;
public float Speed { get; set; } = 500f;
public float SpeedUpMultiplier { get; set; } = .0125f;
private readonly Random random = new();
private IRigidBody2D rigidBody = null!;
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out IRigidBody2D? foundRigidBody))
throw new Exception($"{nameof(IRigidBody2D)} is missing on {HierarchyObject.Name}.");
if (!BehaviourController.TryGetBehaviour(out ICollider2D? foundCollider))
throw new Exception($"{nameof(ICollider2D)} is missing on {HierarchyObject.Name}.");
foundCollider.OnCollisionDetected += OnCollisionDetected;
rigidBody = foundRigidBody;
if (HierarchyObject.GameManager.TryFindBehaviour(out PongManagerBehaviour? pongManager))
{
pongManager.OnReset += ResetBall;
pongManager.OnScored += ResetBall;
pongManager.OnFinished += DisableBall;
}
}
private void DisableBall(PongManagerBehaviour pongManager)
{
Transform.Position = Vector2D.Zero;
rigidBody.Velocity = Vector2D.Zero;
}
private void ResetBall(PongManagerBehaviour pongManager)
{
StateEnable.Enabled = true;
Transform.Position = Vector2D.Zero;
rigidBody.Velocity = GetRandomDirection() * Speed;
}
private Vector2D GetRandomDirection()
{
const float AllowedRadians = 45f * Syntriax.Engine.Core.Math.DegreeToRadian;
float rotation = (float)random.NextDouble() * 2f * AllowedRadians - AllowedRadians;
bool isBackwards = (random.Next() % 2) == 1;
return Vector2D.Right.Rotate(isBackwards ? rotation + Syntriax.Engine.Core.Math.PI : rotation);
}
protected override void OnUpdate()
{
if (rigidBody.Velocity.MagnitudeSquared <= 0.01f)
return;
Vector2D speedUp = rigidBody.Velocity.Normalized * GameManager.Time.DeltaTime;
rigidBody.Velocity += speedUp * SpeedUpMultiplier;
}
private void OnCollisionDetected(ICollider2D collider2D, CollisionDetectionInformation information)
{
if (Syntriax.Engine.Core.Math.Abs(information.Normal.Dot(Vector2D.Right)) > .25)
rigidBody.Velocity = information.Detected.Transform.Position.FromTo(information.Detector.Transform.Position).Normalized * rigidBody.Velocity.Magnitude;
else
rigidBody.Velocity = rigidBody.Velocity.Reflect(information.Normal);
}
public BallBehaviour() { }
public BallBehaviour(Vector2D startDirection, float speed)
{
StartDirection = Vector2D.Normalize(startDirection);
Speed = speed;
}
}

View File

@@ -0,0 +1,71 @@
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class CameraController : Behaviour
{
private MonoGameCamera2DBehaviour cameraBehaviour = null!;
private IButtonInputs<Keys> buttonInputs = null!;
private float defaultZoomLevel = 1f;
protected override void OnFirstActiveFrame()
{
if (BehaviourController.TryGetBehaviour(out MonoGameCamera2DBehaviour? foundCameraBehaviour))
cameraBehaviour = foundCameraBehaviour;
cameraBehaviour ??= BehaviourController.AddBehaviour<MonoGameCamera2DBehaviour>();
buttonInputs = GameManager.FindBehaviour<IButtonInputs<Keys>>() ?? throw new("Unable to find inputs");
buttonInputs.RegisterOnPress(Keys.F, SwitchToFullScreen);
buttonInputs.RegisterOnPress(Keys.R, ResetCamera);
}
protected override void OnUpdate()
{
if (buttonInputs.IsPressed(Keys.U))
cameraBehaviour.Zoom += GameManager.Time.DeltaTime * 5f;
if (buttonInputs.IsPressed(Keys.J))
cameraBehaviour.Zoom -= GameManager.Time.DeltaTime * 5f;
if (buttonInputs.IsPressed(Keys.NumPad8)) cameraBehaviour.Transform.LocalPosition += Vector2D.Up * GameManager.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.NumPad2)) cameraBehaviour.Transform.LocalPosition -= Vector2D.Up * GameManager.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.NumPad6)) cameraBehaviour.Transform.LocalPosition += Vector2D.Right * GameManager.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.NumPad4)) cameraBehaviour.Transform.LocalPosition -= Vector2D.Right * GameManager.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.Q))
cameraBehaviour.Transform.Rotation += GameManager.Time.DeltaTime * 45f;
if (buttonInputs.IsPressed(Keys.E))
cameraBehaviour.Transform.Rotation -= GameManager.Time.DeltaTime * 45f;
}
private void SwitchToFullScreen(IButtonInputs<Keys> inputs, Keys keys)
{
if (cameraBehaviour.Graphics.IsFullScreen)
return;
cameraBehaviour.Graphics.PreferMultiSampling = false;
cameraBehaviour.Graphics.PreferredBackBufferWidth = cameraBehaviour.Graphics.GraphicsDevice.Adapter.CurrentDisplayMode.Width;
cameraBehaviour.Graphics.PreferredBackBufferHeight = cameraBehaviour.Graphics.GraphicsDevice.Adapter.CurrentDisplayMode.Height;
cameraBehaviour.Graphics.IsFullScreen = true;
cameraBehaviour.Graphics.ApplyChanges();
float previousScreenSize = Math.Sqrt(Math.Sqr(cameraBehaviour.Viewport.Width) + Math.Sqr(cameraBehaviour.Viewport.Height));
float currentScreenSize = Math.Sqrt(Math.Sqr(cameraBehaviour.Graphics.GraphicsDevice.Viewport.Width) + Math.Sqr(cameraBehaviour.Graphics.GraphicsDevice.Viewport.Height));
defaultZoomLevel /= previousScreenSize / currentScreenSize;
cameraBehaviour.Zoom /= previousScreenSize / currentScreenSize;
cameraBehaviour.Viewport = cameraBehaviour.Graphics.GraphicsDevice.Viewport;
}
private void ResetCamera(IButtonInputs<Keys> inputs, Keys keys)
{
cameraBehaviour.Zoom = defaultZoomLevel;
cameraBehaviour.Transform.LocalPosition = Vector2D.Zero;
cameraBehaviour.Transform.LocalRotation = 0f;
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Pong.Behaviours;
public class CircleBehaviour : Syntriax.Engine.Physics2D.Collider2DCircleBehaviour, IDisplayableShape
{
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;
public void Draw(ShapeBatch shapeBatch)
{
if (!IsActive)
return;
Recalculate();
shapeBatch.BorderCircle(CircleWorld.Center.ToDisplayVector2(), CircleWorld.Radius, Color);
}
public CircleBehaviour(Circle circle) : base(circle) { }
public CircleBehaviour(Circle circle, float thickness) : base(circle) { Thickness = thickness; }
public CircleBehaviour(Circle circle, Color color) : base(circle) { Color = color; }
public CircleBehaviour(Circle circle, Color color, float thickness) : base(circle) { Thickness = thickness; Color = color; }
}

View File

@@ -0,0 +1,102 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Pong.Behaviours;
public class MonoGameCamera2DBehaviour(GraphicsDeviceManager Graphics) : Behaviour2D, ICamera2D
{
public event OnMatrixTransformChangedDelegate? OnMatrixTransformChanged = null;
public event OnViewportChangedDelegate? OnViewportChanged = null;
public event OnZoomChangedDelegate? OnZoomChanged = null;
private Matrix _matrixTransform = Matrix.Identity;
private Viewport _viewport = default;
private float _zoom = 1f;
public GraphicsDeviceManager Graphics { get; } = Graphics;
public Matrix MatrixTransform
{
get => _matrixTransform;
set
{
if (_matrixTransform == value)
return;
_matrixTransform = value;
OnMatrixTransformChanged?.Invoke(this);
}
}
public Vector2D Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport
{
get => _viewport;
set
{
if (_viewport.Equals(value))
return;
_viewport = value;
OnViewportChanged?.Invoke(this);
}
}
public float Zoom
{
get => _zoom;
set
{
float newValue = Math.Max(0.1f, value);
if (_zoom == newValue)
return;
_zoom = newValue;
OnZoomChanged?.Invoke(this);
}
}
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Vector2D ScreenToWorldPosition(Vector2D screenPosition)
{
Vector2D worldPosition = Vector2.Transform(screenPosition.ToVector2(), Matrix.Invert(MatrixTransform)).ToVector2D();
return worldPosition.Scale(EngineConverter.screenScale);
}
public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{
Vector2D screenPosition = Vector2.Transform(worldPosition.ToVector2(), MatrixTransform).ToVector2D();
return screenPosition.Scale(EngineConverter.screenScale);
}
protected override void OnFirstActiveFrame()
=> Viewport = Graphics.GraphicsDevice.Viewport;
protected override void OnPreDraw()
{
MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation * Math.DegreeToRadian) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f));
}
public delegate void OnMatrixTransformChangedDelegate(MonoGameCamera2DBehaviour sender);
public delegate void OnViewportChangedDelegate(MonoGameCamera2DBehaviour sender);
public delegate void OnZoomChangedDelegate(MonoGameCamera2DBehaviour sender);
}

View File

@@ -0,0 +1,53 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Abstract;
namespace Pong.Behaviours;
public class MovementBallBehaviour : Behaviour2D
{
public Vector2D StartDirection { get; private set; } = Vector2D.Zero;
public float Speed { get; set; } = 500f;
public float SpeedUpMultiplier { get; set; } = .0125f;
private IRigidBody2D rigidBody = null!;
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out IRigidBody2D? foundRigidBody))
throw new Exception($"{nameof(IRigidBody2D)} is missing on {HierarchyObject.Name}.");
if (!BehaviourController.TryGetBehaviour(out ICollider2D? foundCollider))
throw new Exception($"{nameof(ICollider2D)} is missing on {HierarchyObject.Name}.");
foundRigidBody.Velocity = StartDirection * Speed;
foundCollider.OnCollisionDetected += OnCollisionDetected;
rigidBody = foundRigidBody;
}
protected override void OnUpdate()
{
if (rigidBody.Velocity.MagnitudeSquared <= 0.01f)
return;
Vector2D speedUp = rigidBody.Velocity.Normalized * GameManager.Time.DeltaTime;
rigidBody.Velocity += speedUp * SpeedUpMultiplier;
}
private void OnCollisionDetected(ICollider2D collider2D, CollisionDetectionInformation information)
{
if (Syntriax.Engine.Core.Math.Abs(information.Normal.Dot(Vector2D.Right)) > .25)
rigidBody.Velocity = information.Detector.Transform.Position.FromTo(information.Detected.Transform.Position).Normalized * rigidBody.Velocity.Magnitude;
else
rigidBody.Velocity = rigidBody.Velocity.Reflect(information.Normal);
}
public MovementBallBehaviour() { }
public MovementBallBehaviour(Vector2D startDirection, float speed)
{
StartDirection = Vector2D.Normalize(startDirection);
Speed = speed;
}
}

View File

@@ -0,0 +1,60 @@
using System;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : Behaviour2D
{
private Keys Up { get; } = Up;
private Keys Down { get; } = Down;
public float High { get; } = High;
public float Low { get; } = Low;
public float Speed { get; set; } = Speed;
private bool isUpPressed = false;
private bool isDownPressed = false;
private IButtonInputs<Keys> inputs = null!;
protected override void OnUpdate()
{
if (isUpPressed && isDownPressed)
return;
if (isUpPressed)
Transform.Position = Transform.Position + Vector2D.Up * GameManager.Time.DeltaTime * Speed;
else if (isDownPressed)
Transform.Position = Transform.Position + -Vector2D.Up * GameManager.Time.DeltaTime * Speed;
Transform.Position = new Vector2D(Transform.Position.X, MathF.Max(MathF.Min(Transform.Position.Y, High), Low));
}
protected override void OnFirstActiveFrame()
{
inputs = GameManager.FindBehaviour<IButtonInputs<Keys>>() ?? throw new("Unable to find inputs");
inputs.RegisterOnPress(Up, OnUpPressed);
inputs.RegisterOnRelease(Up, OnUpReleased);
inputs.RegisterOnPress(Down, OnDownPressed);
inputs.RegisterOnRelease(Down, OnDownReleased);
}
protected override void OnFinalize()
{
inputs.UnregisterOnPress(Up, OnUpPressed);
inputs.UnregisterOnRelease(Up, OnUpReleased);
inputs.UnregisterOnPress(Down, OnDownPressed);
inputs.UnregisterOnRelease(Down, OnDownReleased);
}
private void OnUpPressed(IButtonInputs<Keys> inputs, Keys keys) => isUpPressed = true;
private void OnUpReleased(IButtonInputs<Keys> inputs, Keys keys) => isUpPressed = false;
private void OnDownPressed(IButtonInputs<Keys> inputs, Keys keys) => isDownPressed = true;
private void OnDownReleased(IButtonInputs<Keys> inputs, Keys keys) => isDownPressed = false;
}

View File

@@ -0,0 +1,61 @@
using System;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class PongManagerBehaviour : Behaviour
{
public Action<PongManagerBehaviour>? OnReset { get; set; } = null;
public Action<PongManagerBehaviour>? OnFinished { get; set; } = null;
public Action<PongManagerBehaviour>? OnScored { get; set; } = null;
public int ScoreLeft { get; private set; } = 0;
public int ScoreRight { get; private set; } = 0;
public int ScoreSum => ScoreLeft + ScoreRight;
public int WinScore { get; } = 5;
public PongManagerBehaviour() => WinScore = 5;
public PongManagerBehaviour(int winScore) => WinScore = winScore;
protected override void OnFirstActiveFrame()
{
var buttonInputs = GameManager.FindBehaviour<IButtonInputs<Keys>>() ?? throw new("Unable to find inputs");
buttonInputs.RegisterOnRelease(Keys.Space, (_, _1) => Reset());
}
public void ScoreToLeft()
{
ScoreLeft++;
OnScored?.Invoke(this);
CheckFinish();
}
public void ScoreToRight()
{
ScoreRight++;
OnScored?.Invoke(this);
CheckFinish();
}
public void Reset()
{
ScoreLeft = ScoreRight = 0;
OnReset?.Invoke(this);
}
private void CheckFinish()
{
int halfwayScore = (int)(WinScore * .5f);
if (ScoreLeft > halfwayScore || ScoreRight > halfwayScore)
OnFinished?.Invoke(this);
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class ShapeAABBBehaviour : Behaviour2D, IDisplayableShape
{
private IShapeCollider2D? shapeCollider = null;
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;
public bool display = true;
protected override void OnFirstActiveFrame()
{
BehaviourController.TryGetBehaviour(out shapeCollider);
if (BehaviourController.TryGetBehaviour(out IButtonInputs<Microsoft.Xna.Framework.Input.Keys>? keys))
keys.RegisterOnPress(Microsoft.Xna.Framework.Input.Keys.D, (_1, _2) => display = !display);
}
public void Draw(ShapeBatch shapeBatch)
{
if (!display)
return;
if (shapeCollider is null)
return;
AABB aabb = AABB.FromVectors(shapeCollider.ShapeWorld);
shapeBatch.BorderCircle(aabb.Center.ToDisplayVector2(), 7.5f, Color.Beige);
shapeBatch.DrawRectangle(aabb.Center.ApplyDisplayScale().Subtract(aabb.SizeHalf).ToVector2(), aabb.Size.ToVector2(), Color.Transparent, Color.Blue);
}
public ShapeAABBBehaviour() { }
public ShapeAABBBehaviour(float Thickness) { this.Thickness = Thickness; }
public ShapeAABBBehaviour(Color color) { Color = color; }
public ShapeAABBBehaviour(Color color, float Thickness) { this.Thickness = Thickness; Color = color; }
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Pong.Behaviours;
public class ShapeBehaviour : Syntriax.Engine.Physics2D.Collider2DShapeBehaviour, IDisplayableShape
{
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;
public void Draw(ShapeBatch shapeBatch)
{
if (!IsActive)
return;
Recalculate();
int count = ShapeWorld.Vertices.Count;
for (int i = 0; i < count - 1; i++)
shapeBatch.DrawLine(ShapeWorld[i].ToDisplayVector2(), ShapeWorld[i + 1].ToDisplayVector2(), Thickness, Color, Color);
shapeBatch.DrawLine(ShapeWorld[0].ToDisplayVector2(), ShapeWorld[^1].ToDisplayVector2(), Thickness, Color, Color);
}
public ShapeBehaviour(Shape2D shape) : base(shape) { }
public ShapeBehaviour(Shape2D shape, float thickness) : base(shape) { Thickness = thickness; }
public ShapeBehaviour(Shape2D shape, Color color) : base(shape) { Color = color; }
public ShapeBehaviour(Shape2D shape, Color color, float thickness) : base(shape) { Thickness = thickness; Color = color; }
}

View File

@@ -0,0 +1,25 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Pong.Behaviours;
public class TextBehaviour : Behaviour2D, IDisplayableSprite
{
public SpriteFont? Font { get; set; } = null;
public int Size { get; set; } = 16;
public string Text { get; set; } = string.Empty;
public void Draw(SpriteBatch spriteBatch)
{
if (!IsActive || Font is null)
return;
spriteBatch.DrawString(Font, Text, Transform.Position.ToDisplayVector2(), Color.White, Transform.Rotation, Vector2.One * .5f, Transform.Scale.Magnitude, SpriteEffects.None, 0f);
}
public TextBehaviour() { }
public TextBehaviour(SpriteFont font) => Font = font;
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class TextScoreBehaviour : TextBehaviour
{
public bool IsLeft { get; }
private PongManagerBehaviour? pongManager = null;
protected override void OnFirstActiveFrame()
{
if (!HierarchyObject.GameManager.TryFindBehaviour(out pongManager))
return;
pongManager.OnScored += UpdateScores;
pongManager.OnReset += UpdateScores;
UpdateScores(pongManager);
}
private void UpdateScores(PongManagerBehaviour pongManager)
{
if (IsLeft)
Text = pongManager.ScoreLeft.ToString();
else
Text = pongManager.ScoreRight.ToString();
}
public TextScoreBehaviour(bool IsLeft) => this.IsLeft = IsLeft;
public TextScoreBehaviour(bool IsLeft, SpriteFont font) : base(font) => this.IsLeft = IsLeft;
}

View File

@@ -0,0 +1,19 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D.Abstract;
namespace Pong.Behaviours;
public class WallScoreBehaviour(Action OnCollision) : Behaviour2D
{
private Action OnCollision { get; } = OnCollision;
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out ICollider2D? collider2D))
return;
collider2D.OnCollisionDetected += (_, _1) => OnCollision?.Invoke();
}
}