Compare commits

...

72 Commits

Author SHA1 Message Date
Syntriax 73ae55e1d4 refactor: Actions to Delegates 2024-07-15 01:17:05 +03:00
Syntriax a35e25eb31 refactor: Renamed Cacher to Collector 2024-02-09 17:52:23 +03:00
Syntriax 775f24c560 chore: Engine Update 2024-02-09 11:53:57 +03:00
Syntriax 257e414c2a feat: CameraController Rotated Movement 2024-02-01 18:50:06 +03:00
Syntriax 0cb8170530 chore: Engine Time Update 2024-02-01 18:47:27 +03:00
Syntriax bbf71f0be6 chore: Engine Update 2024-02-01 15:37:16 +03:00
Syntriax 41a17219b6 chore: Engine Update 2024-02-01 15:35:40 +03:00
Syntriax 29e79cdf26 chore: Engine Update 2024-02-01 14:52:12 +03:00
Syntriax b7a4fe9cb0 chore: Engine Update 2024-02-01 14:43:13 +03:00
Syntriax da83802427 chore: Engine Update 2024-02-01 11:33:55 +03:00
Syntriax 364c383e08 chore: ICamera2D to Engine 2024-01-31 17:30:37 +03:00
Syntriax 1fd8060857 feat: Applied ICamera2D 2024-01-31 17:28:41 +03:00
Syntriax 35933dfd14 feat: ICamera2D 2024-01-31 17:23:24 +03:00
Syntriax d66b119119 refactor: Constructor Placements 2024-01-31 14:40:14 +03:00
Syntriax e7ee460323 feat: Random Ball Start Direction 2024-01-31 13:56:32 +03:00
Syntriax e86cf71010 fix: Build Errors 2024-01-31 13:43:04 +03:00
Syntriax a357f0fdcb refactor: Removed Unnecessary Graphics Folder 2024-01-31 12:00:14 +03:00
Syntriax ff3202eda9 refactor: Renamed Behaviours 2024-01-31 11:52:22 +03:00
Syntriax 8929d3282f refactor: Removed Unused Behaviours 2024-01-31 11:50:38 +03:00
Syntriax bc7925dbab refactor: Text Behaviour Font Constructor 2024-01-31 11:23:07 +03:00
Syntriax 80ee5d760a refactor: Unnecessary Declarations Removed 2024-01-31 11:20:14 +03:00
Syntriax de2d5fb446 refactor: Removed Static GamePong Fields 2024-01-31 11:06:42 +03:00
Syntriax 753c0031cd feat: Upgraded Engine to BehaviourCachers 2024-01-31 10:09:41 +03:00
Syntriax a734f52b38 refactor: Removed Unnecessary Camera Viewport Assignment 2024-01-30 17:31:25 +03:00
Syntriax 4cb9c7dc8d refactor: Shortened Code 2024-01-30 17:03:04 +03:00
Syntriax fe4618abd3 fix: Warning Fix 2024-01-30 15:14:45 +03:00
Syntriax 283556d329 refactor: Game1 to GamePong 2024-01-30 15:14:09 +03:00
Syntriax 5ef3d491c3 refactor: Math.Pow to Math.Sqr 2024-01-30 15:09:04 +03:00
Syntriax 46f589d3aa feat: Camera Reset 2024-01-30 14:35:24 +03:00
Syntriax b7d49e41c3 refactor: General 2024-01-30 14:11:58 +03:00
Syntriax 677fec7acc refactor: Pong Reset Key Press Moved Into PongScoreboard 2024-01-30 13:22:29 +03:00
Syntriax a9c7b8b660 refactor: Camera Controller 2024-01-30 13:16:07 +03:00
Syntriax 958a75552f refactor: MonoGameCamera 2024-01-30 12:07:25 +03:00
Syntriax 3717701171 refactor: Engine.Core ICamera Removal 2024-01-30 12:03:31 +03:00
Syntriax 4e95a3a420 feat: Ball Bounce Depending on Side Distance 2024-01-29 17:58:42 +03:00
Syntriax ad60412d5f feat: Ball Speed Up Over Time 2024-01-29 16:16:58 +03:00
Syntriax 0b2158cc7b fix: Ball Not Resetting Position On Reset 2024-01-28 16:25:53 +03:00
Syntriax 3580469a07 refactor: Removed Shape Center Circles 2024-01-28 16:09:06 +03:00
Syntriax a7e8787579 style: Moved Play Area Boxes Further Out 2024-01-28 15:55:35 +03:00
Syntriax 45df32dabb feat: Implemented Basic Mechanics 2024-01-28 15:45:39 +03:00
Syntriax b3af693678 chore: Engine Update 2024-01-28 15:45:15 +03:00
Syntriax c87827d1ef fix: UbuntuMono Smooth Text Problems 2024-01-28 15:45:04 +03:00
Syntriax c2d2e1fd52 feat: TextBehaviour 2024-01-28 15:44:33 +03:00
Syntriax 7ad2cc5912 refactor: MovementBallBehaviour 2024-01-28 15:44:20 +03:00
Syntriax ca92b12833 feat: Score Boundary 2024-01-28 15:33:45 +03:00
Syntriax fbe3087502 feat: PongScoreboard 2024-01-28 15:33:36 +03:00
Syntriax ef347320fd feat: Circle & Shape Behaviours IsActive Check 2024-01-28 15:33:15 +03:00
Syntriax 1207d04d50 feat: Ubuntu Mono Font Added & Removed Old Unusued Sprites 2024-01-28 15:32:45 +03:00
Syntriax 26f11b5cc0 refactor: Circle Behaviour Constructors Improved Readability 2024-01-27 22:45:49 +03:00
Syntriax 481a49bb52 chore: Engine Update 2024-01-27 21:51:12 +03:00
Syntriax ffe4fc044e feat: Basic Pong Setting 2024-01-27 21:50:28 +03:00
Syntriax 91b05c97c4 Merge branch 'physics' 2024-01-27 20:39:25 +03:00
Syntriax 2f500bef19 chore: Engine Submodule Update 2024-01-27 20:38:53 +03:00
Syntriax 7ed6c1e050 fix: RotatableBehaviour Not Assigning KeyboardInputsBehaviour when It's Missing 2024-01-27 14:47:29 +03:00
Syntriax bd9974f410 chore: Engine Update 2024-01-26 20:46:51 +03:00
Syntriax 42b427f49b chore: Engine Update 2024-01-26 17:19:57 +03:00
Syntriax ef08eedc34 feat: Test Shapes Scene 2024-01-26 17:07:29 +03:00
Syntriax 9eed074025 chore: Removed Unnecessary .sln Files 2024-01-26 09:59:04 +03:00
Syntriax 427ab5ed54 feat: Mouse Movement 2024-01-25 22:15:52 +03:00
Syntriax e252d8fbb2 chore: Physics Test 2024-01-25 22:04:04 +03:00
Syntriax 80c0f4cdd6 refactor: Removed Unnecessary Recalculate on Shape Behaviour 2024-01-25 22:03:31 +03:00
Syntriax 91ad13628c perf: Removed Unnecessary Recalculate in CircleBehaviour 2024-01-25 22:01:42 +03:00
Syntriax 76484017e1 feat: Circle Behaviour 2024-01-25 17:58:47 +03:00
Syntriax faf30b8227 feat: EngineConverter.ApplyDisplayScale 2024-01-25 13:15:59 +03:00
Syntriax bf825fd961 Lots of Stuff That I Can't Break Into Smaller Commits 2024-01-24 17:53:00 +03:00
Syntriax 6e6475f8bf chore: Fixed Test Shape 2024-01-24 15:10:41 +03:00
Syntriax 5b484eb38c feat: ShapeAABB 2024-01-24 15:07:29 +03:00
Syntriax 04653711e9 feat: Apo.Shapes Added 2024-01-24 10:56:26 +03:00
Syntriax 40903eacc3 chore: Engine Submodule Update 2024-01-23 17:50:50 +03:00
Syntriax a70a934b3d chore: Removed Old References 2024-01-23 16:03:35 +03:00
Syntriax 8ea76f91f6 chore: Removed Physics Code 2024-01-23 15:55:57 +03:00
Syntriax 11074b89d5 chore: Engine Update 2023-11-30 17:54:40 +03:00
52 changed files with 813 additions and 1385 deletions

