質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

3回答

3161閲覧

UnityのTileMapをJSONデータから作成した時の不具合

momiji0210

総合スコア60

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2020/04/22 04:44

UnityでTileMapのエディターもどきを制作しております。

JSONの文字列データからマップの生成を行っており、数値ごとにセルにスプライトを割り当ててます。

例)-1ならタイルなし、0なら0番目のスプライトを使用

PlayMode(再生ボタン)中なら問題なく動作するのですが、
再生中以外に生成してしまうとTileMapの保存情報が壊れているみたいです。

具体的にはタイルを置いていない場所に0番目ののスプライトタイルがおかれてしまいます。
再生していない際でも、正常に動作させる方法はないでしょうか。

特定のGameObject下のgameObjectを全削除するプログラムを書いているつもりなのですが、
こちらも再生中以外はうまく動作しないようです。

3~4件削除した後に処理がとまってしまいます。

JSONデータ

1"listMap": [ 2 "0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0", 3 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 4 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 5 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 6 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 7 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 8 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 9 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 10 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 11 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 12 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 13 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,3", 14 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,4, 4, 4, 3", 15 "2, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,4, 4, 4, 3", 16 "2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3", 17 "2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 3", 18 "2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 3", 19 "7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7" 20 ],

CustomAddButton.cs

1 2[CustomEditor(typeof(TileMapManager))] //拡張するクラスを指定 3public class CustomAddButton : Editor { 4 5 /// <summary> 6 /// InspectorのGUIを更新 7 /// </summary> 8 public override void OnInspectorGUI(){ 9 10 //ボタンを表示 11 if (GUILayout.Button("Json Load")){ 12 // エディタが再生しているか or しようとしてるかどうか 13 if(EditorApplication.isPlayingOrWillChangePlaymode){ 14 tileMapManager.JsonLoad(); 15 Debug.Log("Json Load"); 16 } else { 17 Debug.Log("Not Play Mode"); 18 } 19 } 20 } 21 22 //ボタンを表示 23 if (GUILayout.Button("Map Delete")){ 24 // エディタが再生しているか or しようとしてるかどうか 25 if(EditorApplication.isPlayingOrWillChangePlaymode){ 26 tileMapManager.MapDelete(); 27 Debug.Log("Map Delete"); 28 } else { 29 tileMapManager.MapDelete(); 30 Debug.Log("Map Delete"); 31 Debug.Log("Not Play Mode"); 32 } 33 } 34 35}

TileMapManager

