Unity——#06 下落

角色在高处往下跳亦或是走出高处的边缘时都应该表现出下落的动画,就目前而言,角色下落时只会播放闲置状态(idle)的动画,所以应该给它添加一个下落动画,如图:

现在要想的就是,fall动画与其他动画的转换(Transition),应该要有:
1.jump到fall的转换,从高处按跳跃键往低处跳时触发这个转换
2.ground到fall的转换,在高处没跳跃但直接走/跑到高处的边缘时(高低差很大)触发
3.fall到ground的转换,下落到地面时触发这个转换。
这里要注意区分好jump到ground和fall到ground两个转换的触发条件,如果跳的不高就不用播放fall了,直接转回到ground,不过这是后话。
一步一步来,逐渐实现各个转换,看上去只有3大步,其实里面有很多问题需要考虑。
1.jump到fall的转换
二话不说,先给它两者拉个Transition,方向自然就是从jump到fall:
那么遇到的第一个问题就是,该怎么触发这个转换呢?我们应该在一个时间点设一个判断,在jump的动画播放完后,要准备进行转换,此时jump动画由两条路可以选,一个是fall,一个是ground,那决定它的去向自然就是角色此刻是否在地上,这个时间点就是在jump动画播放完后。
要实现这个判断,就要引入一个虚拟碰撞体,虚拟碰撞体能在角色发生碰撞时返回与其碰撞的物体的名称。实现这个虚拟碰撞体的是一个叫Physics.OverlapCapsule的函数,其实Overlap的函数有很多,因为角色本身用的就是胶囊碰撞体,所以在投射一个Capsule能比较好的检测碰撞。这个函数的官方描述是:
public static Collider[] OverlapCapsule(Vector3 point0, Vector3 point1, float radius, int layerMask = AllLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
Returns
Collider[] Colliders touching or inside the capsule.Description
Check the given capsule against the physics world and return all overlapping colliders.
由此得知,功能就是给定一个胶囊,然后检查这个胶囊与物理世界的碰撞(against),然后返回所有碰撞体,据Returns得知返回的碰撞体放在了一个Collider类型的数组里。
由声明式知,它吃两个Vector3变量,这两个Vector3变量确定这个胶囊的位置;吃一个float变量radius,这个radius决定胶囊体上下两个圆的半径,画个图就是这个亚子:

还有一个int参数layerMask,这个参数是跟Inspector里的Layer有关:

以后如果有机会就详细介绍一下Layer和layerMask。这个函数的layerMask默认是所有的Layers,即它碰到的所有物体都会放在返回的Collider数组里面,不过可以通过LayerMask.GetMask()方法来指定传递某一个Layer。
首先,我们现在是要检测是否于地面相撞,所以我们要新增一个Layer命名为Ground,如图:

然后我们把地面Plane和这个作为斜坡的Cube的Layer设为Ground:


3个参数中的layerMask准备就绪,现在来看看怎么设置这个虚拟碰撞体的坐标和半径。我们暂时把这个虚拟碰撞体跟角色的碰撞体重合,这样利用角色自身坐标和角色碰撞体的半径和高度得到虚拟碰撞体的坐标。角色的自身坐标是transform.position,而角色碰撞体的半径可以通过外传一个CapsuleCollier来获得:

说完这些参数如何获得之后,我们来真正写如何调用OverlapCapsule:
1.先在角色碰撞体下创建一个空的子物体Create Empty,并命名为senser(传感器),负责各种碰撞的判断。

2.在这个senser里Add Component,命名为OnGroundSenser:

3.双击打开,先新建两个Vector3变量、一个CapsuleCollider变量、一个Collider数组、一个float变量
public CapsuleCollider capcol; //接收胶囊碰撞器
Vector3 point1; //虚拟胶囊体的底圆坐标
Vector3 point2; //虚拟胶囊体的顶圆坐标
private Collider[] outputCols; //用来接收与虚拟胶囊体发生碰撞的物体
private float radius; //模型胶囊体的半径
4.把角色胶囊体传给capcol

5.在Awake()函数里获得角色碰撞体的半径
void Awake () {
radius = capcol.radius;
}
6.在FixedUpdate()函数(角色的移动是在FixedUpdate()里完成)里建立坐标并传给Physics.OverlapCapsule()函数:
void FixedUpdate () {
point1 = transform.position + transform.up * radius;
point2 = transform.position + transform.up * capcol.height - transform.up * radius;
outputCols = Physics.OverlapCapsule (point1, point2, radius,LayerMask.GetMask("Ground"));
}
虚拟碰撞体的底圆坐标(point1)为角色当前坐标在加一个角色碰撞体半径的高度,顶圆坐标(point2)为角色当前坐标加一角色碰撞体的高度再减一个角色碰撞体半径的高度。然后使用LayerMask.GetMask("Ground")
函数获得Layer里的Ground,这样它就只会返回与虚拟碰撞体碰撞到的Layer为Ground的GameObject。
7.用先前宣告的Collider数组来接收Physics.OverlapCapsule()的返回值,并判断如果这个数组的长度不为0,就往上层(这里是角色的碰撞体PlayerHandle)发消息:
void FixedUpdate () {
point1 = transform.position + transform.up * radius;
point2 = transform.position + transform.up * (capcol.height-offset) - transform.up * radius;
outputCols = Physics.OverlapCapsule (point1, point2, radius,LayerMask.GetMask("Ground"));
if (outputCols.Length!=0) {
SendMessageUpwards ("IsOnGround");
} else {
SendMessageUpwards("IsNotOnGround");
}
}
现在,这个传感器就基本ok了,现在应该处理一下传感器的反馈了。
我们应该设置一个bool变量,在senser给出反馈(发消息)后,修改这个bool变量的布尔值,这个bool变量就是角色是否在地上的依据。在Animator视窗里的Paramaters创建一个bool参数命名为isGround:

在ActorController.cs里,接收senser发送的信息,并改变isGround的布尔值:
void IsOnGround(){ //当与地面发生碰撞时
anim.SetBool ("isGround", true);
}
void IsNotOnGround(){ //当不与地面发生碰撞时,跳跃/下坠状态
anim.SetBool("isGround", false);
}
至此,在离开jump动画之后,就能判断Transition往哪边走了,可以看看效果:

因为我们现在并没有拉fall到ground的Transition所以进入到fall动画之后就出不去了,这问题不大,之后肯定会解决。现在我们能看到,当地面跳跃的时候,在jump动画还没播放完,isGround就已经经历上勾→消勾→上勾的过程,所以到fall动画的Transition不会触发,回到ground;而在斜波上一跃而下时,由于在jump动画播放完后,isGround仍处在消勾状态,即不在地面,所以触发到fall动画Transition,播放fall动画。
前几天开始每天的时间都用来练车了,所以每天能写的内容不算多,但日更不能断,共勉!