Minecraft Forge Mod 开发笔记 ---- Brain 系统 (WIP)

本文介绍了Minecraft 1.16.5版本Forge Mod中实体的Brain AI系统,该系统包含Memory、Sensor和状态机三部分,为猪灵、村民和犹猪兽提供复杂行为。Sensor获取环境数据存储到Memory,状态机通过Activity和Task执行行为。Core Activity作为核心活动,始终保持激活,用于实现如行走、游泳等习惯动作,可能取代了旧版Goal系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概况

Brain 系统是实体的高级 AI 系统, 目前 (1.16.5) 仅有猪灵, 村民, 犹猪兽使用此套系统. 这套系统分为三大部分: Memory, Sensor, 和状态机. 和之前的 Goal 系统相比 (较早加入的实体比如猪, 僵尸等), Brain 系统可以给予生物更复杂的行为, 从开发角度上讲, 也实现了 AI 中三个模块的解耦, 减少了开发的复杂度.

数据获取
数据
数据
编辑
状态机
Memory
Sensor

Sensor 负责从环境中获取数据, 并存储到 Memory 中.

状态机

状态机由 Activity 和其下属的一系列具有优先级先后的 Task 组成. 在选定 Activity 之后, Activity 会自动选择下属的最高优先级的可执行的 Task 执行.

在 Task 执行过程中可以切换 Activity. 切换时一般需要清除上一个 Activity 相关的 Memory.

状态机中有一类特殊的 Activity, 叫做 Core Activity (核心活动). 核心活动在任意时刻都处于激活状态. 一般用于类似于习惯动作一类的 AI, 例如: 走向目标点, 游泳, 村民关门, 看向玩家, 村民躲避劫掠者, 猪灵捡东西, 生气等. 从功能上来说, 可能是为了彻底替代旧版的 Goal 系统的附加设计.

Brain
核心活动(一直启用)
CoreBehavior1
CoreBehavior2
CoreBehavior3
CoreBehavior4
IDLE
Behavior1
Behavior2
Activity2
Behavior3
Activity3
Behavior4

工作流程简析

时隔多年我更新啦!这次是基于 1.20.1 的 Minecraft 代码进行分析。

创建 Brain

现在 Brain 是内嵌到 LivingEntity 里面的,也就是所有的生物都有 “大脑”。实体在生成时会调用自己的 LivingEntity::makeBrain(Dynamic<?>) 函数来给自己初始化一颗大脑。原版新出的生物基本都是用的 Brain 系统。

大脑有 3 个组件:Sensor<? super E>MemoryModuleType<?>Activity,这里 <E extends LivingEntity>

SensorMemoryBrain 实例化后就固定的,Activity 是在 Brain 实例化后添加的。一般使用 Brain.provider(Collection<MemoryModuleType<?>>, Collection<SensorType<? extends Sensor<? super E>>>) 创建一个 Brain.Provider<E> 的实例,再由这个实例为每个实体生成一个 Brain<E>

添加 Activity 和 Behavior 时,需要为每个 Behavior 赋予一个优先级(int)。优先级数字越小,越先被尝试执行。在调用 addActivity 及其衍生接口添加时的参数如下:(伪代码)

activity: Activity,
tasks: Map {
	优先级1:Behavior1,
	优先级2:Behavior2,
	……
},
condition:Set [
	Pair (MemoryModuleType1: MemoryStatus1),
	Pair (MemoryModuleType2: MemoryStatus2)
	……
] default = [],
toEraseOnStop: Set [
	MemoryModule1, MemoryModule2, ……
] default = []

condition 是启动这个 activity 的条件,只有当 Brain 中所有记忆都符合 condition 的条件后才会激活这个 activity。

toEraseOnStop 是这个 activity 终止时,需要擦除的记忆

在调用后,tasks 的索引会被转换成红黑树 TreeMap<Integer, Map<Activity, BehaviorController>> 中,树的变量名是 availableBehaviorsByPriority。第一个 key (type: Integer) 是优先级。

组件的使用、自定义和注册

MemoryModuleType<?>

古希腊 今MC 掌管记忆的神

MemoryModuleType<T> 中的 T 代表记忆的数据类型,不过一般没有为其创建子类的必要。自定义时直接 new 一个就行。
构造函数 MemoryModuleType<T>(Optional<Codec<T>>) 唯一的参数是填写其数据类型对应的序列化器。记忆项目的名字是其注册表中的 ID。

