问题
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);
}
}
}
回答1:
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.
回答2:
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 away2 * impactCorrection
in X so the ball always travels the same distance every frame.
来源:https://stackoverflow.com/questions/5636607/xna-rectangle-intersection