2
Engine

@ -1 +1 @@
Subproject commit d08495afbb7ca9f1afa2c8edcdaf0786bf095e9d
Subproject commit ef21cdf2138a2f5107a628c0fe970b88bc03dcfd

View File

@ -0,0 +1,8 @@
using Apos.Shapes;
namespace Syntriax.Engine.Core.Abstract;
public interface IDisplayableShape
{
void Draw(ShapeBatch shapeBatch);
}

View File

@ -2,7 +2,7 @@ using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
public interface IDisplayable
public interface IDisplayableSprite
{
public void Draw(SpriteBatch spriteBatch);
}

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 : BehaviourOverride
{
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 {GameObject.Name}.");
if (!BehaviourController.TryGetBehaviour(out ICollider2D? foundCollider))
throw new Exception($"{nameof(ICollider2D)} is missing on {GameObject.Name}.");
foundCollider.OnCollisionDetected += OnCollisionDetected;
rigidBody = foundRigidBody;
if (GameObject.GameManager.TryFindBehaviour(out PongManagerBehaviour? pongManager))
{
pongManager.OnReset += ResetBall;
pongManager.OnScored += ResetBall;
pongManager.OnFinished += DisableBall;
}
}
private void DisableBall(PongManagerBehaviour pongManager)
{
BehaviourController.GameObject.Transform.Position = Vector2D.Zero;
rigidBody.Velocity = Vector2D.Zero;
}
private void ResetBall(PongManagerBehaviour pongManager)
{
StateEnable.Enabled = true;
BehaviourController.GameObject.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 * Time.DeltaTimeFrame;
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.Left.Transform.Position.FromTo(information.Right.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,74 @@
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
namespace Pong.Behaviours;
public class CameraController : BehaviourOverride
{
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>();
if (BehaviourController.TryGetBehaviour(out IButtonInputs<Keys>? foundButtonInputs))
buttonInputs = foundButtonInputs;
buttonInputs ??= BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
buttonInputs.RegisterOnPress(Keys.F, SwitchToFullScreen);
buttonInputs.RegisterOnPress(Keys.R, ResetCamera);
}
protected override void OnUpdate()
{
if (buttonInputs.IsPressed(Keys.U))
cameraBehaviour.Zoom += Time.Elapsed.Nanoseconds * 0.00025f;
if (buttonInputs.IsPressed(Keys.J))
cameraBehaviour.Zoom -= Time.Elapsed.Nanoseconds * 0.00025f;
if (buttonInputs.IsPressed(Keys.NumPad8)) cameraBehaviour.BehaviourController.GameObject.Transform.Position += Vector2D.Up.Rotate(Transform.Rotation * Math.DegreeToRadian) * Time.DeltaTimeFrame;
if (buttonInputs.IsPressed(Keys.NumPad2)) cameraBehaviour.BehaviourController.GameObject.Transform.Position -= Vector2D.Up.Rotate(Transform.Rotation * Math.DegreeToRadian) * Time.DeltaTimeFrame;
if (buttonInputs.IsPressed(Keys.NumPad6)) cameraBehaviour.BehaviourController.GameObject.Transform.Position += Vector2D.Right.Rotate(Transform.Rotation * Math.DegreeToRadian) * Time.DeltaTimeFrame;
if (buttonInputs.IsPressed(Keys.NumPad4)) cameraBehaviour.BehaviourController.GameObject.Transform.Position -= Vector2D.Right.Rotate(Transform.Rotation * Math.DegreeToRadian) * Time.DeltaTimeFrame;
if (buttonInputs.IsPressed(Keys.Q))
cameraBehaviour.BehaviourController.GameObject.Transform.Rotation += Time.Elapsed.Nanoseconds * 0.0025f;
if (buttonInputs.IsPressed(Keys.E))
cameraBehaviour.BehaviourController.GameObject.Transform.Rotation -= Time.Elapsed.Nanoseconds * 0.0025f;
}
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;
Transform.Position = Vector2D.Zero;
Transform.Rotation = 0f;
}
}

View File

@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
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,106 @@
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) : BehaviourOverride, 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;
}
public event IAssignableTransform.OnTransformAssignedDelegate? OnTransformAssigned { add => GameObject.OnTransformAssigned += value; remove => GameObject.OnTransformAssigned -= value; }
ITransform IAssignableTransform.Transform => GameObject.Transform;
bool IAssignableTransform.Assign(ITransform transform) => GameObject.Assign(transform);
// 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

@ -1,106 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Pong.Behaviours;
public class MonoGameCameraBehaviour : BehaviourOverride, ICamera
{
public Action<ICamera>? OnPositionChanged { get; set; } = null;
public Action<ICamera>? OnMatrixTransformChanged { get; set; } = null;
public Action<ICamera>? OnViewportChanged { get; set; } = null;
public Action<ICamera>? OnRotationChanged { get; set; } = null;
public Action<ICamera>? OnZoomChanged { get; set; } = null;
private Matrix _matrixTransform = Matrix.Identity;
private Viewport _viewport = default;
private float _zoom = 1f;
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 = value >= .1f ? value : .1f;
if (_zoom == newValue)
return;
_zoom = newValue;
OnZoomChanged?.Invoke(this);
}
}
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
public Action<IAssignableTransform>? OnTransformAssigned { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
ITransform IAssignableTransform.Transform => throw new NotImplementedException();
public void Update()
{
MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f));
}
protected override void OnInitialize()
{
Transform.OnRotationChanged += OnTransformRotationChanged;
Transform.OnPositionChanged += OnTransformPositionChanged;
}
protected override void OnFinalize()
{
Transform.OnRotationChanged -= OnTransformRotationChanged;
Transform.OnPositionChanged -= OnTransformPositionChanged;
}
private void OnTransformRotationChanged(ITransform _) => OnRotationChanged?.Invoke(this);
private void OnTransformPositionChanged(ITransform _) => OnPositionChanged?.Invoke(this);
public bool Assign(ITransform transform) => GameObject.Assign(transform);
}

View File

@ -1,50 +1,53 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Abstract;
namespace Pong.Behaviours;
public class MovementBallBehaviour(Vector2D StartDirection, float Speed) : BehaviourOverride
public class MovementBallBehaviour : BehaviourOverride
{
public Vector2D StartDirection { get; private set; } = Vector2D.Normalize(StartDirection);
public float Speed { get; set; } = Speed;
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? rigidBody))
throw new Exception($"Where's my {nameof(IRigidBody2D)}????");
if (!BehaviourController.TryGetBehaviour(out IRigidBody2D? foundRigidBody))
throw new Exception($"{nameof(IRigidBody2D)} is missing on {GameObject.Name}.");
if (!BehaviourController.TryGetBehaviour(out ICollider2D? foundCollider))
throw new Exception($"{nameof(ICollider2D)} is missing on {GameObject.Name}.");
rigidBody.Velocity = StartDirection * Speed;
foundRigidBody.Velocity = StartDirection * Speed;
foundCollider.OnCollisionDetected += OnCollisionDetected;
rigidBody = foundRigidBody;
}
// protected override void OnUpdate(GameTime time)
// {
// GameObject.Transform.Position += StartDirection * (time.ElapsedGameTime.Nanoseconds * .001f) * Speed;
protected override void OnUpdate()
{
if (rigidBody.Velocity.MagnitudeSquared <= 0.01f)
return;
// float absY = MathF.Abs(GameObject.Transform.Position.Y);
// float differenceY = absY - PlayAreaBehaviour.PlayArea.Y * 0.5f;
// if (differenceY > 0f)
// {
// if (GameObject.Transform.Position.Y > 0f)
// GameObject.Transform.Position -= Vector2.UnitY * differenceY * 2f;
// else
// GameObject.Transform.Position += Vector2.UnitY * differenceY * 2f;
Vector2D speedUp = rigidBody.Velocity.Normalized * Time.DeltaTimeFrame;
rigidBody.Velocity += speedUp * SpeedUpMultiplier;
}
// StartDirection = new(StartDirection.X, -StartDirection.Y);
// }
private void OnCollisionDetected(ICollider2D collider2D, CollisionDetectionInformation information)
{
if (Syntriax.Engine.Core.Math.Abs(information.Normal.Dot(Vector2D.Right)) > .25)
rigidBody.Velocity = information.Left.Transform.Position.FromTo(information.Right.Transform.Position).Normalized * rigidBody.Velocity.Magnitude;
else
rigidBody.Velocity = rigidBody.Velocity.Reflect(information.Normal);
}
// float absX = MathF.Abs(GameObject.Transform.Position.X);
// float differenceX = absX - PlayAreaBehaviour.PlayArea.X * 0.5f;
// if (differenceX > 0f)
// {
// if (GameObject.Transform.Position.X > 0f)
// GameObject.Transform.Position -= Vector2.UnitX * differenceX * 2f;
// else
// GameObject.Transform.Position += Vector2.UnitX * differenceX * 2f;
// StartDirection = new(-StartDirection.X, StartDirection.Y);
// }
// }
public MovementBallBehaviour() { }
public MovementBallBehaviour(Vector2D startDirection, float speed)
{
StartDirection = Vector2D.Normalize(startDirection);
Speed = speed;
}
}

