我的世界1.20.1模组开发---12.实战练习(制作一个简单的矿物探测器)
介绍
前面介绍了这么多理论性的东西,比如环境配置、添加物品、添加方块、数据生成、自定义装备和武器等等,这次我们就来真正的实战一下。从最基础的素材绘制、物品功能的定制以及配方的定义等等,全都是我们自己一个人来。
言归正传,我们来具体描述一下我们的矿物探测器需要有的功能,相信玩过模组生存的老方块人应该或多或少的玩到过一些模组里面有类似的东西,当我们拿到探测器时对着一个方框点击右键,会向下检测下面是否有特定的矿石,如果找到就告诉玩家,找不到就说没找到,同时我们右键成功后还要损坏一点耐久,最好是每次使用后都会有一个冷却时间,间隔一段时间后才能再次使用,避免玩家瞎点。
我们来大概梳理一下具体的功能:
- 对着方块点击右键,可以检测下面一定距离内是否有矿石,并将结果返回给玩家。
- 右键使用成功后损坏一点耐久,当耐久为0时物品损坏。
- 右键使用后给物品一个冷却时间,不能一直连续使用。
大致的功能明确之后,我们就来说明一下如何实现,可见我们最本质的工作就是拿着物品对着方块成功右键,之前提到过在Item
类里面有一个叫做useOn
的方法,当物品右键方块时就会执行这个方法,方法也会给我们一个上下文参数,里面包含了所有我们可能用到的信息,我们就需要用这个函数所提供的参数信息来解决我们的问题。目标明确之后我们就可以开始进行我都矿物探测器的制作了。
贴图准备
在游戏中,任何物品都是由一个模型以及他的贴图组成的,模型就是决定了这个物品的形状,贴图则决定了物品的样子。就好比游戏中的模型(model)是我们人体的骨骼,决定了我们人体的高度和形状;而贴图(texture)就是我们的皮肤,让我们有各种各样漂亮的外貌,可以区分每个不同的人。由于我们这只是一个普通的物品,游戏中为我们提供了一个通用·物品模型,我们直接使用那个作为我们的模型即可,之前已经被封装成方法使用了。
贴图我们一般都是准备一个16x16(更高分辨率的也可以)像素的透明背景的图片,注意背景一一定要是透明的,我们可以去网上找素材,也可以自己手动画。我这里就用PS简单画了一个。比较是个纯理工男,没有美术功底,审美也比较差,画的稍微难看一点。不过这就是模组开发的魅力所在吧,不论我们是不是专业的,只要我们想,自己画的垃圾东西也能放到游戏中去。如果没有灵感的话也欢迎模仿我这个画 x.x 。画好保存好导出为png格式放到一个我们能找到的目录留着等会用就行了。
代码
分析和贴图都准备好了,下面就可以开始写代码了。
首先在我们的item/custom目录先新建一个OreDetecotr
类,这个类要继承Item类,实现一些必须要重写的方法后,我们直接重写父类的useOn方法即可。
因为我们的探测器有一个最大检测距离,当下方没有矿石或者超过了最大检测距离,都会找不到矿石,所以我们在创建物品的时候需要传入一个最大检测距离参数(以格为单位)。又因为我们还想让物品有一个使用后的冷却时间,所以再创建一个属性表示冷却时间(以tick为单位)。
useOn方法会返回这次右键操作的执行结果,如果执行成功我们就返回SUCCESS,失败就返回FAIL,这个返回值会告诉游戏要不要继续执行下一步的操作。进一步的详细描述在代码的注释部分。
public class OreDetector extends Item {
private final int detectionDistance;//检测距离
private final int COOLDOWN_TICKS=16;//冷却时间(tick)
//修改一下构造函数,我们还需要传入检测距离(以格为单位)
public OreDetector(Properties pProperties,int detectionDistance) {
super(pProperties);
this.detectionDistance=detectionDistance;
}
//重写useOn方法
@Override
public InteractionResult useOn(UseOnContext pContext) {
Level level = pContext.getLevel();
//逻辑在服务端执行
if(level.isClientSide()){
return InteractionResult.FAIL;
}
//返回处理结果
return process(pContext);
}
private InteractionResult process(UseOnContext pContext){
Level level = pContext.getLevel();
Player player=pContext.getPlayer();
//没有冷却时间才会进去执行
if (!player.getCooldowns().isOnCooldown(this)) {
// 处理物品的使用逻辑
//获取被点击的方块的位置
BlockPos clickedPos = pContext.getClickedPos();
int f=1;
for(int i=1;i<=detectionDistance;i++){
//不断向下找,直到达到最大距离或者找到为止
BlockState blockState = level.getBlockState(clickedPos.offset(0, i * -1, 0));
//如果找到矿石
if(blockState.is(Tags.Blocks.ORES)){
f=0;
//获取这个矿石的名字
String ore = blockState.getBlock().getName().getString();
//向玩家发送信息找到了
player.sendSystemMessage(Component.literal("在下方"+i+"格发现"+ore));
break;
}
}
//没有找到
if(f==1){
player.sendSystemMessage(Component.literal("没有发现矿物"));
}
//每次减少一点耐久,当耐久为0时损坏物品,并广播一个物品损害事件
pContext.getItemInHand()
.hurtAndBreak(1, pContext.getPlayer(),
(p) -> p.broadcastBreakEvent(pContext.getHand()));
// 设置冷却时间
player.getCooldowns().addCooldown(this, COOLDOWN_TICKS);
return InteractionResult.SUCCESS;
}
else{
return InteractionResult.FAIL;
}
}
}
对于代码中需要进一步说明的是,每一个方块在游戏中都有着各种各样的状态,比如位置、方向等等,这些信息就放在BlockState
类里面,以后有机会的话会介绍一下这个类。
另外学习过unity或者其他3D相关的小伙伴应该不陌生游戏中对位置的描述,一般都是一个三维向量,表示物品在三维空间中的坐标位置,代码中使用的clickedPos.offset(x, y, z)
方法就是让当前的位置进行便宜的方法,以当前位置为基础,向其他方向进行偏移,例如clickedPos.offset(0, -1, 0)
就是当前方块的位置的下面一格位置,因为y轴的值为-1表示向下偏移了一格。
另外还有一个知识点就是游戏中的每个方块和物品身上都会有一个或者多个Tag
,或者叫标签,这些标签可以表示该方块或者物品具有的特性,就比如ORES标签就表示了所有的矿物,如果你想让一个方块只能被稿子挖掉,就可以给他打上MINEABLE_WITH_PICKAXE
标签并在创建物品时设置该方块只能被正确工具破坏。因此我们在判断某个方块是不是矿石时就可以用blockState.is(Tags.Blocks.ORES)
进行判断。
还有一个损坏耐久的方法就是,hurtAndBreak
方法,我们可以设置使用一次损坏的耐久度,当耐久为0时会自动破坏该物品,并广播一个物品损坏事件,以此监听触发物品损坏的事件,如果你想物品损坏时有什么要执行,可以在那里面接着写逻辑。
最后一个就是用addCooldown
方法为物品添加一个冷却时间,当处于冷却时无法执行逻辑代码,冷却结束才可继续执行。
然后在ModItems类中注册我们的探测器,注意这里要new
的是我们的OreDetector
类,而不是Item
类。
//矿石探测器
public static final RegistryObject<Item>ORE_DETECTOR=ITEMS.register("ore_detector",
()->new OreDetector(new Item.Properties()
.stacksTo(1)//最大堆叠数为1
.durability(500)//耐久度为500
,20));
后面就是和其他物品一样的步骤(添加到创造模式物品栏、设置语言文件、设置模型、把刚才画的贴图放到对应位置、RunData并启动游戏),这些步骤不再一一演示。下面就进入游戏查看一下效果。
进入游戏后可以看到我们想实现的效果都已经成功的实现了。