聊聊 PaperAPI 提供的自定义生物 AI 系统
本文也发表于我的博客: https://my.minecraft.kim/tech/2021/12/19/post-393/
灵感:https://www.mcbbs.net/thread-1285618-1-1.html(原文发布于 https://izzel.io/2021/12/19/living-things)
本文旨在介绍由 PaperAPI 封装的自定义生物行为(AI)系统(com.destroystokyo.paper.entity.ai),籍由此系统,我们可以在不接触 NMS 的情况下为单个生物自定义其 AI。本文代码基于 Paper-API 1.16.5。
阅读本文可能需要了解原版的生物 AI 机制,如果您不了解这些机制,则可以阅读海螺的 聊聊生物和 AI 文章(即本文灵感)来对这些机制有一些初步的了解
摒弃 NMS
众所周知,与 Forge 不同,Bukkit API 总是希望包揽一切,提供一套稳定的,高度封装的 API 给服务端插件开发者,而不希望开发者基于内部代码进行开发。但因为各种原因,原生 Bukkit API(甚至 Spigot API)提供的封装总是有限,对于一些进阶的操作,我们总是需要访问和调用内部代码来实现我们所需要的操作。自定义生物 AI 就是其中的一个:以往,开发者们往往需要自行继承原来的生物实体类,然后重载 Goal 初始化方法,甚至利用反射来添加,或是擦除生物 AI——但有了 Paper API 后,这一切都会变得简单,且可控。
了解 PaprAPI 封装的自定义生物 AI 系统
大致来看,PaperAPI 封装的自定义生物 AI 系统主要由 Goal<T extends Mob> 和 MobGoals 两部分组成
先来看 Goal 类的构造:
复制代码
如果接触过 Minecraft 原版 Goal 的开发者,相信已经八九不离十的知道这是什么东西了 —— 其作用,甚至结构都和 Goal 差不多,即用于描述生物的一种行为。在这其中,GoalType:
复制代码
和原版的 Goal.Flag 也大差不差,除了多了一个 UNKNOWN_BEHAVIOR 枚举用于映射 Vanilla 的 Goal。
但细心的人也许会发现,Paper API 的 Goal 和原版的 Goal 还是有一些不同:Paper API 的 Goal 是一个泛型接口,同时额外要求实现一个 GoalKey<T> getKey() 方法。
当我们查看 GoalKey<T extends Mob> 的主要部分,我们立即就能明白其作用:
复制代码
它存在的作用就是为了作唯一标识符标识单个 Goal,同时配合 Goal 使用泛型约束这个 Goal 可以被应用到的生物类型。
那么如此以来,我们便摸透了 Goal 的内容,可以开始编写我们自己的自定义 AI了,但是...如何将这些 Goal 应用到我们的生物上呢?这时就需要介绍 ModGoals 了:
复制代码
看完代码我们就会明白,这个所谓的 MobGoals 其实就是一个 Manager,用来方便的为生物获取、添加和删除 Goal,至于这些方法的作用,相信我不用说大家也都知道了。
最后,要想获取 MobGoals 实例,只需调用 Bukkit.getMobGoals() 方法(同 Bukkit.getServer().getMobGoals() 方法)即可。
当然,额外的,我们还可以配合 Pathfinder 和 PaperAPI 提供的其他 API 封装辅助开发自定义生物 AI,在这里对这些手段进行一些简单的介绍:
Pathfinder
即 com.destroystokyo.paper.entity.Pathfinder,可以通过 Mob#getPathfinder() 获取到 Pathfinder 实例。和他的名字一样,Pathfinder 就是一个生物的寻路器,PaperAPI 封装的 Pathfinder 为我们提供了像是 寻路、寻路并按此路径移动、设置生物是否可以开门、设置生物是否可以漂浮在水上 之类的便捷方法,令开发者便捷的使生物寻路和自定义移动行为
PaperAPI 提供的其他 API 封装辅助开发自定义生物 AI
除此之外,PaperAPI 还为我们提供了其他的一些便于辅助开发自定义生物 AI 的方法,例如 Mob#lookAt(@NotNull org.bukkit.Location location) 和 Mob#lookAt(@NotNull Entity entity) 就允许我们命令一个生物望向指定 Location 或指定 Enrtity。
使用 Minecraft 原生生物 AI —— VanillaGoal
但是,如果我想偷懒,希望使用 Minecraft 原生的生物 AI,而不是从零开始自己实现一个全新的 AI,该怎么做呢?
在 VanillaGoal<T extends Mob> 类中,我们可以看到其中已经预先声明了很多原版 Goal 对应的 GoalKey:
复制代码
在这里,我们可以很容易的获得到所有 Minecraft 原版 Goal 对应的 GoalKey,然后通过 MobGoals来方便的从一个生物中删除其中一个 Goal,亦或者从一个生物身上获取一个通用的 Goal,再添加到另一个生物身上。
对于 VanillaGoal 的具体实现,不幸的是,因为各种各样的原因,PaperAPI 本身不对外开放 VanillaGoal 的实现,但是通过导入 Paper 服务端,我们可以窥见 VanillaGoal 的真面目:
复制代码
所以实际上,这个所谓的 VanillaGoal 就是一个 Wrapper,用来封装 NMS 的 PathfinderGoal。在使用了 NMS 的环境时,我们也可以直接通过构造一个 PathfinderGoal,然后使用 PaperVanillaGoal 封装,再使用 MobGoal 添加行为到生物身上,以此省去复杂的反射流程。
(正文完)
最后
很多人因为兼容,或者各种原因,不愿意接触 PaperAPI,但是不可否认的是,PaperAPI 确实基于 SpigotAPI 做了太多的拓展和优化,对于一些不那么在意兼容性(比如自用)的情况下,使用 PaperAPI 进行开发,的确可以有效增加开发效率。
(全文完)
来自群组: Complex Studio
本文也发表于我的博客: https://my.minecraft.kim/tech/2021/12/19/post-393/
灵感:https://www.mcbbs.net/thread-1285618-1-1.html(原文发布于 https://izzel.io/2021/12/19/living-things)
本文旨在介绍由 PaperAPI 封装的自定义生物行为(AI)系统(com.destroystokyo.paper.entity.ai),籍由此系统,我们可以在不接触 NMS 的情况下为单个生物自定义其 AI。本文代码基于 Paper-API 1.16.5。
阅读本文可能需要了解原版的生物 AI 机制,如果您不了解这些机制,则可以阅读海螺的 聊聊生物和 AI 文章(即本文灵感)来对这些机制有一些初步的了解
摒弃 NMS
众所周知,与 Forge 不同,Bukkit API 总是希望包揽一切,提供一套稳定的,高度封装的 API 给服务端插件开发者,而不希望开发者基于内部代码进行开发。但因为各种原因,原生 Bukkit API(甚至 Spigot API)提供的封装总是有限,对于一些进阶的操作,我们总是需要访问和调用内部代码来实现我们所需要的操作。自定义生物 AI 就是其中的一个:以往,开发者们往往需要自行继承原来的生物实体类,然后重载 Goal 初始化方法,甚至利用反射来添加,或是擦除生物 AI——但有了 Paper API 后,这一切都会变得简单,且可控。
了解 PaprAPI 封装的自定义生物 AI 系统
大致来看,PaperAPI 封装的自定义生物 AI 系统主要由 Goal<T extends Mob> 和 MobGoals 两部分组成
先来看 Goal 类的构造:
- package com.destroystokyo.paper.entity.ai;
- import org.jetbrains.annotations.NotNull;
- import java.util.EnumSet;
- import org.bukkit.entity.Mob;
- /**
- * Represents an AI goal of an entity
- */
- public interface Goal<T extends Mob> {
- /**
- * Checks if this goal should be activated
- *
- * [url=home.php?mod=space&uid=491268]@Return[/url] if this goal should be activated
- */
- boolean shouldActivate();
- /**
- * Checks if this goal should stay active, defaults to {[url=home.php?mod=space&uid=41191]@link[/url] Goal#shouldActivate()}
- *
- * @return if this goal should stay active
- */
- default boolean shouldStayActive() {
- return shouldActivate();
- }
- /**
- * Called when this goal gets activated
- */
- default void start() {
- }
- /**
- * Called when this goal gets stopped
- */
- default void stop() {
- }
- /**
- * Called each tick the goal is activated
- */
- default void tick() {
- }
- /**
- * A unique key that identifies this type of goal. Plugins should use their own namespace, not the minecraft
- * namespace. Additionally, this key also specifies to what mobs this goal can be applied to
- *
- * @return the goal key
- */
- @NotNull
- GoalKey<T> getKey();
- /**
- * Returns a list of all applicable flags for this goal.<br>
- *
- * This method is only called on construction.
- *
- * @return the subtypes.
- */
- @NotNull
- EnumSet<GoalType> getTypes();
- }
如果接触过 Minecraft 原版 Goal 的开发者,相信已经八九不离十的知道这是什么东西了 —— 其作用,甚至结构都和 Goal 差不多,即用于描述生物的一种行为。在这其中,GoalType:
- package com.destroystokyo.paper.entity.ai;
- /**
- * Represents the subtype of a goal. Used by minecraft to disable certain types of goals if needed.
- */
- public enum GoalType {
- MOVE,
- LOOK,
- JUMP,
- TARGET,
- /**
- * Used to map vanilla goals, that are a behavior goal but don't have a type set...
- */
- UNKNOWN_BEHAVIOR,
- }
和原版的 Goal.Flag 也大差不差,除了多了一个 UNKNOWN_BEHAVIOR 枚举用于映射 Vanilla 的 Goal。
但细心的人也许会发现,Paper API 的 Goal 和原版的 Goal 还是有一些不同:Paper API 的 Goal 是一个泛型接口,同时额外要求实现一个 GoalKey<T> getKey() 方法。
当我们查看 GoalKey<T extends Mob> 的主要部分,我们立即就能明白其作用:
- package com.destroystokyo.paper.entity.ai;
- import com.google.common.base.Objects;
- import org.jetbrains.annotations.NotNull;
- import java.util.StringJoiner;
- import org.bukkit.NamespacedKey;
- import org.bukkit.entity.Mob;
- /**
- *
- * Used to identify a Goal. Consists of a {@link NamespacedKey} and the type of mob the goal can be applied to
- *
- * @param <T> the type of mob the goal can be applied to
- */
- public class GoalKey<T extends Mob> {
- private final Class<T> entityClass;
- private final NamespacedKey namespacedKey;
- private GoalKey(@NotNull Class<T> entityClass, @NotNull NamespacedKey namespacedKey) {
- this.entityClass = entityClass;
- this.namespacedKey = namespacedKey;
- }
- // Omit getter, equals, hashcode and toString methods...
- @NotNull
- public static <A extends Mob> GoalKey<A> of(@NotNull Class<A> entityClass, @NotNull NamespacedKey namespacedKey) {
- return new GoalKey<>(entityClass, namespacedKey);
- }
- }
它存在的作用就是为了作唯一标识符标识单个 Goal,同时配合 Goal 使用泛型约束这个 Goal 可以被应用到的生物类型。
那么如此以来,我们便摸透了 Goal 的内容,可以开始编写我们自己的自定义 AI了,但是...如何将这些 Goal 应用到我们的生物上呢?这时就需要介绍 ModGoals 了:
- package com.destroystokyo.paper.entity.ai;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
- import java.util.Collection;
- import org.bukkit.entity.Mob;
- /**
- * Represents a part of the "brain" of a mob. It tracks all tasks (running or not), allows adding and removing goals
- */
- public interface MobGoals {
- <T extends Mob> void addGoal(@NotNull T mob, int priority, @NotNull Goal<T> goal);
- <T extends Mob> void removeGoal(@NotNull T mob, @NotNull Goal<T> goal);
- <T extends Mob> void removeAllGoals(@NotNull T mob);
- <T extends Mob> void removeAllGoals(@NotNull T mob, @NotNull GoalType type);
- <T extends Mob> void removeGoal(@NotNull T mob, @NotNull GoalKey<T> key);
- <T extends Mob> boolean hasGoal(@NotNull T mob, @NotNull GoalKey<T> key);
- @Nullable
- <T extends Mob> Goal<T> getGoal(@NotNull T mob, @NotNull GoalKey<T> key);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getGoals(@NotNull T mob, @NotNull GoalKey<T> key);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getAllGoals(@NotNull T mob);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getAllGoals(@NotNull T mob, @NotNull GoalType type);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getAllGoalsWithout(@NotNull T mob, @NotNull GoalType type);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getRunningGoals(@NotNull T mob);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getRunningGoals(@NotNull T mob, @NotNull GoalType type);
- @NotNull
- <T extends Mob> Collection<Goal<T>> getRunningGoalsWithout(@NotNull T mob, @NotNull GoalType type);
- }
看完代码我们就会明白,这个所谓的 MobGoals 其实就是一个 Manager,用来方便的为生物获取、添加和删除 Goal,至于这些方法的作用,相信我不用说大家也都知道了。
最后,要想获取 MobGoals 实例,只需调用 Bukkit.getMobGoals() 方法(同 Bukkit.getServer().getMobGoals() 方法)即可。
当然,额外的,我们还可以配合 Pathfinder 和 PaperAPI 提供的其他 API 封装辅助开发自定义生物 AI,在这里对这些手段进行一些简单的介绍:
Pathfinder
即 com.destroystokyo.paper.entity.Pathfinder,可以通过 Mob#getPathfinder() 获取到 Pathfinder 实例。和他的名字一样,Pathfinder 就是一个生物的寻路器,PaperAPI 封装的 Pathfinder 为我们提供了像是 寻路、寻路并按此路径移动、设置生物是否可以开门、设置生物是否可以漂浮在水上 之类的便捷方法,令开发者便捷的使生物寻路和自定义移动行为
PaperAPI 提供的其他 API 封装辅助开发自定义生物 AI
除此之外,PaperAPI 还为我们提供了其他的一些便于辅助开发自定义生物 AI 的方法,例如 Mob#lookAt(@NotNull org.bukkit.Location location) 和 Mob#lookAt(@NotNull Entity entity) 就允许我们命令一个生物望向指定 Location 或指定 Enrtity。
使用 Minecraft 原生生物 AI —— VanillaGoal
但是,如果我想偷懒,希望使用 Minecraft 原生的生物 AI,而不是从零开始自己实现一个全新的 AI,该怎么做呢?
在 VanillaGoal<T extends Mob> 类中,我们可以看到其中已经预先声明了很多原版 Goal 对应的 GoalKey:
- package com.destroystokyo.paper.entity.ai;
- import com.destroystokyo.paper.entity.RangedEntity;
- import org.bukkit.NamespacedKey;
- import org.bukkit.entity.*;
- /**
- * Represents a vanilla goal. Plugins should never implement this.<br>
- * Generated by VanillaPathfinderTest in paper-server
- */
- public interface VanillaGoal<T extends Mob> extends Goal<T> {
- GoalKey<Bee> BEE_ATTACK = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_attack"));
- GoalKey<Bee> BEE_BECOME_ANGRY = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_become_angry"));
- GoalKey<Bee> BEE_ENTER_HIVE = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_enter_hive"));
- GoalKey<Bee> BEE_GO_TO_HIVE = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_go_to_hive"));
- GoalKey<Bee> BEE_GO_TO_KNOWN_FLOWER = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_go_to_known_flower"));
- GoalKey<Bee> BEE_GROW_CROP = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_grow_crop"));
- GoalKey<Bee> BEE_HURT_BY_OTHER = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_hurt_by_other"));
- GoalKey<Bee> BEE_LOCATE_HIVE = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_locate_hive"));
- GoalKey<Bee> BEE_POLLINATE = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_pollinate"));
- GoalKey<Bee> BEE_WANDER = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_wander"));
- GoalKey<Blaze> BLAZE_FIREBALL = GoalKey.of(Blaze.class, NamespacedKey.minecraft("blaze_fireball"));
- GoalKey<Cat> TEMPT_CHANCE = GoalKey.of(Cat.class, NamespacedKey.minecraft("tempt_chance"));
- GoalKey<Cat> CAT_AVOID_ENTITY = GoalKey.of(Cat.class, NamespacedKey.minecraft("cat_avoid_entity"));
- GoalKey<Cat> CAT_RELAX_ON_OWNER = GoalKey.of(Cat.class, NamespacedKey.minecraft("cat_relax_on_owner"));
- GoalKey<Dolphin> DOLPHIN_SWIM_TO_TREASURE = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("dolphin_swim_to_treasure"));
- GoalKey<Dolphin> DOLPHIN_SWIM_WITH_PLAYER = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("dolphin_swim_with_player"));
- GoalKey<Dolphin> DOLPHIN_PLAY_WITH_ITEMS = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("dolphin_play_with_items"));
- GoalKey<Drowned> DROWNED_ATTACK = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack"));
- GoalKey<Drowned> DROWNED_GOTO_BEACH = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_goto_beach"));
- GoalKey<Creature> DROWNED_GOTO_WATER = GoalKey.of(Creature.class, NamespacedKey.minecraft("drowned_goto_water"));
- GoalKey<Drowned> DROWNED_SWIM_UP = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_swim_up"));
- GoalKey<RangedEntity> DROWNED_TRIDENT_ATTACK = GoalKey.of(RangedEntity.class, NamespacedKey.minecraft("drowned_trident_attack"));
- GoalKey<Enderman> ENDERMAN_PICKUP_BLOCK = GoalKey.of(Enderman.class, NamespacedKey.minecraft("enderman_pickup_block"));
- GoalKey<Enderman> ENDERMAN_PLACE_BLOCK = GoalKey.of(Enderman.class, NamespacedKey.minecraft("enderman_place_block"));
- GoalKey<Enderman> PLAYER_WHO_LOOKED_AT_TARGET = GoalKey.of(Enderman.class, NamespacedKey.minecraft("player_who_looked_at_target"));
- GoalKey<Enderman> ENDERMAN_FREEZE_WHEN_LOOKED_AT = GoalKey.of(Enderman.class, NamespacedKey.minecraft("enderman_freeze_when_looked_at"));
- GoalKey<Evoker> EVOKER_ATTACK_SPELL = GoalKey.of(Evoker.class, NamespacedKey.minecraft("evoker_attack_spell"));
- GoalKey<Evoker> EVOKER_CAST_SPELL = GoalKey.of(Evoker.class, NamespacedKey.minecraft("evoker_cast_spell"));
- GoalKey<Evoker> EVOKER_SUMMON_SPELL = GoalKey.of(Evoker.class, NamespacedKey.minecraft("evoker_summon_spell"));
- GoalKey<Evoker> EVOKER_WOLOLO_SPELL = GoalKey.of(Evoker.class, NamespacedKey.minecraft("evoker_wololo_spell"));
- GoalKey<Fish> FISH_SWIM = GoalKey.of(Fish.class, NamespacedKey.minecraft("fish_swim"));
- GoalKey<Fox> FOX_DEFEND_TRUSTED = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_defend_trusted"));
- GoalKey<Fox> FOX_FACEPLANT = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_faceplant"));
- GoalKey<Fox> FOX_BREED = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_breed"));
- GoalKey<Fox> FOX_EAT_BERRIES = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_eat_berries"));
- GoalKey<Fox> FOX_FLOAT = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_float"));
- GoalKey<Fox> FOX_FOLLOW_PARENT = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_follow_parent"));
- GoalKey<Fox> FOX_LOOK_AT_PLAYER = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_look_at_player"));
- GoalKey<Fox> FOX_MELEE_ATTACK = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_melee_attack"));
- GoalKey<Fox> FOX_PANIC = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_panic"));
- GoalKey<Fox> FOX_PERCH_AND_SEARCH = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_perch_and_search"));
- GoalKey<Fox> FOX_POUNCE = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_pounce"));
- GoalKey<Fox> FOX_SEARCH_FOR_ITEMS = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_search_for_items"));
- GoalKey<Fox> FOX_SLEEP = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_sleep"));
- GoalKey<Fox> FOX_STROLL_THROUGH_VILLAGE = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_stroll_through_village"));
- GoalKey<Fox> FOX_SEEK_SHELTER = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_seek_shelter"));
- GoalKey<Fox> FOX_STALK_PREY = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_stalk_prey"));
- GoalKey<Ghast> GHAST_ATTACK_TARGET = GoalKey.of(Ghast.class, NamespacedKey.minecraft("ghast_attack_target"));
- GoalKey<Ghast> GHAST_IDLE_MOVE = GoalKey.of(Ghast.class, NamespacedKey.minecraft("ghast_idle_move"));
- GoalKey<Ghast> GHAST_MOVE_TOWARDS_TARGET = GoalKey.of(Ghast.class, NamespacedKey.minecraft("ghast_move_towards_target"));
- GoalKey<Guardian> GUARDIAN_ATTACK = GoalKey.of(Guardian.class, NamespacedKey.minecraft("guardian_attack"));
- GoalKey<Illager> RAIDER_OPEN_DOOR = GoalKey.of(Illager.class, NamespacedKey.minecraft("raider_open_door"));
- GoalKey<Illusioner> ILLUSIONER_BLINDNESS_SPELL = GoalKey.of(Illusioner.class, NamespacedKey.minecraft("illusioner_blindness_spell"));
- GoalKey<Illusioner> ILLUSIONER_MIRROR_SPELL = GoalKey.of(Illusioner.class, NamespacedKey.minecraft("illusioner_mirror_spell"));
- GoalKey<Spellcaster> SPELLCASTER_CAST_SPELL = GoalKey.of(Spellcaster.class, NamespacedKey.minecraft("spellcaster_cast_spell"));
- // ......
在这里,我们可以很容易的获得到所有 Minecraft 原版 Goal 对应的 GoalKey,然后通过 MobGoals来方便的从一个生物中删除其中一个 Goal,亦或者从一个生物身上获取一个通用的 Goal,再添加到另一个生物身上。
对于 VanillaGoal 的具体实现,不幸的是,因为各种各样的原因,PaperAPI 本身不对外开放 VanillaGoal 的实现,但是通过导入 Paper 服务端,我们可以窥见 VanillaGoal 的真面目:
- //
- // Source code recreated from a .class file by IntelliJ IDEA
- // (powered by FernFlower decompiler)
- //
- package com.destroystokyo.paper.entity.ai;
- import java.util.EnumSet;
- import net.minecraft.server.v1_16_R3.PathfinderGoal;
- import org.bukkit.entity.Mob;
- public class PaperVanillaGoal<T extends Mob> implements VanillaGoal<T> {
- private final PathfinderGoal handle;
- private final GoalKey<T> key;
- private final EnumSet<GoalType> types;
- public PaperVanillaGoal(PathfinderGoal handle) {
- this.handle = handle;
- this.key = MobGoalHelper.getKey(handle.getClass());
- this.types = MobGoalHelper.vanillaToPaper(handle.getGoalTypes());
- }
- public PathfinderGoal getHandle() {
- return this.handle;
- }
- public boolean shouldActivate() {
- return this.handle.shouldActivate2();
- }
- public boolean shouldStayActive() {
- return this.handle.shouldStayActive2();
- }
- public void start() {
- this.handle.start();
- }
- public void stop() {
- this.handle.onTaskReset();
- }
- public void tick() {
- this.handle.tick();
- }
- public GoalKey<T> getKey() {
- return this.key;
- }
- public EnumSet<GoalType> getTypes() {
- return this.types;
- }
- }
所以实际上,这个所谓的 VanillaGoal 就是一个 Wrapper,用来封装 NMS 的 PathfinderGoal。在使用了 NMS 的环境时,我们也可以直接通过构造一个 PathfinderGoal,然后使用 PaperVanillaGoal 封装,再使用 MobGoal 添加行为到生物身上,以此省去复杂的反射流程。
(正文完)
最后
很多人因为兼容,或者各种原因,不愿意接触 PaperAPI,但是不可否认的是,PaperAPI 确实基于 SpigotAPI 做了太多的拓展和优化,对于一些不那么在意兼容性(比如自用)的情况下,使用 PaperAPI 进行开发,的确可以有效增加开发效率。
(全文完)
来自群组: Complex Studio
这个API就有些尴尬。。。想要理解AI运行逻辑,搞这方面开发,基本都要把MC源代码的AI实现了解个七七八八。
好。
看得我都想学Paper了(过几天就学。。。(
看得我都想学Paper了(过几天就学。。。(
emmmm羡慕我也想学
每日回复帮您顶贴呱呱呱呱呱呱呱呱
众所周知,与 Forge 不同,Bukkit API 总是希望包揽一切,提供一套稳定的,高度封装的 API 给服务端插件开发者,而不希望开发者基于内部代码进行开发。但因为各种原因,原生 Bukkit API(甚至 Spigot API)提供的封装总是有限,对于一些进阶的操作,我们总是需要访问和调用内部代码来实现我们所需要的操作。自定义生物 AI 就是其中的一个:以往,开发者们往往需要自行继承原来的生物实体类,然后重载 Goal 初始化方法,甚至利用反射来添加,或是擦除生物 AI——但有了 Paper API 后,这一切都会变得简单,且可控。