路点
你需要添加一些额外的信息到路点和NPC相对于路点的位置。
代码 sn-p(伪代码)显示了如何将一组路点创建为链表。每个路点都有一个链接和到下一个路点的距离,以及这个路点的总距离。
然后每一步你只需增加一组路点上的 NPC 距离。如果该距离大于下一个路径点的totalDistance,请点击指向next 的链接。您可以使用 while 循环来搜索下一个路径点,这样无论您的速度如何,您都将始终处于正确的位置。
一旦你在正确的路点,只需计算 NPC 在当前和下一个路点之间的位置。
定义一个路径点
class WayPoint {
public:
WayPoint(float, float);
float x, y, distanceToNext, totalDistance;
WayPoint next;
WayPoint addNext(WayPoint wp);
}
WayPoint::WayPoint(float px, float py) {
x = px; y = py;
distanceToNext = 0.0f;
totalDistance = 0.0f;
}
WayPoint WayPoint::addNext(WayPoint wp) {
next = wp;
distanceToNext = sqrt((next.x - x) * (next.x - x) + (next.y - y) * (next.y - y));
next.totalDistance = totalDistance + distanceToNext;
return wp;
}
声明和链接航路点
WayPoint a(10.0f, 10.0f);
WayPoint b(100.0f, 400.0f);
WayPoint c(200.0f, 100.0f);
a.addNext(b);
b.addNext(c);
NPC 以任何速度跟随尖锐的路径
WayPoint currentWayPoint = a;
NPC ship;
ship.distance += ship.speed * time;
while (ship.distance > currentWayPoint.next.totalDistance) {
currentWayPoint = currentWayPoint.next;
}
float unitDist = (ship.distance - currentWayPoint.totalDistance) / currentWayPoint.distanceToNext;
// NOTE to smooth the line following use the ease curve. See Bottom of answer
// float unitDist = sigBell((ship.distance - currentWayPoint.totalDistance) / currentWayPoint.distanceToNext);
ship.pos.x = (currentWayPoint.next.x - currentWayPoint.x) * unitDist + currentWayPoint.x;
ship.pos.y = (currentWayPoint.next.y - currentWayPoint.y) * unitDist + currentWayPoint.y;
注意您可以链接回起点,但要小心检查 while 循环中总距离何时回到零,否则您将进入无限循环。当您通过零重新计算 NPC distance 作为最后一个路点 totalDistance 的模数时,您永远不会经过一个以上的路点循环才能找到下一个路点。
例如,如果经过最后一个路点,则在 while 循环中
if (currentWayPoint.next.totalDistance == 0.0f) {
ship.distance = mod(ship.distance, currentWayPoint.totalDistance);
}
平滑路径
使用上述方法,您可以将附加信息添加到路点。
例如,为每个航路点添加一个与下一个路径成 90 度的矢量。
// 90 degh CW
offX = -(next.y - y) / distanceToNext; // Yes offX = - y
offY = (next.x - x) / distanceToNext; //
offDist = ?; // how far from the line you want to path to go
然后,当您沿路点之间的线计算 unitDist 时,您可以使用该单位 dist 平滑地插入偏移量
float unitDist = (ship.distance - currentWayPoint.totalDistance) / currentWayPoint.distanceToNext;
// very basic ease in and ease out or use sigBell curve
float unitOffset = unitDist < 0.5f ? (unitDist * 2.0f) * (unitDist * 2.0f) : sqrt((unitDist - 0.5f) * 2.0f);
float x = currentWayPoint.offX * currentWayPoint.offDist * unitOffset;
float y = currentWayPoint.offY * currentWayPoint.offDist * unitOffset;
ship.pos.x = (currentWayPoint.next.x - currentWayPoint.x) * unitDist + currentWayPoint.x + x;
ship.pos.y = (currentWayPoint.next.y - currentWayPoint.y) * unitDist + currentWayPoint.y + y;
现在,如果您添加 3 个路点,第一个 offDist 距离为正,第二个距离为负 offDist,您将获得一条路径,该路径可以像您在图像中显示的那样平滑曲线。
注意 NPC 的实际速度会随着每个航点的变化而变化。使用这种方法获得恒定速度的数学太繁重,不值得付出努力,因为对于没有人注意到的小偏移量。如果您的偏移量太大,请重新考虑您的航点布局
注意上述方法是对二次贝塞尔曲线的修改,其中控制点定义为端点之间距离中心的偏移量
S形曲线
您不需要添加偏移量,因为您可以通过操纵 unitDist 值沿路径获得一些(有限的)平滑(参见第一个 sn-p 中的注释)
使用以下函数将单位值转换为钟形曲线sigBell 和标准缓出曲线。使用参数power 来控制曲线的斜率。
float sigmoid(float unit, float power) { // power should be > 0. power 1 is straight line 2 is ease out ease in 0.5 is ease to center ease from center
float u = unit <= 0.0f ? 0.0f : (unit >= 1.0f ? 1.0f: unit); // clamp as float errors will show
float p = pow(u, power);
return p / (p + pow(1.0f - u, power));
}
float sigBell(float unit, float power) {
float u = unit < 0.5f ? unit * 2.0f : 1.0f - (unit - 0.5f) * 2.0f;
return sigmoid(u, power);
}