Иконка ресурса

Анимация модели блока через AnimationTESR

Версия(и) Minecraft
1.12.2
Всем привет! Сегодня мы с вами научимся юзать анимацию для блока.

Например, вы решили сделать плавно открывающуюся дверь, как я, но посчитали что не трушно использовать какие то левые апи. Этот тутор для вас!

Суть работы AnimationAPI- это плавный переход из одного блокстейта в другой. Грубо говоря, если в обычной ванили у нас есть только константа - блокстейт(вспомним матан ( f(state) = const)), то тут мы уже имеем функцию, которая является зависимой от времени(f(state) = t).

Тутор будет написан на примере открывания\закрывания двери.
Итак, давайте посмотрим что нам надо:
1) Нам нужны JSON файлы моделей. Джва штука. Один для статической части и один для динамической части.
2) Нам нужны будут тайл нашего блока, и сам блок.

Основная запара здесь конечно с JSON-ами этими деревянными, нежели с кодом.

Посему давайте с них и начнем. Первым делом нам понадобится файл с параметрами анимации, который нам надо расположить по пути modid:asms/block/doortest.json
JSON:
{
    //Это не предназначено для копипастинга.
    "parameters": {
        // триггер end_anim указывает нам что к переменной click_time нужно прибавлять 1
        "end_anim": [ "compose", [ "+", 1 ] , "#click_time" ],
            //этот триггер вычитает по единице из #end_anim
        "trigger_anim": [ "-", "#end_anim" ],
            //это триггер прогресса, он вычитает по единичке каждый тик, пока не будет 0. То есть 20 раз.
        "progress": [ "-", "#click_time" ]
    },
        //это клипы, или ключевые точки анимации.
        //У нас тут в данном случае имеется два пограничных состояния и два промежуточных
    "clips": {
        //закрытое состояние. "Тождественно" обычному состоянию
        "closed": "#identity",
            //открывающееся состояние. Тут используется
        "opening": [
            "trigger_positive",
            [ "slerp", "#closed", "#open", "#identity", "#progress" ],
            "#trigger_anim",
            "!transition:open"
        ],
//открытое состояние, в данном случае мы устанавливаем для клипа open модель
        "open": [ "apply", "soviet:bigdoor_open@open", 0 ],
        "closing": [
            "trigger_positive",
            [ "slerp", "#open", "#closed", "#identity", "#progress" ],
            "#trigger_anim",
            "!transition:closed"
        ]
    },
//это просто список состояний, которые у нас есть. Важно соблюдать порядок.
    "states": [
        "closed",
        "opening",
        "open",
        "closing"
    ],
//перемещения. Тут мы указываем какое состояние должно в какое перейти. К примеру, закрытое //переходит в закрывающееся и так далее
    "transitions": {
        "closed": "opening",
        "opening": "open",
        "open": "closing",
        "closing": "closed"
    },
    "start_state": "closed"
}
Итак, второй файл который нам понадобится, это файл собственно анимации. Его нужно расположить по пути modid:armatures/block/bigdoor_open.json Насколько я понял, его название должно быть идентично названию динамической модели.
JSON:
{
  "joints": {
      //части модели, которые нужно анимировать. Их количество должно быть равно количеству элементов в динамической модели.
    "door":    { "0": [ 1.0 ], "1": [ 1.0 ], "2": [ 1.0 ], "3": [ 1.0 ], "4": [ 1.0 ], "5": [ 1.0 ], "6": [ 1.0 ], "7": [ 1.0 ], "8": [ 1.0 ], "9": [ 1.0 ], "10": [ 1.0 ], "11": [ 1.0 ], "12": [ 1.0 ], "13": [ 1.0 ], "14": [ 1.0 ], "15": [ 1.0 ], "16": [ 1.0 ], "17": [ 1.0 ], "18": [ 1.0 ], "19": [ 1.0 ], "20": [ 1.0 ], "21": [ 1.0 ], "22": [ 1.0 ], "23": [ 1.0 ], "24": [ 1.0 ], "25": [ 1.0 ], "26": [ 1.0 ], "27": [ 1.0 ], "28": [ 1.0 ], "29": [ 1.0 ], "30": [ 1.0 ], "31": [ 1.0 ], "32": [ 1.0 ], "33": [ 1.0 ], "34": [ 1.0 ], "34": [ 1.0 ], "35": [ 1.0 ], "36": [ 1.0 ], "37": [ 1.0 ], "38": [ 1.0 ], "39": [ 1.0 ], "40": [ 1.0 ]}
  },
      //А вот и тутошние клипы
  "clips": {
    "closed": {
        //loop - параметр состояния. Если true то анимация продолжит отображаться
        //если значение превысит 1(конечную точку),
        //если false, то остановится в финальном положении
      "loop": false,
         //тут собственно список переменных(которые мы задали в начале), в моем случае
          //"door" и то что с ними можно делать.
      "joint_clips": {},
          //какие то события можно делать в каких то точках. Я не пользовался этим, хз
      "events": {}
    },
    "open": {
      "loop": true,
      "joint_clips": {
        "door": [
          {
              //это дебажные настройки. Выбираете интерполяцию, вид трансформации и т д.
              //Подробнее, какие они бывают, можете почитать в доках форге(единственное полезное что там есть)
                    "variable": "origin_x",
                    "type": "uniform",
                    "interpolation": "linear",
                    "samples": [ 0.794 ]
                    },
                    {
                    "variable": "origin_z",
                    "type": "uniform",
                    "interpolation": "linear",
                    "samples": [ 0.255 ]
                    },
                    {
                    "variable": "axis_y",
                    "type": "uniform",
                    "interpolation": "linear",
                    "samples": [ 4 ]
                    },
                    {
                    "variable": "angle",
                    "type": "uniform",
                    "interpolation": "nearest",
                    "samples": [ -90, 0, 0 ]
          }
    ]
      },

      "events": {
      }
    }
  }
}
Итак два json файла для анимации у нас есть. Давайте сделаем теперь наш блок и тайл и зарегистрируем всю эту шнягу.
Сперва блок. Для удобства я стырил в одном месте хороший BlockBase для этого дела. Расширимся от него.
Java:
public class BlockBase extends BlockContainer {



