現場レベルのゲーム制作が、すべてここで学べます。
この記事はUnityを用いた3DダンジョンRPG作り方講座の17回目になります。
前回はアイテム図鑑・モンスター図鑑を実装し、ゲーム内でのやりこみ要素やコレクション要素を追加しました。RPGに限らず様々なゲーム開発に応用できるスキルです。
前回の記事:

今回はダンジョンマップ内に配置するイベントを実装し、階層の進行やアイテム獲得などを特定の場所で起こせるようにします。
具体的には、出入り口、階段、宝箱、アイテムドロップ、シンボルエンカウント敵(FOE)、扉、鍵付き扉、テレポート床、矢印の方向に移動させる床、ダメージ床などです。
ウィザードリィや世界樹の迷宮といった3DダンジョンRPGにおなじみのマップギミックの作り方を習得していきます。
ダンジョンイベント(Entity)を実装する
これまでダンジョン設計で扱っていたマップエディタでは地形情報(地面か壁か)しか設定することが出来ませんでした。
これを機能拡張し、特定のマスにおいて指定したイベントを発生させられるようにしていきます。
この記事では今後、ダンジョンで発生するイベント(およびそこから生成される実体)をEntityと呼称していきます。
Entityの種類
サンプルゲームでは以下の11種類を用意しています。
| 定義名 | 名称 | 説明 |
| ExitPoint | ダンジョン出口 | 重なることで街シーンへと遷移する |
| Up_Stairs | 昇り階段 | 1つ上の階層へ移動してシーンを再読み込みする |
| Down_Stairs | 降り階段 | 1つ下の階層へ移動してシーンを再読み込みする |
| Treasure | 宝箱 | 指定したアイテムを獲得する 1度起動したらこのEntityは消滅し、ダンジョンに入りなおしても復活しない |
| Gathering | 採集ポイント | 指定したアイテムを獲得する 1度起動したらこのEntityは消滅するが、ダンジョンに入りなおせば復活する |
| FOE | FOE | マップ内を徘徊するシンボルエネミー 振れることで指定したエネミー編成との戦闘を開始する |
| Locked_Door | 鍵穴つき扉 | 通行不可能な壁 “扉の鍵”を取得で消滅し、ダンジョンに入りなおしても復活しない |
| KeyofDoor | 扉の鍵 | 重なることで取得して消滅し、ダンジョンに入りなおしても復活しない |
| Conveyor | 強制移動床 | 指定した方向にプレイヤーを強制移動させる |
| WarpPoint | ワープポイント | 指定した地点・方向にプレイヤーをワープさせる |
| Spikes | ダメージ床 | 重なることで編成中のアクター全員がダメージを受ける |
Entityクラス作成
マップエディタ内でEntityを扱うときはMapEntityクラス、実際にダンジョンシーン内で動作させるときはDungeonEntityという名前でそれぞれクラスを用意します。
併せてMapDataSOクラスが扱える情報の種類を拡張します。
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 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 164 165 166 167 168 |
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; /// <summary> /// 対象位置のタイル種類をセットする /// </summary> public void SetTile (int x, int y, TileType type) { // 該当位置のタイルデータを検索 TileData tile = tiles.Find (t => t.tileX == x && t.tileY == y); if (tile != null) {// タイルがある場合、種類情報を更新 tile.type = type; } else {// タイルが無ければ新規追加 tiles.Add (new TileData (x, y, type)); } } /// <summary> /// 対象位置のタイル種類を返す /// </summary> public TileType GetTile (int x, int y) { // 該当位置のタイルデータを検索 TileData tile = tiles.Find (t => t.tileX == x && t.tileY == y); // 取得したタイルデータを返す(データが無ければ壁タイルとして返す) return tile != null ? tile.type : TileType.Wall; } /// <summary> /// 指定位置にEntityを追加する /// </summary> public void AddMapEntity (int x, int y, MapEntityType type, string name = "", int id = 0) { // 同じ位置に既存のエンティティがある場合は削除 mapEntitys.RemoveAll (obj => obj.x == x && obj.y == y); if (type != MapEntityType.None) { mapEntitys.Add (new MapEntity (x, y, type, name, id)); } } /// <summary> /// 指定された位置のEntityを削除する /// </summary> public void RemoveMapEntity (int x, int y) { mapEntitys.RemoveAll (obj => obj.x == x && obj.y == y); } /// <summary> /// 指定された位置のEntityを取得する /// </summary> public MapEntity GetMapEntity (int x, int y) { return mapEntitys.Find (obj => obj.x == x && obj.y == y); } } // タイルの種類定義(列挙型) [Serializable] public enum TileType { Wall, // 壁タイル Ground, // 床タイル } // 各タイルの情報定義クラス [Serializable] public class TileData { public int tileX; // このタイルの位置(X方向) public int tileY; // このタイルの位置(Y方向) public TileType type; // このタイルの種類 // コンストラクタ public TileData (int tileX, int tileY, TileType type) { this.tileX = tileX; this.tileY = tileY; this.type = type; } } // Entity種類定義 [Serializable] public enum MapEntityType { None, ExitPoint, // ダンジョン出口 Up_Stairs, // 昇り階段 Down_Stairs,// 降り階段 Treasure, // 宝箱 Gathering, // 採集ポイント FOE, // FOE Locked_Door,// 鍵穴つき扉 KeyofDoor, // 扉の鍵 Conveyor, // 強制移動床 WarpPoint, // ワープポイント Spikes, // ダメージ床 } // MapEditor用Entityクラス [Serializable] public class MapEntity { public MapEntityType type; // Entity種類 public int x; // X位置 public int y; // Y位置 public string objectName; // 採集ポイントデータとの連携などに使用 public int objectId; // ワープポイントの接続先やアイテムIDなどに使用 // 初期化処理 public MapEntity (int x, int y, MapEntityType type, string name = "", int id = 0) { this.x = x; this.y = y; this.type = type; this.objectName = name; this.objectId = id; } } /// <summary> /// 採集ポイント・宝箱データリスト /// </summary> [Serializable] public class GatheringPoint { public string pointName; // ポイント名 public List<ItemData> itemDatas; // 獲得できるアイテム } /// <summary> /// 敵出現テーブル /// </summary> [Serializable] public class EnemyTable { // エネミーリスト public List<EnemyData> spawnnemyDatas = new List<EnemyData> (); } |
多数のpublic変数を追加しました。これは主にマップエディタで入力するものになります。
内容についてはMapEditorクラス拡張時に触れていきます。
採集ポイントおよび宝箱から獲得できるアイテムのデータはGatheringPointクラスが保持します。
EnemyTableクラスは出現するエネミーの編成(組み合わせ)を設定できるものです。
DungeonEntity.cs (新規)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using UnityEngine; /// <summary> /// (Dungeon) /// ダンジョンシーン内Entityクラス /// </summary> public class DungeonEntity : MonoBehaviour { // Entityデータ [HideInInspector] public MapEntity mapEntity; // 現在位置 [HideInInspector] public int nowTileX; [HideInInspector] public int nowTileY; } |
ダンジョンシーン内で各Entityのプレハブにアタッチしておくクラスになります。現在位置の情報を持ちます。(移動するEntityが現在位置情報を使用)
UnityでMapEditorを拡張する
MapEditorWindow.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 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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
using UnityEngine; using UnityEditor; using System.IO; /// <summary> /// (Unityエディタ拡張) /// マップエディタウィンドウ /// </summary> public class MapEditorWindow : EditorWindow { // エディタ用変数 private MapDataSO currentMapData; // 編集中マップデータ private TileType currentBrush = TileType.Ground; // 選択中タイルブラシ private Vector2 scrollPosition = Vector2.zero; private MapEntityType currentEntityType = MapEntityType.None; // 選択中Entity種類 private string currentEntityName = ""; // 設定中Entity名 private int currentEntityId = 0; // 設定中EntityID private bool isPlacingTile = true; // タイル配置モードかEntity配置モードか // GUI表示用 private SerializedObject serializedMapData; private SerializedProperty gatheringPointsProperty; private SerializedProperty enemyTablesProperty; private SerializedProperty foeTablesProperty; private MapEntity selectedMapEntity = null; // 選択中のEntityデータ private bool isEditingSelectedEntity = false; // Entity編集モード // Tooltips(マウスオーバー説明文章) private readonly GUIContent label_EnterLocation = new GUIContent ("Enter Location", "この階層に街から進入した時orデバッグでこの階層から開始した時の初期地点"); private readonly GUIContent label_EntityName = new GUIContent ("Entity名", "Entityの種類によって以下を入力\nTreasure・Gathering:採集ポイント名\nWarpPoint:次の書式で移動先を入力 X位置_Y位置_方向\nUp_Stairs/Down_Stairs:WarpPointと同様、その階層の移動先を入力\nFOE:方向(0~3)を続けて入力でその方向に移動する(ループ動作)"); private readonly GUIContent label_EntityID = new GUIContent ("EntityID", "Conveyor:方向(北0 東1 南2 西3)\nFOE:FOE Tableの対象番号"); // 定数定義 private const string MAP_DATAS_FOLDER = "Assets/ScriptableObjects/Maps"; // マップデータ作成先パス public const int MapTileNum_Width = 20; // マップの横方向のタイル数 public const int MapTileNum_Height = 20; // マップの縦方向のタイル数 public const float MapTileSize_Width = 20.0f; // エディタ上のタイル表示の横幅 public const float MapTileSize_Height = 20.0f; // エディタ上のタイル表示の縦幅 public const float MapTileSize_Gap = 1.0f; // エディタ上のタイル間の余白 // メニューからエディタウィンドウを開く [MenuItem ("Window/Map Editor")] public static void ShowWindow () { GetWindow<MapEditorWindow> ("マップエディタ"); } // ウィンドウが開かれた時呼び出される処理 void OnEnable () { // ウィンドウの最小表示サイズを設定 minSize = new Vector2 (MapTileNum_Width * (MapTileSize_Width + MapTileSize_Gap) + 20, MapTileNum_Height * (MapTileSize_Height + MapTileSize_Gap) + 140); // プロパティ取得 if (currentMapData != null) { serializedMapData = new SerializedObject (currentMapData); gatheringPointsProperty = serializedMapData.FindProperty ("gatheringPoints"); enemyTablesProperty = serializedMapData.FindProperty ("enemyTables"); foeTablesProperty = serializedMapData.FindProperty ("foeTables"); } } // 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); // (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削除")) { // Entityの削除処理 if (currentMapData != null) { currentMapData.RemoveMapEntity (selectedMapEntity.x, selectedMapEntity.y); EditorUtility.SetDirty (currentMapData); selectedMapEntity = null; // 選択解除 Repaint (); // エディタを再描画 } } // 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 (); } /// <summary> /// 重複しない新規マップデータ名を取得する /// </summary> private string GenerateUniqueFileName () { string baseName = "MapData"; // デフォルト名 string extension = ".asset"; // ファイル拡張子 int counter = 1; // 連番用カウンター string fileName = $"{baseName}_{counter}{extension}"; // ファイル名 // 重複していないファイル名を見つけるまでループ while (File.Exists (Path.Combine (MAP_DATAS_FOLDER, fileName))) { counter++; fileName = $"{baseName}_{counter}{extension}"; // 拡張子や番号を含めたファイル名 } return fileName; } /// <summary> /// マップタイル描画および編集処理 /// </summary> private void DrawCustomGrid (Rect gridRect, MapDataSO mapData) { Event currentEvent = Event.current; // 全タイルに対して処理 for (int y = 0; y < 20; y++) { for (int x = 0; x < 20; x++) { // タイル表示領域を計算 Rect tileRect = new Rect ( gridRect.x + x * (MapTileSize_Width + MapTileSize_Gap), gridRect.y + y * (MapTileSize_Height + MapTileSize_Gap), MapTileSize_Width, MapTileSize_Height ); // タイルタイプに応じた色で描画 Color tileColor = GetColorForTileType (mapData.GetTile (x, y)); EditorGUI.DrawRect (tileRect, tileColor); // MapEntity描画 MapEntity obj = mapData.GetMapEntity (x, y); if (obj != null) { DrawMapEntityIcon (tileRect, obj.type); // Entityがクリックされた場合の選択処理 if (tileRect.Contains (currentEvent.mousePosition) && currentEvent.type == EventType.MouseDown && currentEvent.button == 0) // 左クリック { selectedMapEntity = obj; currentEvent.Use (); Repaint (); } } // マウス操作でのタイル編集 if (tileRect.Contains (currentEvent.mousePosition)) { // マウスクリックまたはドラッグ操作でタイルペイント if (currentEvent.type == EventType.MouseDown || currentEvent.type == EventType.MouseDrag) { if (isPlacingTile) {// タイル設置モード mapData.SetTile (x, y, currentBrush); } else if (currentEvent.type == EventType.MouseDown) // オブジェクトはドラッグ配置不可 {// Entity設置モード mapData.AddMapEntity (x, y, currentEntityType, currentEntityName, currentEntityId); } // 表示情報更新 currentEvent.Use (); // イベントを消費 Repaint (); // ウィンドウを再描画 } } } } } /// <summary> /// タイルの種類に対応する表示色を返す /// </summary> private Color GetColorForTileType (TileType type) { switch (type) { case TileType.Wall: // 壁タイル return Color.gray; case TileType.Ground: // 床タイル return Color.green; default: return Color.white; } } /// <summary> /// マップエディタ上にEntityの表示を追加する /// </summary> private void DrawMapEntityIcon (Rect position, MapEntityType type) { // Entity種類に応じたアイコン描画 string label = type.ToString ()[0].ToString (); // 頭文字を表示 GUI.color = GetColorForMapEntity (type); GUI.Label (position, label, EditorStyles.boldLabel); GUI.color = Color.white; } /// <summary> /// Entity種類の表示色を返す /// </summary> private Color GetColorForMapEntity (MapEntityType type) { switch (type) { case MapEntityType.ExitPoint: return new Color (1f, 0.5f, 0f); case MapEntityType.Up_Stairs: return Color.yellow; case MapEntityType.Down_Stairs: return Color.blue; case MapEntityType.Treasure: return Color.magenta; case MapEntityType.Gathering: return Color.green; case MapEntityType.FOE: return Color.red; case MapEntityType.Locked_Door: return new Color (0.5f, 0.3f, 0.1f); case MapEntityType.KeyofDoor: return new Color (0.75f, 0.5f, 0.3f); case MapEntityType.Conveyor: return Color.cyan; case MapEntityType.WarpPoint: return new Color (0.4f, 1.0f, 0.2f); case MapEntityType.Spikes: return new Color (0.75f, 0.75f, 0.75f); default: return Color.white; } } } |
タイルを描画するモードとEntityを配置するモードを自由に切り替えられるようにしました。
またマップ名や出現するエネミーレベル、採集ポイントから獲得できるアイテムの種類や個数など細かく設定できるGUIを追加しています。
テスト用マップ作成
画面上部メニューのWindow > MapEditor画面を開き、従来と同じようにMapDataのScriptableObjectをセットして編集を開始します。複数のボタンや入力ボックス等が追加されているのが確認できるはずです。
上部UIにある[Map Name]にはマップ名、[Enemy Level]には出現するエネミーのレベルを入力します。
[Enter Location]は、ダンジョンへの突入時のそのマップ内での初期位置を入力します。
一番左上のマスが(0, 0)であり、1つ右へ行けばxが+1し、1つ下へ行けばyが+1します。
[Enter Direction]は同様に初期に向いている方向を設定します。
その下に2つ並んでいるボタンのうち右側、[Entity配置]をクリックすればEntityを配置するモードに切り替わります。
[Entity Type]はエディタ画面クリックでその場所に配置するEntityの種類を選択します。(None選択時には何も起こりません。)
[Entity名]と[EntityID]には各Entityの設定を入力しますが、これらの動作はあとで実装するため現在は両方とも空欄でOKです。
また画面下部にある[Gathering Points]なども現在は未設定のままで良いです。
ひとまずはEntityの配置とダンジョン画面での表示を確認したいため、全11種類あるEntityをそれぞれマップ内の適当な場所に配置してみましょう。
Entity Typeの選択→タイルクリックの流れでペイントできます。

