寻路导航系统
TabooLib 提供了一套基于 A* 算法的高级寻路导航系统,可以为 NPC、怪物或其他实体生成智能的移动路径,支持复杂地形、障碍物避让、随机位置生成等功能。
为什么使用 TabooLib Navigation?
- 智能寻路:基于 A* 算法,自动避开障碍物
- 地形感知:支持门、水、悬崖等复杂地形类型
- 随机生成:提供多种随机位置生成策略
- 高度定制:可自定义实体属性、路径代价等
- 性能优化:缓存机制、可控搜索深度
功能概览
核心概念
NodeEntity(实体代理)
NodeEntity 代表一个需要寻路的实体,定义了实体的物理属性和寻路能力:
val entity = NodeEntity(
location = npc.location, // 实体当前位置
height = 2.0, // 实体高度(格)
width = 0.6, // 实体宽度(格)
canPassDoors = true, // 能否穿过门
canOpenDoors = false, // 能否打开门
canFloat = true // 能否在水中游泳
)
Path(路径)
Path 对象包含从起点到终点的完整路径节点列表:
val path: Path = pathFinder.findPath(targetLocation, distance = 50f)
// 路径包含的节点列表
path.nodes.forEach { node ->
println("节点: ${node.x}, ${node.y}, ${node.z}")
}
// 路径是否可达目标
if (path.canReach()) {
println("可以到达目标")
}
PathFinder(路径查找器)
PathFinder 使用 A* 算法计算从当前位置到目标的最优路径。
快速开始
基础寻路示例
为 NPC 寻找到目标位置的路径:
基础寻路示例
import org.bukkit.Location
import org.bukkit.entity.Villager
import taboolib.module.navigation.*
fun findPathToTarget(villager: Villager, target: Location) {
// 1. 创建实体代理
val entity = NodeEntity(
location = villager.location,
height = 2.0, // 村民高度约 2 格
width = 0.6 // 村民宽度约 0.6 格
)
// 2. 创建路径查找器
val nodeReader = NodeReader(entity)
val pathFinder = PathFinder(nodeReader)
// 3. 寻找路径
val path = pathFinder.findPath(
position = target,
distance = 50f, // 最大搜索距离(格)
distanceManhattan = 1, // 曼哈顿距离阈值
deep = 1f // 搜索深度系数
)
if (path != null && path.canReach()) {
println("找到路径,共 ${path.getNodeCount()} 个节点")
// 使用路径...
} else {
println("无法到达目标位置")
}
}
代码说明:
NodeEntity: 创建实体的物理代理,定义尺寸和能力NodeReader: 负责读取和缓存地形信息PathFinder: 执行 A* 算法寻找路径distance: 最大搜索距离,越大越消耗性能distanceManhattan: 到达目标的曼哈顿距离阈值(通常为 1)deep: 搜索深度系数,影响性能和路径质量
路径查找
寻找到指定位置的路径
寻找路径
import taboolib.module.navigation.*
fun navigateToLocation(npc: LivingEntity, target: Location) {
val entity = NodeEntity(
location = npc.location,
height = npc.height,
width = npc.width
)
val pathFinder = PathFinder(NodeReader(entity))
// 寻找路径
val path = pathFinder.findPath(target, distance = 100f)
if (path != null) {
// 遍历路径节点
while (path.hasNext()) {
val node = path.getNextNode()
val nodePos = node.asBlockPos()
println("下一个节点: ${nodePos.x}, ${nodePos.y}, ${nodePos.z}")
// 移动到节点位置
// npc.teleport(nodePos.toLocation(npc.world))
path.advance() // 前进到下一个节点
}
if (path.isDone()) {
println("已到达目标!")
}
}
}
寻找到多个目标的路径
寻找到最近可达目标的路径:
多目标寻路
fun findPathToNearestTarget(npc: LivingEntity, targets: List<Location>) {
val entity = NodeEntity(
location = npc.location,
height = npc.height,
width = npc.width
)
val pathFinder = PathFinder(NodeReader(entity))
// 将 Location 转换为 Vector
val targetVectors = targets.map { it.toVector() }.toSet()
// 寻找到最近目标的路径
val path = pathFinder.findPath(
position = targetVectors,
distance = 100f
)
if (path != null && path.canReach()) {
println("找到最近目标的路径")
println("目标位置: ${path.target}")
}
}
设置搜索参数
调整搜索参数以平衡性能和效果:
搜索参数调整
// 近距离寻路(性能友好)
val nearPath = pathFinder.findPath(
position = target,
distance = 30f, // 较小的搜索范围
deep = 0.5f // 较浅的搜索深度
)
// 远距离寻路(高质量)
val farPath = pathFinder.findPath(
position = target,
distance = 100f, // 较大的搜索范围
deep = 2.0f // 较深的搜索深度
)
// 精确寻路(要求到达目标附近)
val precisePath = pathFinder.findPath(
position = target,
distance = 50f,
distanceManhattan = 0 // 必须精确到达目标格子
)
参数说明:
distance: 最大搜索距离,单位:格- 推荐值:近距离 20-50,远距离 50-100
- 过大会严重影响性能
distanceManhattan: 曼哈顿距离阈值0: 必须到达目标格子1: 到达相邻格子即可(推荐)2+: 允许更大的误差
deep: 搜索深度系数0.5: 快速搜索,路径可能不是最优1.0: 标准搜索(推荐)2.0: 深度搜索,路径更优但更耗时
NodeEntity 配置
基础属性配置
实体属性配置
// 不同类型实体的配置
val villagerEntity = NodeEntity(
location = villager.location,
height = 2.0,
width = 0.6,
canPassDoors = true,
canOpenDoors = false,
canFloat = true
)
val zombieEntity = NodeEntity(
location = zombie.location,
height = 2.0,
width = 0.6,
canPassDoors = false, // 僵尸不能穿过关闭的门
canOpenDoors = true, // 但可以破门
canFloat = false // 僵尸会沉入水底
)
val spiderEntity = NodeEntity(
location = spider.location,
height = 1.0,
width = 1.4, // 蜘蛛较宽
canPassDoors = false,
canOpenDoors = false,
canFloat = false
)
// 飞行实体
val phantomEntity = NodeEntity(
location = phantom.location,
height = 1.0,
width = 1.0,
canFloat = true // 可以在空中"游泳"
)
限制活动范围
为实体设置活动范围限制:
限制范围示例
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6
)
// 限制实体在指定范围内活动
entity.restrictTo(
pos = Vector(100, 64, 100), // 中心点
radius = 50 // 半径(格)
)
val pathFinder = PathFinder(NodeReader(entity))
val path = pathFinder.findPath(target, distance = 100f)
// 路径会避免超出限制范围
if (path != null && path.canReach()) {
println("路径在限制范围内")
}
适用场景:
- NPC 守卫特定区域
- 宠物跟随范围限制
- 怪物巡逻区域
自定义路径代价
为不同地形类型设置移动代价:
路径代价配置
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6
)
// 设置路径类型的移动代价(越高越不愿意经过)
entity.setPathfindingMalus(PathType.WATER, 8.0f) // 不喜欢水
entity.setPathfindingMalus(PathType.DANGER_FIRE, 16.0f) // 非常害怕火
entity.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0f) // 绝对不走火焰
entity.setPathfindingMalus(PathType.DOOR_WOOD_CLOSED, 2.0f) // 稍微避开门
val pathFinder = PathFinder(NodeReader(entity))
val path = pathFinder.findPath(target, distance = 50f)
PathType 说明:
WALKABLE: 可行走的地面(默认代价 0)WATER: 水(默认代价 8)LAVA: 岩浆(默认代价 -1,不可通过)DANGER_FIRE: 火焰附近(危险)DAMAGE_FIRE: 火焰(造成伤害)DOOR_WOOD_CLOSED: 关闭的木门FENCE: 栅栏(默认不可通过)
代价设置:
0: 无代价,优先选择> 0: 有代价,数值越大越避免-1: 不可通过
随机位置生成
RandomPositionGenerator 提供多种随机位置生成策略,适用于 AI 行为。
生成随机地面位置
随机地面位置
import taboolib.module.navigation.RandomPositionGenerator
fun wanderRandomly(npc: LivingEntity) {
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6
)
// 在当前位置附近生成随机地面位置
val randomPos = RandomPositionGenerator.generateLand(
nodeEntity = entity,
restrictX = 10, // X/Z 方向的最大偏移(格)
restrictY = 5 // Y 方向的最大偏移(格)
)
if (randomPos != null) {
println("随机位置: ${randomPos.x}, ${randomPos.y}, ${randomPos.z}")
// 寻找到该位置的路径
val pathFinder = PathFinder(NodeReader(entity))
val path = pathFinder.findPath(randomPos, distance = 30f)
if (path != null && path.canReach()) {
println("开始游荡到随机位置")
// 移动到该位置...
}
}
}
生成朝向目标的位置
生成朝向特定目标方向的随机位置:
朝向目标位置
fun approachTarget(npc: LivingEntity, target: Location) {
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6
)
// 生成朝向目标的随机位置
val approachPos = RandomPositionGenerator.generateLandTowards(
nodeEntity = entity,
restrictX = 8,
restrictY = 4,
target = target.toVector() // 目标位置
)
if (approachPos != null) {
println("接近目标的位置: $approachPos")
// 移动到该位置...
}
}
适用场景:
- AI 接近玩家
- NPC 追逐目标
- 怪物围攻
生成远离目标的位置
生成远离特定目标的随机位置:
远离目标位置
fun fleeFromDanger(npc: LivingEntity, danger: Location) {
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6
)
// 生成远离危险的随机位置
val fleePos = RandomPositionGenerator.generateLandAvoid(
nodeEntity = entity,
restrictX = 10,
restrictY = 5,
start = danger.toVector() // 危险位置
)
if (fleePos != null) {
println("逃离位置: $fleePos")
// 移动到该位置...
}
}
适用场景:
- 村民逃离僵尸
- NPC 躲避危险
- 怪物逃离玩家
生成空中位置
生成朝向目标的空中位置(适用于飞行实体):
空中位置
fun flyTowardsTarget(phantom: LivingEntity, target: Location) {
val entity = NodeEntity(
location = phantom.location,
height = 1.0,
width = 1.0,
canFloat = true // 飞行实体
)
// 生成朝向目标的空中位置
val airPos = RandomPositionGenerator.generateAirTowards(
nodeEntity = entity,
restrictX = 15,
restrictY = 10,
target = target.toVector()
)
if (airPos != null) {
println("飞行目标位置: $airPos")
// 飞向该位置...
}
}
Path 路径操作
遍历路径节点
路径遍历
fun walkPath(npc: LivingEntity, path: Path) {
println("路径共有 ${path.getNodeCount()} 个节点")
// 方式 1:使用 hasNext() 和 advance()
while (path.hasNext()) {
val node = path.getNextNode()
val position = node.asBlockPos().toLocation(npc.world)
println("移动到节点: ${node.x}, ${node.y}, ${node.z}")
// 移动 NPC
npc.teleport(position)
// 前进到下一个节点
path.advance()
// 延迟一段时间
Thread.sleep(200)
}
if (path.isDone()) {
println("已完成路径遍历")
}
}
获取路径信息
路径信息查询
fun analyzePathinfo(path: Path, entity: NodeEntity) {
// 路径节点总数
val nodeCount = path.getNodeCount()
println("路径长度: $nodeCount 个节点")
// 当前节点索引
val currentIndex = path.getNextIndex()
println("当前位置: 节点 $currentIndex")
// 目标位置
val target = path.target
println("目标位置: $target")
// 路径是否可达
val canReach = path.canReach()
println("是否可达: $canReach")
// 距离目标的距离
val distToTarget = path.distToTarget
println("距离目标: $distToTarget 格")
// 获取终点节点
val endNode = path.getEndNode()
println("终点: ${endNode?.x}, ${endNode?.y}, ${endNode?.z}")
// 获取特定索引的节点
val firstNode = path.getNode(0)
println("起点: ${firstNode.x}, ${firstNode.y}, ${firstNode.z}")
// 获取下一个节点位置(考虑实体尺寸)
val nextPos = path.getNextEntityPos(entity)
println("下一个位置(实体中心): $nextPos")
}
路径修改
路径修改
// 截断路径(保留前 N 个节点)
path.truncateNode(5) // 只保留前 5 个节点
// 替换特定节点
val newNode = Node(10, 64, 20)
path.replaceNode(index = 2, node = newNode)
完整示例
NPC 巡逻系统
NPCPatrolSystem.kt
import org.bukkit.Location
import org.bukkit.entity.LivingEntity
import taboolib.common.platform.function.submit
import taboolib.module.navigation.*
/**
* NPC 巡逻系统
*/
class NPCPatrolSystem(val npc: LivingEntity, val patrolPoints: List<Location>) {
private var currentPointIndex = 0
private var currentPath: Path? = null
private var isPatrolling = false
/**
* 开始巡逻
*/
fun startPatrol() {
if (isPatrolling) return
isPatrolling = true
println("${npc.name} 开始巡逻")
goToNextPoint()
}
/**
* 停止巡逻
*/
fun stopPatrol() {
isPatrolling = false
currentPath = null
println("${npc.name} 停止巡逻")
}
/**
* 前往下一个巡逻点
*/
private fun goToNextPoint() {
if (!isPatrolling) return
val target = patrolPoints[currentPointIndex]
println("${npc.name} 前往巡逻点 $currentPointIndex: ${target.blockX}, ${target.blockY}, ${target.blockZ}")
// 创建实体代理
val entity = NodeEntity(
location = npc.location,
height = npc.height,
width = npc.width,
canPassDoors = true,
canFloat = true
)
// 寻找路径
val pathFinder = PathFinder(NodeReader(entity))
val path = pathFinder.findPath(target, distance = 100f)
if (path != null && path.canReach()) {
currentPath = path
followPath(entity, path)
} else {
println("${npc.name} 无法到达巡逻点 $currentPointIndex,跳过")
nextPoint()
}
}
/**
* 沿着路径移动
*/
private fun followPath(entity: NodeEntity, path: Path) {
submit(period = 10L) { // 每 0.5 秒移动一次
if (!isPatrolling || currentPath != path) {
cancel()
return@submit
}
if (path.hasNext()) {
// 获取下一个节点位置
val nextPos = path.getNextEntityPos(entity)
val nextLocation = nextPos.toLocation(npc.world)
// 让 NPC 看向目标
val direction = nextLocation.toVector().subtract(npc.location.toVector())
npc.location.direction = direction.normalize()
// 移动 NPC
npc.teleport(nextLocation)
// 前进到下一个节点
path.advance()
} else {
// 路径完成
cancel()
println("${npc.name} 到达巡逻点 $currentPointIndex")
// 等待 3 秒后前往下一个点
submit(delay = 60L) {
nextPoint()
}
}
}
}
/**
* 切换到下一个巡逻点
*/
private fun nextPoint() {
currentPointIndex = (currentPointIndex + 1) % patrolPoints.size
goToNextPoint()
}
}
// 使用示例
fun setupNPCPatrol(npc: LivingEntity) {
val patrolPoints = listOf(
Location(npc.world, 100.0, 64.0, 100.0),
Location(npc.world, 120.0, 64.0, 100.0),
Location(npc.world, 120.0, 64.0, 120.0),
Location(npc.world, 100.0, 64.0, 120.0)
)
val patrolSystem = NPCPatrolSystem(npc, patrolPoints)
patrolSystem.startPatrol()
}
AI 游荡与追逐系统
NPCAISystem.kt
import org.bukkit.Location
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Player
import taboolib.common.platform.function.submit
import taboolib.module.navigation.*
/**
* NPC AI 系统(游荡 + 追逐)
*/
class NPCAISystem(val npc: LivingEntity, val detectionRange: Double = 10.0) {
private var currentPath: Path? = null
private var isActive = true
private var state = AIState.WANDERING
enum class AIState {
WANDERING, // 游荡
CHASING // 追逐
}
/**
* 启动 AI
*/
fun start() {
isActive = true
// 每 2 秒更新一次 AI
submit(period = 40L) {
if (!isActive) {
cancel()
return@submit
}
update()
}
}
/**
* 停止 AI
*/
fun stop() {
isActive = false
currentPath = null
}
/**
* 更新 AI 状态
*/
private fun update() {
// 检测附近的玩家
val nearbyPlayer = npc.world.players.find {
it.location.distance(npc.location) < detectionRange && !it.hasMetadata("vanished")
}
when {
nearbyPlayer != null -> {
// 发现玩家,切换到追逐状态
if (state != AIState.CHASING) {
state = AIState.CHASING
println("${npc.name} 发现目标: ${nearbyPlayer.name}")
}
chaseTarget(nearbyPlayer)
}
else -> {
// 没有目标,游荡
if (state != AIState.WANDERING) {
state = AIState.WANDERING
println("${npc.name} 失去目标,开始游荡")
}
wander()
}
}
}
/**
* 游荡行为
*/
private fun wander() {
// 如果正在移动,不重新生成路径
val path = currentPath
if (path != null && !path.isDone()) {
return
}
val entity = createEntity()
// 生成随机位置
val randomPos = RandomPositionGenerator.generateLand(
nodeEntity = entity,
restrictX = 15,
restrictY = 5
)
if (randomPos != null) {
// 寻找路径
val pathFinder = PathFinder(NodeReader(entity))
val newPath = pathFinder.findPath(randomPos, distance = 50f)
if (newPath != null && newPath.canReach()) {
currentPath = newPath
followPath(entity, newPath)
}
}
}
/**
* 追逐目标
*/
private fun chaseTarget(target: Player) {
val entity = createEntity()
// 生成接近目标的位置
val approachPos = RandomPositionGenerator.generateLandTowards(
nodeEntity = entity,
restrictX = 10,
restrictY = 5,
target = target.location.toVector()
)
if (approachPos != null) {
val pathFinder = PathFinder(NodeReader(entity))
val newPath = pathFinder.findPath(approachPos, distance = 30f)
if (newPath != null && newPath.canReach()) {
currentPath = newPath
followPath(entity, newPath, speed = 1.5) // 追逐时速度更快
}
}
}
/**
* 沿路径移动
*/
private fun followPath(entity: NodeEntity, path: Path, speed: Double = 1.0) {
val period = (10L / speed).toLong()
submit(period = period) {
if (!isActive || currentPath != path) {
cancel()
return@submit
}
if (path.hasNext()) {
val nextPos = path.getNextEntityPos(entity)
val nextLocation = nextPos.toLocation(npc.world)
// 让 NPC 看向目标
val direction = nextLocation.toVector().subtract(npc.location.toVector())
if (direction.lengthSquared() > 0) {
npc.location.direction = direction.normalize()
}
// 移动 NPC
npc.teleport(nextLocation)
path.advance()
} else {
cancel()
}
}
}
/**
* 创建实体代理
*/
private fun createEntity(): NodeEntity {
return NodeEntity(
location = npc.location,
height = npc.height,
width = npc.width,
canPassDoors = true,
canFloat = true
)
}
}
// 使用示例
fun setupNPCAI(npc: LivingEntity) {
val aiSystem = NPCAISystem(npc, detectionRange = 15.0)
aiSystem.start()
}
常见问题
寻路失败怎么办?
检查以下几点:
-
距离参数是否合理
// ❌ 距离太小,可能搜索不到
pathFinder.findPath(target, distance = 5f)
// ✅ 适当的距离
pathFinder.findPath(target, distance = 50f) -
目标是否可达
val path = pathFinder.findPath(target, distance = 50f)
if (path == null) {
println("无法生成路径(可能有障碍物)")
} else if (!path.canReach()) {
println("路径未完全到达目标")
} -
实体尺寸是否正确
// 确保 width 和 height 符合实体实际尺寸
val entity = NodeEntity(
location = npc.location,
height = 2.0, // 检查高度
width = 0.6 // 检查宽度
)
如何优化寻路性能?
-
限制搜索范围
// 近距离寻路
pathFinder.findPath(target, distance = 30f, deep = 0.5f) -
降低搜索频率
// 不要每 tick 都寻路
submit(period = 40L) { // 每 2 秒更新一次
val path = pathFinder.findPath(target, distance = 50f)
// ...
} -
复用 NodeReader
// ❌ 每次都创建新的(性能差)
val pathFinder1 = PathFinder(NodeReader(entity))
val pathFinder2 = PathFinder(NodeReader(entity))
// ✅ 复用(性能好,但需要注意线程安全)
val nodeReader = NodeReader(entity)
val pathFinder = PathFinder(nodeReader) -
缓存路径
// 只有在必要时才重新寻路
if (currentPath == null || currentPath.isDone()) {
currentPath = pathFinder.findPath(target, distance = 50f)
}
如何让 NPC 平滑移动?
使用 Location.direction 和渐进式移动:
fun smoothMove(npc: LivingEntity, targetLocation: Location) {
val direction = targetLocation.toVector().subtract(npc.location.toVector())
// 设置朝向
npc.location.direction = direction.normalize()
// 渐进移动
val distance = direction.length()
val step = 0.2 // 每次移动 0.2 格
if (distance > step) {
val movement = direction.normalize().multiply(step)
npc.teleport(npc.location.add(movement))
} else {
npc.teleport(targetLocation)
}
}
如何处理门和栅栏?
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6,
canPassDoors = true, // 可以穿过门
canOpenDoors = false // 不能打开门
)
// 如果需要打开门
entity.canOpenDoors = true
// 如果需要避开门
entity.setPathfindingMalus(PathType.DOOR_WOOD_CLOSED, 10.0f)
随机位置生成失败怎么办?
val randomPos = RandomPositionGenerator.generateLand(entity, 10, 5)
if (randomPos == null) {
// 1. 尝试更大的范围
val largerPos = RandomPositionGenerator.generateLand(entity, 20, 10)
if (largerPos == null) {
// 2. 尝试不同的生成策略
val abovePos = RandomPositionGenerator.generateLandAbove(entity, 15, 7)
if (abovePos == null) {
// 3. 使用固定位置或原地等待
println("无法生成随机位置")
}
}
}
如何让实体避开危险区域?
val entity = NodeEntity(
location = npc.location,
height = 2.0,
width = 0.6
)
// 设置危险类型的高代价
entity.setPathfindingMalus(PathType.DANGER_FIRE, 16.0f) // 避开火焰附近
entity.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0f) // 绝不通过火焰
entity.setPathfindingMalus(PathType.LAVA, -1.0f) // 绝不通过岩浆
entity.setPathfindingMalus(PathType.DAMAGE_CACTUS, 8.0f) // 避开仙人掌
性能优化建议
- 搜索距离:近距离 20-50,远距离 50-100,不建议超过 150
- 更新频率:寻路操作建议间隔 1-2 秒(20-40 ticks)
- 路径复用:如果目标未移动,不需要重新寻路
- 异步处理:对于非关键寻路,考虑异步计算路径
- 限制数量:避免同时为大量实体寻路
总结
TabooLib Navigation 模块提供了:
✅ 智能寻路:基于 A* 算法的高效路径查找 ✅ 地形感知:支持门、水、悬崖等复杂地形 ✅ 随机生成:多种随机位置生成策略 ✅ 高度定制:可自定义实体属性和路径代价 ✅ 性能优化:缓存机制、可控搜索深度
适用于 NPC 巡逻、怪物追逐、宠物跟随、AI 游荡等各种场景!