diff --git a/PongFE/Components/BounceResponseComponent.cs b/PongFE/Components/BounceResponseComponent.cs new file mode 100644 index 0000000..937aac0 --- /dev/null +++ b/PongFE/Components/BounceResponseComponent.cs @@ -0,0 +1,6 @@ +using Encompass; + +namespace PongFE.Components +{ + public struct BounceResponseComponent : IComponent { } +} diff --git a/PongFE/Components/CanBeBouncedComponent.cs b/PongFE/Components/CanBeBouncedComponent.cs new file mode 100644 index 0000000..eef03eb --- /dev/null +++ b/PongFE/Components/CanBeBouncedComponent.cs @@ -0,0 +1,6 @@ +using Encompass; + +namespace PongFE.Components +{ + public struct CanBeBouncedComponent : IComponent { } +} diff --git a/PongFE/Components/CanCauseBounceComponent.cs b/PongFE/Components/CanCauseBounceComponent.cs new file mode 100644 index 0000000..5ca6ad3 --- /dev/null +++ b/PongFE/Components/CanCauseBounceComponent.cs @@ -0,0 +1,6 @@ +using Encompass; + +namespace PongFE.Components +{ + public struct CanCauseBounceComponent : IComponent { } +} diff --git a/PongFE/Components/CollisionComponent.cs b/PongFE/Components/CollisionComponent.cs new file mode 100644 index 0000000..8bfcd05 --- /dev/null +++ b/PongFE/Components/CollisionComponent.cs @@ -0,0 +1,15 @@ +using Encompass; +using MoonTools.Bonk; + +namespace PongFE.Components +{ + public struct CollisionComponent : IComponent + { + public Rectangle Rectangle { get; } + + public CollisionComponent(Rectangle rectangle) + { + Rectangle = rectangle; + } + } +} diff --git a/PongFE/Components/VelocityComponent.cs b/PongFE/Components/VelocityComponent.cs new file mode 100644 index 0000000..b9643ef --- /dev/null +++ b/PongFE/Components/VelocityComponent.cs @@ -0,0 +1,15 @@ +using System.Numerics; +using Encompass; + +namespace PongFE.Components +{ + public struct VelocityComponent : IComponent + { + public Vector2 Velocity { get; } + + public VelocityComponent(Vector2 velocity) + { + Velocity = velocity; + } + } +} diff --git a/PongFE/Engines/BounceEngine.cs b/PongFE/Engines/BounceEngine.cs new file mode 100644 index 0000000..7109568 --- /dev/null +++ b/PongFE/Engines/BounceEngine.cs @@ -0,0 +1,42 @@ +using System.Numerics; +using Encompass; +using PongFE.Components; +using PongFE.Messages; + +namespace PongFE.Engines +{ + [Reads( + typeof(BounceResponseComponent), + typeof(VelocityComponent) + )] + [Receives(typeof(BounceMessage))] + [Sends(typeof(UpdateVelocityMessage))] + public class BounceEngine : Engine + { + public override void Update(double dt) + { + foreach (ref readonly var message in ReadMessages()) + { + if (HasComponent(message.Entity) && HasComponent(message.Entity)) + { + System.Console.WriteLine("hello bounce!"); + ref readonly var velocityComponent = ref GetComponent(message.Entity); + + Vector2 newVelocity; + if (message.HitOrientation == HitOrientation.Horizontal) + { + newVelocity = + new Vector2(-velocityComponent.Velocity.X, velocityComponent.Velocity.Y); + } + else + { + newVelocity = + new Vector2(velocityComponent.Velocity.X, -velocityComponent.Velocity.Y); + } + + SendMessage(new UpdateVelocityMessage(message.Entity, newVelocity)); + } + } + } + } +} diff --git a/PongFE/Engines/CollisionEngine.cs b/PongFE/Engines/CollisionEngine.cs new file mode 100644 index 0000000..97b2767 --- /dev/null +++ b/PongFE/Engines/CollisionEngine.cs @@ -0,0 +1,36 @@ +using Encompass; +using PongFE.Components; +using PongFE.Messages; + +namespace PongFE.Engines +{ + [Reads( + typeof(CanCauseBounceComponent), + typeof(CanBeBouncedComponent) + )] + [Receives(typeof(CollisionMessage))] + [Sends(typeof(BounceMessage))] + public class CollisionEngine : Engine + { + public override void Update(double dt) + { + foreach (ref readonly var message in ReadMessages()) + { + CheckBounce(message.EntityA, message.EntityB, message.HitOrientation); + CheckBounce(message.EntityB, message.EntityA, message.HitOrientation); + } + } + + private void CheckBounce(Entity a, Entity b, HitOrientation hitOrientation) + { + if (HasComponent(a)) + { + if (HasComponent(b)) + { + System.Console.WriteLine("bounce"); + SendMessage(new BounceMessage(b, hitOrientation)); + } + } + } + } +} diff --git a/PongFE/Engines/MotionEngine.cs b/PongFE/Engines/MotionEngine.cs index 46f9b41..c0b68fb 100644 --- a/PongFE/Engines/MotionEngine.cs +++ b/PongFE/Engines/MotionEngine.cs @@ -1,25 +1,125 @@ +using System.Collections.Generic; +using System.Numerics; using Encompass; +using MoonTools.Bonk; +using MoonTools.Structs; using PongFE.Components; using PongFE.Messages; namespace PongFE.Engines { - [Reads(typeof(PositionComponent))] + [Reads( + typeof(PositionComponent), + typeof(CollisionComponent) + )] [Receives(typeof(MotionMessage))] - [Writes(typeof(PositionComponent))] + [Sends( + typeof(UpdatePositionMessage), + typeof(CollisionMessage) + )] public class MotionEngine : Engine { + private readonly SpatialHash _spatialHash = new SpatialHash(32); + private readonly Dictionary _moveAmounts = new Dictionary(); + private readonly Dictionary _finalPositions = new Dictionary(); + public override void Update(double dt) { - foreach (ref readonly var motionMessage in ReadMessages()) + _spatialHash.Clear(); + _moveAmounts.Clear(); + _finalPositions.Clear(); + + foreach (ref readonly var entity in ReadEntities()) { - if (HasComponent(motionMessage.Entity)) + ref readonly var collisionComponent = ref GetComponent(entity); + + if (HasComponent(entity)) { - ref readonly var positionComponent = ref GetComponent(motionMessage.Entity); - var newPosition = positionComponent.Position + motionMessage.Movement; - SetComponent(motionMessage.Entity, new PositionComponent(newPosition)); + ref readonly var positionComponent = ref GetComponent(entity); + _spatialHash.Insert(entity, collisionComponent.Rectangle, new Transform2D(positionComponent.Position)); } } + + foreach (ref readonly var entity in ReadEntities()) + { + ref readonly var positionComponent = ref GetComponent(entity); + + _finalPositions[entity] = positionComponent.Position; + _moveAmounts[entity] = Vector2.Zero; + + foreach (var motionMessage in ReadMessagesWithEntity(entity)) + { + _moveAmounts[entity] += motionMessage.Movement; + } + } + + foreach (var pair in _moveAmounts) + { + var entity = pair.Key; + var moveAmount = pair.Value; + + ref readonly var positionComponent = ref GetComponent(entity); + + var projectedPosition = positionComponent.Position + moveAmount; + + if (!HasComponent(entity)) + { + SendMessage(new UpdatePositionMessage(entity, projectedPosition)); + } + else + { + ref readonly var collisionComponent = ref GetComponent(entity); + var rectangle = collisionComponent.Rectangle; + var (xHit, yHit, newPosition, collisionEntity) = SolidCollisionPosition(rectangle, positionComponent.Position, projectedPosition); + + if (xHit || yHit) + { + projectedPosition = newPosition; + + if (xHit) + { + SendMessage(new CollisionMessage(entity, collisionEntity, HitOrientation.Horizontal)); + } + else + { + SendMessage(new CollisionMessage(entity, collisionEntity, HitOrientation.Vertical)); + } + } + } + + SendMessage(new UpdatePositionMessage(entity, projectedPosition)); + } + } + + private (bool, bool, Position2D, Entity) SolidCollisionPosition(Rectangle rectangle, Position2D startPosition, Position2D endPosition) + { + var startX = startPosition.X; + var endX = endPosition.X; + + var startY = startPosition.Y; + var endY = endPosition.Y; + + bool xHit, yHit; + int xPosition, yPosition; + Entity xCollisionEntity, yCollisionEntity; + + (xHit, xPosition, xCollisionEntity) = SweepX(_spatialHash, rectangle, Position2D.Zero, new Position2D(startX, startY), endX - startX); + if (!xHit) { xPosition = endX; } + (yHit, yPosition, yCollisionEntity) = SweepY(_spatialHash, rectangle, Position2D.Zero, new Position2D(xPosition, startY), endY - startY); + + return (xHit, yHit, new Position2D(xPosition, yPosition), xHit ? xCollisionEntity : yCollisionEntity); + } + + private (bool, int, Entity) SweepX(SpatialHash solidSpatialHash, Rectangle rectangle, Position2D offset, Position2D startPosition, int horizontalMovement) + { + var sweepResult = SweepTest.Test(solidSpatialHash, rectangle, new Transform2D(offset + startPosition), new Vector2(horizontalMovement, 0)); + return (sweepResult.Hit, startPosition.X + (int)sweepResult.Motion.X, sweepResult.ID); + } + + public static (bool, int, Entity) SweepY(SpatialHash solidSpatialHash, Rectangle rectangle, Position2D offset, Position2D startPosition, int verticalMovement) + { + var sweepResult = SweepTest.Test(solidSpatialHash, rectangle, new Transform2D(offset + startPosition), new Vector2(0, verticalMovement)); + return (sweepResult.Hit, startPosition.Y + (int)sweepResult.Motion.Y, sweepResult.ID); } } } diff --git a/PongFE/Engines/Spawners/BallSpawner.cs b/PongFE/Engines/Spawners/BallSpawner.cs index f86a747..2f018d3 100644 --- a/PongFE/Engines/Spawners/BallSpawner.cs +++ b/PongFE/Engines/Spawners/BallSpawner.cs @@ -17,8 +17,12 @@ namespace PongFE.Spawners protected override void Spawn(BallSpawnMessage message) { var ball = CreateEntity(); - AddComponent(ball, new PositionComponent(new MoonTools.Structs.Position2D(640, 360))); + AddComponent(ball, new PositionComponent(message.Position)); + AddComponent(ball, new VelocityComponent(message.Velocity)); + AddComponent(ball, new CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, 16, 16))); AddComponent(ball, new Texture2DComponent(BallTexture, 0)); + AddComponent(ball, new CanBeBouncedComponent()); + AddComponent(ball, new BounceResponseComponent()); } } } diff --git a/PongFE/Engines/UpdatePositionEngine.cs b/PongFE/Engines/UpdatePositionEngine.cs new file mode 100644 index 0000000..681cfb0 --- /dev/null +++ b/PongFE/Engines/UpdatePositionEngine.cs @@ -0,0 +1,19 @@ +using Encompass; +using PongFE.Components; +using PongFE.Messages; + +namespace PongFE.Engines +{ + [Receives(typeof(UpdatePositionMessage))] + [Writes(typeof(PositionComponent))] + public class UpdatePositionEngine : Engine + { + public override void Update(double dt) + { + foreach (ref readonly var message in ReadMessages()) + { + SetComponent(message.Entity, new PositionComponent(message.Position)); + } + } + } +} diff --git a/PongFE/Engines/UpdateVelocityEngine.cs b/PongFE/Engines/UpdateVelocityEngine.cs new file mode 100644 index 0000000..834ff98 --- /dev/null +++ b/PongFE/Engines/UpdateVelocityEngine.cs @@ -0,0 +1,19 @@ +using Encompass; +using PongFE.Components; +using PongFE.Messages; + +namespace PongFE.Engines +{ + [Receives(typeof(UpdateVelocityMessage))] + [Writes(typeof(VelocityComponent))] + public class UpdateVelocityEngine : Engine + { + public override void Update(double dt) + { + foreach (ref readonly var message in ReadMessages()) + { + SetComponent(message.Entity, new VelocityComponent(message.Velocity)); + } + } + } +} diff --git a/PongFE/Engines/VelocityEngine.cs b/PongFE/Engines/VelocityEngine.cs new file mode 100644 index 0000000..cb95cf9 --- /dev/null +++ b/PongFE/Engines/VelocityEngine.cs @@ -0,0 +1,20 @@ +using Encompass; +using PongFE.Components; +using PongFE.Messages; + +namespace PongFE.Engines +{ + [Sends(typeof(MotionMessage))] + [QueryWith(typeof(PositionComponent), typeof(VelocityComponent))] + public class VelocityEngine : Engine + { + public override void Update(double dt) + { + foreach (var entity in TrackedEntities) + { + ref readonly var velocityComponent = ref GetComponent(entity); + SendMessage(new MotionMessage(entity, velocityComponent.Velocity * (float)dt)); + } + } + } +} diff --git a/PongFE/Messages/BallSpawnMessage.cs b/PongFE/Messages/BallSpawnMessage.cs index f918d0b..ecf6795 100644 --- a/PongFE/Messages/BallSpawnMessage.cs +++ b/PongFE/Messages/BallSpawnMessage.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Encompass; using MoonTools.Structs; @@ -6,10 +7,12 @@ namespace PongFE.Messages public struct BallSpawnMessage : IMessage { public Position2D Position { get; } + public Vector2 Velocity { get; } - public BallSpawnMessage(Position2D position) + public BallSpawnMessage(Position2D position, Vector2 velocity) { Position = position; + Velocity = velocity; } } } diff --git a/PongFE/Messages/BounceMessage.cs b/PongFE/Messages/BounceMessage.cs new file mode 100644 index 0000000..af41abd --- /dev/null +++ b/PongFE/Messages/BounceMessage.cs @@ -0,0 +1,16 @@ +using Encompass; + +namespace PongFE.Messages +{ + public struct BounceMessage : IMessage + { + public Entity Entity { get; } + public HitOrientation HitOrientation { get; } + + public BounceMessage(Entity entity, HitOrientation hitOrientation) + { + Entity = entity; + HitOrientation = hitOrientation; + } + } +} diff --git a/PongFE/Messages/CollisionMessage.cs b/PongFE/Messages/CollisionMessage.cs new file mode 100644 index 0000000..e06b837 --- /dev/null +++ b/PongFE/Messages/CollisionMessage.cs @@ -0,0 +1,24 @@ +using Encompass; + +namespace PongFE.Messages +{ + public enum HitOrientation + { + Horizontal, + Vertical + } + + public struct CollisionMessage : IMessage + { + public Entity EntityA { get; } + public Entity EntityB { get; } + public HitOrientation HitOrientation; + + public CollisionMessage(Entity a, Entity b, HitOrientation hitOrientation) + { + EntityA = a; + EntityB = b; + HitOrientation = hitOrientation; + } + } +} diff --git a/PongFE/Messages/UpdatePositionMessage.cs b/PongFE/Messages/UpdatePositionMessage.cs new file mode 100644 index 0000000..0252ae6 --- /dev/null +++ b/PongFE/Messages/UpdatePositionMessage.cs @@ -0,0 +1,17 @@ +using Encompass; +using MoonTools.Structs; + +namespace PongFE.Messages +{ + public struct UpdatePositionMessage : IMessage, IHasEntity + { + public Entity Entity { get; } + public Position2D Position { get; } + + public UpdatePositionMessage(Entity entity, Position2D position) + { + Entity = entity; + Position = position; + } + } +} diff --git a/PongFE/Messages/UpdateVelocityMessage.cs b/PongFE/Messages/UpdateVelocityMessage.cs new file mode 100644 index 0000000..deefc1c --- /dev/null +++ b/PongFE/Messages/UpdateVelocityMessage.cs @@ -0,0 +1,17 @@ +using System.Numerics; +using Encompass; + +namespace PongFE.Messages +{ + public struct UpdateVelocityMessage : IMessage, IHasEntity + { + public Entity Entity { get; } + public Vector2 Velocity { get; } + + public UpdateVelocityMessage(Entity entity, Vector2 velocity) + { + Entity = entity; + Velocity = velocity; + } + } +} diff --git a/PongFE/PongFE.Core.csproj b/PongFE/PongFE.Core.csproj index 5929c87..1b82a06 100644 --- a/PongFE/PongFE.Core.csproj +++ b/PongFE/PongFE.Core.csproj @@ -25,7 +25,10 @@ - + + + + diff --git a/PongFE/PongFE.Framework.csproj b/PongFE/PongFE.Framework.csproj index 96aac9b..462e657 100644 --- a/PongFE/PongFE.Framework.csproj +++ b/PongFE/PongFE.Framework.csproj @@ -1,8 +1,7 @@ - - obj\$(MSBuildPongFE) - - + + obj\$(MSBuildPongFE) + WinExe net461 @@ -20,6 +19,9 @@ $(DefaultItemExcludes);DllMap.cs + + + Always @@ -27,9 +29,10 @@ - - + + + + + - - diff --git a/PongFE/PongFEGame.cs b/PongFE/PongFEGame.cs index 71125e6..ec00db7 100644 --- a/PongFE/PongFEGame.cs +++ b/PongFE/PongFEGame.cs @@ -56,17 +56,31 @@ namespace PongFE WorldBuilder.AddEngine(new InputEngine()); WorldBuilder.AddEngine(new PaddleMovementEngine()); + WorldBuilder.AddEngine(new VelocityEngine()); WorldBuilder.AddEngine(new MotionEngine()); + WorldBuilder.AddEngine(new CollisionEngine()); + WorldBuilder.AddEngine(new BounceEngine()); + WorldBuilder.AddEngine(new UpdatePositionEngine()); + WorldBuilder.AddEngine(new UpdateVelocityEngine()); + WorldBuilder.AddEngine(new BallSpawner(BallTexture)); + WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch)); var paddle = WorldBuilder.CreateEntity(); WorldBuilder.SetComponent(paddle, new PlayerInputComponent(PongFE.Components.PlayerIndex.One)); WorldBuilder.SetComponent(paddle, new PaddleMoveSpeedComponent(400)); WorldBuilder.SetComponent(paddle, new PositionComponent(new MoonTools.Structs.Position2D(5, 5))); + WorldBuilder.SetComponent(paddle, new CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, 20, 80))); + WorldBuilder.SetComponent(paddle, new CanCauseBounceComponent()); WorldBuilder.SetComponent(paddle, new Texture2DComponent(PaddleTexture, 0)); - WorldBuilder.SendMessage(new BallSpawnMessage(new MoonTools.Structs.Position2D(640, 360))); + WorldBuilder.SendMessage( + new BallSpawnMessage( + new MoonTools.Structs.Position2D(640, 360), + new System.Numerics.Vector2(-100, -20) + ) + ); World = WorldBuilder.Build(); } @@ -78,7 +92,6 @@ namespace PongFE protected override void Update(GameTime gameTime) { - System.Console.WriteLine(1 / gameTime.ElapsedGameTime.TotalSeconds); World.Update(gameTime.ElapsedGameTime.TotalSeconds); base.Update(gameTime);