XNA - Pong Clone - 当它击中墙壁时反射球?

时间:2011-01-12 16:29:08

标签: c# math xna collision-detection

我正在尝试在创建2D Pong Clone时让球从UI的顶部和底部“Walls”反弹。 这是我的Game.cs

public void CheckBallPosition()
{
    if (ball.Position.Y == 0 || ball.Position.Y >= graphics.PreferredBackBufferHeight)
        ball.Move(true);
    else
        ball.Move(false);

    if (ball.Position.X < 0 || ball.Position.X >= graphics.PreferredBackBufferWidth)
        ball.Reset();
}

目前我在Ball.cs中使用它

    public void Move(bool IsCollidingWithWall)
    {
        if (IsCollidingWithWall)
        {
            Vector2 normal = new Vector2(0, 1);
            Direction = Vector2.Reflect(Direction,normal);
            this.Position += Direction;
            Console.WriteLine("WALL COLLISION");
        }
        else
            this.Position += Direction;
    }

它有效,但我正在使用手动输入法线,我想知道如何计算屏幕顶部和底部的法线?

4 个答案:

答案 0 :(得分:4)

嗯,这就是我处理它的方式

public void CheckBallPositionAndMove()
{
    if (ball.Position.Y <= 0 || ball.Position.Y >= graphics.PreferredBackBufferHeight)
        ball.HandleWallCollision();

    ball.Move();

    if (ball.Position.X < 0 || ball.Position.X >= graphics.PreferredBackBufferWidth)
        ball.Reset();
}

//In Ball.cs:
private void HandleWallCollision(Vector2 normal)
{
    Direction.Y *= -1; //Reflection about either normal is the same as multiplying y-vector by -1
}

private void Move()
{
    this.Position += Direction;
}

但请注意,使用此“离散”碰撞检测,您要等到球移过屏幕顶部/底部后才能检测到碰撞; 在“两个”帧之间发生的碰撞可能会明显偏离,特别是如果球快速移动。如果您使用此碰撞检测方法来检测,这尤其是一个问题与球拍相撞,因为如果球的移动速度足够快,球可能会直接穿过球拍!

此问题的解决方案是使用所谓的Continuous Collision Detection。 CCD通常比离散碰撞检测复杂得多;幸运的是,pong很简单,做CCD只会稍微复杂一些。但是,你仍然需要扎实掌握高中代​​数来解决方程式。

如果您仍然感兴趣,可以在this lecture中对CCD进行很好的解释,而this GameDev article会更加深入。在SO上还有many questions与之相关。

答案 1 :(得分:1)

你可以用一些枚举来改变布尔IsCollidingWithWall

enum CollideType
{
    None,
    Vertical,
    Horizontal
}

并在创建普通时检查此类型。

答案 2 :(得分:1)

你们世界的每个边界都是一条线。线的一边是实心的,另一边不是。您尝试计算的法线是该线的等式的一部分。它指向线的非实心侧。线方程的另一部分是从线到原点的距离。该线的等式可以从该线上的两个点找到。您可以根据游戏空间中您想要墙的坐标来定义这两个点。

通过将由两个点定义的线段旋转90度然后归一化来计算法线。

public static Vector2 ComputeNormal(Vector2 point1, Vector2 point2)
{
    Vector2 normal = new Vector2();
    normal.X = point2.Y - point1.Y;
    normal.Y = point1.X - point2.X;

    normal.Normalize();

    return normal;
}

您正在使用首选的后缓冲区宽度和高度来定义您的世界空间,因此您可以使用这些来定义用于计算法线的点。

float left = 0.0f;
float right = graphics.PreferredBackBufferWidth;
float top = 0.0f;
float bottom = graphics.PreferredBackBufferHeight;

Vector2 topNormal = ComputeNormal(new Vector2(left, top), new Vector2(right, top));
Vector2 bottomNormal = ComputeNormal(new Vector2(right, bottom), new Vector2(left, bottom));

请注意,必须以顺时针顺序给出这些点,以便正常方向指向正确的方向。

