提供: Minecraft Modding Wiki
移動先: 案内検索
(ブロックの追加)
3行目: 3行目:
 
{{チュートリアル難易度|difficulty=1|clear=none}}
 
{{チュートリアル難易度|difficulty=1|clear=none}}
 
{{チュートリアルカテゴリー ‎|type=Render| difficulty=1}}
 
{{チュートリアルカテゴリー ‎|type=Render| difficulty=1}}
<p>ワールド上に設置できる簡単なブロックの追加方法</p>
 
 
==ICustomModelLoader利用==
 
==ICustomModelLoader利用==
 
注意:一度入れたMODを外すと、再び入れてもテクスチャが反映されなくなります。デバッグ時にご注意を。
 
注意:一度入れたMODを外すと、再び入れてもテクスチャが反映されなくなります。デバッグ時にご注意を。

2015年7月27日 (月) 00:25時点における版

この記事は"Minecraft Forge Universal 11.14.0.1296~"を前提MODとしています。

この記事はMCPのMappingが"stable_16"であることを前提としています。

Stone pickaxe.png
中級者向けのチュートリアルです。
C none.png
Renderに関係のあるチュートリアルです。

ICustomModelLoader利用

注意:一度入れたMODを外すと、再び入れてもテクスチャが反映されなくなります。デバッグ時にご注意を。

ソースコード

  • SampleMod.java
package com.example.examplemod;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.ItemMeshDefinition;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.item.ItemDye;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockPos;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;

@Mod(modid = "SampleMod", name = "SampleMod", version = "1.0", dependencies = "required-after:Forge@[1.8-11.14.0.1296,)", useMetadata = true)
public class SampleMod{
    public static final String MOD_ID = "SampleMod";

    @Mod.Instance("SampleMod")
    public static SampleMod INSTANCE;

    public static Block sampleBlock;

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event){
        sampleBlock = new Block(Material.rock){
            /**
             tintindexが動作しているか確認。
             */
            public int colorMultiplier(IBlockAccess worldIn, BlockPos pos, int renderPass){
                return ItemDye.dyeColors[renderPass];
            }
        }.setUnlocalizedName("sampleBlock").setCreativeTab(CreativeTabs.tabBlock);

        GameRegistry.registerBlock(sampleBlock, SampleItemBlock.class, "sampleBlock");

        //テクスチャ・モデル指定JSONファイル名の登録。
        if(event.getSide().isClient()){
            ModelLoader.setCustomMeshDefinition(Item.getItemFromBlock(sampleBlock), new ItemMeshDefinition(){
                public ModelResourceLocation getModelLocation(ItemStack stack){
                    return new ModelResourceLocation(new ResourceLocation(MOD_ID, "sampleBlock"), "inventory");
                }
            });
        }

        //ModelLoaderの登録。
        ModelLoaderRegistry.registerLoader(new SampleModelLoader());
    }

    @Mod.EventHandler
    public void init(FMLInitializationEvent event){

    }
}
  • SampleItemBlock.java
package com.example.examplemod;

import net.minecraft.block.Block;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

public class SampleItemBlock extends ItemBlock{
    public SampleItemBlock(Block block){
        super(block);
    }

    /**
     Layerが機能しているかの確認。
     */
    @SideOnly(Side.CLIENT)
    public int getColorFromItemStack(ItemStack stack, int renderPass){
        return block.colorMultiplier(null, null, renderPass);
    }
}
  • SampleModelLoader.java
package com.example.examplemod;

import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.ICustomModelLoader;
import net.minecraftforge.client.model.IModel;

public class SampleModelLoader implements ICustomModelLoader{
    /**
     どんな物にこのModelLoaderを適応するか返す。
     今回は、判定が面倒いのでこのMod内で作られたModelなら全て適応する。
     */
    public boolean accepts(ResourceLocation modelLocation){
        return modelLocation.getResourceDomain().equals(SampleMod.MOD_ID.toLowerCase());
    }

    /**
     描画するIModelを返す。 IResourceManagerがあれば、ここで外部ファイルを読み込んでIModelに渡せる。
     今回は、パターンが一つだけなのでそのまま返している。

     @see #onResourceManagerReload
     */
    public IModel loadModel(ResourceLocation modelLocation){
        return new SampleModel();
    }

    /**
     外部リソースを利用する場合はresourceManagerをここで取得しておく必要がある。
     今回は、内部で完結するので必要ない。
     */
    public void onResourceManagerReload(IResourceManager resourceManager){}
}
  • SampleModel.java
