見出し画像

【Unity】3Dローグライクゲームの作り方〜Step12-11〜

前回の記事はこちら
前回はアイテムをランダム配置しました。

部屋と通路の接続口をマーキングする

今回は敵の移動AIを改良したいと思います。
まずは固定マップを一周させられるようにします。その為に通路口にマーキングをしましょう。
マップデータファイルを開いて、以下のようにオブジェクトを配置して下さい。

スクリーンショット 2020-06-11 6.21.57

部屋側の方にTypeに「Connection」を指定したオブジェクトを置いて下さい。また、曲がり角にも配置します。
保存してAssetsフォルダ内のファイルと置き換えておきます。

マップデータに沿ってマーキングする

今度はUnityエディターを触っていきます。
ヒエラルキータブ内のField階層下に「Connections」という名前の空のオブジェクトを作成しましょう。
また、その階層下に「Connection」という名前の空のオブジェクトを作成し、ObjectPositionスクリプトをアタッチして、プレハブ化して下さい。
これができたら、Fieldクラスに新しいパラメーターを追加し、SetObjectメソッドとResetメソッドに追記していきます。

// パラメーターを追加
public GameObject connections;

// メソッドに追記
public void SetObject(string name, string type, int xgrid, int zgrid)
{
   switch (type)
   {
       case "Stairs":
           /*   省略   */
       case "Connection":
           GameObject connectObj = (GameObject)Resources.Load("Prefabs/Connection");
           GameObject connect = Instantiate(connectObj, connections.transform);
           connect.GetComponent<ObjectPosition>().SetPosition(xgrid, zgrid);
           break;
       case "Enemy":
           /*   省略   */
       case "Item":
           /*   省略   */
   }
}

public void Reset()
{
   /*   省略   */
   for (int i = 0; i < floor.transform.childCount; i++)
   {
       /*   省略   */
   }
   for (int i = 0; i < connections.transform.childCount; i++)
       Destroy(connections.transform.GetChild(i).gameObject);
}

実行してみて、通路口に空のゲームオブジェクトが配置されていたらOKです。

スクリーンショット 2020-06-10 11.12.30

マーキングをセーブする

ついでにこのマーキングの位置をセーブしたいと思います。
まずMapSaveDataクラスにパラメーターを追加します。

public Pos2D[] connections;

次にSaveDataManagerクラスに新しいパラメーターとメソッドを追加し、2つのメソッドを変更します。

// パラメーターを追加
public GameObject connections;

// メソッドを追加
/**
* マップオブジェクトデータを作成、返す
*/
private Pos2D[] MakeMapObjectData(Transform parent)
{
   Pos2D[] data = new Pos2D[parent.childCount];
   for (int i = 0; i < parent.childCount; i++)
   {
       ObjectPosition objPos = parent.GetChild(i).GetComponent<ObjectPosition>();
       Pos2D pos = new Pos2D();
       pos.x = objPos.grid.x;
       pos.z = objPos.grid.z;
       data[i] = pos;
   }
   return data;
}

// メソッドを変更
private MapSaveData MakeMapData()
{
   MapSaveData mapSaveData = new MapSaveData();
   mapSaveData.stairs = MakeMapObjectData(stairs.transform);
   mapSaveData.connections = MakeMapObjectData(connections.transform);
   mapSaveData.map = field.GetMapData();
   return mapSaveData;
}

private void LoadMapData(SaveData saveData)
{
   field.Reset();
   field.Create(saveData.mapData.map);
   for (int i = 0; i < 2; i++)
   {
       ObjectPosition objPos = stairs.transform.GetChild(i).GetComponent<ObjectPosition>();
       objPos.SetPosition(saveData.mapData.stairs[i].x, saveData.mapData.stairs[i].z);
   }
   for (int i = 0; i < saveData.mapData.connections.Length; i++)
   {
       GameObject connectObj = (GameObject)Resources.Load("Prefabs/Connection");
       GameObject connect = Instantiate(connectObj, connections.transform);
       ObjectPosition objPos = connect.GetComponent<ObjectPosition>();
       objPos.SetPosition(saveData.mapData.connections[i].x, saveData.mapData.connections[i].z);
   }
}

ビルドしたら、テストしてみます。セーブしてロードしてもマーキングが消えなければOKです。

スクリーンショット 2020-06-10 14.44.15

SaveDataEditerでも確認してみます。

スクリーンショット 2020-06-10 14.48.39

大丈夫そうですね!

フィールド内を巡回させる

それではマーキングに沿ってフィールド内を巡回させてみましょう。
EnemyOperationクラスに新しいパラメーターとメソッドを追加して、メソッドを変更して下さい。

