Subscribed unsubscribe Subscribe Subscribe

Modのロードの流れとロードに介入する方法

https://github.com/r3qu13m/PythonModLoader を書いた時に調べたことのメモ

Minecraft 1.4.7, Minecraft Forge #534を対象とする.

基本的な流れ

まず最初に cpw.mods.fml.common.Loader#loadMods から始まる. ファイル自体の読み込みと識別, クラス探索はここでは置いておい, 実際にModのpreinit, init等をどのようにして呼んでいるのかについて見ていく.

まずModを探索し, 依存関係によってModをソートした後にリスト自体をImmutableListへ突っ込む. 493行目の時点でModのリストアップは終わっているので, 後はLoaderStateをCONSTRUCTING, PREINITIALIZATION, INITIALIZATIONとtransitionさせるだけ. この時, EventBusへイベントを流していくことでModのinit, preinit等を呼ぶことになる.

ModContainer

coremodsを作るなら大体cpw.fml.common.DummyModContainerを実装する(はず)だが, これについてちゃんと調べた. ModContainer自体はインターフェースで, Modのメインファイルや名前, ModIdについての情報のメソッドを定義している. この中にregisterBusというメソッドがあり, 次のような定義となっている.

    /**
     * Register the event bus for the mod and the controller for error handling
     * Returns if this bus was successfully registered - disabled mods and other
     * mods that don't need real events should return false and avoid further
     * processing
     *
     * @param bus
     * @param controller
     */
    boolean registerBus(EventBus bus, LoadController controller);

このbus変数はその名の通りModのロードについてのEventBus. cpw.fml.common.FMLModContainerクラスではこの時にオブジェクトとして自分自身を登録しており, 次のようなメソッドでイベントを受け取っている.

cpw.fml.common.FMLModContainer 474〜495行目

    @Subscribe
    public void handleModStateEvent(FMLEvent event)
    {
        Class<? extends Annotation> annotation = modAnnotationTypes.get(event.getClass());
        if (annotation == null)
        {
            return;
        }
        try
        {
            for (Object o : annotations.get(annotation))
            {
                Method m = (Method) o;
                m.invoke(modInstance, event);
            }
        }
        catch (Throwable t)
        {
            controller.errorOccurred(this, t);
            Throwables.propagateIfPossible(t);
        }
    }

各フィールドの意味については実際にコードを読んでもらえば分かるが, 見たままアノテーションとイベントのクラスが一致するメソッドを順に呼び出していることが分かる. これは結局, @Mod.Init等のアノテーションの付いているメソッドを順に呼び出していることに等しい.

以上で大まかな流れは終わり.

実際の介入

ここに介入するタイミングはいくつか存在する. 例えば,

  • Modのメインクラスのコンストラクタ (LoaderState.CONSTRUCTING)
  • @Mod.PreInit (LoaderState.PREINITIALIZATION)
  • @Mod.Init (LoaderState.INITIALIZATION)

等が挙げられる. ここでは, Mod自体にinit, preinit, postinitを持たせたかったので, コンストラクタで書くことにした. コンストラクタ内ではReflectionHelper経由でLoadController, EventBus, Modの一覧のリストを取得し, このうちModのリストについてはImmutableListになっているのでこれをLinkedListへ変更する. その後, 適宜検索・JavaのクラスとしてのModを構築し, Mod一覧のリストへこれを追加する. 注意点として, LoadController#getActiveModList()で取得できるリストへModContainerを追加しておかないとActive ModとLoaded Modの数に差が発生し, 見た目だけだが少し微妙になってしまう. また, registerBusも自前で呼ぶ必要がある. 最後にReflectionHelperで操作後のModのリストをセットする. これにより, この後のpreinit, init, postinitでのイベントをModContainerの実装側で受け取れるようになる. PythonModLoaderのPythonModContainer.javaのhandleModStateEventメソッドでは簡単にこれを受け取るだけのメソッドを実装して利用している.

PythonModLoaderではJythonを利用したが, Jythonは少し遅いので実際に使うのであれば標準のRhinoインタプリタや高速ということで有名なJPHPを利用するほうが良いだろう. ファイル監視とRhinoインタプリタを組み合わせれば, JavaScriptで実装を書けるアイテムレンダラなんかも作ることが出来ると思う.

