Привет, всем! Продолжаем улучшать своего моба. В прошлый раз мы сделали копию существующей коровы, но немного иного цвета. Это круто, но не особо интересно. А сегодня мы пропишем нашей корове новую модель, что уже сделает отличия более существенными.
Рекомендую пройти предыдущие уроки для большего понимания.
И обязательно этот:
Погнали!
Модель
Проще всего модель для моба создать в программе Blockbench. Она бесплатная, так что качаем, устанавливаем и запускаем.
Не смотрим на историю, нас интересует меню New. А конкретно пункт:
Нажимаем и видим окошко настройки проекта:
Версию понятное дело не меняем, размеры текстуры будут зависеть от размеров модели, её детализации и т.д. Я остановился на этом:
И в открывшейся рабочей области можно приступать к созданию модели.
Левая сторона для добавления текстур, правая — дерево элементов и их параметры. Над полем редактора есть несколько доступных инструментов. Перемещение, изменение размера, поворот и положение точки опоры.
Прежде попробуйте создать что-то простое, возможно, лучше понять помогут гайды посвященные конкретно этой программе. Я не буду расписывать работу подробно тут, т.к. урок не о том.
Получилось что-то такое:
Основные отличия тут в голове, но вы можете хоть крылья добавлять, это дело желания 😀
На что нужно обратить внимание при создании модели
Есть важные моменты, о которых нельзя забывать. Первое – точки опоры.
К примеру, точка опоры головы должна быть тут для обеспечения нормального поворота головы.
Изменим угол поворота:
Если же точка опоры будет где-то не там, к примеру в позиции (0,0,0), то сразу будут видны проблемы.
Тут точка уже внизу, её видно. А голова с тем же углом поворота куда-то совсем не туда улетает.
Ещё более наглядно это с ногами:
Снова точка опоры осталась где-то в стороне и нога не может правильно совершить наклон.
Тут всё тот же угол наклона, но и точка опоры находиться в самой вершине ноги, что даёт ноге возможность совершать правильные круговые движения.
Это работает со всеми поворотами:
Тут разобрались, думаю.
Второе, о чём нельзя забывать, это папки. В дереве элементов можно увидеть, что у меня всё разделено по папкам.
Есть корневая папка (Body) для всей модели, а также все блоки внутри разделены на голову, ноги, тело и т.д. Это нужно для правильно обработки и является обязательным. Так же стоит помнить, что блоки сами по себе не могут иметь никакого угла поворота, но вот папка, с целой группой блоков – может.
Таким образом для того, чтобы сделать поворот сложной головы (с рогами и ушами, мордой) нам не нужно прописывать всё отдельно, а можно просто повернуть цельный объект Head.
Добавляем модель
Итак. Если считаете, что модель полностью готова и можно переходить далее, то нужно экспортировать модель в Java класс.
Для этого открываем меню File, находим Export и выбираем Expoty Java Entity.
И в итоге получаем что-то такое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
public class GSCowModel extends EntityModel<Entity> { private final ModelRenderer Body; private final ModelRenderer Legs; private final ModelRenderer Back; private final ModelRenderer RightBack; private final ModelRenderer LeftBack; private final ModelRenderer Front; private final ModelRenderer RightFront; private final ModelRenderer LeftFront; private final ModelRenderer Head; private final ModelRenderer Nose; private final ModelRenderer RightEar; private final ModelRenderer LeftEar; private final ModelRenderer Chest; private final ModelRenderer IDK; public GSCowModel() { textureWidth = 64; textureHeight = 64; Body = new ModelRenderer(this); Body.setRotationPoint(0.0F, 24.0F, 0.0F); Legs = new ModelRenderer(this); Legs.setRotationPoint(0.0F, 0.0F, 0.0F); Body.addChild(Legs); Back = new ModelRenderer(this); Back.setRotationPoint(0.0F, 0.0F, 0.0F); Legs.addChild(Back); RightBack = new ModelRenderer(this); RightBack.setRotationPoint(-4.0F, -12.0F, 8.0F); Back.addChild(RightBack); RightBack.setTextureOffset(0, 38).addBox(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, 0.0F, false); LeftBack = new ModelRenderer(this); LeftBack.setRotationPoint(4.0F, -12.0F, 8.0F); Back.addChild(LeftBack); LeftBack.setTextureOffset(0, 38).addBox(-2.0F, -0.1551F, -1.8511F, 4.0F, 12.0F, 4.0F, 0.0F, false); Front = new ModelRenderer(this); Front.setRotationPoint(0.0F, 0.0F, 0.0F); Legs.addChild(Front); RightFront = new ModelRenderer(this); RightFront.setRotationPoint(-4.0F, -12.0F, -6.0F); Front.addChild(RightFront); RightFront.setTextureOffset(0, 38).addBox(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, 0.0F, false); LeftFront = new ModelRenderer(this); LeftFront.setRotationPoint(4.0F, -12.0F, -6.0F); Front.addChild(LeftFront); LeftFront.setTextureOffset(0, 38).addBox(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, 0.0F, false); Head = new ModelRenderer(this); Head.setRotationPoint(0.0F, -21.0F, -8.0F); Body.addChild(Head); Head.setTextureOffset(0, 22).addBox(-4.0F, -4.0F, -6.0F, 8.0F, 8.0F, 6.0F, 0.0F, false); Head.setTextureOffset(0, 39).addBox(3.0F, -6.0F, -4.0F, 1.0F, 2.0F, 1.0F, 0.0F, false); Head.setTextureOffset(0, 39).addBox(-4.0F, -6.0F, -4.0F, 1.0F, 2.0F, 1.0F, 0.0F, false); Nose = new ModelRenderer(this); Nose.setRotationPoint(0.0F, 4.0F, -7.0F); Head.addChild(Nose); setRotationAngle(Nose, 0.1745F, 0.0F, 0.0F); Nose.setTextureOffset(42, 49).addBox(-3.0F, -3.0F, 0.0F, 6.0F, 3.0F, 2.0F, 0.0F, false); RightEar = new ModelRenderer(this); RightEar.setRotationPoint(-4.0F, -1.0F, -3.0F); Head.addChild(RightEar); setRotationAngle(RightEar, 0.0873F, 0.0F, 0.0873F); RightEar.setTextureOffset(42, 46).addBox(-2.0F, -2.0F, -1.0F, 2.0F, 1.0F, 1.0F, 0.0F, false); RightEar.setTextureOffset(42, 45).addBox(-3.0F, -1.0F, -1.0F, 3.0F, 2.0F, 1.0F, 0.0F, false); LeftEar = new ModelRenderer(this); LeftEar.setRotationPoint(4.0F, -1.0F, -4.0F); Head.addChild(LeftEar); setRotationAngle(LeftEar, 0.0873F, 0.0F, -0.0873F); LeftEar.setTextureOffset(50, 45).addBox(0.0F, -1.0F, 0.0F, 3.0F, 2.0F, 1.0F, 0.0F, false); LeftEar.setTextureOffset(51, 44).addBox(0.0F, -2.0F, 0.0F, 2.0F, 1.0F, 1.0F, 0.0F, false); Chest = new ModelRenderer(this); Chest.setRotationPoint(0.0F, -16.0F, 0.0F); Body.addChild(Chest); Chest.setTextureOffset(0, 36).addBox(-6.0F, -6.0F, -8.0F, 12.0F, 10.0F, 18.0F, 0.0F, false); IDK = new ModelRenderer(this); IDK.setRotationPoint(0.0F, 0.0F, 0.0F); Body.addChild(IDK); IDK.setTextureOffset(0, 14).addBox(-2.0F, -12.0F, 4.0F, 4.0F, 2.0F, 6.0F, 0.0F, false); } @Override public void setRotationAngles(Entity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch){ } @Override public void render(MatrixStack matrixStack, IVertexBuilder buffer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha){ Body.render(matrixStack, buffer, packedLight, packedOverlay); } public void setRotationAngle(ModelRenderer modelRenderer, float x, float y, float z) { modelRenderer.rotateAngleX = x; modelRenderer.rotateAngleY = y; modelRenderer.rotateAngleZ = z; } } |
Это уже практически готовый код модели. Нужно добавить его в проект, для моделей я создал отдельную папку, вот полный путь: mod / astler / tutorial_mod_gs / client / renderer / entity / model. Кроме того нужно добавить импорты (ide скорее всего сделает это сам) и изменить имена функций, которые могут быть не распознаны. К примеру у меня не нашло addBox, т.к. оно у меня называется func_228303_a_. Если подобное возникнет, то ищите подходящие по параметрам функции.
И после пары правок вот итоговый код модели:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
package mod.astler.tutorial_mod_gs.client.renderer.entity.model; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.vertex.IVertexBuilder; import mod.astler.tutorial_mod_gs.entity.passive.GSCowEntity; import net.minecraft.client.renderer.entity.model.EntityModel; import net.minecraft.client.renderer.model.ModelRenderer; import net.minecraft.util.math.MathHelper; public class GSCowModel extends EntityModel<GSCowEntity> { private final ModelRenderer mBody; private final ModelRenderer mRightBack; private final ModelRenderer mLeftBack; private final ModelRenderer mRightFront; private final ModelRenderer mLeftFront; private final ModelRenderer mHead; private final ModelRenderer mNose; private final ModelRenderer mRightEar; private final ModelRenderer mLeftEar; public GSCowModel() { textureWidth = 64; textureHeight = 64; mBody = new ModelRenderer(this); mBody.setRotationPoint(0.0F, 24.0F, 0.0F); ModelRenderer legs = new ModelRenderer(this); legs.setRotationPoint(0.0F, 0.0F, 0.0F); mBody.addChild(legs); ModelRenderer back = new ModelRenderer(this); back.setRotationPoint(0.0F, 0.0F, 0.0F); legs.addChild(back); mRightBack = new ModelRenderer(this); mRightBack.setRotationPoint(-4.0F, -12.0F, 8.0F); back.addChild(mRightBack); mRightBack.setTextureOffset(0, 38).func_228303_a_(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, 0.0F, false); mLeftBack = new ModelRenderer(this); mLeftBack.setRotationPoint(4.0F, -12.0F, 8.0F); back.addChild(mLeftBack); mLeftBack.setTextureOffset(0, 38).func_228303_a_(-2.0F, -0.1551F, -1.8511F, 4.0F, 12.0F, 4.0F, 0.0F, false); ModelRenderer front = new ModelRenderer(this); front.setRotationPoint(0.0F, 0.0F, 0.0F); legs.addChild(front); mRightFront = new ModelRenderer(this); mRightFront.setRotationPoint(-4.0F, -12.0F, -6.0F); front.addChild(mRightFront); mRightFront.setTextureOffset(0, 38).func_228303_a_(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, 0.0F, false); mLeftFront = new ModelRenderer(this); mLeftFront.setRotationPoint(4.0F, -12.0F, -6.0F); front.addChild(mLeftFront); mLeftFront.setTextureOffset(0, 38).func_228303_a_(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, 0.0F, false); mHead = new ModelRenderer(this); mHead.setRotationPoint(0.0F, -21.0F, -8.0F); mBody.addChild(mHead); mHead.setTextureOffset(0, 22).func_228303_a_(-4.0F, -4.0F, -6.0F, 8.0F, 8.0F, 6.0F, 0.0F, false); mHead.setTextureOffset(0, 39).func_228303_a_(3.0F, -6.0F, -4.0F, 1.0F, 2.0F, 1.0F, 0.0F, false); mHead.setTextureOffset(0, 39).func_228303_a_(-4.0F, -6.0F, -4.0F, 1.0F, 2.0F, 1.0F, 0.0F, false); mNose = new ModelRenderer(this); mNose.setRotationPoint(0.0F, 4.0F, -7.0F); mHead.addChild(mNose); setRotationAngle(mNose, 0.1745F, 0.0F, 0.0F); mNose.setTextureOffset(42, 49).func_228303_a_(-3.0F, -3.0F, 0.0F, 6.0F, 3.0F, 2.0F, 0.0F, false); mRightEar = new ModelRenderer(this); mRightEar.setRotationPoint(-4.0F, -1.0F, -3.0F); mHead.addChild(mRightEar); setRotationAngle(mRightEar, 0.0873F, 0.0F, 0.0873F); mRightEar.setTextureOffset(42, 46).func_228303_a_(-2.0F, -2.0F, -1.0F, 2.0F, 1.0F, 1.0F, 0.0F, false); mRightEar.setTextureOffset(42, 45).func_228303_a_(-3.0F, -1.0F, -1.0F, 3.0F, 2.0F, 1.0F, 0.0F, false); mLeftEar = new ModelRenderer(this); mLeftEar.setRotationPoint(4.0F, -1.0F, -4.0F); mHead.addChild(mLeftEar); setRotationAngle(mLeftEar, 0.0873F, 0.0F, -0.0873F); mLeftEar.setTextureOffset(50, 45).func_228303_a_(0.0F, -1.0F, 0.0F, 3.0F, 2.0F, 1.0F, 0.0F, false); mLeftEar.setTextureOffset(51, 44).func_228303_a_(0.0F, -2.0F, 0.0F, 2.0F, 1.0F, 1.0F, 0.0F, false); ModelRenderer chest = new ModelRenderer(this); chest.setRotationPoint(0.0F, -16.0F, 0.0F); mBody.addChild(chest); chest.setTextureOffset(0, 36).func_228303_a_(-6.0F, -6.0F, -8.0F, 12.0F, 10.0F, 18.0F, 0.0F, false); ModelRenderer IDK = new ModelRenderer(this); IDK.setRotationPoint(0.0F, 0.0F, 0.0F); mBody.addChild(IDK); IDK.setTextureOffset(0, 14).func_228303_a_(-2.0F, -12.0F, 4.0F, 4.0F, 2.0F, 6.0F, 0.0F, false); } public void setRotationAngle(ModelRenderer modelRenderer, float x, float y, float z) { modelRenderer.rotateAngleX = x; modelRenderer.rotateAngleY = y; modelRenderer.rotateAngleZ = z; } @Override public void func_225598_a_(MatrixStack p_225598_1_, IVertexBuilder p_225598_2_, int p_225598_3_, int p_225598_4_, float p_225598_5_, float p_225598_6_, float p_225598_7_, float p_225598_8_) { mBody.func_228308_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_); } } |
В конструкторе мы буквально по частям собираем модель, а в функции func_225598_a_ (render) происходит рендер Body (та самая корневая папка).
Прописываем новую модель вместо модели стандартной коровы в файле GSCowRenderer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package mod.astler.tutorial_mod_gs.client.entity.render; import mod.astler.tutorial_mod_gs.TutorialGSMod; import mod.astler.tutorial_mod_gs.client.renderer.entity.model.GSCowModel; import mod.astler.tutorial_mod_gs.entity.passive.GSCowEntity; import net.minecraft.client.renderer.entity.EntityRendererManager; import net.minecraft.client.renderer.entity.MobRenderer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @OnlyIn(Dist.CLIENT) public class GSCowRenderer extends MobRenderer<GSCowEntity, GSCowModel> { private static final ResourceLocation COW_TEXTURES = new ResourceLocation(TutorialGSMod.MODID, "textures/entity/cow/gs_cow.png"); public GSCowRenderer(EntityRendererManager renderManagerIn) { super(renderManagerIn, new GSCowModel(), 0.7F); } public ResourceLocation getEntityTexture(GSCowEntity entity) { return COW_TEXTURES; } } |
И делаем пробный запуск.
Да! Новая модель! Правда, она теперь деревянная. Вот видите ванильную корову? Она голову подняла, а наша так не делает не потому, что не хочет, а потому, что не может. Ведь никаких анимаций у модели пока нет. При этом ходить, получать урон и т.д. она уже может, и реагировать на всё будет, но без движений.
Именно для этого всего есть функция setRotationAngles (которая у меня имеет имя func_225597_a_).
Пропишем туда такой код:
1 2 3 4 5 6 7 8 |
public void func_225597_a_(GSCowEntity p_225597_1_, float p_225597_2_, float p_225597_3_, float p_225597_4_, float p_225597_5_, float p_225597_6_) { this.mHead.rotateAngleX = p_225597_6_ * ((float) Math.PI / 180F); this.mHead.rotateAngleY = p_225597_5_ * ((float) Math.PI / 180F); this.mLeftFront.rotateAngleX = MathHelper.cos(p_225597_2_ * 0.6662F) * 1.4F * p_225597_3_; this.mLeftBack.rotateAngleX = MathHelper.cos(p_225597_2_ * 0.6662F + (float) Math.PI) * 1.4F * p_225597_3_; this.mRightFront.rotateAngleX = MathHelper.cos(p_225597_2_ * 0.6662F + (float) Math.PI) * 1.4F * p_225597_3_; this.mRightBack.rotateAngleX = MathHelper.cos(p_225597_2_ * 0.6662F) * 1.4F * p_225597_3_; } |
Как видите, тут указаны углы для поворота головы, а также движения ног. Запускаем снова!
Ура! Теперь новая модель еще и умеет совершать базовые движение, т.е. ходить и смотреть. Уже неплохо. На этом пока всё, в следующий раз будет много работы конкретно с классом сущности, но при этом изменения будут и в коде модели и не только. Так что не переключайте 😀
Пингбэк: [1.15.2] Улучшаем интеллект своего моба – GeekStand
Пингбэк: Создание модов для Minecraft 1.15 – GeekStand
А как модель нашла текстуру?
Пингбэк: Свои модели для мобов в Minecraft Optifine – GeekStand