作物 1.21 Fabric
本篇教程的视频
本篇教程的源代码
Github地址:TutorialMod-Crop-1.21
介绍
作物
在Minecraft中是一类特殊的方块,它们可以被种植,自己会生长,最后成熟时可以被玩家收获
游戏中有很多作物,单方块
的如小麦
、胡萝卜
、马铃薯
等,还有一些是多方块
的,如甘蔗
、南瓜
、西瓜
、仙人掌
等,还有像其他种植在特殊地域的作物,如海带
、可可豆
等
本篇教程我们就来实现一个单方块
的作物,在下一篇教程中我们会实现一个多方块
的作物
查看源代码
那么首先第一步,我们还是来看看游戏的那些作物的源代码,我们以WHEAT
(小麦)为例
作物方块注册
1 | public static final Block WHEAT = register( |
这个是在Blocks
类中的注册,因为作物本质还是方块,我们可以看到WHEAT
实例化了CropBlock
后面是它的一些Settings
中设置了一些属性,之前见过的我这里就不再赘述,我们看看新出现的几个
noCollision
这是没有碰撞体积,也就是说玩家可以穿过这个方块。
游戏里的大多数作物是没有碰撞体积的,当然也有特殊的,比如仙人掌
是有碰撞体积的,并且碰到会受到伤害;甜浆果
虽然没有碰撞体积,但实体碰到会减慢其速度,并造成伤害。
这些其实都可以去研究一下
ticksRandomly
这个是随机刻,关于随机刻的介绍可以看这里。
它是关乎我们作物生长的,wiki上也指出了除了作物生长之外还要响应随机刻的事件,比如说铜方块的氧化、草方块的蔓延等等。
如果说你有一些方块需要进行随机计算的,也可以使用这个方法
breakInstantly
这个是瞬间破坏,也就是说玩家用手去破坏这个方块的时候会瞬间破坏,不会有破坏的动画(也就是那裂开的动画)
pistonBehavior
这个是活塞行为,这个是用来设置活塞推动这个方块的时候的行为,这里设置的是DESTROY
,也就是推动的时候会破坏这个方块
CropBlock类
那么我们再来看看CropBlock
这个类
类有点长,我们截取一点,来看看里面的一些方法
1 | public static final int MAX_AGE = 7; |
前面这两个是定义作物的生长阶段,MAX_AGE
是最大的生长阶段,AGE
是一个IntProperty
,也就是说它是一个整数类型的属性,这个属性的值是0-7,也就是说作物有8个生长阶段
后面一个是碰撞箱
(外轮廓线,也就是光标对准方块后的那些黑色的边线,这个线和碰撞箱的体积是一致的,只是作物没有碰撞体积)
作物的碰撞箱是会随着生长阶段而变化的
,这个数组里面存放了8个生长阶段的碰撞箱,下面也有方法返回其碰撞箱
1 | public CropBlock(AbstractBlock.Settings settings) { |
这个是构造函数,它设置了作物的默认状态,也就是说作物的默认生长阶段AGE
是0
1 |
|
这个是获取碰撞箱的方法,它会根据作物的目前的生长阶段返回对应的碰撞箱
1 | protected IntProperty getAgeProperty() { |
这个方法是返回作物的生长阶段属性,也就是AGE
1 |
|
这个是判断作物是否可以种植在某个方块上,这里是判断是否是种植在耕地上,你也可以重写一下,让我们的作物种植在其他方块上
1 | public int getMaxAge() { |
这个是获取作物的最大生长阶段,也就是MAX_AGE
,也可以直接返回数字
1 | public int getAge(BlockState state) { |
这个是获取作物的生长阶段,调用了上面的getAgeProperty
方法,返回了作物的生长阶段
1 | public BlockState withAge(int age) { |
这个是设置方块状态,根据age
的值返回一个新的BlockState
,这是在后面的一系列生长逻辑中调用的
1 | public final boolean isMature(BlockState state) { |
这两个方法是判断作物是否成熟,isMature
是判断作物的生长阶段是否大于等于最大生长阶段
hasRandomTicks
是判断是否还需要随机刻,作物成熟后就不需要随机刻了
1 |
|
这里的一串都是作物生长的逻辑,randomTick
是随机刻的逻辑
applyGrowth
是应用生长的逻辑
getGrowthAmount
是获取随机值,用于生长的逻辑
getAvailableMoisture
是获取作物生长环境的湿度(这玩意还挺离谱的)
canPlaceAt
是判断作物是否可以种植
hasEnoughLightAt
是判断环境是否有足够的光照
1 |
|
这个是碰撞实体的逻辑,这里是判断如果碰到的实体是RavagerEntity
(劫掠兽)并且游戏规则允许怪物破坏方块,那么就破坏这个方块
1 | protected ItemConvertible getSeedsItem() { |
这个是获取种子的方法,这里是返回小麦的种子
1 |
|
这个是判断作物是否可以施肥,施肥可以促进作物生长,但如果作物已经成熟了施肥也没有用
1 |
|
这个是添加属性的方法,这里添加了AGE
属性
因为AGE
属性是我们的方块状态,我们在构造函数中也设置了其初始方块状态。
你翻过那些方块状态文件也会知道,根据不同的AGE
值返回不同的模型
如果说你也写了一些带有不同状态的方块,这玩意一定不能忘记重写
种子物品注册
那么除此之外,我们还得再看看Items
中的注册
1 | public static final Item WHEAT_SEEDS = register("wheat_seeds", new AliasedBlockItem(Blocks.WHEAT, new Item.Settings())); |
我们可以发现,其实Blocks
中注册的小麦方块是没有对应的方块物品的,但是有个对应的种子,另外一个WHEAT
是对应的作物,这个是用来做食物的
所以我们待会在注册的时候就得注意这个问题
不过既然来了,先看看这个种子怎么注册的
它实例化的是AliasedBlockItem
,其参数是方块和物品设置,这个AliasedBlockItem
是一个继承自BlockItem
的类,它的作用是将方块和物品绑定在一起,也就是说你在游戏中种下这个种子,长出来的作物就是这个方块
当然这里的小麦只是其中一个例子,小麦的收获物和种子是分开的,和甜菜根一样。
但还有像马铃薯、胡萝卜、甜浆果等作物,它们的种子和收获物是同一个,这个也值得去研究研究
注册作物
这里我们就来仿照小麦写一个简单的作物,不过参照上面的CropBlock
,小麦它是有8
个生长阶段的(0-7),我们自己写的时候可能不需要这么多(也许是贴图不想画这么多…)
所以我们可以自己去写一个自定义的作物类,重写它的生长阶段就好
创建StrawberryCropBlock类
我们前面写过一个Strawberry
这个食物,所以以此衍生,我们来创建一个StrawberryCropBlock
类,继承CropBlock
1 | public class StrawberryCropBlock extends CropBlock { |
然后,按照我们的想法,重新定义其生长阶段
1 | public static final int MAX_AGE = 5; |
这里我们定义了最大生长阶段的值为5
AGE
的属性可以调用Properties
中的AGE_5
,这个是一个IntProperty
,它的值是0-5
,也就是说有6
个生长阶段
如果说Properties
中没有你要的值,那就仿照里面的语句自己创建呗(下一篇会说)
碰撞箱倒是不用再定义了,因为可以直接调用父类的碰撞箱
1 |
|
这里我们重写了getMaxAge
、getAgeProperty
、getAge
、appendProperties
这几个方法
这些也是上面提到过的一些方法
1 |
|
这里我们先返回null
,因为我们还没有注册种子物品
1 |
|
这里我们重写了canPlantOnTop
方法,让我们的作物也可以种植在DIRT(也就是那些土壤上,比如砂土、土壤、草方块等等)
上
注册作物方块
那么我们就来注册我们的作物方块
1 | public static final Block STRAWBERRY_CROP = Registry.register(Registries.BLOCK, Identifier.of(TutorialMod.MOD_ID, "strawberry_crop"), |
这里我们注册了一个STRAWBERRY_CROP
,实例化StrawberryCropBlock
,我们把WHEAT
的属性copy过来
另外,我们这里使用的注册方法就直接用了Registry.register
,因为我们不需要注册方块物品,也就不能用我们之前写的方法(毕竟作物方块是种在地上的,总不能把植株作为一种方块物品吧?)
注册种子物品
接下来,我们注册种子物品
1 | public static final Item STRAWBERRY_SEEDS = registerItems("strawberry_seeds", |
这就是用之前我们看到的写法来写,实例化AliasedBlockItem
,把STRAWBERRY_CROP
和Item.Settings
传进去
另外也记得完善我们重写的getSeedsItem
方法
1 |
|
这样我们使用这个种子的时候,作物就可以被种到地上了
写入物品栏
我们还得将作物的种子写入物品栏
1 | entries.add(ModItems.STRAWBERRY_SEEDS); |
渲染层设置
最后,我们还得设置渲染层,作物的贴图基本上是带全透明区域的吧,毕竟植株之间是有空隙的,所以我们还得单独设置渲染层
还是两种方法,以下是Mixin
的方法,到我们之前写的RenderLayersMixin
中写
1 | BLOCKS.put(ModBlocks.STRAWBERRY_CROP, RenderLayer.getCutout()); |
另一种是在TutorialModClient
写
1 | BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.STRAWBERRY_CROP, RenderLayer.getCutout()); |
这样透明的区域就不会变成黑色的了
数据文件
语言文件
这里就只要写种子的就好了
1 | translationBuilder.add(ModItems.STRAWBERRY_SEEDS, "Strawberry Seeds"); |
模型文件
种子的模型文件不需要写,它会根据我们的作物方块中设置的种子进行生成
1 | blockStateModelGenerator.registerCrop(ModBlocks.STRAWBERRY_CROP, Properties.AGE_5, 0, 1, 2, 3, 4, 5); |
这里我们就注册方块的模型文件即可,作物方块的生成有单独的方法,其最后的可变参数是各个生长阶段的值,有n个就从0写到n(为啥它就不能写个循环遍历嘞?)
战利品列表
作物的战利品列表也是值得讲一下的,因为它在不同状态下返回的掉落物是不一样的
比如说小麦在未成熟之前被破坏了,只会掉落1个种子;但是成熟之后被破坏,会掉落1个小麦和1-4个小麦种子
所以这个东西写起来可能有点复杂,不过我们可以参考原版小麦的战利品列表生成方法
1 | LootCondition.Builder builder2 = BlockStatePropertyLootCondition.builder(ModBlocks.STRAWBERRY_CROP) |
这个就是搬小麦的,改了一些参数,这里的builder2
是战利品列表的判定情况(这里就是当我们的作物的age值到5时,也就是它成熟了),cropDrops
是作物方块生成掉落物的方法
那么折腾完这些东西之后,我们就可以进入我们的游戏种植我们的作物了