はじめに
この記事はテラリアというゲームでのMod開発に関する情報です。
サンプルコードや、公開されているコードを参考にMod開発をしている状況で、備忘録としてのまとめでもあり、記事投稿時点での知識で書いていますので、間違いもあるかもしれませんのでご注意ください。
やりたいこと
指定したブロックや壁紙を破壊しないようにするModを作成したい。
そのためには、Tile
オブジェクトからドロップするItem
オブジェクトの情報が欲しい。
背景
テラリアでは、ブロックなどを破壊した際にそのアイテムがドロップするが、ブロックなどを管理するオブジェクトはTile
オブジェクトであり、そこには破壊した際にドロップするItem
オブジェクトの情報は紐付いていない。
では、Tileを破壊した際にどのようにしてドロップしているかと言うと、Terraria.WorldGen
クラスに、KillTile
メソッドがある。
Tile破壊時にはこれが呼ばれるようで、途中の判定でTileの破壊が失敗しなかった場合で、Itemがドロップする場合に、次のような感じで個別に設定されている。
int num23 = 0;
int num24 = 0;
if (tile.type == 0 || tile.type == 2 || tile.type == 109)
{
num23 = 2;
}
else if (tile.type == 426)
{
num23 = 3621;
}
※ソースコードはtModLoaderにてTerraria.exeを逆コンパイルして作成されたもの
この中から、ブロック系のみを対象にして、どのtile.type
が、どのitem.type
をドロップするかを抜き出したいが、いかんせんわかりづらい。
また、今後の事も考えて、tile->item を取得するようなメソッドを作っておきたい。
このため、この部分のコードを丸ごとコピーし、不要な処理を排除して、かつ、TileIDとItemIDで表記したコードにしたい。
なお、Tileのテクスチャは、次のようにおり、ゲーム上でどのテクスチャを表示するかは、tile.frameX
で管理されている。
ブロック系のテクスチャは、表現が異なっていても、ドロップするアイテムは同一だが、上記のようなタイプはそれぞれドロップするアイテムが異なる。
今回生成するコードでは、単純にtile.type
のみから判断できるものを対象とすることにしている。
実際のコード
やっていることは、
-
TileID
クラスとItemID
クラスをリフレクションにて、値と名のディクショナリーを作成 - TileIDの数分、
Terraria.WorldGen.KillTile
メソッドのドロップ処理部分を別途抜き出し、不要な処理や、判断できない部分をコメントかしたメソッドを呼び出して、ItemIDを取得する(長いコードなのでここでは省略) - 取得できたItemID分の値とアイテム名のディクショナリーを作成
- TileIDからItemIDを取得するメソッドのコードをファイルへ出力する
public static void MakeCodeOfTileTypeToDropItemType()
{
//タイルタイプとTileID名のディクショナリーを作成する
var dicTileID = new Dictionary<int, string>();
var classTileID = typeof(TileID);
classTileID.GetFields().ToList().ForEach(x => dicTileID.Add(int.Parse(x.GetValue(classTileID).ToString()), x.Name));
//アイテムタイプとItemID名のディクショナリーを作成する
var dicItemID = new Dictionary<int, string>();
var classItemID = typeof(ItemID);
classItemID.GetFields().ToList().ForEach(x =>
{
if (x.FieldType.Equals(typeof(short))) dicItemID.Add(int.Parse(x.GetValue(classItemID).ToString()), x.Name);
});
//タイルタイプからドロップするアイテムのリストを作成する
List<Tuple<int, int>> listOK = new List<Tuple<int, int>>();
List<int> listNG = new List<int>();
for (int i = 0; i < dicTileID.Count; i++)
{
Tile tile = new Tile();
tile.type = (ushort)i;
int itemType = TileUtils.getKillDropItemType(tile);
if (0 < itemType)
{
listOK.Add(new Tuple<int, int>(i, itemType));
}
else
{
listNG.Add(i);
}
}
//ドロップするアイテムのアイテムタイプとアイテム名のディクショナリーを作成する
var dicItemName = new Dictionary<int, string>();
foreach (var x in listOK)
{
Item item = new Item();
item.SetDefaults(x.Item2);
if (!dicItemName.ContainsKey(x.Item2))
dicItemName.Add(x.Item2, item.Name);
}
//TileIDとItemIDで置き換えたコードを生成する
using (FileStream fs = new FileStream($@"{Main.SavePath}\TileUtils.cs", FileMode.Create))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine("public static class TileUtils");
sw.WriteLine("{");
sw.WriteLine(" public static int getKillDropItemType(Tile tile)");
sw.WriteLine(" {");
sw.WriteLine(" int result = 0;");
sw.WriteLine(" switch (tile.type)");
sw.WriteLine(" {");
foreach (var x in listOK)
{
sw.WriteLine($" //{dicItemName[x.Item2]}");
sw.WriteLine($" case TileID.{dicTileID[x.Item1]}:");
sw.WriteLine($" result = ItemID.{dicItemID[x.Item2]};");
sw.WriteLine(" break;");
}
foreach (var x in listNG)
{
sw.WriteLine($" case TileID.{dicTileID[x]}:");
sw.WriteLine(" break;");
}
sw.WriteLine(" }");
sw.WriteLine(" return result;");
sw.WriteLine(" }");
sw.WriteLine("}");
}
}
実際に出力されたものの一部
public static class TileUtils
{
public static int getKillDropItemType(Tile tile)
{
int result = 0;
switch (tile.type)
{
//土のブロック
case TileID.Dirt:
result = ItemID.DirtBlock;
break;
//石のブロック
case TileID.Stone:
result = ItemID.StoneBlock;
break;
と言った感じで出力したので、後はここから必要な修正を施したりします。
以上