点击蓝字 关注我们
你是否曾经幻想过,我们其实生活在一款名为《地球OL》的游戏中。又是否曾经憧憬过,我们能过在网络中创造出另一个地球。或许在未来的某一天,游戏 AI 达到了自主思考行动的水平,就可以改变现有的模式,事件都是随机触发相互影响的,而非完全预先设定好的,如同现实社会的发展,有社会发展规律可循但总体上难以预料未来社会的发展路径。
在目前市面上的游戏中,游戏人工智能的应用还远未普及,大多数游戏仍采用状态机和行为树的模式设立NPC,相较而言游戏人工智能在游戏性的体现上更加饱满,具有相当好的发展前景。本文将通过一款简单的机器学习案例,来展示Unity3D中AI的训练与成果。
游戏设计:在一块固定的平面内存在一红一绿两颗小球,玩家可以控制红球进行移动,当红球靠近绿球时会将绿球吃掉,然后在平面内的随机位置刷新出一颗新的绿球,如此往复循环。如果红球跌出平面外,则游戏重新开始。而机器学习所要做的事情,就是学会如何控制红球不断的“吃掉”绿球并不会从平面内跌落下去。
尽管该游戏的功能十分简单,但作为一款Unity机器学习的入门案例,它能够通过具体的行为将枯燥的代码转化为有趣的游戏,方便我们体验游戏的开发过程与机器学习的魅力,希望能够借此吸引更多热爱游戏的人加入到机器学习与游戏开发的行列。
一、Unity3D
由丹麦Unity Technologies公司开发的Unity3D是被广泛使用的全平台全面整合的专业游戏引擎,它可以让使用者轻松创建如二维平面游戏、三维视频游戏、实时三维动画等类型互动内容的综合型的游戏开发工具。它支持包括Windows、IOS、Android、PC、Web、Xbox等多个平台游戏的发布,例如许多玩家钟爱的手游《炉石传说》、《神庙逃亡》,做工精良的3A大作《逃离塔科夫》甚至最近热度持续高涨的跨平台游戏《原神》都出自Unity引擎之下。Unity3D的普及带动了游戏产业的发展,大大提高了游戏开发的品质与效率。
1.Machine Learning Agents
Unity Machine Learning Agents (ML-Agents) 是一款开源的 Unity 插件, 使得我们得以在游戏环境和模拟环境中训练智能 agent。开发者可以使用 reinforcement learning(强化学习)、imitation learning(模仿学习)、neuroevolution(神经进化)或其他机器学习方法, 通过简单易用的 Python API进行控制,对 Agent 进行训练。我们还提供最先进算法的实现方式(基于 TensorFlow),让游戏开发者和业余爱好者能够轻松地训练用于 2D、3D 和 VR/AR 游戏的智能 agent。这些经过训练的 agent 可用于多种目的, 包括控制 NPC 行为(采用各种设置, 例如多个 agent 和对抗)、对游戏内部版本进行自动化测试、以及评估不同游戏设计决策的预发布版本。ML-Agents 对于游戏开发者和 AI 研究人员双方 都有利,因为它提供了一个集中的平台, 使得我们得以在 Unity 的丰富环境中测试 AI 的最新进展, 并使结果为更多的研究者和游戏开发者所用。
mlagents是该案例所需要的依赖包,可以通过Unity的官方页面了解并下载mlagents。在下载的Github页面中选择code→download ZIP选项下载压缩包,下载到指定目录下解压即可(解压目录需牢记,后面配置环境时需用到)。
mlagents下载官网:
https://unity.com/products/machine-learning-agents
2.项目创建
通过Unity-hub创建roll-ball项目。
创建项目后会来到Unity的初始界面(可以通过左上角的layout按钮选择自己喜欢的样式界面)。
3.导入依赖
在Unity界面上方选择Window→Package Manager进入包裹管理界面,搜索并安装Barracuda以及ML Agents包。
二、机器学习
1.简单理解学习过程
我们可以把机器学习的过程简单当作是教会狗狗如何把主人扔出的东西东西叼回来,则在狗狗学习过程中会可能遇到以下三种情况:
- 出界:当狗狗严重偏离所要完成的目标时需要及时阻止该行为。
- 循环:当狗狗原地打转或对目标没有响应时,也应当阻止该行为。
- 成功:当狗狗成功完成了我们的任务时,我们应当给予其奖励,以此来训练狗狗的行为。
因此狗狗学习的流程图如下。
这一次完整的过程我们称之为训练,反复进行训练过程后,狗狗为了获得奖励便会自己学习如何完成目标,最终学会如何获得奖励。这便是一种机器学习的过程。
三、配置环境
1.通过Anaconda创建虚拟环境
安装好Anaconda后,在开始菜单中选择Anaconda3→Anaconda Prompt来运行Anaconda命令行工具。
通过命令行来创建一个新的虚拟环境。
conda create -n Unity_py36_RollBall python=3.6
其中Unity_py36_RollBall为创建环境的名称,python=3.6为该环境的python版本。(这里没有选择python3.8版本是因为mlagents在最新版python下会出现一些适配问题,因此选择python3.6版本)。之后运行即可,出现下图即为创建成功。
返回Anaconda程序即可看见刚才所创建的环境。
2.配置环境依赖
创建好虚拟环境后,第一步需要的是激活环境,可以通过activate+环境名来激活。
activate Unity_py36_RollBall
环境激活后,会看到命令行顶部会显示当前的环境名,此即为激活成功。
在该环境下,需要安装机器学习的依赖tensorflow,可以通过conda进行安装,conda版本会将所需要的各类包集成好,一键下载,非常方便。
conda install tensorflow
剩下常用的库如scipy、matplotlib等按需安装即可。
命令行中显示done即为安装成功,之后返回Anaconda会看到该环境下多了许多许多库。
在命令行中通过CD命令进入之前解压的ml-agents-master目录下,需要在ml-agents-envs目录和ml-agents目录中安装mlagents。
cd ml-agents-envspip install -e .cd ../cd ml-agentspip install -e .
注:以上命令需依次执行而非一起执行,如遇到问题可在留言区留言告诉我。
看到Successfully installed字样则表明安装成功。可以通过以下命令来检测是否安装成功。
mlagents-learn --help
如果出现帮助文档,那么恭喜你安装成功。
至此,该项目所需的环境依赖配置完成。
四、游戏开发
接下来让我们回到Unity引擎进行游戏的开发。相较于枯燥无味且繁杂的环境激活过程,游戏开发过程则体现的更易收获反馈,更易获得成就感。
1.场景创建
场景是一款游戏的基础,游戏的所有功能都需要在场景内实现。首先是最基本的游戏场景搭建,在Unity引擎Hierarchy窗口中右键选择3D Object→Plane创建出一块空地。选中Plane,在Inspector窗口中将Plane的位置Position置为(0,0,0),空地默认的大小值并不是很大,我们可以将Plane的大小Scale放大为(1.5,1,1.5)。然后在Hierarchy窗口中右键选择3D Object→Sphere来创建两个小球,创建时小球会重叠在一起,因为它们的默认初始位置是相同的,我们可以通过在Inspector窗口中将Sphere的位置Position置为(0,0.5,0)和(2,0.5,0)的方式将它们分开,然后将两个小球分别改名为Target 和AI以示区分,AI球作为操纵球供玩家或AI操纵移动,Target球目标球作为AI球的移动目标。
接下来为小球添加材质Material,为小球着上不同的颜色用以区分。在Project窗口中创建两个Material并分别命名为Red、Green,改变材质的颜色为红色和绿色,将材质拖到对应的小球上。这样一个简单的游戏场景便搭建完成。
接下来就是调整视角的工作,视角是玩家操纵游戏的窗口,好的视角能让玩家的体验更加舒适。在Hierarchy窗口中对Main Camera相机进行调整,相机是我们在进行游戏时的观察窗口,通过调整相机的Position(位置)和Rotation(旋转角度)来使游戏镜头达到适合进行游戏的位置,通过game窗口可以观察到游戏时的镜头。这里给出推荐的Position(0,10,-13)和Rotation(35,0,0),具体数据可依自身做出调整。
完成后,还需要在Inspector窗口中为红球(AI球)添加Rigidbody(刚体)组件,在Unity中Rigidbody组件可以为游戏对象赋予物理属性,使游戏对象在物理系统的控制下接受推力和阻力,从而实现现实世界中的运动效果,这样,红球就会受到向下的引力以及其他的受力运动了。
2.代码实现
在完成场景搭建后,为了实现控制红球的移动以及游戏的功能,我们需要为红球AI添加功能脚本,在Project窗口中新建C#脚本并命名为RollerAgent。以下分别介绍该脚本中的重要函数,游戏源码放在文章末以供参考。
在MLAgents库中,包含四个主要的方法:
OnEpisodeBegin()----进入新的一轮时调用的函数
CollectObservations(VectorSensor sensor)----收集观察的结果
OnActionReceived(float[] vectorAction)----接收动作,给予奖励
Heuristic(float[] action)----手动操作游戏
基于游戏的功能,我们需要重写这四个函数。
Heuristic( )函数用于实现定义手动输入功能,通过Input.GetAxis( )函数来获取水平和垂直方向的移动输入,将其存储在actionsOut[ ]数组中。
public override void Heuristic(float[] actionsOut) { actionsOut[0] = Input.GetAxis("Horizontal"); actionsOut[1] = Input.GetAxis("Vertical"); }
OnActionReceived( )函数用于实现接收输入、处理动作的功能。获取actionsOut[ ]数组中的输入并将其乘以速度,通过AddForce( )函数给其添加对应方向上的力使小球运动起来。当小球从平台跌落时,通过小球的Y轴坐标进行判断,小球跌落时Y轴坐标小于0,便结束该轮游戏。当小球碰到绿球时,通过距离检测,两球距离小于一定值时视为AI球吃到Target球,此时给予AI奖励并结束该轮游戏。
public override void OnActionReceived(float[] vectorAction) { Vector3 control = Vector3.zero; control.x = vectorAction[0]; control.z = vectorAction[1]; rBody.AddForce(control * speed); if (this.transform.position.y < 0) { EndEpisode(); } float distance = Vector3.Distance(this.transform.position, target.position); if (distance < 1.15f) { SetReward(1.0f); EndEpisode(); } }
OnEpisodeBegin( )函数为游戏进入新的一轮游戏时所执行的函数。在以下两种情况下游戏会进入新的一轮:一是AI球吃掉Target球时,此时AI获得奖励并开始新一轮游戏,在平面内随机位置生成Target球即可。二是AI球没有吃到Target球,此时AI没有获得奖励,重置AI球的位置、速度和旋转,开始新的游戏。
public override void OnEpisodeBegin() { if (this.transform.position.y < 0) { this.transform.position = new Vector3(0, 0.5f, 0); this.rBody.velocity = Vector3.zero; this.rBody.angularVelocity = Vector3.zero; } target.position = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4); }
CollectObservas( )函数用于收集观察的结果,在Unity中通常使用神经网络算法来进行计算。我们需要收集两个球的坐标以及AI球在X轴和Z轴方向上的速度用于神经网络算法的输入。
public override void CollectObservations(VectorSensor sensor) { sensor.AddObservation(this.transform.position); sensor.AddObservation(target.position); sensor.AddObservation(rBody.velocity.x); sensor.AddObservation(rBody.velocity.z); }
3.组件配置
脚本编写完成后将RollerAgent脚本拖至AI球上,并在AI球中点击Add Component为AI球添加Decision Requester组件,将Decision Period参数调至10。
之后在AI球的Behavior Parameters组件中修改参数。Behavior Name行为名可以自定义,但后续配置文件需与之统一,这里我们将其命名为RollerBall。在Vector Observation中,神经网络需要接收的值包括两颗球的位置坐标和AI球在X、Z轴方向上的速度,由于每个坐标均包含下X、Y、Z三个数值,故共需要接收8个数值,将Vector Observation中的Space Size设定为8。由于小球的输入参数是二维连续的,因此将Vector Type中的Space Size设定为2,将Space Type设定为Continuou。
最后,在AI球的Roller Agent(Script)组件中添加Target目标,将Target球拖至Target栏即可。
至此该游戏功能全部完成,点击Play便可测试游戏。
五、训练AI
在项目目录下创建Train文件夹,在Train文件夹中新建一个文件config.yaml。文件内容如下。其中第二行的RollerBall需与AI球Behavior Parameters组件中Behavior Name保持一致。
behaviors: RollerBall: trainer_type: ppo hyperparameters: batch_size: 64 buffer_size: 12000 learning_rate: 0.0003 beta: 0.001 epsilon: 0.2 lambd: 0.99 num_epoch: 3 learning_rate_schedule: linear network_settings: normalize: true hidden_units: 128 num_layers: 2 vis_encode_type: simple reward_signals: extrinsic: gamma: 0.99 strength: 1.0 keep_checkpoints: 5 max_steps: 300000 time_horizon: 1000 summary_freq: 1000 threaded: true
打开Anaconda Prompt,如果之前关掉了,别忘了在命令行中重新激活环境。
通过命令行进入项目目录下的Train目录下。
在该目录下输入训练命令准备进行训练。
mlagents-learn config.yaml
出现上图标志即为准备开始,在Unity点击上方的play按钮便可开启漫长的训练过程。
在一个半小时的训练后,训练次数达到300000次便自动结束,此时在Train目录下会生成results/ppo目录,将ppo目录下的RollerBall.nn文件拖至AI球的Behavior Parameters组件中的Model栏中,该nn文件为训练好的模型,再次运行游戏时会发现AI球已经学会了如何控制自己“吃掉”绿球。
训练过程如下视频展示,视频已经过加速处理,请耐心观看。
该模型训练成功后,游戏仍具有良好的扩充性,例如在此基础上训练绿球如何逃出红球的魔爪、扩充地形添加运动障碍等,读者如果有兴趣的话可以继续进行延伸。
游戏源码:
using System.Collections;using System.Collections.Generic;using UnityEngine;//机器学习包using Unity.MLAgents;using Unity.MLAgents.Sensors;public class RollerAgent : Agent{ //获得目标坐标 public Transform target; //定义小球速度 public float speed = 10; //获得红球的刚体 Rigidbody rBody; // Start is called before the first frame update void Start() { rBody = GetComponent(); } //重写agent内四个函数 //进入新一轮时调用的函数 public override void OnEpisodeBegin() { //print("strat"); //优化:只有当小球掉落时才重置当前小球的位置 if (this.transform.position.y < 0) { //重新开始,设置小球的初始属性(位置、速度、旋转) this.transform.position = new Vector3(0, 0.5f, 0); this.rBody.velocity = Vector3.zero; this.rBody.angularVelocity = Vector3.zero; } //随机target位置 target.position = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4); } //收集观察的结果 通过神经网络计算 public override void CollectObservations(VectorSensor sensor) { //传参:2个坐标(当前位置的坐标,Target坐标) //一个position包含x、y、z三个值 //故observation space = 3+3+2 = 8 sensor.AddObservation(this.transform.position); sensor.AddObservation(target.position); //传参:2个速度(当前x轴与z轴方向上的速度) sensor.AddObservation(rBody.velocity.x); sensor.AddObservation(rBody.velocity.z); } //接收动作,是否给予奖励 public override void OnActionReceived(float[] vectorAction) { //做出动作,处理输出 //print("Horizontal" + vectorAction[0]); //print("Vertical" + vectorAction[1]); Vector3 control = Vector3.zero; control.x = vectorAction[0]; control.z = vectorAction[1]; rBody.AddForce(control * speed); //红球出界(Y轴坐标判定) if (this.transform.position.y < 0) { //结束该轮游戏 EndEpisode(); } //完成目标(碰到小绿)(可能用碰撞检测会更好) float distance = Vector3.Distance(this.transform.position, target.position); if (distance < 1.15f) { //奖励并结束该轮测试 SetReward(1.0f); EndEpisode(); } } //手动操作AI public override void Heuristic(float[] actionsOut) { //获得水平和垂直方向的输入 actionsOut[0] = Input.GetAxis("Horizontal"); actionsOut[1] = Input.GetAxis("Vertical"); }}
恭喜你学完了一例Unity机器学习案例,当然你可能已经发现该游戏仍存在许多不足之处,如果你是游戏美术、程序、策划等大佬,并且同样对游戏开发和AI感兴趣,欢迎加入我们同我们一起学习!
指导老师: