1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

tModLoader 1.4 World Generation (ワールドの生成) チュートリアル

Last updated at Posted at 2022-09-25

さて... また最近tModLoaderの熱が上がり、Mod公開までしてしまった。

最近少しModのネタが減ってきたので、いったんここ(Qiita)で少々難解な WorldGen について備忘録程度に書いていこうと思う。

余談だが、私は何の資格も持ち合わせていない趣味プログラマだ。
専門的なことや、記事を書くための国語力はないことに注意していただきたい。

この記事は、https://github.com/tModLoader/tModLoader/wiki/World-Generation を基に書いている。
Terrariaのテクスチャなどを載せることができないため、分かりづらかったり、説明が不十分だと思ったらこれを見るといい...ただし英語。

参考記事

もし英語でもいいのであれば 一部1.3用のコードや解説もあるものの

を見るといい。ここの記事より役に立つ...かもしれない。

前提知識

まず、生成コードの説明の前に、Terrariaのタイル(=ブロック)や壁(ウォール)がどのように機能しているのか、どのような変数があるのか、など知っておく必要がある。

もし、この記事を読んでいる君が、鉱石作れた!よし、鉄とか鉛とかみたいに世界に散らばらせてみたいな~ 程度であれば、ここを飛ばして、実例を見るといい。
そこにあるコードを持っていくといい...
私も最初は おまじない として書いていたからね。

タイルの位置や世界の限界

世界の各タイルのデータを格納している変数があり、それはMain.tile[,]である。
Main.tile[,]は二次元配列であり、Main.tile[x, y]のように使う。値はTile構造体である。Tile構造体に関しては後述する。
Terrariaの世界では、左上端が0, 0となっている。各ワールドの大きさがあるが、右下端(X座標、Y座標共に最大になる位置)はMain.maxTilesXMain.maxTilesYで取得できる。
Y座標のみ数学(のグラフ)と逆転しているので注意。 (Y座標の値が大きいほど下を表す)
世界の全ブロックを調べたかったら、次のようなコードを書く。

//using Terraria;

for (var i = 0; i < Main.maxTilesX; i++)
{
    for (var j = 0; j < Main.maxTilesY; j++)
    {
        Tile tile = Main.tile[i, j]; // Terrariaでは x, y ではなく i, j がよく使われがち
        // if (tile.HasTile && tile.TileType == ...
    }
}

Main.tile[,]のインデックスは、0Main.maxTilesX0Main.maxTilesYまでであり、負の値や、Main.maxTilesを越える値は配列の範囲外になるので注意。
範囲外の対策として、ifを使ってももちろんいいが、Terrariaにはデフォルトで便利なものがある。
それはFraming.Framing.GetTileSafely(int i, int j)。これは範囲外を指定した場合、default(Tile)を返し、エラーを防ぐメソッドである。エラーが出たからなどと、むやみやたらに使うものではないが、有用なので活用するべし。
一応、WorldGen.InWorldという物もあるが、ここでは割愛。

Tile構造体

Terrariaの世界のタイルや壁のデータは全て、Tile構造体で表される。
ここでは、使うことが多いメンバを紹介する。

  • tile.HasTile (bool) - タイルが設置されているか(=空気ではないこと)を表す。
  • tile.TileType (ushort) - タイルの種類を表す。タイルの種類を表す値はTileIDにある。
  • tile.WallType (ushort) - 壁の種類を表す。壁の種類を表す値はWallIDにある。
  • tile.LiquidAmount (byte) - 液体の量を表す。0が空、255で満タン。
  • tile.LiquidType (int) - 液体の種類を表す。液体の種類を表す値はLiquidIDにある。
  • tile.TileFrameX/tile.TileFrameY (short) - タイルのフレームを表す。

TileIDを使ったことのある人なら分かると思うが、
TileIDには、ItemIDWallIDのようなTileID.Noneがない(=タイルがないことを表す値がない)。
そのため、タイルがあるかどうかを調べるときは、Tile.HasTileを使用する。

//using Terraria;
//using Terraria.ModLoader;

Tile tile = Main.tile[i, j];
if (tile.HasTile && tile.TileType == TileID.Stone) // タイルが設置されている かつ タイルが石ブロックである
{
    tile.TileType = (ushort)ModContent.TileType<MyMod.Tiles.MyTile>(); //石ブロックを自作のタイルに置き換えるってだけ
    // タイルの置き換えを行ったことがある人なら、ここで
    // WorldGen.SquareTileFrame(i, j);
    // とか
    // NetMessage.SendTileSquare(-1, i, j);
    // を書くかもしれないが、**ワールドの生成では**不要。
}

ほとんどの場合、Tile.HasTileの確認は必須である。

壁の確認の場合は、壁が設置していない場合、tile.WallType0(WallID.None)になるので、この確認方法はタイルのみのものである。

タイルの種類