package com.example.examplemod;

import java.util.Collection;
import java.util.Collections;

public class SampleModel implements IModel{
    /**
     先に読み込まれていてほしいModelのLocationを返すっぽい。
     今回は、特に親とするModelはないので空Listを返す。
     */
    public Collection<ResourceLocation> getDependencies(){
        return Collections.emptyList();
    }

    /**
     Modelに必要なTextureを返す。
     といっても、バニラのTextureは特に指定しなくてもよく、独自にTextureを用意するときのみ返す。
     今回は、バニラの石テクスチャを用いるので空Listを返す。
     */
    public Collection<ResourceLocation> getTextures(){
        return Collections.emptyList();
    }

    /**
     Modelを焼成する。
     IModelで最も重要な部分。
     ここで返したいBakedModelを返せれば後はなんとかなる。
     */
    public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation,TextureAtlasSprite> bakedTextureGetter){
        return new SampleBakedModel(bakedTextureGetter);
    }

    /**
     上のbakeメソッドに渡されるModelStateのデフォルト。
     現在のバニラの実装でNPEが出る箇所はないが、念の為にダミーを返す。
     */
    public IModelState getDefaultState(){
        return ModelRotation.X0_Y0;
    }
}
  • SampleBakedModel.java
package com.example.examplemod;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.block.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.resources.model.ModelRotation;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IFlexibleBakedModel;
import net.minecraftforge.client.model.ISmartBlockModel;
import net.minecraftforge.client.model.ISmartItemModel;

import javax.vecmath.Vector3f;
import java.util.Collections;
import java.util.List;

public class SampleBakedModel implements IFlexibleBakedModel, ISmartBlockModel, ISmartItemModel{
    //Textureの保持。
    private TextureAtlasSprite stone;
    //BakedQuadを作るためのクラスを保持。
    private FaceBakery faceBakery = new FaceBakery();

    public SampleBakedModel(Function<ResourceLocation,TextureAtlasSprite> bakedTextureGetter){
        //ResourceLocationをTextureに変換する。
        stone = bakedTextureGetter.apply(new ResourceLocation("blocks/stone"));
    }

    /**
     IBlockStateにあわせてBlockのModelの形状を変えるときに用いる。
     IBlockStateはBlock#getActualStateでいろいろ詰め込めるのでこのメソッドの使い方は大変重要。
     今回は、Stateの変更がないのでそのまま返す。

     @see net.minecraft.block.Block#getActualState
     */
    public IFlexibleBakedModel handleBlockState(IBlockState state){
        return this;
    }

    /**
     ItemStackの状況でItemのModelの形状を変えるときに用いる。
     NBTは知っての通りいろいろ詰め込めるのでこのメソッドの使い方は大変重要。
     今回は、NBTやMetadataに変更がないのでそのまま返す。

     @see ItemStack#getTagCompound
     */
    public IFlexibleBakedModel handleItemState(ItemStack stack){
        return this;
    }

    /**
     面が不透明なBlockに接しているならば描画されない面を指定する。
     描画しないのでgetGeneralQuadsで指定するより大幅に軽量化できる。
     が、気づく通り銀の弾丸ではないので注意。
     もちろん、一つの面につき一つのQuadのような制約はない。
     今回は、目一杯1Block分の範囲を使っているのでここで全て指定している。

     @see #getGeneralQuads
     */
    public List<BakedQuad> getFaceQuads(EnumFacing face){
        //面の始点
        Vector3f from = new Vector3f(0, 0, 0);

        //面の終点
        Vector3f to = new Vector3f(16, 16, 16);

        //TextureのUVの指定
        BlockFaceUV uv = new BlockFaceUV(new float[]{0.0F, 0.0F, 16.0F, 16.0F}, 0);

        //面の描画の設定、ほぼ使用されないと思われる。
        //第一引数:cullface
        //第二引数:tintindex兼layer兼renderPass
        //第三引数:テクスチャの場所
        //第四引数:TextureのUVの指定
        BlockPartFace partFace = new BlockPartFace(face, face.getIndex(), new ResourceLocation("blocks/stone").toString(), uv);

        //Quadの設定
        //第一引数:面の始点
        //第二引数:面の終点
        //第三引数:面の描画の設定
        //第四引数:テクスチャ
        //第五引数:面の方向
        //第六引数:モデルの回転
        //第七引数:面の回転
        //第八引数:モデルの回転に合わせてテクスチャを回転させるか
        //第九引数:陰らせるかどうか
        BakedQuad bakedQuad = faceBakery.makeBakedQuad(from, to, partFace, stone, face, ModelRotation.X0_Y0, null, true, true);

        return Lists.newArrayList(bakedQuad);
    }

