using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace BadGame { public struct PlayerData { public Vector2 Position; public Vector2 Velocity; public bool IsAlive; public bool Win; public bool CanJump; } public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; GraphicsDevice device; SpriteBatch spriteBatch; SpriteFont font; Texture2D backgroundTexture; Texture2D foregroundTexture; Texture2D playerTexture; Texture2D rect; int screenHeight; int screenWidth; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; IsFixedTimeStep = false; } protected override void Initialize() { graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 400; graphics.IsFullScreen = false; graphics.ApplyChanges(); Window.Title = "Terrible Game"; base.Initialize(); } PlayerData Player = new PlayerData(); void BuildPlayerData() { Player.Position = new Vector2(0, 250); // Start them in the air because it looks/feels cool when a player falls at the start Player.IsAlive = true; Player.Win = false; //Player.Velocity = new Vector2(0.3f, 0); // It defaults to Vector2.Zero (stationary) Player.CanJump = true; } List Platform = new List(); void BuildPlatformData() { Platform.Add(new Rectangle(100, 210, 150, 30)); Platform.Add(new Rectangle(150, 140, 100, 30)); Platform.Add(new Rectangle(150, 310, 230, 30)); Platform.Add(new Rectangle(230, 280, 150, 30)); Platform.Add(new Rectangle(310, 250, 70, 30)); Platform.Add(new Rectangle(350, 140, 100, 30)); Platform.Add(new Rectangle(400, 230, 150, 30)); Platform.Add(new Rectangle(400, 170, 30, 60)); Platform.Add(new Rectangle(500, 170, 30, 30)); Platform.Add(new Rectangle(600, 130, 30, 300)); Platform.Add(new Rectangle(600, 130, 130, 30)); Platform.Add(new Rectangle(600, 330, 200, 10)); Platform.Add(new Rectangle(700, 250, 100, 30)); Platform.Add(new Rectangle(770, 0, 30, 260)); // Adding new platforms to keep the player confined within the level // These others are positioned right off the screen so you won't see them // The only downside would be that these are now DRAWN on the screen (just out of view) // However, if you ever expand on the project, you would likely check if an element is visible on the screen, and if not, don't draw it :) // You could do this by settings a rectangle that is the size/position of the screen.. and check if the platforms intersect this screen rect Platform.Add(new Rectangle(0, 340, 380, 61)); // Bottom boundry (THIS USED TO BE "LEVEL") Platform.Add(new Rectangle(0, -30, screenWidth, 30)); // Top boundry Platform.Add(new Rectangle(-30, 0, 30, screenHeight)); // Left boundry Platform.Add(new Rectangle(screenWidth, 0, 30, screenHeight)); // Right boundry } List Hazard = new List(); void BuildHazardData() { Hazard.Add(new Rectangle(380, 340, 420, 61)); } protected override void LoadContent() { device = graphics.GraphicsDevice; spriteBatch = new SpriteBatch(GraphicsDevice); backgroundTexture = Content.Load("background"); playerTexture = Content.Load("player"); foregroundTexture = Content.Load("foreground"); font = Content.Load("gameFont"); rect = new Texture2D(GraphicsDevice, 1, 1); rect.SetData(new[] { Color.White }); screenWidth = device.PresentationParameters.BackBufferWidth; screenHeight = device.PresentationParameters.BackBufferHeight; BuildPlayerData(); BuildPlatformData(); BuildHazardData(); } protected override void UnloadContent() { } void CollisionDetection() { // I removed checking collision with LEVEL and added "LEVEL" as a platform.. there is nothing special about it // And it should really just be treated like any other platform // I've also removed the screen bounds checking... because if you ever add a camera and have your level "scroll" // Your code wouldn't be useful anymore. Just add platforms to the screen edges to prevent movement! Then all your code above works with it :) // Less, more generalized code is the way to do things! // Figure out the character's destination... collision code will be run based on this position... not the current position! Rectangle player = new Rectangle((int)Player.Position.X, (int)Player.Position.Y, 28, 28); Player.CanJump = false; // if they're moving they can't jump until they collide with the top of an object (platform/ground) // Check horizontal collision int finalH = (int)(player.X + Player.Velocity.X); while (true) { // We want to move from point A to point B slowly to check for collisions along the way player.X += Interpolate(player.X, finalH, player.Width); bool collided = false; foreach (Rectangle i in Platform) { if (player.Intersects(i)) { collided = true; if (player.Center.X > i.Center.X) player.X = i.Right; // Intersecting right else player.X = i.Left - player.Width; // intersecting left } } // If we collided with an object or reached our destination with no collision, we're done here if (collided || player.X == finalH) break; } // Check Vertical collision int finalV = (int)(player.Y + Player.Velocity.Y); while (true) { // We want to move from point A to point B slowly to check for collisions along the way player.Y += Interpolate(player.Y, finalV, player.Height); bool collided = false; foreach (Rectangle i in Platform) { if (player.Intersects(i)) { collided = true; if (player.Center.Y > i.Center.Y) player.Y = i.Bottom; // Intersecting bottom else { player.Y = i.Top - player.Height; // Intersecting top Player.CanJump = true; } } } foreach (Rectangle o in Hazard) { if (player.Intersects(o)) { Player.IsAlive = false; collided = true; if (player.Center.Y > o.Center.Y) player.Y = o.Bottom; // Intersecting bottom else { player.Y = o.Top ; // Intersecting top Player.CanJump = true; } } } // If we collided with an object or reached our destination with no collision, we're done here if (collided || player.Y == finalV) break; } // Now that the collisions have been figured out, let's update the player's location to where it should be! Player.Position = new Vector2(player.X, player.Y); } void CheckWin() { if (Player.Position.X >= screenWidth - 28) Player.Win = true; } int Interpolate(int start, int end, int step) { // This function just returns a value starting from START and moving towards END in STEP increments int difference = Math.Abs(end - start); int increment = Math.Min(difference, step); if (start > end) return -increment; else return increment; } // We can make this a const since we won't be changing it const float gravity = 1.25f; protected override void Update(GameTime gameTime) { float elapsedTime = (float)gameTime.ElapsedGameTime.TotalMilliseconds; float speed = elapsedTime * .3f; KeyboardState keyboard = Keyboard.GetState(); if (Player.IsAlive && !Player.Win) { // Horizontal movement if (keyboard.IsKeyDown(Keys.A)) Player.Velocity.X += speed*-5; else if (keyboard.IsKeyDown(Keys.D)) Player.Velocity.X += speed*5; else Player.Velocity.X = 0; // Cancel momentum... Stop moving when they let go // Limit their horizontal velocity const float maxVelocityH = 5f; Player.Velocity.X = MathHelper.Clamp(Player.Velocity.X, -maxVelocityH, maxVelocityH); // Vertical movement if (keyboard.IsKeyDown(Keys.W) && Player.CanJump) Player.Velocity.Y += speed*-15; else if (keyboard.IsKeyDown(Keys.S)) Player.Velocity.Y += speed*1; // Limit their vertical velocity const float maxVelocityV = 15f; Player.Velocity.Y = MathHelper.Clamp(Player.Velocity.Y, -maxVelocityV, maxVelocityV / 2); // Apply Gravity (downwards force) Player.Velocity.Y += gravity; // Collision Detection CollisionDetection(); CheckWin(); } if (keyboard.IsKeyDown(Keys.R)) BuildPlayerData(); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); DrawLevel(); DrawText(); if (!Player.IsAlive) PlayerDead(); else if (Player.Win) PlayerWin(); spriteBatch.End(); base.Draw(gameTime); } private void PlayerDead() { //spriteBatch.Draw(rect, new Rectangle(screenWidth / 2, screenHeight / 2, 92, 22), Color.Red); spriteBatch.DrawString(font, "YOU DIED!\nPress R to restart.", new Vector2(screenWidth / 2, screenHeight / 2), Color.White); } private void PlayerWin() { spriteBatch.DrawString(font, "YOU WON!\nPress R to restart.", new Vector2(screenWidth / 2, screenHeight / 2), Color.White); } private void DrawText() { spriteBatch.DrawString(font, String.Format("Position: {0} Gravity: {1} Velocity: {2}", Player.Position, gravity, Player.Velocity), new Vector2(0, 0), Color.White); } private void DrawLevel() { Rectangle screenRectangle = new Rectangle(0, 0, screenWidth, screenHeight); spriteBatch.Draw(backgroundTexture, screenRectangle, Color.White); foreach (Rectangle i in Platform) spriteBatch.Draw(rect, i, Color.Green); foreach (Rectangle i in Hazard) spriteBatch.Draw(rect, i, Color.Red); DrawPlayer(); spriteBatch.Draw(foregroundTexture, new Rectangle(0, 0, 380, screenHeight), Color.White); } private void DrawPlayer() { spriteBatch.Draw(playerTexture, Player.Position, Color.White); } } }