    public BlockBase(Material materialIn, String name) {
        super(materialIn);
        setUnlocalizedName(name);
        setRegistryName(name);
    }

    @Override
    public EnumBlockRenderType getRenderType(IBlockState state) {
        return EnumBlockRenderType.MODEL;
    }

    @Override
    public TileEntity createNewTileEntity(World worldIn, int meta) {
        return null;
    }

    @Override
    public void harvestBlock(World worldIn, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, @Nullable ItemStack stack) {
        player.addStat(StatList.getBlockStats(this));
        player.addExhaustion(0.025F);
        ItemStack itemstack = new ItemStack(this);
        NBTTagCompound nbttagcompound = new NBTTagCompound();
        te.writeToNBT(nbttagcompound);
        itemstack.setTagInfo("BlockEntityTag", nbttagcompound);
        spawnAsEntity(worldIn, pos, itemstack);
    }
}

И наш блок.
Java:
public class TestDoor extends BlockBase {


    public static final PropertyDirection FACING = PropertyDirection.create("facing", EnumFacing.Plane.HORIZONTAL);


    public TestDoor(Material materialIn, String name, float hardness, float resistanse, SoundType soundtype) {
        super(materialIn, name);
        setSoundType(SoundType.WOOD);
        setDefaultState(blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH));
    }
    @Override
    public boolean isFullCube(IBlockState state) {
        return false;
    }

    @Override
    public boolean isOpaqueCube(IBlockState state) {
        return false;
    }
//основная суть - нам необходимо взять специальный ExtendedBlockState, в котором есть эта поддержка анимации. Первый аргумент - это обычный проперти, а второй это форджевский для анимации.
    @Nonnull
    @Override
    public ExtendedBlockState createBlockState() {
        return new ExtendedBlockState(this, new IProperty[]{FACING, Properties.StaticProperty }, new IUnlistedProperty[]{ Properties.AnimationProperty });
    }
    @Override
    public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {

        if(worldIn.isRemote) {

            TileEntity te = worldIn.getTileEntity(pos);

            if(te instanceof TileEntityTestDoor) {
//при правом клике по блоку вызывается метод click из тайла
                ((TileEntityTestDoor)te).click();

              
            }
        }
        return true;
    }
//так как наша анимация будет рендерится через TESR, наличие тайла естесно обязательно
     @Override
        public TileEntity createNewTileEntity(@Nonnull World worldIn, int meta) {
            return new TileEntityTestDoor();
        }

    @Override
    public IBlockState getActualState(@Nonnull IBlockState state, IBlockAccess world, BlockPos pos) {
        return state.withProperty(Properties.StaticProperty, true);
    }

    //это всякие меты фигеты, для поворота по направлению взгляда, это к делу не относится
    @Override
    public IBlockState getStateFromMeta(int meta) {
        EnumFacing facing = EnumFacing.getHorizontal(meta);
        return getDefaultState().withProperty(FACING, facing);
    }

    @Override
    public int getMetaFromState(IBlockState state) {
        return state.getValue(FACING).getHorizontalIndex();
    }

    @Override
    public IBlockState getStateForPlacement(World worldIn, BlockPos pos, EnumFacing facingIn, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer) {
        EnumFacing facing = (placer == null) ? EnumFacing.NORTH : EnumFacing.fromAngle(placer.rotationYaw);
        return getDefaultState().withProperty(FACING, facing);
    }

}

