XNA - Pong Clone - Отражение мяча при ударе о стену - PullRequest
4 голосов
/ 12 января 2011

Я пытаюсь заставить шар отскочить от верхних и нижних «стенок» моего пользовательского интерфейса при создании 2D-клона Pong. Это мой 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 ]

4 голосов
/ 12 января 2011

Ну, вот как бы я справился с этим

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;
}

Обратите внимание, однако, что, используя это "дискретное" обнаружение столкновений, вы ждете, пока мяч не пройдет мимо верха / низаэкран для обнаружения столкновения; столкновения, которые происходят "между" кадрами, могут быть заметно отключены, особенно если мяч движется быстро. Это особенно проблема, если вы используете этот метод обнаружения столкновений для обнаружения столкновения сэто весло, так как, если мяч движется достаточно быстро, возможно, что мяч движется прямо через весло!

Решение этой проблемы состоит в использовании так называемого Непрерывное обнаружение столкновений .ПЗС обычно значительно сложнее, чем дискретное обнаружение столкновений;к счастью, понг настолько прост, что выполнение ПЗС будет лишь немного сложнее.Тем не менее, вам все еще нужно твердое понимание алгебры средней школы для решения уравнений.

Если вы все еще заинтересованы, есть хорошее объяснение CCD в этой лекции , и эта статья GameDev идет немного глубже.Есть также много вопросов , относящихся к нему на SO.

1 голос
/ 13 января 2011

Каждая из границ вашего мира - это линия.Одна сторона линии сплошная, а другая нет.Нормаль, который вы пытаетесь вычислить, является одной частью уравнения для этой линии.Он указывает на нетвердую сторону линии.Другая часть уравнения линии - это расстояние от линии до начала координат.Уравнение для линии можно найти из двух точек на этой линии.Вы можете определить эти две точки на основе координат в игровом пространстве, где вы хотите стену.

Нормаль вычисляется путем поворота отрезка, заданного двумя точками на 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);
        }
    }
}
1 голос
/ 12 января 2011

Вы можете изменить логическое значение IsCollidingWithWall с помощью некоторого перечисления вроде:

enum CollideType
{
    None,
    Vertical,
    Horizontal
}

и проверить этот тип при создании normal.

0 голосов
/ 12 января 2011

Не могли бы вы просто взять положение мяча за вычетом положения стены и затем нормализовать этот вектор, чтобы получить то, что вам нужно, без его жесткого кодирования?

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

Остальная часть вашего кода должна работать так же.

...