ネタのメモ

  • ケルトンのレアドロにSkeleton's Arrow(テクスチャは普通の矢にポーションキラキラつけただけ)、これを弓で撃ってクリーパーを倒すとレアドロでレコード - coremods?
  • スライムのドロップにSlime Bloodを追加, これでSlime-Bloody Swordを作って使うとスライムが分裂せずに一発で倒せる(ドロップは一体分) - スライムの死亡判定周りでcoremods化必要?
  • BuildCraftの木エンジンがエネルギーパイプに繋がるようにする - 書いた
  • BuildCraftのクァーリー周りの処理を書き直す - 面倒
  • Forestory for MinecraftのアイテムがLP Extractor Mk[1-3]で吸いだされた時にNullPointerExceptionで落ちるバグの修正 - デコンパイラ作ってから or coremodsでファイル差し替えする?
  • 和製版 Additional Pipes 2.0.10のテレポパイプでLPネットワークがつながらないバグの修正 - ソースコードあるしやっておきたい
  • RP2のコンフィグを読み込めるパーサー - RP2のアイテム使いたい
  • フィラーのアイテム回収モード追加Mod - 書いたけど公開面倒なので放置
  • 1.2.5のModの移植 - 面倒
  • コンパイラ作る - 面倒
  • ライブラリの更新, Java 8への対応 - 面倒そう
  • リソースパック?とか1.5.xのModを無修正で使えるようにするようなローダー or コンバーター - 確実に面倒
  • 描画方式の最適化 - 大体検討はついてる
  • Bukkitプラグインをロード出来るようにするcoremods - 面倒
Remove all ads

サウンドシステムの扱いについて

Minecraft 1.4.7, Minecraft Forge #534でサウンドシステムを触った時のメモ。


MinecraftのサウンドシステムはPaul's Code SoundSystem (http://www.paulscode.com/forum/index.php?topic=4.0)を利用していることがクレジット等からわかるので、まずはそのソースコードを読む。また、Forge側にもいくつかサウンド関連のイベントが存在するのでそちらも読む。列挙すると、

  • SoundEvent(抽象クラス)
  • SoundSetupEvent
  • SoundLoadEvent
  • PlaySoundSourceEvent
  • PlayStreamingSourceEvent
  • PlaySoundEffectSourceEvent

  • SoundResultEvent(抽象クラス)

  • PlaySoundEvent
  • PlayBackgroundMusicEvent
  • PlaySoundEffectEvent

となる。今回やりたいのはいわゆる効果音の再生なので、用があるのはSoundSetupEventだけになる。

そのSoundSetupEventは、SoundManagerクラスのtryToSetLibraryAndCodecsメソッドでイベントバスに投げられている。実際にやってみると分かるが、このイベントが投げられるのは@Mod.PreInit@Mod.Initの間なので、イベントハンドラの設定は@Mod.PreInitでするべき。

SoundSetupEventは、SoundManagerの初期化終了後に呼び出されるイベントという意味になる。今回のような時はいちいち取得するのも面倒なので、このイベントでSoundManagerを取得し、適当なフィールドに保存しておくのが良いかと思う。

@SideOnly(Side.CLIENT)
public class SoundEventHandler {
  public static SoundManager manager;
  
  @ForgeSubscribe
  public void onSoundSetup(SoundSetupEvent event) {
    manager = event.manager;
  }
}

サウンドを利用するにはまず登録が必要となる。この登録処理はどこで行っても良いが、私はイベントハンドラ内で行っている。 注意点として、.minecraft/resources/***/***のような実際のファイルシステム上のパスではなくSomeMod.class.getResource("***/***")のような取得方法をしている場合、開発環境ではうまく行っても実際にはうまくいかない。理由としては、恐らく内部実装がjar(zip)内データの扱いが出来ないことが原因だと推測する。

登録は以下の形にする。

manager.addSound(登録名, 実体へのFileインスタンス);

登録名とは、"aaa/bbb/ccc.ogg"というような任意の文字列 + 拡張子の形の文字列、実体へのFileインスタンスは、実際のファイルへのFile(java.io.File)インスタンスである。


アイテムのonItemUse内で音を再生する時、次のように書く。