View File

@ -1,12 +1,13 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
namespace Pong.Behaviours;
public class MovementBoxBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : BehaviourOverride
public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : BehaviourOverride
{
private Keys Up { get; } = Up;
private Keys Down { get; } = Down;
@ -35,9 +36,7 @@ public class MovementBoxBehaviour(Keys Up, Keys Down, float High, float Low, flo
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour<IButtonInputs<Keys>>(out var behaviourResult))
throw new Exception($"{nameof(IButtonInputs<Keys>)} is missing on ${GameObject.Name}.");
inputs = behaviourResult;
inputs = behaviourResult ?? BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
inputs.RegisterOnPress(Up, OnUpPressed);
inputs.RegisterOnRelease(Up, OnUpReleased);

View File

@ -1,26 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class PlayAreaBehaviour : BehaviourOverride
{
public Action<PlayAreaBehaviour>? OnPlayAreaChanged { get; set; } = null;
private Vector2 _playArea = Vector2.Zero;
public Vector2 PlayArea
{
get => _playArea;
set
{
if (_playArea == value)
return;
_playArea = value;
OnPlayAreaChanged?.Invoke(this);
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class PongManagerBehaviour : BehaviourOverride
{
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()
{
KeyboardInputsBehaviour? buttonInputs = null!;
if (!BehaviourController.TryGetBehaviour(out buttonInputs))
buttonInputs = BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
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,48 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Input;
using Syntriax.Engine.Physics2D.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
namespace Pong.Behaviours;
public class ShapeAABBBehaviour : BehaviourOverride, 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.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
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(Shape shape) : base(shape) { }
public ShapeBehaviour(Shape shape, float thickness) : base(shape) { Thickness = thickness; }
public ShapeBehaviour(Shape shape, Color color) : base(shape) { Color = color; }
public ShapeBehaviour(Shape 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 : BehaviourOverride, 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 (!GameObject.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) : BehaviourOverride
{
private Action OnCollision { get; } = OnCollision;
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out ICollider2D? collider2D))
return;
collider2D.OnCollisionDetected += (_, _1) => OnCollision?.Invoke();
}
}

View File

@ -13,51 +13,10 @@
#---------------------------------- Content ---------------------------------#
#begin Sprites/Circle.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
#begin UbuntuMono.spritefont
/importer:FontDescriptionImporter
/processor:FontDescriptionProcessor
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Circle.png
#begin Sprites/Circle.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Circle.png
#begin Sprites/Pixel.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Pixel.png
#begin Sprites/Pixel.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Pixel.png
/processorParam:TextureFormat=Compressed
/build:UbuntuMono.spritefont

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<!--
Modify this string to change the font that will be imported.
-->
<FontName>Ubuntu Mono</FontName>
<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->
<Size>144</Size>
<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->
<Spacing>0</Spacing>
<!--
UseKerning controls the layout of the font. If this value is true, kerning information
will be used when placing characters.
-->
<UseKerning>true</UseKerning>
<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->
<Style>Bold</Style>
<!--
If you uncomment this line, the default character will be substituted if you draw
or measure text that contains characters which were not included in the font.
-->
<!-- <DefaultCharacter>*</DefaultCharacter> -->
<!--
CharacterRegions control what letters are available in the font. Every
character from Start to End will be built and made available for drawing. The
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
character set. The characters are ordered according to the Unicode standard.
See the documentation for more information.
-->
<CharacterRegions>
<CharacterRegion>
<Start>&#32;</Start>
<End>&#126;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>

View File

@ -6,10 +6,18 @@ namespace Pong;
public static class EngineConverter
{
public readonly static Vector2D screenScale = Vector2D.Down + Vector2D.Right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EngineTime ToEngineTime(this GameTime gameTime) => new(gameTime.TotalGameTime, gameTime.ElapsedGameTime);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ToVector2D(this Vector2 vector) => new(vector.X, vector.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ToVector2D(this Point point) => new(point.X, point.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToVector2(this Vector2D vector) => new(vector.X, vector.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToDisplayVector2(this Vector2D vector) => vector.Scale(screenScale).ToVector2();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ApplyDisplayScale(this Vector2D vector) => vector.Scale(screenScale);
}

View File

@ -20,12 +20,14 @@
<EmbeddedResource Include="Icon.bmp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Apos.Shapes" Version="0.2.3" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Engine\Engine.Core\Engine.Core.csproj" />
<ProjectReference Include="..\Engine\Engine.Input\Engine.Input.csproj" />
<ProjectReference Include="..\Engine\Engine.Physics2D\Engine.Physics2D.csproj" />
</ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />

View File

@ -1,31 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game", "Game.csproj", "{42644486-9F9E-4242-B6C4-AF31BBFA31D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Core", "..\Engine\Engine.Core\Engine.Core.csproj", "{EF1FE4A2-40DF-4967-8003-CF6D98010D02}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{42644486-9F9E-4242-B6C4-AF31BBFA31D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42644486-9F9E-4242-B6C4-AF31BBFA31D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42644486-9F9E-4242-B6C4-AF31BBFA31D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42644486-9F9E-4242-B6C4-AF31BBFA31D2}.Release|Any CPU.Build.0 = Release|Any CPU
{EF1FE4A2-40DF-4967-8003-CF6D98010D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF1FE4A2-40DF-4967-8003-CF6D98010D02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF1FE4A2-40DF-4967-8003-CF6D98010D02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF1FE4A2-40DF-4967-8003-CF6D98010D02}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB3225EE-1621-439F-8F83-DF4515922FEF}
EndGlobalSection
EndGlobal

View File

@ -1,244 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Pong.Behaviours;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Graphics.TwoDimensional;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Primitives;
namespace Pong;
public class Game1 : Game
{
private GraphicsDeviceManager _graphics = null!;
private PhysicsEngine2D engine;
private SpriteBatch _spriteBatch = null!;
public static GameManager gameManager = null!;
public static Sprite spriteBox = null!;
private MonoGameCameraBehaviour cameraBehaviour = null!;
public Game1()
{
engine = new PhysicsEngine2D();
_graphics = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = 1024,
PreferredBackBufferHeight = 576
};
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
gameManager = new();
// TODO: Add your initialization logic here
gameManager.Initialize();
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
spriteBox = new Sprite() { Texture2D = Content.Load<Texture2D>("Sprites/Pixel") };
Sprite spriteBall = new Sprite() { Texture2D = Content.Load<Texture2D>("Sprites/Circle") };
IGameObject gameObjectCamera = gameManager.InstantiateGameObject<GameObject>();
gameObjectCamera.Name = "Camera";
gameObjectCamera.Transform.Position = Vector2D.Zero;
cameraBehaviour = gameObjectCamera.BehaviourController.AddBehaviour<MonoGameCameraBehaviour>();
cameraBehaviour.Viewport = GraphicsDevice.Viewport;
gameManager.Camera = cameraBehaviour;
GameObject gameObjectBall = gameManager.InstantiateGameObject<GameObject>();
gameObjectBall.Name = "Ball";
gameObjectBall.Transform.Position = Vector2D.Zero;
gameObjectBall.Transform.Scale = new Vector2D(1f / 51.2f, 1f / 51.2f);
engine.AddRigidBody(gameObjectBall.BehaviourController.AddBehaviour<RigidBody2D>());
gameObjectBall.BehaviourController.AddBehaviour<MovementBallBehaviour>(new Vector2D(.1f, .1f), 500f);
gameObjectBall.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBall);
gameObjectBall.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * 512f * .5f, Vector2D.One * 512f * .5f);
// gameObjectBall = gameManager.InstantiateGameObject<GameObject>();
// gameObjectBall.Name = "Ball";
// gameObjectBall.Transform.Position = Vector2D.UnitY * 30f;
// gameObjectBall.Transform.Scale = new Vector2D(1f / 51.2f, 1f / 51.2f);
// engine.AddRigidBody(gameObjectBall.BehaviourController.AddBehaviour<RigidBody2D>());
// gameObjectBall.BehaviourController.AddBehaviour<MovementBallBehaviour>(new Vector2D(.1f, .01f), 500f);
// gameObjectBall.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBall);
// gameObjectBall.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * 512f * .5f, Vector2D.One * 512f * .5f);
// IGameObject gameObjectLeft = gameManager.InstantiateGameObject<GameObject>();
// gameObjectLeft.Name = "Left";
// gameObjectLeft.Transform.Position = new Vector2D(-452, 0f);
// gameObjectLeft.Transform.Scale = new Vector2D(10f, 40f);
// gameObjectLeft.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
// gameObjectLeft.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.W, Keys.S, 268f, -268f, 400f);
// gameObjectLeft.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
// gameObjectLeft.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * .5f, Vector2D.One * .5f);
// engine.AddRigidBody(gameObjectLeft.BehaviourController.AddBehaviour<RigidBody2D>());
// IGameObject gameObjectRight = gameManager.InstantiateGameObject<GameObject>();
// gameObjectRight.Name = "Right";
// gameObjectRight.Transform.Position = new Vector2D(452, 0f);
// gameObjectRight.Transform.Scale = new Vector2D(10f, 40f);
// gameObjectRight.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
// gameObjectRight.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.Up, Keys.Down, 268f, -268f, 400f);
// gameObjectRight.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
// gameObjectRight.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * .5f, Vector2D.One * .5f);
// engine.AddRigidBody(gameObjectRight.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject goPlayAreaTop = gameManager.InstantiateGameObject<GameObject>();
goPlayAreaTop.Transform.Position = new Vector2D(0f, 288f + 20f);
goPlayAreaTop.Transform.Scale = new Vector2D(10240f, 40f);
goPlayAreaTop.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
goPlayAreaTop.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * .5f, Vector2D.One * .5f);
engine.AddRigidBody(goPlayAreaTop.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject goPlayAreaBottom = gameManager.InstantiateGameObject<GameObject>();
goPlayAreaBottom.Transform.Position = new Vector2D(0f, -(288f + 20f));
goPlayAreaBottom.Transform.Scale = new Vector2D(10240f, 40f);
goPlayAreaBottom.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
goPlayAreaBottom.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * .5f, Vector2D.One * .5f);
engine.AddRigidBody(goPlayAreaBottom.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject goPlayAreaRight = gameManager.InstantiateGameObject<GameObject>();
goPlayAreaRight.Transform.Position = new Vector2D(512f + 20f, 0f);
goPlayAreaRight.Transform.Scale = new Vector2D(40f, 5760f);
goPlayAreaRight.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
goPlayAreaRight.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * .5f, Vector2D.One * .5f);
engine.AddRigidBody(goPlayAreaRight.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject goPlayAreaLeft = gameManager.InstantiateGameObject<GameObject>();
goPlayAreaLeft.Transform.Position = new Vector2D(-(512f + 20f), 0f);
goPlayAreaLeft.Transform.Scale = new Vector2D(40f, 5760f);
goPlayAreaLeft.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
goPlayAreaLeft.BehaviourController.AddBehaviour<Collider2DAABBBehaviour>().AABBLocal = new AABB(-Vector2D.One * .5f, Vector2D.One * .5f);
engine.AddRigidBody(goPlayAreaLeft.BehaviourController.AddBehaviour<RigidBody2D>());
// IGameObject goPlayAreaCenter = gameManager.InstantiateGameObject<GameObject>();
// goPlayAreaCenter.Transform.Position = new Vector2D(100f, 100f);
// goPlayAreaCenter.Transform.Scale = new Vector2D(40f, 40f);
// // goPlayAreaCenter.BehaviourController.AddBehaviour<DisplayableSpriteBehaviour>().Assign(spriteBox);
// engine.AddRigidBody(goPlayAreaCenter.BehaviourController.AddBehaviour<RigidBody2D>());
// TODO: use this.Content to load your game content here
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (Keyboard.GetState().IsKeyDown(Keys.F))
{
if (_graphics.IsFullScreen)
return;
_graphics.PreferMultiSampling = false;
_graphics.PreferredBackBufferWidth = GraphicsDevice.Adapter.CurrentDisplayMode.Width;
_graphics.PreferredBackBufferHeight = GraphicsDevice.Adapter.CurrentDisplayMode.Height;
_graphics.IsFullScreen = true;
_graphics.ApplyChanges();
float previousScreenSize = MathF.Sqrt(MathF.Pow(cameraBehaviour.Viewport.Width, 2f) + MathF.Pow(cameraBehaviour.Viewport.Height, 2f));
float currentScreenSize = MathF.Sqrt(MathF.Pow(GraphicsDevice.Viewport.Width, 2f) + MathF.Pow(GraphicsDevice.Viewport.Height, 2f));
cameraBehaviour.Zoom /= previousScreenSize / currentScreenSize;
cameraBehaviour.Viewport = GraphicsDevice.Viewport;
}
if (Keyboard.GetState().IsKeyDown(Keys.U))
cameraBehaviour.Zoom += gameTime.ElapsedGameTime.Nanoseconds * 0.00025f;
if (Keyboard.GetState().IsKeyDown(Keys.J))
cameraBehaviour.Zoom -= gameTime.ElapsedGameTime.Nanoseconds * 0.00025f;
if (Keyboard.GetState().IsKeyDown(Keys.Q))
cameraBehaviour.BehaviourController.GameObject.Transform.Rotation += gameTime.ElapsedGameTime.Nanoseconds * 0.000025f;
if (Keyboard.GetState().IsKeyDown(Keys.E))
cameraBehaviour.BehaviourController.GameObject.Transform.Rotation -= gameTime.ElapsedGameTime.Nanoseconds * 0.000025f;
if (Keyboard.GetState().IsKeyDown(Keys.N))
{
seconds = 70f;
while (physicsTimer + 0.01f < seconds)
{
physicsTimer += 0.01f;
engine.Step(.01f);
}
}
if (Keyboard.GetState().IsKeyDown(Keys.M))
{
seconds = 0f;
while (physicsTimer - 0.01f > seconds)
{
physicsTimer -= 0.01f;
engine.Step(-.01f);
}
}
if (Keyboard.GetState().IsKeyDown(Keys.Space))
{
seconds += gameTime.ElapsedGameTime.Milliseconds * .005f;
while (physicsTimer + 0.01f < seconds)
{
Console.WriteLine($"Physics Timer: {physicsTimer}");
physicsTimer += 0.01f;
engine.Step(.01f);
}
}
if (Keyboard.GetState().IsKeyDown(Keys.B))
{
seconds -= gameTime.ElapsedGameTime.Milliseconds * .005f;
while (physicsTimer - 0.01f > seconds)
{
Console.WriteLine($"Physics Timer: {physicsTimer}");
physicsTimer -= 0.01f;
engine.Step(-.01f);
}
}
// while (physicsTimer + 0.01f < gameTime.TotalGameTime.TotalMilliseconds * .001f)//seconds)
// {
// Console.WriteLine($"Physics Timer: {physicsTimer}");
// physicsTimer += 0.01f;
// engine.Step(.01f);
// }
gameManager.Update(gameTime.ToEngineTime());
base.Update(gameTime);
}
static float physicsTimer = 0f;
static float seconds = 0f;
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(new Color() { R = 32, G = 32, B = 32 });
// gameManager.Camera.Position = gameObjectBall.Transform.Position;
// Console.WriteLine($"Pos: {gameManager.Camera.Position}");
// TODO: Add your drawing code here
gameManager.PreDraw();
gameManager.Camera.Update();
_spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cameraBehaviour.MatrixTransform);
foreach (IGameObject gameObject in gameManager)
{
if (!gameObject.BehaviourController.TryGetBehaviour(out IDisplayable? displayable))
continue;
displayable.Draw(_spriteBatch);
}
_spriteBatch.End();
base.Draw(gameTime);
}
}

168
Game/GamePong.cs Normal file
View File

@ -0,0 +1,168 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Pong.Behaviours;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Primitives;
using Syntriax.Engine.Physics2D.Abstract;
namespace Pong;
public class GamePong : Game
{
private readonly GraphicsDeviceManager graphics = null!;
private IPhysicsEngine2D physicsEngine = null!;
private SpriteBatch spriteBatch = null!;
private ShapeBatch shapeBatch = null!;
private GameManager gameManager = null!;
private BehaviourCollector<IDisplayableSprite> displayableCollector = null!;
private BehaviourCollector<IDisplayableShape> displayableShapeCollector = null!;
private MonoGameCamera2DBehaviour cameraBehaviour = null!;
private PongManagerBehaviour pongManager = null!;
private float physicsTimer = 0f;
public GamePong()
{
graphics = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = 1024,
PreferredBackBufferHeight = 576,
GraphicsProfile = GraphicsProfile.HiDef
};
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
gameManager = new();
displayableCollector = new(gameManager);
displayableShapeCollector = new(gameManager);
physicsEngine = new PhysicsEngine2DCollector(gameManager) { IterationPerStep = 3 };
gameManager.Initialize();
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
shapeBatch = new ShapeBatch(GraphicsDevice, Content);
SpriteFont spriteFont = Content.Load<SpriteFont>("UbuntuMono");
////////////////////////////////////////////////////////////////////////////////////
IGameObject gameObjectCamera = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Camera"); ;
gameObjectCamera.BehaviourController.AddBehaviour<CameraController>();
cameraBehaviour = gameObjectCamera.BehaviourController.AddBehaviour<MonoGameCamera2DBehaviour>(graphics);
////////////////////////////////////////////////////////////////////////////////////
IGameObject gameObjectPongManager = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Pong Game Manager");
pongManager = gameObjectPongManager.BehaviourController.AddBehaviour<PongManagerBehaviour>(5);
////////////////////////////////////////////////////////////////////////////////////
IGameObject gameObjectBall = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Ball");
gameObjectBall.Transform.SetTransform(position: new Vector2D(0, 0f), scale: new Vector2D(10f, 10f));
gameObjectBall.BehaviourController.AddBehaviour<CircleBehaviour>(new Circle(Vector2D.Zero, 1f));
gameObjectBall.BehaviourController.AddBehaviour<BallBehaviour>();
gameObjectBall.BehaviourController.AddBehaviour<RigidBody2D>();
////////////////////////////////////////////////////////////////////////////////////
IGameObject gameObjectLeftPaddle = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Left Paddle");
gameObjectLeftPaddle.Transform.SetTransform(position: new Vector2D(-468f, 0f), scale: new Vector2D(15f, 60f));
gameObjectLeftPaddle.BehaviourController.AddBehaviour<PaddleBehaviour>(Keys.W, Keys.S, 228f, -228f, 400f);
gameObjectLeftPaddle.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Box);
gameObjectLeftPaddle.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
IGameObject gameObjectRightPaddle = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Right Paddle");
gameObjectRightPaddle.Transform.SetTransform(position: new Vector2D(468f, 0f), scale: new Vector2D(15f, 60f));
gameObjectRightPaddle.BehaviourController.AddBehaviour<PaddleBehaviour>(Keys.Up, Keys.Down, 228f, -228f, 400f);
gameObjectRightPaddle.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Box);
gameObjectRightPaddle.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
////////////////////////////////////////////////////////////////////////////////////
IGameObject gameObjectWallTop = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Wall Top");
gameObjectWallTop.Transform.SetTransform(position: new Vector2D(0f, 308f), scale: new Vector2D(552f, 20f));
gameObjectWallTop.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Box);
gameObjectWallTop.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
IGameObject gameObjectWallBottom = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Wall Bottom");
gameObjectWallBottom.Transform.SetTransform(position: new Vector2D(0f, -308f), scale: new Vector2D(552f, 20f));
gameObjectWallBottom.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Box);
gameObjectWallBottom.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
IGameObject gameObjectWallRight = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Wall Right");
gameObjectWallRight.Transform.SetTransform(position: new Vector2D(532f, 0f), scale: new Vector2D(20f, 328f));
gameObjectWallRight.BehaviourController.AddBehaviour<WallScoreBehaviour>((Action)pongManager.ScoreToLeft);
gameObjectWallRight.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Box);
gameObjectWallRight.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
IGameObject gameObjectWallLeft = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Wall Left");
gameObjectWallLeft.Transform.SetTransform(position: new Vector2D(-532f, 0f), scale: new Vector2D(20f, 328f));
gameObjectWallLeft.BehaviourController.AddBehaviour<WallScoreBehaviour>((Action)pongManager.ScoreToRight);
gameObjectWallLeft.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Box);
gameObjectWallLeft.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
////////////////////////////////////////////////////////////////////////////////////
IGameObject gameObjectLeftScoreText = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Score Left");
gameObjectLeftScoreText.Transform.SetTransform(position: new Vector2D(-250f, 250f), scale: Vector2D.One * .25f);
gameObjectLeftScoreText.BehaviourController.AddBehaviour<TextScoreBehaviour>(true, spriteFont);
IGameObject gameObjectRightScoreText = gameManager.InstantiateGameObject<GameObject>().SetGameObject("Score Right");
gameObjectRightScoreText.Transform.SetTransform(position: new Vector2D(250f, 250f), scale: Vector2D.One * .25f);
gameObjectRightScoreText.BehaviourController.AddBehaviour<TextScoreBehaviour>(false, spriteFont);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
gameManager.Update(gameTime.ToEngineTime());
while (physicsTimer + 0.01f < gameTime.TotalGameTime.TotalMilliseconds * .001f)//seconds)
{
physicsTimer += 0.01f;
physicsEngine.Step(.01f);
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(new Color() { R = 32, G = 32, B = 32 });
// TODO: Add your drawing code here
gameManager.PreDraw();
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cameraBehaviour.MatrixTransform);
foreach (var displayable in displayableCollector)
displayable.Draw(spriteBatch);
spriteBatch.End();
shapeBatch.Begin(cameraBehaviour.MatrixTransform);
foreach (var displayableShape in displayableShapeCollector)
displayableShape.Draw(shapeBatch);
shapeBatch.End();
base.Draw(gameTime);
}
}

