# エイチーム 1Dayインターン INVINCIBLE ## メモ * 回復は強いからとるべき * メイン機は移動が遅い * 逃げる時は細かく曲がるようにすると良さそう。壁に沿わせる? ## classes ```csharp enum MODE { // 巡回、接敵、アイテム、救援 PATROL, ENCOUNT, ITEM, HELP } ``` ## member var. ```csharp static int [,] map; List<CharacterModel.Data> characterData; // キャラクター情報のリスト List<CharacterModel.Data> enemyData; // 敵情報のリスト int middleInterval; // 中距離攻撃のインターバル 30フレームで再使用可能 int shortInterval; // 短距離攻撃のインターバル 180フレームで再使用可能 int invincibleInterval; // 無敵状態のインターバル 900フレームで再使用可能 mode [] mode; //各機体のモード管理 ``` ## member func. ```csharp // A-starアルゴリズムでposからtoまでの最短経路をリストで返す private List<Common.MOVE_TYPE> AStar(Vector2 pos, Vector2 to) { // とりあえず動かす用 return new List<Common.MOVE_TYPE>(); } // nowY > toY という前提で書いているので、それ以外はバグる bool ExistsBlockX(int x, int nowY, int toY) { return Enumerable.Range(nowY, toY).ToArray().All(y => map[y, x] == 0); } // 同じく bool ExistsBlockY(int nowX, int toX, int y) { return Enumerable.Range(nowX, toX).ToArray().All(x => map[y, x] == 0); } Common.MOVE_TYPE CoverEnemy(int i) { var c = characterData[i].BlockPos; foreach (var e in enemyData) { var ePos = e.BlockPos; if (ePos.x == c.x) { if (ePos.y < c.y && ExistsBlockY(c.x, c.y, ePos.y)) return Common.MOVE_TYPE.DOWN; else if (ePos.y > c.y && ExistsBlockY(c.x, ePos.y, c.y)) return Common.MOVE_TYPE.UP; } else if (ePos.y == c.y) { if (ePos.x < c.x && ExistsBlockX(c.x, ePos.x, c.y)) return Common.MOVE_TYPE.RIGHT; else if (ePos.x > c.x && ExistsBlockX(ePos.x, c.x, c.y)) return Common.MOVE_TYPE.LEFT; } } return Common.MOVE_TYPE.NONE_MAX; } ``` ## initializeAI ```csharp: mode = new MODE [] {MODE.PATROL, MODE.PATROL, MODE.PATROL}; map = getStageData(); middleInterval = 30; shortInterval = 180; invincibleInterval = 900; characterData = new List<CharacterModel.Data>(); enemyData = new List<CharacterModel.Data>(); DataUpdate(); ``` ## UpdateAI ```csharp= DataUpdate(); for (var i = 0; i < 3; i++) { var wayOfEnemy = CoverEnemy(i); switch(mode[i]) { case MODE.PATROL: if (wayOfEnemy != Common.MOVE_TYPE.NONE_MAX) { mode[i] = MODE.ENCOUNT; Move(characterData[i].ActorId, wayOfEnemy); } else { Move(characterData[i].ActorId, RouteSearch(characterData[i].BlockPos).First()); } break; case MODE.ENCOUNT: if (wayOfEnemy == Common.MOVE_TYPE.NONE_MAX) { mode[i] = MODE.PATROL; Action(chara.ActorId, Define.Battle.ACTION_TYPE.ATTACK_LONG); } break; case MODE.ITEM: mode[i] = MODE.PATROL; // あとでアイテムの処理を書く break; } } foreach (var chara in characterData) Action(chara.ActorId, Define.Battle.ACTION_TYPE.ATTACK_LONG); ``` ## 状態更新(その他更新をかけたいものをまとめると楽かと) ```csharp= void DataUpdate() { // インターバルの更新 middleInterval++; shortInterval++; invincibleInterval++; // 前回の状態を削除 characterData.Clear(); enemyData.Clear(); // 自機の状態を取得 foreach (CharacterModel.Data data in GetTeamCharacterDataList(TEAM_TYPE.PLAYER)) { characterData.Add(data); } // 敵機の状態を取得 foreach (CharacterModel.Data data in GetTeamCharacterDataList(TEAM_TYPE.ENEMY)) { enemyData.Add(data); } } ``` ## 隣接ブロックへの移動可能判定 ```csharp= // 入:座標(BlockPos)、出:そこから移動できる方向 List<Common.MOVE_TYPE> RouteSearch(Vector2 position) { List<Common.MOVE_TYPE> movable = new List<Common.MOVE_TYPE>(); int x = (int)position[0]; int y = (int)position[1]; if (map[y + 1, x] == 0) { movable.Add(Common.MOVE_TYPE.UP); } if (map[y - 1, x] == 0) { movable.Add(Common.MOVE_TYPE.DOWN); } if (map[y, x - 1] == 0) { movable.Add(Common.MOVE_TYPE.LEFT); } if (map[y, x + 1] == 0) { movable.Add(Common.MOVE_TYPE.RIGHT); } return movable; } ``` ## メイン機のキーボード操作 ```csharp= /* キーボードでメイン戦闘機を操作 */ /* 十字キーで移動、スペースキーで短距離攻撃 */ override public void UpdateAI() { /* characterData[0].ActorIdはメイン自機*/ /* 後で消す */ if (Input.GetKey(KeyCode.UpArrow)) { Move(characterData[0].ActorId, Common.MOVE_TYPE.UP); } if (Input.GetKey(KeyCode.DownArrow)) { Move(characterData[0].ActorId, Common.MOVE_TYPE.DOWN); } if (Input.GetKey(KeyCode.LeftArrow)) { Move(characterData[0].ActorId, Common.MOVE_TYPE.LEFT); } if (Input.GetKey(KeyCode.RightArrow)) { Move(characterData[0].ActorId, Common.MOVE_TYPE.RIGHT); } if (Input.GetKeyDown(KeyCode.Space) && shortInterval > 180) { Action(characterData[0].ActorId, Define.Battle.ACTION_TYPE.ATTACK_SHORT); shortInterval = 0; } } ``` #### 現状 ```csharp= using UnityEngine; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Ateam { public class Delta : BaseBattleAISystem { enum MODE { PATROL, ENCOUNT, ITEM, HELP } static int [,] map; MODE [] mode; List<CharacterModel.Data> characterData; // キャラクター情報のリスト List<CharacterModel.Data> enemyData; // 敵情報のリスト int shortInterval; // 短距離攻撃のインターバル int middleInterval; // 中距離攻撃のインターバル int invincibleInterval; // 無敵状態のインターバル private List<Common.MOVE_TYPE> AStar(Vector2 pos, Vector2 to) { return new List<Common.MOVE_TYPE>(); } void DataUpdate() { characterData = new List<CharacterModel.Data>(); enemyData = new List<CharacterModel.Data>(); // 自機の状態を取得 foreach (CharacterModel.Data data in GetTeamCharacterDataList(TEAM_TYPE.PLAYER)) { characterData.Add(data); } // 敵機の状態を取得 foreach (CharacterModel.Data data in GetTeamCharacterDataList(TEAM_TYPE.ENEMY)) { enemyData.Add(data); } } // nowY > toY という前提で書いているので、それ以外はバグる bool NonExistsBlockX(float x, float nowY, float toY) { return Enumerable.Range((int)nowY, (int)toY).ToArray().Where(y => 0 <= y && y < map.GetLength(0)).All(y => map[y, (int)x] == 0); } // 同じく bool NonExistsBlockY(float nowX, float toX, float y) { return Enumerable.Range((int)nowX, (int)toX).ToArray().Where(x => 0<= x && x < map.GetLength(1)).All(x => map[(int)y, x] == 0); } Common.MOVE_TYPE CoverEnemy(int i) { var c = characterData[i].BlockPos; foreach (var e in enemyData) { var ePos = e.BlockPos; if (ePos.x == c.x) { if (ePos.y < c.y && NonExistsBlockY(c.x, c.y, ePos.y)) return Common.MOVE_TYPE.DOWN; else if (ePos.y > c.y && NonExistsBlockY(c.x, ePos.y, c.y)) return Common.MOVE_TYPE.UP; } else if (ePos.y == c.y) { if (ePos.x < c.x && NonExistsBlockX(c.x, ePos.x, c.y)) return Common.MOVE_TYPE.LEFT; else if (ePos.x > c.x && NonExistsBlockX(ePos.x, c.x, c.y)) return Common.MOVE_TYPE.RIGHT; } } return Common.MOVE_TYPE.NONE_MAX; } List<Common.MOVE_TYPE> RouteSearch(Vector2 position) { List<Common.MOVE_TYPE> movable = new List<Common.MOVE_TYPE>(); int x = (int)position[0]; int y = (int)position[1]; if (y+1 < map.GetLength(0) && map[y + 1, x] == 0) { movable.Add(Common.MOVE_TYPE.UP); } if (0 < y && map[y - 1, x] == 0) { movable.Add(Common.MOVE_TYPE.DOWN); } if (0 < x && map[y, x - 1] == 0) { movable.Add(Common.MOVE_TYPE.LEFT); } if (x+1 < map.GetLength(1) && map[y, x + 1] == 0) { movable.Add(Common.MOVE_TYPE.RIGHT); } return movable; } //--------------------------------------------------- // InitializeAI //--------------------------------------------------- override public void InitializeAI() { mode = new MODE [] {MODE.PATROL, MODE.PATROL, MODE.PATROL}; map = GetStageData(); shortInterval = 0; middleInterval = 0; invincibleInterval = 0; characterData = new List<CharacterModel.Data>(); enemyData = new List<CharacterModel.Data>(); } //--------------------------------------------------- // UpdateAI //--------------------------------------------------- override public void UpdateAI() { DataUpdate(); for (var i = 0; i < 3; i++) { var wayOfEnemy = CoverEnemy(i); switch (mode[i]) { case MODE.PATROL: if (wayOfEnemy != Common.MOVE_TYPE.NONE_MAX) { Move(characterData[i].ActorId, wayOfEnemy); mode[i] = MODE.ENCOUNT; } else { var route = RouteSearch(characterData[i].BlockPos).ToArray(); Move(characterData[i].ActorId, route[Random.Range(0, route.Length)]); } break; case MODE.ENCOUNT: if (wayOfEnemy == Common.MOVE_TYPE.NONE_MAX) { Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_SHORT); mode[i] = MODE.PATROL; } Move(characterData[i].ActorId, wayOfEnemy); Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_MIDDLE); Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_SHORT); Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.INVINCIBLE); break; case MODE.ITEM: mode[i] = MODE.PATROL; // あとでアイテムの処理を書く break; } Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_LONG); } } //--------------------------------------------------- // ItemSpawnCallback //--------------------------------------------------- override public void ItemSpawnCallback(ItemSpawnData itemData) { if (mode[1] == MODE.PATROL) mode[1] = MODE.ITEM; else mode[2] = MODE.ITEM; } } } ``` #### 8/15追記分 GetByVecの辺りにバグがある。 仕様上一回消えかけたので、ここに一旦保存。 ```csharp using UnityEngine; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Ateam { public class Delta : BaseBattleAISystem { enum MODE { PATROL, ENCOUNT, ITEM, WAIT } enum TO { ITEM, ENEMY } static int [,] map; MODE [] mode; List<CharacterModel.Data> characterData; // キャラクター情報のリスト List<CharacterModel.Data> enemyData; // 敵情報のリスト int target; Stack<Common.MOVE_TYPE> [] routesForItem = new Stack<Common.MOVE_TYPE> [3]; Stack<Common.MOVE_TYPE> [] routesForEnemy = new Stack<Common.MOVE_TYPE> [3]; Vector2 DirToVec(Common.MOVE_TYPE direction) { switch (direction) { case Common.MOVE_TYPE.LEFT: return Vector2.left; case Common.MOVE_TYPE.RIGHT: return Vector2.right; case Common.MOVE_TYPE.UP: return Vector2.up; case Common.MOVE_TYPE.DOWN: return Vector2.down; } return Vector2.zero; } T GetByVec<T>(T [,] m, Vector2 pos) { var y = pos.y < 0 ? 0 : (map.GetLength(0) <= pos.y ? map.GetLength(0) : (int)pos.y); var x = pos.x < 0 ? 0 : (map.GetLength(1) <= pos.x ? map.GetLength(1) : (int)pos.x); return m[y, x]; } void SetByVec<T>(T [,] m, Vector2 pos, T t) { m[(int)pos.y, (int)pos.x] = t; } bool CanMove(int [,] m, Vector2 pos, Common.MOVE_TYPE direction) { var xMax = m.GetLength(1); var yMax = m.GetLength(0); switch (direction) { case Common.MOVE_TYPE.LEFT: return 0 <= pos.y && pos.y < yMax && 0 <= pos.x-1 && GetByVec(m, pos + Vector2.left) == 0; case Common.MOVE_TYPE.RIGHT: return 0 <= pos.y && pos.y < yMax && pos.x+1 < xMax && GetByVec(m, pos + Vector2.right) == 0; case Common.MOVE_TYPE.UP: return 0 <= pos.x && pos.x < xMax && pos.y+1 < yMax && GetByVec(m, pos + Vector2.up) == 0; case Common.MOVE_TYPE.DOWN: return 0 <= pos.x && pos.x < xMax && 0 <= pos.y-1 && GetByVec(m, pos + Vector2.down) == 0; } return false; } void FindRoutesTo(TO to, int i, Vector2 goal) { var start = characterData[i].BlockPos; var visitor = new Queue<Vector2>(); var parent = new Common.MOVE_TYPE [map.GetLength(0), map.GetLength(1)]; visitor.Enqueue(start); var d = new int [map.GetLength(0), map.GetLength(1)]; SetByVec(d, start, 1); while (visitor.Count != 0) { var now = visitor.Dequeue(); if (now == goal) break; foreach (var move in new Common.MOVE_TYPE [] {Common.MOVE_TYPE.LEFT, Common.MOVE_TYPE.RIGHT, Common.MOVE_TYPE.UP, Common.MOVE_TYPE.DOWN}) if (CanMove(d, now, move) && CanMove(map, now, move)) { var p = now + DirToVec(move); SetByVec(d, p, 1); SetByVec(parent, p, move); visitor.Enqueue(p); } } var moveLog = GetByVec(parent, goal); var posLog = goal - DirToVec(moveLog); while (posLog != start) { if (to == TO.ITEM) routesForItem[i].Push(moveLog); else routesForEnemy[i].Push(moveLog); moveLog = GetByVec(parent, posLog); posLog = posLog - DirToVec(moveLog); } if (to == TO.ITEM) mode[i] = MODE.ITEM; } // nowY > toY という前提で書いているので、それ以外はバグる bool NonExistsBlockX(float x, float nowY, float toY) { return Enumerable.Range((int)nowY, (int)toY).ToArray().Where(y => 0 <= y && y < map.GetLength(0)).All(y => map[y, (int)x] == 0); } // 同じく bool NonExistsBlockY(float nowX, float toX, float y) { return Enumerable.Range((int)nowX, (int)toX).ToArray().Where(x => 0<= x && x < map.GetLength(1)).All(x => map[(int)y, x] == 0); } Common.MOVE_TYPE CoverEnemy(int i) { var c = characterData[i].BlockPos; foreach (var e in enemyData) { var ePos = e.BlockPos; if (ePos.x == c.x) { if (ePos.y < c.y && NonExistsBlockY(c.x, c.y, ePos.y)) return Common.MOVE_TYPE.DOWN; else if (ePos.y > c.y && NonExistsBlockY(c.x, ePos.y, c.y)) return Common.MOVE_TYPE.UP; } else if (ePos.y == c.y) { if (ePos.x < c.x && NonExistsBlockX(c.x, ePos.x, c.y)) return Common.MOVE_TYPE.LEFT; else if (ePos.x > c.x && NonExistsBlockX(ePos.x, c.x, c.y)) return Common.MOVE_TYPE.RIGHT; } } return Common.MOVE_TYPE.NONE_MAX; } List<Common.MOVE_TYPE> RouteSearch(Vector2 position) { List<Common.MOVE_TYPE> movable = new List<Common.MOVE_TYPE>(); int x = (int)position[0]; int y = (int)position[1]; if (y+1 < map.GetLength(0) && map[y + 1, x] == 0) { movable.Add(Common.MOVE_TYPE.UP); } if (0 < y && map[y - 1, x] == 0) { movable.Add(Common.MOVE_TYPE.DOWN); } if (0 < x && map[y, x - 1] == 0) { movable.Add(Common.MOVE_TYPE.LEFT); } if (x+1 < map.GetLength(1) && map[y, x + 1] == 0) { movable.Add(Common.MOVE_TYPE.RIGHT); } return movable; } //--------------------------------------------------- // InitializeAI //--------------------------------------------------- override public void InitializeAI() { mode = new MODE [] {MODE.PATROL, MODE.PATROL, MODE.PATROL}; map = GetStageData(); target = 2; characterData = new List<CharacterModel.Data>(); enemyData = new List<CharacterModel.Data>(); for (var i = 0; i < 3; i++) { routesForItem[i] = new Stack<Common.MOVE_TYPE>(); routesForEnemy[i] = new Stack<Common.MOVE_TYPE>(); } DataUpdate(); } void DataUpdate() { // 前回の状態を削除 characterData.Clear(); enemyData.Clear(); // 自機の状態を取得 foreach (CharacterModel.Data data in GetTeamCharacterDataList(TEAM_TYPE.PLAYER)) { characterData.Add(data); } // 敵機の状態を取得 foreach (CharacterModel.Data data in GetTeamCharacterDataList(TEAM_TYPE.ENEMY)) { enemyData.Add(data); } if (enemyData[2].Hp <= 0) target = 1; if (enemyData[1].Hp <= 0) target = 0; } //--------------------------------------------------- // UpdateAI //--------------------------------------------------- override public void UpdateAI() { DataUpdate(); for (var i = 0; i < 3; i++) { if (characterData[i].Hp == 0) continue; var directionOfEnemy = CoverEnemy(i); switch (mode[i]) { case MODE.ITEM: if (routesForItem[i].Count > 0) { if (Move(characterData[i].ActorId, routesForItem[i].Peek())) routesForItem[i].Pop(); Debug.Log(routesForItem[i].Count); } else mode[i] = MODE.ENCOUNT; break; case MODE.PATROL: if (directionOfEnemy != Common.MOVE_TYPE.NONE_MAX) { routesForEnemy[i].Clear(); if (!Move(characterData[i].ActorId, directionOfEnemy)) continue; if (!Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.INVINCIBLE)) continue; mode[i] = MODE.ENCOUNT; } else { if (routesForEnemy[i].Count > 0) { if (Move(characterData[i].ActorId, routesForEnemy[i].Peek())) routesForEnemy[i].Pop(); } else { var route = RouteSearch(characterData[i].BlockPos).ToArray(); Move(characterData[i].ActorId, route[Random.Range(0, route.Length)]); } } break; case MODE.ENCOUNT: if (directionOfEnemy == Common.MOVE_TYPE.NONE_MAX) { FindRoutesTo(TO.ENEMY, i, enemyData[target].BlockPos); Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_SHORT); mode[i] = MODE.PATROL; } Move(characterData[i].ActorId, directionOfEnemy); Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_MIDDLE); break; } Action(characterData[i].ActorId, Define.Battle.ACTION_TYPE.ATTACK_LONG); } } //--------------------------------------------------- // ItemSpawnCallback //--------------------------------------------------- override public void ItemSpawnCallback(ItemSpawnData itemData) { DataUpdate(); var i = characterData[1].Hp != 0 && mode[1] != MODE.ITEM ? 1 : 2; if (characterData[1].Hp == 0 && characterData[2].Hp == 0) i = 0; mode[i] = MODE.WAIT; FindRoutesTo(TO.ITEM, i, itemData.BlockPos); } } } ```