Анимация зомби

Версия Minecraft
1.16.5
API
Forge
198
1
24
Сделал кое-как зомби с использованием модельки игрока (чтобы можно было наделать таких зомбей в разных вариантах с использованием скинов игроков), но не могу разобраться, как управлять анимацией. В частности, нужно, чтобы зомби вытягивал вперёд руки, направляясь к жертве, и опускал их, бесцельно бродя по миру. В текущем варианте он вообще руки не использует.

Класс моба:
package com.madalchemist.betterzombies.zombies;

import com.madalchemist.betterzombies.ai.ModdedZombieAttackGoal;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.ModelHelper;
import net.minecraft.entity.CreatureAttribute;
import net.minecraft.entity.EntitySize;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.Pose;
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.entity.ai.goal.*;
import net.minecraft.entity.merchant.villager.AbstractVillagerEntity;
import net.minecraft.entity.monster.MonsterEntity;
import net.minecraft.entity.monster.ZombifiedPiglinEntity;
import net.minecraft.entity.passive.IronGolemEntity;
import net.minecraft.entity.passive.TurtleEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.pathfinding.GroundPathNavigator;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

import javax.annotation.Nonnull;

public class BasicZombie extends MonsterEntity {

    public BasicZombie(EntityType<? extends BasicZombie> zombie, World world) {
        super(zombie, world);

    }

    public static AttributeModifierMap.MutableAttribute createAttributes() {
        return MonsterEntity.createMonsterAttributes()
                .add(Attributes.FOLLOW_RANGE, 35.0D)
                .add(Attributes.MOVEMENT_SPEED, 0.3D)
                .add(Attributes.ATTACK_DAMAGE, 1.0D)
                .add(Attributes.ARMOR, 2.0D)
                .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE);
    }

    protected SoundEvent getAmbientSound() {
        return SoundEvents.ZOMBIE_AMBIENT;
    }
    protected SoundEvent getHurtSound(DamageSource p_184601_1_) {
        return SoundEvents.ZOMBIE_HURT;
    }
    protected SoundEvent getDeathSound() {
        return SoundEvents.ZOMBIE_DEATH;
    }
    protected SoundEvent getStepSound() {
        return SoundEvents.ZOMBIE_STEP;
    }
    protected void playStepSound(BlockPos p_180429_1_, BlockState p_180429_2_) {
        this.playSound(this.getStepSound(), 0.15F, 1.0F);
    }

    protected void registerGoals() {
        //this.goalSelector.addGoal(8, new RandomWalkingGoal(this, 0.1D));
        this.goalSelector.addGoal(8, new LookAtGoal(this, PlayerEntity.class, 8.0F));
        this.goalSelector.addGoal(8, new LookRandomlyGoal(this));
        this.goalSelector.addGoal(2, new ModdedZombieAttackGoal(this, 1.0D, false));
        this.goalSelector.addGoal(6, new MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors));
        this.goalSelector.addGoal(5, new MoveTowardsTargetGoal(this, 1, 1.0f));

        this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, PlayerEntity.class, true));
        this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, AbstractVillagerEntity.class, false));
        this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolemEntity.class, true));
        this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, TurtleEntity.class, 10, true, false, TurtleEntity.BABY_ON_LAND_SELECTOR));
    }


    @Override
    public void aiStep() {
        super.aiStep();
    }

    public boolean canBreakDoors(){
        return true;
    }

    @Override
    protected int getExperienceReward(@Nonnull PlayerEntity player) {
        return 2;
    }

    @Override
    public boolean canBreatheUnderwater() {
        return true;
    }

    @Override
    @Nonnull
    public ResourceLocation getDefaultLootTable() {
        return super.getDefaultLootTable();
    }

    @Override
    protected float getStandingEyeHeight(@Nonnull Pose pose, @Nonnull EntitySize size) {
        return 0.9F;
    }

    public CreatureAttribute getMobType() {
        return CreatureAttribute.UNDEAD;
    }
}

Класс рендера моба:
package com.madalchemist.betterzombies.zombies;

import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.client.renderer.entity.layers.BipedArmorLayer;
import net.minecraft.client.renderer.entity.layers.HeldItemLayer;
import net.minecraft.client.renderer.entity.model.BipedModel;
import net.minecraft.client.renderer.entity.model.PlayerModel;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

import javax.annotation.Nonnull;

@OnlyIn(Dist.CLIENT)
public class BasicZombieRenderer extends MobRenderer<BasicZombie, PlayerModel<BasicZombie>> {

    public BasicZombieRenderer(EntityRendererManager renderManager) {
        super(renderManager, new PlayerModel<BasicZombie>(0, true), 0.5F);
        this.addLayer(new BipedArmorLayer<>(this, new BipedModel(0.5F), new BipedModel(1.0F)));
        this.addLayer(new HeldItemLayer<>(this));
    }

    @Override
    @Nonnull
    public ResourceLocation getTextureLocation(@Nonnull BasicZombie zombie) {
        return this.getTexture("zombie_steve");
    }

    private ResourceLocation getTexture(String fileName) {
        return new ResourceLocation("betterzombies", "textures/entity/zombie/" + fileName + ".png");
    }
}

ИИ задача атаки:
package com.madalchemist.betterzombies.ai;

import net.minecraft.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.entity.monster.MonsterEntity;
import net.minecraft.entity.monster.ZombieEntity;

public class ModdedZombieAttackGoal extends MeleeAttackGoal {
    private final MonsterEntity zombie;
    private int raiseArmTicks;