既にEntityが配置されているマスをクリックするとエディタ画面下部に選択中のオブジェクト情報が表示されます。
ここからEntity名やEntityIDの確認と編集ができるほか、Entityの削除ボタンもあるので誤って配置したものはここから削除しましょう。
Unityでダンジョンギミック用3Dオブジェクトを作る
これでマップデータがEntityの配置情報を保持できるようになりました。次はゲーム中にこれらのEntityがオブジェクトとなって画面内に表示されるのを確認できるところまで進めましょう。
ダンジョン画面は3Dオブジェクトによって構成されているので、各Entityオブジェクト(プレハブ)も3D立体で作成します。
Dungeonシーン内にEntityオブジェクトの親となる空オブジェクト(Entities)を適当に用意し、その下にプレハブを作成していきましょう。

各ダンジョンギミックのEntityプレハブを作成
各オブジェクトはDungeonEntityコンポーネントさえアタッチされていれば使用できるようになっています。必要なギミックのEntityは好きなように自作しても構いません。以下は各Entity種類に応じた作成方法のサンプルです。
ExitPoint (ダンジョン出口)
Entities以下に[3D Object]→[Cube]を選択して立方体のオブジェクトを作成します。
名前をExitPointに変更し、Y以外のScaleを減らして縦に細長い形状にします。

