初心者modderの備忘録

マイクラのmodを作りたくて、初めて見たのですが難しくて忘れそうなので自分用の備忘録も兼ねてブログにしようと思います

5日目 Mobの追加 (1.15 ver)

前回の終わりにストラクチャーブロックを使った構造物の追加について書くと言ったのですが、いろいろ忘れそうなので少し予定を変更してMobEntityの追加を先に書きます。

Mobの追加は1.12以前と比べると特に登録の仕方が結構変わっていました。

1. ~Entity.class
2. ~Model.class
3. ~Renderer.class
の3つのクラスを作成する点は1.12以前と同様です。
これらのクラスの詳細は後述しますので、先に登録について書いておきます。

まずなにかクラスを作成し、そこで宣言および登録をします。今回はModEntitiesというクラスを作成しました。
そして、1.13以降では登録するのはEntityではなくEntityTypeなのでEntityTypeを宣言して、EntityType.Builderで作成します。
その際にEntityClassificationやサイズ等を設定します。
その後、registerEntities内で、registryNameを設定してから、登録します。この辺はアイテムやブロックの登録と同様だと思います。


続いて、Entityの場合にはレンダリングの登録が必要になります。
レンダリングはクライアントでのみ行なわれるので、登録用のメソッドはMainModクラスのclientRegistriesで呼びだします。
登録自体は比較的単純で
RenderingRegistry.registerEntityRenderingHandler(EntityType, ~~Renderer::new);
と打ち込めばOKです。

1.13および1.14では、第一引数がEntityTypeではなく~~Entity.classかもしれません (未検証)。



ModEntities.class

@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
public class ModEntities
{
	public static final EntityType<ExampleEntity> EXAMPLE_ENTITY = EntityType.Builder.<ExampleEntity>create(ExampleEntity::new,EntityClassification.CREATURE)
			.size(1.0f, 2.2f)
			.setShouldReceiveVelocityUpdates(false)
			.build("example_entity");

	public static final EntityType<TestEntity> TEST_ENTITY = EntityType.Builder.create(TestEntity::new,EntityClassification.CREATURE)
			.size(1.0f, 1.5f)
			.setShouldReceiveVelocityUpdates(false)
			.build("test_entity");

	@SubscribeEvent
	public static void registerEntities(final RegistryEvent.Register<EntityType<?>> event)
	{
		EXAMPLE_ENTITY.setRegistryName(TestMod.MODID, "example_entity");
		TEST_ENTITY.setRegistryName(TestMod.MODID, "test_entity");

		event.getRegistry().registerAll(EXAMPLE_ENTITY, TEST_ENTITY);
	}

	//called by clientRegistries method in MainMod class
	@OnlyIn(Dist.CLIENT)
	public static void clientInit()
	{
		RenderingRegistry.registerEntityRenderingHandler(EXAMPLE_ENTITY, ExampleRenderer::new);
		RenderingRegistry.registerEntityRenderingHandler(TEST_ENTITY, TestRenderer::new);
	}
}

TestMod.class

~~~
private void clientRegistries(final FMLClientSetupEvent event)
	{
		ModEntities.clientInit();
	}
~~~


以上が登録なので、続いて作成する3つのクラスについて書いていきます。




まず1つ目の~Entity.classは、そのEntityの挙動等を決めるメインとなるクラスです。
registerGoals()で基本となる行動を決めたり、registerAttributes()で体力や速度を決めます。
このクラスは他にも設定できる項目が非常にたくさんあるので、今回は牛の行動パターンをまねたシンプルなMobを追加します。
自分の追加したい挙動に近いバニラに存在するEntityのコードを参考に編集するのが良い気がします。


TestEntity.class

public class TestEntity extends AnimalEntity
{

	protected TestEntity(EntityType<? extends AnimalEntity> type, World worldIn)
	{
		super(type, worldIn);
	}

	@Override
	protected void registerGoals()
	{
		this.goalSelector.addGoal(0, new SwimGoal(this));
		this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D));
		this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
		this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.fromItems(Items.WHEAT), false));
		this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D));
		this.goalSelector.addGoal(5, new WaterAvoidingRandomWalkingGoal(this, 1.0D));
		this.goalSelector.addGoal(6, new LookAtGoal(this, PlayerEntity.class, 6.0F));
		this.goalSelector.addGoal(7, new LookRandomlyGoal(this));
	}

	protected void registerAttributes()
	{
		super.registerAttributes();
		this.getAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(10.0D);
		this.getAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.25D);
	}

	@Override
	public TestEntity createChild(AgeableEntity ageable)
	{
		return ModEntities.TEST_ENTITY.create(world);
	}

}


続いて、Modelを設定します。Modelに関しては1.12以前と同様にTabulaを使って作成するのが良いと思います。

