Articles

Unity:how to create2D Tilemap programmatically

For video tutorial click here

様々なサイズの異なるTilemapを作成する必要がある場合は、このチュートリアルの第二部をチェックしてください

Tilemapコンポーネ バージョン2018.3では、Isometric Tilemapが導入され、2.5Dゲームに優れたサポートが提供されました。 最近、私はこのコンポーネントを密接に扱う機会があり、プログラムでタイルを作成するタスクに挑戦されました。 私の最後のゲームでは、私は多くのレベルを持っており、それらのすべてが同じTilemapベースのボードを持っています。 しかし、ボード自体はレベル固有の設定を持っています。 明らかに、プロの開発者であることは、私は60のゲームシーンを作成し、手ですべてのレベルをペイントするのではなく、与えられた入力に応じて適切な要素でボードを埋めるためのメカニズムを持っていたくありませんでした。 あなたが最終結果がどのように見えるか興味があるなら、ここにゲームへのリンクがあります。

ソースコードはGitHubで入手できます。私は最新の利用可能なUnity2019.2.0.1fを使用しています。 このチュートリアルを使用するには、少なくともバージョン2018.3が必要です。 アイソメトリックグリッドを使用しますが、記載された技術はどのタイプにも適用できます。

開始する前に、私は強く等尺性Tilemapの基本的な理解を得るためにTilemapブログエントリでこの華麗な等尺性の2D環境を読むことをお勧めします。

2D環境で作業しているので、新しい2Dプロジェクトを設定する必要があります(2dand3dmodesettingsを参照)

目標を達成するために必要な3つの主なコンポー: ゲームが行われるゲームボード、レベルの説明と別のものに接続するいくつかのコードを維持する場所。

パート1。 ボードの作成

まず、必要な画像アセットをインポートしましょう。 私は私のゲームに使用したのと同じ画像を使用します:

Used to create a base tilemap level

Used as a path

Used to mark the start and end points of a パス

私たちは、単にそこにドラッグアンドドロップ、タイルフォルダ内の画像を保持します。Div>

タイルセットアップ
div>

注意! 各タイル画像ごとに正しい”単位あたりのピクセル”サイズが設定されている必要があります(詳細については、Tilemapを使用した等尺性の2D環境を参照)。 これらの画像の値は1096です。div>

画像セットアップ
div>

シーン上の異なるレイヤーを、説明的な名前を持つ専用のゲームオブジェクトに分離することをお勧めします。 ゲームゾーン、UI、広告プレースメントなどを含む典型的なゲーム画面を想像してみてください。このアプローチに続いて、ボードとすべてのゲーム要素を配置できるGameZoneという名前の新しい空のゲームオブジェクトを作成しましょう。

今では、我々は以前にインポートした画像から実際のタイルを作成する時間です

プレスウィンドウ->2D->タイルパレット

タイルパレット

タイルパレットビューが開きます。 「Create new Palette」をクリックしてパレットを作成します。:

New Palette

Drag and drop tile images one by one to create an actual tiles.

Tiles setup

With those tiles we can finally create the game board which will be filled with elements programmatically on a level startup later.階層ビューでGameZoneを検索し、マウスの右クリック→2Dオブジェクト→アイソメトリックタイルマップ。 新しいゲームオブジェクトGridが作成されます。

ボードのサイズは11×11タイルになり、塗装を開始できます。 “タイルパレット”ビューでボックスブラシツール(左から4番目の要素)を選択し、”タイルをきれいにする”を選択します。 シーンビューでペイントします。Div>

ペイントが完了したら、タイルマップの境界を手動で圧縮する必要があります。 こるか選択する必要がありますTilemapの階層表示、プレスを設定(ギア) ボタンをTilemap部品を選んで”圧縮Tilemapとなります。

となっています。figcaption>圧縮Tilemap境界

も、ゲームボードを使用!● 私たちは、パート3でそれに戻って取得します。

パート2。 レベルデータ

同じシーンと同じゲームボードを再利用したいので、レベルデータはどこかに保管する必要があります。 そのための簡単な解決策は、各レベルをどのように構築するかを記述する単純なjsonファイルです。私たちが達成しようとしていることを理解することが重要です。

私は力学についていくつかの言葉を言わなければならない理由です。

ゲームでは、最初から最後までパスと一緒に移動しているオブジェクトがあります(ほとんどズマのように)、プレイヤーの目標はそれらのすべてを破壊す このチュートリアルでは、各レベルで一意になるこのパスを作成します。

さて、プロジェクトに戻ります。

スクリプトから実行時に外部データにアクセスするには、複数の方法があります。 ここでは、リソースフォルダを使用します

のは、新しいフォルダを作成してみましょう—ファイル—とサブフォルダのリソース。 これがデータを保持したい場所なので、新しいファイルレベルを作成します。jsonをそこに配置します。

チュートリアルの目的のために、各レベルを記述するためのフィールドは二つだけです。

  • number—レベルを識別するためのint
  • path—プログラムで作成 最初の値が開始点で、最後の値が終了点である値の配列。

これは私が使用するファイルです。 これらの値について心配しないでくださいpath、後でそれに来るでしょう。

{
"levels":
},
{
"number": 2,
"path":
},
{
"number": 3,
"path":
}
]
}

別のフォルダ—Scripts—を作成してみましょう。

コード内のファイルからデータにアクセスしたいので、そのためのモデルクラスが必要です。 最初のスクリプトLevelsDataを作成します。 Unityによってインスタンス化されることを意図していないため、MonoBehaviourStartUpdatearrayintint array注釈を入れることを忘れないでください。P>