View File

@ -1,26 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="ISprite"/> field.
/// </summary>
public interface IAssignableSprite : IAssignable
{
/// <summary>
/// Callback triggered when the <see cref="ISprite"/> value has has been assigned a new value.
/// </summary>
Action<IAssignableSprite>? OnSpriteAssigned { get; set; }
/// <inheritdoc cref="ISprite" />
ISprite Sprite { get; }
/// <summary>
/// Assign a value to the <see cref="ISprite"/> field of this object
/// </summary>
/// <param name="sprite">New <see cref="ISprite"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(ISprite sprite);
}

View File

@ -1,13 +0,0 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
// TODO Probably gonna have to rethink this
public interface ISprite
{
Action<ISprite>? OnTextureChanged { get; set; }
Texture2D Texture2D { get; set; }
}

View File

@ -1,27 +0,0 @@
using System;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class Sprite : ISprite
{
public Action<ISprite>? OnTextureChanged { get; set; }
private Texture2D _texture = null!;
public Texture2D Texture2D
{
get => _texture;
set
{
if (_texture == value)
return;
_texture = value;
OnTextureChanged?.Invoke(this);
}
}
}

View File

@ -1,20 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Graphics.TwoDimensional.Abstract;
public interface IDisplayableSprite : IDisplayable, IAssignableSprite
{
Action<IDisplayableSprite>? OnSpriteEffectsChanged { get; set; }
Action<IDisplayableSprite>? OnOriginChanged { get; set; }
Action<IDisplayableSprite>? OnColorChanged { get; set; }
Action<IDisplayableSprite>? OnDepthChanged { get; set; }
SpriteEffects SpriteEffects { get; set; }
Vector2 Origin { get; set; }
Color Color { get; set; }
float Depth { get; set; }
}

