在之前的教程里,我们很简单的创建了一个item——物品,这个物品可以是食物,可以是药水,也可以是武器和工具,甚至是没有用的物品,那仅仅是这样还不行,我还要给他加一些自定义功能,比如武器右键可以释放技能,吃下食物可以获得自定义效果,药水也是,当然上一期的药水效果还不够“自定义”,因此这期教程先从武器说起,如何实现自定义的武器?本期的武器需要有以下功能:“下界合金剑放到高炉里烧成咱们的自定义武器,自定义武器拥有击退和火焰附加附魔,右键释放技能——发射自定义火球,这个火球和别的火球不一样,他不会破坏方块,而且由火球引燃的实体不会停止燃烧
一.新建一个武器类
首先要实现一个完全自定义的武器,需要单独写一个类,这个类继承SwordItem,也就是剑的物品,然后使用注解@Override重写父类的方法,然后再用注册表注册该物品即可,和之前的是一样的,下面上代码:
public class FlameSwordItem extends SwordItem {
public FlameSwordItem() {
super(Tiers.NETHERITE, 10, -2.4F,
new Properties()
.durability(2031)
.rarity(Rarity.EPIC)
.fireResistant()
);
}
// 确保物品可被附魔
@Override
public boolean isEnchantable(@NotNull ItemStack stack) {
return true;
}
// 设置附魔等级系数
@Override
public int getEnchantmentValue() {
return 30; // 30级附魔能力
}
// 显示附魔光效
@Override
public boolean isFoil(ItemStack stack) {
return true;
}
// 物品描述
@Override
public void appendHoverText(ItemStack stack, @Nullable Level level,
List<Component> tooltip, TooltipFlag flag) {
tooltip.add(Component.translatable("tooltip.flame_sword.desc")
.withStyle(ChatFormatting.GOLD));
super.appendHoverText(stack, level, tooltip, flag);
}
public static void applyDefaultEnchantments(ItemStack stack) {
stack.enchant(Enchantments.KNOCKBACK, 2);
stack.enchant(Enchantments.FIRE_ASPECT, 3);
}
@Override
public void onCraftedBy(@NotNull ItemStack stack, @NotNull Level level, @NotNull Player player) {
if (!EnchantmentHelper.getEnchantments(stack).containsKey(Enchantments.KNOCKBACK)&&stack.getItem() instanceof FlameSwordItem) {
applyDefaultEnchantments(stack);
}
}
@Override
public @NotNull InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (world.isClientSide || player.getCooldowns().isOnCooldown(this)) {
return InteractionResultHolder.fail(stack);
}
// 射线追踪参数计算
Vec3 eyePos = player.getEyePosition(1.0F);
Vec3 viewVec = player.getViewVector(1.0F);
Vec3 endVec = eyePos.add(viewVec.x * 32D, viewVec.y * 32D, viewVec.z * 32D);
AABB aabb = player.getBoundingBox().expandTowards(viewVec.scale(32D)).inflate(1.0D);
// 获取目标实体
EntityHitResult entityResult = ProjectileUtil.getEntityHitResult(
world, player, eyePos, endVec, aabb,
entity -> entity instanceof LivingEntity && entity != player
);
if (entityResult != null) {
Entity target = entityResult.getEntity();
if (target instanceof LivingEntity) {
CustomFireball fireball = getCustomFireball(world, player);
Vec3 look = player.getLookAngle();
fireball.setPos(eyePos.add(look));
fireball.xPower = look.x * 0.5D;
fireball.yPower = look.y * 0.5D;
fireball.zPower = look.z * 0.5D;
world.addFreshEntity(fireball);
ServerLevel serverLevel = (ServerLevel) world;
// 添加冷却和粒子效果
player.getCooldowns().addCooldown(this, 60);
serverLevel.sendParticles(ParticleTypes.FLAME,
player.getX(), player.getY() + 1.0D, player.getZ(),
20, 0, 0, 0, 0.1D);
return InteractionResultHolder.success(stack);
}
}
return InteractionResultHolder.pass(stack);
}
private static @NotNull CustomFireball getCustomFireball(Level world, Player player) {
CustomFireball fireball = new CustomFireball(
world,
player,
player.getLookAngle().x * 0.1,
player.getLookAngle().y * 0.1,
player.getLookAngle().z * 0.1
);
// 设置火球初始位置
fireball.setPos(
player.getX() + player.getLookAngle().x,
player.getEyeY(),
player.getZ() + player.getLookAngle().z
);
return fireball;
}
}
解释:
这段代码定义了一个名为FlameSwordItem
的自定义物品,继承自Minecraft中的SwordItem
类。这个物品是一个附魔可以生成火焰球的剑。下面我将逐步分解并详细解释这段代码。
-
类声明和构造函数:
public class FlameSwordItem extends SwordItem { public FlameSwordItem() { super(Tiers.NETHERITE, 10, -2.4F, new Properties() .durability(2031) .rarity(Rarity.EPIC) .fireResistant() ); }
- 这段代码定义了一个名为
FlameSwordItem
的类,它继承自SwordItem
。 - 构造函数中,调用了父类
SwordItem
的构造函数,指定了该剑的材料为Tiers.NETHERITE
,基础攻击伤害为10点,攻击速度为-2.4F。 - 同时设置了物品的一些属性,包括耐久度为2031,稀有度为
Rarity.EPIC
,且具有抗火特性。
- 这段代码定义了一个名为
-
物品附魔相关方法:
@Override public boolean isEnchantable(@NotNull ItemStack stack) { return true; } @Override public int getEnchantmentValue() { return 30; // 30级附魔能力 }
isEnchantable
方法返回true
,表示该物品可以被附魔。getEnchantmentValue
方法返回30,这意味着该剑可以被附上最高30级的附魔。
-
附魔光效:
@Override public boolean isFoil(ItemStack stack) { return true; }
isFoil
方法返回true
,表示当物品被附魔时会有特殊的光效,通常在物品上会有光亮的光芒。
-
物品描述:
@Override public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) { tooltip.add(Component.translatable("tooltip.flame_sword.desc") .withStyle(ChatFormatting.GOLD)); super.appendHoverText(stack, level, tooltip, flag); }
appendHoverText
方法用于向玩家显示该物品的额外信息(悬停提示)。- 使用
Component.translatable
方法可以翻译提示文本,这里添加了一个自定义的描述文本,颜色为金色。
-
默认附魔:
public static void applyDefaultEnchantments(ItemStack stack) { stack.enchant(Enchantments.KNOCKBACK, 2); stack.enchant(Enchantments.FIRE_ASPECT, 3); }
applyDefaultEnchantments
方法用于给该剑附上默认的附魔。- 该剑会附上2级击退和3级火焰附加。
-
物品合成后处理:
@Override public void onCraftedBy(@NotNull ItemStack stack, @NotNull Level level, @NotNull Player player) { if (!EnchantmentHelper.getEnchantments(stack).containsKey(Enchantments.KNOCKBACK)&&stack.getItem() instanceof FlameSwordItem) { applyDefaultEnchantments(stack); } }
onCraftedBy
方法在该物品被合成时调用。- 它检查物品是否已经附有击退附魔,如果没有,则调用
applyDefaultEnchantments
方法给物品附上默认的附魔。
-
物品使用方法:
@Override public @NotNull InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); if (world.isClientSide || player.getCooldowns().isOnCooldown(this)) { return InteractionResultHolder.fail(stack); } Vec3 eyePos = player.getEyePosition(1.0F); Vec3 viewVec = player.getViewVector(1.0F); Vec3 endVec = eyePos.add(viewVec.x * 32D, viewVec.y * 32D, viewVec.z * 32D); AABB aabb = player.getBoundingBox().expandTowards(viewVec.scale(32D)).inflate(1.0D); EntityHitResult entityResult = ProjectileUtil.getEntityHitResult( world, player, eyePos, endVec, aabb, entity -> entity instanceof LivingEntity && entity != player ); if (entityResult != null) { Entity target = entityResult.getEntity(); if (target instanceof LivingEntity) { CustomFireball fireball = getCustomFireball(world, player); Vec3 look = player.getLookAngle(); fireball.setPos(eyePos.add(look)); fireball.xPower = look.x * 0.5D; fireball.yPower = look.y * 0.5D; fireball.zPower = look.z * 0.5D; world.addFreshEntity(fireball); ServerLevel serverLevel = (ServerLevel) world; player.getCooldowns().addCooldown(this, 60); serverLevel.sendParticles(ParticleTypes.FLAME, player.getX(), player.getY() + 1.0D, player.getZ(), 20, 0, 0, 0, 0.1D); return InteractionResultHolder.success(stack); } } return InteractionResultHolder.pass(stack); }
use
方法定义了当玩家使用该物品时的行为。- 首先检查是否在客户端或该物品是否处于冷却中。
- 如果没有问题,则计算射线追踪参数,通过射线追踪获取目标实体。
- 如果目标实体存在且为生物实体,则生成一个
CustomFireball
火焰球并设置其位置和速度。 - 将火焰球添加进世界,并在服务器端添加冷却效果和粒子效果。
- 根据操作结果返回
InteractionResultHolder
以告知游戏引擎处理成功还是失败。
-
获取自定义火焰球的方法:
private static @NotNull CustomFireball getCustomFireball(Level world, Player player) { CustomFireball fireball = new CustomFireball( world, player, player.getLookAngle().x * 0.1, player.getLookAngle().y * 0.1, player.getLookAngle().z * 0.1 ); fireball.setPos( player.getX() + player.getLookAngle().x, player.getEyeY(), player.getZ() + player.getLookAngle().z ); return fireball; }
getCustomFireball
方法用于生成一个CustomFireball
对象。- 通过玩家的视角角度设置火焰球的速度和初始位置。
总结:
这段代码定义了一个特殊的火焰剑,继承自Minecraft的SwordItem
类。该剑具有抗火特性,且可以被附魔,附魔后会有特殊的光效。当该剑被合成时,它会自动附上击退和火焰附加附魔。玩家使用该剑时,可以向目标生物实体发射一个自定义的火焰球,并且该剑会有60秒的冷却时间。这是一把强大且附带火焰效果的剑,适用于需要远程攻击和控制敌人的场合。
添加描述
在Minecraft Forge开发里,appendHoverText方法和ItemTooltipEvent事件都可用于给物品添加描述,不过它们在使用方式、调用时机和应用场景上存在差异。
appendHoverText方法
定义:appendHoverText是Item类中的一个方法,你可以在自定义物品类里重写这个方法来给物品添加描述。
调用时机:当鼠标悬停在物品上时,游戏会自动调用这个方法。
示例代码:
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class CustomItem extends Item {
public CustomItem(Properties properties) {
super(properties);
}
@Override
public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> tooltipComponents, TooltipFlag isAdvanced) {
tooltipComponents.add(Component.translatable("item.custom_item.tooltip"));
}
}
- 优点:代码简洁,直接在物品类里处理描述逻辑,易于维护和管理。
- 缺点:缺乏灵活性,只能针对特定物品添加描述,若要对多个物品统一处理,需在每个物品类里重写该方法。
ItemTooltipEvent事件
定义:ItemTooltipEvent是Forge提供的一个事件,你可以通过订阅这个事件来统一处理所有物品的描述。
调用时机:当鼠标悬停在物品上时,游戏会触发这个事件。
示例代码:
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.List;
import static com.example.examplemod.MyMod.FLAME_SWORD;
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE)
public class ModItemProperties {
@SubscribeEvent
public static void onItemTooltip(ItemTooltipEvent event) {
ItemStack stack = event.getItemStack();
List<Component> tooltip = event.getToolTip();
if (stack.is(FLAME_SWORD.get())) {
tooltip.add(Component.translatable("item.mymod.flame_sword.tooltip"));
}
}
}
- 优点:灵活性高,可在一个地方统一处理多个物品的描述逻辑,便于进行复杂的条件判断和统一管理。
- 缺点:代码相对复杂,需要订阅事件并处理事件逻辑。
总结
- 若只需为单个物品添加简单描述,使用appendHoverText方法更合适。
- 若要对多个物品进行统一的描述处理,或者需要根据复杂条件添加描述,使用ItemTooltipEvent事件更合适。
如果物品对应的描述是键值而不是你想要的描述怎么办?只需要在语言模型生成器里加入即可
add("tooltip.flame_sword.desc","appendHoverText生成的描述");
add("item.mymod.flame_sword.tooltip","ItemTooltipEvent生成的描述");
二、主类:
@Mod("你的modid")
public class MyMod{
public static final String MODID = "mymod";
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, "你的modid");
public static final RegistryObject<Item> FLAME_SWORD = ITEMS.register("flame_sword",
FlameSwordItem::new);
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MyMod.MODID);
public static final RegistryObject<CreativeModeTab> EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () ->
CreativeModeTab.builder()
.icon(() -> new ItemStack(FLAME_SWORD.get()))
.title(Component.translatable("itemGroup.mymod.example_tab"))
.displayItems((parameters, output) -> {
output.accept(FLAME_SWORD.get());
})
.build()
);
public static void register(IEventBus eventBus) {
ITEMS.register(eventBus);
CREATIVE_MODE_TABS.register(eventBus);
}
public MyMod() {
register(modEventBus);
}
}
代码解析:
这段代码是一个 Minecraft 模组的主类,注册了一个名为 "mymod" 的模组,并定义了一些物品和创造模式标签。
- 首先使用 @Mod("你的modid") 注解指定了 modid 为 "mymod"。
- 类 MyMod 中定义了 MODID 常量为 "mymod",以及一个 DeferredRegister<Item> 类型的 ITEMS 对象用于注册物品。
- 使用 ITEMS 对象注册了一个名为 "flame_sword" 的物品 FLAME_SWORD,类型为 FlameSwordItem。
- 创建了一个 DeferredRegister<CreativeModeTab> 类型的 CREATIVE_MODE_TABS 对象用于注册创造模式标签。
- 使用 CREATIVE_MODE_TABS 对象注册了一个名为 "example_tab" 的创造模式标签 EXAMPLE_TAB,其中包含了图标、标题和显示物品等属性。
- 定义了一个 register 方法用于注册 ITEMS 和 CREATIVE_MODE_TABS。
- 在构造方法中调用了 register 方法注册了事件总线 modEventBus。
这段代码的功能是注册了一个名为 "mymod" 的模组,其中包含了一个名为 "flame_sword" 的物品和一个名为 "example_tab" 的创造模式标签,其中的创造模式标签显示的图标为 "flame_sword" 物品。
你看着乱你也可以分开写,可以把物品的注册单独放到一个类里,然后在主类中把物品和创造模式物品栏注册到主类的事件总线中即可。
那么,怎么实现火球不破坏方块,还能使被击中的实体不断燃烧的效果?这就不得不提到自定义实体了,那怎么让剩余冷却时间显示在下面?那就不得不用到HUD绘制了,这部分内容我打算放到下期再讲,接下来我们讲讲物品模型生成器,有时候我们不会写模型,但是forge给我们提供了一个模型生成器,可以用这个来生成模型。
三、模型生成器
在 Minecraft Forge 开发中,模型生成器(Model Generator)是一种工具,它能够自动为游戏内的物品、方块等资源生成对应的模型文件。这些模型文件主要是 JSON 格式,用于定义游戏元素在 3D 环境中的外观展示。
模型生成器的工作原理
模型生成器通常会依据你在代码里定义的物品或方块属性,如类型、纹理等,按照特定的规则自动生成对应的模型文件。例如,当你创建了一个新的方块类,模型生成器就会根据这个方块类的名称、纹理路径等信息,生成一个描述该方块外观的 JSON 文件。
使用模型生成器的好处
提高开发效率 手动编写模型文件是一件繁琐且容易出错的事情,尤其是在处理大量物品和方块时。模型生成器能够自动完成这个过程,节省开发者的时间和精力,让开发者可以将更多的精力投入到功能开发上。
保证一致性 模型生成器按照统一的规则生成模型文件,这样可以保证所有物品和方块的模型在格式和结构上保持一致。这有助于避免因手动编写模型文件时可能出现的格式错误或不一致问题。
便于维护和更新 当你需要修改物品或方块的外观时,只需修改相关的代码或纹理文件,然后重新运行模型生成器,就可以快速更新对应的模型文件。这种方式比手动修改大量的模型文件更加方便和高效。
减少错误 手动编写模型文件时,很容易出现拼写错误、语法错误等问题,这些错误可能会导致模型无法正常显示。模型生成器可以自动检查和避免这些问题,提高模型文件的质量。
模型生成器分为好几种,这里只讲物品模型生成器,语言文件生成器,配方生成器,运行runData后相应的json会生成在generated\resources目录下。
语言文件生成器:
public class ModLangGen extends LanguageProvider {
public ModLangGen(PackOutput output, String locale){super(output, MyMod.MODID,locale);}
//重写这个方法,这个方法里添加对应物品的信息
@Override
protected void addTranslations() {
add(MyMod.FLAME_SWORD.get(),"烈焰剑");
}
}
当然你有更多的物品需要显示不同的文字可以在后面加
配方文件生成器:
public class RecipeGenerator extends RecipeProvider {
public RecipeGenerator(DataGenerator generator) {
super(generator.getPackOutput());
}
@Override
protected void buildRecipes(Consumer<FinishedRecipe> consumer) {
SimpleCookingRecipeBuilder.blasting(
Ingredient.of(Items.NETHERITE_SWORD),
RecipeCategory.COMBAT,
MyMod.FLAME_SWORD.get(),
20.0f,
400
).unlockedBy("has_netherite_sword", has(Items.NETHERITE_SWORD))
.save(consumer, new ResourceLocation("mymod", "flame_sword"));
}
private static InventoryChangeTrigger.TriggerInstance has(net.minecraft.world.item.Item item) {
return InventoryChangeTrigger.TriggerInstance.hasItems(
ItemPredicate.Builder.item().of(item).build()
);
}
}
这段代码定义了一个名为RecipeGenerator
的类,这个类继承自RecipeProvider
,用于生成游戏内的合成或加工食谱。下面将逐步分解并详细解释这段代码:
-
类定义:
public class RecipeGenerator extends RecipeProvider {
RecipeGenerator
类被声明为public
,这意味着它可以在任何地方被访问。- 该类继承自
RecipeProvider
,这意味着它将用于提供游戏内的食谱数据。
-
构造方法:
public RecipeGenerator(DataGenerator generator) { super(generator.getPackOutput()); }
RecipeGenerator
的构造方法接收一个参数DataGenerator
对象。- 调用了父类
RecipeProvider
的构造方法,并传入generator.getPackOutput()
作为参数。DataGenerator
是Minecraft数据生成工具的一部分,用于生成游戏内的各种数据,包括语言文件、模型、食谱等。getPackOutput()
则是用于获取数据生成的目标输出对象。
-
重写
buildRecipes
方法:@Override protected void buildRecipes(Consumer<FinishedRecipe> consumer) { SimpleCookingRecipeBuilder.blasting( Ingredient.of(Items.NETHERITE_SWORD), RecipeCategory.COMBAT, MyMod.FLAME_SWORD.get(), 20.0f, 400 ).unlockedBy("has_netherite_sword", has(Items.NETHERITE_SWORD)) .save(consumer, new ResourceLocation("mymod", "flame_sword")); }
buildRecipes
方法被重写,用于自定义食谱的生成逻辑。SimpleCookingRecipeBuilder.blasting()
是一个用于创建爆炉(blasting)食谱的静态方法。它需要五个参数:原料、食谱类别、烹饪结果、经验值和烹饪时间。Ingredient.of(Items.NETHERITE_SWORD)
指定了食谱的原料,这里是“下界合金剑”。RecipeCategory.COMBAT
指定了食谱的类别,这里是“战斗类”。MyMod.FLAME_SWORD.get()
指定了烹饪的结果,这里是MyMod
类中的FLAME_SWORD
物品。.get()
方法用于获取物品实例。20.0f
指定了烹饪结果所能提供的经验值。400
指定了烹饪所需的时间(以游戏刻为单位)。unlockedBy
方法用于指定解锁该食谱的条件,这里是指玩家必须拥有“下界合金剑”才能解锁这个食谱。save(consumer, new ResourceLocation("mymod", "flame_sword"))
方法用于将生成的食谱保存到DataGenerator
指定的输出位置。ResourceLocation
指定了食谱的唯一标识,格式为<modid>:<name>
,这里对应的标识为mymod:flame_sword
。
-
has
方法:private static InventoryChangeTrigger.TriggerInstance has(net.minecraft.world.item.Item item) { return InventoryChangeTrigger.TriggerInstance.hasItems( ItemPredicate.Builder.item().of(item).build() ); }
has
方法是一个私有静态方法,用于创建一个InventoryChangeTrigger.TriggerInstance
对象。- 该对象用于判断玩家的物品栏中是否有指定的物品,从而决定是否可以解锁特定的食谱。
ItemPredicate.Builder.item().of(item).build()
用于构建一个物品谓词,指定了需要的物品类型。InventoryChangeTrigger.TriggerInstance.hasItems
则用于根据这个物品谓词来创建触发实例。
总结: 这段代码的主要功能是通过继承RecipeProvider
类来自定义游戏内的合成或加工食谱。具体来说,这段代码定义了一个食谱生成器,用于生成一种名为“火焰剑”的物品。该物品可以通过在爆炉中使用“下界合金剑”来生成,并且这一操作会为玩家提供20点经验值。这个食谱只有在玩家拥有“下界合金剑”时才能被解锁。食谱的唯一标识为mymod:flame_sword
,其中mymod
是这个物品所属模组的ID。
物品模型生成器:
public class HandheldItemModelGenerator extends ItemModelProvider {
public HandheldItemModelGenerator(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
super(output, modid, existingFileHelper);
}
@Override
protected void registerModels() {
ModelFile.ExistingModelFile itemHandheld = getExistingFile(mcLoc("item/handheld"));
getBuilder("flame_sword")
.parent(itemHandheld)
.texture("layer0", new ResourceLocation("mymod", "item/flame_sword"));
}
}
代码解析:
这段代码定义了一个名为HandheldItemModelGenerator
的类,该类继承自ItemModelProvider
类。在构造方法中,传入了PackOutput
、modid
和ExistingFileHelper
作为参数,并调用了父类的构造方法进行初始化。
在registerModels
方法中,首先通过getExistingFile
方法获取了一个名为itemHandheld
的现有模型文件。接着使用getBuilder
方法创建了一个名为flame_sword
的新模型构建器,并设置其父模型为itemHandheld
,同时指定了layer0
纹理为mymod:item/flame_sword
。
总的来说,这段代码的作用是生成一个手持物品(handheld item)的模型,该模型名为flame_sword
,并设置其纹理为mymod:item/flame_sword
。
模型生成器还有方块模型生成器,这里没用到,先不讲了,以后用到了会单独拿出来讲,还有其他功能的实现下期在讲,关注我,带你学习更多模组开发的知识