XNA Rectangle intersection - c#

I recently started on XNA development and having some (very limited) experience with it in the past, I decided to try and make a Pong clone to see how much I could remember. Most of what I knew came back to me, but I am having problems with collision detection between the bats and the ball. I have 3 rectangles set to update position along with the sprites that I am using, and when the ball rectangle intersects with a bat rectangle, I multiply the X speed of the ball by -1. However this produces an unusual effect by which the ball bounces around the bat as shown in this video.What am I doing wrong here?
Here is the code for this project (poor, I know):
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace Pong
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
System.Random generator = new Random();
Texture2D ball;
Texture2D bat1;
Texture2D bat2;
Texture2D middle;
Vector2 midPos;
Vector2 bat1Pos;
Vector2 bat2Pos;
Vector2 ballPos;
Vector2 ballVelo;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Load sprites
ball = Content.Load<Texture2D>(#"sprites/pongball");
bat1 = Content.Load<Texture2D>(#"sprites/pongbat");
bat2 = Content.Load<Texture2D>(#"sprites/pongbat");
middle = Content.Load<Texture2D>(#"sprites/pongmiddle");
//Set default sprite positions
midPos.X = (Window.ClientBounds.Width / 2) - 5;
midPos.Y = 0;
bat1Pos.X = 10;
bat1Pos.Y = (Window.ClientBounds.Height/2) - 50;
bat2Pos.X = Window.ClientBounds.Width - 20;
bat2Pos.Y = (Window.ClientBounds.Height/2) - 50;
ballPos.X = (Window.ClientBounds.Width / 2) - 5;
ballPos.Y = (Window.ClientBounds.Height / 2) - 5;
//Generate random ball velocity
ballVelo.X = generator.Next(5,10);
ballVelo.Y = generator.Next(4, 7);
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
//Update rectangle values
Rectangle bat1Rect = new Rectangle((int)bat1Pos.X, (int)bat1Pos.Y, 10, 100);
Rectangle bat2Rect = new Rectangle((int)bat2Pos.X, (int)bat2Pos.Y, 10, 100);
Rectangle ballRect = new Rectangle((int)ballPos.X, (int)ballPos.Y, 10, 100);
//Move ball
ballPos += ballVelo;
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
this.Exit();
//Bat 1 movement and restriction
if (Keyboard.GetState().IsKeyDown(Keys.Up))
bat1Pos.Y -= 4;
if (Keyboard.GetState().IsKeyDown(Keys.Down))
bat1Pos.Y += 4;
if (bat1Pos.Y <= 0)
bat1Pos.Y = 0;
if (bat1Pos.Y >= Window.ClientBounds.Height - 100)
bat1Pos.Y = Window.ClientBounds.Height - 100;
//Bat 2 movement and restriction
if (Keyboard.GetState().IsKeyDown(Keys.W))
bat2Pos.Y -= 4;
if (Keyboard.GetState().IsKeyDown(Keys.S))
bat2Pos.Y += 4;
if (bat2Pos.Y <= 0)
bat2Pos.Y = 0;
if (bat2Pos.Y >= Window.ClientBounds.Height - 100)
bat2Pos.Y = Window.ClientBounds.Height - 100;
//Ball movement restrictions
if (ballPos.X <= 0)
ballVelo.X *= -1;
if (ballPos.Y <= 0)
ballVelo.Y *= -1;
if (ballPos.X >= Window.ClientBounds.Width - 5)
ballVelo.X *= -1;
if (ballPos.Y >= Window.ClientBounds.Height - 5)
ballVelo.Y *= -1;
//Collision detection between bats and ball
if (ballRect.Intersects(bat1Rect))
{
ballVelo.X *= -1;
}
if (ballRect.Intersects(bat2Rect))
{
ballVelo.X *= -1;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
spriteBatch.Draw(middle, midPos, Color.White);
spriteBatch.Draw(bat1, bat1Pos, Color.White);
spriteBatch.Draw(bat2, bat2Pos, Color.White);
spriteBatch.Draw(ball, ballPos, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
}

Lyise's solution will work, but it does not attack the actual source of the problem. In bigger games you will require a more robust solution, so I'll explain the high level fix.
The issue is that you are changing the X velocity while the ball is still inside the bat. In the next frame, the most probable outcome is that the ball will move away, but not enough to exit the bat, so a new collision is detected. The speed is changed again, and the cycle repeats until the ball exits the bat by going below or above it.
The precise solution requires moving the ball out of the bat and then inverting the speed.
Options:
Return the ball to where it was at the beginning of the frame. The ball will never touch the bats. If the ball speed is high enough or the frame rate low enough, this will be noticeable.
Calculate how far into the bat the ball has gone, and substract that amount from the ball's position.
// Colliding from the right
impactCorrection = bat.Right - ball.Left;
// Colliding from the left
impactCorrection = bat.Left - ball.Right;
For extra points you might move the ball away 2 * impactCorrection in X so the ball always travels the same distance every frame.

One way around this would be to do make the following change to your ballRect.Intersect(barXRect)) if statements:
if (ballRect.Intersects(bat1Rect))
{
ballVelo.X = Math.Abs(ballVelo.X) * -1;
}
if (ballRect.Intersects(bat2Rect))
{
ballVelo.X = Math.Abs(ballVelo.X);
}
This way the left bar will only send the ball right, and the right bar will only send it left. I may have the bars around the wrong way, so it might be best to double check, but it just means moving the * -1 to the other bat.

Related

Asteroids clone in Monogame spawns the asteroids with the same position, rotation and direction

EDIT: Fixed the problem. I created a new Random for every asteroid so there wasn't enough time between them. But when I stepped trough the code manually it was enough time to not create the same value.
Here's the code. Control with WASD, shoot with space and press "L" to spawn an asteroid.
https://github.com/Geblin/Monogame-Asteroids
The asteroids split into 2-3 smaller asteroids when shot at and it's those smaller ones that have the same values, even though I give them random ones. I've stepped trough the code and it seems like they get different values initially.
Here's the Asteroid class. So I input a scale and a position so I can spawn smaller asteroids on different positions. The angle and speed is randomly selected here.
public Asteroid(Texture2D texture, float scale, Vector2 pos)
{
rand = new Random();
this.texture = texture;
this.scale = scale;
this.pos = pos;
angle = degreesToRadian(rand.Next(0,360));
direction = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
origin = new Vector2(texture.Width / 2, texture.Height / 2);
isVisible = true;
sourceRectangle = new Rectangle(0, 0, texture.Width, texture.Height);
boundingCircle = new BoundingCircle(pos, texture, scale);
speed = (float)rand.NextDouble() * (3 - 1) + 1;
setupRotation();
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, pos, sourceRectangle, Color.White, angle, origin, scale, SpriteEffects.None, 1f);
}
public void Update(GameTime gameTime)
{
boundingCircle.x = pos.X;
boundingCircle.y = pos.Y;
pos += direction * speed;
//Sets the asteroids bounds
if (pos.X > 2120)
pos.X = -200;
else if (pos.X < -200)
pos.X = 2120;
if (pos.Y > 1280)
pos.Y = -200;
else if (pos.Y < -200)
pos.Y = 1280;
angle += (float)rotationDirection;
}
public float degreesToRadian(int degrees)
{
float radian = (float)(Math.PI / 180) * degrees;
return radian;
}
//Sets a random rotation speed and rotation direction.
public void setupRotation()
{
rotationDirection = rand.NextDouble() * (0.01f - 0.005f) + 0.005f;
int x = rand.Next(2);
if (x == 1)
rotationDirection = -rotationDirection;
}
Here's the asteroid spawner class. The SetRandomSpawn method is for the first spawning asteroids. Those that spawn outside of the window and drifts in. In the main class I have the code that checks collisions between the lasers and asteroids and then spawns the smaller ones at the same position.
public AsteroidSpawner(List<Asteroid> asteroidList)
{
rand = new Random();
this.asteroidList = asteroidList;
}
public void LoadContent(ContentManager content)
{
texture = content.Load<Texture2D>("Asteroid");
}
public void Draw(SpriteBatch spriteBatch)
{
foreach (Asteroid asteroid in asteroidList)
asteroid.Draw(spriteBatch);
}
public void Update(GameTime gameTime)
{
UpdateAsteroids(gameTime);
if (Keyboard.GetState().IsKeyDown(Keys.L))
SpawnAsteroid(SetRandomSpawn(), 1f, 1);
}
public void SpawnAsteroid(Vector2 pos, float scale, int amount)
{
for (int i = 1; i <= amount; i++)
{
Asteroid newAsteroid = new Asteroid(texture, scale, pos);
asteroidList.Add(newAsteroid);
}
}
public void UpdateAsteroids(GameTime gameTime)
{
foreach (Asteroid asteroid in asteroidList)
{
asteroid.Update(gameTime);
}
for (int i = 0; i < asteroidList.Count; i++)
{
if (!asteroidList[i].isVisible)
{
asteroidList.RemoveAt(i);
i--;
}
}
}
//Sets a random spawn outside of screen bounds
public Vector2 SetRandomSpawn()
{
int side = rand.Next(4);
//Each number represents a side
switch (side)
{
// Left
case 0:
return new Vector2(2120, rand.Next(0, 1080));
// Top
case 1:
return new Vector2(rand.Next(0, 1920), 1280);
// Right
case 2:
return new Vector2(-200, rand.Next(0, 1080));
//Bottom
case 3:
return new Vector2(rand.Next(0, 1920), -200);
default:
throw new ArgumentException("Incorrect CrystalTypeEnum");
}
}
The seed of the Random object is time based, and because the spawner creates an Asteroid, and hence a Random in a loop, the random numbers generated are the same.
From the docs:
The default seed value is derived from the system clock and has finite resolution. As a result, different Random objects that are created in close succession by a call to the default constructor will have identical default seed values and, therefore, will produce identical sets of random numbers.
You can see this effect with a wee test:
for (var i = 0; i < 10; i++) {
var rand = new Random();
Console.WriteLine(rand.Next());
}
... which outputs:
1337050944
1337050944
1337050944
1337050944
1337050944
1337050944
1337050944
1337050944
1337050944
1337050944
Fix this by re-using the same Random (suggest your spawner creates an Asteroid then sets its position).

XNA sprite movement gone wrong

Hy everyone, so first off all, here is the image of my sprite:
I scaled it down in my game so its not that big in-game.
So anyway my desired result of the code below:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
spritePos = spriteVelocity + spritePos;
spriteR = new Rectangle((int)spritePos.X, (int)spritePos.Y, spriteT.Width, spriteT.Height);
spriteOrigin = new Vector2(spriteR.Width/2, spriteR.Height / 2);
if (Keyboard.GetState().IsKeyDown(Keys.Right)) rotation += 0.1f;
if (Keyboard.GetState().IsKeyDown(Keys.Left)) rotation -= 0.1f;
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
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;
}
base.Update(gameTime);
}
and then Draw method:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
foreach(Bushes bush in bushes)
{
bush.Draw(spriteBatch);
}
player.Draw(spriteBatch);
spriteBatch.Draw(spriteT, spritePos, null, Color.White, rotation, spriteOrigin, 0.1f, SpriteEffects.None, 0f);
spriteBatch.End();
base.Draw(gameTime);
}
is that when I press 'Up' arrow this little rocket should go into direction in whis it is pointing (becouse I can rotate it with Left and Right arrow to have it pointed where desired), but the result of this code is that when I press Up arrow the sprite moves to the right at first, and when its rotated it doesnt go where its pointed but kinda sideways :/
What am I doing wrong here?
PS. all those variables that arent declared and initialized in code are global variables and initialized in Initialize() and LoadContent() methods :/
If your rotation is initialized at 0, it should point to the right, look at where 0 points at on the trig circle below :)
You could compute your direction with Vector2.Transform and give as value parameter (first) Vector2(0,1) so your reference direction would be up.
Or you rotate your sprite to the right and initialize your angle at Pi/2.