ModTileでタイルを作った人ならわかるかもしれない。
タイルは2種類あり、FramedFrameImportant(これらの名称は tModLoader wiki より)である。
Framedは簡単に言えば、ブロック。土ブロックとか石ブロックとかの地形のメインになるタイルである。
対し、FrameImportantはワークベンチやチェスト、延べ棒(インゴット)のような、作業台になったり何かしらの機能を持っていたりなどの特殊なタイルである。
大体の見分け方は簡単で、1x1(通常のブロック)より大きいものは全てFrameImportantである。
厳密には、1x1のものもあり、延べ棒(インゴット)や論理ゲート(ロジックゲート)などもFrameImportantである。

Framedタイルは、隣接しているタイルに合わせてテクスチャが変化するタイルである。
対し、FrameImportantタイルは(特段設定されていない限り)テクスチャが固定されている。
Framedタイルの方が難しそうに聞こえるかもしれないが、実際はFrameImportantタイルの方がめんどくさい。
Frmaedタイルは、テクスチャの描き方が共通であるため、タイルを置き換えても問題なく、
また、1x1ブロックのサイズであるため、コードで設置することも簡単である。
どのフレームを使うかなど面倒な処理は、ワールドの読み込み時などでTerraria側で自動的に行ってくれる。(tile.TileFrameXtile.TileFrameYを手動で設定する必要が無い)
対し、FrameImportantタイルの設置はtile.TileFrameXtile.TileFrameYを使用して、適切なフレームを選ぶなど少々書かなければいけないことが増える。

まとめると、Frmaedタイルの設置やFrmaedタイル同士のや置き換えは簡単にできるが、FrameImportantタイルはそう簡単には置き換えができない、ということである。

タイルフレーム

ModTileでタイルを作った人ならわかるかもしれない。(2回目)
さっきから「フレーム」という言葉を出してどういうことだと思った人もいるだろう。ここではタイルのフレームに関して説明する。
(正直、国語力のない私には説明しがたい...少しおおざっぱになる。)

