敌人路径跟随(Space 射击游戏)
Enemies path following (Space Shooter game)
我最近在使用 SFML 库,我正在尝试从头开始制作 Space 射击游戏。经过一段时间的努力,我得到了一些可以正常工作的东西,但我面临一个问题,我不知道如何继续,所以我希望你的智慧能引导我找到一个好的解决方案。我会尽力解释它:
敌人遵循路径:目前在我的游戏中,我的敌人可以遵循以下线性路径:
float vx = (float)m_wayPoints_v[m_wayPointsIndex_ui8].x - (float)m_pos_v.x;
float vy = (float)m_wayPoints_v[m_wayPointsIndex_ui8].y - (float)m_pos_v.y;
float len = sqrt(vx * vx + vy * vy);
//cout << len << endl;
if (len < 2.0f)
{
// Close enough, entity has arrived
//cout << "Has arrived" << endl;
m_wayPointsIndex_ui8++;
if (m_wayPointsIndex_ui8 >= m_wayPoints_v.size())
{
m_wayPointsIndex_ui8 = 0;
}
}
else
{
vx /= len;
vy /= len;
m_pos_v.x += vx * float(m_moveSpeed_ui16) * time;
m_pos_v.y += vy * float(m_moveSpeed_ui16) * time;
}
*m_wayPoints_v 是一个向量,基本上包含要遵循的 2d 点。
关于这一小段代码,我不得不说有时会给我带来问题,因为随着敌人的速度越来越快,接近下一个点变得困难。
有没有其他方法可以更准确地跟踪不依赖于敌人速度的路径?并且还与路径跟踪相关,如果我想在每个波浪运动模式开始之前介绍敌人(在到达终点之前做圆圈,螺旋形,椭圆形或其他任何东西),例如:
例如下图:
黑线是我希望飞船在开始 IA 模式(从左到右移动,从右到左移动)之前遵循的路径,即红色圆圈。
是否完成了所有和每个动作的硬编码,或者是否有其他更好的解决方案?
我希望我已经说清楚了...如果我没有说清楚,请告诉我,我会提供更多详细信息。非常感谢您!
这没有回答您的具体问题。我很好奇你为什么不使用 sfml 类型 sf::Vector2(或其类型定义 2i、2u、2f)?看起来它可能会清理你的一些代码。
就动画而言。您可以考虑将所需飞行模式的说明加载到堆栈或其他内容中。然后弹出每个位置并将你的船移动到那个位置并渲染,重复。
而且如果你想要一个类似于你图片的类似罪恶的飞行路径,你可以找到一个类似于你喜欢的飞行路径的方程式。使用desmos或其他东西来制作适合您需要的酷图。然后以 w/e 间隔迭代,将每次迭代输入此方程,你的结果就是你在每次迭代中的位置。
嗯,我想我发现了其中一个问题,但我不确定解决方案是什么。
在使用我之前贴出的那段代码时,发现由于速度值的原因,在到达目的地点的时候出现了问题。目前要流畅地移动 space 船,我需要将速度设置为 200...这意味着在这些公式中:
m_pos_v.x += vx * float(m_moveSpeed_ui16) * time;
m_pos_v.y += vy * float(m_moveSpeed_ui16) * time;
新位置可能会超过“2.0f”公差,因此space飞船无法找到目标点并卡住,因为每帧可以完成的最小移动(假设60fps)200 * 1 / 60 = 3.33 像素。有什么办法可以避免这种行为?
路点
您需要向路标和 NPC 相对于路标的位置添加一些附加信息。
代码片段(伪代码)显示了如何将一组路径点创建为 linked 列表。每个路点都有一个 link 和到下一个路点的距离,以及这个路点的总距离。
然后每走一步,你就增加 NPC 在路径点集上的距离。如果该距离大于下一个路径点的 totalDistance
,则沿着 link 到达 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;
}
声明和linking waypoints
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;
注意 你可以 link 回到起点,但要小心检查 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
值(请参阅第一个片段中的评论)
沿路径进行一些(有限的)平滑处理
使用以下函数将单位值转换为钟形曲线 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);
}
我最近在使用 SFML 库,我正在尝试从头开始制作 Space 射击游戏。经过一段时间的努力,我得到了一些可以正常工作的东西,但我面临一个问题,我不知道如何继续,所以我希望你的智慧能引导我找到一个好的解决方案。我会尽力解释它:
敌人遵循路径:目前在我的游戏中,我的敌人可以遵循以下线性路径:
float vx = (float)m_wayPoints_v[m_wayPointsIndex_ui8].x - (float)m_pos_v.x;
float vy = (float)m_wayPoints_v[m_wayPointsIndex_ui8].y - (float)m_pos_v.y;
float len = sqrt(vx * vx + vy * vy);
//cout << len << endl;
if (len < 2.0f)
{
// Close enough, entity has arrived
//cout << "Has arrived" << endl;
m_wayPointsIndex_ui8++;
if (m_wayPointsIndex_ui8 >= m_wayPoints_v.size())
{
m_wayPointsIndex_ui8 = 0;
}
}
else
{
vx /= len;
vy /= len;
m_pos_v.x += vx * float(m_moveSpeed_ui16) * time;
m_pos_v.y += vy * float(m_moveSpeed_ui16) * time;
}
*m_wayPoints_v 是一个向量,基本上包含要遵循的 2d 点。
关于这一小段代码,我不得不说有时会给我带来问题,因为随着敌人的速度越来越快,接近下一个点变得困难。
有没有其他方法可以更准确地跟踪不依赖于敌人速度的路径?并且还与路径跟踪相关,如果我想在每个波浪运动模式开始之前介绍敌人(在到达终点之前做圆圈,螺旋形,椭圆形或其他任何东西),例如:
例如下图:
黑线是我希望飞船在开始 IA 模式(从左到右移动,从右到左移动)之前遵循的路径,即红色圆圈。
是否完成了所有和每个动作的硬编码,或者是否有其他更好的解决方案?
我希望我已经说清楚了...如果我没有说清楚,请告诉我,我会提供更多详细信息。非常感谢您!
这没有回答您的具体问题。我很好奇你为什么不使用 sfml 类型 sf::Vector2(或其类型定义 2i、2u、2f)?看起来它可能会清理你的一些代码。
就动画而言。您可以考虑将所需飞行模式的说明加载到堆栈或其他内容中。然后弹出每个位置并将你的船移动到那个位置并渲染,重复。
而且如果你想要一个类似于你图片的类似罪恶的飞行路径,你可以找到一个类似于你喜欢的飞行路径的方程式。使用desmos或其他东西来制作适合您需要的酷图。然后以 w/e 间隔迭代,将每次迭代输入此方程,你的结果就是你在每次迭代中的位置。
嗯,我想我发现了其中一个问题,但我不确定解决方案是什么。
在使用我之前贴出的那段代码时,发现由于速度值的原因,在到达目的地点的时候出现了问题。目前要流畅地移动 space 船,我需要将速度设置为 200...这意味着在这些公式中:
m_pos_v.x += vx * float(m_moveSpeed_ui16) * time;
m_pos_v.y += vy * float(m_moveSpeed_ui16) * time;
新位置可能会超过“2.0f”公差,因此space飞船无法找到目标点并卡住,因为每帧可以完成的最小移动(假设60fps)200 * 1 / 60 = 3.33 像素。有什么办法可以避免这种行为?
路点
您需要向路标和 NPC 相对于路标的位置添加一些附加信息。
代码片段(伪代码)显示了如何将一组路径点创建为 linked 列表。每个路点都有一个 link 和到下一个路点的距离,以及这个路点的总距离。
然后每走一步,你就增加 NPC 在路径点集上的距离。如果该距离大于下一个路径点的 totalDistance
,则沿着 link 到达 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;
}
声明和linking waypoints
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;
注意 你可以 link 回到起点,但要小心检查 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
值(请参阅第一个片段中的评论)
使用以下函数将单位值转换为钟形曲线 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);
}