实体目标(Goal)系统
基础说明
AI 目标(Goal)系统是 Minecraft 实体 AI 的核心,定义了实体的行为优先级与决策逻辑。每个生物的行为都由一系列目标组成,这些目标按优先级执行,形成复杂而自然的行为模式。
KubeJS 通过 Java 反射机制提供了对实体 AI 目标的完整控制能力,让你可以创建完全自定义的实体行为。
架构
Entity
└── Mob
├── goalSelector(行为目标选择器)
└── targetSelector(攻击目标选择器)
两种目标选择器的区别:
goalSelector
:控制实体的日常行为,如移动、观察、浮水、巡逻等基础行为targetSelector
:专门处理敌对行为,如选择攻击目标、反击、追踪敌人等
目标执行机制
目标生命周期
每个 Goal 都遵循以下执行流程:
生命周期详解:
- 评估阶段:系统调用
canUse()
检查目标是否应该激活 - 启动阶段:如果可以使用,调用
start()
初始化目标状态 - 执行阶段:持续调用
tick()
执行目标逻辑,同时检查canContinueToUse()
- 中断检测:检查是否被更高优先级的目标中断(基于
isInterruptable()
) - 结束阶段:调用
stop()
清理目标状态,然后重新进入评估循环
优先级与冲突管理
- 优先级规则:数值越小,优先级越高(0 为最高优先级)
- 并行执行:不冲突的目标可以同时运行
- 冲突解决:通过 Goal.Flag 系统判断目标间是否冲突
- 中断机制:高优先级目标可以中断正在执行的低优先级目标
常用类型与用法
基础行为目标
目标类 | 功能描述 | 构造参数 | 使用场景 |
---|---|---|---|
FloatGoal | 在水中保持浮起状态 | (mob) | 几乎所有陆地生物的必备目标 |
RandomStrollGoal | 在附近区域随机游走 | (mob, speed) | 让生物看起来更有活力 |
LookAtPlayerGoal | 注视玩家 | (mob, targetType, range) | 增加生物的互动感 |
RandomLookAroundGoal | 随机四处张望 | (mob) | 让生物显得更自然 |
PanicGoal | 受伤后惊慌逃跑 | (mob, speed) | 被动生物的标准反应 |
攻击与目标选择
目标类 | 功能描述 | 构造参数 | 使用场景 |
---|---|---|---|
MeleeAttackGoal | 近战攻击行为 | (mob, speed, followTarget) | 近战敌对生物必备 |
RangedAttackGoal | 远程攻击行为 | (mob, speed, attackRadius, attackInterval) | 弓箭手、法师类生物 |
HurtByTargetGoal | 反击攻击者 | (mob, ...allies) | 大多数生物的自卫机制 |
NearestAttackableTargetGoal | 主动攻击最近敌人 | (mob, targetClass, mustSee) | 敌对生物的核心目标 |
特殊行为目标
目标类 | 功能描述 | 构造参数 | 使用场景 |
---|---|---|---|
AvoidEntityGoal | 避开特定实体 | (mob, entityType, distance, walkSpeed, sprintSpeed) | 让生物害怕某些威胁 |
FollowOwnerGoal | 跟随主人 | (mob, speed, minDistance, maxDistance) | 驯服生物的忠诚行为 |
SitWhenOrderedToGoal | 听从坐下指令 | (mob) | 可驯服生物的服从性 |
BreedGoal | 繁殖行为 | (mob, speed) | 让生物能够繁殖后代 |
用法与实践
创建自定义僵尸
js
EntityEvents.spawned(event => {
const entity = event.entity;
if (entity.type === "minecraft:zombie") {
// 加载所需的目标类
const FloatGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.FloatGoal");
const MeleeAttackGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.MeleeAttackGoal");
const RandomStrollGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.RandomStrollGoal");
const LookAtPlayerGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.LookAtPlayerGoal");
const HurtByTargetGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal");
const NearestAttackableTargetGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal");
const PlayerClass = Java.loadClass("net.minecraft.world.entity.player.Player");
// 清空现有的所有目标
entity.goalSelector.removeAllGoals(goal => true);
entity.targetSelector.removeAllGoals(goal => true);
// 重新设置目标系统
// 行为目标
entity.goalSelector.addGoal(0, new FloatGoal(entity)); // 浮水
entity.goalSelector.addGoal(3, new MeleeAttackGoal(entity, 1.0, true)); // 近战攻击
entity.goalSelector.addGoal(7, new RandomStrollGoal(entity, 1.0)); // 随机游走
entity.goalSelector.addGoal(8, new LookAtPlayerGoal(entity, PlayerClass, 8.0)); // 注视玩家
// 攻击目标
entity.targetSelector.addGoal(1, new HurtByTargetGoal(entity)); // 反击
entity.targetSelector.addGoal(2, new NearestAttackableTargetGoal(entity, PlayerClass, true)); // 主动攻击玩家
entity.customName = "定制僵尸";
}
});
创建无害生物
js
ItemEvents.entityInteracted(event => {
const { target: entity, player, hand } = event;
if (hand !== "MAIN_HAND") return;
if (entity.type === "minecraft:zombie") {
const NearestAttackableTargetGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal");
const HurtByTargetGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal");
// 移除所有攻击性目标
entity.targetSelector.removeAllGoals(goal =>
goal instanceof NearestAttackableTargetGoal ||
goal instanceof HurtByTargetGoal
);
// 清除当前攻击目标
entity.setTarget(null);
player.tell("这只僵尸现在是友好的了!");
}
});
类型安全检查
js
// 推荐:使用类型检查而非 try-catch
const $PathfinderMob = Java.loadClass("net.minecraft.world.entity.PathfinderMob");
const $Mob = Java.loadClass("net.minecraft.world.entity.Mob");
if (entity instanceof $PathfinderMob) {
// 只有 PathfinderMob 才能使用复杂的导航目标
const complexGoal = new SomeNavigationGoal(entity);
entity.goalSelector.addGoal(priority, complexGoal);
} else if (entity instanceof $Mob) {
// 基础 Mob 使用简单目标
const simpleGoal = new SimpleGoal(entity);
entity.goalSelector.addGoal(priority, simpleGoal);
}
目标优先级规划
建议的优先级分配:
js
// 0-2: 生存必需(浮水、逃跑、反击)
entity.goalSelector.addGoal(0, new FloatGoal(entity));
entity.goalSelector.addGoal(1, new PanicGoal(entity, 2.0));
entity.targetSelector.addGoal(1, new HurtByTargetGoal(entity));
// 3-5: 主要行为(攻击、繁殖、跟随)
entity.goalSelector.addGoal(3, new MeleeAttackGoal(entity, 1.0, true));
entity.goalSelector.addGoal(4, new BreedGoal(entity, 1.0));
entity.targetSelector.addGoal(2, new NearestAttackableTargetGoal(entity, PlayerClass, true));
// 6-8: 社交行为(注视、交互)
entity.goalSelector.addGoal(6, new LookAtPlayerGoal(entity, PlayerClass, 8.0));
// 9+: 闲置行为(游走、四处张望)
entity.goalSelector.addGoal(9, new RandomStrollGoal(entity, 1.0));
entity.goalSelector.addGoal(10, new RandomLookAroundGoal(entity));
性能优化
js
// 避免过于频繁的检查
EntityJSEvents.addGoalSelectors('minecraft:villager', e => {
e.customGoal(
'optimizedBehavior',
5,
mob => {
// 使用随机数降低检查频率
return mob.getRandom().nextInt(100) < 5;
},
mob => true,
false, // 不要求每帧更新
mob => {},
mob => {},
true,
mob => {
// 缓存昂贵的计算
const data = mob.getPersistentData();
const lastUpdate = data.getLong("lastExpensiveUpdate");
const currentTime = mob.level.getGameTime();
if (currentTime - lastUpdate > 20) { // 每秒更新一次
// 执行昂贵的计算
const nearbyEntities = mob.level.getNearbyEntities(mob.blockPosition(), 16);
data.putInt("nearbyEntityCount", nearbyEntities.length);
data.putLong("lastExpensiveUpdate", currentTime);
}
}
);
});
调试技巧
js
// 添加调试信息
function debugEntityGoals(entity) {
console.log("=== Goal Selector ===");
entity.goalSelector.getAvailableGoals().forEach(goalWrapper => {
const goal = goalWrapper.getGoal();
const priority = goalWrapper.getPriority();
const isRunning = goalWrapper.isRunning();
console.log(`Priority ${priority}: ${goal} (${isRunning ? "运行中" : "未激活"})`);
});
console.log("=== Target Selector ===");
entity.targetSelector.getAvailableGoals().forEach(goalWrapper => {
const goal = goalWrapper.getGoal();
const priority = goalWrapper.getPriority();
const isRunning = goalWrapper.isRunning();
console.log(`Priority ${priority}: ${goal} (${isRunning ? "运行中" : "未激活"})`);
});
}
// 在游戏中使用调试命令
ItemEvents.entityInteracted(event => {
const { target: entity, player, hand } = event;
if (hand === "MAIN_HAND" && player.getMainHandItem().id === "minecraft:stick") {
debugEntityGoals(entity);
}
});
实用工具函数
js
// 实体行为管理工具
const EntityBehaviorUtils = {
// 安全地添加目标(检查是否已存在)
safeAddGoal(entity, selector, priority, goal) {
const existingGoals = selector.getAvailableGoals();
const hasGoal = existingGoals.some(wrapper =>
wrapper.getGoal().getClass() === goal.getClass()
);
if (!hasGoal) {
selector.addGoal(priority, goal);
return true;
}
return false;
},
// 切换实体的敌对状态
toggleHostility(entity, isHostile) {
const NearestAttackableTargetGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal");
const PlayerClass = Java.loadClass("net.minecraft.world.entity.player.Player");
if (isHostile) {
this.safeAddGoal(entity, entity.targetSelector, 2,
new NearestAttackableTargetGoal(entity, PlayerClass, true));
} else {
entity.targetSelector.removeAllGoals(goal => goal instanceof NearestAttackableTargetGoal);
entity.setTarget(null);
}
},
// 获取实体当前活跃的目标数量
getActiveGoalCount(entity) {
const goalCount = entity.goalSelector.getAvailableGoals()
.filter(wrapper => wrapper.isRunning()).length;
const targetCount = entity.targetSelector.getAvailableGoals()
.filter(wrapper => wrapper.isRunning()).length;
return { goals: goalCount, targets: targetCount };
}
};
// 使用示例
ItemEvents.entityInteracted(event => {
const { target: entity, player, hand } = event;
if (hand === "MAIN_HAND" && player.isShiftKeyDown()) {
const counts = EntityBehaviorUtils.getActiveGoalCount(entity);
player.tell(`活跃目标: ${counts.goals} 个行为, ${counts.targets} 个攻击目标`);
// 切换敌对状态
const isCurrentlyHostile = entity.getTarget() != null;
EntityBehaviorUtils.toggleHostility(entity, !isCurrentlyHostile);
player.tell(`实体现在${!isCurrentlyHostile ? "敌对" : "友好"}`);
}
});
EntityJS
事件
addGoalSelectors
:注册行为目标addGoals
:注册攻击目标removeGoal
/removeGoals
:移除目标- 支持所有原生目标类,也支持自定义 Goal
customGoal 参数
参数名 | 类型 | 说明 |
---|---|---|
goalName | string | 目标名称,必须全局唯一,建议用英文+功能描述,如 'customGoalPatrol' |
priority | int | 优先级,越小越高 |
canUse | (mob) => boolean | 激活条件 |
canContinueToUse | (mob) => boolean | 继续条件 |
requiresUpdateEveryTick | boolean | 是否每帧更新 |
start | (mob) => void | 开始执行时调用 |
stop | (mob) => void | 结束执行时调用 |
isInterruptable | boolean | 是否可被中断 |
tick | (mob) => void | 每帧逻辑 |
命名规范
- 每个 customGoal 的 goalName 必须唯一,不能重复。
- 推荐命名格式:
customGoal功能
,如customGoalPatrol
、customGoalPack
、customGoalBuild
。 - Markdown 段落锚点也应唯一,如 {#EntityJsCustomGoalPatrol}。
高级示例
智能巡逻
js
EntityJSEvents.addGoalSelectors('minecraft:iron_golem', e => {
e.customGoal(
'customGoalPatrol',
3,
mob => {
// 只在白天且村庄中心附近巡逻
const world = mob.level;
const isDaytime = world.isDay();
const nearVillage = world.getNearbyEntities(mob.blockPosition(), 32)
.some(entity => entity.type === "minecraft:villager");
return isDaytime && nearVillage && mob.getRandom().nextInt(200) < 5;
},
mob => {
// 继续巡逻直到到达目标或夜晚降临
return !mob.getNavigation().isDone() && mob.level.isDay();
},
false,
mob => {
// 开始巡逻:选择村庄边缘的巡逻点
const world = mob.level;
const currentPos = mob.blockPosition();
// 寻找最远的村民作为参考点
const villagers = world.getNearbyEntities(currentPos, 32)
.filter(e => e.type === "minecraft:villager");
if (villagers.length > 0) {
const farthestVillager = villagers.reduce((prev, current) =>
mob.distanceTo(current) > mob.distanceTo(prev) ? current : prev
);
const targetPos = farthestVillager.blockPosition().offset(
(mob.getRandom().nextInt(10) - 5),
0,
(mob.getRandom().nextInt(10) - 5)
);
mob.getNavigation().moveTo(targetPos.x, targetPos.y, targetPos.z, 0.8);
console.log(`${mob.getName()} 开始巡逻村庄边界`);
}
},
mob => {
mob.getNavigation().stop();
console.log(`${mob.getName()} 巡逻结束`);
},
true,
mob => {
// 巡逻中检测威胁
const nearbyHostiles = mob.level.getNearbyEntities(mob.blockPosition(), 16)
.filter(e => e.getMobCategory().toString() === "MONSTER");
if (nearbyHostiles.length > 0) {
const threat = nearbyHostiles[0];
mob.getNavigation().moveTo(threat.getX(), threat.getY(), threat.getZ(), 1.5);
mob.setTarget(threat);
mob.level.playSound(null, mob.blockPosition(),
"minecraft:entity.iron_golem.attack", "neutral", 1.0, 0.8);
}
}
);
});
群体协作
js
EntityJSEvents.addGoalSelectors('minecraft:wolf', e => {
e.customGoal(
'customGoalPack',
2,
mob => {
if (!mob.isTame() || mob.getTarget() == null) return false;
const packMates = mob.level.getEntitiesWithin(mob.boundingBox.inflate(20))
.filter(e => e.type === "minecraft:wolf" && e !== mob && e.isTame());
return packMates.length >= 1;
},
mob => mob.getTarget() != null && mob.getTarget().isAlive(),
false,
mob => {
const target = mob.getTarget();
const packMates = mob.level.getEntitiesWithin(mob.boundingBox.inflate(20))
.filter(e => e.type === "minecraft:wolf" && e !== mob && e.isTame());
packMates.forEach((mate, index) => {
const angle = (index * 2 * Math.PI) / packMates.length;
const radius = 3;
const offsetX = Math.cos(angle) * radius;
const offsetZ = Math.sin(angle) * radius;
mate.getNavigation().moveTo(
target.getX() + offsetX,
target.getY(),
target.getZ() + offsetZ,
1.2
);
mate.setTarget(target);
});
console.log(`${mob.getName()} 发起群体狩猎`);
},
mob => {
console.log(`${mob.getName()} 群体狩猎结束`);
},
false,
mob => {
const target = mob.getTarget();
if (target && mob.distanceTo(target) > 10) {
mob.getNavigation().moveTo(target.getX(), target.getY(), target.getZ(), 1.8);
}
if (mob.getRandom().nextInt(40) === 0) {
mob.level.playSound(null, mob.blockPosition(),
"minecraft:entity.wolf.growl", "neutral", 0.8, 1.0);
}
}
);
});
建造任务
js
EntityJSEvents.addGoalSelectors('minecraft:villager', e => {
e.customGoal(
'customGoalBuild',
1,
mob => {
const data = mob.getPersistentData();
const hasTask = data.contains("ConstructionTask");
const hasTools = mob.getMainHandItem().toString().includes("pickaxe") ||
mob.getMainHandItem().toString().includes("shovel");
return hasTask && hasTools;
},
mob => {
const data = mob.getPersistentData();
return data.contains("ConstructionTask") && !data.getBoolean("TaskCompleted");
},
true,
mob => {
const data = mob.getPersistentData();
const taskType = data.getString("ConstructionTask");
console.log(`${mob.getName()} 开始执行建造任务: ${taskType}`);
let buildPattern = [];
switch (taskType) {
case "road":
for (let i = 0; i < 10; i++) buildPattern.push([i, 0, 0]);
break;
case "wall":
for (let i = 0; i < 5; i++) {
buildPattern.push([i, 0, 0], [i, 0, 4], [0, 0, i], [4, 0, i]);
buildPattern.push([i, 1, 0], [i, 1, 4], [0, 1, i], [4, 1, i]);
}
break;
case "platform":
for (let x = -1; x <= 1; x++) for (let z = -1; z <= 1; z++) buildPattern.push([x, 0, z]);
break;
}
data.putString("BuildPattern", JSON.stringify(buildPattern));
data.putInt("BuildIndex", 0);
data.putLong("TaskStartTime", mob.level.getGameTime());
},
mob => {
const data = mob.getPersistentData();
const taskTime = mob.level.getGameTime() - data.getLong("TaskStartTime");
console.log(`${mob.getName()} 建造任务完成,耗时 ${taskTime} ticks`);
data.remove("ConstructionTask");
data.remove("BuildPattern");
data.remove("BuildIndex");
data.remove("TaskStartTime");
data.putBoolean("TaskCompleted", true);
mob.level.addFreshEntity(
new (Java.loadClass("net.minecraft.world.entity.ExperienceOrb"))(
mob.level, mob.getX(), mob.getY(), mob.getZ(), 10
)
);
},
true,
mob => {
const level = mob.level;
const data = mob.getPersistentData();
const patternStr = data.getString("BuildPattern");
const buildIndex = data.getInt("BuildIndex");
if (!patternStr) return;
const pattern = JSON.parse(patternStr);
if (buildIndex >= pattern.length) {
data.putBoolean("TaskCompleted", true);
return;
}
const [dx, dy, dz] = pattern[buildIndex];
const basePos = mob.blockPosition();
const targetPos = basePos.offset(dx, dy, dz);
const distance = mob.distanceToSqr(targetPos.x + 0.5, targetPos.y, targetPos.z + 0.5);
if (distance > 2) {
mob.getNavigation().moveTo(targetPos.x + 0.5, targetPos.y, targetPos.z + 0.5, 1.0);
return;
}
const currentBlock = level.getBlockState(targetPos);
if (currentBlock.isAir()) {
const taskType = data.getString("ConstructionTask");
let blockToPlace;
switch (taskType) {
case "road": blockToPlace = "minecraft:cobblestone"; break;
case "wall": blockToPlace = "minecraft:stone_bricks"; break;
case "platform": blockToPlace = "minecraft:oak_planks"; break;
default: blockToPlace = "minecraft:cobblestone";
}
if (block) {
level.setBlock(targetPos, blockToPlace, 3);
data.putInt("BuildIndex", buildIndex + 1);
level.addParticle("minecraft:cloud",
targetPos.x + 0.5, targetPos.y + 1, targetPos.z + 0.5, 0, 0.1, 0);
level.playSound(null, targetPos, "minecraft:block.stone.place", "block", 0.8, 1.0);
if (mob.getRandom().nextInt(5) === 0) mob.getNavigation().stop();
}
} else {
data.putInt("BuildIndex", buildIndex + 1);
}
}
);
});
KubeJS 原生 Goal 适合常规 AI 行为,EntityJS 适合高级自定义和复杂 AI。建议根据需求选择合适的 API,避免混用导致维护困难。