1public class TileMapManager : MonoBehaviour { 2 3 [ContextMenu("Method")] 4 private void Method () { 5 6 } 7 8 public Tilemap tilemap; 9 10 // ステージを生成する 11 public void JsonLoad() { 12 13 // タイルマップのリセット 14 ResetTileMap(tilemap); 15 16 // 保存 17 jsonStage.SetFileName(fileNameJson); 18 19 // リセット 20 jsonStage.stage.Reset(); 21 22 // 読み込み 23 jsonStage.Load(); 24 25 Sprite sprite; 26 var bound = tilemap.cellBounds; 27 Tile tile = ScriptableObject.CreateInstance<Tile>(); 28 29 // マップの生成 30 for(int y=0; y<jsonStage.stage.listMap.Count; y++){ 31 32 // マップデータ文字を分解 33 String width = jsonStage.stage.listMap[y]; 34 width = width.Replace(" ", ""); // 空白を削除 35 36 // 文字列を分解してlistに追加 37 string[] arrayWidth = width.Split(','); 38 List<int> listWidth = new List<int>(); 39 for(int i=0; i<arrayWidth.Length; i++){ 40 int num = int.Parse( arrayWidth[i] ); 41 listWidth.Add(num); 42 } 43 44 // タイルマップのセット 45 for(int x=0; x<listWidth.Count; x++){ 46 47 // -1なら次へ 48 int spriteNum = listWidth[x]; 49 if( spriteNum < 0) continue; 50 51 // タイルマップのポジションの取得 52 //int posX = bound.position.x + x + 1; 53 int posX = bound.position.x + x; 54 int posY = (bound.position.y + jsonStage.stage.listMap.Count) - y - 1; 55 //int posY = (bound.position.y + jsonStage.stage.listMap.Count) - y; 56 57 // タイルマップのセット 58 string spriteName = jsonStage.stage.listSpriteName[spriteNum]; 59 sprite = GetSprite("Sprites/test", spriteName); 60 61 tile.sprite = sprite; 62 63 // 角度の設定 64 Quaternion rot = Quaternion.Euler(0.0f, 0.0f, 0.0f); 65 66 // 右上タイル反転 67 if(spriteName == CORNER_1_SPRITE_NAME){ 68 if(cornerCount1 == CORNER_1_FLIP){ 69 rot = Quaternion.Euler(0.0f, 180.0f, 0.0f); 70 } 71 cornerCount1++; 72 } 73 74 // 左下タイル反転 75 if(spriteName == CORNER_2_SPRITE_NAME){ 76 if(cornerCount2 == CORNER_2_FLIP){ 77 rot = Quaternion.Euler(0.0f, 180.0f, 0.0f); 78 } 79 cornerCount2++; 80 } 81 82 // タイルの設定と回転 83 SetTile(new Vector3Int( posX, posY, 0 ), rot, tilemap, tile); 84 85 } 86 } 87 88 // マップを全削除 89 public void MapDelete(){ 90 91 // 子オブジェクトを全削除 92 AllDestroy(stageObject); 93 ResetTileMap(tilemap); 94 95 } 96 97 // サイズを変えずに全てのタイルをリセット 98 public void ResetTileMap(Tilemap tilemap){ 99 100 var bound = tilemap.cellBounds; 101 // 全て削除 102 for (int y = bound.max.y - 1; y >= bound.min.y; --y){ 103 for (int x = bound.min.x; x < bound.max.x; ++x){ 104 Tile tile = ScriptableObject.CreateInstance<Tile>(); 105 Quaternion rot = Quaternion.Euler(0.0f, 0.0f, 0.0f); 106 //tilemap.SetTile(new Vector3Int(x, y, 0), tile); 107 SetTile(new Vector3Int( x, y, 0 ), rot, tilemap, tile); 108 } 109 } 110 } 111 112 // タイルをセット 113 private void SetTile(Vector3Int pos, Quaternion rot, Tilemap tilemap, Tile tile) { 114 tilemap.SetTile(pos, tile); 115 tilemap.SetTransformMatrix(pos, Matrix4x4.TRS(Vector3.zero, rot, Vector3.one)); 116 } 117 118 // 子オブジェクトを全削除 119 public void AllDestroy(GameObject gameobject){ 120 121 // PlayModeでなければ 122 if(EditorApplication.isPlayingOrWillChangePlaymode){ 123 foreach ( Transform n in gameobject.transform ) { 124 GameObject.Destroy(n.gameObject); 125 } 126 } else { 127 foreach ( Transform n in gameobject.transform ) { 128 GameObject.DestroyImmediate(n.gameObject); 129 } 130 } 131 } 132 } 133 134}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

#Object Delete Flagがオンの状態でMap Deleteボタンを押しても一度に全部消えない件について
たとえばオブジェクトを8個作った状態だと、Map Deleteボタンを押すたびに8個→4個→2個→1個→0個といった具合にオブジェクトが半々に減っていきます。この奇妙な挙動はTransformが子を列挙する仕組みのせいのような気がします。

子の列挙が開始されると、まず内部的なインデックスが0に初期化された状態で、0番目の子が返されます。ここでDestroyImmediateによりオブジェクトが削除されると、子の総数は7個に減って、削除前は1番だった子が0番に変わるはずです。
ですがTransformの列挙子はそれにはおかまいなく、内部的なインデックスをインクリメントして1にします。結果として次は1番の子...つまり初期状態では2番だった子が返されることになり、0番の子...つまり初期状態では1番だった子は列挙から漏れて生き残ります。
Transformの列挙子はループのたびにその時点の子の数と内部的なインデックスを比較し、インデックスが子の数以上になったところで列挙を終了します。結果として初期状態で0、2、4、6番だった子は削除され、1、3、5、7番だった子は生き残るのでしょう。

ゲーム実行中だとちゃんと全件削除されるのは、おそらくDestroyImmediateではなくDestroyを使っているためでしょう。
こちらでは、オブジェクトが実際に削除されるのはレンダリング直前まで遅延されるので、子の列挙作業中には子の数は変化しないはずです。ですのでTransformの列挙子がすべての子を漏れなく列挙できたのだろうと思われます。

TileMapManager2の全削除メソッドを(ついでに追加メソッドも)下記のようにしてみるとどうでしょうか。

C#

1 // 子オブジェクトを全削除 2 public void AllDestroy(GameObject gameobject) { 3 // PlayModeでなければ 4 if(EditorApplication.isPlayingOrWillChangePlaymode){ 5 foreach ( Transform n in gameobject.transform ) { 6 GameObject.Destroy(n.gameObject); 7 } 8 } else { 9 // 子を列挙しながら削除していくのではなく、まず子を配列にして 10 // 処理対象を確定させてしまってから... 11 // (Cast<Transform>を使うには冒頭にusing System.Linq;が必要です) 12 Transform[] children = gameobject.transform.Cast<Transform>().ToArray(); 13 14 // それらを削除していく 15 foreach ( Transform n in children ) { 16 // 削除によってヒエラルキーの状態が変わっても、ヒエラルキービュー上のシーン名部分に 17 // 要保存を示す「*」マークが付かないことにお気付きかもしれません 18 // DestroyImmediateの代わりにUndo.DestroyObjectImmediateを使うと、シーン状態の変化が 19 // ちゃんと認識されて「*」マークが付くかと思います 20 // GameObject.DestroyImmediate(n.gameObject); 21 Undo.DestroyObjectImmediate(n.gameObject); 22 } 23 } 24 }

C#

1 // 追加 2 public void Add() { 3 GameObject obj = Instantiate( 4 prefab, 5 Vector3.zero, 6 Quaternion.identity, 7 stageObject.transform 8 ) as GameObject; 9 10 // 上記と同じく、こちらについても生成したオブジェクトをUndoレコードに 11 // 登録してやることで変更が認識され、「*」マークが付くと思います 12 Undo.RegisterCreatedObjectUndo(obj, $"Create {prefab.name}"); 13 }

#タイルの外観が急に全部同じになってしまう件について
どうやら先の回答で申し上げた、一つのタイルオブジェクトを使い回すことによる弊害が現れたような感じですね...
対処案として、まずJSON_PARENTには後のコード中コメントで言及しました「スプライトと対応する既存のタイルを探す」という作業に失敗した場合の保険として、下記のようなプロパティとメソッドを追加しました。

C#

1 // このマップ用のタイルアセット保存フォルダーパス 2 private string TilesDirectoryPath => (Path.GetDirectoryName(filePath) == "StreamingAssets" ? "Assets/" : filePath) + Path.GetFileNameWithoutExtension(fileName) + "Tiles"; 3 4 // フルパスをAssets起点のパスにする 5 private static string GetAssetPath(string fullPath) 6 { 7 // fullPathの「Assets」で始まる以降の部分を取り出して返す 8 var root = Application.dataPath; 9 return fullPath.StartsWith(root) ? fullPath.Substring(root.Length - 6) : null; 10 } 11 12 // このマップ用に作成されたタイルアセットをすべて削除 13 public void DeleteTileAssets(){ 14 #if UNITY_EDITOR 15 string directoryPath = TilesDirectoryPath; 16 if (Directory.Exists(directoryPath)) 17 { 18 foreach (string path in Directory.EnumerateFiles(directoryPath)) 19 { 20 string assetPath = GetAssetPath(path); 21 if (AssetDatabase.DeleteAsset(assetPath)) 22 { 23 Debug.Log($"Delete {assetPath}"); 24 } 25 else 26 { 27 File.Delete(path); 28 Debug.Log($"Delete {path}"); 29 } 30 } 31 Directory.Delete(directoryPath); 32 Debug.Log($"Delete {directoryPath}"); 33 } 34 #endif 35 } 36 37 // タイルアセットをプロジェクト内に保存 38 public void SaveTileAsset(UnityEngine.Tilemaps.Tile tile){ 39 #if UNITY_EDITOR 40 string directoryPath = TilesDirectoryPath; 41 if (!Directory.Exists(directoryPath)) 42 { 43 Directory.CreateDirectory(directoryPath); 44 Debug.Log($"Create directory {directoryPath}"); 45 } 46 47 string filePath = directoryPath + "/" + tile.name + ".asset"; 48 if (File.Exists(filePath)) 49 { 50 Debug.LogError($"{filePath} exists."); 51 } 52 else 53 { 54 string assetPath = GetAssetPath(filePath); 55 AssetDatabase.CreateAsset(tile, assetPath); 56 Debug.Log($"Create {assetPath}"); 57 } 58 #endif 59 }

そしてTileMapManager2の変更点として、まず宣言部分を

C#

1public class TileMapManager2 : MonoBehaviour, ISerializationCallbackReceiver {

とし、

C#

1 // ゲーム実行時にタイルを得るため、タイルへの参照をシリアライズして持っておく 2 [SerializeField, HideInInspector] private Sprite[] tileKeys; 3 [SerializeField, HideInInspector] private Tile[] tileValues; 4 private Dictionary<Sprite, Tile> tiles = new Dictionary<Sprite, Tile>(); 5 6 public void OnBeforeSerialize() 7 { 8 tileKeys = tiles.Keys.ToArray(); 9 tileValues = tiles.Values.ToArray(); 10 } 11 12 public void OnAfterDeserialize() 13 { 14 foreach ((Sprite key, Tile value) in tileKeys.Zip(tileValues, (key, value) => (key, value))) 15 { 16 tiles.Add(key, value); 17 } 18 } 19 20 // スプライトと対応するタイルを得る 21 // tiles内に存在しなければアセットデータベース内を探し、それでも見つからなければ新規にタイルを生成する 22 private Tile GetTileForSprite(Sprite sprite) { 23 if (!tiles.TryGetValue(sprite, out Tile tile)) 24 { 25 #if UNITY_EDITOR 26 string guid = AssetDatabase.FindAssets($"{sprite.name} t:Tile").FirstOrDefault(); 27 if (string.IsNullOrEmpty(guid)) 28 { 29 tile = ScriptableObject.CreateInstance<Tile>(); 30 tile.name = sprite.name; 31 tile.sprite = sprite; 32 jsonStage.SaveTileAsset(tile); 33 } 34 else 35 { 36 tile = AssetDatabase.LoadAssetAtPath<Tile>(AssetDatabase.GUIDToAssetPath(guid)); 37 } 38 #else 39 // UNITY_EDITORでない状態でこのメソッドが実行されたということは 40 // 実機上でのプレイ時であると考えられるため、タイルをアセットデータベースに 41 // 保存してやるといった考慮は不要なはず 42 // そのため、単純にタイルを生成するだけとする 43 tile = ScriptableObject.CreateInstance<Tile>(); 44 tile.name = sprite.name; 45 tile.sprite = sprite; 46 #endif 47 tiles[sprite] = tile; 48 } 49 50 return tile; 51 }

を追加、GenerateMapは下記のようにして(大部分は変更なしですので、字数節約のためばっさり省略しました)...

C#

1 void GenerateMap(){ 2 3 cornerCount1 = 0; 4 cornerCount2 = 0; 5 6 // タイルマップのリセット 7 // 古いタイルアセットは一旦削除する 8 #if UNITY_EDITOR 9 jsonStage.DeleteTileAssets(); 10 tiles.Clear(); 11 #endif 12 ResetTileMap(tilemap); 13 14 Sprite sprite; 15 //var bound = tilemapTest.cellBounds; 16 var bound = tilemap.cellBounds; 17 18 // タイルをいきなり新規生成するのはやめる 19 //Tile tile = ScriptableObject.CreateInstance<Tile>(); 20 21 // 省略...「マップの生成」から「タイルマップのセット」のsprite = GetSprite("Sprites/test", spriteName);まで変更なし 22 23 // タイルのスプライトを切り替えながら塗るのではなく、スプライトと対応する 24 // 既存のタイルを探して(なければ生成して)、それを使って塗る 25 // tile.sprite = sprite; 26 Tile tile = GetTileForSprite(sprite); 27 28 // 省略...「角度の設定」以降は変更なし

ResetTileMapも下記のように変えました。

C#

1 // サイズを変えずに全てのタイルをリセット 2 public void ResetTileMap(Tilemap tilemap){ 3 4 /* 5 var bound = tilemap.cellBounds; 6 var size = tilemap.editorPreviewSize; 7 Debug.Log(bound); 8 Debug.Log(size); 9 tilemap.ClearAllTiles(); 10 tilemap.cellBounds = bound; 11 Debug.Log(bound); 12 Debug.Log(size); 13 */ 14 // ここでもやはりUndoレコードにグリッドオブジェクト以下を登録しておき、 15 // ダーティーフラグ周りの不整合を防止する(取り消しもできるようになる) 16 #if UNITY_EDITOR 17 Undo.RegisterFullObjectHierarchyUndo(tilemap.layoutGrid.gameObject, "Modify Map"); 18 #endif 19 20 var bound = tilemap.cellBounds; 21 // 全て削除 22 // 「スプライトを持たない新規タイル」をセットするのではなく、単にnullをセットする 23 for (int y = bound.max.y - 1; y >= bound.min.y; --y){ 24 for (int x = bound.min.x; x < bound.max.x; ++x){ 25 //Tile tile = ScriptableObject.CreateInstance<Tile>(); 26 Quaternion rot = Quaternion.Euler(0.0f, 0.0f, 0.0f); 27 //tilemap.SetTile(new Vector3Int(x, y, 0), tile); 28 SetTile(new Vector3Int( x, y, 0 ), rot, tilemap, null); 29 } 30 } 31 }

投稿2020/04/26 03:10

編集2020/04/26 05:35
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Bongo

2020/04/26 05:39

すみません、tilesをDictionary<Sprite, Tile>型にしましたが、これはシリアライズ対象外なのを忘れていました! 代わりにキーと値を配列化してシリアライズするよう変更しました。
momiji0210

2020/04/27 06:10

ご回答が遅くなってしまい申し訳ございません。 わからない関数など調べながら進めていたため時間がかかってしまいました。 簡潔な処理や手順までありがとうございます。 ここまで丁寧にアドバイスもらえたことなかったので感謝しかないです。 実際、原因だと思っていた場所とタイルがおかしくなる場所違ったので本当に助かりました。 削除の件についても、参考がなくメチャクチャわかりやすかったです。 試しにログを出してみたところ、その動きをしておりました。 キャラの動きなどはほぼできているため、もう少しでリリースできそうです。 ほとんふぉBongoさんのおかげです・・・。 本当にありがとうございました。
guest

0

おーー!!!メッチャ詳細な回答ありがとうございます!
前回も回答いただいたのに諸々ありがとうございます。説明省くために抜けてて申し訳ないです。
タイルアセット名は実は吐き出しておりまして。ミニマムのプロジェクトを作って再度貼らせてくださいませ。

ただ頂いたスクリプトに大ヒントがありそうでして、調査させてくださいませ。
ご丁寧に本当にありがとうございます!

投稿2020/04/23 23:31

momiji0210

総合スコア60

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

手作業でタイルマップを描くのを代替する目的でしたら、動的にTileオブジェクトを生成する方法をとるのであれば、そのTileオブジェクトを新規アセットとしてプロジェクト内に保存してやらないとまずいような気がします。
しかも、単一のTileオブジェクトを使い回してスプライトだけ切り替えながら配置していったのでは、タイルマップができあがった直後は正常そうに見えても、タイルマップのリフレッシュが発生した際には、その単一のTileオブジェクトが提示するスプライトが全タイルに供給されてしまうと思われ、勝手に見た目が差し替わってしまいそうです。

もし「すでに各種タイルアセットは作成済みであり、タイルパレットでタイルを選んでタイルマップを描いていくことはできる状態になっているが、手作業で描いていく代わりにJSONで記述した配置図に従ってボタン一発で描いてほしい」というようなシチュエーションならば、JSONに記載するのをスプライト名ではなくタイルアセット名にするというのはどうでしょう。

たとえば仮にJSONの作りを...

  • タイルアセット名の一覧表を記述する。タイルアセット名はマップ記述に使用する記号と対にして並べる(ご質問者さんのように整数値を使用してもかまわないのですが、「何番目が何のタイルなのか脳内置き換えするのはややこしそう」、「タイルのない部分が-1だと字面の自己主張が強くて、何もない感じが薄い」といった個人の感想からこんな風にしてしまいました...)。
  • 前述の記号を使ってマップを記述する。タイルのない部分は空白とし、タイルを裏返す必要がある場合は記号の前に*を付ける(これもやはりご質問者さんの好みに従って自由に決めてかまわないのですが、私の感覚だと「JSONを読んだときにどのタイルが裏返るか視覚的にわかりやすそう」なんて気分になり、こんな感じにしてみました...)。

とすることにして、

JSON

1{ 2 "tiles" : [ 3 "c, TopCorner", 4 "T, TopEdge", 5 "L, LeftEdge", 6 "R, RightEdge", 7 "O, Stone", 8 "w, Grass", 9 "=, Underground", 10 "C, BottomCorner", 11 "B, BottomEdge" 12 ], 13 "listMap" : [ 14 " c, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,*c", 15 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 16 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 17 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 18 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 19 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 20 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 21 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 22 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 23 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 24 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 25 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , R", 26 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , O, O, O, R", 27 " L, , , , , , , , , , , , , , , , , , , , , , , , , , , , O, O, O, R", 28 " L, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, R", 29 " L, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, R", 30 " L, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, =, R", 31 "*C, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, C" 32 ] 33}

といった感じで記述したとします。
これに対して、マネージャースクリプトを...

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEngine.Tilemaps; 6#if UNITY_EDITOR 7using UnityEditor; 8#endif 9 10public class TilemapManager : MonoBehaviour 11{ 12 // 配置図JSONはプロジェクト内にインポートしておき、それをインスペクター上でセットする方式にしました 13 // これもただの個人的な気まぐれですので、お好みの方法で読み込んでいいと思います 14 public TextAsset sourceJson; 15 16 // 編集対象のタイルマップをここにセットしておく 17 public Tilemap tilemap; 18 19 // 配置図上の記号をキーに、タイルアセットを選択するディクショナリー 20 private Dictionary<string, Tile> tilePalette; 21 22 // JSONから読み込んだ配置図は入れ子のリストの形にすることにしました 23 private List<List<string>> tileKeys; 24 25 // マップ上に配置されているタイルをすべて取り除く 26 // クリア後に、クリア前のcellBoundsを復元する 27 public bool Clear() 28 { 29 if (this.tilemap == null) 30 { 31 Debug.LogError("No tilemap."); 32 return false; 33 } 34 35 var bounds = this.tilemap.cellBounds; 36 this.tilemap.ClearAllTiles(); 37 this.tilemap.origin = bounds.position; 38 this.tilemap.size = bounds.size; 39 this.tilemap.ResizeBounds(); 40 return true; 41 } 42 43 // 現在のタイルマップはクリアし、JSONの内容を元にタイルを配置する 44 public void PlaceTiles() 45 { 46 // まずJSONの配置図を入れ子リストに直してやる 47 if ((this.tilePalette == null) || (this.tilePalette.Count <= 0) || !this.FillTileKeys() || !this.Clear()) 48 { 49 return; 50 } 51 52 // tileKeysが構築されたはずなので、中のタイル記号を列挙して対応するタイルを置いていく 53 var bounds = this.tilemap.cellBounds; 54 var rowCount = this.tileKeys.Count; 55 foreach (var (tileKeyF, x, y) in this.tileKeys.SelectMany( 56 (row, y) => row.Select((tileKeyF, x) => (tileKeyF, x, y)))) 57 { 58 // もしキーが*で始まるなら、タイルを裏返す必要がある 59 // 裏返しフラグを立て、キーからは頭の*記号を取り除く 60 var flip = false; 61 var tileKey = tileKeyF; 62 if (tileKey.StartsWith("*")) 63 { 64 flip = true; 65 tileKey = tileKey.Substring(1); 66 } 67 68 // キーが空白、または未知の文字列なら(タイル名一覧に存在しない記号なら)スキップしてタイルを置かない 69 // タイルがnull...つまりタイル名に対応するタイルアセットが見つからなかった場合もスキップ 70 if (string.IsNullOrWhiteSpace(tileKey) || !this.tilePalette.TryGetValue(tileKey, out var tile) || (tile == null)) 71 { 72 continue; 73 } 74 75 // タイルを置く 76 // もし裏返しが必要なら、その座標に変換行列を仕込む 77 var position = new Vector3Int(bounds.position.x + x, (bounds.position.y + rowCount) - 1 - y, 0); 78 this.tilemap.SetTile(position, tile); 79 if (flip) 80 { 81 this.tilemap.SetTransformMatrix(position, Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 180, 0), Vector3.one)); 82 } 83 } 84 } 85 86 #if UNITY_EDITOR 87 // sourceJsonにセットされているJSONを読み取ってtilePaletteの内容を置き換える 88 public bool LoadTilePalette() 89 { 90 if (this.sourceJson == null) 91 { 92 Debug.LogError("No source."); 93 return false; 94 } 95 96 var tiles = JsonUtility.FromJson<TilemapData>(this.sourceJson.text).tiles; 97 if (this.tilePalette == null) 98 { 99 this.tilePalette = new Dictionary<string, Tile>(); 100 } 101 102 this.tilePalette.Clear(); 103 foreach (var pair in tiles.Select(keyAndName => keyAndName.Split(','))) 104 { 105 var tileKey = pair[0].Trim(); 106 var tileName = pair[1].Trim(); 107 var tileGuid = AssetDatabase.FindAssets($"{tileName} t:{typeof(Tile)}").FirstOrDefault(); 108 var tile = (Tile)null; 109 if (string.IsNullOrEmpty(tileGuid)) 110 { 111 Debug.LogError($"Tile '{tileName}' not found."); 112 } 113 else 114 { 115 tile = AssetDatabase.LoadAssetAtPath<Tile>(AssetDatabase.GUIDToAssetPath(tileGuid)); 116 } 117 118 this.tilePalette.Add(tileKey, tile); 119 } 120 121 return true; 122 } 123 #endif 124 125 // sourceJsonにセットされているJSONを読み取ってtileKeysを構築する 126 private bool FillTileKeys() 127 { 128 if (this.sourceJson == null) 129 { 130 Debug.LogError("No source."); 131 return false; 132 } 133 134 var listMap = JsonUtility.FromJson<TilemapData>(this.sourceJson.text).listMap; 135 var rowCount = listMap.Length; 136 if (this.tileKeys == null) 137 { 138 this.tileKeys = new List<List<string>>(); 139 } 140 141 while (this.tileKeys.Count < rowCount) 142 { 143 this.tileKeys.Add(new List<string>()); 144 } 145 146 if (this.tileKeys.Count > rowCount) 147 { 148 this.tileKeys.RemoveRange(rowCount, this.tileKeys.Count - rowCount); 149 } 150 151 for (var i = 0; i < rowCount; i++) 152 { 153 var row = this.tileKeys[i]; 154 row.Clear(); 155 row.AddRange(listMap[i].Split(',').Select(key => key.Trim())); 156 } 157 158 return true; 159 } 160 161 [Serializable] 162 private class TilemapData 163 { 164 public string[] tiles; 165 public string[] listMap; 166 } 167}

とし、エディタースクリプトを...

C#

1using UnityEditor; 2using UnityEngine; 3 4[CustomEditor(typeof(TilemapManager))] 5public class TilemapManagerEditor : Editor 6{ 7 public override void OnInspectorGUI() 8 { 9 base.OnInspectorGUI(); 10 var tilemapManager = this.target as TilemapManager; 11 using (new EditorGUI.DisabledScope(tilemapManager.tilemap == null)) 12 { 13 using (new EditorGUI.DisabledScope(tilemapManager.sourceJson == null)) 14 { 15 if (GUILayout.Button("Place Tiles") && tilemapManager.LoadTilePalette()) 16 { 17 RecordTilemap(tilemapManager.tilemap, "Place Tiles"); 18 tilemapManager.PlaceTiles(); 19 } 20 } 21 22 if (GUILayout.Button("Clear All")) 23 { 24 RecordTilemap(tilemapManager.tilemap, "Clear All"); 25 tilemapManager.Clear(); 26 } 27 } 28 } 29 30 private static void RecordTilemap(Tilemap tilemap, string name) 31 { 32 var grid = tilemap.GetComponentInParent<GridLayout>(); 33 if (grid == null) 34 { 35 Undo.RegisterCompleteObjectUndo(tilemap, name); 36 } 37 else 38 { 39 Undo.RegisterFullObjectHierarchyUndo(grid, name); 40 } 41 } 42}

としてみました。

下図の画像をベースに...

図1

9つのスプライトを作り...

図2

タイルパレットを作ったところ...

図3

プロジェクト中にタイルアセットが生成され...

図4

この状態で「Place Tiles」ボタンを押したところ、下図の形に配置されました。

図5

投稿2020/04/23 21:20

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

momiji0210

2020/04/25 13:57

回答が遅くなってしまい申し訳ございません。 Bongoさんのプログラムで正常に動きました。 手順まで丁寧にありがとうございました。 TileMapを使うのが初めてで、書きたいコードが簡潔にかかれてたので非常に勉強になりました。 本当に感謝しかないです。 BongoさんのものをSample、私が書いたものをOriginalとしてミニマムプロジェクトを作ってみました。 こちらで検証したところ、やはり私のソースではタイルマップが壊れてしまうようです。 この辺、Tileの初期化方法が違ったため、このあたりに不具合の原因がありそうです。 ①TileMapManager2のJsonLoadを再生時以外に呼ぶと、再生、停止するとTileMapがすべて0番目のものになる ※ TileMapManager2はMainCameraにアタッチ ②MapDeleteを再生以外で呼ぶと全件消えてくれない、Addでオブジェクトを追加できるように修正 foreachを使って全件検索しているつもりなのですが、再生していない際はダメなようです。 流石に甘えすぎているので、お時間があればで構いませんので、ご確認いただけないでしょうか。 サンプルPG https://37.gigafile.nu/0502-d2ce9138fef59c3aca04fe8c4c4e45707 DLコード 1234
Bongo

2020/04/26 03:11

プロジェクトご提示ありがとうございます。対処法を検討してみましたがいかがでしょうか(字数制限のため別回答になってしまいましたがご容赦ください...)。 動作確認をそこまでしっかりやったわけではありませんので、何か見落としがあるかもしれません。変な挙動に気付きましたら、その現象が発生する手順をコメントいただければ調べてみようと思います。
momiji0210

2020/04/27 06:11

回答遅くなって申し訳ないです。 わかりやすいプログラムで助かりました。 TileMapについては参考記述がほとんど海外なので勉強になりました。 最後までありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問