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.
Related
I'm new to C# and Monogame, and I'm trying to create an effect of a 'psychic' ability, the user will press a key and a circular aura blast will emit from their position. I'm just trying to get the actual blast working before setting up the positions and all that, but my problem is when trying to get all the particles to move out from the origin of a circle to the outside.
I've never worked with circles yet and only with Rectangles so my knowledge of this is very basic. You'll probably recognise the code from a youtube channel and that's because I've been trying to learn from wherever I can, most of the time that leads me to youtube since a video demonstration works best but I digress.
This is my code for the particle generator so far.
namespace Particles
{
class ParticleGenerator
{
Texture2D texture;
float spawnWidth;
float density;
List<Particles> particles = new List<Particles>();
float timer;
public ParticleGenerator(Texture2D newTexture, float newSpawnWidth, float newDensity)
{
texture = newTexture;
spawnWidth = newSpawnWidth;
density = newDensity;
}
public void createParticle(GraphicsDevice graphics)
{
particles.Add(new Particles(texture, new Vector2(graphics.Viewport.Width / 2 , graphics.Viewport.Height /2), new Vector2(5, 1)));
}
public void Update(GameTime gameTime, GraphicsDevice graphics)
{
timer += (float)gameTime.ElapsedGameTime.TotalSeconds;
while (timer > 0)
{
timer -= 1f / density;
createParticle(graphics);
}
for (int i = 0; i < particles.Count; i++)
{
particles[i].Update();
if (particles[i].Position.Y > graphics.Viewport.Height)
{
particles.RemoveAt(i);
i--;
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
foreach (Particles particle in particles)
{
particle.Draw(spriteBatch);
}
}
}
}
Many thanks for any help.
I'm working on a script for Android tablet devices in Unity3d, where the user drags to move the camera. I want the "ground" at the touch position to stay underneath the users finger while he is panning. Here is my simplyfied working code so far:
using UnityEngine;
public class CameraMovement : MonoBehaviour
{
Plane plane = new Plane(Vector3.forward, Vector3.zero);
Vector2 worldStartPoint;
void Update()
{
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
this.worldStartPoint = GetWorldPoint(touch.position);
}
if (touch.phase == TouchPhase.Moved)
{
Vector2 worldDelta = GetWorldPoint(touch.position) - worldStartPoint;
transform.Translate(-worldDelta.x, -worldDelta.y, 0);
}
}
}
Vector2 GetWorldPoint(Vector2 screenPoint)
{
Ray ray = Camera.main.ScreenPointToRay(screenPoint);
float rayDistance;
if (plane.Raycast(ray, out rayDistance))
return ray.GetPoint(rayDistance);
return Vector2.zero;
}
}
Now the problematic part: I would like the camera to move like a physics object once the user lifts up his finger. I'm trying to calculate the current velocity while dragging and then applying it as a dampened/inertia-like effect while not currently dragging. Theoretically I would do this:
Vector2 worldStartPoint;
Vector3 velocity;
void Update()
{
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
this.worldStartPoint = GetWorldPoint(touch.position);
}
if (touch.phase == TouchPhase.Moved)
{
Vector2 worldDelta = GetWorldPoint(touch.position) - worldStartPoint;
transform.Translate(-worldDelta.x, -worldDelta.y, 0);
velocity = worldDelta / Time.deltaTime;
}
}
else
{
transform.Translate(-velocity * Time.deltaTime);
velocity = Vector3.MoveTowards(velocity, Vector3.zero, damping * Time.deltaTime);
}
}
So, I always calculate velocity while moving, and once I stop input, it should remain at the last known velocity, apply it and reduce from there on until stop. However, usually the last velocity is zero, because it appears that when dragging/swiping across the screen the finger actually stops shortly and reports zero velocity before TouchPhase.Moved is over.
Now my solution/workaround would be to keep an array of velocities of the last few frames (maybe 30) and once the finger is lifted up, I would calculate the average velocity.
Vector3[] velocityBuffer = new Vector3[bufferSize];
int nextBufferIndex;
Vector3 GetAverage()
{
Vector3 sum = Vector3.zero;
for (int i = 0; i < bufferSize; i++)
sum += velocityBuffer[i];
return sum / bufferSize;
}
This works a little better, since more often than not it reports at least some velocity, but in total it's not much better and also feels very hacky. Depending on the speed of the touch, I might end up with 20 entries of zero velocity, making the damping much too strong, and sometimes the velocity randomly becomes so big that the camera just flicks a few hundred units.
Is there anything wrong with my calculations or am I overseeing something simple to fix this? Does anybody have a working solution of a smooth camera drag? I looked at a few mobile games and a lot of them actually were feeling a little clumsy, like snapping the camera to the finger position, after a few pixels of deltaMovement and then suddenly releasing it without any damping.
I'm not feeling as if I've found the perfect solution, but at least by now I have something which works better and also is more "physically correct". First, I am now storing the camera position and deltaTime in my buffer instead of the velocity. This way I can calculate a rolling average every 10 frames with the correct time factor.
/// <summary>
/// Stores Vector3 samples such as velocity or position and returns the average.
/// </summary>
[Serializable]
public class Vector3Buffer
{
public readonly int size;
Sample[] sampleData;
int nextIndex;
public Vector3Buffer(int size)
{
if (size < minSize)
{
size = minSize;
Debug.LogWarning("Sample count must be at least one. Using default.");
}
this.size = size;
sampleData = new Sample[size];
}
public void AddSample(Vector3 position, float deltaTime)
{
sampleData[nextIndex] = new Sample(position, deltaTime);
nextIndex = ++nextIndex % size;
}
public void Clear()
{
for (int i = 0; i < size; i++)
sampleData[i] = new Sample();
}
public Vector3 GetAverageVelocity(Vector3 currentPosition, float currentDeltaTime)
{
// The recorded sample furthest back in time.
Sample previous = sampleData[nextIndex % size];
Vector3 positionDelta = currentPosition - previous.position;
float totalTime = currentDeltaTime;
for (int i = 0; i < size; i++)
totalTime += sampleData[i].deltaTime;
return positionDelta / totalTime;
}
[Serializable]
struct Sample
{
public Vector3 position;
public float deltaTime;
public Sample(Vector3 position, float deltaTime)
{
this.position = position;
this.deltaTime = deltaTime;
}
}
public const int minSize = 1;
}
Also, I noticed, that I was recording a lot of zero velocity values, which is now mitigated because I'm tracking the position, which I also want to update when not dragging, but holding:
if (input.phase == TouchPhase.Moved || input.phase == TouchPhase.Stationary)
{
velocityBuffer.AddSample(transform.position, Time.deltaTime);
}
if (input.phase == TouchPhase.Ended || input.phase == TouchPhase.Canceled)
{
velocity = -velocityBuffer.GetAverageVelocity(transform.position, Time.deltaTime);
}
Lastly, instead of setting the camera to the fingers world position every frame, I use a tiny bit of Lerp/MoveTowards interpolation to smooth out any jitter. It's hard to get best value between crisp control and smooth look, but I assume that's the way to go with user input, which can vary rapidly.
Of course, I'd still be interested in other approaches, better solutions or opinions about my current implementation.
I'm writing a game and I would like an explosion animation to run whenever a collision occurs. However the approaches I know how to animate are based on keyboard input. What is the best way to code the method so that when called the animation makes a single pass through all frames then stops?
public void Update(GameTime gametime)
{
timer += gametime.ElapsedGameTime.Milliseconds;
if (timer >= msBetweenFrames)
{
timer = 0;
if (CurrentFrame++ == numberOfFrames - 1)
CurrentFrame = 0;
rect.X = CurrentFrame * width;
rect.Y = 0;
}
}
public void Render(SpriteBatch sb, Vector2 position, Color color)
{
sb.Draw(aniTex, position, rect, color, 0, Vector2.Zero, 2.0f, SpriteEffects.None, 0);
}
If the problem is simply the looping issue, you can just remove the bit of code in your update that causes it to loop (the if (CurrentFrame++ == numberOfFrames - 1) CurrentFrame = 0;) All you would need to do to get the animation to play again, is set the current frame back to 0 when you want it to play. (To stop it drawing, just only do the draw call when you want it.)
If you're looking for a way to structure it (which is how I initially interpreted your question!) why not have a Animated class which you set up with whatever objects you want animated. Something along the lines of
class AnimatedObject
{
Texture2D texture;
Rectangle currentRectangle;
Rectangle frameSize;
int frameCount;
int currentFrame;
bool isRunning;
public AnimatedObject(Texture2D lTexture, Rectangle lFrameSize, int lFrameCount)
{
texture = lTexture;
frameSize = lFrameSize;
currentRectangle = lFrameSize;
currentFrame = 0;
}
public void Pause()
{
isRunning = false;
}
public void Resume()
{
isRunning = true;
}
public void Restart()
{
currentFrame = 0;
isRunning = true;
}
public void Update()
{
if(isRunning)
{
++currentFrame;
if(currentFrame == frameCount)
{
currentFrame = 0;
}
currentRectangle.X = frameSize.Width * currentFrame;
}
}
public void Draw(SpriteBatch spriteBatch)
{
if(isRunning)
{
spriteBatch.Draw(texture, currentRectangle, Color.White);
}
}
}
Obviously you can extend this to be more sophisticated (eg using your timers to choose when to move frames etc.
You can stop the animation, for example, by removing the object with the animation from the game when the animation has finished.
Or otherwise, you could set the currentFrame to an illegal value (e.g. -1) when the animation is done, and test for that.
public void Update(GameTime gametime)
{
if (currentFrame >= 0)
{
timer += gametime.ElapsedGameTime.Milliseconds;
if (timer >= msBetweenFrames)
{
timer = 0;
currentFrame++;
if (currentFramr >= numberOfFrames - 1)
currentFrame = -1;
rect.X = CurrentFrame * width;
rect.Y = 0;
}
}
}
public void Render(SpriteBatch sb, Vector2 position, Color color)
{
if (currentFrame >= 0)
sb.Draw(aniTex, position, rect, color, 0, Vector2.Zero, 2.0f, SpriteEffects.None, 0);
}
So heres my problem. I have a box that I want my character to move around. But I want to be able to move around it while holding multiple move commands, for instance..
when moving right (towards the left of the obstacle) I want to be able to hold move right and up or down at the same time without the character sticking to the box. The funny part is, it works fine for the left and right side of the obstacle, yet it sticks when i try it on the top and bottom side of the obstacle.
Heres the Player Class (object im moving)
<!-- language: c# -->
public class Player
{
public Texture2D texture;
public Vector2 position;
public int speed;
public Vector2 offset;
public bool left, right, up, down;
public Rectangle collisionRect
{
get
{
return new Rectangle((int)position.X , (int)position.Y, texture.Width, texture.Height);
}
}
public Vector2 direction;
public Player(Texture2D texture, Vector2 position, int speed)
{
this.texture = texture;
this.position = position;
this.speed = speed;
offset.X = speed;
offset.Y = speed;
left = false;
right = false;
up = false;
down = false;
}
public virtual void Update(GameTime gameTime, Rectangle clientBounds)
{
direction = Vector2.Zero;
if (Keyboard.GetState().IsKeyDown(Keys.A))
{
direction.X -= 1;
left = true;
}
else
left = false;
if (Keyboard.GetState().IsKeyDown(Keys.D))
{
direction.X += 1;
right = true;
}
else
right = false;
if (Keyboard.GetState().IsKeyDown(Keys.W))
{
direction.Y -= 1;
up = true;
}
else
up = false;
if (Keyboard.GetState().IsKeyDown(Keys.S))
{
direction.Y += 1;
down = true;
}
else
down = false;
position += (direction * speed);
}
public virtual void Draw(GameTime gameTime, SpriteBatch spritebatch)
{
spritebatch.Draw(texture, collisionRect, Color.White);
}
}
Heres the Update Method in my maingame
<!-- language: c# -->
public override void Update(GameTime gameTime)
{
// TODO: Add your update code here
player.Update(gameTime, Game.Window.ClientBounds);
if (player.right && HitWall(player))
{
player.position.X -= player.offset.X;
}
else if (player.left && HitWall(player))
{
player.position.X += player.offset.X;
}
if (player.down && HitWall(player))
{
player.position.Y -= player.offset.Y;
}
else if (player.up && HitWall(player))
{
player.position.Y += player.offset.Y;
}
base.Update(gameTime);
}
And the HitWall function
<!-- language: c# -->
public bool HitWall(Player player)
{
for (int i = player.collisionRect.Top; i < player.collisionRect.Bottom; i++)
for (int j = player.collisionRect.Left; j < player.collisionRect.Right; j++)
if (TextureData[i * gameMap.map.Width + j] != Color.White)
return true;
return false;
}
I'm not sure where offset is defined, but I'm assuming it's the movement you've just made that frame.
Your problem is that because you check left & right before up and down, if you're moving diagonally down onto the top edge of the box, then you'll register a hit in the Y direction — HitWall doesn't check which direction you're going, it just checks for a collision. Therefore, the collision in the Y axis stil counts on the line if (player.right && HitWall(player)) and stops your lateral movement.
Best bet is to apply your sideways movement, check for a hit, move back if there is one — then apply your downwards movement, check for a hit, and move back if there is one. Correcting the position like this should mean you slide along the sides as you want.
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.