• 我的世界Bukkit服务器插件开发教程(十)实体


    十、实体

    这一章原本被定在第五章,但由于一些原因(其实就是懒啊)拖了五章。


    什么是实体

    实体是Minecraft中动态的、移动中的对象。牛牛君是实体、可爱的小僵是实体、摇头的村民是个实体。

    实体分为两类,一类为有生命实体所有活性实体都继承LivingEntity,比如村民、牛、兔子等等。

    另一类为无生命实体,如箭、船、盔甲架等,没有生命。

    除了玩家(EntityHuman)外,几乎所有实体都由 AI 控制


    1.实体

    1.1.创建一个实体

    我们假设需要一只可爱的小僵在我们身边。

    先创建一个 Player 对象。

    Player p = (Player) sender;
    
    • 1

    sender 是发送指令的人,将 sender 强制转换成 Player 类型并赋值于p
    sender 是什么?赶快到以前翻翻温故吧!)

    p.getWorld().spawnEntity(p.getLocation(), EntityType.ZOMBIE);
    
    • 1

    这行代码,首先获取玩家当前的世界(getWorld),然后将这个世界的僵尸实体传送到玩家所在位置

    现在,你应该看到一只僵尸在你身边了。


    1.2.更改名字
    首先创建一个Zombie对象。

    Zombie zombie = (Zombie) p.getWorld().spawnEntity(p.getLocation(), EntityType.ZOMBIE);
    
    • 1

    然后就可以更改名字了:

    //设置实体名字
    zombie.setCustomName("守卫者");
    
    • 1
    • 2

    可是你会发现名字没有显示,还需设置名字可以显示:

    //设置实体名字可见
    zombie.setCustomNameVisible(true);
    
    • 1
    • 2

    这些只是小菜一碟,本章最难的部分在于自定义实体。


    2.自定义实体

    自己创建一个实体是不太可能的,亲这边建议你转 Mod 开发,插件开发还是要看服务器的心情的呢。

    所谓插件,无非是在原版的基础上加点特色,所以我们无法增加一个新物品,增加一个新实体,增加一个新方块。

    那么,也只能在现有的实体上做做更改了。


    1.1.自定义实体行为/重构AI(PathFinderGoal)

    现有的实体已经能满足我们的大部分要求了,我们没有必要自己创建一个实体,可以重写一个实体啊。

    现在我们需要一头牛,能吃钻石,并且能生产钻石块。

    public class MyCustomCowEntity extends EntityCow {
    	public MyCustomCowEntity(EntityTypes<? extends EntityCow> entitytypes, World world) {
            super(entitytypes, world);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在上面的代码中,我们继承了EntityCow类(当然这是继承 NMS 下的类),现在就可以开始进行重写了。

    我们写一个构造方法,传入牛生成的位置。

    public MyCustomCowEntity(Location location) {
    	this(EntityTypes.COW, ((CraftWorld) location.getWorld()).getHandle());
    	//更改牛生成的位置
    	this.setPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后重写一下initPathfinder方法:

    initPathfinder方法是初始化AI处理器的,牛原本的处理器我们不要了,自己重写一下这个方法,加入自己想要的处理器。

    @Override
    public void initPathfinder() {
    	this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
    	this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们共添加了两个 AI处理器,NMS 中有很多这样的处理器。如果没有你想要的处理器,也可以自己写一个。

    每个处理器都有自己的优先级,用 0~8 这几个数字表示。数字越小代表优先级越高,数字越高代表优先级越低。


    1.2.交互

    当我们拿着小麦右键牛,牛会吃这棵小麦。进而概括成「玩家右键实体」这一事件。

    public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand);
    
    • 1

    上面的b方法就是处理用来「右键实体」的。来看这方法的两个参数。

    entityhuman代表玩家,enumhand就代表玩家的手。显然我们可以得到玩家手上的物品。

    //获取玩家手上的物品
    ItemStack itemStack = entityhuman.b(enumhand);
    
    • 1
    • 2

    我们还需判断这个物品是什么,按照甲方的需求,应该当玩家手中物品为钻石才行。

    我们规定需要吃完一组钻石才能生产钻石块。那么就开个变量记录吃了多个钻石。

    private int food = 0;
    
    //是不是钻石且牛已经长大
    if(itemStack.getItem() == Items.DIAMOND && !this.isBaby()) {
    	//钻石吃的数量+1
    	this.food += 1;
    }
    
    //由于 food 是私有的,自己写两个函数用于获取和更改 food
    public int getFood() { return this.food; }
    public void setFood(int food) { this.food = food; }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最后也就可以收尾了,让玩家手中的钻石数量减去 1就可以了。

    this.a(entityhuman, itemStack);//物品数量减去1
    return EnumInteractionResult.a(this.world.isClientSide);//判断是否是客户端
    
    • 1
    • 2

    1.3.重写处理器

    现有的处理器不能满足甲方的需求,那么我们需要自己写一个处理器。

    一样的,新建一个继承PathfinderGoal的类。

    public class PathfinderGoalDiamond extends PathfinderGoal {
    	
    }
    
    • 1
    • 2
    • 3

    开始之前,我们要想好自己这个处理器应该做什么。这里我就写牛的位置会出现一个钻石块。

    照例,先写好构造函数:

    private EntityCow cow;
    
    public PathfinderGoalDiamond(EntityCow cow) {
    	this.cow = cow;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们传入一个Cow对象,表明这个对象将由我们这个AI处理器所控制

    于是,这个对象的一切我们就都知道了,下面的也就好写了。

    @Override
    public boolean a();
    @Override
    public boolean b();
    @Override
    public void c();
    @Override
    public void d();
    @Override
    public void e();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    看到这五个函数,发现它们都已被混淆,它们原来的函数名分别是:

    @Override
    public boolean shouldExecute();
    @Override
    public boolean continueExecuting();
    @Override
    public void startExecuting();
    @Override
    public void resetTask();
    @Override
    public void updateTask();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    顾名思义,首先 Bukkit 会去调用a方法,判断是否执行。如果可以则调用c方法开始执行,之后的每 1 tick(0.05s) 中,都会去调用一次b方法,判断是否继续执行。如果返回真就调用e方法,表示更新Task,如果返回假就调用d方法,清除Task恢复原样。

    需要注意,我们的处理器要有一定概率的去执行,如果没有概率你可以完全写一个BukkitRunnable,这就多此一举了。所以,在a方法中,我们需要设置概率。嗯……就先来个五分之一的概率吧(毕竟喂了这么多钻石)。

    也要注意,我们还需要判断玩家是否喂满一组钻石。

    //是否是白天
    if(!this.cow.getWorld().isDay()) {
    	return false;
    }
    //是否吃了一组钻石
    if(this.cow.getFood() < 64) {
    	return false;
    }
    //五分之一的概率触发
    if(this.cow.getRandom().nextInt(5) != 0) {
    	return false;
    }
    //消耗吃完的一组钻石
    this.cow.setFood(0);
    //以上都满足,可以执行该处理器
    return true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们设置了三个条件,第一个条件是需要当前时间处于白天,第三个这就是我们所说的概率触发。

    假设三个条件都可以满足,那么吃完的那一组钻石需要消耗掉啊。

    紧接着就是调用c方法,开始执行了,生成钻石块的时间设定为 600 ticks,也就是半分钟。

    this.luckyTime = 600;
    //设置牛的所处位置为钻石块
    this.cow.getBukkitEntity().getLocation().getBlock().setType(Material.DIAMOND_BLOCK);
    
    • 1
    • 2
    • 3

    之后的每 1tick 中,都会去调用b方法,判断是否继续执行,那么每次只需判断luckyTime是否大于 0 即可。

    return this.luckyTime > 0;
    
    • 1

    如果这个条件也满足了,就执行e方法更新我们的Task,每次luckyTime都需要减去1,同时也别忘了在脚底下生成钻石块!

    this.luckyTime -= 1;
    this.cow.getBukkitEntity().getLocation().getBlock().setType(Material.DIAMOND_BLOCK);
    
    • 1
    • 2

    如果b方法返回假,表示条件不满足,此时应当清空我们的Task,使它变回原来的正常的牛牛,好像也不需要做些什么,就让luckyTime设为-1吧。

    this.luckyTime = -1;
    
    • 1

    一个处理器就这么完成了。


    1.4.大功告成?

    恭喜!这是我们自己写的第一个实体。

    实际上,我们的“钻石牛”还只是冰山一角,实体还有很多用法,只不过没有提及。你应该去翻阅反编译后的代码,虽然 NMS 已经是混淆过的,但不影响我们的整体阅读啊。


    上一篇:我的世界Bukkit服务器插件开发教程(九)NMS
    下一篇:我的世界Bukkit服务器插件开发教程(十一)粒子、药水效果与音效

  • 相关阅读:
    Java进阶03 IO基础
    [附源码]计算机毕业设计springboot演唱会门票售卖系统
    vscode交叉编译cmake工程,toolchains设置
    操作系统之零拷贝
    Python调用ChatGPT API使用国内中转key 修改接口教程
    数学中的上确界和下确界
    后端接口并行调用方案
    netty系列之:来,手把手教你使用netty搭建一个DNS tcp服务器
    web前端期末大作业:旅游网页设计与实现——个人旅游博客(4页)HTML+CSS
    代码随想录 | Day 58 - LeetCode 739. 每日温度、496. 下一个更大元素 I
  • 原文地址:https://blog.csdn.net/weixin_45445598/article/details/118874975