【问题标题】:XNA - Pong Clone - Reflecting ball when it hits a wall?XNA - Pong Clone - 当它撞到墙上时反射球?
【发布时间】:2011-01-12 16:29:08
【问题描述】:

在创建 2D Pong 克隆时,我试图让球从 UI 的顶部和底部“墙壁”反弹。 这是我的 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;
    }

可以,但我使用的是手动输入的法线,我想知道如何计算屏幕顶部和底部的法线?

【问题讨论】:

    标签: c# math xna collision-detection


    【解决方案1】:

    好吧,这就是我的处理方式

    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 通常比离散碰撞检测复杂得多。幸运的是,乒乓球很简单,做 CCD 只会稍微复杂一些。但是,您仍然需要扎实掌握高中代​​数才能解方程。

    如果您仍然感兴趣,this lecture 中对 CCD 有很好的解释,this GameDev article 会更深入一些。 SO上还有manyquestions与之相关。

    【讨论】:

      【解决方案2】:

      您可以使用以下枚举更改布尔值 IsCollidingWithWall

      enum CollideType
      {
          None,
          Vertical,
          Horizontal
      }
      

      并在正常创建时检查该类型。

      【讨论】:

      • 这是一个很好的解决方案,但是有没有更优雅的方法呢?我觉得我在制作这个游戏时一直在破解!
      • 不确定它是否更干净,但您可以为枚举创建一个扩展方法(例如GetNormal(this CollideType collideType)),其中包含一个开关并为每个返回正常...
      • 谢谢!我发现代码很简单,但数学很难。网络上也没有太多文档,反正 4.0 也没有。
      【解决方案3】:

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

      法线是通过将两点定义的线段旋转 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);
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        难道你不只是将球的位置减去墙壁的位置,然后对该向量进行归一化以获得你需要的东西而不用硬编码吗?

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

        你的代码的其余部分应该是一样的。

        【讨论】:

        • 如何获取墙的位置?
        • 墙的位置应该是你知道的。它要么是一个常量,因为它从不移动,要么是一个精灵对象并且有一个位置。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-14
        • 1970-01-01
        相关资源
        最近更新 更多