我想要一个可玩的角色,他可以以任何角度(包括侧身和倒置)在有机表面上“行走”。通过具有倾斜和弯曲特征的“有机”水准仪代替90度角的直线。
我目前正在从事AS3(中等业余经验)的工作,并使用Nape(几乎是新手)来进行基于重力的基本物理学,该行走机械手显然是一个例外。
有没有可能使用Nape约束的程序方法来进行这种步行动作?还是最好按照水平面的轮廓创建明确的步行“路径”,并使用它们来约束步行运动?
我想要一个可玩的角色,他可以以任何角度(包括侧身和倒置)在有机表面上“行走”。通过具有倾斜和弯曲特征的“有机”水准仪代替90度角的直线。
我目前正在从事AS3(中等业余经验)的工作,并使用Nape(几乎是新手)来进行基于重力的基本物理学,该行走机械手显然是一个例外。
有没有可能使用Nape约束的程序方法来进行这种步行动作?还是最好按照水平面的轮廓创建明确的步行“路径”,并使用它们来约束步行运动?
Answers:
这是我完整的学习经验,使用Nape的内部方法生成了所需功能的功能版本。所有这些代码都在我的Spider类中,从其父类Level提取一些属性。
其他大多数类和方法都是Nape软件包的一部分。这是我的导入列表的相关部分:
import flash.events.TimerEvent;
import flash.utils.Timer;
import nape.callbacks.CbEvent;
import nape.callbacks.CbType;
import nape.callbacks.InteractionCallback;
import nape.callbacks.InteractionListener;
import nape.callbacks.InteractionType;
import nape.callbacks.OptionType;
import nape.dynamics.Arbiter;
import nape.dynamics.ArbiterList;
import nape.geom.Geom;
import nape.geom.Vec2;
首先,当蜘蛛被添加到舞台上时,我将监听器添加到Nape世界进行碰撞。随着开发的深入,我将需要区分碰撞组。目前,从技术上讲,当任何主体与任何其他主体发生碰撞时,将运行这些回调。
var opType:OptionType = new OptionType([CbType.ANY_BODY]);
mass = body.mass;
// Listen for collision with level, before, during, and after.
var landDetect:InteractionListener = new InteractionListener(CbEvent.BEGIN, InteractionType.COLLISION, opType, opType, spiderLand)
var moveDetect:InteractionListener = new InteractionListener(CbEvent.ONGOING, InteractionType.COLLISION, opType, opType, spiderMove);
var toDetect:InteractionListener = new InteractionListener(CbEvent.END, InteractionType.COLLISION, opType, opType, takeOff);
Level(this.parent).world.listeners.add(landDetect);
Level(this.parent).world.listeners.add(moveDetect);
Level(this.parent).world.listeners.add(toDetect);
/*
A reference to the spider's parent level's master timer, which also drives the nape world,
runs a callback within the spider class every frame.
*/
Level(this.parent).nTimer.addEventListener(TimerEvent.TIMER, tick);
回调更改了Spider的“状态”属性(该属性是一组布尔值),并记录了所有Nape冲突仲裁器,以供以后在我的行走逻辑中使用。他们还设置并清除了toTimer,这使蜘蛛可以在长达100ms的时间内失去与水平面的接触,然后再重新抓住世界重力。
protected function spiderLand(callBack:InteractionCallback):void {
tArbiters = callBack.arbiters.copy();
state.isGrounded = true;
state.isMidair = false;
body.gravMass = 0;
toTimer.stop();
toTimer.reset();
}
protected function spiderMove(callBack:InteractionCallback):void {
tArbiters = callBack.arbiters.copy();
}
protected function takeOff(callBack:InteractionCallback):void {
tArbiters.clear();
toTimer.reset();
toTimer.start();
}
protected function takeOffTimer(e:TimerEvent):void {
state.isGrounded = false;
state.isMidair = true;
body.gravMass = mass;
state.isMoving = false;
}
最后,我根据蜘蛛的状态及其与关卡几何的关系,计算对蜘蛛施加的力。我将主要让评论自己说出来。
protected function tick(e:TimerEvent):void {
if(state.isGrounded) {
switch(tArbiters.length) {
/*
If there are no arbiters (i.e. spider is in midair and toTimer hasn't expired),
aim the adhesion force at the nearest point on the level geometry.
*/
case 0:
closestA = Vec2.get();
closestB = Vec2.get();
Geom.distanceBody(body, lvBody, closestA, closestB);
stickForce = closestA.sub(body.position, true);
break;
// For one contact point, aim the adhesion force at that point.
case 1:
stickForce = tArbiters.at(0).collisionArbiter.contacts.at(0).position.sub(body.position, true);
break;
// For multiple contact points, add the vectors to find the average angle.
default:
var taSum:Vec2 = tArbiters.at(0).collisionArbiter.contacts.at(0).position.sub(body.position, true);
tArbiters.copy().foreach(function(a:Arbiter):void {
if(taSum != a.collisionArbiter.contacts.at(0).position.sub(body.position, true))
taSum.addeq(a.collisionArbiter.contacts.at(0).position.sub(body.position, true));
});
stickForce=taSum.copy();
}
// Normalize stickForce's strength.
stickForce.length = 1000;
var curForce:Vec2 = new Vec2(stickForce.x, stickForce.y);
// For graphical purposes, align the body (simulation-based rotation is disabled) with the adhesion force.
body.rotation = stickForce.angle - Math.PI/2;
body.applyImpulse(curForce);
if(state.isMoving) {
// Gives "movement force" a dummy value since (0,0) causes problems.
mForce = new Vec2(10,10);
mForce.length = 1000;
// Dir is movement direction, a boolean. If true, the spider is moving left with respect to the surface; otherwise right.
// Using the corrected "down" angle, move perpendicular to that angle
if(dir) {
mForce.angle = correctAngle()+Math.PI/2;
} else {
mForce.angle = correctAngle()-Math.PI/2;
}
// Flip the spider's graphic depending on direction.
texture.scaleX = dir?-1:1;
// Now apply the movement impulse and decrease speed if it goes over the max.
body.applyImpulse(mForce);
if(body.velocity.length > 1000) body.velocity.length = 1000;
}
}
}
我发现的真正粘性部分是,在蜘蛛接触到锐角或位于深谷的多触点情况下,运动角度必须沿实际期望的运动方向。特别是,鉴于给定的粘附力矢量,该力将从我们想要移动的方向拉开,而不是垂直于它,因此我们需要对此加以抵消。因此,我需要逻辑来选择一个接触点作为运动矢量角度的基础。
当蜘蛛到达一个尖锐的凹角/曲线时,粘附力的“拉力”的副作用是轻微的犹豫,但是从外观的角度来看,这实际上是现实的,因此除非它会造成问题,否则我会保持原样。如果需要,我可以使用此方法的一种变型来计算粘附力。
protected function correctAngle():Number {
var angle:Number;
if(tArbiters.length < 2) {
// If there is only one (or zero) contact point(s), the "corrected" angle doesn't change from stickForce's angle.
angle = stickForce.angle;
} else {
/*
For more than one contact point, we want to run perpendicular to the "new" down, so we copy all the
contact point angles into an array...
*/
var angArr:Array = [];
tArbiters.copy().foreach(function(a:Arbiter):void {
var curAng:Number = a.collisionArbiter.contacts.at(0).position.sub(body.position, true).angle;
if (curAng < 0) curAng += Math.PI*2;
angArr.push(curAng);
});
/*
...then we iterate through all those contact points' angles with respect to the spider's COM to figure out
which one is more clockwise or more counterclockwise, depending, with some restrictions...
...Whatever, the correct one.
*/
angle = angArr[0];
for(var i:int = 1; i<angArr.length; i++) {
if(dir) {
if(Math.abs(angArr[i]-angle) < Math.PI)
angle = Math.max(angle, angArr[i]);
else
angle = Math.min(angle, angArr[i]);
}
else {
if(Math.abs(angArr[i]-angle) < Math.PI)
angle = Math.min(angle, angArr[i]);
else
angle = Math.max(angle, angArr[i]);
}
}
}
return angle;
}
到目前为止,这种逻辑似乎是“完美的”,因为它似乎正在按照我想要的去做。但是,存在一个挥之不去的修饰问题,因为如果我尝试使蜘蛛的图形与附着力或移动力对齐,我会发现蜘蛛最终会朝移动方向“倾斜”,如果他是一个两腿短跑运动员,但他不是,而且角度很容易受到地形变化的影响,因此,蜘蛛经过一点点颠簸时都会抖动。我可以对Byte56的解决方案进行一些改进,对附近的景观进行采样并平均这些角度,以使蜘蛛的方向更平滑,更真实。
如何使角色触摸的任何“粘”表面沿该表面的反法线施加力?只要它们与表面接触,力就保持不变,并且只要作用力就可以覆盖重力。因此,跳下天花板将具有预期的跌落到地板上的效果。
您可能希望实现一些其他功能,以使此工作顺利进行并更易于实现。例如,除了角色触摸之外,还可以使用围绕角色的圆圈来求和反转法线。正如这张this脚的油漆图像所示:
(表示的蜘蛛像是Byte56的属性)
蓝线是该点处表面的反法线。绿线是施加到星形轮的总力。红色圆圈代表蜘蛛正在寻找要使用的法线的范围。
这将使地形有些颠簸,而蜘蛛不会“松开其抓地力”。关于圆形大小和形状的实验,也许只是使用一个朝下的蜘蛛定向的半圆形,也许只是一个围绕腿部的矩形。
此解决方案使您可以保持物理状态开启,而无需处理角色可以遵循的特定路径。它还使用了相当容易获得和解释的信息(法线)。最后,它是动态的。即使更改世界的形状也很容易解决,因为您可以轻松获取所绘制几何图形的法线。
请记住,当没有任何物体在蜘蛛网范围内时,法向重力会接管。