【Unity】3Dローグライクゲームの作り方〜Step12-11〜
前回の記事はこちら。
前回はアイテムをランダム配置しました。
部屋と通路の接続口をマーキングする
今回は敵の移動AIを改良したいと思います。
まずは固定マップを一周させられるようにします。その為に通路口にマーキングをしましょう。
マップデータファイルを開いて、以下のようにオブジェクトを配置して下さい。
部屋側の方に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です。
マーキングをセーブする
ついでにこのマーキングの位置をセーブしたいと思います。
まず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です。
SaveDataEditerでも確認してみます。
大丈夫そうですね!
フィールド内を巡回させる
それではマーキングに沿ってフィールド内を巡回させてみましょう。
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です。
長くなってしまったので、今回はここまでにします。
次回は敵がプレイヤーの近くにいる場合はプレイヤーを追いかけるようにしたいと思います。
この記事が気に入ったらサポートをしてみませんか?