Skip to content
KubeJS Entity Goal

实体目标(Goal)系统

基础说明

AI 目标(Goal)系统是 Minecraft 实体 AI 的核心,定义了实体的行为优先级与决策逻辑。每个生物的行为都由一系列目标组成,这些目标按优先级执行,形成复杂而自然的行为模式。

KubeJS 通过 Java 反射机制提供了对实体 AI 目标的完整控制能力,让你可以创建完全自定义的实体行为。

架构

Entity
└── Mob
    ├── goalSelector(行为目标选择器)
    └── targetSelector(攻击目标选择器)

两种目标选择器的区别:

  • goalSelector:控制实体的日常行为,如移动、观察、浮水、巡逻等基础行为
  • targetSelector:专门处理敌对行为,如选择攻击目标、反击、追踪敌人等

目标执行机制

目标生命周期

每个 Goal 都遵循以下执行流程:

生命周期详解:

  1. 评估阶段:系统调用 canUse() 检查目标是否应该激活
  2. 启动阶段:如果可以使用,调用 start() 初始化目标状态
  3. 执行阶段:持续调用 tick() 执行目标逻辑,同时检查 canContinueToUse()
  4. 中断检测:检查是否被更高优先级的目标中断(基于 isInterruptable()
  5. 结束阶段:调用 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功能,如 customGoalPatrolcustomGoalPackcustomGoalBuild
  • 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,避免混用导致维护困难。