public boolean onItemUse(ItemStack par1ItemStack, EntityPlayer par2EntityPlayer, World par3World, int par4, int par5, int par6, int par7, float par8, float par9, float par10) {
    if (FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT) {
        System.out.printf("[+] Play Sound!\n");
        SoundEventHandler.manager.playSound("aaa.bbb.ccc", (float) par2EntityPlayer.posX + 0.5F, (float) par2EntityPlayer.posY + 0.5F, (float) par2EntityPlayer.posZ + 0.5F, 1.0F, 1.0F);
    }
    return true;
}

playSoundのパラメータは、登録名から拡張子を除いたもの, 再生するX座標, 再生するY座標, 再生するZ座標, ボリューム, ピッチとなっている。


Streaming等の他の要素についても同様に進めることが出来るが、それについてはソースコードを参照。

チェストのようなブロックのレンダリング処理周りについて

Minecraft 1.4.7, Minecraft Forge #534でチェストのようにアニメーションするブロックを書いた時に感じたことのメモ。

目的はいわばIronChestsのダイヤモンドチェストのような機能を提供するチェストブロックを書くこと。基本はEnderChestのような感じなので、まずはEnderChest関係のコードを読み、実装する。

別に星形等の変な形を作るのでもない限り、ここはMinecraftのデフォルトのチェストのモデルを活用すべきだろう。また、TileEntity, レンダラなどを先のEnderChestの実装から考えると次のような構成が考えられる。->のように短い矢印はextends, implements等継承関係を表し、それ以外の矢印は単なる関係を表している。

BlockMyChest <- BlockContainer
     |
     |
     +--> TileEntityMyChest <- TileEntity, IInventory
     |            |
     +------------+----------> TileEntityMyChestRenderer
                                          Λ
                                          |
                               TileEntitySpecialRenderer

これだけを実装し、適宜登録処理をすればひと通りは動く。しかし、よくよく考えるとTileEntitySpecialRendererの実装範囲はワールド上のブロックのレンダリング処理のみを提供しており、インベントリでの描画についてはノータッチとなる。EnderChestやChestはRenderBlocksクラスのハードコーディング部分でうまく処理分けされているのでちゃんと描画される。しかし、今回のようにデフォルト処理を流用して構築したクラスにはハードコーディングされているために対応できず、結果どうなるかといえばチェストが描画されてしまうのである。

これを解決するには以下のような方法がある。

  • ハードコーディング部分をcoremodsやクラス書き換えを用いて拡張可能なコードに置き換える
  • IronChestで用いられている方法を用いる(後述)
  • 地道にアイテムレンダラを書く(デフォルト処理の流用の恩恵を受けない)

2番目の手法について、実際のソースコードを示す。

ironchest/ClientProxy.java at 2735d04de27f5244458d30818e55ad38fe3ec1d6 · cpw/ironchest · GitHub

ironchest/IronChestRenderHelper.java at 2735d04de27f5244458d30818e55ad38fe3ec1d6 · cpw/ironchest · GitHub

この手法は、RenderBlocksの以下の箇所に関連している。

ChestItemRenderHelper.instance.renderChest(par1Block, par2, par3);

RenderTypeがChest/EnderChestの場合(つまり、getRenderType() == 22)の、ChestItemRenderHelper.instance.renderChestというメソッドを利用してインベントリ描画を行っている。

IronChestでは、このChestItemRenderHelper.instanceフィールドに着目した。instanceフィールドは次のように宣言されている。

public static ChestItemRenderHelper instance = new ChestItemRenderHelper();

見て分かるとおり、const指定がないのでこのフィールドには再代入することが可能だ。さらに、ChestItemRenderHelperクラスはfinal指定がされていないため、拡張クラスを作ることが出来る。これらから、ChestItemRenderHelperクラスの拡張クラスを作り、ChestItemRenderHelper.instanceフィールドを拡張したクラスのインスタンスに置き換えることで、レンダリング処理を任意の差し替えることが可能となる。

この手法にはデメリットが存在する。というのも、見て分かる通りこの手法でのキーとなっているChestItemRenderHelper.instanceフィールドは何度でも書き換えることが出来てしまう。その上、同様の手法を他のModで利用しようとすると、何も考えずに書くと競合してどちらかのModのレンダリング結果がおかしくなってしまう。これについては、個別に対応するか、自動判定処理を書いて一般的に適用出来るようにする他ないだろう。その意味では、この手法は非常にその場任せの適当なコードに見える。(しかし、本来書かないといけなかったアイテムレンダラよりは何十倍も短いコード量となる。)