XNA doesn't jump of the platform

I am working on my game using XNA. I have found a series of tutorials by Oyyou from YouTube and have gone to the collision tutorial so far. I have successfully processed gravity, movement and animation. But I have a problem with collision. The spawn point of my character is up in the air.
The first value is my bool hasJumped. And the second is y velocity. It is accelerating by 0.25.
The character is falling down slowly accelerating. All four platforms are made the same way. All four are in the list and I don't make anything to work with any of them particularly.
When I land on the lowest (ground) I get the parameters I need. hasJumped is false and velocity.Y is 0.
But when I'm on one of three non-ground platforms results are not as I want them. The velocity is 0.25 even though I am making it 0. And hasJumped doesn't become true because my velocity is a non-zero variable. Unluckily I can't post images so I can't show it. But I will post my code:
this is the maingame class where I add players and platforms.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
player = new Animation(Content.Load<Texture2D>("player"), Content.Load<Texture2D>("rect"), new Vector2(300 + 28, graphics.PreferredBackBufferHeight - 500), 47, 44, graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(300,graphics.PreferredBackBufferHeight-100), 80,50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(500, graphics.PreferredBackBufferHeight - 50), 80, 50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(700, graphics.PreferredBackBufferHeight - 60),80,50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(0, graphics.PreferredBackBufferHeight - 10), graphics.PreferredBackBufferWidth, 10));
// TODO: use this.Content to load your game content here
}
This is my Animation class:
class Animation
{
Texture2D texture;
Texture2D rectText;
public Rectangle rectangle;
public Rectangle collisionRect;
public Vector2 position;
Vector2 origin;
public Vector2 velocity;
int currentFrame;
int frameHeight;
int frameWidth;
int screenHeight;
int screenWidth;
float timer;
float interval = 60;
bool movingRight;
public bool hasJumped;
public Animation(Texture2D newTexture, Texture2D newRectText, Vector2 newPosition, int newHeight, int newWidth, int screenH, int screenW)
{
rectText = newRectText;
texture = newTexture;
position = newPosition;
frameHeight = newHeight;
frameWidth = newWidth;
screenHeight = screenH;
screenWidth = screenW;
hasJumped = true;
}
public void Update(GameTime gameTime)
{
rectangle = new Rectangle(currentFrame * frameWidth, 0, frameWidth, frameHeight);
collisionRect = new Rectangle((int)position.X-frameWidth/2, (int)position.Y - frameHeight/2, frameWidth, frameHeight);
origin = new Vector2(rectangle.Width / 2, rectangle.Height / 2);
position = position + velocity;
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
AnimateRight(gameTime);
velocity.X = 5;
movingRight = true;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
AnimateLeft(gameTime);
velocity.X = -5;
movingRight = false;
}
else
{
velocity.X = 0f;
if (movingRight)
currentFrame = 0;
else currentFrame = 4;
}
if(Keyboard.GetState().IsKeyDown(Keys.Space) && !hasJumped)
{
position.Y -= 5f;
velocity.Y = -10;
hasJumped = true;
}
if(hasJumped)
{
velocity.Y += 0.25f;
}
if(position.X<0+frameWidth/2)
{
position.X = 0 + frameWidth / 2;
}
else if (position.X>screenWidth-frameWidth/2)
{
position.X = screenWidth - frameWidth / 2;
}
}
public void AnimateRight(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds/2;
if(timer>interval)
{
currentFrame++;
timer = 0;
if (currentFrame > 3)
{
currentFrame = 0;
}
}
}
public void AnimateLeft(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds / 2;
if (timer > interval)
{
currentFrame++;
timer = 0;
if (currentFrame > 7 || currentFrame < 4)
{
currentFrame = 4;
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture,position,rectangle,Color.White, 0f, origin, 1.0f, SpriteEffects.None, 0);
// spriteBatch.Draw(rectText,collisionRect,Color.White);
}
}}
The platform class is pretty simple:
class Platform
{
Texture2D texture;
Vector2 position;
public Rectangle rectangle;
public Platform(Texture2D newTexture, Vector2 newPosition, int platformWidth, int platformHeight)
{
texture = newTexture;
position = newPosition;
rectangle = new Rectangle((int)position.X, (int)position.Y, platformWidth, platformHeight);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, rectangle, Color.White);
}
}
and the collision method:
public static bool isOnTopOf(this Rectangle r1, Rectangle r2)
{
return (r1.Bottom >= r2.Top - 10 &&
r1.Bottom <= r2.Top + 10 &&
r1.Right >= r2.Left + 10 &&
r1.Left <= r2.Right - 10);
}
The problem is supposed to be here:
foreach (Platform a in platformList)
{
if (player.collisionRect.isOnTopOf(a.rectangle) && player.velocity.Y>=0)
{
player.hasJumped = false;
player.velocity.Y = 0f;
if(player.collisionRect.Bottom > a.rectangle.Top || player.collisionRect.Bottom - a.rectangle.Top <=10)
{
player.position.Y = a.rectangle.Y - 48 / 2;
player.hasJumped = false;
}
}
else
{
player.hasJumped = true;
}
}
But if I exclude the last else it won't fall of the platform.
Thank you for upvoting the question. I have added images.
Your problem is that when the user is on top of a platform that is not the lowest one, during the else when you check if the user is on a platform, then you explicitly set the hasJumped to true.
What you must do is in your foreach where you check your platforms, to order them by INCREASING Y-coordinate (very much depending on your camera settings) and break after you set hasJumped to false. This way you will avoid your issue:
foreach (Platform a in platformList.OrderByY())
{
if (player.collisionRect.isOnTopOf(a.rectangle) && player.velocity.Y>=0)
{
player.hasJumped = false;
player.velocity.Y = 0f;
if(player.collisionRect.Bottom > a.rectangle.Top || player.collisionRect.Bottom - a.rectangle.Top <=10)
{
player.position.Y = a.rectangle.Y - 48 / 2;
player.hasJumped = false;
}
break;
}
else
{
player.hasJumped = true;
}
}
Unfortunately, it is often the case with 3D development that we have to do some hacks to get the application to render correctly. This is especially the case when rendering a complex item with advanced transparencies. In your case however, I would advise to re-imagine the way you work with collisions.
Games development is not an easy task. Superficially, the constraints under which you are working enforce a less than ideal architecture. Everything in an XNA game revolves around the update loop and the ability to control which item gets updated first is so easy and tempting that people tend to use that. It is however generally a much better approach to try and extrapolate functionality in such a way that your models behave in an event-driven way. One way to achieve this is to split the update cycle of your game in the following phases:
Input Phase: Retrieve input from the user and handle them as events (these can be simple method calls on items in a method other than Update or a more complex but better method like using Rx)
Movement Phase: Develop some physical characteristics in your models (acceleration, velocity, weight, etc.) and letting them do the work of positioning, rotating on their own during the movement phase. This is the only thing that they should be doing in Update.
Collision Phase: Test for collision and then fire events on the objects to handle collision.
Cleanup Phase: Cleanup any problems, like moving inside a wall during the collision detection phase. or phasing out particles etc. This gets important when you have requirements to draw on exact pixels to avoid blurring etc.
The above is not necessarily the best way to do it, but it works. If you rely only on the update cycle you will very soon reach a point where the interdependencies between models and the requirement to have some events fired in order will be a nightmare to support. Enforcing an event driven mechanism will save you from a lot of scrapped work with a very small initial overhead.
My suggestion to use Rx may sound a little strange for an XNA game, but trust me, it is an awesome way to work with events. Finally, I would like to mention MonoGame, an excellent framework to develop games for Windows, Mac, Linux, Android, iPhone, Windows Phone, WinRT (and even more) if you do not already know it.

