mc中复杂的实体逻辑通过Brain
系统实现,为了使用这套系统,我们需要在实体类中覆写以下方法来为这个实体添加一个大脑
1 |
|
然后我们需要使得brain
接管该实体的动作,并实时更新brain
的状态
1 |
|
下面我们来到了最重要的一部分:如何构建这个复杂的大脑。我们可以先看看brainProvider
中这两个方法MarisaBrain.getMemoryTypes()
和MarisaBrain.getSensorTypes()
1 | public static ImmutableList<MemoryModuleType<?>> getMemoryTypes() { |
在这个方法中,我们返回了一个不可变列表,这里记录了该实体所需要的记忆种类,这样我们就可以把对应种类的数据存在Brain中,以供调用。
1 | public static ImmutableList<SensorType<? extends Sensor<? super MarisaEntity>>> getSensorTypes() { |
在这个方法中,我们依然返回了一个不可变列表,它是该实体会使用的监听器,这些监听器每tick执行,来实行操作。比如在SensorType.NEAREST_PLAYERS
的注册中,我们看到是一个PlayerSensor
实例,在其中会把最近的玩家写入MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER
在Brain.tick()
方法中,我们看到了实际上大脑的逻辑中,监听器会先于实体任务执行。
1 | public void tick(ServerLevel level, E entity) { |
下面一部分是大脑中最重要的部分:实体执行的任务。
1 | public static void registerBrainGoals(Brain<MarisaEntity> brain, MarisaEntity marisa) { |
这里我们分别注册了核心目标,默认目标,攻击目标,魔法施放目标。
核心目标是在任何情况下都会执行的目标,默认目标为初始状态下的默认目标,其他则是用来切换状态后执行的目标。
这里注册目标有两种方法
1 | private static void registerIdleGoals(Brain<MarisaEntity> brain) { |
第一种是手动设置每一个目标的优先级,数值低的会优先执行,这里我们构建了一个装有多个Behavior
的不可变列表,然后写入大脑的Activity.IDLE
,这样在大脑处于IDEL状态时,会依次执行这里的行为。
1 | private static void registerAggressiveGoals(Brain<MarisaEntity> brain, MarisaEntity marisaEntity) { |
第二种会自动按列表顺序给予优先级,这里我们设置了攻击行为。
在大脑中写入了Activity
和对应的ImmutableList<? extends BehaviorControl<? super E>>
后,我们设置了大脑的核心活动,设置默认活动并启用。这样,我们就完成了大脑的基础工作。
注意,如果你的Behavior
中向大脑写入了没有在getMemoryTypes
中的内容,会给出报错,需要加入对应的记忆种类。
当然,如果你对原版提供的Activity,SensorType不满足的话,可以自行注册对应的部分并使用,比如
1 | public class ActivityRegistry { |
而对于想自己设置执行的Behavior
时,可以实现BehaviorControl<E>
接口,或者继承自Behavior<E extends LivingEntity>
。
在使用已有的Behavior
或者新的Behavior
时需要注意,在构造方法中需要传入一个Map<MemoryModuleType<?>, MemoryStatus> entryCondition
,必须要符合这里的条件才会执行这个行为。
在MemoryStatus中可以看到所需要的状态
1 | public enum MemoryStatus { |
另外,在写入MemoryTypes时,可以使用setMemoryWithExpiry
方法设置该记忆存储的时间,会在Brain.forgetOutdatedMemories
中被遗忘,或者直接调用Brain.eraseMemory(MemoryModuleType<U> type)
擦除该记忆。
接下来解释在brain.tick()中发生了什么
1 | public void tick(ServerLevel level, E entity) { |
接下来我们解析一下BehaviorControl
接口中的内容
1 | public interface BehaviorControl<E extends LivingEntity> { |
当然麻将为我们提供了一套实现的很好的Behavior<E extends LivingEntity>
。在继承这个类后,可以通过覆写一些方法简单实现目标逻辑
覆写checkExtraStartConditions
方法,来附加一些启动条件,只有返回true时才会设置RUNNING状态
覆写tick
方法,会在RUNNING状态时,每tick执行一次其中的动作
覆写stop
方法,在行为停止后进行的额外动作
覆写canStillUse
方法中,判断是否可以继续执行该动作
在构造函数中,还可以传入minDuration
和maxDuration
,在新建该行为时,会在二者之间随机取一个数值作为该行为的持续时间,到时间会停止该行为。默认的DEFAULT_DURATION
为60
通过合理的覆写,我们可以实现一套条件启动的行为逻辑。然后通过Activity的切换和对MemoryModuleType的操作,实现一套有用的AI。
本文感谢TartaricAcid老师的检查。