# エイチーム 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);
}
}
}
```