ITickHandler#tickStart, tickEndの引数の調査

Minecraft 1.4.7, Minecraft Forge #534。

ゲーム内でTickごとに実行される処理を記述するのに使われるITickHandlerと呼ばれるインターフェースが存在する。その中で、実際にTickの始まり/終わりのそれぞれで呼ばれるメソッドがtickStart, tickEndだが、それぞれ定義は次のようになっている。

    /**
     * Called at the "start" phase of a tick
     * 
     * Multiple ticks may fire simultaneously- you will only be called once with all the firing ticks
     * 
     * @param type
     * @param tickData
     */
    public void tickStart(EnumSet<TickType> type, Object... tickData);
    
    /**
     * Called at the "end" phase of a tick
     * 
     * Multiple ticks may fire simultaneously- you will only be called once with all the firing ticks
     * 
     * @param type
     * @param tickData
     */
    public void tickEnd(EnumSet<TickType> type, Object... tickData);

typeはともかく、tickDataはこれだけでは何が渡されるのかも分からない。そこで、呼び出し元を探してこれをまとめてみることにした。

以下、基本的なITickHandlerの理解はできているものとし、TickTypeによって場合分けをする。

基本的に、tickStart/tickEndはcpw.mods.fml.common.FMLCommonHandlerから呼び出される。そして、 on(Pre|Post)\w+Tick()メソッドで実際に値を代入して実行しているため、これを見ることで調査が可能。

TickType.WORLD

FMLCommonHandler#on(Pre|Post)WorldTickメソッドが該当。実際の引数はWorldのインスタンス

TickType.WORLDLOAD

FMLCommonHandler#onWorldLoadTickメソッドが該当。こちらはワールドロード時に呼び出され、読み込み可能なWorld全てについて一度ずつ呼び出される。引数はTickType.WORLDと同じく該当するWorldのインスタンス

このTickTypeの時は、tickEndは呼び出されない。

TickType.SERVER

FMLCommonHandler#on(Pre|Post)ServerTickメソッドが該当。引数はなし。

TickType.CLIENT

FMLCommonHandler#on(Pre|Post)ClientTickメソッドが該当。引数はTickType.SERVERと同様になし。

TickType.Render

FMLCommonHandler#onRenderTick(Start|End)メソッドが該当。引数はPartial Render Timeと呼ばれる補正用?の値。一度レンダリング関連で使ったことがあるものの理解はできていない。

TickType.Player

FMLCommonHandler#onPlayer(Pre|Post)Tickメソッドが該当。引数はEntityPlayerインスタンスで、注意点としてはServer/Client両方で呼び出されること。

Ubuntu 14.04で開発環境を整える際のメモ

Minecraft 1.4.7 with Minecraft Forge #534の環境をUbuntu14.04で整えた際、いくつかエラーにあたったのでその対処法。

astyleについて

mcp付属のastyle.exeは少し古いバージョン(2.02.x)で、Ubuntu 14.04のapt-getで入るastyle 2.03と相違がある。このため、python install.pyを実行する前に fml/conf/astyle.cfgmax-instatement-indent=2 の箇所を削除、または max-instatement-indent=199等とする。これは、astyle 2.03から40から120の間の整数でなければならなくなったための処置である。しかし、このためにいくつかForgeのパッチを当てることができず、エラーが生じる箇所がある。RenderPlayer.javaの401行目、var21をvar22に書き換えれば(とりあえずは)動作する。エラーでpatchが失敗している場合は必ず該当ファイルと同階層に失敗したファイルの作業ファイルが残るため、問題があればこれを探して手でマージすれば良い。

JDK 1.8使用時のasmについて

Forgeの手に入れてくるライブラリの中に、Objectweb ASMと呼ばれるライブラリが存在する。しかし、このライブラリはForgeの手に入れてくるバージョンではJDK 1.8に対応しておらず、ライブラリを差し替える必要がある。これはライブラリ自体を最新のもの(私はasm-all-5.1.jarを利用した)に置き換えるだけなので大した作業にはならない。

LWJGLについて

デフォルトのLWJGLは古く、環境によっては動作しないことがあるため、これも入れ替える。asmと同様に最新版をダウンロードし、該当ファイルを差し替えるだけで動作する。私は2.9.3を利用した。 Linux上なので、natives以下にはlinux版のsoを入れないといけない。また、x86_64環境の場合はlib***64.soとなっているsoファイルをlib***.soへリネームしてやらないと、ビット数の違いでエラーを起こす。