Smooth Collision (Wall sliding)

I recently started learning XNA and C# because of a school project I am doing. I followed this tutorial on YouTube to learn how XNA works with 3D:
http://www.youtube.com/watch?v=XkpZLzT5OV4
It works pretty well actually, and I already made some modification/added some features to my project. I recently started to implement the collision detection, which works well using BoundingBoxes, but the camera literally stops when it collides. I would like it to kinda slide on the wall, as every other first person games do. Basically, the camera stops if the movement is directed to the wall. I would like the camera to "slide" on the wall, removing the movement directed to the wall, and making it only going parallel to the wall. I hope that makes sense. This way, the player will be able to still move around while he's touching the wall.
I looked at a lot of posts on google, and I saw that I needed to play with the player's velocity. However, I don't have any velocity implemented in my code, and I don't know how to add it.
Here are my main classes:
Here is my Camera class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Deimos
{
class Camera : GameComponent
{
// ...
// Constructor
public Camera(Game game, Vector3 position, Vector3 rotation, float speed)
: base(game)
{
CameraSpeed = speed;
// Setup projection matrix
Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4,
Game.GraphicsDevice.Viewport.AspectRatio,
0.05f,
1000.0f // Draw distance
);
// Set the camera position and rotation
moveTo(position, rotation);
PreviousMouseState = Mouse.GetState();
}
// Set camera position and rotation
private void moveTo(Vector3 position, Vector3 rotation)
{
// Thanks to the properties set at the beginning, setting up these values will execute
// the code inside the property (i.e update our vectors)
Position = position;
Rotation = rotation;
}
// Update the look at vector
private void updateLookAt()
{
// Build a rotation matrix
Matrix rotationMatrix = Matrix.CreateRotationX(CameraRotation.X) * Matrix.CreateRotationY(CameraRotation.Y);
// Build look at offset vector
Vector3 lookAtOffset = Vector3.Transform(Vector3.UnitZ, rotationMatrix);
// Update our camera's look at vector
CameraLookAt = CameraPosition + lookAtOffset;
}
// Methods that simulate movement
private Vector3 previewMove(Vector3 amount)
{
// Create a rotate matrix
Matrix rotate = Matrix.CreateRotationY(CameraRotation.Y);
// Create a movement vector
Vector3 movement = new Vector3(amount.X, amount.Y, amount.Z);
movement = Vector3.Transform(movement, rotate);
// Return the value of camera position + movement vector
if (Collision.CheckCollision(CameraPosition + movement)) // Testing for the UPCOMING position
{
return CameraPosition;
}
else
{
return CameraPosition + movement;
}
}
// Method that actually moves the camera
private void move(Vector3 scale)
{
moveTo(previewMove(scale), Rotation);
}
// Update method, overriding the original one
public override void Update(GameTime gameTime)
{
float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
CurrentMouseState = Mouse.GetState();
// Let's get user inputs
KeyboardState ks = Keyboard.GetState();
// Handle basic key movement
Vector3 moveVector = Vector3.Zero;
if (ks.IsKeyDown(ForwardKey))
{
moveVector.Z = 1;
}
if (ks.IsKeyDown(BackKey))
{
moveVector.Z = -1;
}
if (ks.IsKeyDown(LeftKey))
{
moveVector.X = 1;
}
if (ks.IsKeyDown(RightKey))
{
moveVector.X = -1;
}
if (ks.IsKeyDown(Keys.Up))
{
moveVector.Y = 1;
}
if (ks.IsKeyDown(Keys.Down))
{
moveVector.Y = -1;
}
if (moveVector != Vector3.Zero) // If we are actually moving (if the vector changed depending on the ifs)
{
// Normalize that vector so that we don't move faster diagonally
moveVector.Normalize();
// Now we add in move factor and speed
moveVector *= dt * CameraSpeed;
DebugScreen.Log(moveVector.ToString());
// Move camera!
move(moveVector);
}
// Handle mouse movement
float deltaX;
float deltaY;
if (CurrentMouseState != PreviousMouseState)
{
// Cache mouse location
deltaX = CurrentMouseState.X - (Game.GraphicsDevice.Viewport.Width / 2); // We devide by 2 because mouse will be in the center
deltaY = CurrentMouseState.Y - (Game.GraphicsDevice.Viewport.Height / 2);
MouseRotationBuffer.X -= MouseSpeed * deltaX * dt;
MouseRotationBuffer.Y -= MouseSpeed * deltaY * dt;
// Limit the user so he can't do an unlimited movement with his mouse (like a 7683°)
if(MouseRotationBuffer.Y < MathHelper.ToRadians(-75.0f))
MouseRotationBuffer.Y = MouseRotationBuffer.Y - (MouseRotationBuffer.Y - MathHelper.ToRadians(-75.0f));
if(MouseRotationBuffer.Y > MathHelper.ToRadians(75.0f))
MouseRotationBuffer.Y = MouseRotationBuffer.Y - (MouseRotationBuffer.Y - MathHelper.ToRadians(75.0f));
float mouseInverted = (MouseInverted == true) ? 1 : -1;
Rotation = new Vector3(
mouseInverted * MathHelper.Clamp(
MouseRotationBuffer.Y,
MathHelper.ToRadians(-75.0f),
MathHelper.ToRadians(75.0f)
),
MathHelper.WrapAngle(MouseRotationBuffer.X),
// This is so the camera isn't going really fast after some time
// (as we are increasing the speed with time)
0
);
// Resetting them
deltaX = 0;
deltaY = 0;
}
// Putting the cursor in the middle of the screen
Mouse.SetPosition(Game.GraphicsDevice.Viewport.Width / 2, Game.GraphicsDevice.Viewport.Height / 2);
PreviousMouseState = CurrentMouseState;
base.Update(gameTime);
}
}
}
And here is my Collision class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Deimos
{
class Collision
{
// ...
public Boolean CheckCollision(Vector3 cameraPosition)
{
// Creating the sphere of the camera for later collisions checks
BoundingBox cameraBox = new BoundingBox(
new Vector3(
cameraPosition.X - (PlayerDimention.X / 2),
cameraPosition.Y - (PlayerDimention.Y),
cameraPosition.Z - (PlayerDimention.Z / 2)
),
new Vector3(
cameraPosition.X + (PlayerDimention.X / 2),
cameraPosition.Y,
cameraPosition.Z + (PlayerDimention.Z / 2)
)
);
// Let's check for collision with our boxes
if (CollisionBoxesArray != null)
{
for (int i = 0; i < CollisionBoxesArray.Length; i++)
{
if (CollisionBoxesArray[i].Contains(cameraBox) != ContainmentType.Disjoint) // If our player is inside the collision region
return true;
}
}
if (CollisionSpheresArray != null)
{
// And with our spheres
for (int i = 0; i < CollisionSpheresArray.Length; i++)
{
if (CollisionSpheresArray[i].Contains(cameraBox) != ContainmentType.Disjoint)
return true;
}
}
return false;
}
}
}
I really don't know what to do. Thanks a lot.
EDIT: I updated my post removing some unnecessary code so it's more readable for you.
I also explained better my problem.
I think something like this might work. This is basically the same thing you did, except the previewMove returns a Vector3 that should allow you to slide along the wall. What the function does now is returns a value that checks to see if the camera can move in any of the X, Y, or Z directions of the movement vector, and if the camera can move in the X, Y, and/or Z, it adds that(those) value(s) to the CameraPosition.
private Vector3 previewMove(Vector3 amount)
{
// Create a rotate matrix
Matrix rotate = Matrix.CreateRotationY(CameraRotation.Y);
// Create a movement vector
Vector3 movement = new Vector3(amount.X, amount.Y, amount.Z);
movement = Vector3.Transform(movement, rotate);
// Return the value of camera position + movement vector
return CameraPosition + new Vector3(
Collision.CheckCollision(CameraPosition + new Vector3(movement.X, 0, 0)) ? 0 : movement.X,
Collision.CheckCollision(CameraPosition + new Vector3(0, movement.Y, 0)) ? 0 : movement.Y,
Collision.CheckCollision(CameraPosition + new Vector3(0, 0, movement.Z)) ? 0 : movement.Z);
}
I haven't tried the code, so I don't know how well this will work. But hopefully it will help.