View File

@ -1,117 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Graphics.TwoDimensional.Abstract;
namespace Syntriax.Engine.Graphics.TwoDimensional;
public class DisplayableSpriteBehaviour : Behaviour, IDisplayableSprite, IAssignableSprite
{
public Action<IAssignableSprite>? OnSpriteAssigned { get; set; } = null;
public Action<IDisplayableSprite>? OnSpriteEffectsChanged { get; set; } = null;
public Action<IDisplayableSprite>? OnOriginChanged { get; set; } = null;
public Action<IDisplayableSprite>? OnColorChanged { get; set; } = null;
public Action<IDisplayableSprite>? OnDepthChanged { get; set; } = null;
private ISprite _sprite = null!;
private Color _color = Color.White;
private float _depth = 0f;
private SpriteEffects _spriteEffects = SpriteEffects.None;
private Vector2 _origin = Vector2.One * .5f;
public ISprite Sprite => _sprite;
public SpriteEffects SpriteEffects
{
get => _spriteEffects;
set
{
if (_spriteEffects == value)
return;
_spriteEffects = value;
OnSpriteEffectsChanged?.Invoke(this);
}
}
public Vector2 Origin
{
get => _origin;
set
{
if (_origin == value)
return;
_origin = value;
OnOriginChanged?.Invoke(this);
}
}
public Color Color
{
get => _color;
set
{
if (_color == value)
return;
_color = value;
OnColorChanged?.Invoke(this);
}
}
public float Depth
{
get => _depth;
set
{
if (_depth == value)
return;
_depth = value;
OnDepthChanged?.Invoke(this);
}
}
public void Draw(SpriteBatch spriteBatch)
{
if (!BehaviourController.GameObject.StateEnable.Enabled || !StateEnable.Enabled)
return;
ITransform transform = BehaviourController.GameObject.Transform;
Vector2D position = transform.Position;
Vector2D scale = transform.Scale;
Rectangle rectangle = new((int)position.X, -(int)position.Y, (int)(Sprite.Texture2D.Width * scale.X), (int)(Sprite.Texture2D.Height * scale.Y));
spriteBatch.Draw(Sprite.Texture2D, rectangle, null, Color, transform.Rotation, new Vector2(Sprite.Texture2D.Width, Sprite.Texture2D.Height) * Origin, SpriteEffects, Depth);
}
public bool Assign(ISprite sprite)
{
_sprite = sprite;
OnSpriteAssigned?.Invoke(this);
return true;
}
public DisplayableSpriteBehaviour() => OnUnassigned += OnUnassign;
public DisplayableSpriteBehaviour(
Color? color = null,
float? depth = null,
SpriteEffects? spriteEffects = null,
Vector2? origin = null)
{
OnUnassigned += OnUnassign;
_color = color ?? _color;
_depth = depth ?? _depth;
_spriteEffects = spriteEffects ?? _spriteEffects;
_origin = origin ?? _origin;
}
private void OnUnassign(IAssignable assignable) => _sprite = null!;
}

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract;
public interface ICollider2D : IBehaviour, IAssignableTransform
{
Action<ICollider2D, ICollider2D>? OnCollisionPreResolve { get; set; }
IRigidBody2D? RigidBody2D { get; }
IList<Vector2D> Vertices { get; }
bool CheckCollision(Vector2D point);
void Recalculate();
}

