P2中的形状(中)
今天翻看了一下以前的笔记。2011年~2013年,密密麻麻写了一个本子,写的很仔细。再看看博客里的文章,也大部分都是那个时间段写的。很佩服当年的自己。
现在的自己,自从结婚之后,每天上班检产品,下班看孩子,文章基本没有更新。老抱怨没有时间,工资又低。后来想想,不是时间少,挣钱少,而是自己太懒。
2012年开通个人博客的时候,励志要做中国版的emanueleferonato,人家依然成了业界大牛,而我还在原地踏步。都是自己太懒啦!
好啦,不说那么多没用了啦!来,今天我能来学习新的教程:p2中的形状(中)
上一节,我们简单了解了p2中的形状,以及这些形状的属性。这一节,我们来仔细了解每个形状的用法,以及相应的属性。
形状详解
首先,我们回忆一下创建和使用形状的过程,总共分三步,一,把冰箱门打开~,啊不是:
- 创建Shape形状,或其子类形状。
- 创建刚体
- 使用body类的addShape()函数,将形状添加到刚体中
大致代码如下:var shape: p2.Shape = new p2.Shape(); var body: p2.Body = new p2.Body(); body.addShape(shape);
我们可以把上面代码中的Shape类,换成Box、Circle等Shape的子类,然后就可以创建对应形状的刚体了。
需要注意的是:截止到当前
2015-12-2
,p2的最新版本为v7.0,在最新版的p2中,所有形状的预置属性,如Circle的radius属性,均不作为构造函数的指定参数,而是成为参数options中的一个可选参数。代码说明如下:
变更前:
function Circle(
radius: number
)
变更后:
function Circle(
options?: {
radius: number
}
)
接下来,我们就来仔细学习一下这些形状。
Box
Box矩形(p2 v7.0之前的版本里,矩形是Rectangle类)是p2中的基本形状通过它的width和height属性,可以创建任意长宽尺寸的矩形。Box的构造函数如下:
function Box(
options: {
width?: number;
height?: number
}
)
参数说明:
- width:矩形的宽度,默认为1。
- height:矩形的高度,默认为1。
Circle
Circle圆形,同样是p2中的基本形状,这里的圆形是一个半径为radius的标准圆,它的构造函数如下
function Circle(
options?: {
radius: number
}
)
参数说明:
- radius:圆形的半径,默认为1。
Capsule
胶囊形状,啊哈,又学了一个新的单词
,顾名思义,就是外形类似胶囊的形状,或者你也可以把它想象成egret中的drawRoundRect()函数绘制的圆角矩形,其长度为length,高度为2*radius,两端是和两个半径为radius的半圆形,具体如上图所示,
Capsule的构造函数如下
function Capsule(
optoins?: {
length?: number;
radius?: number
}
)
参数说明:
- length:胶囊形状的长度,默认为1。
- radius:胶囊形状的半径,或者叫半度,默认为1。
Line
线段形状,顾名思义就是一个长度为length的线段,看上去与高度为1的rectangle形状无异,但算法上省去了对高度的检测,效率上会有所提升
Line形状的构造函数如下
function Line(
option?: {
length?: number
}
)
参数说明:
- length:线段形状的长度,默认为1。
Plane
这是一个特殊的形状,普通的形状都有一个有限的边界,平面形状的特殊之处在于,它沿y轴负方向是无限扩展的,这个描述让人很容易联想到地平面,无论我们走多久都走不到尽头。
plane形状的构造函数如下,
function Plane()
很简单,没有任何的参数,然而,初始情况下的plane并不是我们所设想的地平面,而是一个倒置的地平面,如下图所示
实际应用中,我们可以通过调整刚体的angle角度,使plane平面朝向不同的方向,来模拟墙体的物体。
Particle
粒子形状,粒子大家都知道,就是微小的颗粒,小到像一粒沙子,p2物理引擎中的粒子尺寸为零,这个意思是说,当粒子形状与其他形状堆叠到一起时,不会对其他形状或角度产生影响。
呃…那这个粒子能用来做什么?
粒子形状可以用来模拟钉子,做为关节的一个刚体,将另一个刚体固定在某个位置,这一点,类似于Box2d中的b2World.GetGroundBody()返回的空刚体相似。
不过空刚体正确的做法,实际上直接创建一个刚体,不为其添加形状,即可。
为了调试可见,在p2DebugDraw中,我用上图所示的形状表示粒子形状。
Particle的构造函数如下
function Particle()
HeightField
海拔形状,这是一个类似于plane的形状,不过它不像地平面那样一马平川,而是有一组y坐标组成的高低不平的丘陵。这些“丘陵”之间的间隔都是elementWidth,如下图所示:
和Plane形状一样,HeightField形状也是朝向y轴负方向,无限扩展的,水平方向的宽度是elementWidth与y坐标数量的乘积。
HeightField形状的构造函数如下:
function Heightfield(
options?: {
heights: number[];
minValue?: number;
maxValue?: number;
elementWidth?: number;
}
)
参数说明如下:
- heights:每个丘陵的高度组成的一个数组
- minValue:heights中最小的高度值。当heights中的高度是由一定的算法计算得出时,为了防止高度小于某个指定,可以设置minValue为高度的最小值。
- maxValue:与minValue参数类似,表示heights中的最大高度。
- elementWidth:每个高度之间的间隔,如上图所示。
其实,创建HeightField形状的过程,就是定义data属性中y坐标数组的过程,正如我在第一节里讲的,因为x坐标是以elementWidth逐渐增加的,这个过程恰恰和二元一次线性方程是相似的,所以根据上学时学习过的线性方式,可以很容易的创建出有规律的heightField形状。
例如,我们非常熟悉的三角函数,y=sin(i),取i从0到100时,y坐标计算结果,并保存x=i*elementWidth,到data属性中,这样就可以创建出圆滑的sin曲线。
对应的Egret代码大致如下:
private createHeightfeild(): void {
var radius:number = 50/ this.factor;
var heights: number[]=[];
var elementWidth:number = 1/ this.factor;
for(var i:number = 0; i<600;i++){
var y:number = Math.sin(i*0.02) *radius +4;
heights.push(y);
}
var heightfeildShape: p2.Heightfield = new p2.Heightfield({heights:heights,elementWidth:elementWidth});
var body: p2.Body = new p2.Body({ mass: 1 });
body.type = p2.Body.STATIC;
body.addShape(heightfeildShape);
this.world.addBody(body);
}
前方有坑
相信很多同学,都和我一样,学习了这么多稀奇古怪的形状,都想赶紧编写代码试一试,但p2并不像Box2D那么成熟,作者还有很多todo项目没有完成,以下几点说明,是拉登一脚一脚才过来的坑,请大家注意绕行。
- 形状键碰不完整
前面介绍的7中形状,他们之间并不是都可以完美的实现碰撞检测,例如,particle和圆形之间不进行碰撞 ,作者也在Github文档中有专门提出,后续会慢慢实现这些形状之间的碰撞模拟。
类似的情况,还有很多,具体请参考下图中的,形状碰撞关系表
- HeightField不能旋转
前面说过,Plane和HeightField形状,都是沿y轴负方向无限扩展的,其中plane形状可以通过旋转刚体,实现其他角度的地平面,遗憾的是,HeightField形状目前还不支持这一点,不随刚体的旋转发生变化。始终保持朝向y轴负方向。
举个例子
本节的内容主要是讲解各种行的实现过程,以及相关的属性。属性部分,和上一节类似,这里就不在单独举例了,具体可以参考上一节内容。
下一节预告
- 使用Convex创建三角形等多边形
- 使用fromPolygon()函数,随手绘制刚体
联系作者
ding