現場レベルのゲーム制作が、すべてここで学べます。
この講座は3Dダンジョン探索型RPGの作り方について解説しています。元々は第20回が最終回でしたが不具合修正とおまけの演出強化の第21回を設けてみました。
前回はBGMや効果音、そしてエフェクトアニメーションやスマホ対応などを行い、遊べるゲームとして完成させました。
前回の記事:

今回は戦闘不能になっているキャラクターに対してメニュー画面からスキル使用を行う対象にできることへの対応、そしてダンジョンテクスチャの貼り方について追記していきます。
ダンジョンテクスチャを変更するだけで3Dダンジョンの雰囲気がガラッと変わるのでぜひ試してみてください
戦闘不能まわりの不具合修正
現在、ダンジョン探索中のメニュー画面から戦闘不能になったキャラクターに対してスキルを使用できたり、回復などを行える不具合が存在しています。スクリプトを一部修正しこの問題に対処しましょう。
MenuWindow.cs内 SelectActorUIメソッド
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/// <summary> /// アクターUIタップ選択時処理 /// </summary> public void SelectActorUI(ActorUI actorUI) { // 対象キャラが不在なら終了 if (actorUI == null || actorUI.actorData == null || actorUI.actorData.isKnockedOut) return; if (!selectTargetActorUIs.Contains(actorUI)) { // 単体選択タイプであった場合、それまでの選択対象をリセットする if (!isAllTarget) ClearActorSelectData(); // 対象に追加 selectTargetActorUIs.Add(actorUI); // UIの対象選択時表示 actorUI.SetTargerSelectMode(true); } // 決定ボタン有効化 decideButton.interactable = true; if (selectingSkillButton != null && !isSkillAvailable) decideButton.interactable = false; } |
戦闘不能のキャラはターゲットに選べないように修正しました。戦闘画面側では既にこの条件式が追加されているためそちらは修正不要です。
また全体回復スキルなどで対象に選ばれることもありますが、HPが1以上に回復することはないためそちらも対処不要です。
ダンジョンブロックにテクスチャを設定しよう
サンプルテクスチャ配布
新しくブロックに設定するテクスチャは任意の画像を使用可能ですが、すぐに確認したい人のためにサンプル用のテクスチャを2種類ずつ配布します。
壁テクスチャ
床テクスチャ
Texture/MapTileTextureフォルダ以下に格納すると良いでしょう。
画像をそれぞれ右クリックで保存する場合は拡張子を.webpから.pngに変更してください。
スクリプト設定
MapDataSO.cs内 変数宣言部
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using UnityEngine; using System; using System.Collections.Generic; /// <summary> /// マップデータScriptableObject定義クラス /// </summary> [CreateAssetMenu(fileName = "MapDataSO", menuName = "Dungeon/MapDataSO")] public class MapDataSO : ScriptableObject { // マップ内の全タイル情報リスト(20×20個のデータ) public List<TileData> tiles = new List<TileData>(); // マップ名 public string mapName = "Map Name"; // 出現エネミーレベル public int enemyLevel = 1; // Entityデータリスト public List<MapEntity> mapEntitys = new List<MapEntity>(); // 採集ポイント・宝箱データリスト public List<GatheringPoint> gatheringPoints = new List<GatheringPoint>(); // 出現エネミー編成リスト public List<EnemyTable> enemyTables = new List<EnemyTable>(); // FOEエネミー編成リスト public List<EnemyTable> foeTables = new List<EnemyTable>(); // ダンジョン進入時初期位置 public Vector2Int enterLocation; // ダンジョン進入時初期方向 public PlayerManager.Direction enterDirection; // 壁タイル用テクスチャ(未指定時はプレハブのデフォルトを使用) public Texture2D wallTexture; // 床タイル用テクスチャ(未指定時はプレハブのデフォルトを使用) public Texture2D groundTexture; (以降省略) |
MapEditorWindow.cs内 OnGUIメソッド内
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
// GUI上の描画処理 void OnGUI() { // スクロール位置を保持する変数を追加(クラスのメンバ変数として宣言) scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); // マップデータの選択フィールド(Inspector風) EditorGUI.BeginChangeCheck(); currentMapData = EditorGUILayout.ObjectField("編集マップデータ", currentMapData, typeof(MapDataSO), false) as MapDataSO; // プロパティ取得 if (EditorGUI.EndChangeCheck() && currentMapData != null) { serializedMapData = new SerializedObject(currentMapData); gatheringPointsProperty = serializedMapData.FindProperty("gatheringPoints"); enemyTablesProperty = serializedMapData.FindProperty("enemyTables"); foeTablesProperty = serializedMapData.FindProperty("foeTables"); } if (currentMapData == null) {// マップデータ未指定 // マップデータ新規作成ボタン if (GUILayout.Button("新規マップデータ作成")) { // MapDatasフォルダが存在しない場合に作成 if (!Directory.Exists(MAP_DATAS_FOLDER)) { Directory.CreateDirectory(MAP_DATAS_FOLDER); AssetDatabase.Refresh(); } // 一意のファイル名を生成 string fileName = GenerateUniqueFileName(); string fullPath = Path.Combine(MAP_DATAS_FOLDER, fileName); // 新しいScriptableObjectを作成し、アセットとして保存 currentMapData = CreateInstance<MapDataSO>(); AssetDatabase.CreateAsset(currentMapData, fullPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } // スクロールビューの終了 EditorGUILayout.EndScrollView(); return; } // (GUI上の区切り線) GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); // マップ名の入力 currentMapData.mapName = EditorGUILayout.TextField("Map Name", currentMapData.mapName); // 敵のレベル入力 currentMapData.enemyLevel = EditorGUILayout.IntField("Enemy Level", currentMapData.enemyLevel); // 階層進入時の指定位置と方向を入力 currentMapData.enterLocation = EditorGUILayout.Vector2IntField(label_EnterLocation, currentMapData.enterLocation); currentMapData.enterDirection = (PlayerManager.Direction)EditorGUILayout.EnumPopup("Enter Direction", currentMapData.enterDirection); // タイルテクスチャ指定(未指定時はプレハブのデフォルトテクスチャを使用) currentMapData.wallTexture = (Texture2D)EditorGUILayout.ObjectField ("壁テクスチャ", currentMapData.wallTexture, typeof (Texture2D), false); currentMapData.groundTexture = (Texture2D)EditorGUILayout.ObjectField ("床テクスチャ", currentMapData.groundTexture, typeof (Texture2D), false); // (GUI上の空白) GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); // タイル/Entity配置モード切り替え GUILayout.BeginHorizontal(); if (GUILayout.Toggle(isPlacingTile, "タイル配置", EditorStyles.toolbarButton)) isPlacingTile = true; if (GUILayout.Toggle(!isPlacingTile, "Entity配置", EditorStyles.toolbarButton)) isPlacingTile = false; GUILayout.EndHorizontal(); if (isPlacingTile) { // タイル配置UI currentBrush = (TileType)EditorGUILayout.EnumPopup("Tile Type", currentBrush); } else { // Entity配置UI currentEntityType = (MapEntityType)EditorGUILayout.EnumPopup("Entity Type", currentEntityType); if (currentEntityType != MapEntityType.None) { currentEntityName = EditorGUILayout.TextField(label_EntityName, currentEntityName); currentEntityId = EditorGUILayout.IntField(label_EntityID, currentEntityId); } } // マップタイル描画領域を確保 Rect gridRect = GUILayoutUtility.GetRect( MapTileNum_Width * (MapTileSize_Width + MapTileSize_Gap), MapTileNum_Height * (MapTileSize_Height + MapTileSize_Gap)); // タイル編集画面の描画処理呼び出し DrawCustomGrid(gridRect, currentMapData); // 各設定リストの表示 GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); // 採集ポイント if (serializedMapData != null && gatheringPointsProperty != null) { serializedMapData.Update(); EditorGUILayout.PropertyField(gatheringPointsProperty, true); serializedMapData.ApplyModifiedProperties(); } // 敵出現テーブル if (serializedMapData != null && enemyTablesProperty != null) { serializedMapData.Update(); EditorGUILayout.PropertyField(enemyTablesProperty, true); serializedMapData.ApplyModifiedProperties(); } // FOEテーブル if (serializedMapData != null && foeTablesProperty != null) { serializedMapData.Update(); EditorGUILayout.PropertyField(foeTablesProperty, true); serializedMapData.ApplyModifiedProperties(); } // Entity編集セクション if (selectedMapEntity != null) { EditorGUILayout.Space(); EditorGUILayout.LabelField("選択中のオブジェクト", EditorStyles.boldLabel); // Entityの基本情報表示 EditorGUILayout.LabelField($"タイプ: {selectedMapEntity.type}"); EditorGUILayout.LabelField($"座標: ({selectedMapEntity.x}, {selectedMapEntity.y})"); // Entity名とIDの編集 EditorGUI.BeginChangeCheck(); selectedMapEntity.objectName = EditorGUILayout.TextField(label_EntityName, selectedMapEntity.objectName); selectedMapEntity.objectId = EditorGUILayout.IntField(label_EntityID, selectedMapEntity.objectId); // Entity削除ボタン(警告付き) EditorGUILayout.Space(); if (GUILayout.Button("Entity削除")) { DeleteSelectedEntity(); } // Entity選択解除ボタン if (GUILayout.Button("Entity選択解除")) { selectedMapEntity = null; } } // (GUI上の区切り線) GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); // 保存ボタン if (GUILayout.Button("マップデータを保存", GUILayout.Height(30))) { AssetDatabase.SaveAssets(); EditorUtility.SetDirty(currentMapData); // 編集フラグを立てる } // スクロールビューの終了 EditorGUILayout.EndScrollView(); } |
マップデータ定義クラスおよびマップエディタ画面を編集し、階層ごとに異なる壁・床テクスチャ画像を指定できるようにしました。
MapManager.cs内 Initメソッド内
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
// 初期化関数(DungeonManager.csから呼出) public void Init(DungeonManager _dungeonManager) { // 参照取得 dungeonManager = _dungeonManager; // 変数初期化 dungeonEntities = new List<DungeonEntity>(); foeMoveCount = new Dictionary<MapEntity, int>(); // マップデータ取得 currentMapData = Data.instance.allMapDatas[Data.instance.nowDungeonLevel]; // タイル作成 for (int i = 19; i >= 0; i--) // MapEditorWindow.cs の MapTileNum_Width-1 に合わせる { for (int j = 19; j >= 0; j--) // MapEditorWindow.cs の MapTileNum_Height-1 に合わせる { // プレハブからタイルインスタンス作成 GameObject obj = Instantiate(mapTilePrefabs[(int)currentMapData.GetTile(i, j)], mapTilesParent); // タイルの位置を設定 Vector3 targetPos = GetVector3ByMapLocation(i, j); targetPos.y = obj.transform.localPosition.y; // Y座標はプレハブに設定されたものを使用 obj.transform.localPosition = targetPos; // マップデータで指定されたテクスチャを適用(壁・床のみ) TileType tileType = currentMapData.GetTile (i, j); Texture2D overrideTexture = null; if (tileType == TileType.Wall && currentMapData.wallTexture != null) overrideTexture = currentMapData.wallTexture; else if (tileType == TileType.Ground && currentMapData.groundTexture != null) overrideTexture = currentMapData.groundTexture; if (overrideTexture != null) { // 壁タイルは子Quadにも同マテリアルがあるため全Rendererに適用 Renderer[] renderers = obj.GetComponentsInChildren<Renderer> (); foreach (Renderer renderer in renderers) { // マテリアルのインスタンスを生成してテクスチャを差し替え Material mat = renderer.material; mat.mainTexture = overrideTexture; } } } } // Entity作成 foreach (var entity in currentMapData.mapEntitys) { // エンティティ種類ごとの生成ルール switch (entity.type) { case MapEntityType.None: continue; case MapEntityType.Treasure:// 宝箱 // 取得済みなら生成しない if (Data.instance.lootedTreasure.Contains(entity.objectName)) continue; break; case MapEntityType.KeyofDoor:// 鍵 case MapEntityType.Locked_Door:// 鍵付き扉 // そのフロアの鍵を取得済みなら生成しない if (Data.instance.floorFoundedKeys.Contains(Data.instance.nowDungeonLevel)) continue; break; case MapEntityType.Gathering:// 採集ポイント // その探索中に取得済みなら生成しない if (Data.instance.lootedGathering.Contains(entity.objectName)) continue; break; case MapEntityType.FOE:// FOE // その探索中に撃破済みなら生成しない if (Data.instance.defeatedFOEs.Contains(GetFOEKeyString(entity))) continue; foeMoveCount.Add(entity, 0); break; } // Entityオブジェクト作成 var obj = Instantiate(mapEntityPrefabs[(int)entity.type], mapEntitysParent); var proc = obj.GetComponent<DungeonEntity>(); dungeonEntities.Add(proc); // Entity情報設定 proc.mapEntity = entity; proc.nowTileX = entity.x; proc.nowTileY = entity.y; // オブジェクト位置設定 Vector3 position = GetVector3ByMapLocation(proc.nowTileX, proc.nowTileY); position.y = obj.transform.position.y; proc.transform.position = position; // Conveyorの場合は方向に応じてrotationを設定 if (entity.type == MapEntityType.Conveyor) proc.transform.rotation = PlayerManager.GetQuaternionByDirection((PlayerManager.Direction)entity.objectId); } } |
MapManagerで壁・床ブロックのオブジェクトを作成する際にテクスチャの割り当てを行う処理を追加しました。
マップエディタからテクスチャ画像を貼り付けよう
ここまででマップデータやマップ表示機能の処理追加は完了です。ここからは実際にマップエディタでテクスチャ画像を貼り付けてみましょう。
メニューからWindow > MapEditorを選択して以前作ったScriptable Objectを選ぶとテクスチャを設定できるようになっています。

