Đây là kinh nghiệm học tập hoàn chỉnh của tôi, dẫn đến một phiên bản chức năng khá nhiều của phong trào tôi muốn, tất cả đều sử dụng các phương pháp nội bộ của Nape. Tất cả các mã này đều nằm trong lớp Spider của tôi, lấy một số thuộc tính từ lớp cha của nó, một lớp Level.
Hầu hết các lớp và phương thức khác là một phần của gói Nape. Đây là phần thích hợp trong danh sách nhập khẩu của tôi:
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;
Đầu tiên, khi con nhện được thêm vào sân khấu, tôi thêm người nghe vào thế giới Nape để va chạm. Khi tôi phát triển hơn nữa, tôi sẽ cần phân biệt các nhóm va chạm; hiện tại, các cuộc gọi lại này về mặt kỹ thuật sẽ được chạy khi BẤT K body cơ thể nào va chạm với bất kỳ cơ thể nào khác.
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);
Các cuộc gọi lại thay đổi thuộc tính "trạng thái" của con nhện, đó là một tập hợp các booleans và ghi lại bất kỳ trọng tài va chạm Nape nào để sử dụng sau này trong logic đi bộ của tôi. Họ cũng thiết lập và xóa toTimer, cho phép con nhện mất liên lạc với bề mặt cấp độ trong tối đa 100ms trước khi cho phép trọng lực thế giới giữ lại.
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;
}
Cuối cùng, tôi tính toán những lực nào tác dụng lên con nhện dựa trên trạng thái của nó và mối quan hệ của nó với hình học cấp độ. Tôi sẽ chủ yếu để cho các ý kiến tự nói.
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;
}
}
}
Phần dính thực sự tôi tìm thấy là góc chuyển động cần phải theo hướng chuyển động mong muốn thực tế trong kịch bản nhiều điểm tiếp xúc trong đó con nhện đạt đến một góc nhọn hoặc nằm trong một thung lũng sâu. Đặc biệt là, do các vectơ tổng hợp của tôi cho lực bám dính, lực đó sẽ kéo AWAY từ hướng chúng ta muốn di chuyển thay vì vuông góc với nó, vì vậy chúng ta cần phải chống lại điều đó. Vì vậy, tôi cần logic để chọn một trong các điểm tiếp xúc để sử dụng làm cơ sở cho góc của vectơ chuyển động.
Một tác dụng phụ của "lực kéo" của lực bám dính là một chút do dự khi con nhện đạt đến một góc / đường cong lõm sắc nét, nhưng đó thực sự là loại thực tế từ quan điểm nhìn và cảm nhận, trừ khi nó gây ra vấn đề trên đường để nó như vậy Nếu tôi cần, tôi có thể sử dụng một biến thể của phương pháp này để tính toán lực bám dính.
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;
}
Logic này là khá "hoàn hảo", cho đến nay nó dường như đang làm những gì tôi muốn nó làm. Tuy nhiên, có một vấn đề về mỹ phẩm kéo dài, đó là nếu tôi cố gắng căn chỉnh đồ họa của con nhện với độ bám dính hoặc lực di chuyển, tôi thấy rằng con nhện cuối cùng "nghiêng" theo hướng di chuyển, sẽ ổn thôi nếu anh ta là một vận động viên chạy nước rút hai chân nhưng anh ta thì không, và các góc rất dễ bị biến đổi trong địa hình, do đó, con nhện giật mình khi đi qua vết va chạm nhẹ nhất. Tôi có thể theo đuổi một biến thể của giải pháp Byte56, lấy mẫu phong cảnh gần đó và lấy trung bình các góc đó, để làm cho hướng của con nhện mượt mà và chân thực hơn.