// パラメーターを追加
private Pos2D nextConnectionPos;

// メソッドを追加
/**
* 次に進む予定の通路口を設定する
*/
public void SetNextConnection(int xgrid, int zgrid, EDir dir)
{
   Field field = GetComponentInParent<Field>();
   ObjectPosition[] pos = field.connections.GetComponentsInChildren<ObjectPosition>();
   List<ObjectPosition> posList = new List<ObjectPosition>();
   posList.AddRange(pos);
   List<Pos2D> cGrids = new List<Pos2D>();
   foreach (var p in posList)
   {
       if (p.grid.x == xgrid && p.grid.z == zgrid) continue;
       if (dir == EDir.Left && p.grid.x < xgrid) continue;
       if (dir == EDir.Right && p.grid.x > xgrid) continue;
       if (dir == EDir.Up && p.grid.z > zgrid) continue;
       if (dir == EDir.Down && p.grid.z < zgrid) continue;
       int minX = Mathf.Min(p.grid.x, xgrid);
       int maxX = Mathf.Max(p.grid.x, xgrid);
       int minZ = Mathf.Min(p.grid.z, zgrid);
       int maxZ = Mathf.Max(p.grid.z, zgrid);
       bool isEnd = false;
       for (int x = minX; x <= maxX; x++)
       {
           for (int z = minZ; z <= maxZ; z++)
           {
               if (field.IsCollide(x, z))
               {
                   isEnd = true;
                   break;
               }
           }
           if (isEnd) break;
       }
       if (isEnd) continue;
       cGrids.Add(p.grid);
   }
   if (cGrids.Count < 1)
   {
       nextConnectionPos = null;
       return;
   }
   int idx = Random.Range(0, cGrids.Count);
   nextConnectionPos = cGrids[idx];
}

/**
* フィールド内を巡回する移動AI
*/
private EAct GoAroundAI(ActorMovement actorMovement)
{
   Pos2D grid = actorMovement.grid;
   EDir dir = actorMovement.direction;
   if (nextConnectionPos == null)
   {
       SetNextConnection(grid.x, grid.z, dir);
       if (nextConnectionPos == null)
       {
           dir = DirUtil.ReverseDirection(dir);
           SetNextConnection(grid.x, grid.z, dir);
           actorMovement.SetDirection(dir);
       }
   }
   if ((dir == EDir.Left || dir == EDir.Right) && grid.x == nextConnectionPos.x)
   {
       if (grid.z > nextConnectionPos.z) actorMovement.SetDirection(EDir.Up);
       if (grid.z < nextConnectionPos.z) actorMovement.SetDirection(EDir.Down);
   }
   if ((dir == EDir.Up || dir == EDir.Down) && grid.z == nextConnectionPos.z)
   {
       if (grid.x > nextConnectionPos.x) actorMovement.SetDirection(EDir.Right);
       if (grid.x < nextConnectionPos.x) actorMovement.SetDirection(EDir.Left);
   }
   if (!actorMovement.IsMoveBegin()) return EAct.KeyInput;
   grid = actorMovement.newGrid;
   if (grid.x == nextConnectionPos.x && grid.z == nextConnectionPos.z)
       SetNextConnection(grid.x, grid.z, actorMovement.direction);
   return EAct.MoveBegin;
}

// メソッドを変更
private EAct EasyActionAI(ActorMovement actorMovement)
{
   EDir d = GetPlayerDirection(actorMovement);
   if(d == EDir.Pause)
   {
       d = EasyMovementAI(actorMovement, target.newGrid); // この行を変更
       actorMovement.SetDirection(d);
       if (actorMovement.IsMoveBegin()) return EAct.MoveBegin;
       return EAct.KeyInput;
   }
   actorMovement.SetDirection(d);
   return EAct.ActBegin;
}

private EDir EasyMovementAI(ActorMovement actorMovement, Pos2D grid)
{
   int dx = grid.x - actorMovement.newGrid.x;
   int dz = grid.z - actorMovement.newGrid.z;
   /*   省略   */
}

public override EAct Operate(ActorMovement actorMovement, ActorParamsController actorParam)
{
   if (actorParam.CantAction()) return EAct.TurnEnd;
   if (actorParam.IsConfusion()) return ConfusionActionAI(actorMovement);
   return GoAroundAI(actorMovement); // ここを変更
}

解説は省きます。もしわからないところがあったらご連絡下さい。
テストしてみます。以下のように敵が部屋を見回ればOKです。

自動巡回固定マップ

長くなってしまったので、今回はここまでにします。
次回は敵がプレイヤーの近くにいる場合はプレイヤーを追いかけるようにしたいと思います。

この記事が気に入ったらサポートをしてみませんか?