はまったこと
world.createExplosion()
を実行したときに、表示されていないが当たり判定だけが残っている、いわゆるゴーストブロック現象が発生してしまった。いろいろ調べたところ、Server
とClient
という、サイドの概念がForgeには存在しているらしい。で、それぞれのサイドでプログラムが実行されていることが分かった。こちらのリファレンスを参考にした。
サイドの種類について
大体の認識だと、クライアントというものはプレーヤーに影響するもので、サーバーというものはマルチプレイをするときに接続するものだという認識だろう。
さてここで、この二つのサイドのあいまいな点を解説していこう。
物理サーバー
物理サーバーというのは、よくdedicated server(専用サーバー)といわれるものだ。専用サーバーはminecraft_server.jar
のような種類のプログラム全体のことで、操作できる画面を持たない。
物理クライアント
物理クライアントというのは、Minecraftをランチャーから起動するプログラム全体のことだ。ゲームの描画や画面操作などを担っているすべてのスレッドやプロセス、そしてサービスは物理クライアントの一部だといえる。
論理サーバー
論理サーバーはゲームの統括をする。モブのスポーン、天候、インベントリの更新、モブの知能など、ゲームの仕組みすべてを担っている。論理サーバーは物理サーバーの中にあるものだが、シングルプレイのときは物理クライアントの中で論理クライアントと一緒に実行することができる。これはServer Thread
という名前で実行される。
論理クライアント
論理クライアントはプレーヤーからの入力を受け取って、それを論理サーバーに送信する役目を持っている。加えて論理クライアントは論理サーバーから情報を受け取り、その情報をプレーヤーが見えるように画面に表示する。これは、Client Thread
という名前で実行される。
サイドに合わせたプログラムの実行
world.isRemote
この真偽値はどちらのサイドでプログラムが実行されているかを調べるものである。この真偽値で調べられるのは、論理サーバーか論理クライアントかどうかだ。論理クライアントだったらtrue
を返し、論理サーバーだったらfalse
を返す。これは、物理サーバーでは常にfalse
を返すため、物理クライアントの論理サーバーと値が被っている。原則、それが物理サーバーか論理サーバーか、この値からではわからない。
この真偽値の使いどころは、ゲームのルールやそのほかの仕組みを実行したいときである。例えば、
- あるブロックに触れている間は、ずっとダメージを受け続ける
- ある機械に土を入れると、それをダイヤモンドに変える
などである。これらはworld.isRemote
の値がfalse
の時に実行するべきだ。これらを論理クライアントで実行してしまうと、論理サーバーとの整合性が取れなくなり、軽いバグの場合、モブのゴースト現象や体力の不一致などが発生する。重いバグの場合はゲームがクラッシュしてしまうこともある。
getEffectiveSide
FMLCommonHandler.getEffectiveSide()
は何らかの理由によって、world.isRemote
が使えない時に使われる。これはどっちのサイドかを、推測する。なぜ推測かというと、これはスレッドの名前(Server Thread
もしくはClient Thread
)から、どちらのサイドかを判断するからだ。world
が使用可能の時は、できるだけworld.isRemote
を使うようにして、これはその方法が使えないときの代替手段として使うべきだ。
解決
なるほど、どうやらワールドのブロックなどの管理をしているのは論理サーバーらしいから、論理クライアントと論理サーバーでworld.createExplosion()
を実行してしまうと、サーバーにあるブロック情報とクライアントにあるブロック情報が食い違ってしまうから、ゴーストブロック現象が起こるらしい。爆発で破壊するブロックはランダムで決めているから、クライアントでは破壊したことになっているブロックもサーバーでは爆発を免れているかもしれない。ここで、存在しないブロックに対する当たり判定が発生していたのだ。よって、この問題の解決方法は、
if (!world.isRemote)
world.createExplosion(...);
となる。当たり判定やゴーストエンティティで悩んでいるModderは論理サーバーのみ、論理クライアントのみでの実行を一回試してみたら幸せになれるだろう。