Malmo是微软针对Minecraft开发的AI框架,包括游戏本身的mod和一个多平台框架,用于发送输入和接收有关世界的数据。
Minecraft的瞄准是圆柱形的。它存储在偏航(左右)和俯仰(上下)中,而不是完整的旋转四元数中。偏航从最左边的-180度开始,并在最右边包装到180度。俯仰从-90度直接朝上(天顶)到90度直接朝下(天底)。在我的代码中,我将它们存储为Vector2
(重新创建以类似于XNA),其中X表示偏航,Y表示俯仰。
我正在努力创建一个连续瞄准目标算法,使得AI能够将其相机瞄准到给定的目标偏航和俯仰。因为唯一的方法是通过连续瞄准(设置偏航和俯仰速度,而不是值)来实现连续运动,所以我需要反复逐步增加偏航和俯仰方向。
我通过将目标方向存储在可空属性中来实现这一点。如果该属性为空,则表示不更改瞄准。否则,每次调用更新方法时,减去存储值(目标瞄准)和当前瞄准(通过参数提供)之间的距离。然后它将差异缩放,以便偏航或俯仰为1(最大速度),而另一个则正确地比例分配。这个比例化的Vector2
速度被分为其偏航和俯仰,然后通过turn
和pitch
命令发送到客户端。一旦与目标方向相差10度以内,目标就设置为空。
在纸上,这似乎会使相机的瞄准直接朝向目标方向(不包括偏航包装)。然而,客户端的俯仰通常会直接超过目标方向,尽管更新方法发送了一个说要往相反方向走的pitch
命令。这意味着俯仰在天顶和天底处某种程度上被“卡住”,但会自行修复并“转向”,并在几秒钟后在相反的极点处被“卡住”。在转向前卡住的时间似乎呈指数(或者可能是二次函数)增加。
这是我的瞄准更新方法的源代码:
public void UpdateAim(Observations obs)
{
// AimTarget is a Vector2? property
if (AimTarget == null || obs == null)
{
return;
}
if (AimTarget.Value.Distance(obs.Aim) < AIM_CLOSE_ENOUGH) // < 10
{
Logger.LogInfo("Reached {0}", AimTarget.Value);
Look(Vector2.Zero);
AimTarget = null;
return;
}
double deltaYaw = AimTarget.Value.X - obs.Aim.X;
double deltaPitch = AimTarget.Value.Y - obs.Aim.Y;
// These two are stored as private Vector2 fields
deltaAim = new Vector2(deltaYaw, deltaPitch);
scaledAim = deltaAim / Math.Max(Math.Abs(deltaAim.X), Math.Abs(deltaAim.Y));
Look(scaledAim); // sets continuous aim velocity
}
以下是Look(Vector2)
的(简化版)源代码:
public void Look(Vector2 direction)
{
// Agent is an AgentHost property
Agent.sendCommand("turn " + velocity);
Agent.sendCommand("pitch " + velocity);
}
UpdateAim()
在主游戏循环期间被调用20次(我已经尝试过高达50次和低至5次)。
在上一次运行AI时(它被卡在最低点),我的瞄准调试数据如下:
// Format: (yaw, pitch)
Target Aim: (134.75, 27.90)
Actual In-Game Aim: (-6.50, 90.00) // Lines up with MC's debug screen
Delta Aim : (145.17, -62.10) // Total degrees needed to go in yaw and pitch
Scaled Aim Velocity: (1.00, -0.43)
缩放后的目标速度是提供给Look()
的。如您所见,俯仰速度为负值,这正是预期的,但实际的游戏瞄准仍保持在90度,原因不明。我的计算是否正确?
pitch -1
后跟着pitch 1
(以及它们之间的各种值)时,响应是即时的。也许它有一个积压。我不完全确定,因为在循环运行之间设置500毫秒延迟进行测试会导致野蛮的不准确性和过度校正,几乎无法与此问题区分开来。 - tageta72