[英]C# XNA - Breakout-style ball physics
我正在為大學任務創建一個項目,但遇到了一些麻煩。 這個問題很小,很容易被忽視,但是它仍然困擾着我,作為完成工作的程序人,在解決之前,我無法繼續進行其他任何事情。
我的槳和球代碼工作得很好,並且基於球擊中槳的位置的簡單角速度,但是我的問題是球何時擊中磚頭。 該代碼通常可以正常工作,而且看起來也很完美,但是當球執行以下兩項操作之一時,這種情況就會改變:
我正在使用的代碼在這里(Ball.cs):
編輯(更新代碼):
public bool Touching(Brick brick)
{
var position = Position + Velocity;
return position.Y + Size.Y >= brick.Position.Y &&
position.Y <= brick.Position.Y + brick.Size.Y &&
position.X + Size.X >= brick.Position.X &&
position.X <= brick.Position.X + brick.Size.X;
}
public bool Collide(Brick brick)
{
//Secondary precaution check
if (!Touching(brick)) return false;
//Find out where the ball will be
var position = Position + Velocity;
//Get the bounds of the ball based on where it will be
var bounds = new Rectangle((int)position.X, (int)position.Y, Texture.Width, Texture.Height);
//Stop the ball from moving here, so that changes to velocity will occur afterwards.
Position = Position - Velocity;
//If the ball hits the top or bottom of the brick
if (bounds.Intersects(brick.Top) || bounds.Intersects(brick.Bottom))
{
Velocity = new Vector2(Velocity.X, -Velocity.Y); //Reverse the Y axe of the Velocity
}
//If the ball hits the left or right of the brick
if (bounds.Intersects(brick.Left) || bounds.Intersects(brick.Right))
{
Velocity = new Vector2(-Velocity.X, Velocity.Y); //Reverse the X axe of the Velocity
}
return true;
}
這些方法是從Level.cs的Update方法中調用的:
public override void Update(GameTime gameTime)
{
player.Update(gameTime);
balls.ForEach(ball => ball.Update(gameTime));
foreach (var brick in bricks)
{
foreach (var ball in balls)
{
if (ball.Touching(brick))
{
if (!collidingBricks.Contains(brick)) collidingBricks.Add(brick);
}
}
brick.Update(gameTime);
}
if (collidingBricks.Count > 0)
{
foreach (var ball in balls)
{
Brick closestBrick = null;
foreach (var brick in collidingBricks)
{
if (closestBrick == null)
{
closestBrick = brick;
continue;
}
if (Vector2.Distance(ball.GetCenterpoint(brick), ball.GetCenterpoint()) < Vector2.Distance(ball.GetCenterpoint(closestBrick), ball.GetCenterpoint()))
{
closestBrick = brick;
}else
{
brick.Health--;
if (brick.Health > 0) brick.Texture = Assets.GetBrick(brick.TextureName, brick.Health);
}
}
if (ball.Collide(closestBrick))
{
closestBrick.Health--;
if (closestBrick.Health > 0) closestBrick.Texture = Assets.GetBrick(closestBrick.TextureName, closestBrick.Health);
}
}
collidingBricks = new List<Brick>();
}
bricks.RemoveAll(brick => brick.Health <= 0);
base.Update(gameTime);
}
球的“碰撞”方法決定了球的新速度值。 正如你所看到的,它完全是基於在球會 ,而不是球的位置。 此外,我跑了那個球與碰撞最接近磚碰撞檢查,但我減少所有磚,球被觸碰(因此,速度只會更新僅基於最近的磚,並不是所有的碰撞磚健康)。
我了解為什么這會給我帶來麻煩,這可能是因為球同時擊中了兩側(擊中了角落)。
就像我說的那樣,這是一個小問題,但它甚至仍然會影響游戲體驗,尤其是當您期望球邏輯上只能朝一個方向移動而相反時。
我假設每次調用Update
方法時,您都需要通過將按幀時間縮放的速度矢量相加來將球移動到新位置,然后測試新位置。 這個對嗎?
在此模型中,球占據了一系列的點位置,而沒有經過中間空間。 由於所謂的“隧道效應”,這種運動會引起各種問題。 當對象移動得足夠快以致似乎部分或全部通過對象時,就會發生這種情況。
想象一下,您的球以每幀3像素的垂直方向移動,並且當前距離塊的邊緣1像素。 下一幀,球將移動到塊內 2個像素的位置。 由於球和塊不能重疊,所以這是一個錯誤。 也許不是很大的錯誤,但仍然不正確。
正如您所發現的那樣,當您走到角落時,錯誤確實會咬住您。 如果球的新位置與積木的拐角重疊,則確定正確的動作將變得更加困難,因為積木的最接近的邊緣可能不是您(從概念上)經過的那一條直至到達最終位置。
解決方案稱為連續碰撞檢測。 每次移動球時,都必須檢查整個運動路徑,以確定是否發生碰撞。
最簡單的方法(雖然肯定不是最快的方法)可能是使用布雷森漢姆算法之類的方法來繪制球沿運動線的位置。 布雷森漢姆(Bresenham)會以整數步長為您提供合理的運動路線-每個像素一步。 這些步驟將轉換為可用於檢測碰撞的坐標。
您可以通過預先計算是否可能發生碰撞以及如果發生碰撞來稍微加快速度。 如果屏幕上有100個塊,則您不想檢查每一步的每一塊。 您可以通過為整個移動(當前位置到結束位置)計算一個矩形邊界框來縮小范圍,並建立一個所有位於該框內的塊和其他對象的列表。 沒有結果意味着沒有碰撞,因此這是使昂貴的操作短路的好方法,同時最終也使它們的成本降低。
要使它完美,需要做很多工作,而要使其足夠接近則需要做大量工作。 Google continuous collision detection
和game physics tunneling
可獲取有關該主題的更多信息。
如果您可以解決這個問題,可以使用[Box2D]或Farseer之類的東西給您一些有趣的選擇。 當然,您可能會花費大約與解決原始問題有關的時間花在圍繞物理引擎重新設計游戲上:P
我認為可以通過解決沿其淺軸的碰撞來解決這兩個問題。 考慮這種情況:
在您當前的代碼中,此沖突將沿Y軸得到解決,因為有一個優先選擇,盡管沖突顯然發生在X軸上。
進行碰撞的一般方法是首先獲得碰撞深度(這可以通過使用Rectangle.Intersect
靜態方法制作一個新矩形並使用其寬度和高度來輕松實現)。 然后,您必須檢查兩個軸的交點有多深,在這種情況下,請檢查球速度矢量的淺軸反向分量。 最后一步是檢查兩個相交對象的相對位置,然后沿淺軸移動其中一個-在給定的示例中,球在磚塊的右側,因此必須在相交深度處向右移動。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.