View File

@ -1,6 +0,0 @@
namespace Syntriax.Engine.Physics2D.Abstract;
public interface ICollisionResolver2D
{
void ResolveCollision(ICollider2D colliderA, ICollider2D colliderB);
}

View File

@ -1,11 +0,0 @@
namespace Syntriax.Engine.Physics2D.Abstract;
public interface IPhysicsEngine2D
{
int IterationCount { get; set; }
void AddRigidBody(IRigidBody2D rigidBody);
void RemoveRigidBody(IRigidBody2D rigidBody);
void Step(float deltaTime);
}

View File

@ -1,7 +0,0 @@
namespace Syntriax.Engine.Physics2D.Abstract;
public interface IPhysicsMaterial2D
{
float Friction { get; }
float Restitution { get; }
}

View File

@ -1,14 +0,0 @@
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract;
public interface IRigidBody2D : IBehaviour, IAssignableTransform
{
IPhysicsMaterial2D Material { get; set; }
Vector2D Velocity { get; set; }
float AngularVelocity { get; set; }
float Mass { get; set; }
}

View File

@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D;
public class Collider2DAABBBehaviour : BehaviourOverride, ICollider2D
{
public AABB AABBLocal { get; set; } = null!;
public AABB AABBWorld { get; private set; } = null!;
private IRigidBody2D? _rigidBody2D = null;
private List<Vector2D> _vertices = new List<Vector2D>(4);
public IRigidBody2D? RigidBody2D
{
get
{
if (_rigidBody2D is null)
BehaviourController.TryGetBehaviour(out _rigidBody2D);
return _rigidBody2D;
}
}
public Action<ICollider2D, ICollider2D>? OnCollisionPreResolve { get; set; } = null;
public Action<IAssignableTransform>? OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; }
ITransform IAssignableTransform.Transform => Transform;
public bool Assign(ITransform transform) => GameObject.Assign(transform);
public IList<Vector2D> Vertices => _vertices;
public bool CheckCollision(Vector2D point)
{
return AABBWorld.Overlaps(point);
}
public void Recalculate()
{
AABBWorld = new AABB(
AABBLocal.LowerBoundary.Scale(Transform.Scale) + Transform.Position,
AABBLocal.UpperBoundary.Scale(Transform.Scale) + Transform.Position
);
Vertices.Clear();
Vertices.Add(AABBWorld.LowerBoundary);
Vertices.Add(new Vector2D(AABBWorld.LowerBoundary.X, AABBWorld.UpperBoundary.Y));
Vertices.Add(AABBWorld.UpperBoundary);
Vertices.Add(new Vector2D(AABBWorld.UpperBoundary.X, AABBWorld.LowerBoundary.Y));
}
public Collider2DAABBBehaviour(Vector2D lowerBoundary, Vector2D upperBoundary)
{
AABBLocal = new AABB(lowerBoundary, upperBoundary);
AABBWorld = new AABB(lowerBoundary, upperBoundary);
}
public Collider2DAABBBehaviour()
{
AABBLocal = new(Vector2D.Zero, Vector2D.Zero);
AABBWorld = new(Vector2D.Zero, Vector2D.Zero);
}
}

View File

@ -1,9 +0,0 @@
using Microsoft.Xna.Framework;
namespace Syntriax.Engine.Physics2D;
public record CollisionInformation
(
Vector2 Normal,
Vector2 ContactPosition
);

View File

@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D;
public class PhysicsEngine2D : IPhysicsEngine2D
{
private List<IRigidBody2D> rigidBodies = new List<IRigidBody2D>(32);
private List<ICollider2D> colliders = new List<ICollider2D>(64);
private int _iterationCount = 1;
public int IterationCount { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
public void AddRigidBody(IRigidBody2D rigidBody)
{
if (rigidBodies.Contains(rigidBody))
return;
rigidBodies.Add(rigidBody);
foreach (var collider2D in rigidBody.BehaviourController.GetBehaviours<ICollider2D>())
colliders.Add(collider2D);
rigidBody.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
rigidBody.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
}
public void RemoveRigidBody(IRigidBody2D rigidBody)
{
rigidBodies.Remove(rigidBody);
}
public void Step(float deltaTime)
{
float intervalDeltaTime = deltaTime / IterationCount;
for (int iterationIndex = 0; iterationIndex < IterationCount; iterationIndex++)
{
foreach (var rigidBody in rigidBodies)
StepRigidBody(rigidBody, intervalDeltaTime);
foreach (var collider in colliders)
collider.Recalculate();
}
}
private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)
{
rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
}
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not ICollider2D collider2D)
return;
colliders.Add(collider2D);
}
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not ICollider2D collider2D)
return;
colliders.Remove(collider2D);
}
}

View File

@ -1,5 +0,0 @@
using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D;
public record PhysicsMaterial2D(float Friction, float Restitution) : IPhysicsMaterial2D { }

View File

@ -1,6 +0,0 @@
namespace Syntriax.Engine.Physics2D;
public record PhysicsMaterial2DDefault : PhysicsMaterial2D
{
public PhysicsMaterial2DDefault() : base(.1f, .1f) { }
}

View File