如果想自定义一个 MemoryModuleType,那么需要将其在注册表中注册。具体对应的 ResourceKeynet.minecraft.core.registries.Registries.MEMORY_MODULE_TYPE

Sensor<E>SensorType<Sensor<E>>
  • Sensor<E> 是探测器类,用于实体从外界获取信息并记录到记忆中。自定义一般需要继承基类并实现相应方法。E 是可以使用这个探测器的实体类型。例如 class MySensor extends Sensor<TamableAnimal>
    • private final int scanRate 扫描间隔 tick 数,默认为 20(=1秒),可以用构造函数设置。
    • protected abstract void doTick(ServerLevel level, E entity) 执行 Sensor 逻辑的方法,每 scanRate 刻由 tick 函数调用一次
      • level 是执行时生物所在的维度
      • entity 是执行这个探测器逻辑的生物
      • 一般用 entity.getBrain().setMemory(MemoryModuleType<U>, Optional<U>) 来修改记忆
      • 有时效的记忆用 Brain::setMemoryWithExpiry 来设定,最后一个 long 参数是记忆有效期,过期删除该记忆
    • public abstract Set<MemoryModuleType<?>> requires() 返回这个探测器需要用到的所有记忆类型,在实例化 Brain 时会自动添加到记忆列表中。
  • SensorType 是探测器类型的类,用于注册探测器类型。与 Sensor 的关系类似于 EntityTypeEntity 的关系。注册时需要提供一个 Supplier<Sensor<E>> 以实例化一个 SensorType<? extends Sensor<? super E>>。注册 ResourceKeynet.minecraft.core.registries.Registries.SENSOR_TYPE

Activity

Activity 代表一类动作,需要注册,仅保存一个名字。可以理解为状态机中的“状态”。

Behavior

Behavior 则类似原来的 Goal 系统,是实际执行各种活动的类,不需要注册。

tick 流程

Brain<E>::tick() 函数

这个函数由使用了 Brain 系统的各个生物的 customServerAiStep()调用,是 Brain 的基础逻辑入口,用来唤起各个 Behavior

    public void tick(ServerLevel level, E entity) {
        this.forgetOutdatedMemories();
        this.tickSensors(level, entity);
        this.startEachNonRunningBehavior(level, entity);
        this.tickEachRunningBehavior(level, entity);
    }
  1. forgetOutdataedMemories:清空所有过期的记忆
  2. tickSensors:调用所有 Sensortick 方法以刷新记忆
  3. startEachNonRunningBehavior:尝试启动所有属于正在进行的 Activity 的 Behavior
  4. tickEachRunningBehavior:执行正在进行的 Behavior

可以看到这里并没有调整 Action 开关的相关代码。原版的实现中,各个生物的 Action 之间的转换是由各自生物的 Ai 类独立控制的,例如蝾螈的 AxolotlAi::updateActivity(Axolotl):

    public static void updateActivity(Axolotl axolotl) {
        Brain<Axolotl> brain = axolotl.getBrain();
        Activity activity = (Activity)brain.getActiveNonCoreActivity().orElse((Object)null);
        if (activity != Activity.PLAY_DEAD) {
            brain.setActiveActivityToFirstValid(ImmutableList.of(Activity.PLAY_DEAD, Activity.FIGHT, Activity.IDLE));
            if (activity == Activity.FIGHT && brain.getActiveNonCoreActivity().orElse((Object)null) != Activity.FIGHT) {
                brain.setMemoryWithExpiry(MemoryModuleType.HAS_HUNTING_COOLDOWN, true, 2400L);
            }
        }
    }

就是在 Axolotl::customServerAiStep() 中调用的:

    protected void customServerAiStep() {
        this.level().getProfiler().push("axolotlBrain");
        this.getBrain().tick((ServerLevel)this.level(), this);
        this.level().getProfiler().pop();
        this.level().getProfiler().push("axolotlActivityUpdate");
        AxolotlAi.updateActivity(this);
        this.level().getProfiler().pop();
        if (!this.isNoAi()) {
            Optional<Integer> optional = this.getBrain().getMemory(MemoryModuleType.PLAY_DEAD_TICKS);
            this.setPlayingDead(optional.isPresent() && (Integer)optional.get() > 0);
        }
    }

操作状态(Activity)

操作状态相关的函数有好几个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值