实体 AI
实体 AI (和现实中的 AI 不同)控制执行类实体(Agent)的行为。它包含目标(例如击败敌人)和更小的任务(例如靠近敌人和用武器攻击)。下面阐述实体 AI 中目标、任务和其他的部分,也会介绍如何改变实体的行为和如何写自定义 AI。
执行类实体(Agent)
一个执行类实体(Agent)是一种有 AI 和生命的:javadoc:Entity,可以执行一般生物的行为。要检查一个``Entity``是否为``Agent``可以直接强转。
import org.spongepowered.api.entity.living.Agent;
World world = ...;
UUID uuid = ...;
Agent entity = (Agent) world.getEntity(uuid).get();
if (entity.getAgentData().aiEnabled().get()) {
configureAI(entity);
}
在强转后,我们还可以检查 AI 是否启用。一些插件禁用 AI 来制作雕像、守卫、商店和其他 NPC。
目标
在原版 Minecraft 中有两 :javadoc:` GoalTypes {种}` :javadoc:` Goal {目标}`。
第一个是默认的 NORMAL。AI 的这一部分控制实体的行为。这个目标的任务有使用近战或远程武器攻击一个目标和在周围没有敌人时闲逛。如果我们想要覆盖或修改这类 AI, 我们通常会改动这个目标及其任务。
第二个目标是 TARGET,适合敌对生物。 这个目标里面的任务让实体选择敌人,然后实体会使用正常目标攻击敌人。TARGET 目标包含诸如选择最近可攻击目标和选择攻击您的敌人以反击的任务。
下面的代码从实体获取 NORMAL
目标并清除其中任务。
import org.spongepowered.api.entity.ai.Goal;
import org.spongepowered.api.entity.ai.GoalTypes;
Agent entity = ...;
Optional<Goal<Agent>> normalGoal = entity.getGoal(GoalTypes.NORMAL);
if (normalGoal.isPresent()) {
normalGoal.get().clear();
}
只有一个空的目标的实体是不会自己移动的,但它会首先完成当前的动作(或移动)并进入“待机状态”(比如直直地盯着前面看)。AI 不影响实体会随机播放的任何音效。
備註
请注意,直接修改目标或目标下的 AITask
会直接修改目标实体,这一点和其他 API 中需要重新将返回的复本应用回目标上不一样。这也意味着你不能直接将 AITask
从一个实体转移到另一实体上。
備註
服务器重启后(或实体卸载后),所有对实体 AI 的修改都会消失。
任务
很多不同种实体的行为其实是相近的。有鉴于此,实体 AI 被拆分成了大量零碎的 AI,每一个小部分都可以复用,这样一个小部分便是一个 AITask。每一个 AITask 都代表了实体的某一类行为,比如说 WATCH_CLOSEST 会令实体盯着最近的某一个匹配中的实体看,而 AVOID_ENTITY 会令实体避开所有匹配中的实体。
備註
原版 Minecraft 提供了大量 AITask
,但绝大多数还无法通过 API 访问。
添加 AI 任务
将 AITask
添加到 Entity
的目标是相当容易的。我们从创建一个简单的``AITask``开始。
import org.spongepowered.api.entity.ai.task.builtin.WatchClosestAITask;
Agent entity = ...;
Goal<Agent> goal = ...;
WatchClosestAITask watchClosestAiTask = WatchClosestAITask.builder()
.chance(1)
.maxDistance(30)
.watch(Player.class)
.build(entity);
goal.addTask(0, watchClosestAiTask);
在这个例子中,我们使用了对应的 builder 来创建 WatchClosestAITask 对象,其中我们定义触发该目标的概率是 100%。因此,这个实体会看着距离其 30 格内最近的 Player。我们为其赋予的优先级是 0,它代表最高优先级,因此它会先于所有其他 AITask
执行。
備註
Minecraft 的优先值里,小数值优先。默认情况下, Minecraft 使用从 0 到 10 的优先级。
移除 AI 任务
移除``AITask``会比较困难,有模组实体和自定义 AI 时会雪上加霜。Sponge API 有一种方法让插件辨别 AI 任务。呼叫 AITask#getType() 会返回 AITaskType,能够让插件辨别 AI 任务种类。
我们先试试看没有其它插件或模组影响时进行移除:
Goal<Zombie> goal = ...;
AITask<Zombie> attackTask = (AITask<Zombie>) goal.getTasks().get(1); // EntityAIZombieAttack
goal.removeTask(attackTask);
在这个例子中,我们直接假定在 Minecraft 1.12.2 中 Zombie
的第二个 AITask
是 EntityAIZombieAttack
。在此之后你就不用担心僵尸会来打你了。你也应该已经意识到了,这样操作存在诸多缺陷,比如假定 Zombie
的 AITask
是按某种顺序排列的。
備註
请勿从:javadoc:Goal#getTasks()`返回的列表中移除``AITask`;那个列表不可改动。
另一种更简单但功能稍弱的方式是按类型移除。如果你不需要依赖 AITask
的内部信息以精确确定要移除的 AI 的话,你应当优先使用这个方式。
goal.removeTasks(AITaskTypes.WANDER);
在这个例子中,我们移除了所有类型是 WANDER 的 AITask
。
備註
有鉴于目前 AITaskType
的 API 支持还不完整,这个方法的局限非常大。
如果你想移除所有的 AITask
然后从零开始定义实体 AI,你可以调用 Goal#clear()。
编写自定义 AI 任务
我们也可以实现自己的 AITask
。“实现自定义 AITask”一文描述了实现 AITask
的过程及中途可能会遇到的问题。
事件
AI API 和 SpongeAPI 其他部分一样用到了事件。关于事件系统的详细描述可参考这里。
AI API 是用来下列三个事件:
AITaskEvent.Add
事件会在新的 AITask
加入 Goal
时发布;相应的,AITaskEvent.Remove
事件会在某一 AITask
移除时发布。
SetAITargetEvent
在 Agent
每次选择新针对实体(一般是攻击目标)或者清除针对实体时触发。
这些事件都可以取消。这样,我们可以避免第三方改动自定义实体的 AI。