続けてマテリアルを適用します。素材は Textures/DungeonEntity/EntityMaterials 以下にあります。ヒエラルキーのExitPointオブジェクトにExitPointMatをドラッグ&ドロップします。

↑上図のようにMaterialが変更されていればOKです。
今回は他のパラメータをいじる必要がないため、先ほどC#で作成したDungeonEntityをアタッチします。

最後にこれをプレハブ化し、シーン内からは削除します。(以降もプレハブ化については同じ流れです。)

↑サンプルではPrefabs/Dungeon/Entityというフォルダを作成して格納しています。
Up_Stairs (昇り階段)
Cylinderオブジェクトとして作成します。
Yスケールは0.5くらいが良いでしょう。
本体のMaterialはOthersMatを使用します。DungeonEntity.csのアタッチも忘れずに。

これだけでは見た目が分かりづらいので、パーティクルによる演出を追加します。
子オブジェクトを作成しますが、このとき[Effects]から[Particle System]を選択します。これがUnityにおける標準的なパーティクル演出の機能になります。
パーティクルの解説は本ゲーム開発の本筋ではないため割愛しますが、以下のように設定することで「上方向に矢印が出現し続ける」パーティクルを作成できます。
Main Module

Emission / Shape

Color over Lifetime

↑右上の黒い矢印をクリックしてAlphaの値を0にします。パーティクルの寿命が尽きる際に透明になります。
Renderer

