여기에 완전한 학습 경험이 있으며 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);
콜백은 거미의 "상태"속성 (부울 집합)을 변경하고 나중에 걷는 논리에 사용하기 위해 Nape 충돌 중재자를 기록합니다. 또한 타이머를 설정하고 지워서 거미가 최대 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의 솔루션을 변형하여 주변 풍경을 샘플링하고 그 각도를 평균화 할 수 있습니다.