Tabulaに関する説明は長くなるので後述し、とりあえず作成したコードを乗せておきます。


TestModel.class

public class TestModel extends EntityModel<TestEntity>
{
    public ModelRenderer body;
    public ModelRenderer head;
    public ModelRenderer arm1;
    public ModelRenderer arm2;

    //func_228301_a_ is addBox
	public TestModel()
	{
		this.textureHeight = 64;
		this.textureWidth =128;

        this.body = new ModelRenderer(this, 0, 0);
        this.body.setRotationPoint(0.0F, 3.6F, 0.0F);
        this.body.func_228301_a_(-5.0F, -5.0F, -5.0F, 10, 10, 10, 0f);

        this.arm1 = new ModelRenderer(this, 40, 0);
        this.arm1.setRotationPoint(-7.7F, 10.0F, 0.0F);
        this.arm1.func_228301_a_(-0.5F, 0.0F, -0.5F, 1, 10, 1, 0f);
        this.setRotationOffset(arm1, 0.0F, 0.0F, 0.3490658503988659F);

        this.arm2 = new ModelRenderer(this, 44, 0);
        this.arm2.setRotationPoint(7.7F, 10.0F, 0.0F);
        this.arm2.func_228301_a_(-0.5F, 0.0F, -0.5F, 1, 10, 1, 0f);
        this.setRotationOffset(arm2, 0.0F, 0.0F, -0.3490658503988659F);

        this.head = new ModelRenderer(this, 40, 0);
        this.head.setRotationPoint(0.0F, 16.0F, 0.0F);
        this.head.func_228301_a_(-7.5F, -7.5F, -7.5F, 15, 15, 15, 0f);
	}

	//This method is setRotationAngles
	@Override
	public void func_225597_a_(TestEntity p_225597_1_, float p_225597_2_, float p_225597_3_, float p_225597_4_, float p_225597_5_, float p_225597_6_)
	{
		float f = MathHelper.sin(this.swingProgress * (float)Math.PI);
		float f1 = MathHelper.sin((1.0F - (1.0F - this.swingProgress) * (1.0F - this.swingProgress)) * (float)Math.PI);
		this.arm1.rotateAngleZ = 0.0F;
		this.arm2.rotateAngleZ = 0.0F;
		this.arm1.rotateAngleY = -(0.1F - f * 0.6F);
		this.arm2.rotateAngleY = 0.1F - f * 0.6F;
		float f2 = -(float)Math.PI / 2.25F;
		this.arm1.rotateAngleX = f2;
		this.arm2.rotateAngleX = f2;
		this.arm1.rotateAngleX += f * 1.2F - f1 * 0.4F;
		this.arm2.rotateAngleX += f * 1.2F - f1 * 0.4F;
		this.arm1.rotateAngleZ += MathHelper.cos(p_225597_4_ * 0.09F) * 0.05F + 0.05F;
		this.arm2.rotateAngleZ -= MathHelper.cos(p_225597_4_ * 0.09F) * 0.05F + 0.05F;
		this.arm1.rotateAngleX += MathHelper.sin(p_225597_4_ * 0.067F) * 0.05F;
		this.arm2.rotateAngleX -= MathHelper.sin(p_225597_4_ * 0.067F) * 0.05F;
	}

	//This method is render
	@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_)
	{
		body.func_228309_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_, p_225598_5_, p_225598_6_, p_225598_7_, p_225598_8_);
		head.func_228309_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_, p_225598_5_, p_225598_6_, p_225598_7_, p_225598_8_);
		arm1.func_228309_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_, p_225598_5_, p_225598_6_, p_225598_7_, p_225598_8_);
		arm2.func_228309_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_, p_225598_5_, p_225598_6_, p_225598_7_, p_225598_8_);
	}

	private void setRotationOffset(ModelRenderer renderer, float x, float y, float z)
	{
		renderer.rotateAngleX = x;
		renderer.rotateAngleY = y;
		renderer.rotateAngleZ = z;
	}

}


最後にテクスチャーを設定するためのRendererクラスを作成します。
このクラスはそんなに設定することはないです。


TestRenderer.class

public class TestRenderer extends MobRenderer<TestEntity, TestModel>
{
	public TestRenderer(EntityRendererManager p_i50961_1_)
	{
		super(p_i50961_1_, new TestModel(), 0.5f);
	}

	@Override
	public ResourceLocation getEntityTexture(TestEntity entity)
	{
		return new ResourceLocation(TestMod.MODID, "textures/entity/test_entity.png" );
	}
}

スーパーコンストラクターで第二引数のModelと第三引数の影サイズを設定し、 getEntityTextureをオーバーライドしてテクスチャのファイルを設定します。

