2011-01-12 23 views
4

我試圖讓球反彈了我的UI的頂部和底部的「牆」創建2D傍克隆時的感覺。 這是我Game.csXNA - 傍克隆 - 反思球時撞到牆?

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

它的工作原理,但我使用的是手動輸入正常,我想知道如何計算屏幕頂部和底部的法線?

回答

1

你可以改變布爾IsCollidingWithWall一些枚舉,如:

enum CollideType 
{ 
    None, 
    Vertical, 
    Horizontal 
} 

和創建正常檢查時,這種類型。

+0

這是一個很好的解決方案,有沒有雖然做的更優雅的方式?我覺得在製作這款遊戲​​時我會不斷地黑客入侵! – JuniorDeveloper1208

+1

不知道它是否更乾淨,但你可以爲枚舉創建一個擴展方法(例如'GetNormal(this CollideType collideType)'),它有一個開關,並返回每個標準的正常值... – Cameron

+0

謝謝!我覺得代碼很簡單,但數學很難。網上沒有太多的文檔,反正也不是4.0。 – JuniorDeveloper1208

0

難道你只取球的位置減去牆的位置,然後歸該矢量得到你需要的東西沒有硬編碼呢?

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

其餘的代碼應該工作相同。

+0

我如何獲得牆的位置? – JuniorDeveloper1208

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

不過請注意,使用這種「獨立」的碰撞檢測,你等到球已經移動屏幕的頂部/底部檢測碰撞;發生「之間」 衝突的幀可以是noticably掉,特別是當球快速移動。這是尤其如果您使用此碰撞檢測方法,檢測了槳式碰撞,因爲如果球移動速度不夠快的問題,是可能的球通過槳向右移動!

此問題的解決方案是使用所謂的Continuous Collision Detection。 CCD通常比離散碰撞檢測複雜得多;幸運的是,乒乓球很簡單,做CCD只會稍微複雜一些。但是,你仍然需要牢牢把握高中代數來解決方程。

如果你還有興趣,在this lecture有一個很好的解釋CCD和this GameDev article去更深入一點。也有manyquestions與它左右。

+0

感謝您的幫助:) – JuniorDeveloper1208

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