Вот мой полный опыт обучения, в результате чего я получил довольно функциональную версию движения, которое я хотел, используя все внутренние методы 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;
Во-первых, когда на сцену добавляется паук, я добавляю слушателей в мир затылка для столкновений. По мере продвижения в развитии мне нужно будет различать группы столкновений; на данный момент эти обратные вызовы будут технически выполняться, когда ЛЮБОЕ тело сталкивается с любым другим телом.
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);
Обратные вызовы изменяют свойство «состояния» паука, которое является набором логических значений, и записывают любые арбитры столкновения Nape для последующего использования в моей логике ходьбы. Они также устанавливают и сбрасывают toTimer, который позволяет пауку потерять контакт с поверхностью уровня на срок до 100 мс, прежде чем снова станет действовать мировая гравитация.
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, отобрав ближайший ландшафт и усреднив эти углы, чтобы сделать ориентацию паука более плавной и реалистичной.