その後、プロジェクト全体をリビルド、Client(必要ならServerも)の起動までを確認して構築終了となる。私はこの後全体に対してFormatをかけ、recompile→updatemd5を実行したが、これは任意で良いかと思う。

BuildCraft Utilsクラス

Minecraft 1.4.7, Minecraft Forge #534, BuildCraft 3.4.2の環境で、非常に便利なbuildcraft.core.utils.Utilsクラスについてまとめる。

BuildCraft 3.4.2のソースコードhttps://github.com/BuildCraft/BuildCraft/tree/6ab8a50b6408a75833a7ddc02e30c61d14f72756 に存在する。

フィールド

pipeMinPos/pipeMaxPos

public static final float pipeMinPos = 0.25F;
public static final float pipeMaxPos = 0.75F;

パイプのサイズ。これを変えると太さを調整できる?(意味は無いが)

pipeNormalSpeed

public static float pipeNormalSpeed = 0.01F;

アイテムがパイプを流れるスピード。

メソッド

addToRandomInventory

public static ItemStack addToRandomInventory(ItemStack stack, World world, int x, int y, int z, ForgeDirection from)

近傍インベントリを検索し、見つかった中からランダムに選んだインベントリへstackを入れ、インベントリに入ったのと同じサイズのItemStackを返す。fromはコード的には恐らく除外すべきDirectionを表しているのだが、BuildCraftの全コードの中でこの引数は全てForgeDirection.UNKNOWNだったので無視しても良いかと思う。これではわかりにくいので、サンプルとしてクァーリーの採掘のメソッドを読んでみる。

build.factory.TileQuarry 444行目〜471行目

   private void mineStack(ItemStack stack) {
        // First, try to add to a nearby chest
        ItemStack added = Utils.addToRandomInventory(stack, worldObj, xCoord, yCoord, zCoord, ForgeDirection.UNKNOWN);
        stack.stackSize -= added.stackSize;

        // Second, try to add to adjacent pipes
        if (stack.stackSize > 0) {
            Utils.addToRandomPipeEntry(this, ForgeDirection.UNKNOWN, stack);
        }

        // Lastly, throw the object away
        if (stack.stackSize > 0) {
            float f = worldObj.rand.nextFloat() * 0.8F + 0.1F;
            float f1 = worldObj.rand.nextFloat() * 0.8F + 0.1F;
            float f2 = worldObj.rand.nextFloat() * 0.8F + 0.1F;

            EntityItem entityitem = new EntityItem(worldObj, xCoord + f, yCoord + f1 + 0.5F, zCoord + f2, stack);

            entityitem.lifespan = BuildCraftCore.itemLifespan;
            entityitem.delayBeforeCanPickup = 10;

            float f3 = 0.05F;
            entityitem.motionX = (float) worldObj.rand.nextGaussian() * f3;
            entityitem.motionY = (float) worldObj.rand.nextGaussian() * f3 + 1.0F;
            entityitem.motionZ = (float) worldObj.rand.nextGaussian() * f3;
            worldObj.spawnEntityInWorld(entityitem);
        }
    }

addToRandomInventoryを呼び出し、帰ってきたItemStack(added)のstackSizeを引いた分が残りのItemStackとなることが分かる。450行目のifでstack.stackSize > 0という条件があるが、これは残りのスタック数のカウントだと取れるため、addToRandomPipeEntry(後述)を用いてパイプへ流している。また、それでも残りがある場合は(455行目)、EntityItemをスポーンさせてドロップアイテムとして出力するようにしている。

getPipeFloorOf

public static float getPipeFloorOf(ItemStack item)

実際にはitemに依らず、pipeMinPosが返される点、PipeItemsObsidianクラスの243行目でEntityPassiveItemのコンストラクタに指定されている点、レンダラクラスでのオフセット指定として使われている点から、パイプ内を流れるアイテムの高さの下限となる値を返すと推測される。

get2dOrientation

public static ForgeDirection get2dOrientation(Position pos1, Position pos2)

pos1から見たpos2の方向をForgeDirectionで返す。この時見られるのはXZのみで、Y軸はここでは無視される(get3dOrientationを利用する。)

get3dOrientation

