8.1 线性碰撞

本章我们将讨论如何判断物体是否碰撞,当它们碰撞时如何处理。我们会讨论一根轴上的碰撞、多根轴上的碰撞和与斜面的碰撞,我们还会讨论如何使用内建的FindElementInHostCoordinates()方法实现逐像素碰撞检测。

碰撞检测基础

碰撞检测最简单的方法是使用包围球或包围盒。为什么是球?因为大部分物体都能很好地适合球形,球也很容易进行碰撞检测,开销也不大,图8-1显示包围球中的飞船。如果我们要判断图8-1中的飞船是否和图8-2中的太阳碰撞,只需简单地判断两个物体间的距离,如果这个距离小于两个包围球半径之和,则发生碰撞,如图8-3所示。

图8-1

图8-1 包围球中的宇宙飞船

图8-2

图8-2 包围球中的太阳

图8-3

图8-3 如果两个物体的距离小于两个包围球半径之和,则发生碰撞

理论上听起来很简单,下面看一下代码。

线性碰撞

线性碰撞发生在一根轴上,下面我们将实现一个x轴上的碰撞。

1.打开LinearCollisions项目,项目中已经创建了一个小球,可见Ball.xmal文件。用户控件Ball的默认大小为50×50。LayoutRoot元素设置为1×1,BallShape为50×50,偏移量为–25,–25。

2.每个球都拥有公有变量:质量和速度。首先声明一个包含所有球对象的集合,两个球对象和一个随机数发生器。这个代码位于MainPage()构造函数之前:

private List<Ball> allBalls;
private Ball Ball1 = new Ball();
private Ball Ball2 = new Ball();
private Random Rand = new Random();

3.在构造函数中,InitializeComponent()方法之后,初始化集合:

allBalls = new List<Ball>();

4.然后添加两个球对象,并生成球的随机x方向的速度,因为处理的一维碰撞,所以无需设置y方向的速度:

Ball1.Velocity.X = Rand.Next(1, 16) - 8; 

5.现在定义小球,首先定义BallShape的长和宽,然后根据宽度设置质量,最后两行代码调整BallShape相对于LayoutRoot的位置,只是简单地将BallShape中心放置到LayoutRoot的中心。

Ball1.BallShape.Width = Ball1.BallShape.Height = 75; 
Ball1.Mass = Ball1.BallShape.Width / 50; 
Canvas.SetLeft(Ball1.BallShape,  -Ball1.BallShape.Width / 2);
Canvas.SetTop(Ball1.BallShape, -Ball1.BallShape.Height / 2);
Canvas.SetLeft(Ball1, (Ball1.BallShape.Width - Ball1.Width) / 2);
Canvas.SetTop(Ball1, 275);

6.然后,小球放置到main canvas上,小球放置在canvas的左方并添加到集合中。

Canvas.SetLeft(Ball1, (Ball1.BallShape.Width - Ball1.Width) / 2);
Canvas.SetTop(Ball1, 275); 
LayoutRoot.Children.Add(Ball1);
allBalls.Add(Ball1);

7.添加第二个小球,这个小球小一点,为25个像素,放置在右边:

Ball2.Velocity.X = Rand.Next(1, 16) - 8; 
Ball2.BallShape.Width = Ball2.BallShape.Height = 25; 
Ball2.Mass = Ball2.BallShape.Width / 50;
Canvas.SetLeft(Ball2.BallShape,  -Ball2.BallShape.Width / 2);
Canvas.SetTop(Ball2.BallShape, -Ball2.BallShape.Height / 2);
Canvas.SetLeft(Ball2, LayoutRoot.Width - Ball2.BallShape.Width + (Ball2.BallShape.Width - Ball2.Width) / 2); 
Canvas.SetTop(Ball2, 275); 
LayoutRoot.Children.Add(Ball2);
allBalls.Add(Ball2);

8.好了,创建完两个小球,现在为Move storyboard的Completed事件创建一个事件句柄,然后开始storyboard:

Move.Completed += new EventHandler(Move_Completed); 
Move.Begin(); 

9.添加事件处理函数:

private void Move_Completed(object sender, EventArgs e) { } 

10.在函数中添加一个循环体遍历集合中的每个小球:

for (int i = 0; i < allBalls.Count; i++) { }

11.在循环中,添加以下两行代码,第一行代码创建一个集合中的球,第二行代码根据球的随机x速度设置它的位置:

Ball currentBall = allBalls[i];
Canvas.SetLeft(currentBall, Canvas.GetLeft(currentBall) + currentBall.Velocity.X);