以下XNA 4.0程序演示了这些使用中的概念:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace WindowsGame
{
    public class Ball
    {
        const int DIAMETER = 40;
        const float RADIUS = DIAMETER * 0.5f;
        const float MASS = 0.25f;
        const int PIXELS = DIAMETER * DIAMETER;

        static readonly uint WHITE = Color.White.PackedValue;
        static readonly uint BLACK = new Color(0, 0, 0, 0).PackedValue;

        Texture2D m_texture;
        Vector2 m_position;
        Vector2 m_velocity;

        public Ball(GraphicsDevice graphicsDevice)
        {
            m_texture = new Texture2D(graphicsDevice, DIAMETER, DIAMETER);

            uint[] data = new uint[PIXELS];

            for (int i = 0; i < DIAMETER; i++)
            {
                float iPosition = i - RADIUS;

                for (int j = 0; j < DIAMETER; j++)
                {
                    data[i * DIAMETER + j] = new Vector2(iPosition, j - RADIUS).Length() <= RADIUS ? WHITE : BLACK;
                }
            }

            m_texture.SetData<uint>(data);
        }

        public float Radius
        {
            get
            {
                return RADIUS;
            }
        }

        public Vector2 Position
        {
            get
            {
                return m_position;
            }
        }

        public Vector2 Velocity
        {
            get
            {
                return m_velocity;
            }

            set
            {
                m_velocity = value;
            }
        }

        public void ApplyImpulse(Vector2 impulse)
        {
            Vector2 acceleration = impulse / MASS;
            m_velocity += acceleration;
        }

        public void Update(float dt)
        {
            m_position += m_velocity;   // Euler integration - innaccurate and unstable but it will do for this simulation
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(m_texture, DrawRectangle, Color.White);
        }

        private Rectangle DrawRectangle
        {
            get
            {
                int x = (int)Math.Round(m_position.X - RADIUS);
                int y = (int)Math.Round(m_position.Y - RADIUS);

                return new Rectangle(x, y, DIAMETER, DIAMETER);
            }
        }
    }

    public class Boundary
    {
        private Vector2 m_point1;
        private Vector2 m_point2;
        private Vector2 m_normal;
        private float m_distance;

        public Boundary(Vector2 point1, Vector2 point2)
        {
            m_point1 = point1;
            m_point2 = point2;

            m_normal = new Vector2();
            m_normal.X = point2.Y - point1.Y;
            m_normal.Y = point1.X - point2.X;

            m_distance = point2.X * point1.Y - point1.X * point2.Y;

            float invLength = 1.0f / m_normal.Length();

            m_normal *= invLength;
            m_distance *= invLength;
        }

        public Vector2 Normal
        {
            get
            {
                return m_normal;
            }
        }

        public void PerformCollision(Ball ball)
        {
            float distanceToBallCenter = DistanceToPoint(ball.Position);

            if (distanceToBallCenter <= ball.Radius)
            {
                ResolveCollision(ball);
            }
        }

        public void ResolveCollision(Ball ball)
        {
            ball.Velocity = Vector2.Reflect(ball.Velocity, m_normal);
        }

        private float DistanceToPoint(Vector2 point)
        {
            return 
                m_normal.X * point.X + 
                m_normal.Y * point.Y + 
                m_distance;
        }
    }

    public class World
    {
        Boundary m_left;
        Boundary m_right;
        Boundary m_top;
        Boundary m_bottom;

        public World(float left, float right, float top, float bottom)
        {
            m_top = new Boundary(new Vector2(right, top), new Vector2(left, top));
            m_right = new Boundary(new Vector2(right, bottom), new Vector2(right, top));
            m_bottom = new Boundary(new Vector2(left, bottom), new Vector2(right, bottom));
            m_left = new Boundary(new Vector2(left, top), new Vector2(left, bottom));
        }

        public void PerformCollision(Ball ball)
        {
            m_top.PerformCollision(ball);
            m_right.PerformCollision(ball);
            m_bottom.PerformCollision(ball);
            m_left.PerformCollision(ball);
        }
    }

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Matrix viewMatrix;
        Matrix inverseViewMatrix;
        Ball ball;
        World world;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            ball = new Ball(GraphicsDevice);

            float right = Window.ClientBounds.Width * 0.5f;
            float left = -right;
            float bottom = Window.ClientBounds.Height * 0.5f;
            float top = -bottom;

            world = new World(left, right, top, bottom);

            viewMatrix = Matrix.CreateTranslation(Window.ClientBounds.Width * 0.5f, Window.ClientBounds.Height * 0.5f, 0.0f);
            inverseViewMatrix = Matrix.Invert(viewMatrix);

            base.Initialize();
        }

        private void ProcessUserInput()
        {
            MouseState mouseState = Mouse.GetState();

            Vector2 mousePositionClient = new Vector2((float)mouseState.X, (float)mouseState.Y);
            Vector2 mousePositionWorld = Vector2.Transform(mousePositionClient, inverseViewMatrix);

            if (mousePositionWorld != ball.Position)
            {
                Vector2 impulse = mousePositionWorld - ball.Position;
                impulse *= 1.0f / impulse.LengthSquared();
                ball.ApplyImpulse(-impulse);
            }
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            ProcessUserInput();

            ball.Update(dt);
            world.PerformCollision(ball);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, viewMatrix);

            ball.Draw(spriteBatch);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

答案 3 :(得分:0)

难道你不只是将球的位置减去墙的位置然后将该矢量标准化以获得你需要的东西而不用硬编码吗?

Vector2 normal = Position - WallPosition;
normal.Normalize();

您的其余代码应该可以正常工作。