StairsArrpwMatを用いることで矢印が上に移動するようになります。
この設定によって以下のようなパーティクル表示を確認できるはずです。

また、この演出をさらに強化するため、追加でUp_Stairsオブジェクト以下にMagicCircleという名前の([3D Object]の)[Quad]を作成します。
RectTransformを以下のような値にし、MaterialにMagicCircleを指定します。すると円柱の底面付近に魔法陣が表示されるようになります。

さらにゲーム中はこの魔法陣を一定の速度で回転させ続けるように、以下のスクリプト(RotateContinuous.cs)を作成してアタッチします。
RotateContinuous.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 36 37 38 39 |
using UnityEngine; /// <summary> /// アタッチされたGameObjectを一定速度で回転させ続ける /// </summary> public class RotateContinuous : MonoBehaviour { [Header ("回転軸")] [SerializeField] private Axis axis = Axis.Z; enum Axis { X, Y, Z, } [Header ("回転速度(度/秒)")] [SerializeField] private float rotationSpeed = 30.0f; // Update void Update () { // 指定した軸と速度で回転させる // (Space.Selfを指定することでオブジェクト自身のローカルY軸周りに回転) float rotationValue = rotationSpeed * Time.deltaTime; switch (axis) { case Axis.X: transform.Rotate (rotationValue, 0f, 0f, Space.Self); break; case Axis.Y: transform.Rotate (0f, rotationValue, 0f, Space.Self); break; case Axis.Z: transform.Rotate (0f, 0f, rotationValue, Space.Self); break; } } } |
これがアタッチされたオブジェクトはゲーム中、常に指定された回転軸に沿って一定速度で回転を続けます。
MagicCircleにアタッチすれば設定完了です。

