Modding > Peripheral


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

ComputerCraftに周辺機器を追加するModの製作について解説する。
対象:Minecraft Forgeを利用して独自のブロック、アイテムを追加できるModding初級者以上。

参考資料:
執筆時のバージョン:
  • ComputerCraft 1.52 for Minecraft 1.5.1




周辺機器追加Modの概要

周辺機器とは、Disk DriveやMonitorのように隣接したComputerやTurtle(以下まとめて「Computer」)から呼び出されて、様々な機能を提供するブロックである。

周辺機器はブロックとして実装し、ブロックのTileEntityにIPeripheralインターフェイスを実装することで、ComputerCraftから呼び出された時の動作を実装する。そのため、Minecraft Forgeを利用して独自のブロックを追加できる人なら、誰でも簡単に周辺機器追加Modを作ることができる。

周辺機器についての注意

周辺機器について推奨されていること:
  • 周辺機器のタイプ(getType()の戻り値)は他のタイプと重複しないものにする
  • 周辺機器のメソッド(callMethod())で引数を要求するものは渡された引数をチェックし、不正な場合は要求する引数を例外として投げる
    例: 引数が(数値、ブーリアン)なメソッドの引数が不正だよ例外 → throw new Exception("Expected number, boolean");
  • 周辺機器は複数のComputerから同時に接続され、またいつでも取り外されることに留意する。隣接した接続だけでなくWired Modemからの接続もあるので同時接続数は(上限も含め)不定。IComputerAccessインスタンスの保存やその他フィールドの扱いに注意。
    接続Computer毎に値を保存するにはIComputerAccess.getID()の戻り値をキーにしたHashMapなどを利用するとよい(attachでput、detachでremove)。競合が発生する可能性のあるアクセスを含む処理はsynchronizedでスレッドセーフにする
  • IPeripheralの一部のメソッド(callMethod、attach、detach)はComputerCraftのLuaスレッドから呼び出される。これらがMinecraftのオブジェクトへアクセスするときはスレッドセーフでなければならない。
    例: アイテムスロットを持つ周辺機器ではcallMethodとプレイヤー(Minecraft)のアイテムスロットへのアクセスが競合するかもしれない。双方の処理をsynchronizedでスレッドセーフにする

IComputerAccess.queueEvent()でイベントを発生させる際に推奨されていること:
  • イベントを発生させる場合、イベント名の先頭に周辺機器のタイプを付ける
    例: 周辺機器タイプ“button” → イベント名“button_pressed”
  • 発生させるイベントの第1引数には、周辺機器の接続名(IComputerAccess.getAttachmentName()の戻り値)を渡す
    例: computer.queueEvent( "button_pressed", new Object[] {computer.getAttachmentName()} )

基本的な周辺機器追加Modの例

このサンプルは以下の3クラスからなる。
  • BasicPeripheral
  • BlockBasicPeripheral
  • TileEntityBasicPeripheral
簡略化のためProxyシステムは利用していない。

なお、このサンプルmodの前提modは【MinecraftForge】と【ComputerCraft】である。
(ただし、ComputerCraftAPIを同梱して配布すれば、【MinecraftForge】だけでもブロックは追加される。)

BasicPeripheral.java

Modのメインクラス。@ModアノテーションによりForgeModLoaderにロードされる。

package sample.peripheral;
 
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.Configuration;
import net.minecraftforge.common.Property;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkMod;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.LanguageRegistry;
import dan200.computer.api.ComputerCraftAPI;
 
@Mod(modid="BasicPeripheral", name="BasicPeripheral", version="0.0.0", dependencies="after:CCTurtle")
@NetworkMod(clientSideRequired=true, serverSideRequired=false)
public class BasicPeripheral
{
	public int blockIdBasicPeripheral;
	public static Block blockBasicPeripheral;
 
	@Mod.PreInit
	public void preInit(FMLPreInitializationEvent event)
	{
		Configuration cfg = new Configuration(event.getSuggestedConfigurationFile());
		cfg.load();
 
		Property Prop = cfg.getBlock("basicPeripheralBlockID", 1211);
		blockIdBasicPeripheral = Prop.getInt();
 
		cfg.save();
	}
 
	@Mod.Init
	public void init(FMLInitializationEvent event)
	{
		blockBasicPeripheral = (new BlockBasicPeripheral(blockIdBasicPeripheral))
			.setUnlocalizedName("basicPeripheral");
 
		GameRegistry.registerBlock(blockBasicPeripheral, "BasicPeripheral");
 		GameRegistry.registerTileEntity(TileEntityBasicPeripheral.class, "TileEntityBasicPeripheral");
 
		LanguageRegistry.addName(blockBasicPeripheral, "Basic Peripheral");
 
		GameRegistry.addRecipe(
			new ItemStack(blockBasicPeripheral, 1),
				new Object[]
				{
					"XXX", "XYX", "XXX",
					Character.valueOf('X'), Block.stone,
					Character.valueOf('Y'), Block.dirt
				});
 
		CreativeTabs tabComputerCraft = ComputerCraftAPI.getCreativeTab();
		if(tabComputerCraft != null) {
			blockBasicPeripheral.setCreativeTab(tabComputerCraft);
		} else {
			blockBasicPeripheral.setCreativeTab(CreativeTabs.tabBlock);
		}
	}
 
}

