# 手把手带你用机器学习写unity AI

2017unity机器学习社区挑战赛参加地址：https://connect.unity.com/challenges/ml-agents-1
win 10 上搭建Unity 机器学习环境:https://unity3d.college/2017/10/25/machine-learning-in-unity3d-setting-up-the-environment-tensorflow-for-agentml-on-windows-10/

unity机器学习插件：https://github.com/Unity-Technologies/ml-agents

=====================正文分割=========================

====================角色对象的设置=======================

========================两个ball的设置======================

===================激动人心的代码部分========================
1，在地板上随机生成两个ball：

times -= Time.deltaTime;
if (times < 0)
{
int red_i = Random.Range(-50, 50);
int red_j = Random.Range(-50, 50);
int green_i = Random.Range(-50, 50);
int green_j = Random.Range(-50, 50);

GameObject red = (GameObject)Instantiate(red_ball);
GameObject green = (GameObject)Instantiate(green_ball);

red.transform.position = new Vector3(red_i,1, red_j);
green.transform.position = new Vector3(green_i,1, green_j);

times = 2f;

2，两个ball与角色碰撞后消失，然后把reward传回到agent

private void OnCollisionEnter(Collision collision)
{
FindAgent player = GameObject.Find("player").GetComponent<FindAgent>();
{
player.reward = -1f;
}
else
{
player.reward = +5f;
}
if (collision.gameObject.tag == "Player")
{
Destroy(ball);
}
}

3，生成角色虚拟视线

void DrawFieldOfView()
{
// 获得最左边那条射线的向量，相对正前方，角度是-45
Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * eye_distance;
for (int i = 0; i <= eye_angle; i++)
{// 每条射线都在forward_left的基础上偏转一点，最后一个正好偏转90度到视线最右侧
Vector3 v = Quaternion.Euler(0, (90.0f / eye_angle) * i, 0) * forward_left; ;

// 创建射线
Ray ray = new Ray(transform.position, v);
RaycastHit hitt = new RaycastHit();
// 射线只与两种层碰撞，注意名字和你添加的layer一致，其他层忽略

// Player位置加v，就是射线终点pos
Vector3 pos = transform.position + v;
if (hitt.transform != null)
{
// 如果碰撞到什么东西，射线终点就变为碰撞的点了
pos = hitt.point;
}
// 从玩家位置到pos画线段，只会在编辑器里看到
Debug.DrawLine(transform.position, pos, Color.red); ;

// 如果真的碰撞到敌人，进一步处理
if (hitt.transform != null && hitt.transform.gameObject.layer == LayerMask.NameToLayer("Red_ball"))
{
red_ball = hitt.transform.position;
}
if(hitt.transform != null && hitt.transform.gameObject.layer == LayerMask.NameToLayer("Green_ball"))
{
green_ball = hitt.transform.position;
}
}
}

4，重点！agent的编写
InitializeAgent()类似Start()的初始化方法

public override void InitializeAgent()
{
red_ball = get_from_PlayerMove.red_ball;
green_ball = get_from_PlayerMove.green_ball;
}

List CollectState()每一个训练相关的数值都要在这里，比如在这个demo中，我们要角色自动寻找目标同时还要区分干扰目标，那我们就要让Brain知道，角色的移动，朝向，所以添加了角色的position和rotation，还有两个ball的坐标。另外需要理解一点，不管你什么机器学习，一定是数值到算法层，比如图像识别，最后都是把图像降维成数值再处理。所以这里CollectState()内传到Brain的都是单个数值，比如要传角色的位置，传Vector3类型是不行的，因为Vector3是有3个值得向量（x,y,z),所以你要传角色位置，你把3个方向的数值一个个传。

public override List<float> CollectState()
{
List<float> state = new List<float>();

return state;
}

AgentStep(float[] act)每一步的行动，比如在这个demo中，我们要角色一直移动，所以在AgentStep(float[] act)中添加角色移动，另外这里的act参数是怎么回事呢。act有两种模式，一种是离散，一种是连续。比如在这个demo中我把act设置为离散，因为角色的行为无非是左转右转和向前，那么我们把act为0时作为角色向前的信号，1和2作为角色向左和向右，用离散就合适了，离散和连续在Brain中的action设置即可。

public override void AgentStep(float[] act)
{
reward = -0.01f;
int action = Mathf.FloorToInt(act[0]);
if (action == 0)
{
player.transform.Translate(0, 0, 1, Space.Self);
}
if (action == 1)
{
player.transform.Rotate(0, -10, 0, Space.Self);
}
if (action == 2)
{
player.transform.Rotate(0, 10, 0, Space.Self);
}
if (player.transform.position.x >= 50 || player.transform.position.x <= -50 || player.transform.position.z >= 50 || player.transform.position.z <= -50)
{
reward = -10f;
done = true;
}
}

AgentReset()重置方法，就是一次训练结束了，环境怎么重置。比如我们这个demo，什么时候需要重置？当然是角色跑到地板意外的地方啦，一旦角色离开地板，那么我们就重置环境，把角色重置在地板的中心位置，并给予角色惩罚，让它知道它的活动范围只能在地板范围上。

public override void AgentReset()
{
transform.position = new Vector3(0,1,0);
}

**项目说明：**Findagent,Player_Move,AgentMonitor脚本挂在角色上
Destroy_ball挂在两个ball上
RandomSpawn挂在随机生成ball的管理器上