实体 AI 控制
TabooLib 提供了一套简单易用的实体 AI 控制系统,允许你自定义实体的行为逻辑、寻路、视角控制等功能,而无需深入了解 NMS 底层实现。
核心概念
实体 AI 系统主要包含以下几个核心部分:
- SimpleAi: 自定义 AI 的基础接口,通过实现该接口来创建自定义行为
- Goal AI: 实体的目标行为(如移动、攻击、逃跑等)
- Target AI: 实体的目标选择逻辑(如寻找最近的玩家、选择攻击目标等)
- Navigation: 寻路系统,控制实体的移动
- Controller: 控制器系统,控制实体的视角和跳跃
Goal AI 与 Target AI 的区别
- Goal AI: 定义实体"做什么"(移动、攻击、逃跑等行为)
- Target AI: 定义实体"选择谁"(选择攻击目标、跟随目标等)
两者通常配合使用,Target AI 选择目标后,Goal AI 执行具体行为。
自定义 AI
创建自定义 AI
通过继承 SimpleAi 类来创建自定义 AI:
FollowPlayerAi.kt
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Player
import taboolib.module.ai.SimpleAi
import taboolib.module.ai.navigationMove
import taboolib.module.ai.controllerLookAt
/**
* 跟随玩家的 AI
*/
class FollowPlayerAi(
val entity: LivingEntity, // 实体本身
val target: Player, // 跟随的目标玩家
val speed: Double = 1.0 // 移动速度
) : SimpleAi() {
override fun shouldExecute(): Boolean {
// 判断是否应该执行这个 AI
// 当实体距离玩家超过 3 格时开始跟随
return entity.location.distance(target.location) > 3.0
}
override fun continueExecute(): Boolean {
// 判断是否继续执行
// 当实体距离玩家小于 2 格时停止跟随
return entity.location.distance(target.location) > 2.0
}
override fun startTask() {
// AI 开始时执行(可选)
entity.sendMessage("开始跟随 ${target.name}")
}
override fun updateTask() {
// 每 tick 执行一次(核心逻辑)
// 让实体看向玩家
entity.controllerLookAt(target)
// 移动到玩家位置
entity.navigationMove(target.location, speed)
}
override fun resetTask() {
// AI 结束时执行(可选)
entity.sendMessage("停止跟随")
}
}
代码说明:
shouldExecute(): 判断 AI 是否应该开始执行(必须实现)continueExecute(): 判断 AI 是否应该继续执行(默认调用shouldExecute())startTask(): AI 开始时执行一次(可选)updateTask(): 每 tick 执行一次,包含 AI 的核心逻辑(可选)resetTask(): AI 结束时执行一次(可选)
AI 生命周期
- 系统不断检查
shouldExecute() - 当返回
true时,调用startTask()开始任务 - 每 tick 调用
updateTask()更新任务 - 每 tick 检查
continueExecute(),返回false时调用resetTask()结束任务 - 回到步骤 1
AI 管理
添加 AI
为实体添加自定义 AI:
添加 AI 示例
import org.bukkit.entity.Zombie
import taboolib.module.ai.addGoalAi
import taboolib.module.ai.addTargetAi
fun setupZombieAi(zombie: Zombie, player: Player) {
// 添加 Goal AI(目标行为)
// 优先级 1,数字越小优先级越高
zombie.addGoalAi(FollowPlayerAi(zombie, player, speed = 1.2), priority = 1)
// 添加 Target AI(目标选择)
zombie.addTargetAi(FindNearestPlayerAi(zombie), priority = 1)
}
特性:
- 优先级数字越小,优先级越高
- 同一优先级的 AI 按照添加顺序执行
- 高优先级的 AI 会阻止低优先级 AI 的执行
替换 AI
根据优先级或类名替换现有 AI:
替换 AI 示例
import taboolib.module.ai.replaceGoalAi
import taboolib.module.ai.replaceTargetAi
// 根据优先级替换 Goal AI
zombie.replaceGoalAi(NewFollowAi(zombie, player), priority = 1)
// 根据类名替换 Goal AI
zombie.replaceGoalAi(
ai = NewFollowAi(zombie, player),
priority = 1,
name = "FollowPlayerAi"
)
// 替换 Target AI
zombie.replaceTargetAi(NewTargetAi(zombie), priority = 1)
使用场景:
- 动态修改实体行为
- 覆盖原版 AI 逻辑
- 实现状态切换(如战斗/非战斗状态)
移除 AI
根据优先级或类名移除 AI:
移除 AI 示例
import taboolib.module.ai.removeGoalAi
import taboolib.module.ai.removeTargetAi
import taboolib.module.ai.clearGoalAi
import taboolib.module.ai.clearTargetAi
// 根据优先级移除
zombie.removeGoalAi(priority = 1)
zombie.removeTargetAi(priority = 1)
// 根据类名移除
zombie.removeGoalAi(name = "FollowPlayerAi")
zombie.removeTargetAi(name = "FindNearestPlayerAi")
// 清空所有 AI
zombie.clearGoalAi()
zombie.clearTargetAi()
获取和设置 AI
获取或批量设置实体的 AI:
获取和设置 AI
import taboolib.module.ai.getGoalAi
import taboolib.module.ai.getTargetAi
import taboolib.module.ai.setGoalAi
import taboolib.module.ai.setTargetAi
// 获取所有 AI
val goalAis = zombie.getGoalAi()
val targetAis = zombie.getTargetAi()
// 复制 AI 到另一个实体
val anotherZombie: Zombie = ...
anotherZombie.setGoalAi(goalAis)
anotherZombie.setTargetAi(targetAis)
适用场景:
- 批量复制实体行为
- 保存和恢复 AI 状态
- 实体模板系统
寻路系统
移动到位置
让实体移动到指定位置:
寻路示例
import org.bukkit.Location
import taboolib.module.ai.navigationMove
import taboolib.module.ai.navigationReach
val targetLocation: Location = ...
// 移动到目标位置(默认速度 0.2)
zombie.navigationMove(targetLocation)
// 指定移动速度
zombie.navigationMove(targetLocation, speed = 1.5)
// 移动到另一个实体
val player: Player = ...
zombie.navigationMove(player, speed = 1.0)
// 检查是否到达目标
if (zombie.navigationReach()) {
zombie.sendMessage("已到达目标位置!")
}
代码说明:
navigationMove(): 设置寻路目标,返回是否成功设置路径speed: 移动速度,通常在 0.2 ~ 2.0 之间,默认 0.2navigationReach(): 检查实体是否到达目标位置
速度建议
不同实体类型的建议速度:
- 僵尸/骷髅: 0.8 ~ 1.2
- 村民: 0.6 ~ 1.0
- 苦力怕: 1.0 ~ 1.5
- 末影人: 1.2 ~ 2.0
速度过高可能导致实体穿墙或寻路异常。
实战示例:巡逻 AI
PatrolAi.kt
import org.bukkit.Location
import org.bukkit.entity.LivingEntity
import taboolib.module.ai.SimpleAi
import taboolib.module.ai.navigationMove
import taboolib.module.ai.navigationReach
/**
* 巡逻 AI - 在多个点之间循环移动
*/
class PatrolAi(
val entity: LivingEntity,
val points: List<Location>, // 巡逻点列表
val speed: Double = 0.8
) : SimpleAi() {
private var currentIndex = 0 // 当前目标点索引
private var hasSetPath = false // 是否已设置路径
override fun shouldExecute(): Boolean {
return points.isNotEmpty()
}
override fun updateTask() {
val targetPoint = points[currentIndex]
// 如果还没设置路径,先设置
if (!hasSetPath) {
hasSetPath = entity.navigationMove(targetPoint, speed)
}
// 检查是否到达目标点
if (entity.navigationReach()) {
// 切换到下一个巡逻点
currentIndex = (currentIndex + 1) % points.size
hasSetPath = false
}
}
override fun resetTask() {
hasSetPath = false
}
}
控制器系统
视角控制
控制实体看向目标:
视角控制示例
import org.bukkit.Location
import org.bukkit.entity.Player
import taboolib.module.ai.controllerLookAt
val targetLocation: Location = ...
val targetPlayer: Player = ...
// 让实体看向指定位置
zombie.controllerLookAt(targetLocation)
// 让实体看向另一个实体
zombie.controllerLookAt(targetPlayer)
// 实战:让实体盯着玩家看
class StaringAi(
val entity: LivingEntity,
val target: Player
) : SimpleAi() {
override fun shouldExecute(): Boolean {
return entity.location.distance(target.location) < 10.0
}
override fun updateTask() {
// 每 tick 更新视角朝向玩家
entity.controllerLookAt(target)
}
}
特性:
- 自动平滑转向目标
- 支持指定坐标或实体
- 不影响实体的移动
跳跃控制
控制实体的跳跃行为:
跳跃控制示例
import taboolib.module.ai.controllerJumpReady
import taboolib.module.ai.controllerJumpCurrent
// 让实体准备跳跃
zombie.controllerJumpReady()
// 检查实体是否正在跳跃
if (zombie.controllerJumpCurrent()) {
zombie.sendMessage("正在跳跃!")
}
// 实战:遇到障碍物自动跳跃
class AutoJumpAi(val entity: LivingEntity) : SimpleAi() {
override fun shouldExecute(): Boolean {
// 检查前方是否有障碍物
val frontBlock = entity.getTargetBlock(null, 2)
return frontBlock.type.isSolid
}
override fun updateTask() {
if (!entity.controllerJumpCurrent()) {
entity.controllerJumpReady()
}
}
}
最佳实践示例
完整示例:NPC 护卫系统
GuardSystem.kt
import org.bukkit.Location
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import taboolib.module.ai.*
/**
* 守卫 AI - 保护特定区域,驱逐入侵者
*/
class GuardAi(
val entity: Mob,
val guardPoint: Location, // 守卫点
val guardRadius: Double = 10.0 // 警戒半径
) : SimpleAi() {
private var intruder: Player? = null // 入侵者
override fun shouldExecute(): Boolean {
// 寻找警戒范围内的玩家
intruder = entity.world.players
.filter { it.location.distance(guardPoint) < guardRadius }
.minByOrNull { it.location.distance(entity.location) }
return intruder != null
}
override fun continueExecute(): Boolean {
// 入侵者离开或死亡时停止
val player = intruder ?: return false
return player.isOnline
&& !player.isDead
&& player.location.distance(guardPoint) < guardRadius
}
override fun startTask() {
// 发现入侵者时的行为
entity.isAggressive = true
intruder?.sendMessage("§c守卫:离开这里!")
}
override fun updateTask() {
val player = intruder ?: return
// 看向入侵者
entity.controllerLookAt(player)
// 如果距离较远,追逐入侵者
if (entity.location.distance(player.location) > 3.0) {
entity.navigationMove(player, speed = 1.2)
}
// 如果距离较近,设置攻击目标
else {
entity.target = player
}
}
override fun resetTask() {
// 入侵者离开后返回守卫点
entity.isAggressive = false
entity.target = null
entity.navigationMove(guardPoint, speed = 0.8)
}
}
/**
* 返回守卫点 AI
*/
class ReturnToGuardPointAi(
val entity: LivingEntity,
val guardPoint: Location
) : SimpleAi() {
override fun shouldExecute(): Boolean {
// 距离守卫点超过 2 格且没有目标时返回
return entity.location.distance(guardPoint) > 2.0
&& (entity as? Mob)?.target == null
}
override fun updateTask() {
entity.navigationMove(guardPoint, speed = 0.8)
// 到达守卫点后看向正前方
if (entity.navigationReach()) {
entity.controllerLookAt(guardPoint.clone().add(0.0, 0.0, 5.0))
}
}
}
// 使用示例
fun setupGuard(guard: Mob, guardPoint: Location) {
// 清除原有 AI
guard.clearGoalAi()
guard.clearTargetAi()
// 添加守卫 AI(优先级 1 - 最高)
guard.addGoalAi(GuardAi(guard, guardPoint, guardRadius = 15.0), priority = 1)
// 添加返回 AI(优先级 2 - 次要)
guard.addGoalAi(ReturnToGuardPointAi(guard, guardPoint), priority = 2)
}
完整示例:宠物跟随系统
PetSystem.kt
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Player
import org.bukkit.metadata.FixedMetadataValue
import taboolib.module.ai.*
import taboolib.platform.BukkitPlugin
/**
* 宠物跟随 AI
*/
class PetFollowAi(
val entity: LivingEntity,
val owner: Player,
val followDistance: Double = 3.0 // 跟随距离
) : SimpleAi() {
private var stuckTicks = 0 // 卡位计时器
private var lastLocation = entity.location.clone()
override fun shouldExecute(): Boolean {
// 距离主人超过跟随距离时开始跟随
val distance = entity.location.distance(owner.location)
return distance > followDistance && distance < 30.0
}
override fun continueExecute(): Boolean {
// 距离主人小于 2 格或超过 30 格时停止
val distance = entity.location.distance(owner.location)
return distance > 2.0 && distance < 30.0
}
override fun updateTask() {
// 看向主人
entity.controllerLookAt(owner)
// 检测卡位
if (lastLocation.distance(entity.location) < 0.5) {
stuckTicks++
// 卡位超过 3 秒(60 ticks),传送到主人身边
if (stuckTicks > 60) {
entity.teleport(owner.location)
stuckTicks = 0
return
}
} else {
stuckTicks = 0
}
lastLocation = entity.location.clone()
// 距离较近时慢速跟随,距离较远时快速跟随
val distance = entity.location.distance(owner.location)
val speed = when {
distance > 15.0 -> 1.8 // 远距离快速追赶
distance > 8.0 -> 1.2 // 中距离正常速度
else -> 0.8 // 近距离慢速跟随
}
entity.navigationMove(owner.location, speed)
}
override fun resetTask() {
stuckTicks = 0
}
}
/**
* 宠物坐下 AI(停止所有行为)
*/
class PetSitAi(val entity: LivingEntity) : SimpleAi() {
override fun shouldExecute(): Boolean {
return entity.hasMetadata("pet_sitting")
}
override fun startTask() {
// 坐下时取消所有移动
entity.velocity = entity.velocity.zero()
}
override fun updateTask() {
// 持续取消移动
entity.velocity = entity.velocity.zero()
}
}
// 宠物管理器
object PetManager {
/**
* 召唤宠物
*/
fun summonPet(owner: Player, entity: LivingEntity) {
// 标记为宠物
entity.setMetadata("pet_owner", FixedMetadataValue(BukkitPlugin.getInstance(), owner.uniqueId.toString()))
// 清除原有 AI
entity.clearGoalAi()
entity.clearTargetAi()
// 添加宠物 AI
entity.addGoalAi(PetSitAi(entity), priority = 1) // 坐下优先级最高
entity.addGoalAi(PetFollowAi(entity, owner), priority = 2) // 跟随次之
owner.sendMessage("§a宠物已召唤!右键宠物让它坐下/站起。")
}
/**
* 切换宠物坐下/站立状态
*/
fun toggleSit(entity: LivingEntity) {
if (entity.hasMetadata("pet_sitting")) {
entity.removeMetadata("pet_sitting", BukkitPlugin.getInstance())
} else {
entity.setMetadata("pet_sitting", FixedMetadataValue(BukkitPlugin.getInstance(), true))
}
}
}
常见问题
如何让实体停止移动?
有两种方式:
// 方法 1:清除所有 Goal AI(会移除所有行为)
entity.clearGoalAi()
// 方法 2:添加一个空 AI 覆盖移动行为
class StopAi : SimpleAi() {
override fun shouldExecute() = true
override fun updateTask() {
// 什么都不做
}
}
entity.addGoalAi(StopAi(), priority = 0) // 优先级 0 最高
AI 不执行怎么办?
检查以下几点:
-
优先级冲突:高优先级 AI 可能阻止了低优先级 AI
// 查看所有 AI
entity.getGoalAi().forEach { println(it) } -
shouldExecute 返回 false:检查条件是否正确
override fun shouldExecute(): Boolean {
println("检查执行条件") // 添加日志
return true
} -
实体类型不支持:某些实体(如盔甲架)不支持 AI
如何实现实体传送回守卫点?
结合 metadata 和计时器:
class TeleportBackAi(
val entity: LivingEntity,
val guardPoint: Location,
val maxDistance: Double = 50.0
) : SimpleAi() {
private var outOfRangeTicks = 0
override fun shouldExecute(): Boolean {
return entity.location.distance(guardPoint) > maxDistance
}
override fun updateTask() {
outOfRangeTicks++
// 超出范围 5 秒后传送回去
if (outOfRangeTicks > 100) {
entity.teleport(guardPoint)
outOfRangeTicks = 0
}
}
override fun resetTask() {
outOfRangeTicks = 0
}
}
如何让 AI 只在特定条件下执行?
使用 metadata 或自定义标记:
import org.bukkit.metadata.FixedMetadataValue
import taboolib.platform.BukkitPlugin
// 设置标记
entity.setMetadata("can_follow", FixedMetadataValue(BukkitPlugin.getInstance(), true))
// AI 中检查标记
override fun shouldExecute(): Boolean {
return entity.hasMetadata("can_follow")
&& entity.getMetadata("can_follow")[0].asBoolean()
}
// 移除标记
entity.removeMetadata("can_follow", BukkitPlugin.getInstance())
寻路系统在不同世界是否有效?
navigationMove() 只在同一世界有效。跨世界移动需要手动传送:
override fun updateTask() {
// 检查是否在同一世界
if (entity.world.uid != target.world.uid) {
entity.teleport(target.location)
} else {
entity.navigationMove(target.location, speed)
}
}
如何防止实体卡位?
参考宠物系统示例,使用卡位检测:
private var stuckTicks = 0
private var lastLocation = entity.location.clone()
override fun updateTask() {
// 位置变化小于 0.5 格视为卡位
if (lastLocation.distance(entity.location) < 0.5) {
stuckTicks++
if (stuckTicks > 60) { // 卡位 3 秒
entity.teleport(targetLocation) // 直接传送
}
} else {
stuckTicks = 0
}
lastLocation = entity.location.clone()
}
最佳实践
- 优先级规划:将关键 AI(如坐下、停止)设置为高优先级(小数字)
- 性能优化:避免在
updateTask()中进行大量计算,使用计时器控制执行频率 - 异常处理:始终检查目标实体是否有效、是否在线
- 卡位检测:对于长距离移动,务必添加卡位检测和传送逻辑
- 清理 AI:实体移除时记得清理 AI,避免内存泄漏