テキストデータからマップを作ってみる

今回はUnity2Dでテキストのデータを用いた簡単な2Dのマップ作成をしてみたいと思います。
Unity2Dではマップを作るためにTilemapという便利な機能があるのですが、それを使わずにマップを作成します。

最終的にはこのようなマップを作ります。

Unityのバージョン 2021.3.10f1 で作業していきます。

この記事はシリーズものとなっています。

Unityスキルアップ、
始めるなら今

パズル、脱出、RPG...目標のゲームを完成させよう!

人気のUnity講座はこちら

Udemy講座

マップ作成に必要なものを準備

マップの素材をインポート

それではマップの素材になるものを準備します。
今回は地面と壁、そしてプレイヤーというシンプルなものにします。
AssetsフォルダにSpritesフォルダを作成し、素材となる画像をインポートします。
私の素材はドット絵なのでドットがきれいに見えるようにそれぞれインスペクターで調整します。
今回PixelsPerUnitはすべて24で統一しました
※参考記事

このような感じで用意しました。

左から、プレイヤー、地面、壁のつもりです。

素材をプレファブ化

それぞれの画像をPrefab化します。
素材をすべてHierarchyに一度配置します。
その後AssetフォルダにPrefabsというフォルダを作成し、Hierarchy上の素材をPrefabsフォルダにドラッグアンドドロップしてきます。
プレファブ化したらHierarchyに配置した先程の素材が青くなっていますので、それらを削除します。

テキストデータをインポート

最後にマップとなるテキストデータを準備します。
中身は0,1,2が並んでいるCSVのようなものです。
私は0が地面、1が壁、2がプレイヤーとして作成しています。
手動でテキストデータを入力しても良いのですが、マップデータを作ってくれる便利なツールもたくさんありますので、そういったツールを使っても良いかと思います。
私はTiled Map Editorというツールを使っています。

今回は横10マス、縦10マスのマップを作りました。

map.txtというテキストファイルでAssetsにMapTextフォルダを作成してインポートしておきます。
これで必要なものが揃いました。

Assetsフォルダは現在このようになっています。

テキストデータを読み取る

MapGeneratorに必要なものを登録

ここから先程作ったmap.txtをプログラムで読み込んでいく作業に入ります。
AssetsフォルダにC#スクリプトをまとめておくためのScriptsというフォルダを作っておきましょう。
その中にMapGeneratorという名前でC#スクリプトを作成しました。
続いてHierarchy上に空のGameObjectを作成します。
MapManagerとしました。
MapManagerにMapGeneratorをアタッチします。
ではMapGeneratorを作っていきます。

SerializeFieldでTextAsset型のmapText、各Prefab用にGameObject型の配列でprefabsという変数を用意します。

Unityエディターに戻りMapManagerを選択するとインスペクターでmapTextに空の箱ができていますので、map.txtをアタッチします。
続いてPrefabもアタッチしておきましょう。配列の添字、0,1,2とmap.txtの0,1,2が一致すると楽なので、地面、壁、プレイヤーの順番で登録します。

このような感じになりました。

mapTextを読み込む

続いてMapGeneratorでmapTextのデータを読み込む処理を書いていきます。
流れはこのような感じです。

  1. map.txtをstring型にする
  2. データを一行ずつ読み込む
  3. 一行のカンマ区切りのデータを分割する

こういうイメージでしょうか。ではやっていきます。
まずはmapTextをString型にし、一行ずつ読み込みます。

_loadMapData関数を作りました。
続いてmapText.textでstring型で取得、更に改行コードでmapTextをSplit関数で分割してstring型の配列に格納していってます。
Split関数の第二引数で使っているSystem.StringSplitOptions.RemoveEmptyEntriesは空の要素を除いてくれるというSplit関数のオプションです。
ここまでで実際に実行してみるとログに一行ずつ表示されているのがわかるかと思います。

続いて取り出した一行ずつを今度は同様にカンマで分割して一文字ずつ取得していきます。

やっていることは先程の一行ずつ分割するのと同じです。
これでマップデータを一つずつ取得することが出来ました。
ログで確認してみます。

画像だとわかりにくいですが、左上の値から順番にmapの値が取得されています。

列挙型でマップテーブルを作る

ここまででデータの読み込みはできましたが、0,1,2だとどれが壁でどれが地面なのかわかりにくいので、これらをわかりやすくしていきます。
列挙型を使います。

このように定義しました。
enumは上から順番に0,1,2…と数値が割り当てられています。
そしてMAP_TYPE型の二次元配列 mapTableを定義しています。
mapTableに取得したmapTextのデータを使わず、読みやすい形で格納しておくためです。

続いて定義したmapTableを_loadMapData関数内で初期化します。

ではmapTextのデータを取得し、mapTableに入れていきます。
具体的に_loadMapData関数はこのようなコードになりました。

念のためログで確認しておきましょうか。

先程まで0,1,2だったのがGROUND,WALL,PLAYERとなり、わかりやすくなりました。

Prefabを配置していく

とりあえず表示してみる