@ -1,118 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D;
public static class PhysicsMath
{
public static Vector2D Scale(this Vector2D original, Vector2D scale)
=> new Vector2D(original.X * scale.X, original.Y * scale.Y);
public static Triangle ToSuperTriangle(this IList<Vector2D> vertices)
{
float minX = float.MaxValue, minY = float.MaxValue;
float maxX = float.MinValue, maxY = float.MinValue;
foreach (Vector2D point in vertices)
{
minX = MathF.Min(minX, point.X);
minY = MathF.Min(minY, point.Y);
maxX = MathF.Max(maxX, point.X);
maxY = MathF.Max(maxY, point.Y);
}
float dx = maxX - minX;
float dy = maxY - minY;
float deltaMax = MathF.Max(dx, dy);
float midX = (minX + maxX) / 2;
float midY = (minY + maxY) / 2;
Vector2D p1 = new Vector2D((float)midX - 20f * (float)deltaMax, (float)midY - (float)deltaMax);
Vector2D p2 = new Vector2D((float)midX, (float)midY + 20 * (float)deltaMax);
Vector2D p3 = new Vector2D((float)midX + 20 * (float)deltaMax, (float)midY - (float)deltaMax);
return new Triangle(p1, p2, p3);
}
public static IList<Line> ToLines(this IList<Vector2D> vertices)
{
List<Line> lines = new List<Line>(vertices.Count - 1);
ToLines(vertices, lines);
return lines;
}
public static void ToLines(this IList<Vector2D> vertices, IList<Line> lines)
{
lines.Clear();
for (int i = 0; i < vertices.Count - 1; i++)
lines.Add(new(vertices[i], vertices[i + 1]));
lines.Add(new(vertices[^1], vertices[0]));
}
public static bool LaysOn(this Vector2D point, Line line)
=> line.Resolve(point.X).ApproximatelyEquals(point);
// Given three collinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
public static bool OnSegment(Vector2D p, Vector2D q, Vector2D r)
{
if (q.X <= MathF.Max(p.X, r.X) && q.X >= MathF.Min(p.X, r.X) &&
q.Y <= MathF.Max(p.Y, r.Y) && q.Y >= MathF.Min(p.Y, r.Y))
return true;
return false;
}
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are collinear
// 1 --> Clockwise
// 2 --> Counterclockwise
public static int Orientation(Vector2D p, Vector2D q, Vector2D r)
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
float val = (q.Y - p.Y) * (r.X - q.X) -
(q.X - p.X) * (r.Y - q.Y);
if (val == 0) return 0; // collinear
return (val > 0) ? 1 : 2; // clock or counterclock wise
}
public static float IntersectionParameterT(Vector2D p0, Vector2D p1, Vector2D q0, Vector2D q1)
=> ((q0.X - p0.X) * (p1.Y - p0.Y) - (q0.Y - p0.Y) * (p1.X - p0.X)) /
((q1.Y - q0.Y) * (p1.X - p0.X) - (q1.X - q0.X) * (p1.Y - p0.Y));
public static bool ApproximatelyEquals(this float a, float b)
=> ApproximatelyEquals(a, b, float.Epsilon);
public static bool ApproximatelyEquals(this Vector2 a, Vector2 b)
=> ApproximatelyEquals(a, b, float.Epsilon);
public static bool ApproximatelyEquals(this Vector2 a, Vector2 b, float epsilon)
=> ApproximatelyEquals(a.X, b.X, epsilon) && ApproximatelyEquals(a.Y, b.Y, epsilon);
public static bool ApproximatelyEquals(this Vector2D a, Vector2D b)
=> ApproximatelyEquals(a, b, float.Epsilon);
public static bool ApproximatelyEquals(this Vector2D a, Vector2D b, float epsilon)
=> ApproximatelyEquals(a.X, b.X, epsilon) && ApproximatelyEquals(a.Y, b.Y, epsilon);
public static bool ApproximatelyEquals(this float a, float b, float epsilon)
{
if (a == b)
return true;
const float floatNormal = (1 << 23) * float.Epsilon;
float absA = MathF.Abs(a);
float absB = MathF.Abs(b);
float diff = MathF.Abs(a - b);
if (a == 0.0f || b == 0.0f || diff < floatNormal)
return diff < (epsilon * floatNormal);
return diff / MathF.Min(absA + absB, float.MaxValue) < epsilon;
}
}

View File

@ -1,17 +0,0 @@
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
public record AABB(Vector2D LowerBoundary, Vector2D UpperBoundary)
{
public bool Overlaps(Vector2D point)
=> point.X >= LowerBoundary.X && point.X <= UpperBoundary.X &&
point.Y >= LowerBoundary.Y && point.Y <= UpperBoundary.Y;
public bool Overlaps(AABB other)
=> LowerBoundary.X <= other.UpperBoundary.X && UpperBoundary.X >= other.LowerBoundary.X &&
LowerBoundary.Y <= other.UpperBoundary.Y && UpperBoundary.Y >= other.LowerBoundary.Y;
public bool ApproximatelyEquals(AABB other)
=> LowerBoundary.ApproximatelyEquals(other.LowerBoundary) && UpperBoundary.ApproximatelyEquals(other.UpperBoundary);
}

View File

@ -1,18 +0,0 @@
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
public record Circle(Vector2D Position, float Radius)
{
public bool Intersects(Circle other)
{
float distanceSquared = (Position - other.Position).LengthSquared();
float radiusSumSquared = Radius * Radius + other.Radius * other.Radius;
return distanceSquared < radiusSumSquared;
}
public bool Overlaps(Vector2D point) => (Position - point).LengthSquared() <= Radius * Radius;
public bool ApproximatelyEquals(Circle other)
=> Position.ApproximatelyEquals(other.Position) && Radius.ApproximatelyEquals(other.Radius);
}

View File

@ -1,146 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
public record Line(Vector2D From, Vector2D To)
{
public Line Reversed => new(To, From);
public Vector2D Direction => Vector2D.Normalize(To - From);
public float Length => (From - To).Length();
public float LengthSquared => (From - To).LengthSquared();
public LineEquation LineEquation
{
get
{
Vector2D slopeVector = To - From;
float slope = slopeVector.Y / slopeVector.X;
float yOffset = From.Y - (slope * From.X);
return new LineEquation(slope, yOffset);
}
}
public bool Intersects(Vector2D point)
=> Resolve(point.X).ApproximatelyEquals(point);
public float GetT(Vector2D point)
{
float fromX = MathF.Abs(From.X);
float toX = MathF.Abs(To.X);
float pointX = MathF.Abs(point.X);
float min = MathF.Min(fromX, toX);
float max = MathF.Max(fromX, toX) - min;
pointX -= min;
float t = pointX / max;
// FIXME
// I don't even know, apparently whatever I wrote up there doesn't take into account of the direction of the line
// Which... I can see how, but I am also not sure how I can make it take into account. Or actually I'm for some reason
// too unmotivated to find a solution. Future me, find a better way if possible, please.
if (!Lerp(t).ApproximatelyEquals(point))
return 1f - t;
return t;
}
public bool Exist(List<Vector2D> vertices)
{
for (int i = 0; i < vertices.Count - 1; i++)
{
Vector2D vertexCurrent = vertices[i];
Vector2D vertexNext = vertices[i];
if (From == vertexCurrent && To == vertexNext) return true;
if (From == vertexNext && To == vertexCurrent) return true;
}
Vector2D vertexFirst = vertices[0];
Vector2D vertexLast = vertices[^1];
if (From == vertexFirst && To == vertexLast) return true;
if (From == vertexLast && To == vertexFirst) return true;
return false;
}
public float IntersectionParameterT(Line other)
{
float numerator = (From.X - other.From.X) * (other.From.Y - other.To.Y) - (From.Y - other.From.Y) * (other.From.X - other.To.X);
float denominator = (From.X - To.X) * (other.From.Y - other.To.Y) - (From.Y - To.Y) * (other.From.X - other.To.X);
// Lines are parallel
if (denominator == 0)
return float.NaN;
return numerator / denominator;
}
public Vector2D Lerp(float t)
=> new Vector2D(
From.X + (To.X - From.X) * t,
From.Y + (To.Y - From.Y) * t
);
public Vector2D Resolve(float x)
=> new Vector2D(x, LineEquation.Resolve(x));
public Vector2D ClosestPointTo(Vector2D point)
{
// Convert edge points to vectors
var edgeVector = new Vector2D(To.X - From.X, To.Y - From.Y);
var pointVector = new Vector2D(point.X - From.X, point.Y - From.Y);
// Calculate the projection of pointVector onto edgeVector
float t = (pointVector.X * edgeVector.X + pointVector.Y * edgeVector.Y) / (edgeVector.X * edgeVector.X + edgeVector.Y * edgeVector.Y);
// Clamp t to the range [0, 1] to ensure the closest point is on the edge
t = MathF.Max(0, MathF.Min(1, t));
// Calculate the closest point on the edge
float closestX = From.X + t * edgeVector.X;
float closestY = From.Y + t * edgeVector.Y;
return new Vector2D((float)closestX, (float)closestY);
}
public Vector2D IntersectionPoint(Line other)
=> Vector2D.Lerp(From, To, IntersectionParameterT(other));
public bool Intersects(Line other)
{
int o1 = PhysicsMath.Orientation(From, To, other.From);
int o2 = PhysicsMath.Orientation(From, To, other.To);
int o3 = PhysicsMath.Orientation(other.From, other.To, From);
int o4 = PhysicsMath.Orientation(other.From, other.To, To);
if (o1 != o2 && o3 != o4)
return true;
if (o1 == 0 && PhysicsMath.OnSegment(From, other.From, To)) return true;
if (o2 == 0 && PhysicsMath.OnSegment(From, other.To, To)) return true;
if (o3 == 0 && PhysicsMath.OnSegment(other.From, From, other.To)) return true;
if (o4 == 0 && PhysicsMath.OnSegment(other.From, To, other.To)) return true;
return false;
}
public bool Intersects(Line other, [NotNullWhen(returnValue: true)] out Vector2D? point)
{
point = null;
bool result = Intersects(other);
if (result)
point = IntersectionPoint(other);
return result;
}
public bool ApproximatelyEquals(Line other)
=> From.ApproximatelyEquals(other.From) && To.ApproximatelyEquals(other.To);
}