public class LevelsData
{
public LevelData levels;
public class LevelData
{
public int number;
public int path;
}
}

いいですね、今、私たちは、ファイルとモデルを持っています。 次のステップは、1つを別のものに変換することです。 別のスクリプトGameZoneGameZoneオブジェクトにアタッチしましょう。 このスクリプトは、後でゲームボード全体を設定するために使用されます。P>

単一責任の原則に従ってくださいさらに別のスクリプトを作成しましょう—LevelsDataLoaderGameZoneオブジェクトにも添付します。このクラスは、データをロードし、キーがレベル番号であり、データがレベルデータ自体である辞書として返します。

public class LevelsDataLoader : MonoBehaviour
{
private const string LevelsPath = "Levels";
public Dictionary<int, LevelsData.LevelData> ReadLevelsData()
{
var jsonFile = Resources.Load(LevelsPath, typeof(TextAsset)) as TextAsset;
if (jsonFile == null)
{
throw new ApplicationException("Levels file is not accessible");
}
var loadedData = JsonUtility.FromJson<LevelsData>(jsonFile.text);
return loadedData.levels.ToDictionary(level => level.number, level => level);
}
}

これで、GameZoneスクリプトのデータにアクセスできるはずです。

public class GameZone : MonoBehaviour
{
private Dictionary<int, LevelsData.LevelData> _levelsData;
private LevelsDataLoader _dataLoader;private void Awake()
{
_dataLoader = GetComponent<LevelsDataLoader>();
}private void Start()
{
_levelsData = _dataLoader.ReadLevelsData();Debug.Log(_levelsData.Count + " levels have been stored in the dictionary!");
}
}

Unityに戻って、再生ボタンを押してコンソールを確認してください-「3つのレベルが辞書に保存されています!”

パート3。 ボードとデータを接続する

おめでとう、あなたはこのチュートリアルの最後と最も興味深い部分に達しました。 実際にボードとデータをどのように接続しますか? それを見つけるために読んでください!

まず、チュートリアルの最初の部分で作成されたhorizontalstart_stopタイルをTilesフォルダの下のResourcesフォルダに配置しましょう。 次に、新しいスクリプトを追加します—TilesResourcesLoader —実行時にそのフォルダからタイルをロードする静的ヘルパークラス。

public static class TilesResourcesLoader
{
private const string PathHorizontal = "horizontal";
private const string StartStop = "start_stop"; public static Tile GetPathHorizontalTile()
{
return GetTileByName(PathHorizontal);
} public static Tile GetStartStopTile()
{
return GetTileByName(StartStop);
} private static Tile GetTileByName(string name)
{
return (Tile) Resources.Load(name, typeof(Tile));
}
}

最後のステップとして、シーンの起動時にこれらのタイルをボードに配置する必要があります。 GameZoneGameZoneに追加し、startの値を1に変更しましょう。 私は最初にあなたに最終的なスクリプトを表示します:

public class GameZone : MonoBehaviour
{
public int Level; private const int FieldLineSize = 11;
private const int FieldTotalTiles = FieldLineSize * FieldLineSize;
private Dictionary<int, LevelsData.LevelData> _levelsData; private void Start()
{
_levelsData = GetComponent<LevelsDataLoader>().ReadLevelsData();
SetupTiles();
} private void SetupTiles()
{
var baseLevel = GetComponentsInChildren<Tilemap>(); var localTilesPositions = new List<Vector3Int>(FieldTotalTiles);
foreach (var pos in baseLevel.cellBounds.allPositionsWithin)
{
Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
localTilesPositions.Add(localPlace);
} SetupPath(localTilesPositions, baseLevel);
} private void SetupPath(List<Vector3Int> localTilesPositions, Tilemap baseLevel)
{
var path = _levelsData.path;
var pathHorizontalTile = TilesResourcesLoader.GetPathHorizontalTile();
var first = path.First();
var last = path.Last();
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
} var startStopTile = TilesResourcesLoader.GetStartStopTile();
baseLevel.SetTile(localTilesPositions, startStopTile);
baseLevel.SetTile(localTilesPositions, startStopTile);
}
}

うわー、それは多くのアクションです! 私はそれを介してあなたを歩いてみましょう。

SetupTilesメソッドでは、タイルの位置を知る必要があるため、最初にtilemap自体を取得する必要があります。 これを達成するために、最初のタイルから始まるタイルのすべての位置を返すtilemap.cellBounds.allPositionsWithinメソッドを使用しています。

各番号は、localTilesPositionsリスト内のインデックスを表す次の図を参照してください。div>

あなたは私たちがパスで使用する値を覚えていますかLevels.json? あなたがすでに推測しているかもしれないように、これらの値は配列内のタイルのインデックスです。 あなたが今する必要があるのは、あなたがレベルを構築するのを助けるためにチートシート画像を持っていることです。 それは私が開発中に使用してきたものです:div>

この例では、パスに水平線を設定しています。SetupPathメソッドを参照してください。 重要な部分は次のループです。

foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
}

ここでは、localTilesPositionsこの場合、目的のタイル水平を設定するものを見つけます。

注意してください!

GetRangestart_stopタイルが使用されます。div>

最終結果
最終結果
最終結果
最終結果
最終結果

figcaption>

今、GameZoneスクリプトのレベル番号フィールドを1から2または3に変更しようとすると、パスが指定されたレベルに対して適切にロードされていることがわかります。

その後