So, me being a new programmer, instead doing actual AI implementation, I used the following solution for making paddle on the right follow the ball:
if(ball.position.Y > p2.position.Y)
{
p2.position.Y += ball.position.Y;
}
else if (ball.position.Y < p2.position.Y)
{
p2.position.Y -= ball.position.Y;
}
However this happens (okay I tried taking a screencap with PrtScn and it semmed to pause the game for a microsecond and capture a single paddle instead of two flickering ones)
So what can I do to make the paddle 2 appear as one?
Full Code of Program:
Game1.cs
namespace Pong
{
public class Game1 : Game
{
//Variables
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
//Borders
Texture2D borders;
//Paddles
Paddle p1 = new Paddle();
Paddle p2 = new Paddle();
//Ball
Ball ball = new Ball();
//Positions
Vector2 bordersPos;
//Constructor
public Game1()
: base()
{
graphics = new GraphicsDeviceManager(this);
graphics.IsFullScreen = false;
graphics.PreferredBackBufferHeight = 800;
graphics.PreferredBackBufferWidth = 800;
//Content Loader
Content.RootDirectory = "Content";
}
//Initialize
protected override void Initialize()
{
base.Initialize();
}
//Load Content
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
//Assigning Textures
p1.texture = Content.Load<Texture2D>("paddle1");
p2.texture = Content.Load<Texture2D>("paddle1");
borders = Content.Load<Texture2D>("borders");
ball.texture = Content.Load<Texture2D>("ball");
//Positions
p1.position.X = 50;
p1.position.Y = graphics.GraphicsDevice.Viewport.Height / 2 - (p1.height / 2);
p2.position.X = graphics.GraphicsDevice.Viewport.Width - 50 - p2.width;
p2.position.Y = graphics.GraphicsDevice.Viewport.Height / 2 - (p2.height / 2);
bordersPos.Y = 0;
bordersPos.X = 0;
ball.position.X = 800 / 2 - ball.width / 2;
ball.position.Y = 800 / 2 - ball.height / 2;
}
protected override void UnloadContent()
{
}
//Update
protected override void Update(GameTime gameTime)
{
//Exit
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
//Update Player Controls
PlayerInput();
p1.Update();
p2.Update();
ball.Update();
//Paddle Collision
if (padCol1())
{
if (ball.movingDownLeft)
{
ball.movingDownRight = true;
ball.movingDownLeft = false;
}
else if (ball.movingUpLeft)
{
ball.movingUpRight = true;
ball.movingUpLeft = false;
}
}
if (padCol2())
{
if (ball.movingDownRight)
{
ball.movingDownLeft = true;
ball.movingDownRight = false;
}
else if (ball.movingUpRight)
{
ball.movingUpLeft = true;
ball.movingUpRight = false;
}
}
//AI
if(ball.position.Y > p2.position.Y){
p2.position.Y += ball.position.Y;
}
else if (ball.position.Y < p2.position.Y)
{
p2.position.Y -= ball.position.Y;
}
base.Update(gameTime);
}
//Draw
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
//Paddles
p1.Draw(spriteBatch);
p2.Draw(spriteBatch);
//Borders
spriteBatch.Draw(borders, bordersPos, Color.White);
//Ball
ball.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
//Player Input
public void PlayerInput()
{
//Player 1
if (Keyboard.GetState(p1.pNumber).IsKeyDown(Keys.W))
{
p1.position.Y -= p1.speed;
}
else if (Keyboard.GetState(p1.pNumber).IsKeyDown(Keys.S))
{
p1.position.Y += p1.speed;
}
}
//Paddle Collision
public bool padCol1()
{
if (ball.position.Y >= p1.position.Y && ball.position.X > p1.position.X && ball.position.X < (p1.position.X + p1.width) && ball.position.Y < (p1.position.Y + p1.height))
{
return true;
}
else
return false;
}
public bool padCol2()
{
if (ball.position.Y >= p2.position.Y && ball.position.X > p2.position.X && ball.position.X < (p2.position.X + p2.width) && ball.position.Y < (p2.position.Y + p2.height))
{
return true;
}
else
return false;
}
}
}
Paddle.cs
namespace Pong
{
class Paddle : Game
{
//Variables
public Texture2D texture;
public Vector2 position;
public PlayerIndex pNumber;
public int width, height;
public float speed;
//Constructor
public Paddle()
{
texture = null;
position = Vector2.Zero;
pNumber = PlayerIndex.One;
width = 64;
height = 187;
speed = 10.0f;
}
//Update
public void Update()
{
//Set Boundaries
if (position.Y <= 30)
position.Y = 30;
if (position.Y >= 800 - 217)
position.Y = 800 - 217;
}
//Draw
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, position, Color.White);
}
}
}
Ball.cs
namespace Pong
{
class Ball : Game
{
//Variables
public Vector2 position;
public Texture2D texture;
public int width, height;
public float speed;
public bool movingDownLeft, movingUpLeft, movingDownRight, movingUpRight;
//Constructor
public Ball()
{
Content.RootDirectory = ("Content");
speed = 6.0f;
width = 20;
height = 20;
movingDownLeft = true;
movingUpRight = false;
movingUpLeft = false;
movingDownRight = false;
position = Vector2.Zero;
}
//Draw
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, position, Color.White);
}
//Update
public void Update()
{
//Directions
if(movingUpLeft){
position.Y -= speed;
position.X -= speed;
}
if (movingDownLeft)
{
position.Y += speed;
position.X -= speed;
}
if (movingUpRight)
{
position.Y -= speed;
position.X += speed;
}
if (movingDownRight)
{
position.Y += speed;
position.X += speed;
}
//Ball Wall Collision
if(movingUpLeft && position.Y <= 30)
{
movingDownLeft = true;
movingUpLeft = false;
}
//1
else if (movingDownLeft && position.X <= 0)
{
movingDownRight = true;
movingDownLeft = false;
}
//2
else if (movingUpLeft && position.X <= 0)
{
movingUpRight = true;
movingUpLeft = false;
}
//3
else if (movingDownLeft && position.Y >= 800 - 45)
{
movingUpLeft = true;
movingDownLeft = false;
}
//4
else if (movingDownRight && position.X >= 800 - width)
{
movingDownLeft = true;
movingDownRight = false;
}
//5
else if (movingUpRight && position.Y <= 30)
{
movingDownRight = true;
movingUpRight = false;
}
//6
else if (movingDownRight && position.Y >= 800 - 45)
{
movingUpRight = true;
movingDownRight = false;
}
//7
else if (movingUpRight && position.X >= 800 - width)
{
movingUpLeft = true;
movingUpRight = false;
}
}
}
}
I've never used MonoGame but this:
if(ball.position.Y > p2.position.Y)
{
p2.position.Y += ball.position.Y;
}
else if (ball.position.Y < p2.position.Y)
{
p2.position.Y -= ball.position.Y;
}
Will always overshoot, then trigger the next update.
Why don't you just do:
p2.position.Y = ball.position.Y;
EDIT: You should probably also make your input frame rate independent (unless MonoGame does this for you). It seems now you'll get higher movement speed on your paddles depending on your framerate.
Paddle and Game must not inherit from Game (GameComponent instead?). There should be only one Game object.
You should replace you booleans and complex logic with Vector2 speed describing speed of a ball, and negate its X or Y when the ball hits something.
Related
I'm working on a custom physics engine for a potential game - based in Monogame/the XNA framework. While the physics itself works pretty well, I'm running into an issue with collision. When the player comes out of a jump, they can often end up inside the floor.
See image below. I did a couple hours of research on my own and found out that what I probably need is continous collision detection (CCD) similar to how something like Unity might implement it, but all the questions I've found here or other places haven't really worked, and neither has any of my solutions, so I'm asking the strangers on the internet who are smarter than me.
Here's game 1:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended;
using AmethystDawn.Utilities;
namespace AmethystDawn
{
internal class Game1 : Game
{
Texture2D spritesheet;
Texture2D spritesheetFlipped;
Texture2D activeSpritesheet;
Texture2D platform;
float timer; // millisecond timer
int threshold;
Rectangle[] sourceRectangles;
byte previousAnimationIndex;
byte currentAnimationIndex;
RectangleF playerCollider;
RectangleF groundCollider;
PhysicsCalculator physics = new();
Vector2 PlayerPos = new Vector2(0, 0);
private GraphicsDeviceManager graphics;
private SpriteBatch sprites;
private SpriteFont font;
public Game1() : base()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsFixedTimeStep = true;
IsMouseVisible = true;
IsFixedTimeStep = false;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
graphics.PreferredBackBufferWidth = GraphicsDevice.DisplayMode.Width;
graphics.PreferredBackBufferHeight = GraphicsDevice.DisplayMode.Height;
graphics.IsFullScreen = true;
graphics.HardwareModeSwitch = false;
graphics.ApplyChanges();
sprites = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>("Fonts/november");
spritesheet = Content.Load<Texture2D>("Sprites/Player/player spritesheet");
spritesheetFlipped = Content.Load<Texture2D>("Sprites/Player/player spritesheet flipped");
platform = Content.Load<Texture2D>("Sprites/platform");
activeSpritesheet = spritesheet;
timer = 0;
threshold = 100;
sourceRectangles = new Rectangle[4];
sourceRectangles[0] = new Rectangle(0, 0, 32, 40);
sourceRectangles[1] = new Rectangle(34, 0, 28, 40);
sourceRectangles[2] = new Rectangle(66, 0, 28, 40);
sourceRectangles[3] = new Rectangle(96, 0, 32, 40);
previousAnimationIndex = 2;
currentAnimationIndex = 1;
base.LoadContent();
}
protected override void UnloadContent()
{
base.UnloadContent();
}
protected override void Update(GameTime gameTime)
{
if (timer > threshold) // check if the timer has exceeded the threshold
{
if (currentAnimationIndex == 1) // if sprite is in the middle sprite of the animation
{
if (previousAnimationIndex == 0) // if the previous animation was the left-side sprite, then the next animation should be the right-side sprite
{
currentAnimationIndex = 2;
}
else
{
currentAnimationIndex = 0; // if not, then the next animation should be the left-side sprite
}
previousAnimationIndex = currentAnimationIndex;
}
else
{
currentAnimationIndex = 1; // if not in the middle sprite of the animation, return to the middle sprite
}
timer = 0;
}
else
{
// if the timer has not reached the threshold, then add the milliseconds that have past since the last Update() to the timer
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
sprites.Begin();
sprites.Draw(platform, new Vector2(0, GraphicsDevice.Viewport.Height - platform.Height - 50), Color.White);
groundCollider = new RectangleF(0, GraphicsDevice.Viewport.Height - platform.Height - 50, platform.Width, platform.Height);
var kstate = Keyboard.GetState();
playerCollider = new(PlayerPos.X, PlayerPos.Y, sourceRectangles[currentAnimationIndex].Width, sourceRectangles[currentAnimationIndex].Height);
if (IsColliding(groundCollider, playerCollider))
{
physics.UpdatePhysicValues(false);
/*if (PlayerPos.Y + playerCollider.Height + 100 > groundCollider.Y)
{
PlayerPos.Y = groundCollider.Y - groundCollider.Height;
}*/
if (kstate.IsKeyDown(Keys.Space))
{
physics.Jump(3f);
}
}
else
{
physics.UpdatePhysicValues(true);
if (kstate.IsKeyDown(Keys.Space))
{
physics.MidairJump(3f);
}
else
{
physics.LockJump();
}
}
if (kstate.IsKeyDown(Keys.A))
{
physics.ApplyWalkingForce(new Vector2(-1, 0), 0.5f);
activeSpritesheet = spritesheetFlipped;
sourceRectangles[0] = new Rectangle(0, 0, 32, 40);
sourceRectangles[1] = new Rectangle(34, 0, 28, 40);
sourceRectangles[2] = new Rectangle(66, 0, 28, 40);
sourceRectangles[3] = new Rectangle(96, 0, 32, 40);
}
else if (kstate.IsKeyDown(Keys.D))
{
physics.ApplyWalkingForce(new Vector2(1, 0), 0.5f);
activeSpritesheet = spritesheet;
sourceRectangles[0] = new Rectangle(96, 0, 32, 40);
sourceRectangles[1] = new Rectangle(66, 0, 28, 40);
sourceRectangles[2] = new Rectangle(34, 0, 28, 40);
sourceRectangles[3] = new Rectangle(0, 0, 32, 40);
}
else
{
}
if (kstate.IsKeyDown(Keys.S) && !IsColliding(groundCollider, playerCollider))
{
physics.ApplyExtraGravity(1f);
}
if (kstate.IsKeyDown(Keys.R))
{
PlayerPos = new Vector2(0, 0);
}
PlayerPos = physics.position(PlayerPos);
// is player on the bounds of the screen
if (PlayerPos.X < 0)
{
PlayerPos.X = 0;
physics.HitWall();
}
else if (PlayerPos.X > GraphicsDevice.Viewport.Width - 32)
{
PlayerPos.X = GraphicsDevice.Viewport.Width - 32;
physics.HitWall();
}
sprites.Draw(activeSpritesheet, PlayerPos, sourceRectangles[currentAnimationIndex], Color.White, 0f, new Vector2(0, 0), 1f, SpriteEffects.None, 0f);
sprites.End();
base.Draw(gameTime);
}
private bool IsColliding(RectangleF rect1, RectangleF rect2)
{
return rect1.Intersects(rect2);
}
}
}
And here's the physics calculator:
using System.Diagnostics;
using Microsoft.Xna.Framework;
namespace AmethystDawn.Utilities
{
internal class PhysicsCalculator
{
private float directionalForce;
private Vector2 direction;
private const float directionalForceMax = 10f;
private float walkingForce;
private const float walkingForceMax = 0.5f;
private float gravityForce;
private const float gravityForceMax = 25f;
private float jumpForce;
private const float jumpForceMax = 5f;
private int framesInAir;
private const int framesInAirMax = 90;
public void UpdatePhysicValues(bool falling)
{
if (directionalForce > 0)
{
directionalForce -= 0.5f;
}
if (walkingForce > 0)
{
walkingForce -= 0.02f;
}
else
{
walkingForce = 0;
}
if (gravityForce > jumpForce)
{
if (falling && !(gravityForce > gravityForceMax))
{
gravityForce += 0.2f;
}
else if (!falling)
{
gravityForce = 0;
direction.Y = 0;
framesInAir = 0;
}
}
else
{
jumpForce -= 0.3f;
}
FixDirection();
}
public void ApplyDirectionalForce(Vector2 directionHit, float forceToApply)
{
direction += directionHit;
directionalForce += forceToApply;
if (directionalForce > directionalForceMax) directionalForce = directionalForceMax;
}
public void ApplyWalkingForce(Vector2 directionWalked, float forceToApply)
{
direction += directionWalked;
walkingForce += forceToApply;
if (walkingForce > walkingForceMax) walkingForce = walkingForceMax;
}
public void Jump(float force)
{
direction += new Vector2(0, -1);
jumpForce += force;
if (jumpForce > jumpForceMax) jumpForce = jumpForceMax;
}
public void MidairJump(float force)
{
framesInAir++;
if (framesInAir > framesInAirMax) return;
jumpForce += force;
if (jumpForce > jumpForceMax) jumpForce = jumpForceMax;
}
public void LockJump()
{
framesInAir = framesInAirMax;
}
public void ApplyExtraGravity(float amount)
{
gravityForce += amount;
}
public Vector2 position(Vector2 currentPosition)
{
currentPosition += new Vector2(0, gravityForce);
currentPosition += new Vector2(direction.X * directionalForce, direction.Y * directionalForce);
currentPosition += new Vector2(direction.X * walkingForce, direction.Y * walkingForce);
currentPosition += new Vector2(0, direction.Y * jumpForce);
return currentPosition;
}
public void HitWall()
{
direction.X = 0;
}
private void CorrectGravity()
{
}
private void FixDirection()
{
if (direction.X > 20) direction.X = 20;
if (direction.Y > 20) direction.Y = 20;
if (direction.X < -20) direction.X = -20;
if (direction.Y < -15) direction.Y = -15;
if (walkingForce <= 0 && directionalForce <= 0) direction.X = 0;
}
}
}
And the image:
I remember watching a tutorial that explained the way of handling continious collision, but that was done in GameMaker Studio 2.
I'll try to translate it to XNA.
In short: You need to check for collision ahead of you with calculating the collision with the current speed beforehand, then let the player approach the solid object 1 pixel at a time through a while loop, and once it hits, then set the velocity in that direction to 0.
Original GMS2 code:
if place_meeting(x+hspeed_, y, o_solid) {
while !place_meeting(x+sign(hspeed_), y, o_solid) {
x += sign(hspeed_);
}
hspeed_ = 0;
}
x += hspeed_;
translated to XNA (dummy code as quick example):
private bool IsColliding(RectangleF rect1, RectangleF rect2, int vspeed)
{
if (rect1.Intersects(new Rectangle(rect2.x, rect2.y + vspeed, rect2.Width, rect2.Height))
{
while (!rect1.Intersects(new Rectangle(rect2.x, rect2.y+Sign(vspeed), rect2.Width, rect2.Height)
{
rect1 += Sign(vspeed) //moves towards the collision 1 pixel at a time
}
return true;
}
return false;
}
//Sign is a build-in function in GMS2 that only returns 1, 0 or -1, depending if the number is positive, 0 or negative
private int Sign(value)
{
return (value > 0) ? 1 : (value < 0) ? -1 : 0;
}
Source: https://youtu.be/zqtT_9eWIkM?list=PL9FzW-m48fn3Ya8QUTsqU-SU6-UGEqhx6
I'm learning XNA right now and i'm pretty new to programming in general. I'm making a platformer now but when I walk into a platform from left or right I get teleported to the top of the platform. The collision is only working with the last platform added to the platform list.
This is in Game class:
LoadContent:
for (int i = 0; i < 3; i++)
{
platform0List.Add(new Platform0(new Vector2(70 + (i * 300), 400), platform0Texture));
}
Update:
protected override void Update(GameTime gameTime)
{
keyboard = Keyboard.GetState();
player.Update(keyboard, keyboardPrev, platform0List);
foreach (Platform0 platform in platform0List)
{
if (!Move_Free(player.position.X, player.position.Y + player.gravity, player.texture.Width, player.texture.Height, platform.rectangle) && player.gravity >= 0)
{
player.ground = true;
if (player.position.Y + player.texture.Height + player.gravity > platform.position.Y)
{
player.position.Y = platform.position.Y - player.texture.Height;
}
else if (player.position.Y + player.texture.Height + player.gravity < platform.position.Y)
{
player.gravity = platform.position.Y - player.texture.Height;
}
break;
}
else
{
player.ground = false;
}
}
if (keyboard.IsKeyDown(Keys.Escape)) this.Exit();
keyboardPrev = keyboard;
base.Update(gameTime);
}
This is my Move_Free method
public static bool Move_Free(float x, float y, int width, int height, Rectangle rectangle)
{
Rectangle rect = new Rectangle((int)x,(int)y,width,height);
if(rect.Intersects(rectangle))
{
return false;
}
else
{
return true;
}
}
This is in Player class
foreach (Platform0 platform in platform0List)
{
if (keyboard.IsKeyDown(Keys.Right) && Game1.Move_Free(position.X + 5, position.Y, texture.Width, texture.Height, platform.rectangle))
{
moveRight = true;
}
else if (keyboard.IsKeyDown(Keys.Right) && ((position.X + texture.Width - platform.position.X) * -1) > 0)
{
position.X += (position.X + texture.Width - platform.position.X) * -1;
}
else
{
moveRight = false;
}
if (keyboard.IsKeyDown(Keys.Left) && Game1.Move_Free(position.X - 5, position.Y, texture.Width, texture.Height, platform.rectangle))
{
moveLeft = true;
}
else if (keyboard.IsKeyDown(Keys.Left) && position.X - (platform.position.X + platform.texture.Width) > 0)
{
position.X -= position.X - (platform.position.X + platform.texture.Width);
}
else
{
moveLeft = false;
}
}
if (moveRight) position.X += 5;
if (moveLeft) position.X -= 5;
if (keyboard.IsKeyDown(Keys.Up) && ground)
{
gravity = -10;
ground = false;
}
if(ground)
{
gravity = 0;
}
else
{
gravity += 9.8f / 60f;
position.Y += gravity;
}
Each iteration of your forloop overwrites your moveLeft and moveRight variables.
Therefore, only the last platform values will remain.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D walkRight, walkUp, walkLeft, walkDown, currentWalk, TitleScreen, stand;
Rectangle destRect;
Rectangle sourceRect;
KeyboardState ks;
Vector2 position = new Vector2();
bool isGrounded;
bool isStanding;
float fallSpeed = 5;
enum GameStates { Titlescreen, Playing, PlayerDead, GameOver };
GameStates gameStates = GameStates.Titlescreen;
float elapsed;
float delay = 200f;
int frames = 0;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
destRect = new Rectangle(50, 50, 50, 50);
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
walkRight = Content.Load<Texture2D>("walkRight");
walkUp = Content.Load<Texture2D>("walkUp");
walkLeft = Content.Load<Texture2D>("walkLeft");
walkDown = Content.Load<Texture2D>("walkDown");
TitleScreen = Content.Load<Texture2D>("TitleScreen");
stand = Content.Load<Texture2D>("SpriteStill");
currentWalk = walkRight;
}
protected override void UnloadContent()
{
}
private void Animate(GameTime gameTime)
{
elapsed += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
if (elapsed >= delay)
{
if (frames >= 3)
{
frames = 0;
}
else
{
frames++;
}
elapsed = 0;
}
sourceRect = new Rectangle(50 * frames, 0, 50, 50);
}
protected override void Update(GameTime gameTime)
//{
//switch (gameStates)
{
if (position.Y >= Window.ClientBounds.Height - 50)
{
position.Y = Window.ClientBounds.Height - 50;
isGrounded = true;
}
if (position.X >= Window.ClientBounds.Width - 50)
{
position.X = Window.ClientBounds.Width - 50;
}
if (position.X <=0)
{
position.X = 0;
}
if (position.Y <= 0)
{
position.Y = 0;
}
ks = Keyboard.GetState();
if (ks.IsKeyDown(Keys.Right))
{
position.X += 3f;
currentWalk = walkRight;
}
if (ks.IsKeyDown(Keys.Left))
{
position.X -= 3f;
currentWalk = walkLeft;
}
if (ks.IsKeyDown(Keys.Down))
{
position.Y += 3f;
currentWalk = walkDown;
}
Animate(gameTime);
destRect = new Rectangle((int)position.X, (int)position.Y, 50, 50);
FallManagement(gameTime);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(currentWalk, destRect, sourceRect, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
void FallManagement(GameTime gameTime)
{
position.Y += fallSpeed;
if (isGrounded == false)
{
fallSpeed = 5;
}
if (ks.IsKeyDown(Keys.Up) && isGrounded)
{
fallSpeed -= 40;
isGrounded = false;
}
}
}
I am currently creating a very basic XNA platform game for a year one college course. I am currently trying to add in a statement that says if no keys are pressed then load up texture 2d for the character standing still, but I'm not sure how to check if the there are no keys pressed at all. Basically I would like to check if the character is moving and if not the texture for the character would be set to the standing still png, does anyone know how I might do this. I have put the entire code into this question because I am not using a character class and my level of coding is extremely basic at current.
Maybe something like this would work for you:
ks = Keyboard.GetState();
bool isMoving = false;
if (ks.IsKeyDown(Keys.Right))
{
position.X += 3f;
currentWalk = walkRight;
isMoving = true;
}
if (ks.IsKeyDown(Keys.Left))
{
position.X -= 3f;
currentWalk = walkLeft;
isMoving = true;
}
if (ks.IsKeyDown(Keys.Down))
{
position.Y += 3f;
currentWalk = walkDown;
isMoving = true;
}
if (!isMoving)
{
//Do whatever you need to do when the player is still here
}
Basically set a flag when any key is pressed that you handle, and then use that flag later to do what you need to do when no key is pressed.
Or, if you want to check that no key is pressed what-so-ever:
if (ks.GetPressedKeys() == null || ks.GetPressedKeys().Length == 0)
{
//No keys pressed at all. (not sure if GetPressedKeys returns null
//or zero length array, check when in debug then remove one.
}
I am trying to create collision between my bullets and the enemy. I have created bounding boxes for each and placed them into their own class. However, I am getting a Null reference error in my HandleCollision function, specifically at the if statement with the bounding boxes. I will also post the rest of my code.
I discussed this with two lecturers and some peers and they asy it is because bullet and enemy are equal to null. This is because it takes a couple of seconds for the enemy to spawn and the bullet only spawns once it is fired. To counter this I added an if statement to check if the bullet or enemy is null yet it still throws up the same error.
HandleCollision Function
private void HandleCollision()//collision
{
Sprite toRemove = null;
if (bullet != null || enemyTexture != null)
{
foreach (EnemySprite e in enemyList) //checks each enemy sprite
{
if (bullet.BoundingBox.Intersects(enemy.BoundingBox))
{
enemyList.Remove(enemy); //removes enemy
//toRemove = enemy;
break;
}
}
}
}
Game1.cs
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Rectangle spriteRectangle;
Rectangle bulletBounds;
Rectangle enemyBounds;
Sprite player;
Bullets bullet;
EnemySprite enemy;
Texture2D enemyTexture;
List<EnemySprite> enemyList = new List<EnemySprite>();
List<Bullets> bulletsList = new List<Bullets>();
//Pause
bool paused = false;
Texture2D pauseTexture;
Rectangle pauseRectangle;
KeyboardState pastKey;
Vector2 enemyPos = new Vector2(100, 400);
Vector2 Position;
Vector2 Distance;
Vector2 spriteOrigin;
Vector2 spriteVelocity;
const float tangentialVelocity = 5f;
float friction = 0.1f;
float rotation;
float timer = 0f;
float dropInterval = 2f;
float speed = 4f;
float angle;
Random random;
enum GameState
{
MainMenu,
Options,
Help,
Playing,
Exit,
}
GameState CurrentGameState = GameState.MainMenu;
// Screen adjustments
int screenWidth = 800, screenHeight = 600;
cButton btnPlay;
cButtonExit btnExit;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
random = new Random();
Position = new Vector2(150, 150);
this.IsMouseVisible = true;
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
player = new Sprite();
player.Texture = Content.Load<Texture2D>("graphics/player");
// Screen stuff
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
//graphics.IsFullScreen = true;
graphics.ApplyChanges();
IsMouseVisible = true;
btnPlay = new cButton(Content.Load<Texture2D>("Graphics/play"), graphics.GraphicsDevice);
btnPlay.setPosition(new Vector2(350, 190));
btnExit = new cButtonExit(Content.Load <Texture2D>("Graphics/exit"), graphics.GraphicsDevice);
btnExit.setPosition(new Vector2(350, 220));
enemyTexture = Content.Load<Texture2D>("graphics/enemy");
player.Texture = Content.Load<Texture2D>("Graphics/player");
int screenCenterX = GraphicsDevice.Viewport.Width / 2;
player.Position = new Vector2(screenCenterX - (player.Texture.Width / 2), screenHeight - player.Texture.Height - 20);
pauseTexture = Content.Load<Texture2D>("graphics/paused");
pauseRectangle = new Rectangle(0, 0, pauseTexture.Width, pauseTexture.Height);
Bullets.BulletTexture = Content.Load<Texture2D>("graphics/bullet");
}
protected override void Update(GameTime gameTime)
{
MouseState mouse = Mouse.GetState();
IsMouseVisible = true;
switch (CurrentGameState)
{
case GameState.MainMenu:
if (btnPlay.isClicked == true) CurrentGameState = GameState.Playing;
btnPlay.Update(mouse);
if (btnExit.isClicked == true) CurrentGameState = GameState.Help;
btnExit.Update(mouse);
if (btnExit.isClicked == true) CurrentGameState = GameState.Options;
btnExit.Update(mouse);
if (btnExit.isClicked == true) CurrentGameState = GameState.Exit;
btnExit.Update(mouse);
break;
case GameState.Playing:
timer += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (timer >= dropInterval)
{
int yPos = random.Next(GraphicsDevice.Viewport.Height - 50);
enemyList.Add(new EnemySprite(enemyTexture, new Vector2(GraphicsDevice.Viewport.Width + 100, yPos)));
timer = 0f;
}
HandleCollision();
HandleMovingEnemy();
MouseState curMouse = Mouse.GetState();
Vector2 mouseLoc = new Vector2(curMouse.X, curMouse.Y);
Vector2 direction = mouseLoc - Position;
spriteRectangle = new Rectangle((int)Position.X, (int)Position.Y,
player.Texture.Width, player.Texture.Height);
Position = spriteVelocity + Position;
spriteOrigin = new Vector2(spriteRectangle.Width / 2, spriteRectangle.Height / 2);
Distance.X = mouse.X - Position.X;
Distance.Y = mouse.Y - Position.Y;
rotation = (float)Math.Atan2(Distance.Y, Distance.X); //calculates the rotation(trigonometry)
//angle = (float)(Math.Atan2(direction.Y, direction.X));
KeyboardState keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.A))
Position.X -= 2;
if (keyState.IsKeyDown(Keys.D))
Position.X += 2;
if (keyState.IsKeyDown(Keys.W))
Position.Y -= 2;
if (keyState.IsKeyDown(Keys.S))
Position.Y += 2;
//right and left edge detection
if (Position.X < 0)
Position = new Vector2(0, Position.Y);
int rightEdge = GraphicsDevice.Viewport.Width - player.Texture.Width;
if (Position.X > rightEdge)
Position = new Vector2(rightEdge, Position.Y);
//bottom and top edge detection
if (Position.Y < 0)
Position = new Vector2(Position.X, 0);
int bottomEdge = GraphicsDevice.Viewport.Height - player.Texture.Height;
if (Position.Y > bottomEdge)
Position = new Vector2(Position.X, bottomEdge);
if (!paused)
{
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
{
paused = true;
btnPlay.isClicked = false; //so that everytime its paused I can pause it again
}
enemy.Update();
}
else if(paused)
{
if (btnPlay.isClicked)
{
paused = false;
speed = 4;
}
if (btnExit.isClicked)
Exit();
btnPlay.Update(mouse);
btnExit.Update(mouse);
}
if (Keyboard.GetState().IsKeyDown(Keys.C))
{
spriteVelocity.X = (float)Math.Cos(rotation) * tangentialVelocity;
spriteVelocity.Y = (float)Math.Sin(rotation) * tangentialVelocity;
}
else if (spriteVelocity != Vector2.Zero)
{
float i = spriteVelocity.X;
float j = spriteVelocity.Y;
spriteVelocity.X = i -= friction * i;
spriteVelocity.Y = j -= friction * j;
}
if (Keyboard.GetState().IsKeyDown(Keys.Space) && pastKey.IsKeyUp(Keys.Space))
Fire();
pastKey = Keyboard.GetState();
UpdateBullets();
break;
case GameState.Exit:
this.Exit();
break;
}
base.Update(gameTime);
}
public void UpdateBullets()
{
foreach (Bullets bullet in bulletsList)
{
bullet.position += bullet.velocity;
if(Vector2.Distance(bullet.position, Position) > 500) //finds position
bullet.isVisible = false;
}
for (int i = 0; i < bulletsList.Count; i++)
{
if (!bulletsList[i].isVisible)
{
bulletsList.RemoveAt(i);
i--;
}
}
}
//function to handle movement of enemies
private void HandleMovingEnemy()
{
List<EnemySprite> toRemove = new List<EnemySprite>();
foreach (EnemySprite e in enemyList)
{
if (e.Position.X < (-20))
{
toRemove.Add(e);
}
else
e.Position -= new Vector2(speed, 0);
}
if (toRemove.Count > 0)
{
foreach (EnemySprite e in toRemove)
{
enemyList.Remove(e);
}
}
}
private void HandleCollision()//collision
{
Sprite toRemove = null;
if (bullet != null || enemyTexture != null)
{
foreach (EnemySprite e in enemyList) //checks each enemy sprite
{
if (bullet.BoundingBox.Intersects(enemy.BoundingBox)) //checks if a sprite has intersected an enemy
{
enemyList.Remove(enemy); //removes enemy
//toRemove = enemy;
break;
}
}
}
}
public void Fire()
{
Bullets newBullet = new Bullets(Content.Load<Texture2D>("graphics/bullet"));
Bullets.BulletTexture = Content.Load<Texture2D>("graphics/bullet");
//newBullet.LoadContent(LoadContent);
newBullet.velocity = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * 5f + spriteVelocity;
newBullet.position = Position + newBullet.velocity * 5;
newBullet.isVisible = true;
if (bulletsList.Count() < 20)
bulletsList.Add(newBullet);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
switch (CurrentGameState)
{
case GameState.MainMenu:
spriteBatch.Draw(Content.Load<Texture2D>("Graphics/SeaSideDefenderMainMenu"), new Rectangle(0,0,screenWidth,screenHeight),Color.White);
btnPlay.Draw(spriteBatch);
btnExit.Draw(spriteBatch);
break;
case GameState.Playing:
spriteBatch.Draw(Content.Load<Texture2D>("Graphics/leveltest"), new Rectangle(0, 0, screenWidth, screenHeight), Color.White);
enemy = new EnemySprite();
enemy.Texture = Content.Load<Texture2D>("graphics/enemy");
enemy.Draw(spriteBatch);
spriteBatch.Draw(player.Texture, Position, null, Color.White, rotation, spriteOrigin, 1f, SpriteEffects.None, 0);
//player.Draw(spriteBatch);
foreach (EnemySprite e in enemyList)
{
e.Draw(spriteBatch);
}
foreach (Bullets bullet in bulletsList)
bullet.draw(spriteBatch);
/*for (int i; i < enemyList.Count; i++)
{
enemyList[i].Draw(spriteBatch);
}*/
if (paused)
{
speed = 0;
spriteBatch.Draw(Content.Load<Texture2D>("graphics/paused"), new Rectangle(0, 0, screenWidth, screenHeight), Color.White);
btnPlay.Draw(spriteBatch);
btnExit.Draw(spriteBatch);
}
break;
}
spriteBatch.End();
base.Draw(gameTime);
}
}
Bullets.cs
public class Bullets
{
public Texture2D texture;
public static Texture2D BulletTexture;
public Vector2 position;
public Vector2 velocity;
public Vector2 origin;
public bool isVisible;
public Rectangle BoundingBox
{
get { return new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height); }
}
public Bullets(Texture2D newTexture)
{
texture = newTexture;
isVisible = false;
}
public void draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture,position,null,Color.White,0f,origin,1f, SpriteEffects.None, 0);
}
public void LoadContent(ContentManager Content)
{
BulletTexture = Content.Load<Texture2D>(#"graphics/bullet");
}
}
enemy.cs
public class EnemySprite
{
public Texture2D Texture { get; set; }
public Vector2 Position {get; set; }
public Vector2 origin;
public Vector2 velocity;
public Rectangle rectangle;
float rotation = 0f;
bool right;
float distance;
float oldDistance;
public Rectangle BoundingBox
{
get { return new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height); } //uses enemy position and wiwdth to create bounding box
}
public EnemySprite() { }
public EnemySprite(Texture2D texture, Vector2 position)
{
Texture = texture;
Position = position;
oldDistance = distance;
}
float mouseDistance;
public void Update()
{
Position += velocity;
origin = new Vector2(Texture.Width / 2, Texture.Height / 2);
if (distance <= 0)
{
right = true;
velocity.X = 1f;
}
else if(distance <= oldDistance)
{
right = false;
velocity.X = -1f;
}
if (right) distance += 1; else distance -= 1;
MouseState mouse = Mouse.GetState();
mouseDistance = mouse.X - Position.X;
if (mouseDistance >= -200 && mouseDistance <= 200)
{
if (mouseDistance < -1)
velocity.X = -1f;
else if (mouseDistance > 1)
velocity.X = 1f;
else if (mouseDistance == 0)
velocity.X = 0f;
}
}
public void Draw(SpriteBatch spriteBatch)
{
if (Texture != null)
spriteBatch.Draw(Texture, Position, Color.White);
if (velocity.X > 0)
{
spriteBatch.Draw(Texture, Position, null, Color.White, rotation, origin, 1f, SpriteEffects.FlipHorizontally, 0f);
}
else
{
spriteBatch.Draw(Texture, Position, null, Color.White, rotation, origin, 1f, SpriteEffects.None, 0f);
}
}
}
You've not initialized the global variable Bullet anywhere in your game1.cs, therefore you would never get bullet != null as true. Whereas, you'll always get enemyTexture != null as true since you've already initialized enemyTexture in the LoadContent().
Which means you'll always enter the if block while having the Bullet variable not initialized.
Hope this will lead you to the solution.
PS: Do mark the answer as 'Accepted' if this was the most helpful one.
I want that an enemy shoots a bullet if the variable nextShot is bigger than the variable shotFrequency. Each enemy should fire independently.
But for the moment, every enemy is shooting all the time. Their are no breaks between the shots. But I want that their always is a little break between the shots. I'm sure that something is wrong about the Enemy class, but I don't know what to change. Could somebody help me, please?
public class Map
{
Texture2D myEnemy, myBullet;
Player Player;
List<Enemy> enemieslist = new List<Enemy>();
List<Bullet> bulletslist = new List<Bullet>();
float fNextEnemy = 0.0f;
float fEnemyFreq = 2.0f;
Vector2 Startposition = new Vector2(200, 200);
Vector2 CurrentEnemyPosition;
GraphicsDeviceManager graphicsDevice;
public Map(GraphicsDeviceManager device)
{
graphicsDevice = device;
}
public void Load(ContentManager content)
{
myEnemy = content.Load<Texture2D>("enemy");
myBullet = content.Load<Texture2D>("bullet");
Player = new Player(graphicsDevice);
Player.Load(content);
}
public void Update(GameTime gameTime)
{
Player.Update(gameTime);
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
for(int i = enemieslist.Count - 1; i >= 0; i--)
{
// Update Enemy
Enemy enemy = enemieslist[i];
enemy.Update(gameTime, this.graphicsDevice, Player.playershape.Position, delta);
CurrentEnemyPosition = enemy.Bulletstartposition;
// Does the enemy shot?
if (enemy.Shot == true)
// New bullet
{
Vector2 bulletDirection = Vector2.Normalize(Player.playershape.Position - CurrentEnemyPosition) * 200f;
bulletslist.Add(new Bullet(CurrentEnemyPosition, bulletDirection, Player.playershape.Position));
}
}
this.fNextEnemy += delta;
//New enemy
if (this.fNextEnemy >= fEnemyFreq)
{
Vector2 enemyDirection = Vector2.Normalize(Player.playershape.Position - Startposition) * 100f;
enemieslist.Add(new Enemy(Startposition, enemyDirection, Player.playershape.Position));
fNextEnemy = 0;
}
for(int i = bulletslist.Count - 1; i >= 0; i--)
{
// Update Bullet
Bullet bullets = bulletslist[i];
bullets.Update(gameTime, this.graphicsDevice, delta);
}
}
public void Draw(SpriteBatch batch)
{
Player.Draw(batch);
foreach (Enemy enemies in enemieslist)
{
enemies.Draw(batch, myEnemy);
}
foreach (Bullet bullets in bulletslist)
{
bullets.Draw(batch, myBullet);
}
}
}
Enemy class:
public class Enemy
{
private float nextShot = 0;
private float shotFrequency = 1.0f;
Vector2 vPos;
Vector2 vMove;
Vector2 vPlayer;
public Vector2 Bulletstartposition;
public bool Remove;
public bool Shot;
public Enemy(Vector2 Pos, Vector2 Move, Vector2 Player)
{
this.vPos = Pos;
this.vMove = Move;
this.vPlayer = Player;
this.Remove = false;
this.Shot = false;
}
public void Update(GameTime gameTime, GraphicsDeviceManager graphics, Vector2 PlayerPos, float delta)
{
nextShot += delta;
if (nextShot >= shotFrequency)
{
this.Shot = true;
nextShot = 0;
}
if (!Remove)
{
float fMoveTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
this.vMove = Vector2.Normalize(PlayerPos - this.vPos) * 100f;
this.vPos += this.vMove * fMoveTime;
Bulletstartposition = this.vPos;
if (this.vPos.X > graphics.PreferredBackBufferWidth + 1)
{
this.Remove = true;
}
else if (this.vPos.X < -20)
{
this.Remove = true;
}
if (this.vPos.Y > graphics.PreferredBackBufferHeight + 1)
{
this.Remove = true;
}
else if (this.vPos.Y < -20)
{
this.Remove = true;
}
}
}
public void Draw(SpriteBatch spriteBatch, Texture2D myTexture)
{
if (!Remove)
{
spriteBatch.Draw(myTexture, this.vPos, Color.White);
}
}
}
Bullet class
public class Bullet
{
Vector2 vPos;
Vector2 vMove;
Vector2 vPlayer;
public bool Remove;
public Bullet(Vector2 Pos, Vector2 Move, Vector2 Player)
{
this.Remove = false;
this.vPos = Pos;
this.vMove = Move;
this.vPlayer = Player;
}
public void Update(GameTime gameTime, GraphicsDeviceManager graphics, float delta)
{
if (!Remove)
{
float fMoveTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
this.vPos.X += this.vMove.X * fMoveTime;
this.vPos.Y += this.vMove.Y * fMoveTime;
if (this.vPos.X > graphics.PreferredBackBufferWidth +1)
{
this.Remove = true;
}
else if (this.vPos.X < -20)
{
this.Remove = true;
}
if (this.vPos.Y > graphics.PreferredBackBufferHeight +1)
{
this.Remove = true;
}
else if (this.vPos.Y < -20)
{
this.Remove = true;
}
}
}
public void Draw(SpriteBatch spriteBatch, Texture2D myTexture)
{
if (!Remove)
{
spriteBatch.Draw(myTexture, this.vPos, Color.White);
}
}
}
You are never clearing the Shot flag. After you have an enemy shoot, just do enemy.Shot = false;.
Also, a tiny tip, when nextShot is >= shotFrequency, it's better to do this:
nextShot -= shotFrequency;
It accounts for the tiny bit of overlap when you go beyond shotFrequency.