public static ForgeDirection get3dOrientation(Position pos1, Position pos2)

pos1から見たpos2の方向をForgeDirectionで返す。並行であれば、get2dOrientationが返り、それ以外であればアングルによってForgeDirection.UP/DOWNが返る。

addToRandomPipeEntry

public static boolean addToRandomPipeEntry(TileEntity tile, ForgeDirection from, ItemStack items)

接しているランダムなパイプへItemStackを送出する。送出時点でitems.stackSizeが減算されるため、addToRandomInventoryメソッドのような面倒な処理は不要。

dropItems

public static void dropItems(World world, ItemStack stack, int i, int j, int k)
public static void dropItems(World world, IInventory inventory, int i, int j, int k)

アイテムをワールドへドロップする。IInventoryを指定できるため、インベントリをまるごとドロップすることも可能。

getTile

public static TileEntity getTile(World world, Position pos, ForgeDirection step)

posからstep方向に1つ進んだ先のTileEntityインスタンスを取得する。TileEntityが存在しない場合はWorld#getBlockTileEntityの都合でnullが返る。

getInventory

public static IInventory getInventory(IInventory inv)

IInventoryがTileEntityChestの時、ラージチェストかどうかをチェックして自動的に変換して返す。それ以外は無視なので、基本的にチェストをラージチェストとして扱うためだけにある。

getNearbyAreaProvider

public static IAreaProvider getNearbyAreaProvider(World world, int i, int j, int k)

隣接するIAreaProviderを返す。フィラー・クァーリーでの青マーカーの位置の取得はこれを利用している。

createLaser

public static EntityBlock createLaser(World world, Position p1, Position p2, LaserKind kind)

精密作業台に使うレーザーのEntityを生成する。

createLaserBox

public static EntityBlock[] createLaserBox(World world, double xMin, double yMin, double zMin, double xMax, double yMax, double zMax, LaserKind kind)

青マーカーを右クリックしたりレッドストーン入力したりした際に出るボックスを作る。なので、実は内部的にはあの青/赤線はレーザーと同じもの。

handleBufferedDescription

public static void handleBufferedDescription(ISynchronizedTile tileSynch)

BuildCraftの内部的なTileEntityの同期のためのメソッド。基本外部から使うことはない。

liquidId

public static int liquidId(int blockId)

与えられたブロックIDについて、液体ならそのIDを返し、それ以外なら0を返す。

liquidFromBlockId

public static LiquidStack liquidFromBlockId(int blockId)

ブロックIDからLiquidStackを返す。鉄エンジンのUIにある液体のそれのような感じ。

preDestroyBlock

public static void preDestroyBlock(World world, int i, int j, int k)

ブロック破壊前に呼び出されるメソッド。TileEntityの後始末、IInventoryの中身をdropItemsにかけるなど。

checkPipesConnections

public static boolean checkPipesConnections(TileEntity tile1, TileEntity tile2)
public static boolean checkPipesConnections(IBlockAccess blockAccess, TileEntity tile1, int x2, int y2, int z2)

tile1, tile2がそれぞれ(パイプとして)繋がるかどうかを返す。

checkLegacyPipesConnections

public static boolean checkLegacyPipesConnections(IBlockAccess blockAccess, int x1, int y1, int z1, int x2, int y2, int z2)

checkPipesConnectionsと基本同等だが、こちらは判定方法が異なる(checkPipesConnectionsはTileEntityが特定インターフェースを実装しているかどうかを判断していたのに対し、こちらはBlockのインスタンスが特定のインターフェースを実装しているかどうかで判断している)。クァーリーのフレームで利用されている。

readStacksFromNBT

public static void readStacksFromNBT(NBTTagCompound nbt, String name, ItemStack[] stacks)

NBTからItemStackを読み出し、配列stacksに格納するメソッド

writeStacksToNBT

public static void writeStacksToNBT(NBTTagCompound nbt, String name, ItemStack[] stacks)

NBTにItemStackの配列stacksを書き込むメソッド

consumeItem

public static ItemStack consumeItem(ItemStack stack)

アイテムを使用する際のstackSize減算、コンテナアイテムの扱いをまとめたメソッド

concat

public static <T> T[] concat(T[] first, T[] second)
public static int[] concat(int[] first, int[] second)
public static float[] concat(float[] first, float[] second)

配列の連結を行うメソッド