以上の3つのクラスを作成して、登録すればMobが追加できると思います。まだスポーンエッグは作成していないので、sumommコマンドでMobを出現させました。

f:id:json_fileman:20200416200825p:plain

今回は自作のモデルとバニラのスケルトンを元に編集したモデルの2匹のMobを追加しました。
スポーンエッグや、自然スポーンについては次回以降に書こうと思います。

                                                                                                                                                                                                                                                                  • -

Tabulaの使い方ですが、まず以下のサイトからTabulaをダウンロードします。

Tabula – Minecraft Modeler

Tabulaを使うためにはiChunUtilという前提Modが必要になるので、これもTabulaのバージョンと合ったものをダウンロードします。

iChunUtil - Mods - Minecraft


残念ながら、Tabulaの最新版は1.12.2なので出力されるコードの形式が少し違うものになってしまいますが、とりあえず1.12.2のバージョンを使うのが良いと思います。




この2つのModのjarファイルをバージョンのあったForgeのmodsフォルダに入れて起動すると、タイトル画面のModsボタンの横にTと書かれたボタンが現れるので、これをクリックするとTabulaが起動します。

f:id:json_fileman:20200415233422p:plain


起動すると画像のような画面になります。


f:id:json_fileman:20200415234152p:plain


少し見えづらいかもしれませんが、まず赤いまるで囲った+マークからプロジェクトを作成します。名前等はなんでも良いと思います。テクスチャのサイズは後で変えることもできますが、小さめのMobなら64×32、大きめなら128×64程度でいいのではないでしょうか。スケールは基本的に全部1でいいと思います。

青いまるで囲った部分はインポートで、自作のModelまたはバニラのModelをインポートすることが出来ます。バニラのMobの亜種みたいなものを作るときは、ここでインポートすると非常に楽になります。

次に、右下の紫で囲った部分から直方体の追加、削除、グループ化が出来ます。まとめて角度を変えたい時なんかは、グループボタンを押して作成されたフォルダに、グループ化したい直方体をいれてフォルダごと角度を変えるようなことができます。

直方体を追加したら、左のたくさん数字が並んでいるパラメーターを設定します。Dimensionが直方体の大きさ、Posisionが位置です。次にOffsetですが、これは回転の中心だと思っています。なので、頭などは青い球体が頭の中心にくるように、手足などは球体が始点にくるようにoffsetをずらせばいいと思います。

そんな感じで直方体をいくつか追加して目的のMobの形を作ればいいのですが、右したに竈門が出ているのでこれを見ながらMobの向きが正しいかどうかを確認しながら作ります。間違った向きで作成すると、例えばエサに寄ってくるときに横向きで寄ってきたり不自然な挙動になります。

完成したら、黄色で囲った4色のジグソーパズルのようなアイコンをクリックします。これでテクスチャを1枚の画像内に分配します。このときテクスチャサイズに十分な余分がないとfailed~~と出るので、その場合はテクスチャサイズを大きくします。赤丸の横のアイコンから変更できます。変更後、もう一度ジグソーパズルをクリックして、うまくいっていればメッセージは出ずに右上のテクスチャマップが更新されます。

ここまで行なったら、緑の丸で示した赤い矢印のアイコンをクリックしてエクスポートします。
テクスチャマップとjavaクラスをそれぞれエクスポートします。

その後、右上の「Open Working Directory」をクリックし、exportフォルダを開くと、作成したpng画像とjavaファイルが入っているはずです。


f:id:json_fileman:20200416001244p:plain


無事、出力されていればTabulaでの作業は以上です。

続いてModelクラスを使うのですが、1.12と1.13以降で少し変わっていて、そのままでは使えないため少し変更します。

コンストラクタの中身はメソッド名が変わっているだけで、大きな変化はありません。

次に、func_225597_a_とfunc_225598_a_をオーバーライドします。

func_225597_a_は1.14でのsetRotationAnglesに対応するメソッドで、歩くときに腕を振るような動的なパーツの動き等を設定できます。
今回はゾンビの動きをコピペしました。


func_225598_a_は1.14のrenderに相当するメソッドで、これによって壁画されます。
基本的には、それぞれのModelRendererのfunc_228309_a_ (これはModelRendererの中のrenderメソッド)にそのまま仮引数を渡せば良いと思います。ただ、子供状態などが存在する場合には、少し数値を変換してから渡す必要があるようです。バニラのウサギのモデル等が参考になりそうです。

renderまで設定すればModelクラスの作成は終わりです。
あとはテクスチャをペイントソフト等で編集して、assets.MODID.textures.entityに入れます。


今回は以上です。
次回は、元々予定していたストラクチャーブロックを使った構造物の追加か、Mobの追加の続き(スポーンエッグなど)について書こうと思います。