    public ModdedZombieAttackGoal(MonsterEntity p_i46803_1_, double p_i46803_2_, boolean p_i46803_4_) {
        super(p_i46803_1_, p_i46803_2_, p_i46803_4_);
        this.zombie = p_i46803_1_;
    }

    public void start() {
        super.start();
        this.raiseArmTicks = 0;
    }

    public void stop() {
        super.stop();
        this.zombie.setAggressive(false);
    }

    public void tick() {
        super.tick();
        ++this.raiseArmTicks;
        if (this.raiseArmTicks >= 5 && this.getTicksUntilNextAttack() < this.getAttackInterval() / 2) {
            this.zombie.setAggressive(true);
        } else {
            this.zombie.setAggressive(false);
        }

    }
}
 
198
1
24
Может, я туплю, но откуда должен вызываться код, двигающий модельку? Из класса моба? Но там вроде нет методов и полей для работы с моделькой... Из класса рендера? И там тоже вроде всё подругому... Не похоже, чтобы код для 1.7.10 хоть немного подходил к 1.16.5 :(
 
4,045
63
645
Я точно не помню уже, но большинство этих методов вызываются автоматически через рендер модели...
Для некоторых из них, например для движения руки скелета при прицеливании, есть связка с самим скелетом...
В 1.12 это можно было посмотреть в самой сущности. На 1.16.5 хз.
 
198
1
24
В общем, нихрена не понимая, что же я делаю (а как понять все эти float p_i48914_1_, float p_i48914_2_, int p_i48914_3_, int p_i48914_4_?), сделал вот так:
Слегка переделаный AbstractZombieModel из ванили:
package com.madalchemist.betterzombies.models;

import net.minecraft.client.renderer.entity.model.BipedModel;
import net.minecraft.client.renderer.entity.model.PlayerModel;
import net.minecraft.client.renderer.model.ModelHelper;
import net.minecraft.entity.monster.MonsterEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

@OnlyIn(Dist.CLIENT)
public abstract class ModdedZombieModelBase<T extends MonsterEntity> extends PlayerModel<T> {
    protected ModdedZombieModelBase(float p_i51070_1_, float p_i51070_2_, int p_i51070_3_, int p_i51070_4_) {
        super(p_i51070_1_, /[I]p_i51070_2_, p_i51070_3_, p_i51070_4_[/I]/ false);
    }

    public void setupAnim(T p_225597_1_, float p_225597_2_, float p_225597_3_, float p_225597_4_, float p_225597_5_, float p_225597_6_) {
        super.setupAnim(p_225597_1_, p_225597_2_, p_225597_3_, p_225597_4_, p_225597_5_, p_225597_6_);
        ModelHelper.animateZombieArms(this.leftArm, this.rightArm, this.isAggressive(p_225597_1_), this.attackTime, p_225597_4_);
    }

    public abstract boolean isAggressive(T p_212850_1_);
}
Моделька модового зомби:
package com.madalchemist.betterzombies.models;

import net.minecraft.client.renderer.entity.model.AbstractZombieModel;
import net.minecraft.entity.monster.MonsterEntity;
import net.minecraft.entity.monster.ZombieEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

@OnlyIn(Dist.CLIENT)
public class ModdedZombieModel<T extends MonsterEntity> extends ModdedZombieModelBase<T> {
    public ModdedZombieModel(float p_i1168_1_, boolean p_i1168_2_) {
        this(p_i1168_1_, 0.0F, 64, p_i1168_2_ ? 32 : 64);
    }

    protected ModdedZombieModel(float p_i48914_1_, float p_i48914_2_, int p_i48914_3_, int p_i48914_4_) {
        super(p_i48914_1_, p_i48914_2_, p_i48914_3_, p_i48914_4_);
    }

    public boolean isAggressive(T p_212850_1_) {
        return p_212850_1_.isAggressive();
    }
}


Класс рендера:
package com.madalchemist.betterzombies.zombies;

import com.madalchemist.betterzombies.models.ModdedZombieModel;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.client.renderer.entity.layers.BipedArmorLayer;
import net.minecraft.client.renderer.entity.layers.HeldItemLayer;
import net.minecraft.client.renderer.entity.model.BipedModel;
import net.minecraft.client.renderer.entity.model.PlayerModel;
import net.minecraft.client.renderer.entity.model.ZombieModel;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

import javax.annotation.Nonnull;

@OnlyIn(Dist.CLIENT)
public class BasicZombieRenderer extends MobRenderer<BasicZombie, ModdedZombieModel<BasicZombie>> {

    public BasicZombieRenderer(EntityRendererManager renderManager) {
        super(renderManager, new ModdedZombieModel<BasicZombie>(0, true), 0.5F);
        this.addLayer(new BipedArmorLayer<>(this, new BipedModel(0.5F), new BipedModel(1.0F)));
        this.addLayer(new HeldItemLayer<>(this));
    }

    @Override
    @Nonnull
    public ResourceLocation getTextureLocation(@Nonnull BasicZombie zombie) {
        return this.getTexture("zombie_steve");
    }

    private ResourceLocation getTexture(String fileName) {
        return new ResourceLocation("betterzombies", "textures/entity/zombie/" + fileName + ".png");
    }
}

В результате получилось и лучше, и хуже одновременно: у моба теперь как бы 4 руки, но не совсем - там скорее текстурка пошла в разнос. 2 руки вытяниваются вперёд, как у зомби, а 2 висят и раскачиваются, как у игрока. Это совсем, совсем не то, что нужно - а нужно всего лишь правильно натянуть на зомби скин игрока.
Вот скрин этого безобразия:
2021-07-02_19.43.41.png
 
Сверху