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

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のレンダリング結果がおかしくなってしまう。これについては、個別に対応するか、自動判定処理を書いて一般的に適用出来るようにする他ないだろう。その意味では、この手法は非常にその場任せの適当なコードに見える。(しかし、本来書かないといけなかったアイテムレンダラよりは何十倍も短いコード量となる。)