テキストデータからマップを作ってみる
今回はUnity2Dでテキストのデータを用いた簡単な2Dのマップ作成をしてみたいと思います。
Unity2Dではマップを作るためにTilemapという便利な機能があるのですが、それを使わずにマップを作成します。
最終的にはこのようなマップを作ります。
Unityのバージョン 2021.3.10f1 で作業していきます。
この記事はシリーズものとなっています。
マップ作成に必要なものを準備
マップの素材をインポート
それではマップの素材になるものを準備します。
今回は地面と壁、そしてプレイヤーというシンプルなものにします。
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のデータを読み込む処理を書いていきます。
流れはこのような感じです。
- map.txtをstring型にする
- データを一行ずつ読み込む
- 一行のカンマ区切りのデータを分割する
こういうイメージでしょうか。ではやっていきます。
まずは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の添字としています。
それでは試しに再生してみましょう。
期待した通りには表示されませんでした。
パッと見た問題点はこのような感じでしょうか。
- playerの背景にGroundがない
- マップチップの隙間
- 画面の中心に来ていない
- 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に載せておきます。
Unity開発おすすめPC
NEXTGEAR JG-A5G5A

CPU : AMD Ryzen™ 5 4500 プロセッサー
グラフィックス : NVIDIA® GeForce RTX™ 3050
メモリ標準容量 : 16GB (8GB×2 / デュアルチャネル)
M.2 SSD : 1TB (NVMe)
保証期間 : 3年間センドバック修理保証・24時間×365日電話サポート
Lenovo LOQ Essential Gen 9 - ルナグレー

CPU : インテル® Core™ i7-12650HX
グラフィックス : NVIDIA® GeForce RTX™ 4050 Laptop GPU 6GB GDDR6
メモリ標準容量 : 16 GB DDR5-4800MHz
ストレージ : 512 GB SSD M.2 2242 PCIe-NVMe Gen4 QLC
ディスプレイ :15.6" FHD液晶 (1920 x 1080) IPS, 光沢なし, マルチタッチ非対応, 100%sRGB, 300 nit, 144Hz
内蔵カメラ :720p HDカメラ (プライバシーシャッター付)
無線 :Wi-Fi 6対応 (IEEE 802.11ax/ac/a/b/g/n準拠) 2x2 & Bluetooth®
保証期間 : 1 年間 Legion Ultimate Support
LOQ Tower 17IRR9 :カスタマイズモデル

CPU : インテル® Core™ i5-14400F プロセッサー
グラフィックス : NVIDIA® GeForce RTX™ 3050 6GB GDDR6
メモリ標準容量 : 16 GB DDR5-4800MHz (UDIMM) - (2 x 8 GB)
ストレージ : 512 GB SSD M.2 2280 PCIe-NVMe Gen4 TLC
保証期間 : 1 年間 Legion Ultimate Support
NEXTGEAR JG-A5G60(ホワイトカラーモデル)

CPU : AMD Ryzen™ 5 4500 プロセッサー
グラフィックス : NVIDIA® GeForce RTX™ 4060
メモリ標準容量 : 16GB (8GB×2 / デュアルチャネル)
M.2 SSD : 1TB (NVMe)
保証期間 : 3年間センドバック修理保証・24時間×365日電話サポート
NEXTGEAR JG-A5G60(1周年記念モデル)

CPU : AMD Ryzen™ 5 4500 プロセッサー
グラフィックス : NVIDIA® GeForce RTX™ 4060
メモリ標準容量 : 16GB (8GB×2 / デュアルチャネル)
M.2 SSD : 1TB (NVMe)
保証期間 : 3年間センドバック修理保証・24時間×365日電話サポート
G TUNE FG-A7A7X

CPU : AMD Ryzen™ 7 9800X3D プロセッサ
グラフィックス : AMD Radeon™ RX 7700 XT
メモリ標準容量 : 32GB (16GB×2 / デュアルチャネル
M.2 SSD : 2TB (NVMe Gen4×4)
ドライブ仕様 :DVDスーパーマルチドライブ
無線 :Wi-Fi 6E( 最大2.4Gbps )対応 IEEE 802.11 ax/ac/a/b/g/n準拠 + Bluetooth 5内蔵
保証期間 : 3年間センドバック修理保証・24時間×365日電話サポート
関連記事

Unityで実装するローグライクなマップ自動生成
ローグライクゲームでよく使われそうなマップ生成方法に焦点を当ててみたいと思います。
ローグライクのマップはランダムに生成された部屋と通路で構成されていて、毎回異なるレイアウトのマップが自動的に作られます。
この記事ではその基本的な作り方をやってみます。

ドルアーガの塔みたいな薄い壁の迷路を作る
薄い壁の2Dダンジョンを作ってみたいと思います。
1マスに上下左右の壁を設置する感じです。

ウィザードリィのような疑似3Dダンジョンを作る
ウィザードリィのような疑似3DダンジョンをUnity2Dで作成してみたいと思います。

穴掘り法で2D迷路を作る
穴掘り法を使って2Dマップを作成してみます。
プレイヤーの移動まで実装しています。

見下ろし2Dマップ上でプレイヤーを動かす
プレイヤーを2Dマップ上で移動させてみます。
見下ろし型のRPGとかでありそうなやつです。
最後までご覧頂いてありがとうございました。
宜しければコメントをどうぞ
はじめまして。
どうか教えていただきたいのです。
1~3ページ目まではすべて行いうまくいきましたが、Input.GetKeyDownでキーボードでPlayerを動かすのではなく、スマートフォンで動かしたいためButtonでPlayerを動かしたいです。
そうするとPlayerがうまく動きません。currentPosがうまく取得できないみたいです。
Buttonで動かす方法を教えていただけませんか?
よろしくお願いいたします。
コメントありがとうございます。
ボタン操作の場合ですが、一例を書いてみます。
まずcanvasにbuttonsという空のゲームオブジェクトを配置して、上右下左のボタンを配置します。
つづいてMapGeneratorに
public GameObject buttons;
と変数を用意します。
続いてPlayerスクリプトを変更していきます。
ボタンを操るのでusing UnityEngine.UI;を追記します。
次にButtonの配列を用意します。
私の場合はcursorsという変数名にしました。
次にStart関数内で
cursors = mapGenerator.buttons.GetComponentsInChildren<Button>();
としてmapGeneratorのbuttons変数から子要素のButtonをすべて取得します。
その次にそれぞれのボタンのonClick時にAddListenerで処理を割り当てます。
私の場合はこんな感じに書きました。
cursors[(int)DIRECTION.UP].onClick.AddListener(() => MoveForward());
cursors[(int)DIRECTION.RIGHT].onClick.AddListener(() => TurnRight());
cursors[(int)DIRECTION.DOWN].onClick.AddListener(() => TurnBack());
cursors[(int)DIRECTION.LEFT].onClick.AddListener(() => TurnLeft());
そして、それぞれの関数を書いていきます。
内容はUpdate関数内に書いてあるキーを入力したものを書いていく感じです。
void MoveForward()
{
mapGenerator.ResetView3D();
_move();
_getMapPositions();
}
void TurnRight()
{
mapGenerator.ResetView3D();
direction++;
_setDirection();
_viewArrow();
_getMapPositions();
}
void TurnBack()
{
mapGenerator.ResetView3D();
direction += 2;
_setDirection();
_viewArrow();
_getMapPositions();
}
void TurnLeft()
{
mapGenerator.ResetView3D();
direction–;
_setDirection();
_viewArrow();
_getMapPositions();
}
少し長くなりましたがこんな感じで動くのではないでしょうか!