在以前的Silverlight中,有个HitTest方法可以用来完成碰撞的检测。
But,Older versions (pre 3.0) did have a HitTest method!
在Silverlight4中就不可以使用HitTest方法来完成了。那么我们要该怎么做?
下面我会解读一个国外的源代码,让大家了解怎么进行碰撞检测。
会使用到一个方法FindElementsInHostCoordinates,这个是用来替代没有HitTest来检测碰撞。
还有一个方法作为基础就是Intersect方法,用来确立相交的范围。
碰撞原理:
我们把不规则的两个元素用矩形框来框起来,表示最大的范围,当两个矩形框想碰撞时,我们取出相交的范围,用Intersect方法,但是矩形相交不代表实际的对象是相交的,所以我们还需要遍历交集范围内的每一个点像素,看相交的两个物体是否都在这个点像素上,用FindElementsInHostCoordinates,如果都在,则表示碰撞。
代码展示(我已加上中文注释,根据原理加上注释可以和方便的理解下面代码):
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
namespace
HitTest
{
public
partial
class
Page : UserControl
{
public
Page()
{
InitializeComponent();
}
private
void
UserControl_MouseMove(
object
sender, MouseEventArgs e)
{
Point pt
=
e.GetPosition(cnvHitTest);
ship.SetValue(Canvas.LeftProperty, pt.X);
ship.SetValue(Canvas.TopProperty, pt.Y);
//
lets get pointers to the actual UI elements we care about:
//
获得飞船的Path坐标
Path shipShell
=
ship.FindName(
"
ShipShell
"
)
as
Path;
//
获得陨石的path坐标
Path cnvAsteroid
=
asteroid.FindName(
"
asteroidBig
"
)
as
Path;
//
检测碰撞
//
ship 飞船的用户控件(矩形大范围)
//
shipShell 飞船的外壳Path坐标
//
asteroid 陨石的用户控件(矩形大范围)
//
cnvAsteroid 陨石的外壳Path坐标
if
(CheckCollision(ship, shipShell, asteroid, cnvAsteroid))
txtStatus.Text
=
"
Collision!
"
;
else
txtStatus.Text
=
"
no collision
"
;
}
private
bool
CheckCollision(FrameworkElement control1, FrameworkElement controlElem1, FrameworkElement control2, FrameworkElement controlElem2)
{
//
first see if sprite rectangles collide
//
用户控件使用的是Canvas,我们现在要把用户控件的Canvas转换成矩形表示
//
UserControlBounds()用于转换为矩形来表示编辑
//
rect1 为飞船矩形
//
rect2 为陨石矩形
//
control1 为飞船用户控件(矩形)
//
control2 为运行用户控件(矩形)
Rect rect1
=
UserControlBounds(control1);
Rect rect2
=
UserControlBounds(control2);
//
Intersect(Rect) 查找当前矩形和指定矩形的交集,并将结果存储为当前矩形。
rect1.Intersect(rect2);
if
(rect1
==
Rect.Empty)
//
为空为矩形没有交集,那么飞船和陨石没有碰撞
{
//
no collision - GET OUT!
return
false
;
}
else
//
不为空,有交集,返回交集,但不表示飞船就和陨石有碰撞,需要进行更细致的判断
{
bool
bCollision
=
false
;
//
是否碰撞
Point ptCheck
=
new
Point();
//
点检测
//
now we do a more accurate pixel hit test
//
进行精确的点测试
//
以行为单位,循环扫描矩形内的每个点
//
在这里rect1为交集的矩形
for
(
int
x
=
Convert.ToInt32(rect1.X); x
<
Convert.ToInt32(rect1.X
+
rect1.Width); x
++
)
{
//
遍历行中的每个点
for
(
int
y
=
Convert.ToInt32(rect1.Y); y
<
Convert.ToInt32(rect1.Y
+
rect1.Height); y
++
)
{
//
行增加
//
设置检测点的坐标
ptCheck.X
=
x;
ptCheck.Y
=
y;
//
用FindElementsInHostCoordinates方法找出飞船用户控件中在点ptCheck上的element元素
List
<
UIElement
>
hits
=
System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(ptCheck, control1)
as
List
<
UIElement
>
;
//
controlElem1 为实际的飞船
if
(hits.Contains(controlElem1))
//
hits里面装的是飞船用户控件中所有在点ptCheck中的元素,看实际的飞船是否在其中
{
//
we have a hit on the first control elem, now see if the second elem has a similar hit
//
我们检测的飞船,还需要看点是否也在陨石中
//
同上面获取hits的方法,找出陨石控件中在点ptCheck上的element元素
List
<
UIElement
>
hits2
=
System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(ptCheck, control2)
as
List
<
UIElement
>
;
//
controlElem2 为实际的陨石
if
(hits2.Contains(controlElem2))
//
hits2里面装的是陨石用户控件中所有在点ptCheck中的元素,看实际的陨石是否在其中
{
//
够满足条件,是碰撞
bCollision
=
true
;
break
;
}
}
}
if
(bCollision)
break
;
}
return
bCollision;
}
}
public
Rect UserControlBounds(FrameworkElement control)
{
Point ptTopLeft
=
new
Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)), Convert.ToDouble(control.GetValue(Canvas.TopProperty)));
Point ptBottomRight
=
new
Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty))
+
control.Width, Convert.ToDouble(control.GetValue(Canvas.TopProperty))
+
control.Height);
return
new
Rect(ptTopLeft, ptBottomRight);
}
}
}
代码很少,但是当你理解了碰撞的原理和使用的核心方法,只要你有想法,都可以做出很复杂的碰撞检测。