#AIを作ろう
- 予備知識
- AIの動き
- 作り方
##1. 予備知識 配列を使ってマップを管理しよう
今回AIを作る2Dダンジョンではマップを2次元配列で管理するよ
例として下の画像を配列に直してみよう
まず地面が10×10マスなので配列を10×10で作ろう
private int[,] map;
//まずは配列の宣言をするよ
map =new int[10,10];
//宣言した配列mapが10×10の配列ってことにしてるよ
コード上で上みたいに宣言すればまずは配列が完成するよ
mapの中身はどうなってるかっていうと
map =
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0]
のようにすべて0になってるよ
この配列を変えていって画像のマップみたいにしていこう!
####スタートとゴール、壁を設定しよう
スタートを8、ゴールを9、壁を1とすると
map =
[8,0,1,0,0,0,0,0,0,0],
[0,0,0,0,0,0,1,0,0,0],
[0,0,0,0,1,1,0,1,1,0],
[0,0,0,0,1,0,0,0,0,0],
[0,1,0,1,0,0,0,0,1,1],
[1,1,1,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,9]
こんな感じになるね
これでまずは配列の管理は完了だよ!
##2. AIの動き
###AI
今回作るAIは自分のまわりのマスで動けるマスがあればそこに移動する仕組みなのでランダムに動く方法を考えてみよう
自分を〇とするとまず[0,0]にいて次に移動できるのは[0,1]だね
じゃあその次に移動できるのはどこかというと[1,1]と[0,2]だね
この二つ(もっとたくさんのとき)のうち一つに進むにはどうすればいいか考えてみよう
##3 作り方
###仕組み
もし移動できるマスが複数あるならランダムに進む仕組みにするにはListというものを使ってみよう!
private Vector2Int pos;
//自分の位置をpos[x,y]として保存しているよ
List<Vector2Int> houkou = new List<Vector2Int>();
//ここでhoukouというListを作るよ<Vector2Int>の部分で形を宣言してて、ここを変えれば他の形のListにできるよ
if ((pos.y < 9) && (map[pos.x, pos.y + 1] == 0))
//下の1マスが移動可能か判断してるよ
{houkou.Add(Vector2Int.up); }
if ((pos.x < 9) && (map[pos.x + 1, pos.y] == 0))
//右の1マスが移動可能か判断してるよ
{houkou.Add(Vector2Int.right);}
if ((pos.x > 0) && (map[pos.x - 1, pos.y] == 0))
//左の1マスが移動可能か判断してるよ
{houkou.Add(Vector2Int.left);}
if ((pos.y > 0) && (map[pos.x, pos.y - 1] == 0))
//上の1マスが移動可能か判断してるよ
{houkou.Add(Vector2Int.down);}
//上の4つのif文で自分の今の場所から移動可能なマスを探してもし移動可能ならリストhoukouに指示をいれるよ
pos += houkou[Random.Range(0, houkou.Count)];
//リストhoukouの中からランダムな指示を選んでposを変化させるよ
transform.position = new Vector3(pos.x, 1, pos.y);
//変化したposの座標に移動するよ
####Listの説明
List名.Add(内容)でListに内容を入れます
houkou[Random.Range(0, houkou.Count)]
houkou.CountはListの中身の数-1なので
Random.Range(0, houkou.Count)
ランダムに0~α(中身の数-1)から1つの整数を選ぶ
その数字に対応した指示をおこなうよ
例:pos[0,1]のときにさっきの画像で考えると
List houkou
=(0,[Vector2Int.up])
(1,[Vector2Int.right])
(2,[Vector2Int.down])
とListに3つの要素が入り
Random.Range(0, houkou.Count)で
0~2のランダムな数字、例えば1が出ると
Vector2Int.rightで右に一歩進む
##4.応用
###一度通った道を通らないようにしたい
今まで作ったやつだと一度通った道も通ってしまうね
そこでコスト計算をしてみよう
コスト計算は最初の位置からそのマスに行くために
最低限の歩数!
これを図に表すとこんな感じになるよ
この数字を小さい順にたどっていくと通った道には戻らないよ
実装の仕方は自分で考えてみよう
##5 実装したゲーム
https://youtu.be/TT-3Wpru0nM
実装したゲームとコードの一部を紹介します
https://unityroom.com/games/wgureimonn
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class enemyAI2 : MonoBehaviour
{
public AudioClip em2;
private AudioSource au;
int now=0;
int g=1;
private int[,] OC;//マスの処理をしたかどうかの判定
private int[,] SI;//推定コスト ゴールまでの絶対距離(壁を考えてない)
private int[,] C;//コスト計算
private int[,] SC;//コストと推定コストの合計 少ないほうがいい
int Gx = 10;
int Gz = 10;
int cc = 1;
int cp;
int sui;
private int hp;
private int co=0;
public static bool flag2;
private Vector2Int pos;
private Vector2Int ipos;
int x;
int xa;
int z = 0;
int za;
Ray ray;
RaycastHit hitInfo;
public GameObject kaminome;
// Use this for initialization
void Start()
{
hp=20;
pos = new Vector2Int(0, 0);
SI = new int[10,10];
SC = new int[10,10];
au = gameObject.GetComponent<AudioSource>();
openclose();
suitei();
cost();
score();
InvokeRepeating("idou", 1, 0.71f);
}
private void score()
{
for (x = 0; x < 10; x++)
{
for (z = 0; z < 10; z++)
{
if (OC[x, z] == 0 || OC[x, z] == 1)
{
SC[x, z] = SI[x, z] + C[x, z];
}
}
}
}
private void cost()
{
C = new int[10, 10];
for (x = 0; x < 10; x++)
{
for (z = 0; z < 10; z++)
{
if ((x == pos.x) && (z == pos.y) && (OC[x, z] == 0))
{
C[x, z] = 0;
OC[x, z] = 1;
if (z < 9)
{
if (OC[x, z + 1] == 0)
{
C[x, z + 1] = C[x, z] + 1;
OC[x, z + 1] = 1;
cp = 1;
}
}
if (x < 9)
{
if (OC[x + 1, z] == 0)
{
C[x + 1, z] = C[x, z] + 1;
OC[x + 1, z] = 1;
cp = 1;
}
}
if (z > 0)
{
if (OC[x, z - 1] == 0)
{
C[x, z - 1] = C[x, z] + 1;
OC[x, z - 1] = 1;
cp = 1;
}
}
if (x > 0)
{
if (OC[x - 1, z] == 0)
{
C[x - 1, z] = C[x, z] + 1;
OC[x - 1, z] = 1;
cp = 1;
}
}
}
}
}
cc = 1;
while (true)
{
cp = 0;
for (x = 0; x < 10; x++)
{
for (z = 0; z < 10; z++)
{
if (C[x, z] == cc)
{
if (z < 9)
{
if (OC[x, z + 1] == 0)
{
C[x, z + 1] = C[x, z] + 1;
OC[x, z + 1] = 1;
cp = 1;
}
}
if (x < 9)
{
if (OC[x + 1, z] == 0)
{
C[x + 1, z] = C[x, z] + 1;
OC[x + 1, z] = 1;
cp = 1;
}
}
if (z > 0)
{
if (OC[x, z - 1] == 0)
{
C[x, z - 1] = C[x, z] + 1;
OC[x, z - 1] = 1;
cp = 1;
}
}
if (x > 0)
{
if (OC[x - 1, z] == 0)
{
C[x - 1, z] = C[x, z] + 1;
OC[x - 1, z] = 1;
cp = 1;
}
}
}
}
}
if (cp == 0)
{
break;
}
cc = cc + 1;
}
}
private void suitei()
{
for (x = 0; x < 10; x++)
{
for (z = 0; z < 10; z++)
{
if (wallmaker2.map[x, z] == 0||wallmaker2.map[x,z]==3)
{
SI[x, z] = Mathf.Abs(Gx - x) + Mathf.Abs(Gz - z);
}
}
}
}
private void openclose()
{
OC = new int[10, 10];
for (x = 0; x < 10; x++)
{
for (z = 0; z < 10; z++)
{
OC[x, z] = wallmaker2.map[x, z];
if(OC[x,z]==3){
OC[x,z]=0;
}
}
}
}
// Update is called once per frame
void Update()
{
if(SI[pos.x, pos.y] <= 7){
flush.flag3=true;
ppp.flag4 = true;
}
if(flag2){
flag2=false;
openclose();
suitei();
cost();
score();
}
}
void idou()
{
if(SI[pos.x, pos.y] <= 7){
flush.flag3=true;
}
round();
transform.position = new Vector3(pos.x, 1, pos.y);
au.PlayOneShot(em2);
if(pos.x==9&&pos.y==9){
clear.zikan = 44.0f;
SceneManager.LoadScene("gameover");
}
if(wallmaker2.map[pos.x,pos.y]==3){
hp=hp-3;
}
hp--;
if(hp<0){
Destroy(gameObject);
hakai();
}
}
void round()
{
List<Vector2Int> houkou = new List<Vector2Int>();
if ((pos.y < 9) && (C[pos.x, pos.y + 1] == C[pos.x, pos.y] + g))
{
houkou.Add(Vector2Int.up);
}
if ((pos.x < 9) && (C[pos.x + 1, pos.y] == C[pos.x, pos.y] + g))
{
houkou.Add(Vector2Int.right);
}
if ((pos.x > 0) && (C[pos.x - 1, pos.y] == C[pos.x, pos.y] + g))
{
houkou.Add(Vector2Int.left);
}
if ((pos.y > 0) && (C[pos.x, pos.y - 1] == C[pos.x, pos.y] + g))
{
houkou.Add(Vector2Int.down);
}
if (houkou.Count == 0)
{
hakai();
enemyAI.flag = true;
enemyAI2.flag2 = true;
flush.flag3 = true;
ppp.flag4 = true;
Destroy(gameObject);
}
else
{
pos += houkou[Random.Range(0, houkou.Count)];
}
}
private void hakai()
{
if (Physics.Raycast(kaminome.transform.position, kaminome.transform.forward, out hitInfo, 1))
{
if (hitInfo.collider.gameObject.CompareTag("wall"))
{
Destroy(hitInfo.collider.gameObject);
wallmaker2.map[pos.x, pos.y + 1] = 0;
}
}
if (Physics.Raycast(kaminome.transform.position, kaminome.transform.right, out hitInfo, 1))
{
if (hitInfo.collider.gameObject.CompareTag("wall"))
{
Destroy(hitInfo.collider.gameObject);
wallmaker2.map[pos.x + 1, pos.y] = 0;
}
}
if (Physics.Raycast(kaminome.transform.position, -kaminome.transform.forward, out hitInfo, 1))
{
if (hitInfo.collider.gameObject.CompareTag("wall"))
{
Destroy(hitInfo.collider.gameObject);
wallmaker2.map[pos.x, pos.y - 1] = 0;
}
}
if (Physics.Raycast(kaminome.transform.position, -kaminome.transform.right, out hitInfo, 1))
{
if (hitInfo.collider.gameObject.CompareTag("wall"))
{
Destroy(hitInfo.collider.gameObject);
wallmaker2.map[pos.x - 1, pos.y] = 0;
}
}
}
}