12.循环之后重启计时器:

Move.Begin();

13.现在如果你运行程序,这两个球会一直移动飞出屏幕外。下面我们添加一些边界检测。以下代码测试程序的左右边界:

if (Canvas.GetLeft(currentBall) - (currentBall.BallShape.Width - currentBall.Width)  / 2 < 0 && hasNegativeVelocity)
{
    currentBall.Velocity.X *= -1; 
}
else if (Canvas.GetLeft(currentBall) >LayoutRoot.Width - (currentBall.Width +  (currentBall.BallShape.Width - currentBall.Width) / 2) )
{
    currentBall.Velocity.X *= -1;
}

14.下面我们需要处理小球间的碰撞,首先创建CheckCollision()方法,这个方法以两个球为参数。

private void CheckCollision(Ball firstBall, Ball secondBall) { }

15.在这个方法开头,添加以上三行代码。前两行代码计算两个物体中心间的距离,第三行代码计算是否发生碰撞的距离:

double DX = Canvas.GetLeft(secondBall) - Canvas.GetLeft(firstBall); 
double Distance = Math.Sqrt(DX * DX); 
double CollisionDistance = firstBall.BallShape.Width / 2 +  secondBall.BallShape.Width / 2; 

16.三行代码之后,检测是否发生碰撞。如果两个物体的距离小于碰撞距离则发生碰撞,然后在if判断中处理碰撞反应。

if (Distance < CollisionDistance) { } 

17.在if语句中,首先判断总速度,即两个小球速度相减。

double VXTotal = firstBall.Velocity.X - secondBall.Velocity.X;

18.下面就是计算第一个小球速度的代码,这里你可以无需了解物理原理,只需知道:这个公式是一维方向的动量守恒定律。当两个物体碰撞时,以下公式可以计算第一个小球的速度:

firstBall.Velocity.X = ((firstBall.Mass - secondBall.Mass) * firstBall.Velocity.X + 2 * secondBall.Mass * secondBall.Velocity.X) /  (firstBall.Mass + secondBall.Mass);

19.根据以上代码,计算第二个小球的速度简单得多。

secondBall.Velocity.X = VXTotal + firstBall.Velocity.X; 

20.最后,更新每个球的速度:

Canvas.SetLeft(firstBall, Canvas.GetLeft(firstBall) + firstBall.Velocity.X);
Canvas.SetLeft(secondBall, Canvas.GetLeft(secondBall) + secondBall.Velocity.X);

21.有了碰撞代码,我们要做的就是调用这个方法处理每对对象。在Move_Completed()方法的if语句中,在边界碰撞检测代码后面添加以下代码,需要注意的是,碰撞小球的索引比主循环的当前小球的索引大1。

for (int j = i + 1; j < allBalls.Count; j++) 
    CheckCollision(currentBall, allBalls[j]);  

如果编译程序并运行,你会看到两个小球相对运动,如图8-4所示。当发生碰撞时,需要根据它们的质量和速度进行反弹。

图8-4

图8-4 两个质量和大小都不同的小球在一根轴上的碰撞

22.你可能会注意到当边界附近发生速度很快的碰撞时,小球会粘在边界上,这种情况会发生在物体的速度超出边界检测的情况中。幸运的是只需几行代码就可以修正这个错误,这几行代码限定边界检测只发生在球朝向墙运动的过程中。在Move_Completed()方法循环过程的Ball currentBall = allBalls[i]的代码后面最后添加以下代码,这些代码设置了一个标志判断速度是否是负的。

bool hasNegativeVelocity; 
if (currentBall.Velocity.X < 0) 
    hasNegativeVelocity = true; 
else
    hasNegativeVelocity = false;

23.然后,更新第一个边界检测使它还要检测反向速度。只有在球在向左运动时才会检测左边界检测。

if (Canvas.GetLeft(currentBall) - (currentBall.BallShape.Width - currentBall.Width)/ 2 < 0 && hasNegativeVelocity)

24.右边界的检测也要做出类似的修改,这次,你只在球向右运动时进行边界检测。

else if (Canvas.GetLeft(currentBall) > LayoutRoot.Width - (currentBall.Width+ (currentBall.BallShape.Width - currentBall.Width) / 2) && !hasNegativeVelocity)

因为我们使用了一个集合保存小球对象,因此再添加一个小球并不难,你可以在屏幕中央添加一个半径为40的小球作为练习。

文件下载(已下载 2573 次)

发布时间:2011/6/28 上午11:15:57  阅读次数:8606

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号