Having Difficulties Implementing Bullet/Enemy Collision Detection In A Simple Shooter (C# 2010/XNA 4.0)

Firstly, I would like to say sorry for making another thread asking the same question (more or less), but I am desperate for an answer and the other thread went dead.
I am currently working on a project for school in which I am attempting to make a simple 2d shooter. Problem is, I don't know how to implement collision detection between my two lists (being bullets and enemies as stated in title). I am very new to programming and have been using various tutorials as a guide, some tutorials say to use a nested for loop and if statement while I have seen others using an if statement, oh and I'm using the rectangle collision method not the pixel by pixel one I think it was called. Thanks in advance! :)
Here is my code:
Main:
namespace Software_Design_Major_Project
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D ship; // Declaring the sprite
Vector2 shipposition = Vector2.Zero; // Creating a vector for the sprite.
List<bullets> bullets = new List<bullets>(); // a list is created here so that there can be multiple copies of this item at once without writing the extra code.
Texture2D texture;
KeyboardState pastkey;
//bullet sound effect.
SoundEffect effect;
List<enemies> Enemies = new List<enemies>();
Random random2 = new Random();
float spawn = 0;
public enum GameState
{
MainMenu,
Playing,
}
GameState CurrentGameState = GameState.MainMenu;
int screenWidth = 600, screenHeight = 480;
cButton play;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//changes width of screen to 600px.
graphics.PreferredBackBufferWidth = 600;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
// This statement positions the ship.
ship = Content.Load<Texture2D>("ship"); // Loads the ship into the memory.
shipposition = new Vector2((graphics.GraphicsDevice.Viewport.Width / 2) - (ship.Width / 2), 420);
// loads bullet sprite
texture = Content.Load<Texture2D>("bullet");
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
graphics.ApplyChanges();
IsMouseVisible = true;
play = new cButton(Content.Load<Texture2D>("play"), graphics.GraphicsDevice);
play.setPosition(new Vector2(225, 220));
effect = Content.Load<SoundEffect>("laser");
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
spawn += (float)gameTime.ElapsedGameTime.TotalSeconds;
// spawns enemy every second.
foreach (enemies enemy in Enemies)
enemy.update(graphics.GraphicsDevice);
MouseState mouse = Mouse.GetState();
switch (CurrentGameState)
{
case GameState.MainMenu:
if (play.isClicked == true)
CurrentGameState = GameState.Playing;
play.Update(mouse);
break;
case GameState.Playing:
if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.E))
{
Exit();
}
break;
}
// Allows the ship to move left and stops the ship going off the screen.
if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Left) && shipposition.X >= 0)
{
shipposition.X -= 7;
}// same as above except for the right direction.
if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Right) && shipposition.X < ((graphics.GraphicsDevice.Viewport.Width) - (ship.Width)))
{
shipposition.X += 7;
}
// this prevents the player from holding down space to spam bullets.
if (Keyboard.GetState().IsKeyDown(Keys.Space) && pastkey.IsKeyUp(Keys.Space))
{
bullets bullet = new bullets(texture);
// the ships coordinates are gathered from the top left hand corner of the sprites rectangle therefore 'new Vector2(shipposition.X + 32, shipposition.Y)' had to
// be added rather than just = 'shipposition' to avoid having the bullets shoot from the wing.
bullet.position = new Vector2(shipposition.X + 32, shipposition.Y);
bullets.Add(bullet);
effect.Play();
}
pastkey = Keyboard.GetState();
//calls upon the update method from the bullets class.
foreach (bullets bullet in bullets)
bullet.update();
LoadEnemies();
base.Update(gameTime);
}
public void LoadEnemies()
{
int randX = random2.Next(10, 540);
if (spawn <= 1)
{
spawn = 0;
//limits amount of enemies on screen to 5.
if (Enemies.Count() < 5)
Enemies.Add(new enemies(Content.Load<Texture2D>("enemy"), new Vector2(randX, -100)));
}
for (int i = 0; i < Enemies.Count; i++)
{
if (!Enemies[i].enemyVisble)
{
//removes the enemies when they go off screen.
Enemies.RemoveAt(i);
i--;
}
}
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// calls draw method in bullets class
foreach (bullets bullet in bullets)
{
bullet.Draw(spriteBatch);
}
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
spriteBatch.Draw(ship, shipposition, Color.White); // draws ship sprite
switch (CurrentGameState)
{
case GameState.MainMenu:
play.draw(spriteBatch);
spriteBatch.Draw(Content.Load<Texture2D>("menu"), new Rectangle(0, 0, screenWidth, screenHeight), Color.White);
break;
case GameState.Playing:
break;
}
foreach (enemies enemy in Enemies)
{
enemy.draw(spriteBatch);
}
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Bullets Class:
namespace Software_Design_Major_Project
{
class bullets // A new class needs to be created to allow for bullets.
{
public Texture2D texture;
public Vector2 position;
public bool isvisible;
public bullets(Texture2D newtexture)
{
texture = newtexture;
isvisible = false;
}
public void update()
{
position.Y -= 3; // velocity of the bullet
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Begin();
spriteBatch.Draw(texture, position, Color.White);
spriteBatch.End();
}
}
}
Enemies Class:
namespace Software_Design_Major_Project
{
public class enemies
{
public Texture2D enemyTexture;
public Vector2 enemyPosition;
public bool enemyVisble = true;
public float enemyMoveSpeed;
public int Value;
Random random = new Random();
int randY;
public enemies (Texture2D newEnemyTexture, Vector2 newEnemyPosition)
{
enemyTexture = newEnemyTexture;
enemyPosition = newEnemyPosition;
randY = random.Next(1, 4);
enemyMoveSpeed = randY;
enemyVisble = true;
Value = 100;
}
public void update(GraphicsDevice graphics)
{
enemyPosition.Y += enemyMoveSpeed;
if (enemyPosition.Y > 500)
enemyVisble = false;
}
public void draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(enemyTexture, enemyPosition, Color.White);
enemyVisble = true;
}
}
}
You have several ways to do it... one of them maybe add a radio property to enemies and bullets....
for (int bi=0; bi<bullets.count; )
{
bool should_destroy_bullet = false;
Bullet b = bullets[bi];
for (int ei=0; ei<enemies.count; )
{
Enemy e = ememies[ei];
if (b.radio + e.radio < Vector2.Distance(b.Pos, e.Pos)) // Check collision
{
e.Died();
enemies[ei] = enemies[enemies.Count-1];
enemies.RemoveAt(enemies.Count-1);
should_destroy_bullet = true; // This lets a bullet kill more than one enemy
} else ei++;
}
if (should_destroy_bullet) {
b.Died();
bullets[bi] = bullets[bullets.count-1];
bullets.RemoveAt(bullets.count-1);
} else bi++;
}
Or you can build a rectangle for each sprite and check if they intersects....
if (b.Bounds.Intersects(e.Bounds)) ....
The Pythagorean theorem could work for you.(A squared plus B squared = C squared)
Make your players and enemies have a radius.Put this in your constructor.
public float radius = 15;
Put the following in a update void/subclass so that it happens every second.
Make a float the X location of your player and subtract the X location of the bullet. Square it.
float XDist = Math.Pow(player1.location.X - bullet.location.X,2);
Make a float the Y location of your player and subtract the Y location of the bullet.Square it.
float YDist = Math.Pow(player1.location.X - bullet.location.X,2);
Make a float to figure out the square root of the sum of Xdist and Ydist.
float actualDist = Math.Sqrt(Xdist + Ydist);
Now if the radius of your player is less than the distance between the player and the bullet, the bullet has hit the player.
if(actualDist < radius)
bullethit == true;

Categories