Minecraft是一款没有上限的游戏,你甚至可以在里面实现魂Like游戏中的酷炫动作:我的世界 类魂式战斗模组
今天我们尝试在模组中添加一个能够做各种动作的生物实体
1.首先,为了实现这些效果,我们需要首先使用到一个模组:geckolib(下载地址)
找到项目的build.gradle
文件,在repositories
和dependencies
中添加依赖。
repositories {//添加这个maven { url 'https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/' }}dependencies {minecraft 'net.minecraftforge:forge:1.18.2-40.1.0'//添加这个implementation fg.deobf('software.bernie.geckolib:geckolib-1.18-forge:3.0.18')}
之后我们重新构建gradle项目
构建好了项目后在项目的Main类中添加一句geckolib的初始化语句:
Main.java
public Main() {IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();ItemInit.ITEMS.register(bus);//添加GeckoLib初始化函数GeckoLib.initialize();MinecraftForge.EVENT_BUS.register(this);}
2.之后,与之前的教程一样,我们需要在blockbench中制作一个模组中的生物实体:
进入软件后我们要找到一个插件按钮,然后再搜索栏中输入GeckoLib Animation Utils
,并下载这个插件
将我们制作好的生物实体进行模型转换工作,找到Convert Project
,之后选择Geckolib Animated Model
在这之后,你会发现你的生物实体栏多了一个Animate栏
,点击进去:
具体动作制作的视频:Blockbench动画制作
在制作好所有的动画后我们导出模型和动画json文件。
3.模型制作完成,接下来需要制作生物实体类,我们的生物的动画事件类似于状态机,所以要对生物实体的状态进行枚举。
EntityDund.java
package com.joy187.re8joymod.entity;import java.util.Random;import java.util.function.Predicate;import javax.annotation.Nullable;import net.minecraft.core.BlockPos;import net.minecraft.sounds.SoundEvent;import net.minecraft.sounds.SoundEvents;import net.minecraft.util.Mth;import net.minecraft.world.Difficulty;import net.minecraft.world.damagesource.DamageSource;import net.minecraft.world.effect.MobEffectInstance;import net.minecraft.world.effect.MobEffects;import net.minecraft.world.entity.Entity;import net.minecraft.world.entity.EntityType;import net.minecraft.world.entity.LivingEntity;import net.minecraft.world.entity.ai.attributes.AttributeSupplier;import net.minecraft.world.entity.ai.attributes.Attributes;import net.minecraft.world.entity.ai.goal.BreakDoorGoal;import net.minecraft.world.entity.ai.goal.Goal;import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;import net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal;import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;import net.minecraft.world.entity.ai.util.GoalUtils;import net.minecraft.world.entity.animal.IronGolem;import net.minecraft.world.entity.animal.Turtle;import net.minecraft.world.entity.monster.Monster;import net.minecraft.world.entity.monster.ZombifiedPiglin;import net.minecraft.world.entity.npc.AbstractVillager;import net.minecraft.world.entity.player.Player;import net.minecraft.world.level.Level;import net.minecraft.world.level.block.state.BlockState;import net.minecraft.world.phys.Vec3;import software.bernie.geckolib3.core.IAnimatable;import software.bernie.geckolib3.core.PlayState;import software.bernie.geckolib3.core.builder.AnimationBuilder;import software.bernie.geckolib3.core.controller.AnimationController;import software.bernie.geckolib3.core.event.predicate.AnimationEvent;import software.bernie.geckolib3.core.manager.AnimationData;import software.bernie.geckolib3.core.manager.AnimationFactory;import work.syncher.EntityDataAccessor;import work.syncher.EntityDataSerializers;import work.syncher.SynchedEntityData;//我们的生物类要实现一个IAnimatable的接口public class EntityDund extends Monster implements IAnimatable{//定义一个动画事件处理器private AnimationFactory factory = new AnimationFactory(this);private boolean canBreakDoors;public boolean playAttackAnimation = false;public int attackTick=1;//枚举攻击状态public static final EntityDataAccessor<Integer> STATE = SynchedEntityData.defineId(EntityDund.class,EntityDataSerializers.INT);private final BreakDoorGoal breakDoorGoal = new BreakDoorGoal(this, DOOR_BREAKING_PREDICATE);private static final Predicate<Difficulty> DOOR_BREAKING_PREDICATE = (mode) -> {return mode == Difficulty.HARD || mode == Difficulty.NORMAL;};public EntityDund(EntityType<? extends Monster> type, Level worldIn) {super(type, worldIn);this.xpReward = 10;}protected void registerGoals() {this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));this.addBehaviourGoals();}protected void addBehaviourGoals() {//将我们的攻击AI进行注册this.goalSelector.addGoal(1, new EntityDund.AttackGoal(this));this.goalSelector.addGoal(2, new DundAttackGoal(this, 1.0D, false));this.goalSelector.addGoal(6, new MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors));this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers(ZombifiedPiglin.class));this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));//this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));}public static AttributeSupplier.Builder prepareAttributes() {return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 300.0D).add(Attributes.MOVEMENT_SPEED, 0.32D).add(Attributes.ATTACK_DAMAGE, 5.5D);}public boolean doHurtTarget(Entity entityIn) {if(!super.doHurtTarget(entityIn)){this.playAttackAnimation=false;return false;}else{if(entityIn instanceof LivingEntity){float f = this.level.getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();((LivingEntity)entityIn).addEffect(new MobEffectInstance(MobEffects.WITHER, 100 * (int)f,0,true,true));}return true;}}//该状态播放器控制生物平时的各种动作private <E extends IAnimatable> PlayState predicate(AnimationEvent<E> event) {//生物在移动,就播放移动的动画,路径和第2步的动作字符串对应if (event.isMoving()) {event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.dund.walk", true));return PlayState.CONTINUE;}//不动的话就播放闲置时的动画event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.dund.idle", true));return PlayState.CONTINUE;}//该状态播放器控制生物攻击时的各种动作private <E extends IAnimatable> PlayState predicate1(AnimationEvent<E> event) {//如果生物攻击状态为1并且没有死亡,就执行这个攻击动画if (this.entityData.get(STATE) == 1 && !(this.dead || this.getHealth() < 0.01 || this.isDeadOrDying())) {event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.dund.attack", true));return PlayState.CONTINUE;}//如果生物攻击状态为2并且没有死亡,就执行这个攻击动画//if (this.entityData.get(STATE) == 2 && !(this.dead || this.getHealth() < 0.01 || this.isDeadOrDying())) {//event.getController().setAnimation(new AnimationBuilder().addAnimation("fire", true));//return PlayState.CONTINUE;//}//如果生物攻击状态为3并且没有死亡,就执行这个攻击动画//...//播完停止return PlayState.STOP;}//将我们之前的所有动画控制器进行注册@Overridepublic void registerControllers(AnimationData data) {data.addAnimationController(new AnimationController(this, "controller",0, this::predicate));data.addAnimationController(new AnimationController(this, "controller1",0, this::predicate1)); }@Overridepublic AnimationFactory getFactory() {// TODO Auto-generated method stubreturn this.factory;}//生物实体声音@Nullableprotected SoundEvent getAmbientSound() {return SoundEvents.RAVAGER_AMBIENT;}protected SoundEvent getHurtSound(DamageSource source) {return SoundEvents.RAVAGER_HURT;}protected SoundEvent getDeathSound() {return SoundEvents.RAVAGER_DEATH;}protected void playStepSound(BlockPos pos, BlockState blockstate) {this.playSound(SoundEvents.RAVAGER_STEP, 0.15F, 1.0F);}static class DundAttackGoal extends MeleeAttackGoal {private final EntityDund zombie;public DundAttackGoal(EntityDund entity, double p_i46803_2_, boolean p_i46803_4_) {super(entity, p_i46803_2_, p_i46803_4_);this.zombie=entity;}}public boolean canBreakDoors() {return this.canBreakDoors;}protected boolean supportsBreakDoorGoal() {return true;}//怪物能不能破门而入public void setCanBreakDoors(boolean p_34337_) {if (this.supportsBreakDoorGoal() && GoalUtils.hasGroundPathNavigation(this)) {if (this.canBreakDoors != p_34337_) {this.canBreakDoors = p_34337_;((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(p_34337_);if (p_34337_) {this.goalSelector.addGoal(1, this.breakDoorGoal);} else {this.goalSelector.removeGoal(this.breakDoorGoal);}}} else if (this.canBreakDoors) {this.goalSelector.removeGoal(this.breakDoorGoal);this.canBreakDoors = false;}}public int getAttckingState() {return this.entityData.get(STATE);}public void setAttackingState(int time) {this.entityData.set(STATE, time);}@Overrideprotected void defineSynchedData() {super.defineSynchedData();this.entityData.define(STATE, 0);}//我们生物的AI,决定使用哪种攻击方式static class AttackGoal extends Goal {private final EntityDund parentEntity;//攻击计时器protected int attackTimer = 0;public AttackGoal(EntityDund mob) {this.parentEntity = mob;}public boolean canUse() {return this.parentEntity.getTarget() != null;}public void start() {super.start();this.parentEntity.setAggressive(true);}@Overridepublic void stop() {super.stop();this.parentEntity.setAggressive(false);this.parentEntity.setAttackingState(0);this.attackTimer = -1;}public void tick() {LivingEntity livingentity = this.parentEntity.getTarget();if (this.parentEntity.hasLineOfSight(livingentity)) {Level world = this.parentEntity.level;++this.attackTimer;Random rand = new Random();Vec3 vector3d = this.parentEntity.getViewVector(1.0F);double d0 = Math.min(livingentity.getY(), livingentity.getY());double d1 = Math.max(livingentity.getY(), livingentity.getY()) + 1.0D;double d2 = livingentity.getX() - (this.parentEntity.getX() + vector3d.x * 2.0D);double d3 = livingentity.getY(0.5D) - (0.5D + this.parentEntity.getY(0.5D));double d4 = livingentity.getZ() - (this.parentEntity.getZ() + vector3d.z * 2.0D);float f = (float) Mth.atan2(livingentity.getZ() - parentEntity.getZ(),livingentity.getX() - parentEntity.getX());this.parentEntity.getNavigation().moveTo(livingentity, 1.5D);if (this.attackTimer == 15) {// 将该生物攻击状态设置为2this.parentEntity.setAttackingState(2);} else{// 将该生物攻击状态设置为1this.parentEntity.setAttackingState(1);}//计时器到头后,将攻击状态设置为0,同时计时器清0if (this.attackTimer == 30) {this.parentEntity.setAttackingState(0);this.attackTimer = -5;}} else if (this.attackTimer > 0) {--this.attackTimer;}this.parentEntity.lookAt(livingentity, 30.0F, 30.0F);}}}
4.新建生物实体模型文件ModelDund类
ModelDund.java
package com.joy187.re8joymod.entity.model;import com.joy187.re8joymod.Main;import com.joy187.re8joymod.entity.EntityDund;import com.joy187.re8joymod.entity.render.RenderDund;import net.minecraft.client.model.geom.ModelLayerLocation;import net.minecraft.resources.ResourceLocation;import software.bernie.geckolib3.model.AnimatedGeoModel;//我们的模型类要继承AnimatedGeoModel<T>类public class ModelDund extends AnimatedGeoModel<EntityDund>{public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(Main.MOD_ID, "dund"), "main");我们生物模型实体的路径@Overridepublic ResourceLocation getModelLocation(EntityDund object) {return new ResourceLocation(Main.MOD_ID, "geo/dund.geo.json");}//我们生物皮肤材质的路径@Overridepublic ResourceLocation getTextureLocation(EntityDund object) {//return RenderDund.LOCATION_BY_VARIANT.get(object.getVariant());return new ResourceLocation(Main.MOD_ID, "textures/entity/dund.png");}//我们生物皮肤动画的路径@Overridepublic ResourceLocation getAnimationFileLocation(EntityDund animatable) {return new ResourceLocation(Main.MOD_ID, "animations/dund.animation.json");}}
5.模型部分结束,开始着手渲染类的编写。新建RenderDund类。
RenderDund.java
package com.joy187.re8joymod.entity.render;import com.joy187.re8joymod.Main;import com.joy187.re8joymod.entity.EntityDund;import com.joy187.re8joymod.entity.model.ModelDund;import com.mojang.blaze3d.vertex.PoseStack;import com.mojang.blaze3d.vertex.VertexConsumer;import net.minecraft.client.renderer.MultiBufferSource;import net.minecraft.client.renderer.RenderType;import net.minecraft.client.renderer.entity.EntityRendererProvider;import net.minecraft.client.renderer.entity.EntityRendererProvider.Context;import net.minecraft.resources.ResourceLocation;import software.bernie.geckolib3.model.AnimatedGeoModel;import software.bernie.geckolib3.renderers.geo.GeoEntityRenderer;//我们的渲染类要继承GeoEntityRenderer<T>public class RenderDund extends GeoEntityRenderer<EntityDund>{public RenderDund(EntityRendererProvider.Context renderManager) {super(renderManager, new ModelDund());//我们生物的阴影大小this.shadowRadius = 0.5f;}@Overridepublic ResourceLocation getTextureLocation(EntityDund instance) {//return LOCATION_BY_VARIANT.get(instance.getVariant());return new ResourceLocation(Main.MOD_ID, "textures/entity/dund.png");}@Overridepublic RenderType getRenderType(EntityDund animatable, float partialTicks, PoseStack stack,MultiBufferSource renderTypeBuffer, VertexConsumer vertexBuilder, int packedLightIn,ResourceLocation textureLocation) {//如果是幼年动物,就把其按比例缩小// if(animatable.isBaby()) {// stack.scale(0.4F, 0.4F, 0.4F);// } else {// stack.scale(0.8F, 0.8F, 0.8F);// }//默认原来模型大小stack.scale(1F, 1F, 1F);return super.getRenderType(animatable, partialTicks, stack, renderTypeBuffer, vertexBuilder, packedLightIn, textureLocation);}}
6.在EntityInit.java中
添加我们的生物信息:
public static final RegistryObject<EntityType<EntityDund>> DUND1 = ENTITY_TYPES.register("dund",() -> EntityType.Builder.of(EntityDund::new, MobCategory.MONSTER).sized(1f,1.8f).setTrackingRange(30).build(new ResourceLocation(Main.MOD_ID, "dund").toString()));
在ClientModEventSubscriber.java
中添加我们的模型渲染、属性注册语句:
@Mod.EventBusSubscriber(modid = Main.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)public class ClientModEventSubscriber{@SubscribeEventpublic static void onRegisterRenderer(EntityRenderersEvent.RegisterRenderers event) {//添加渲染注册语句event.registerEntityRenderer(EntityInit.DUND1.get(), RenderDund::new);//event.registerEntityRenderer(EntityInit.DETONATOR_ENTITY.get(), RenderDetonatorEntity::new);}@SubscribeEventpublic static void onAttributeCreate(EntityAttributeCreationEvent event) {//添加属性注册语句event.put(EntityInit.DUND1.get(), EntityDund.prepareAttributes().build());}}
7.生物实体部分结束,接下来我们要给生物制作一个刷怪蛋:
在ItemInit类中添加我们的刷怪蛋物品:
public static final RegistryObject<Item> DUND1_SPAWN_EGG = ITEMS.register("dund_spawn_egg", () -> new ForgeSpawnEggItem(EntityInit.DUND1, 3093009, 65536, new Item.Properties().tab(Main.TUTORIAL_TAB)));
8.代码部分结束,来到资源包制作环节
在resources\assets\你的modid
中的lang包中的en_us.json
添加刷怪蛋和生物实体英文名称:
"item.re8joymod.dund_spawn_egg": "Mob Spawn Egg","entity.re8joymod.dund": "Mob",
在models\item
包中添加刷怪蛋模型文件:
dund_spawn_egg.json
{"parent": "item/template_spawn_egg"}