    /**
     面が不透明なBlockに接しているか否かを問わず、描画する面を指定する。
     見ての通り引数にEnumFacingはないので、このメソッドの中でfor(EnumFacing facing : EnumFacing.values())などしてあげる必要あり。
     もちろん、一つの面につき一つのQuadのような制約はない。
     今回は、全てgetFaceQuadsで指定するので空Listを返す。

     @see #getFaceQuads
     */
    public List<BakedQuad> getGeneralQuads(){
        return Collections.emptyList();
    }


    /**
     BakedQuadのVertexの書式を指定するっぽい?
     使用されている痕跡がないので取り敢えずデフォルトらしきものを返す。
     */
    public VertexFormat getFormat(){
        return DefaultVertexFormats.BLOCK;
    }

    /**
     周りの環境に影響を受けるか。
     trueなら影とかがつくようになる。
     */
    public boolean isAmbientOcclusion(){
        return true;
    }

    /**
     GUI内で3D描画するかどうか。
     Blockなのでtrue。
     */
    public boolean isGui3d(){
        return true;
    }

    /**
     通常、falseである。
     */
    public boolean isBuiltInRenderer(){
        return false;
    }

    /**
     パーティクルに使われる。
     Randomを通せば全部の面をパーティクルにのせるとかもできるかもしれない。
     */
    public TextureAtlasSprite getTexture(){
        return stone;
    }

    /**
     非推奨メソッド。デフォルトを返しておけば間違いはないはず。
     */
    public ItemCameraTransforms getItemCameraTransforms(){
        return ItemCameraTransforms.DEFAULT;
    }
}
  • sampleblock.json(BlockState用)
{
  "variants": {
    "normal": {"model": "samplemod:sampleBlock"}
  }
}

解説

SampleMod.java

/**
 tintindexが動作しているか確認。
 */
public int colorMultiplier(IBlockAccess worldIn, BlockPos pos, int renderPass){
    return ItemDye.dyeColors[renderPass];
}

tintindexが働いているかの確認のためにtintindexごとに色を割り当てている。

//ModelLoaderの登録。
ModelLoaderRegistry.registerLoader(new SampleModelLoader());

ModelLoaderを登録している。

SampleItemBlock.java

/**
 Layerが機能しているかの確認。
 */
@SideOnly(Side.CLIENT)
public int getColorFromItemStack(ItemStack stack, int renderPass){
    return block.colorMultiplier(null, null, renderPass);
}

Layerが働いているかの確認のためにLayerごとに色を割り当てている。

SampleModelLoader.java

/**
 どんな物にこのModelLoaderを適応するか返す。
 今回は、判定が面倒いのでこのMod内で作られたModelなら全て適応する。
 */
public boolean accepts(ResourceLocation modelLocation){
    return modelLocation.getResourceDomain().equals(SampleMod.MOD_ID.toLowerCase());
}

/**
 描画するIModelを返す。 IResourceManagerがあれば、ここで外部ファイルを読み込んでIModelに渡せる。
 今回は、パターンが一つだけなのでそのまま返している。

 @see #onResourceManagerReload
 */
public IModel loadModel(ResourceLocation modelLocation){
    return new SampleModel();
}

Modに一つなどといった制約はないので可読性の観点からできるかぎり分けるべきである。と思う。

SampleModelLoader.java

/**
 どんな物にこのModelLoaderを適応するか返す。
 今回は、判定が面倒いのでこのMod内で作られたModelなら全て適応する。
 */
public boolean accepts(ResourceLocation modelLocation){
    return modelLocation.getResourceDomain().equals(SampleMod.MOD_ID.toLowerCase());
}

/**
 描画するIModelを返す。 IResourceManagerがあれば、ここで外部ファイルを読み込んでIModelに渡せる。
 今回は、パターンが一つだけなのでそのまま返している。

 @see #onResourceManagerReload
 */
public IModel loadModel(ResourceLocation modelLocation){
    return new SampleModel();
}

Modに一つなどといった制約はないので可読性の観点からできるかぎり分けるべきである。と思う。

sampleblock.json(BlockState用)

これはどうしても配置しなければならない。が、複数のBlockを追加する場合でもこのファイル一つあればStateMapperを用いることで適応できる。

他のファイルは、コメントのとおりである。