そしてUp_Stairsオブジェクトをプレハブ化しますが、すぐにはシーン内からは削除せずに一度Unpackしてプレハブとの紐づけを解除して残します。

Up_Stairsオブジェクトを選択して右クリック→Prefab > Unpackで解除されます。
Down_Stairs (降り階段)
Up_Stairsオブジェクトを改変する形で降り階段の方も用意していきます。まずはオブジェクト名を変更します。

Particle Systemの設定も矢印が下向きに動くように変更します。といってもオブジェクト自体の向きとパーティクルの向きを180度変更するだけでOKです。分かりやすいように色も変えておきましょう。

MagicCircleの回転速度も逆向きに設定する(速度をマイナスにする)ことでより視覚的に分かりやすくなります。

これをプレハブ化で3つ目が完了です。

Treasure (宝箱)
Cubeオブジェクトを作成し、Rect Transformの調整・Materialの適用(TreasureMat)・Dungeon Entityのアタッチを行います。

そしてこのオブジェクトはゲーム中、常に回転しながらその場で上下移動を繰り返すように演出します。
回転スクリプトは作成したので、今回作成するクラスはYFloatingContinuous.csです。
まとめ



これでダンジョン内で発生する様々なイベントをマップエディタを使って3Dダンジョン上で表現できるようになりました。
アイテムの収集や強敵との戦闘、トラップ、ギミックなど様々な障害がプレイヤーを阻むダンジョンを自由に作れるようになり、次の階層への進行を楽しめるようになりました。
新しいイベントの内容を思いついたらぜひオリジナルなEntityを実装してみましょう。
次の記事:

現場レベルのゲーム制作が、すべてここで学べます。






コメント