View File

@ -1,8 +0,0 @@
namespace Syntriax.Engine.Physics2D.Primitives;
public record LineEquation(float Slope, float OffsetY)
{
public float Resolve(float x) => Slope * x + OffsetY; // y = mx + b
public bool ApproximatelyEquals(LineEquation other)
=> Slope.ApproximatelyEquals(other.Slope) && OffsetY.ApproximatelyEquals(other.OffsetY);
}

View File

@ -1,9 +0,0 @@
namespace Syntriax.Engine.Physics2D.Primitives;
public static class Math
{
public const float RadianToDegree = 57.29577866666166f;
public const float DegreeToRadian = 0.01745329277777778f;
public static float Clamp(float value, float min, float max) => (value < min) ? min : (value > max) ? max : value;
}

View File

@ -1,68 +0,0 @@
using System;
using System.Collections.Generic;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
public record Shape(IList<Vector2D> Vertices)
{
public Triangle SuperTriangle
{
get
{
float minX = float.MaxValue, minY = float.MaxValue;
float maxX = float.MinValue, maxY = float.MinValue;
foreach (Vector2D point in Vertices)
{
minX = MathF.Min(minX, point.X);
minY = MathF.Min(minY, point.Y);
maxX = MathF.Max(maxX, point.X);
maxY = MathF.Max(maxY, point.Y);
}
float dx = maxX - minX;
float dy = maxY - minY;
float deltaMax = MathF.Max(dx, dy);
float midX = (minX + maxX) / 2;
float midY = (minY + maxY) / 2;
Vector2D p1 = new Vector2D((float)midX - 20f * (float)deltaMax, (float)midY - (float)deltaMax);
Vector2D p2 = new Vector2D((float)midX, (float)midY + 20 * (float)deltaMax);
Vector2D p3 = new Vector2D((float)midX + 20 * (float)deltaMax, (float)midY - (float)deltaMax);
return new Triangle(p1, p2, p3);
}
}
public List<Line> Lines
{
get
{
List<Line> lines = new List<Line>(Vertices.Count - 1);
GetLinesNonAlloc(lines);
return lines;
}
}
public void GetLinesNonAlloc(IList<Line> lines)
{
lines.Clear();
for (int i = 0; i < Vertices.Count - 1; i++)
lines.Add(new(Vertices[i], Vertices[i + 1]));
lines.Add(new(Vertices[^1], Vertices[0]));
}
public bool ApproximatelyEquals(Shape other)
{
if (Vertices.Count != other.Vertices.Count)
return false;
for (int i = 0; i < Vertices.Count; i++)
if (!Vertices[i].ApproximatelyEquals(other.Vertices[i]))
return false;
return true;
}
}

View File

@ -1,54 +0,0 @@
using System;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
public record Triangle(Vector2D A, Vector2D B, Vector2D C)
{
public float Area => MathF.Abs((
A.X * (B.Y - C.Y) +
B.X * (C.Y - A.Y) +
C.X * (A.Y - B.Y)
) * .5f);
public Circle CircumCircle
{
get
{
Vector2D midAB = (A + B) / 2;
Vector2D midBC = (B + C) / 2;
float slopeAB = (B.Y - A.Y) / (B.X - A.X);
float slopeBC = (C.Y - B.Y) / (C.X - B.X);
Vector2D center;
if (MathF.Abs(slopeAB - slopeBC) > float.Epsilon)
{
float x = (slopeAB * slopeBC * (A.Y - C.Y) + slopeBC * (A.X + B.X) - slopeAB * (B.X + C.X)) / (2 * (slopeBC - slopeAB));
float y = -(x - (A.X + B.X) / 2) / slopeAB + (A.Y + B.Y) / 2;
center = new Vector2D(x, y);
}
else
center = (midAB + midBC) * .5f;
return new(center, Vector2D.Distance(center, A));
}
}
public bool Overlaps(Vector2D point)
{
float originalTriangleArea = Area;
float pointTriangleArea1 = new Triangle(point, B, C).Area;
float pointTriangleArea2 = new Triangle(A, point, C).Area;
float pointTriangleArea3 = new Triangle(A, B, point).Area;
float pointTriangleAreasSum = pointTriangleArea1 + pointTriangleArea2 + pointTriangleArea3;
return originalTriangleArea.ApproximatelyEquals(pointTriangleAreasSum, float.Epsilon * 3f);
}
public bool ApproximatelyEquals(Triangle other)
=> A.ApproximatelyEquals(other.A) && B.ApproximatelyEquals(other.B) && C.ApproximatelyEquals(other.C);
}

View File

@ -1,24 +0,0 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D;
public class RigidBody2D : BehaviourOverride, IRigidBody2D
{
public Action<IAssignableTransform>? OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; }
public IPhysicsMaterial2D Material { get; set; } = new PhysicsMaterial2DDefault();
public Vector2D Velocity { get; set; } = Vector2D.Zero;
public float AngularVelocity { get; set; } = 0f;
public float Mass { get; set; } = 0f;
ITransform IAssignableTransform.Transform => Transform;
public bool Assign(ITransform transform) => GameObject.Assign(transform);
}

View File

@ -1,3 +1,3 @@

using var game = new Pong.Game1();
using var game = new Pong.GamePong();
game.Run();

View File

@ -11,7 +11,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game", "Game\Game.csproj",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Input", "Engine\Engine.Input\Engine.Input.csproj", "{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Graphics", "Engine\Engine.Graphics\Engine.Graphics.csproj", "{7371D9AF-6F4D-4F05-8458-197C4EE66B01}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Physics2D", "Engine\Engine.Physics2D\Engine.Physics2D.csproj", "{0D97F83C-B043-48B1-B155-7354C4E84FC0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -34,14 +34,14 @@ Global
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Release|Any CPU.Build.0 = Release|Any CPU
{7371D9AF-6F4D-4F05-8458-197C4EE66B01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7371D9AF-6F4D-4F05-8458-197C4EE66B01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7371D9AF-6F4D-4F05-8458-197C4EE66B01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7371D9AF-6F4D-4F05-8458-197C4EE66B01}.Release|Any CPU.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{990CA10C-1EBB-4395-A43A-456B7029D8C9} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{7371D9AF-6F4D-4F05-8458-197C4EE66B01} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{0D97F83C-B043-48B1-B155-7354C4E84FC0} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
EndGlobalSection
EndGlobal