8.1 线性碰撞
本章我们将讨论如何判断物体是否碰撞,当它们碰撞时如何处理。我们会讨论一根轴上的碰撞、多根轴上的碰撞和与斜面的碰撞,我们还会讨论如何使用内建的FindElementInHostCoordinates()方法实现逐像素碰撞检测。
碰撞检测基础
碰撞检测最简单的方法是使用包围球或包围盒。为什么是球?因为大部分物体都能很好地适合球形,球也很容易进行碰撞检测,开销也不大,图8-1显示包围球中的飞船。如果我们要判断图8-1中的飞船是否和图8-2中的太阳碰撞,只需简单地判断两个物体间的距离,如果这个距离小于两个包围球半径之和,则发生碰撞,如图8-3所示。
图8-1 包围球中的宇宙飞船
图8-2 包围球中的太阳
图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 两个质量和大小都不同的小球在一根轴上的碰撞
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 阅读次数:8635