From d072166bf8e53f3718c0ddd8c536cfb8423e448a Mon Sep 17 00:00:00 2001 From: Maxwell Edwards <93385879+MaxEdwards20@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:37:51 -0600 Subject: [PATCH] Collision detection (#54) * Updates README * updates the name view * Adds collision handler everywhere in the code * Everything still working * Sets up message transmission well * Adds wall generation * Adds wall texture --- README.md | 7 +- src/Client/Content/Content.mgcb | 12 ++ src/Client/Content/Textures/wall.png | Bin 0 -> 3694 bytes src/Client/GameModel.cs | 10 ++ src/Client/Menu/ChooseNameView.cs | 4 +- src/Client/Menu/HowToPlay.cs | 6 +- src/Client/Systems/CollisionDetection.cs | 10 -- src/Client/Systems/Network.cs | 5 + src/Server/GameModel.cs | 52 +++++++- src/Server/MessageQueueServer.cs | 1 - src/Server/Systems/CollisionDetection.cs | 145 ++++++++++++++++++++++- src/Server/Systems/Network.cs | 43 +++++-- src/Shared/Components/SpicePower.cs | 5 + src/Shared/Components/Wall.cs | 6 + src/Shared/Entities/Wall.cs | 19 +++ src/Shared/Messages/Collision.cs | 19 +++ src/Shared/Messages/MessageTypes.cs | 1 + src/Shared/Messages/NewEntity.cs | 24 +++- src/Shared/Messages/UpdateEntity.cs | 23 ++++ src/Shared/Systems/CollisionHandler.cs | 13 ++ src/Shared/Systems/WormMovement.cs | 6 +- 21 files changed, 363 insertions(+), 48 deletions(-) create mode 100644 src/Client/Content/Textures/wall.png delete mode 100644 src/Client/Systems/CollisionDetection.cs create mode 100644 src/Shared/Components/Wall.cs create mode 100644 src/Shared/Entities/Wall.cs create mode 100644 src/Shared/Messages/Collision.cs create mode 100644 src/Shared/Systems/CollisionHandler.cs diff --git a/README.md b/README.md index 70bf589..834d27f 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,14 @@ A game of Snake built using C# in the MonoGame framework, themed around everyone - [ ] Caden: Map generation - [ ] Caden: Spice generation when we spawn - [ ] Caden: Periodic spice generation throughout the game -- [ ] Max: Keyboard support for left, right, up, down, diagonal up left and diagonal up right. Also add these to the wormMovement system. - [ ] Menu Screen to let player name themselves (probably similar to how control selection screen will work) - Satchel - +- [ ] Max: Collision detection. Know whether we hit spice or another sandworm ## Items to Develop + - [ ] Fix ability to exit the game and come back in. Dean talked about what we need to do in Teams. - [ ] 3 different animated sprites for the spice - [ ] Sound effects on death of worm and when food is eaten - Satchel -- [ ] Collision detection. Know whether we hit spice or another sandworm - [ ] On collision, sandworm breaks apart and is available as food for other snakes - [ ] Record players score, kills and highest position. Probably can be added to the `GameScores` object. - [ ] Game over screen with score, kills, and highest position achieved. This is an overlay on the multiplayer game behind it. @@ -30,6 +29,8 @@ A game of Snake built using C# in the MonoGame framework, themed around everyone - [ ] Add a player status in leaderboard to show whether the player is currently invincible. ## Done + +- [x] Max: Keyboard support for left, right, up, down, diagonal up left and diagonal up right. Also add these to the wormMovement system. - [x] Max: Snake Movement with the queue system - [x] Max: Upgrade our movement system to reduce the lag in rotation - [x] Max: Add name support for the player diff --git a/src/Client/Content/Content.mgcb b/src/Client/Content/Content.mgcb index 964a796..188a6c0 100644 --- a/src/Client/Content/Content.mgcb +++ b/src/Client/Content/Content.mgcb @@ -159,3 +159,15 @@ /processorParam:TextureFormat=Color /build:Textures/tail.png + +#begin Textures/wall.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:Textures/wall.png diff --git a/src/Client/Content/Textures/wall.png b/src/Client/Content/Textures/wall.png new file mode 100644 index 0000000000000000000000000000000000000000..50764e424d75ee7491708de554b767c2c7ed53b8 GIT binary patch literal 3694 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 literal 0 HcmV?d00001 diff --git a/src/Client/GameModel.cs b/src/Client/GameModel.cs index 97e3364..b630a44 100644 --- a/src/Client/GameModel.cs +++ b/src/Client/GameModel.cs @@ -23,6 +23,7 @@ public class GameModel private Systems.Interpolation m_systemInterpolation; private Systems.Renderer m_renderer; private Shared.Systems.WormMovement m_systemWormMovement; + private Shared.Systems.CollisionHandler m_systemCollisionHandler; private Controls m_controls; private GraphicsDeviceManager m_graphics; private SpriteFont m_font; @@ -35,6 +36,7 @@ public void update(TimeSpan elapsedTime) { m_systemNetwork.update(elapsedTime, MessageQueueClient.instance.getMessages()); m_systemKeyboardInput.update(elapsedTime); + m_systemCollisionHandler.update(elapsedTime); m_systemWormMovement.update(elapsedTime); m_systemInterpolation.update(elapsedTime); m_systemCamera.update(elapsedTime); @@ -65,6 +67,7 @@ public bool initialize(ContentManager contentManager, Controls controls, Graphic m_systemCamera = new Systems.Camera(new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight)); m_renderer = new Systems.Renderer(m_systemCamera, graphics, m_font, m_sand); + m_systemCollisionHandler = new Shared.Systems.CollisionHandler(); m_systemWormMovement = new Shared.Systems.WormMovement(); m_systemNetwork = new Systems.Network(); @@ -120,6 +123,11 @@ private Entity createEntity(Shared.Messages.NewEntity message) { entity.add(new Collision()); } + + if (message.hasWall) + { + entity.add(new Shared.Components.Wall()); + } // Worm parts @@ -171,6 +179,7 @@ private void addEntity(Entity entity) // NOTE: Update the systems we use here m_entities[entity.id] = entity; m_systemKeyboardInput.add(entity); + m_systemCollisionHandler.add(entity); m_systemWormMovement.add(entity); m_renderer.add(entity); m_systemNetwork.add(entity); @@ -187,6 +196,7 @@ private void removeEntity(uint id) // NOTE: Update the systems we use here m_entities.Remove(id); m_systemKeyboardInput.remove(id); + m_systemCollisionHandler.remove(id); m_systemWormMovement.remove(id); m_systemNetwork.remove(id); m_renderer.remove(id); diff --git a/src/Client/Menu/ChooseNameView.cs b/src/Client/Menu/ChooseNameView.cs index bb748b9..2b47209 100644 --- a/src/Client/Menu/ChooseNameView.cs +++ b/src/Client/Menu/ChooseNameView.cs @@ -82,7 +82,7 @@ public override void render(GameTime gameTime) (m_graphics.PreferredBackBufferHeight - textSize.Y) / 2); // Draw "Enter Your Name" text - m_spriteBatch.DrawString(font, enterNameText, textPosition, Color.PaleGoldenrod); + m_spriteBatch.DrawString(font, enterNameText, textPosition, Colors.displayColor); // Draw "Press Enter to proceed" below the name text if a name has been entered if (playerName.Length > 0) @@ -93,7 +93,7 @@ public override void render(GameTime gameTime) (m_graphics.PreferredBackBufferWidth - proceedTextSize.X) / 2, textPosition.Y + textSize.Y + 20); // 20 pixels below the name text - m_spriteBatch.DrawString(font, proceedText, proceedTextPosition, Color.PaleGoldenrod); + m_spriteBatch.DrawString(font, proceedText, proceedTextPosition, Colors.displayColor); } m_spriteBatch.End(); diff --git a/src/Client/Menu/HowToPlay.cs b/src/Client/Menu/HowToPlay.cs index c5ca20e..c934b18 100644 --- a/src/Client/Menu/HowToPlay.cs +++ b/src/Client/Menu/HowToPlay.cs @@ -65,7 +65,7 @@ public override void render(GameTime gameTime) // Title Vector2 titlePosition = new Vector2(m_graphics.PreferredBackBufferWidth / 2, m_graphics.PreferredBackBufferHeight / 4); Vector2 titleOrigin = font.MeasureString(titleMessage) / 2; - m_spriteBatch.DrawString(font, titleMessage, titlePosition - (titleOrigin * textScale), Color.PaleGoldenrod, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f); + m_spriteBatch.DrawString(font, titleMessage, titlePosition - (titleOrigin * textScale), Colors.displayColor, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f); // How to Play Instructions Vector2 instructionsPosition = new Vector2(m_graphics.PreferredBackBufferWidth / 2, m_graphics.PreferredBackBufferHeight / 2.5f); @@ -73,14 +73,14 @@ public override void render(GameTime gameTime) foreach (string line in lines) { Vector2 lineSize = font.MeasureString(line) * textScale; - m_spriteBatch.DrawString(font, line, instructionsPosition - new Vector2(lineSize.X / 2, 0), Color.PaleGoldenrod, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f); + m_spriteBatch.DrawString(font, line, instructionsPosition - new Vector2(lineSize.X / 2, 0), Colors.displayColor, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f); instructionsPosition.Y += lineSize.Y + 5; // Adjust spacing between lines if necessary, taking scale into account } // Continue Prompt Vector2 continuePosition = new Vector2(m_graphics.PreferredBackBufferWidth / 2, (m_graphics.PreferredBackBufferHeight / 4) * 3); Vector2 continueOrigin = font.MeasureString(continueMessage) / 2; - m_spriteBatch.DrawString(font, continueMessage, continuePosition - (continueOrigin * textScale), Color.PaleGoldenrod, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f); + m_spriteBatch.DrawString(font, continueMessage, continuePosition - (continueOrigin * textScale), Colors.displayColor, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f); m_spriteBatch.End(); } diff --git a/src/Client/Systems/CollisionDetection.cs b/src/Client/Systems/CollisionDetection.cs deleted file mode 100644 index d645076..0000000 --- a/src/Client/Systems/CollisionDetection.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Client.Systems; - -public class CollisionDetection : Shared.Systems.System -{ - public override void update(TimeSpan elapsedTime) - { - } -} \ No newline at end of file diff --git a/src/Client/Systems/Network.cs b/src/Client/Systems/Network.cs index 5ed31e3..2ccf11e 100644 --- a/src/Client/Systems/Network.cs +++ b/src/Client/Systems/Network.cs @@ -52,6 +52,7 @@ public Network() : { m_removeEntityHandler((RemoveEntity)message); }); + } // Have to implement this because it is abstract in the base class @@ -187,6 +188,10 @@ private void handleUpdateEntity(TimeSpan elapsedTime, UpdateEntity message) m_updatedEntities.Add(entity.id); } + if (entity.contains() && message.hasSpicePower) + { + entity.get().setPower(message.spicePower); + } } } } diff --git a/src/Server/GameModel.cs b/src/Server/GameModel.cs index e44ead1..8763ffd 100644 --- a/src/Server/GameModel.cs +++ b/src/Server/GameModel.cs @@ -1,5 +1,6 @@  using Microsoft.Xna.Framework; +using Server.Systems; using Shared.Components; using Shared.Components.Appearance; using Shared.Entities; @@ -14,7 +15,10 @@ public class GameModel private Dictionary m_entities = new Dictionary(); private Dictionary m_clientToEntityId = new Dictionary(); private WormMovement m_systemWormMovement = new WormMovement(); + private CollisionDetection m_systemCollisionDetection = new CollisionDetection(); + private CollisionHandler m_systemCollisionHandler = new CollisionHandler(); Systems.Network m_systemNetwork = new Server.Systems.Network(); + private int mapSize = 5000; /// /// This is where the server-side simulation takes place. Messages @@ -24,6 +28,8 @@ public class GameModel public void update(TimeSpan elapsedTime) { m_systemNetwork.update(elapsedTime, MessageQueueServer.instance.getMessages()); + m_systemCollisionDetection.update(elapsedTime); + m_systemCollisionHandler.update(elapsedTime); m_systemWormMovement.update(elapsedTime); } @@ -32,14 +38,16 @@ public void update(TimeSpan elapsedTime) /// public bool initialize() { + generateWalls(); m_systemNetwork.registerJoinHandler(handleJoin); m_systemNetwork.registerDisconnectHandler(handleDisconnect); - MessageQueueServer.instance.registerConnectHandler(handleConnect); - + m_systemCollisionDetection.registerRemoveEntity(removeEntity); return true; } + + /// /// Give everything a chance to gracefully shutdown. /// @@ -68,13 +76,11 @@ private void handleConnect(int clientId) private void handleDisconnect(int clientId) { m_clients.Remove(clientId); - Message message = new Shared.Messages.RemoveEntity(m_clientToEntityId[clientId]); MessageQueueServer.instance.broadcastMessage(message); - removeEntity(m_clientToEntityId[clientId]); - m_clientToEntityId.Remove(clientId); + } /// @@ -91,6 +97,8 @@ private void addEntity(Entity entity) m_entities[entity.id] = entity; m_systemNetwork.add(entity); + m_systemCollisionDetection.add(entity); + m_systemCollisionHandler.add(entity); m_systemWormMovement.add(entity); } @@ -102,7 +110,10 @@ private void removeEntity(uint id) { m_entities.Remove(id); m_systemNetwork.remove(id); + m_systemCollisionDetection.remove(id); + m_systemCollisionHandler.remove(id); m_systemWormMovement.remove(id); + } /// @@ -134,6 +145,33 @@ private void handleJoin(int clientId, Shared.Messages.Message message) // to the newly joined client createNewWorm(clientId, name); } + + private void generateWalls() + { + // We want to create wall entities around the entire map. 5000x5000 is the size of the map + // We'll create a wall every 100 units + var wallSize = 100; + for (int i = 0; i < mapSize/100; i++) + { + // Top wall + Entity wall = Shared.Entities.Wall.create(new Vector2(i * wallSize, 0), wallSize); + addEntity(wall); + // MessageQueueServer.instance.broadcastMessage(new NewEntity(wall)); + // Bottom wall + wall = Shared.Entities.Wall.create(new Vector2(i * wallSize, mapSize-wallSize), wallSize); + addEntity(wall); + // MessageQueueServer.instance.broadcastMessage(new NewEntity(wall)); + // Left wall + wall = Shared.Entities.Wall.create(new Vector2(0, i * wallSize), wallSize); + addEntity(wall); + // MessageQueueServer.instance.broadcastMessage(new NewEntity(wall)); + // Right wall + wall = Shared.Entities.Wall.create(new Vector2(mapSize-wallSize, i * wallSize), wallSize); + addEntity(wall); + // MessageQueueServer.instance.broadcastMessage(new NewEntity(wall)); + } + + } private void createNewWorm(int clientId, string name) { @@ -201,7 +239,9 @@ private Vector2 getLeastDenseStartLocation() // We want to start the player in the least dense area of the screen // For now, we'll just start them randomly generated location Random random = new Random(); - return new Vector2(random.Next(0, 800), random.Next(0, 600)); + var lowerBound = (int)(.1 * mapSize); + var upperBound = (int)(.9 * mapSize); + return new Vector2(random.Next(lowerBound, upperBound), random.Next(lowerBound, upperBound)); } } } diff --git a/src/Server/MessageQueueServer.cs b/src/Server/MessageQueueServer.cs index 7c8b177..771818e 100644 --- a/src/Server/MessageQueueServer.cs +++ b/src/Server/MessageQueueServer.cs @@ -161,7 +161,6 @@ public void broadcastMessageWithLastId(Message message) public void registerConnectHandler(ConnectHandler handler) { m_connectHandler = handler; - } /// diff --git a/src/Server/Systems/CollisionDetection.cs b/src/Server/Systems/CollisionDetection.cs index 5acdb91..b98447d 100644 --- a/src/Server/Systems/CollisionDetection.cs +++ b/src/Server/Systems/CollisionDetection.cs @@ -1,24 +1,99 @@ using Microsoft.Xna.Framework; +using Shared.Components; +using Shared.Entities; +using Shared.Messages; +using Shared.Systems; namespace Server.Systems; public class CollisionDetection : Shared.Systems.System { + private System.Action m_removeEntity; + public CollisionDetection() : base( - typeof(Shared.Components.Collision) + typeof(Shared.Components.Collision), typeof(Shared.Components.Position), typeof(Shared.Components.Size) ) { } - - + public override void update(TimeSpan elapsedTime) { - throw new NotImplementedException(); + // Get the heads of the worms + List heads = new List(); + foreach (var entity in m_entities.Values) + { + if (entity.contains()) + { + heads.Add(entity); + } + } + + // Check each head against everything else + foreach (var head in heads) + { + var worm = WormMovement.getWormFromHead(head, m_entities); + // Make this worm into a hashset + HashSet wormSet = new HashSet(); + foreach (var entity in worm) + { + wormSet.Add(entity.id); + } + foreach (var entity in m_entities.Values) + { + // Ignore elements of the worm against everyone else + if (wormSet.Contains(entity.id)) + { + continue; + } + + if (entity.contains() ) + { + if (CircleCircleIntersect( + head.get().position, + head.get().size.X, + entity.get().position, + entity.get().size.X + )) + { + handleWormAteSpice(head, entity, elapsedTime); + } + } + else if (entity.contains()) + { + if (CircleCircleIntersect( + head.get().position, + head.get().size.X, + entity.get().position, + entity.get().size.X + )) + { + handleWormAteWorm(worm, entity); + } + } + else if (entity.contains()) + { + if (CircleLineIntersect( + entity.get().position, + entity.get().position + new Vector2(entity.get().size.X, 0), + head.get().size.X, + head.get().position + ) || CircleLineIntersect( + entity.get().position, + entity.get().position + new Vector2(0, entity.get().size.Y), + head.get().size.X, + head.get().position + )) + { + handleWormHitWall(worm); + } + } + } + } } // Reference: https://stackoverflow.com/questions/37224912/circle-line-segment-collision - private static bool CircleLineIntersect(Point pt1, Point pt2, float circleRadius, Vector2 circlePosition) + private static bool CircleLineIntersect(Vector2 pt1, Vector2 pt2, float circleRadius, Vector2 circlePosition) { Vector2 v1 = new Vector2((float)(pt2.X - pt1.X), (float)(pt2.X - pt1.X)); Vector2 v2 = new Vector2((float) pt1.X - circlePosition.X, (float)(pt1.X - circlePosition.Y)); @@ -42,6 +117,66 @@ private static bool CircleLineIntersect(Point pt1, Point pt2, float circleRadius } return false; } + + private static bool CircleCircleIntersect(Vector2 position1, float radius1, Vector2 position2, float radius2) + { + return Vector2.Distance(position1, position2) < radius1 + radius2; + } + + private void handleWormAteSpice(Entity head, Entity spice, TimeSpan elapsedTime) + { + // Remove the spice + MessageQueueServer.instance.broadcastMessage(new RemoveEntity(spice.id)); + // Add power to the worm head + var headPower = head.get(); + var spicePower = spice.get(); + headPower.addPower(spicePower.power); + MessageQueueServer.instance.broadcastMessage(new UpdateEntity(head, elapsedTime)); + } + + private void handleWormAteWorm(List worm, Entity otherHead) + { + // Check if we hit head on + if (otherHead.contains()) + { + // We need to compare the sizes of the two worms to see who dies + List otherWorm = WormMovement.getWormFromHead(otherHead, m_entities); + if (worm.Count > otherWorm.Count) + { + removeWorm(otherWorm); + } + else + { + removeWorm(worm); + } + } + else // We hit the side of the worm + { + // If the worm hit the body, then the worm dies + removeWorm(worm); + } + } + + private void handleWormHitWall(List worm) + { + removeWorm(worm); + } + + private void removeWorm(List worm) + { + foreach (var entity in worm) + { + MessageQueueServer.instance.broadcastMessage(new RemoveEntity(entity.id)); + m_removeEntity(entity.id); + } + // TODO: Add new entities to the world where the body was + + } + + public void registerRemoveEntity(Action removeEntity) + { + m_removeEntity = removeEntity; + } } diff --git a/src/Server/Systems/Network.cs b/src/Server/Systems/Network.cs index 8ad83e0..a5d2794 100644 --- a/src/Server/Systems/Network.cs +++ b/src/Server/Systems/Network.cs @@ -16,6 +16,8 @@ public class Network : Shared.Systems.System private DisconnectHandler m_disconnectHandler; private HashSet m_reportThese = new HashSet(); + private TimeSpan m_lastGlobalUpdateTime = new TimeSpan(m_globalUpdateFrequency); + private static int m_globalUpdateFrequency = 300; /// /// Primary activity in the constructor is to setup the command map @@ -53,7 +55,22 @@ public Network() : } // Have to implement this because it is abstract in the base class - public override void update(TimeSpan elapsedTime) { } + public override void update(TimeSpan elapsedTime) + { + m_lastGlobalUpdateTime -= elapsedTime; + if (m_lastGlobalUpdateTime.TotalMilliseconds < 0) + { + Console.WriteLine("Global Worm Location Update"); + m_lastGlobalUpdateTime = new TimeSpan(m_globalUpdateFrequency); + foreach (var entity in m_entities.Values) + { + if (entity.contains()) + { + m_reportThese.Add(entity.id); + } + } + } + } /// /// Have our own version of update, because we need a list of messages to work with, and @@ -101,44 +118,52 @@ private void handleInput(Shared.Messages.Input message) { var entity = m_entities[message.entityId]; var worm = WormMovement.getWormFromHead(entity, m_entities); + var update = false; foreach (var input in message.inputs) { switch (input) { case Shared.Components.Input.Type.PointLeft: Shared.Systems.WormMovement.left(worm, message.elapsedTime); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointRight: Shared.Systems.WormMovement.right(worm, message.elapsedTime); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointUp: Shared.Systems.WormMovement.up(worm); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointDown: Shared.Systems.WormMovement.down(worm); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointUpLeft: Shared.Systems.WormMovement.upLeft(worm); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointUpRight: Shared.Systems.WormMovement.upRight(worm); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointDownLeft: Shared.Systems.WormMovement.downLeft(worm); - m_reportThese.Add(entity.id); + update = true; break; case Shared.Components.Input.Type.PointDownRight: Shared.Systems.WormMovement.downRight(worm); - m_reportThese.Add(entity.id); + update = true; break; } } + if (update) + { + foreach (var e in worm) + { + m_reportThese.Add(e.id); + } + } } /// diff --git a/src/Shared/Components/SpicePower.cs b/src/Shared/Components/SpicePower.cs index 321e95f..3e109af 100644 --- a/src/Shared/Components/SpicePower.cs +++ b/src/Shared/Components/SpicePower.cs @@ -17,4 +17,9 @@ public void removePower(int power) { this.power -= power; } + + public void setPower(int power) + { + this.power = power; + } } \ No newline at end of file diff --git a/src/Shared/Components/Wall.cs b/src/Shared/Components/Wall.cs new file mode 100644 index 0000000..5c0bb95 --- /dev/null +++ b/src/Shared/Components/Wall.cs @@ -0,0 +1,6 @@ +namespace Shared.Components; + +public class Wall : Component +{ + +} \ No newline at end of file diff --git a/src/Shared/Entities/Wall.cs b/src/Shared/Entities/Wall.cs new file mode 100644 index 0000000..ffd5d61 --- /dev/null +++ b/src/Shared/Entities/Wall.cs @@ -0,0 +1,19 @@ +using Microsoft.Xna.Framework; +using Shared.Components; +using Shared.Components.Appearance; + +namespace Shared.Entities; + +public class Wall +{ + public static Entity create(Vector2 position, int size) + { + Entity entity = new Entity(); + entity.add(new Appearance("Textures/wall")); + entity.add(new Position(position)); + entity.add(new Size(new Vector2(size, size))); + entity.add(new Collision()); + entity.add(new Components.Wall()); + return entity; + } +} \ No newline at end of file diff --git a/src/Shared/Messages/Collision.cs b/src/Shared/Messages/Collision.cs new file mode 100644 index 0000000..caa0636 --- /dev/null +++ b/src/Shared/Messages/Collision.cs @@ -0,0 +1,19 @@ +namespace Shared.Messages; + +public class Collision : Message +{ + public uint entity1Id { get; private set; } + public uint entity2Id { get; private set; } + + public Collision(uint id, uint id2) : base(Type.Collision) + { + entity1Id = id; + entity2Id = id2; + } + + public Collision() : base(Type.Collision) + { + } + + +} \ No newline at end of file diff --git a/src/Shared/Messages/MessageTypes.cs b/src/Shared/Messages/MessageTypes.cs index 1e1bfb1..84a11b2 100644 --- a/src/Shared/Messages/MessageTypes.cs +++ b/src/Shared/Messages/MessageTypes.cs @@ -7,6 +7,7 @@ public enum Type : UInt16 NewEntity, // Server to client UpdateEntity, // Server to client RemoveEntity, // Server to client + Collision, // Server to client Join, // Client to server Input, // Client to server Disconnect // Client to server diff --git a/src/Shared/Messages/NewEntity.cs b/src/Shared/Messages/NewEntity.cs index 4f507e3..748070d 100644 --- a/src/Shared/Messages/NewEntity.cs +++ b/src/Shared/Messages/NewEntity.cs @@ -3,11 +3,13 @@ using Shared.Entities; using System.Text; using Shared.Components.Appearance; +using Wall = Shared.Entities.Wall; namespace Shared.Messages { public class NewEntity : Message { + // TODO: Add a wall component check public NewEntity(Entity entity) : base(Type.NewEntity) { this.id = entity.id; @@ -75,7 +77,7 @@ public NewEntity(Entity entity) : base(Type.NewEntity) this.childId = entity.get().id; } - if (entity.contains()) + if (entity.contains()) { this.hasCollision = true; } @@ -90,6 +92,11 @@ public NewEntity(Entity entity) : base(Type.NewEntity) this.hasName = true; this.name = entity.get().name; } + + if (entity.contains()) + { + this.hasWall = true; + } } public NewEntity() : base(Type.NewEntity) { @@ -101,8 +108,8 @@ public NewEntity() : base(Type.NewEntity) // Appearance public bool hasAppearance { get; private set; } = false; public string texture { get; private set; } - public bool hasCollision { get; private set; } = false; + public bool hasWall { get; private set; } = false; // Worm parts public bool hasHead { get; private set; } = false; @@ -147,6 +154,7 @@ public override byte[] serialize() serializeMovement(data); serializeInput(data); data.AddRange(BitConverter.GetBytes(hasCollision)); + data.AddRange(BitConverter.GetBytes(hasWall)); // Worm entities data.AddRange(BitConverter.GetBytes(hasHead)); @@ -159,9 +167,7 @@ public override byte[] serialize() return data.ToArray(); } - - - + public override int parse(byte[] data) { // NOTE: Add parser for the components on the WormHead, WormSegment, and WormTail entities @@ -176,6 +182,7 @@ public override int parse(byte[] data) offset = parseMovement(data, offset); offset = parseInput(data, offset); offset = parseCollision(data, offset); + offset = parseWall(data, offset); // Worm Entities offset = parseHead(data, offset); @@ -193,6 +200,13 @@ private int parseWorm(byte[] data, int offset) offset += sizeof(bool); return offset; } + + private int parseWall(byte[] data, int offset) + { + this.hasWall = BitConverter.ToBoolean(data, offset); + offset += sizeof(bool); + return offset; + } private int parseId(byte[] data, int offset) { diff --git a/src/Shared/Messages/UpdateEntity.cs b/src/Shared/Messages/UpdateEntity.cs index e5d8f48..18c23c8 100644 --- a/src/Shared/Messages/UpdateEntity.cs +++ b/src/Shared/Messages/UpdateEntity.cs @@ -18,6 +18,11 @@ public UpdateEntity(Entity entity, TimeSpan updateWindow) : base(Type.UpdateEnti this.orientation = entity.get().orientation; } + if (entity.contains()) + { + this.spicePower = entity.get().power; + } + this.updateWindow = updateWindow; } @@ -31,6 +36,10 @@ public UpdateEntity(): base(Type.UpdateEntity) public bool hasPosition { get; private set; } = false; public Vector2 position { get; private set; } public float orientation { get; private set; } + + // SpicePower + public bool hasSpicePower { get; private set; } = false; + public int spicePower { get; private set; } = 0; // Only the milliseconds are used/serialized public TimeSpan updateWindow { get; private set; } = TimeSpan.Zero; @@ -49,6 +58,12 @@ public override byte[] serialize() data.AddRange(BitConverter.GetBytes(position.Y)); data.AddRange(BitConverter.GetBytes(orientation)); } + + data.AddRange(BitConverter.GetBytes(hasSpicePower)); + if (hasSpicePower) + { + data.AddRange(BitConverter.GetBytes(spicePower)); + } data.AddRange(BitConverter.GetBytes((float)updateWindow.TotalMilliseconds)); @@ -74,6 +89,14 @@ public override int parse(byte[] data) this.orientation = BitConverter.ToSingle(data, offset); offset += sizeof(Single); } + + this.hasSpicePower = BitConverter.ToBoolean(data, offset); + offset += sizeof(bool); + if (hasSpicePower) + { + this.spicePower = BitConverter.ToInt32(data, offset); + offset += sizeof(int); + } this.updateWindow = TimeSpan.FromMilliseconds(BitConverter.ToSingle(data, offset)); offset += sizeof(Single); diff --git a/src/Shared/Systems/CollisionHandler.cs b/src/Shared/Systems/CollisionHandler.cs new file mode 100644 index 0000000..d2cda33 --- /dev/null +++ b/src/Shared/Systems/CollisionHandler.cs @@ -0,0 +1,13 @@ +using Shared.Components; +using Shared.Entities; + +namespace Shared.Systems; + +public class CollisionHandler : Shared.Systems.System +{ + public override void update(TimeSpan elapsedTime) + { + // Basically we look at each worm head and see how big it is. If it is above a certain threshold, then we update its size and remove the spice power. + } + +} \ No newline at end of file diff --git a/src/Shared/Systems/WormMovement.cs b/src/Shared/Systems/WormMovement.cs index 1d5de04..c60c4da 100644 --- a/src/Shared/Systems/WormMovement.cs +++ b/src/Shared/Systems/WormMovement.cs @@ -89,7 +89,7 @@ private void applyThrust(Entity wormHead, TimeSpan elapsedTime) var frameTotalMovement = movement.moveRate * (float)elapsedTime.TotalMilliseconds; var orientation = headPosition.orientation; float LOCATION_THRESHOLD = 2f; - const float MIN_SEGMENT_SPACING = 20f; + const float MIN_SEGMENT_SPACING = 30f; const float IDEAL_SEGMENT_SPACING = 40f; // Check how close the head is to its child @@ -129,7 +129,7 @@ private void applyThrust(Entity wormHead, TimeSpan elapsedTime) // Move towards that target var distanceToTarget = Vector2.Distance(currentPosition.position, target.position); var distanceToParent = Vector2.Distance(currentPosition.position, parentPosition.position); - if (distanceToTarget > MIN_SEGMENT_SPACING || distanceToParent > IDEAL_SEGMENT_SPACING) + if (distanceToTarget >= MIN_SEGMENT_SPACING || distanceToParent >= IDEAL_SEGMENT_SPACING) { var directionToTarget = target.position - currentPosition.position; directionToTarget.Normalize(); @@ -179,8 +179,6 @@ private static void changeDirection(List worm, float radians) } } - - private static Entity getHead(Entity entity, Dictionary entities) {