Переходим теперь к следующему - это тайлэнтити.
Java:
public class TileEntityTestDoor extends TileEntity {
    //необходимо создать обьект анемейтстайте машин
    private final IAnimationStateMachine asm;
//это наша переменная, флоат, которая меняется от нуля до бесконечности, но не может быть отрицательной
    private final VariableValue clickTime = new VariableValue(Float.POSITIVE_INFINITY);
  
    public TileEntityTestDoor() {
//в конструкторе тайла мы определяем расположение файла с состояниями анимации
        asm = SovietCore.proxy.load(new ResourceLocation(SovietCore.MODID, "asms/block/doortest_an.json"), ImmutableMap.<String, ITimeValue>of("click_time", clickTime));
    }
  
    @Override
    public NBTTagCompound getUpdateTag() {
        return writeToNBT(new NBTTagCompound());
    }
//состояния анимации
    @Override
    public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing side) {
        return capability == CapabilityAnimation.ANIMATION_CAPABILITY || super.hasCapability(capability, side);
    }

    @Nonnull
    @Override
    public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing side) {
        if(capability == CapabilityAnimation.ANIMATION_CAPABILITY) {
            return CapabilityAnimation.ANIMATION_CAPABILITY.cast(asm);
        }
        return super.getCapability(capability, side);
    }
//метод для правильно регистрации
    public void handleEvents(float time, Iterable<Event> pastEvents) { }
//вот тот самый метод click который мы видели в блоке. Я не знаю, что тут комментировать, вроде бы все очевидно.
    public void click() {
        if(asm != null) {
            if(asm.currentState().equals("open")) {
          
                float time = Animation.getWorldTime(getWorld(), Animation.getPartialTickTime());
                clickTime.setValue(time);
                asm.transition("closing");
                System.out.println("open");
            } else if(asm.currentState().equals("closed")) {
          
                float time = Animation.getWorldTime(getWorld(), Animation.getPartialTickTime());
                clickTime.setValue(time);
          
                asm.transition("opening");
               System.out.println("closed");
            }
        }
    }


}
Не забываем создать блокстейт. Он будет выглядеть немного необычно, но в целом, он такой же как обычно.
JSON:
{
    "forge_marker": 1,
    "defaults": {
        "model": "soviet:bigdoor"
    },
    "variants": {
        "normal": [{}],
        "inventory": [{
            "model": "soviet:bigdoor",
            "submodel": {
                "door": {
                    "model": "soviet:bigdoor_open"
                }
            }
        }],
        "facing": {
            "north": { "transform": "identity"                   },
            "south": { "transform": { "rotation": { "y": 180 } } },
            "east":  { "transform": { "rotation": { "y": 270 } } },
            "west":  { "transform": { "rotation": { "y": 90  } } }
        },
        "static": {
            "true": {
                "model": "soviet:bigdoor"
            },
            "false": {
                "model": "soviet:bigdoor_open"
            }
        }
    }
}

Но у нас горит ошибка на методе load, ведь его нету. Давайте сделаем его в методе ClientProxy
Собственно это метод, подгружающий и регистрирующий модель IAnimationStateMachine
Java:
    @Override
    public IAnimationStateMachine load(ResourceLocation location, ImmutableMap<String, ITimeValue> parameters) {
        return ModelLoaderRegistry.loadASM(location, parameters);
    }
Не забываем зарегистрировать блок как обычно(блок и рендер) и тайлэнтити
А регистрация данного тайлэнтити происходит вот так
1 аргумент - наш тайл, второе это AnimationTESR с нашим тайлом.
Java:
        ClientRegistry.bindTileEntitySpecialRenderer(TileEntityTestDoor.class, new AnimationTESR<TileEntityTestDoor>() {
            @Override
            public void handleEvents(TileEntityTestDoor tileEntity, float time, Iterable<Event> pastEvents) {
                super.handleEvents(tileEntity, time, pastEvents);
                tileEntity.handleEvents(time, pastEvents);
            }
        });
Вроде бы как все. Как вы видите, я сам не до конца разобрался во всех фичах, и вообще этот тутор больше на слив кода похож, но тем не менее это даст вам первоначальный фундамент для изучения остальных фич самостоятельно.

В итоге получается что то такое

Да и вообще я этот тутор для себя написал, чтобы не потерять:)
Автор
Maxik
Просмотры
147
Первый выпуск
Обновление
Оценка
5.00 звёзд 2 оценок

Другие ресурсы пользователя Maxik

Последние рецензии

Исправь: "Джва штука" ) В остальном всё отлично!
Maxik
Maxik
я специально это написал. Старый мем, ток олды вспомнят)
Очень прикольно! На форуме ещё не было гайдов по этой теме!
Сверху