それでは先程作ったmapTableを使ってpreafabを敷き詰めmapを作って行きましょう。
_createMapという関数を作り、その中でmapTableをループし最初に登録しておいたprefabを生成してきます。
具体的にはこのような感じです。

コメントの通りなのですが、まずスタート関数で_createMap関数を呼び出し、mapTableを行、列の順でループさせ値を取得しています。
二次元配列の場合、その長さを取得するのはLengthではなくGetLength(int)で取得します。
今回行は二番目なのでGetLength(1)で最初に長さを取得して、そのループの中で列の長さをGetLength(0)で取得し二重ループしています。

続いてゲームオブジェクトを生成しています。
最初に定義したGameObjectの配列prefabsを使います。
どのprefabsを生成するかについてはmapTable[x,y]の値を使います。
mapTableはMAP_TYPE型なのでintにキャストし、prefabsの添字としています。

それでは試しに再生してみましょう。

期待した通りには表示されませんでした。
パッと見た問題点はこのような感じでしょうか。

  1. playerの背景にGroundがない
  2. マップチップの隙間
  3. 画面の中心に来ていない
  4. map.txtと上下が逆

ではこれらを解消していきましょう。

playerの背景にGroundがない

まずはPlayerの後ろにも背景をつけてあげましょう。
これはまず最初にGroundを全面に敷き詰めれば解消できると思います。
先程のループの中に処理をくわえてあげます。

続いてgroundが下に来るようにPrefabを修正しておきます。
Unityエディタのgroundプレファブを選択しインスペクターからSpriteRendererのOrder in Layerを-1とします。

それでは実行してみます。

無事Playerの後ろにもGroundが表示されました。

マップチップの隙間を埋める

それでは次にマップチップの間を無くしきれいに敷き詰めて行きたいと思います。
それにはまず各マップの縦横の大きさを把握することが必要です。
具体的にはmap用のPrefabにあるSpriteRendererコンポーネントからサイズを取得することができます。

次に並べる方法です。
今まではVector2で順番に並べていましたが、正常にスクリーンに合わせて並べる必要があります。
そのために_screenPosという関数を作り、その中でサイズを考慮したポジションを計算します。
ソースコードはこのようになりました。

まずメンバー変数でfloat型のmapSizeを定義します。
次に_createMap関数内でprefabs[0](今回はgroundのPrefab)にあるSpriteRendererからbounds.size.xでサイズを取得しています。
続いてループに入り、Vector2Int型でposという変数を作り、Vector2Int(x,y)を代入しています。
更に今まで生成したオブジェクトのpositionにVector2(x,y)を代入していましたが、Prefabのサイズを考慮した_screenPos関数を定義しそこで計算した座標を受け取りpositionにしています。
Vector2Intにしているのはx,yは整数の値ですので、float型のVector2にする必要はないためです。
マス目をイメージしている感じです。

では実際に実行してみます。

隙間が消え、ぴっちりとマップが敷き詰められました。

画面の中心に持ってくる

今度はできたマップを真ん中に持ってきましょう。
今はマップの左下が画面の真ん中に来ています。
それをずらせば真ん中に来ると思います。
まずはマップの中心を取得し、その分だけX方向、Y方向にマイナスすれば期待した位置に来てくれると思います。
絵で描くとこんなイメージでしょうか。

コードはこのようになりました。

メンバー変数で中心座標用の変数を宣言します。
次に_createMap関数で縦横のmapTableの長さを半分にし、mapSizeをかけることでマップの中心x,yを求めます。
ただしmapTableが偶数の場合はそれぞれmapSzieを半分にしたものをcenterPosからマイナスしたもので中心が取得できます。
最後に_screenPos関数でmapSizeを考慮したx,yそれぞれにcenterPos.x,centerPos.yをマイナスすることで、全体が中心にスライドされます。
実際に起動して確認してみます。
今回マップは10×10です。

ちゃんと中心に来ているのが確認出来ました。
試しに10×11にmap.txtを修正して実験してみます。

正方形ではなくてもちゃんと画面中心に来てくれました。

上下反転しているバグ

次にmap.txtと表示したマップの上下が反転しているのでそれを修正します。
何故このようなことが起こるかというと、プログラム的にはmap.txtを左上から右下に読み込んでいき、実際にUnityで描画しています。
一方でUnityではY軸が増えていくと左下から右上に描画していきます。
X軸に関しては左から右で問題ないのですが、Y軸だけは逆転しています。
ではどうすればよいかというと単純に_screenPos関数のY軸を反転すれば解消します。
コードはこのようになります。

実際の描画とmap.txtを並べてみます。

いかがでしょうか。Playerがmap.txtでいうと2になります。
ちゃんと左上にPlayerが表示されmap.txt通りのマップが出来上がりました。

この記事の続きで、こちらでプレイヤーの移動を実装しています。

またここまでの内容をgithubに載せておきます。

関連記事

最後までご覧頂いてありがとうございました。

セール中!
C#プログラミング入門

もっと早く教えてほしかった!

Unity C#プログラミング入門