@Modアノテーションを付加したクラスがForge Mod Loaderにロードされる。ここでModの情報も登録している。特筆すべきは dependencies="after:CCTurtle" という値で、これはこのModをComputerCraftよりも後に読み込ませる効果がある。

@Mod.PreInitアノテーションを付加したメソッド preInit (@Mod.Initメソッドの前に呼び出される)でコンフィグファイルを読み込み、サンプル周辺機器のブロックIDを取得している。ブロックIDのデフォルト値は 1211

@Mod.Initアノテーションを付加したメソッド init (初期化時に呼び出される)で周辺機器ブロックやTileEntity、ブロックのレシピを登録している。サンプルの周辺機器ブロックはブロックIDを前述のコンフィグから取得した値に設定している。ゲーム内での表示名は Basic Peripheral 。レシピは中央に土ブロックを置き周囲を石ブロックで囲った形。最後に周辺機器ブロックをクリエイティブ・インベントリに登録している。ComputerCraftAPIのメソッドでComputerCraftのクリエイティブタブを取得し、タブがあった場合はそこに登録、なかった場合はバニラのBlockタブに登録している。

周辺機器は基本的にComputer側から呼び出されて動くため、ここで何かをComputerCraft側に登録するような処理は存在しない。

BlockBasicPeripheral.java

周辺機器ブロック本体。BlockContainerのサブクラスとし、createNewTileEntityでIPeripheralを実装したTileEntityのサブクラスのインスタンスを返す。

package sample.peripheral;
 
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.texture.IconRegister;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
 
public class BlockBasicPeripheral extends BlockContainer
{
 
	protected BlockBasicPeripheral( int blockId )
	{
		super(blockId, Material.rock);
 	}
 
	@Override
	public TileEntity createNewTileEntity(World world) {
		return new TileEntityBasicPeripheral();
	}
 
	@Override
	@SideOnly(Side.CLIENT)
	public void registerIcons(IconRegister par1IconRegister)
	{
		this.blockIcon = Block.stoneSingleSlab.getBlockTextureFromSide(0);
	}
 
}

このサンプルでは、createNewTileEntityで後述のTileEntityBasicPeripheralのインスタンスを返している。また、ブロックの性質を石ブロックに、外見を六面とも石半ブロックの下面と同じテクスチャにしている。

TileEntityBasicPeripheral.java

IPeripheralを実装したTileEntityのサブクラスで周辺機器ブロックの周辺機器としての動作を実装している。

package sample.peripheral;
 
import net.minecraft.tileentity.TileEntity;
import dan200.computer.api.IComputerAccess;
import dan200.computer.api.IPeripheral;
 
public class TileEntityBasicPeripheral extends TileEntity
	implements IPeripheral
{
 
	@Override
	public String getType()
	{
		return "basic";
	}
 
	@Override
	public String[] getMethodNames()
	{
	    return new String[] { "test" };
	}
 
	@Override
	public Object[] callMethod( IComputerAccess computer, int method, Object[] arguments )
			throws Exception
	{
		switch( method )
		{
		case 0:
			if( arguments.length < 1 ) {
				throw new Exception("Expected argument");
			}
			return new Object[] { arguments[0] };
		}
		return null;
	}
 
	@Override
	public boolean canAttachToSide(int side)
	{
		return true;
	}
 
	@Override
	public void attach( IComputerAccess computer )
	{
 
	}
 
	@Override
	public void detach( IComputerAccess computer )
	{
 
	}
 
}

このサンプルでは、周辺機器のタイプ名として basic を返し、ゲーム内でComputerが呼び出せる周辺機器のメソッド(以下「周辺機器メソッド」)として test を実装している。

周辺機器の各メソッドの名前はgetMethodNamesの戻り値で指定する。各周辺機器メソッドの処理は、callMethod内で第二引数の値をswitch構文などで分岐させて実装する。callMethodの第二引数には、Computerから呼び出された周辺機器メソッドの「getMethodNamesの戻り値の配列」でのインデックス番号が入る。

このサンプルの test の場合はgetMethodNamesの戻り値の配列でのインデックス番号が 0 であるため、 test を呼び出した時のcallMethodの第二引数の値(int method)は 0 になる。したがって、methodが 0 のときに test の処理を実行するように実装すればよい。 test の処理の内容は、呼び出し時に引数がなかった場合はComputerにエラーメッセージを表示し、引数があった場合は最初の引数の値を返すというもの。