↑試しにタイルを当てはめてみましょう。

この段階でマップエディタからテクスチャ画像を割り当てれば、ゲーム内でテクスチャが切り替わっていることを確認できるはずです。

テクスチャ表示の上下反転を修正
これまでは上下対象な画像であるワイヤーフレームを使用していたため問題になりませんでしたが、Cubeオブジェクトにテクスチャを貼り付けた場合、見る向きによっては画像が上下に反転してしまいます。

MapBlock_Wallプレハブを編集して、反転して見えてしまう面の部分には新しく面を重ねることでカバーしておきます。
子オブジェクトにQuadオブジェクト(名前は任意ですがWallQuadとしました)を作成します。
これを反転してしまう面の位置、Position=(0, 0, -0.5001)辺りに移動させます。

その後、親オブジェクトであるMapBlock_Wallと同じMaterialを適用します(つまりWireframeマテリアル)。
(同じマテリアルでないとスクリプトからの反映が正常に行われません。)

なお、床ブロックについては真上からしか見ることがないため同様の設定は不要です。
これによってどの角度から壁を見ても同じ向きでテクスチャ画像が表示されるようになりました。
階層ごとに個別に画像を設定できる機能も含めて動作確認してみてください!

意外と簡単に3Dダンジョンにテクスチャを貼ることができましたね。
テクスチャを貼るだけで見栄えが変わってダンジョンRPGの雰囲気がグッと引き立つのでぜひ試してみてください。
現場レベルのゲーム制作が、すべてここで学べます。











コメント