Framedタイルであろうと、FrameImportantタイルであろうと、フレームという概念がある。
フレームは、タイルのテクスチャの、どこを表示するか、を表すようなものである。
タイルのテクスチャは 16px ごとに区切られている。
(https://github.com/tModLoader/tModLoader/wiki/World-Generation にある画像を参考にすると分かりやすいかも...)
どの 16px x 16px を使用するかをtile.TileFrameXtile.TileFrameYで指定する
tile.TileFrameX = 18tile.TileFrameY == 0で、区切られたタイルの左から2番目、上から1番目にある 16px x 16px を表す。
(16ではなく18なのは、区切りのために2px使用しているから)

delegate (C#の用語)

delegateに関してはいくつもの記事があるので、特にここで解説する必要は無いのだが、基礎セクションで出てくるC#の用語(概念?)なので少しだけ。
delegateは、大雑把に言うと、メソッドを変数として格納する型である。
メソッドが変数になりました ってだけ。

public delegate void Hoge(int value1, int value2);

private static void HogeHoge(int a, int b){}

static void Main()
{
    Hoge hoge = HogeHoge;
}

とか

tasks.Insert(index, new PassLegacy("Test", delegate (GenerationProgress progress, GameConfiguration config)
{
}));

みたいな感じで使う。

忍耐力

はて、忍耐力?

前提「知識」ではないが、ワールドの生成には労力がかかる。
後述するが、デバッグの方法はワールドを作っては確認作っては確認...を繰り返すものである。
そもそもMod製作を初めた時点で逃れることはできない。
特に、ただのソフトウェア製作でなく、Mod製作だからデバッグには時間がかかる。
とても大変だ...。

...ただ、チュートリアルを読んでいるということは、
Terrariaのコードを読んで、ワールドの生成に関わるコードを理解し、どのHookを使えばいいのか判断する
という手順は省けているだろう。

これからその書き方は紹介する。あとは健闘を祈る。


悪いが前提知識のセクションはここまでだ。あくまで補助的なもので、前提知識にある具体的なことは別の記事などを探してほしい。

基礎

さて、前提知識でぐったりしているかもしれないが、ここからが本番だ。
最悪、前提知識は、Main.tile[,]Main.maxTilesX/Main.maxTilesYTile.TileTypeくらい知っていれば問題は無い。

ワールド生成の仕組み

Terrariaのワールドはいくつかのパスというもを順々に実行してできている。

このパスは、文字列と、引数有り返り値なしのdelegate型で構成されている。
文字列には、delegateが何の生成を担うのかを表し、
delegateに実際の生成コードが書かれる。
(tModLoader wikiではパスステップでできていて...みたいなことが書かれているが、正直分かりづらいし、個人的に世界生成のコードを書いてて意識して書くような感じでもないので割愛)

んで、このパスの正体は、PassLegacyというクラスである。
PassLegacyのコンストラクタはこの通り。

new PassLegacy(string name, WorldGenLegacyMethod method)

このnameが上記の「文字列」であり、methodに生成コードをぶち込む。

delegateと言っておきながら、WorldGenLegacyMethodという型が出てきているが、WorldGenLegacyMethoddelegate型である。

いくつかバニラのパスを紹介しよう。
例えば、Terrain(おおまかな地形生成)、Jungle(ジャングルの(おおまかな)生成)、Floating Islands(空島の生成)、Shinies(鉱石を埋める)、Pyramids(ピラミッドの生成)、Life Crystals(ライフクリスタルの設置)、Spawn Point(スポーン地点の確定)、Flowers(花の設置)などなど。

これらのパスは、世界を生成する時に、リストに格納される。
Modは、そのリストに、Modで作成したパスを挿入(Insert)して、ワールド生成を追加、変更する。
(もちろん必要に応じて、パスを追加(Add)や削除(RemoveAt)を行うこともあるが、基礎セクションでは行わない。Terrariaのワールドの生成まるごと変えるような特殊なModのみが行うからだ。)

そして、重要なのはModで作成したパスを、リストのどこに入れるか、である
適切な位置に入れなければ、せっかく生成したものがバニラの生成で壊されたり、その逆が起こったり、過剰なタイルまで変換してしまうなどの問題を引き起こす可能性がる。

パスの作り方とModSystem

ここから、具体的にタイルの操作に関わるコードを書く。
つまり、Main.tile[,]などのお出ましだ。

さて、パスを格納するリストに、Modで作成したパスを挿入すると言った。
まずはパスを作る必要がある。

まぁ...その前に、一旦
自分が何をしたいのか(自作の鉱石を散らばらせたいのか、構造物を作りたいのか、など)を決めておく必要がある。
ここからは実例を挙げていくからな。

それでは、初めていこう。

まず、パスを作ることはもちろんだが、バニラのパスを格納したリストにアクセスできなきゃぁ意味がない。
そのリストをいじるには、ModSystemにあるModifyWorldGenTasksというメソッドをオーバーライドして利用する。
とりあえず、ModSystemを継承したクラスが必要である。

//using System.Collections.Generic;
//using Terraria.ModLoader;
//using Terraria.WorldBuilding;

public class TutorialWorldGen : ModSystem
{
    public override void ModifyWorldGenTasks(List<GenPass> tasks, ref float totalWeight)
    {
    }
}

ModifyWorldGenTasksの1番目のパラメータtasksが、パスを格納したリストだ。
このtasksパスを挿入すればいい。

ではパスの作り方だ。

作り方は、前述の通りPassLegacyのコンストラクタを用いる。
(リストの要素はGenPassであるが、PassLegacyで問題ない。PassLegacyGenPassを継承した型である。)

PassLegacyに必要なのは、文字列とWorldGenLegacyMethod(delegate型)。
WorldGenLegacyMethodは、GenerationProgress型とGameConfiguration型の引数を持ち、返り値はない。

delegateを使って書いても、メソッドを用意して書いてもどちらでもよいが、
ここでは、メソッドを用意する方法を紹介する。

実例: 鉱石の生成(鉱石を散らばらせる)

さてお待ちかね、実際のコードだ。
一番簡単で、一番チュートリアル向きの 鉱石の生成 を行う。

実例

では早速コードだ。

//using System.Collections.Generic;
//using Terraria;
//using Terraria.GameContent.Generation;
//using Terraria.IO;
//using Terraria.ModLoader;
//using Terraria.WorldBuilding;

public class TutorialWorldGen : ModSystem
{
    private void GenerateOres(GenerationProgress progress, GameConfiguration config)
    {
        progress.Message = "Places More Ores";

        for (var k = 0; k < (int)((Main.maxTilesX * Main.maxTilesY) * 6E-05); k++)
        {
            WorldGen.TileRunner(WorldGen.genRand.Next(0, Main.maxTilesX), WorldGen.genRand.Next((int)WorldGen.worldSurfaceLow, Main.maxTilesY - 200), WorldGen.genRand.Next(3, 6), WorldGen.genRand.Next(2, 6), ModContent.TileType<Tiles.YourOre>());
        }
    }

    public override void ModifyWorldGenTasks(List<GenPass> tasks, ref float totalWeight)
    {
        int index = tasks.FindIndex(pass => pass.Name.Equals("Shinies"));
        if (index != -1)
        {
            index++;
            tasks.Insert(index, new PassLegacy("TutorialMod: Shinies", GenerateOres));
        }
    }
}

はぁ?
...と言いたくなるかもしれない。特に、鉱石の生成のコードだけを見に来た人は。

鉱石を散らばらせてみたい!ってお試し感覚でここに来ている人は、考えるな感じろ でいいと思う、
ただ、
まず、ModContent.TileType<Tiles.YourOre>()の部分にタイルの種類を入れること、
次にprogress.Messageは、鉱石の生成中に表示される文字を代入すること、
そして、"TutorialMod: Shinies"TutorialModを自分の(内部)Mod名(名前空間の方)にすること、
最後に、鉱石を生成するだけならの方法で書けば問題ない。
ということを言っておこう。

WorldGenLegacyMethod / WorldGen.TileRunner

それでは具体的な解説だ。
とりあえず、delegate型なのでメソッドを用意した。この意味がわからないのであれば、まずdelegateの解説を探して多少理解した方が良い...が、まぁ知らなくてもこの書き方をするんだな。と覚えていただければ。
用意したメソッドは、ここではGenerateOreと名付けている。
メソッド内にあるのが、鉱石を生成するコードである。

まずGenerateOreメソッドをよくよく見ていこう。

//using Terraria;
//using Terraria.IO;
//using Terraria.ModLoader;
//using Terraria.WorldBuilding;

private void GenerateOres(GenerationProgress progress, GameConfiguration config)
{
    progress.Message = "Places More Ores";

    for (var k = 0; k < (int)((Main.maxTilesX * Main.maxTilesY) * 6E-05); k++)
    {
        WorldGen.TileRunner(WorldGen.genRand.Next(0, Main.maxTilesX), WorldGen.genRand.Next((int)WorldGen.worldSurfaceLow, Main.maxTilesY - 200), WorldGen.genRand.Next(3, 6), WorldGen.genRand.Next(2, 6), ModContent.TileType<Tiles.YourOre>());
    }
}

メソッド名は何でも構わないが、パラメータはGenerationProgress型とGameConfiguration型の順でなければならない。
GenerationProgress型は、生成中のメッセージや、プログレスバーの制御を行う。
プログレスバーの制御は、少々面倒なため、また私が研究中のため、ここでは解説をしない。

生成中のメッセージは簡単に書き換えることができ、generationProgress.Messageに文字列を代入するだけである。
つまり、progress.Message = "Places More Ores";は、「この生成を行っている最中は "Places More Ores" とメッセージを表示せよ。」という意味である。

次に、鉱石の生成を担うコードだ。
for文があるが、ひとまずWorldGen.TileRunnerについて解説しよう。

WorldGen.TileRunner(int i, int j, double strength, int steps, int type, bool addTile = false, float speedX = 0f, float speedY = 0f, bool noYChange = false, bool overRide = true, int ignoreTileType = -1)

WorldGen.TileRunnerは鉱石などブロック(Framedタイル)をランダムな形で配置するためのメソッドである。
以下の画像は、鉄鉱石をWorldGen.TileRunnerで配置した様子だ。
(散らばり具合はWorldGen.TileRunnerとは関係ない。私が指定した位置に置いていっただけである)
WorldGen.TileRunner.png
各引数を見ていこう。

  • int i, int j - これはタイルを配置する位置を代入する。
  • double strength - タイルの塊の大きさを調整する。( https://forums.terraria.org/index.php?threads/modding-tutorial-world-generation.47601/strengthを大きくして実験した結果の画像がある。1.3のチュートリアルだが、TileRunnerはおそらく1.4でも変わっていないと思う)
  • int steps - 具体的には分からない。引数名的に、生成試行回数かと思われる。
  • int type - 言わずもかな、生成するタイルの種類を代入する。
  • bool addTile - 鉱石は土や石などに埋める、つまり土や石などを鉱石に変換している。これがfalseであると、ベースとなる上書きされるブロックが無いと生成できない。trueにするとブロックの有無を無視する。false(デフォルト)を推奨。
  • float speedX, float speedY, bool noYChange - これらは未だによく分からない引数。デフォルトのまま使用することを推奨する。
  • bool overRide - これは既存のタイルを変換するかどうか。addTiletrueである時に、これをfalseにすると、既存のタイルを壊さずにタイルを追加できる。addTilefalseでこれもfalseだと何も起こらなくなると思われる。true(デフォルト)を推奨。
  • int ignoreTileType - これも名前の通りである。上書きしたくないタイルの種類を指定するのだが、まず使うことはない。-1で指定しない。-1(デフォルト)を推奨。

それでは、実際に代入しているものを見てみよう。

WorldGen.TileRunner(
    WorldGen.genRand.Next(0, Main.maxTilesX),  // i
    WorldGen.genRand.Next((int)WorldGen.worldSurfaceLow, Main.maxTilesY),  // j
    WorldGen.genRand.Next(3, 6), // strength
    WorldGen.genRand.Next(2, 6), // steps
    ModContent.TileType<Tiles.YourOre>() // type
);

ij(設置位置)に関しては次の生成位置で解説する。
strength35までのランダムな値を代入している。
steps25までのランダムな値を代入している。
typeは、タイルの種類を指定する。バニラのタイルを生成したいのであれば、TileIDを使えばよい。
乱数の値はバニラの鉱石生成を基にしているので、これらの乱数の範囲をいじることはあまりお勧めはしない。(が、塊を大きくしたいのであれば、範囲を多少大きくしても問題はない。)
以降のbool addTile, float speedX, float speedY, bool noYChange, bool overRide, int ignoreTileTypeは全てデフォルト値を使用している。

そうそう、ワールドの生成では、乱数を生成する場合はMain.randではなく、WorldGen.genRandを使用する。

鉱石の塊の配置はWorldGen.TileRunnerを使用すれば簡単にできる。
他にもWorldGen.OreRunnerというものもあるが、これは、バニラではハードモードの鉱石出現に使われる。
そして、バニラのパスのリストの挿入する位置が後の方である場合は、WorldGen.TileRunnerではなくWorldGen.OreRunnerを使用する
...みたいな感じだが、今回はバニラのShinies(バニラの鉱石の生成)の直後に、この鉱石生成パスをぶち込んでいるのでWorldGen.OreRunnerは必要ない。

生成位置

ワールドの生成において、
バニラの生成パスのどこに挿入すれば適切なのか、この形で生成するにはどのようなコードが適切なのか、
よりも難しい問題は、どこに生成するべきなのか である。

鉱石の生成は書き方が決まっているため、考える必要は無いが、
構造物を作りたい!とか、大きなバイオームを作りたい!とかであればよくその位置を考えるべきである。
さて、最後に残したfor文の解説とWorldGen.TileRunnerの引数int i, int jについて解説しよう。

for (var k = 0; k < (int)((Main.maxTilesX * Main.maxTilesY) * 6E-05); k++)
{
    WorldGen.TileRunner(WorldGen.genRand.Next(0, Main.maxTilesX), WorldGen.genRand.Next((int)WorldGen.worldSurfaceLow, Main.maxTilesY - 200), //以下略
}

まずfor文についてだ。繰り返し変数のkは使われない。つまり、このforはただの繰り返す回数を表すのみである。
繰り返す回数は、(int)((Main.maxTilesX * Main.maxTilesY) * 6E-05)回。これもバニラのコードを基にしているため、あまり考えるようなものではない。
強いて言うのであれば、Main.maxTilesX * Main.maxTilesYはワールドの全ブロック数を表し、その数に6E-05(0.00006)を掛けている。これはワールドのサイズが変わっても生成個数の割合が変わらないようにしているのだろう。

次に、WorldGen.genRand.Next(0, Main.maxTilesX)だ。タイルの塊を生成するX座標を表している。0からMain.maxTilesXまでなので、X座標は完全にランダムである。

最後に、WorldGen.genRand.Next((int)WorldGen.worldSurfaceLow, Main.maxTilesY - 200)。これはY座標なのだが...見慣れない奴もいるな。
まず、WorldGen.worldSurfaceLow。具体的にどのくらいか正直よく分からない。私ならMain.worldSurfaceを使うからな。
...いずれにせよ、worldSurfaceというのは、ワールドの地上と地下の境界である。
高度計でLevel Surfaceと表示されるY座標がMain.worldSurfaceであり、WorldGen.worldSurfaceLowはそれより少し上を表すようだ。
(実際にやってみたのだが、Lowと付くもののMain.worldSurfaceより上(より小さい値)に鉱石が生成された。)
そして、Main.maxTilesY - 200だが、これは地下と地底世界(地獄)の境界である。ワールドのサイズに関係なく、最大タイル数-200以下が地底世界(Underworld)となっている。
つまり、Y座標は、地上含む、地下(地底世界より上)のランダムな位置を指定している。

応用では、他にもワールドの境界となる位置を示す変数を紹介する。
とりあえず、次のModifyWorldGenTasksを見ていこう。

ModifyWorldGenTasks / パスの挿入

まだこれで終わりではない。作成したパスをバニラのリストに挿入せねば。
ModifyWorldGenTasksを見ていこう。

//using System.Collections.Generic;
//using Terraria.GameContent.Generation;
//using Terraria.WorldBuilding;

public override void ModifyWorldGenTasks(List<GenPass> tasks, ref float totalWeight)
{
    int index = tasks.FindIndex(pass => pass.Name.Equals("Shinies"));
    if (index != -1)
    {
        index++;
        tasks.Insert(index, new PassLegacy("TutorialMod: Shinies", GenerateOres));
    }
}

list.FindIndexlist.Insertを理解しているのであれば、さっきのコードよりもはるかにスッと頭に入るのではないのだろうか。

今回(この実例で)の目的は、(バニラの鉱石のように)鉱石を埋めることである。
鉱石を埋める程度なのであれば、バニラの鉱石生成の直後にModの鉱石の生成を埋める生成を行うのが理にかなっている。
バニラの鉱石生成のパスShiniesという名前(文字列)がついている。
だからまず、tasks.FindIndex(pass => pass.Name.Equals("Shinies"))Shiniestasksの何番目に入っているかを探す。

次にif (index != -1)で、Shiniestasksに入っているかどうか確認をする。FindIndexで存在しなかった場合は-1を返す仕様になっている。
これは、他のModによってShiniesパスが消された時の対策で、Modの競合が起きないようにするものでもある。必須だ

最後に、indexの値に1加算(index++;)して、tasksに挿入(tasks.Insert())する。
1加算する理由は、tasks.Insert()は指定したインデックスの直前に挿入しようとするから。
今回はShiniesパスの直後なので、1加算する必要がある。

デバッグ方法

さて、ついに鉱石の生成コードを書き、パスにして、バニラのパスのリストに挿入した。
Modをコンパイルして、実際にワールドを生成してみよう。
実際に鉱石が生成されたかどうかを、いちいち掘って確認するのは面倒なので、HERO's Modを使用するといい。(さすがにこの記事を見に来た人は既に持っていると思うが。)
ワールドを照らしたり、カメラを動かしたり、アイテムを無条件無制限で取得できるからな。

失敗したり、生成されていなかったら、スペルミスやC#の構文があっているか確認しよう。
一応、他に大型Modを入れていないかも確認しよう。

...そう。
ワールド生成のデバッグ方法は、新規ワールド作成と確認の繰り返しである。
何かミスをしていれば、コードを直し、コンパイルし、ワールドを作成し、ワールドを探索する必要がある。
とても時間がかかる大変な作業だ。
しかも基礎では大したことはできない。

実際、World Generationのチュートリアル自体、tModLoader wikiでは、Easy(初級), Intermediate(中級), Advanced(高度), Expert(専門的)の中の、 Advanced(高度) に指定されている。
そのくらい複雑で面倒なものである。

...まぁ、ゆっくりやっていけばいいさ。

とりあえず次に進む前に、お茶でも飲んだらどうだい?

応用

基礎でカバー出来るのは鉱石の生成くらいだろう。
ここからは応用だ。

とりあえず、よく使う、ワールド生成で使える変数やメソッドなどを紹介していこう。

タイルの設置方法 (WorldGen)

WorldGen.PlaceTile()

一番単純で基本のメソッド。タイルを置くだけ。
Frmaedタイルはもちろん、ほとんどのFrameImportantタイルにも使用できる。
FrameImportantタイルを指定した場合も、1x1ではなくしっかり置いてくれるので、以降にあるPlace◯x◯を使用しなくても問題ない。(むしろPlace◯x◯を使ってゲームがクラッシュする原因になったことがある)

WorldGen.PlaceTile(int i, int j, int Type, bool mute = false, bool forced = false, int plr = -1, int style = 0)
  • int i, int j - 設置するタイルのワールド座標。0Main.maxTilesX0Main.maxTilesYの範囲まで。
  • int Type - 設置するタイルの種類。TileIDModContent.TileType<T>()の使用を推奨。
  • bool mute - おそらく設置音を鳴らすかどうか[要検証]かと思われる。私は毎回trueを指定している。
  • bool forced - これをtrueにすると、設置する位置にタイルがあろうとも強制的に上書きする。ただし、坂(スロープ)などのデータは残るため、設置前にしっかりタイルを消すことをお勧めする。
  • int plr - 不明[要検証]-1(デフォルト)にしておくことを推奨する。
  • int style - FrameImportantタイルにおいて、Alternateタイルを指定する時に使う。
    (FrameImportantタイルは、チェストのように1つでいくつもの種類を持つものがある。チェストであれば、0(1番目)は普通のチェスト、1(2番目)は金のチェストを表す。)

WorldGen.PlaceWall()

こちらも1番単純で基本のメソッド。壁を置くだけ。

WorldGen.PlaceWall(int i, int j, int type, bool mute = false)
  • int i, int j - 設置する壁のワールド座標。0Main.maxTilesX0Main.maxTilesYの範囲まで。
  • int Type - 設置する壁の種類。WallIDModContent.WallType<T>()の使用を推奨。壁を削除(=破壊)する場合は、これを0にするより、WorldGen.KillWall()の使用を推奨する。
  • bool mute - おそらく設置音を鳴らすかどうか[要検証]かと思われる。

WorldGen.Place◯x◯()

◯には数字が入り、WorldGen.Place1x1()WorldGen.Place2x2()WorldGen.Place3x3Wall()といったものまである。
前述の通り、WorldGen.PlaceTile()を使用しても問題ない。

WorldGen.Placex(int i, int j, int type, int style = 0)
WorldGen.Placex(int i, int j, ushort type, int style)
WorldGen.Placex(int i, int j, ushort type, int style = 0)
// など (ほとんどが上記のうちいずれかの形をとる)
  • int i, int j - 設置するタイルのワールド座標。
  • ushort/int Type - 設置するタイルの種類。TileIDModContent.TileType<T>()の使用を推奨。
  • int style - FrameImportantタイルにおいて、Alternateタイルを指定する時に使う。
WorldGen.Place◯x◯() 一覧
  • WorldGen.Place1x1(int x, int y, int type, int style = 0)
  • WorldGen.Place1x2(int x, int y, ushort type, int style)
  • WorldGen.Place1x2Top(int x, int y, ushort type, int style)
  • WorldGen.Place1xX(int x, int y, ushort type, int style = 0)
  • WorldGen.Place2x1(int x, int y, ushort type, int style = 0)
  • WorldGen.Place2x2(int x, int y, ushort type, int style)
  • WorldGen.Place2x2Horizontal(int x, int y, ushort type, int style = 0)
  • WorldGen.Place2x2Style(int x, int y, ushort type, int style = 0)
  • WorldGen.Place2x3Wall(int x, int y, ushort type, int style)
  • WorldGen.Place2xX(int x, int y, ushort type, int style = 0)
  • WorldGen.Place3x1(int x, int y, ushort type, int style = 0)
  • WorldGen.Place3x2(int x, int y, ushort type, int style = 0)
  • WorldGen.Place3x2Wall(int x, int y, ushort type, int style)
  • WorldGen.Place3x3(int x, int y, ushort type, int style = 0)
  • WorldGen.Place3x3Wall(int x, int y, ushort type, int style)
  • WorldGen.Place3x4(int x, int y, ushort type, int style)
  • WorldGen.Place4x2(int x, int y, ushort type, int direction = -1, int style = 0)
  • WorldGen.Place4x3Wall(int x, int y, ushort type, int style)
  • WorldGen.Place5x4(int x, int y, ushort type, int style) - 私が使って、ゲームクラッシュの原因になった。使用非推奨。
  • WorldGen.Place6x3(int x, int y, ushort type, int direction = -1, int style = 0)
  • WorldGen.Place6x4Wall(int x, int y, ushort type, int style)

WorldGen.PlaceChest()

前提知識

チェストの仕組み

Terrariaでは、チェストはChest型というものでデータを管理している。
世界に置いて機能するチェストは8000までであり、それらは全てMain.chest[]に格納されている。
chest.itemからチェストの中身を確認することができる。

ワールド座標からチェストのインデックスを取得する方法

タイルの座標からチェストの中身を知りたい時は、
通常、Chest.FindChest(int X, int Y)を使用する。
このX座標とY座標は、チェストの左上の座標を指定する必要がある。

チェストを置くことに特化したメソッド。
チェストを設置する場合は、WorldGen.PlaceTile()WorldGen.Place2x2()ではなく、これを使用することを推奨する。

int WorldGen.PlaceChest(int x, int y, ushort type = 21, bool notNearOtherChests = false, int style = 0)
  • int i, int j - 設置するタイルのワールド座標。
  • ushort Type - 設置するタイルの種類。TileIDModContent.TileType<T>()の使用を推奨。いずれもチェストを指定すること。デフォルトの21は、バニラのチェストを表す値である。
  • bool notNearOtherChests - 不明。近くにチェストがあったら生成しない的な...?[要検証]
  • int style - FrameImportantタイルにおいて、Alternateタイルを指定する時に使う。ここでは、チェストの種類を表す。(ex: 0(1番目)は普通のチェスト、1(2番目)は金のチェストを表す。)

そしてこのメソッドは、intを返す。
これは、Main.chest[]のインデックスである。
つまり、このメソッドは、チェストを配置し、Main.chest[]にそのチェストのデータを用意するまでしてくれる、とても便利なメソッドである。
チェストを置いた後にそのチェスト内のアイテムを設定することもできる。

タイルの置き換え (Tile)

タイルの置き換えはとても簡単だ。
tile.TileTypeの値を変えればいいだけだ。

tile.TileType = (ushort)ModContent.TileType<Tiles.YourModTile>();

ただし、tile.HasTilefalseである時は設置されないので、tile.HasTiletrueであることを確認する必要がある。
また、FrmaeImportantタイルでこれを行う場合、正しいフレームを指定する必要がある。
これらより、そもそも置き換えよりも、タイルの削除 → タイルの設置 をお勧めする。

タイルの削除 (WorldGen)

WorldGen.KillTile()

タイルを削除(破壊)するためのメソッド。FrameImportantタイルにも使える。

WorldGen.KillTile(int i, int j, bool fail = false, bool effectOnly = false, bool noItem = false)
  • int i, int j - 破壊するタイルのワールド座標。0Main.maxTilesX0Main.maxTilesYの範囲まで。
  • bool fail - これがtrueの時、タイルは破壊されない。役に立たなく聞こえるが、このメソッドはピッケルを使用した時にも使用され、ピッケルパワーが足りない場合にtrueを指定しているそうだ。しかし、ワールドの生成には関係がないので、falseを指定しよう。
  • bool effectOnly - 不明。failと共に使用して音を鳴らすか鳴らさないかに関わるらしい...?[要検証]
  • bool noItem - これをtrueにすると、タイル破壊後にそのタイルのアイテムを落とさなくなる。私はtrueに指定している。

WorldGen.KillWall()

壁を削除(破壊)するためのメソッド。

WorldGen.KillWall(int i, int j, bool fail = false)
  • int i, int j - 破壊する壁のワールド座標。
  • bool fail - これがtrueの時、壁は破壊されない。ワールドの生成ではfalseを指定しよう。

ワールドの各境界を表す変数など (Main)

これらは全てワールド座標(タイル基準の座標)になっている。

Main.maxTilesX & Main.maxTilesY

前提知識に書いたので必要ないが、これもワールド生成に役に立つ変数の1つだ。
ワールドの右下端の座標を表すとも言えるし、水平/垂直方向の全ブロック数とも言えるだろう。

Main.worldSurface * 0.35[要検証]

これより上は高度計でSpaceと表示される。
地上と宇宙の境界のY座標である。
正確に試したことはないため、要検証ではあるものの、空に何か起きたい時は、この位置を基準にするとよい。

Main.worldSurface

高度計でLevel Surfaceと表示される、地上と地下の境界のY座標である。
これより小さい値は、地上。これより大きい値は地下。

Main.rockLayer

Terrariaの地下には、Dirt LayerRock Layerという2つの層に分けられている。
高度計で、Dirt LayerUndergroundと表示され、Rock LayerCavernsと表示される。
Main.rockLayerはその境界のY座標である。

Main.maxTilesY - 200

地下と地底世界(Underworld)の境界は変数が存在しないが、どのワールドでも共通して、境界はMain.maxTilesY - 200の位置である。

Main.spawnTileX / Main.spawnTileY

プレイヤーのスポーン位置である。
これを使用する場合はSpawn Point パスの後でないと正しく取得できない。



WorldGenにも変数はあるのだが、だいたい上記の変数名にLowとかHighとかつくとかつかないもの見たいな感じで、かつ私が良く分かってないので割愛。

バニラのパス一覧

tModLoader wiki にあるものは 1.3用なので、ここで、1.4用の全てのパスを書こうと思う。
tasks.FindIndexでインデックスを調べる際に使うとよい。
(各パスがどのようなことを行うのかは割愛。文字列でなんとなく分かるからね。)

パス一覧
  • Reset
  • Terrain
  • Dunes
  • Ocean Sand
  • Sand Patches
  • Tunnels
  • Mount Caves
  • Dirt Wall Backgrounds
  • Rocks In Dirt
  • Dirt In Rocks
  • Clay
  • Small Holes
  • Dirt Layer Caves
  • Rock Layer Caves
  • Surface Caves
  • Wavy Caves
  • Generate Ice Biome
  • Grass
  • Jungle
  • Mud Caves To Grass
  • Full Desert
  • Floating Islands
  • Mushroom Patches
  • Marble
  • Granite
  • Dirt To Mud
  • Silt
  • Shinies
  • Webs
  • Underworld
  • Corruption
  • Lakes
  • Dungeon
  • Slush
  • Mountain Caves
  • Beaches
  • Gems
  • Gravitating Sand
  • Create Ocean Caves
  • Clean Up Dirt
  • Pyramids
  • Dirt Rock Wall Runner
  • Living Trees
  • Wood Tree Walls
  • Altars
  • Wet Jungle
  • Jungle Temple
  • Hives
  • Jungle Chests
  • Settle Liquids
  • Remove Water From Sand
  • Oasis
  • Shell Piles
  • Smooth World
  • Waterfalls
  • Ice
  • Wall Variety
  • Life Crystals
  • Statues
  • Buried Chests
  • Surface Chests
  • Jungle Chests Placement
  • Water Chests
  • Spider Caves
  • Gem Caves
  • Moss
  • Temple
  • Cave Walls
  • Jungle Trees
  • Floating Island Houses
  • Quick Cleanup
  • Pots
  • Hellforge
  • Spreading Grass
  • Surface Ore and Stone
  • Place Fallen Log
  • Traps
  • Piles
  • Spawn Point
  • Grass Wall
  • Guide
  • Sunflowers
  • Planting Trees
  • Herbs
  • Dye Plants
  • Webs And Honey
  • Weeds
  • Glowing Mushrooms and Jungle Plants
  • Jungle Plants
  • Vines
  • Flowers
  • Mushrooms
  • Gems In Ice Biome
  • Random Gems
  • Moss Grass
  • Muds Walls In Jungle
  • Larva
  • Settle Liquids Again
  • Cactus, Palm Trees, & Coral
  • Tile Cleanup
  • Lihzahrd Altars
  • Micro Biomes
  • Water Plants
  • Stalac
  • Remove Broken Traps
  • Final Cleanup

実例: チェスト配置(スポーン地点の近くにZenithが入ったチェストを設置する)

TODO

応用(超高難易度)

Modの特殊シードを追加する

...できなくはないんだ。実は。
私の作ったModには特殊シードがある。

TODO

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?