# Aimbot-ParallelEnv 这是一个基于Unity ML-Agents的基于FPS游戏的多智能体训练环境,用于生成指定目标和对应难度,并且根据Agent的Action反馈Reward来训练强化学习Agent的环境。 [![Chinese badge](https://img.shields.io/badge/简体中文-Simplified_Chinese-blue)](./README.md) [![Japanese badge](https://img.shields.io/badge/日本語-Japanese-blue)](./README-JP.md) ## Description 项目基于[ML-Agents 2.0.1](https://github.com/Unity-Technologies/ml-agents/tree/develop),使用Unity 2021.3.14f1开发。 Python侧则使用[mlagents-envs 0.30.0](https://pypi.org/project/mlagents-envs/)与环境进行通信。 ## 快速开始 在项目[Aimbot-PPO](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-PPO)中,使用了本环境进行了PPO算法的训练,其中在[AimbotEnv.py](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-PPO/src/branch/OffP-FullMNN-V2/Aimbot-PPO-Python/Pytorch/AimbotEnv.py)中给出了本环境的使用方法。 ## 游戏简介 这是一个模仿初代Doom的基本模式的FPS游戏环境,Agent将会被给到不同的目标,Agent需要根据目标的类型和位置,以及自身的状态,来决定是否开枪射击,射击的方向,以及移动方向。环境内包含两种模式和3种目标,分别是:训练模式和测试模式,以及`FreeTarget`,`GotoTarget`和`AttackTarget`。`FreeTarget`需要Agent使用武器击倒区域内所有的敌人,`GotoTarget`和`AttackTarget`会在区域内生成一个目标区块,Agent需要对目标区块进行不同的对应来完成任务。同时`GotoTarget`和`AttackTarget`中,根据生成目标区块的构造不同,分为了5种不同的难度。 ## 模式介绍 训练模式和测试模式都需要与Python侧进行通信,根据从本环境中观测到的Observation生成Action并传递回本环境。在环境启动时需要使用者在游戏内进行操作来选择模式,由于ML-Agents存在Time limit,所以需要在45s内进行模式选择,超过45s后将自动退出游戏环境。 ### 训练模式 在进入训练模式前,使用者需要从Start界面中,只训练单个模式的`Train-Free`,`Train-Goto`,`Train-Attack`和训练所有模式的`Train-Mix`中进行选择。在选择后,将会进入训练模式,并且注意该选择不能在之后进行更改。对于拥有多种难度的目标区块的模式`Train-Goto`和`Train-Attack`,在Start界面将会有一个用来调整生成不同难易度目标区块的概率的面板,默认的生成概率为平均。面板为了保证该模式各难易度生成概率总和为1,在调整某一难度的生成概率时,其他难度的生成概率将会被自动调整,用户可以通过点击右侧锁定按钮以锁定或解锁某一难度的生成概率。该面板将在训练模式执行中也可见,用户在训练模式执行中对概率面板进行调整后将会在应用在下次目标区块生成时。
![TargetLevelProbabilityPanel](./ReadmeImages/LevelProbabilityPanel.jpg) ### 测试模式 测试模式中,用户需要手动来对Agent下达命令,用户可以通过点击右上角的菜单来执行生成目标区块或敌人,切换目标模式等。在该模式中,比训练模式多出一个新的Target为StayTarget,当用户未指定目标或者清空游戏时将会以将该Target指派给Agent。通过点击右上角的菜单,可以将鼠标的模式切换为生成Enemy,或者生成对应难度Target区块的模式,此时鼠标移动到游戏区域内后将会出现生成物件的预览,内点击鼠标右键则可以在对应的位置生成对应的Enemy或者区块。当生成Target区块时会自动指派对应target给agent,需要指派`FreeTarget`时则需要点击`FreeMode`按钮。通过点击`ClearGame`可以清空所有enemy和区块并将Agent的目标指派为`StayTarget`,通过点击`StayMode`则可以强制将Agent目标指派为`StayTarget`。 ## 目标介绍 ### FreeTarget 在Agent被指派到`FreeTarget`时,Agent需要使用武器击倒区域内所有的敌人。在该模式中,一个回合开始时将会在区域内随机生成一定数量的敌人,默认为6,Agent将会生成在区域的随机位置,Agent朝向继承上一回合,如果是第一回合则是默认角度为0。Agent在成功击倒所有敌人后被判断为胜利,或者超过30s未能成功击倒所有敌人则被判断为失败。 ### GotoTarget 在Agent被指派到`GotoTarget`时,Agent的目标是移动到指定目标区块的指定位置。在该模式中,一个回合开始时Agent的位置总是继承上一个回合,如果是第一回合将会默认生成在地图的左下角。Goto的目标区块将会根据难度概率在区域内随机生成对应难度的预设区块,不同难易度拥有多个已保存好的预设区块,这些预设区块通过[`SceneBlocksSet`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/TargetContainer/SceneBlocksSet.cs)保存在[Prefab文件夹](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Prefeb)中。所有目标区块的总大小为10*10,生成目标区块时区块将会避开Agent的所在位置。指定目标区域是名为`FireBase`的圆形区域,该区域所覆盖的范围不会超过目标区块,但是它的位置和大小会根据预设区块的不同而不同。Agent在成功移动到目标区块中心位置后被判断为胜利,或者超过30s未能成功移动到目标区块中心位置则被判断为失败。 ### AttackTarget 在Agent被指派到`AttackTarget`时,Agent的目标是对目标区域进行火力压制,同时主要目标是击倒区块内所有敌人。在该模式中,Agent与目标区块的生成策略还有`FireBase`的构成与`GotoTarget`相同。但是在`AttackTarget`的`FireBase`区域内必定会有Enemy生成。Agent在成功击倒区块内所有敌人后被判断为胜利,或者超过30s未能成功击倒区块内所有敌人则被判断为失败。 --- # Environment ## Observation Space 在本强化学习训练设置中,观测数据是理解和交互环境的关键。观测环境由三个主要部分组成:`TargetState`(目标状态)、`AgentState`(自机状态)和`RaycastState`(射线探测状态)。这些观测数据通过ML-Agents的`VectorSensor`类进行收集,并发送给Python侧,为决策提供必要的信息。Observation的Source Code位于[`MLAgentsCustomController.cs`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/GameScript/MLAgentsCustomController.cs)中被override的`CollectObservations`函数中。 ### TargetState(目标状态) `TargetState`包含了Agent接收的目标指令和相关信息,总大小为6。其中包含Target的类型,Target的x,y,z坐标,TargetArea的直径,以及Agent是否处于TargetArea和本回合剩余时间内这些信息。 | Num | Observation | Size | State Space | Description | |-----|------------------|------|-------------|-------------------------------------------------------------------------------------------------------------| | 0 | Target类型 | 1 | 0,1,2,3,4 | 描述被指派的Target类型:0=FreeTarget,1=GotoTarget,2=AttackTarget,3=DefenceTarget(未使用),4=StayTarget | | 1~3 | Target坐标 | 3 | 0~47 | 描述Target中Firebase的连续空间坐标,取值范围为0到47之间的连续值 | | 4 | FireBaseDiameter | 1 | 1~10 | 描述FireBase的直径,取值范围为在1到10之间的连续值 | | 5 | InFireBaseState | 1 | 0,1 | 描述Agent是否处于FireBase中,0=False,1=True | | 6 | RemainTime | 1 | 0~30 | 描述本回合剩余时间,取值范围为在0到30之间的连续值 | ### AgentState(自机状态) `AgentState`包含了Agent的武器可攻击状态,Agent的x,y,z坐标和Agent的朝向角度。为了避免直接使用Agent GameObject朝向角度时,0到360度变化时值的大幅度跳变,同时为了实现更好的周期性表示,这里使用了Agent GameObject旋转角度的余弦和正弦值来表示Agent的朝向。 | Num | Observation | Size | State Space | Description | |------|-------------|------|-------------|---------------------------------------------------------------------| | 7 | GunState | 1 | 0,1 | 描述Agent武器是否处于可使用状态。0=False,1=True | | 8~10 | Agent坐标 | 3 | 0~47 | 描述Agent的连续空间坐标,取值范围为0到47之间的连续值 | | 11~12 | Agent朝向 | 2 | -1~1 | 通过计算Agent GameObject旋转角度的余弦和正弦以描述Agent的正面朝向。 | ### RaycastState(射线探测状态) `RaycastState`记录了视野内发射的射线探测到的Object的Tag及其距离、其中Tag可以通过Label Encoding和OneHot Encoding两种方式来记录,默认使用Label Encoding,可以在Object`CommonParameterContainer`中进行调整。`RaycastState`由[`RaySensors`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/GameScript/RaySensors.cs)类管理。它实现了射线的非均匀分布,默认视野中间15%的区域射线分布更密集,两侧则较为稀疏,默认中间密集部分共射出5条,两侧稀疏部分各7条。 每个射线可探测的对象最远距离为100个单位,超过探测距离则返回0。其中视野中间区域的覆盖范围,区域内RayCast数量以及可探测距离均可以在每个`Agent`GameObject的Inspector中进行调整。
射线分布方式 | Num | Observation | Size | State Space | Description | |------------------------------------------------------------------|-----------------|--------------------|---------------|----------------------------------------------------------------------------------| | 11~Raynum+11 | TagType(Label) | Number of Raycasts | 0, 1, 2 | 描述Raycast所探测到物体的Tag, 0=Wall, 1=Enemy, 2=Player, -1=Nothing | | 11~Raynum * 3+11 | TagType(Onehot) | Raynum * 3 | 0, 1 | 描述Raycast所探测到物体的Tag, 使用Onehot编码记录 | | Raynum+12~2* Raynum+12(Label),
3* Raynum+12~4* Raynum+12(OneHot) | Distance | Number of Raycasts | 0~MaxDistance | 描述Raycast所探测到物体的距离,取值范围在0~MaxDistance,其中MaxDistance默认为100 | *注:n为每个Agent的RayCast数量,MaxDistance为每个Agent的RayCast最大探测距离* ## Action Space 为了模拟FPS游戏中玩家对于键盘和鼠标的同时操作,本环境中的Action Space分为两个部分,分别是`Discrete Action`和`Continuous Action`。其中`Discrete Action`用于模拟玩家对于键盘的离散操作,`Continuous Action`用于模拟玩家对于鼠标的连续操作。两个Action Space的对应操作可以参考于[`MLAgentsCustomController.cs`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/GameScript/MLAgentsCustomController.cs)中被override的`Heuristic`函数。具体实现则于[`AgentController.cs`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/GameScript/Character/AgentController.cs)中的`MoveAgent`和`CameraControl`函数中。 ### Discrete Action 在`Discrete Action`中,Agent可以对各方向的移动和攻击进行离散操作,其中移动操作包括垂直移动操作和水平移动操作,攻击操作可以对武器进行操。 | Num | Action | Action Space | Description | |-----|-----------------|--------------|---------------------------------------------------------------------------| | 0 | Vertical Move | 0,1,-1 | 用于控制Agent在其正面方向的垂直轴上移动。0=停止,1=向前移动,-1=向后移动 | | 1 | Horizontal Move | 0,1,-1 | 用于控制Agent在其正面方向的水平轴上移动。0=停止,1=向右移动,-1=向左移动 | | 2 | Attack | 0,1 | 用于控制Agent的攻击操作。0=不攻击,1=执行攻击 | ### Continuous Action 在`Continuous Action`中,可以对Agent的视角进行操作。由于本环境是模拟早期Doom的FPS环境,所以Agent的视角被设定为仅能在水平方向上进行旋转。 | Num | Action | Action Space | Description | |-----|-------------------|--------------|---------------------------------------------------------------------------------| | 0 | Vertical Rotation | -Inf~Inf | 控制Agent在水平方向(左右)上的视角旋转。正值使视角向右,负值则使视角向左旋转。 | ## Reward 在各给定的目标中,为了让Agent注重于完成不同的任务,Reward的设计也有所不同。在训练模式中,Reward由共同Reward`Common Reward`和各个目标的专用Reward组成。 ### Common Reward 为了引导Agent在训练过程中执行对完成目标有帮助的操作,和避免无意义的动作,在`Common Reward`中,环境为Agent设计了以下的Reward和Penalty。其中由于ML-Agents不能向Python直接传递回合结束的结果,同时为了避免其他Reward数值过小或者过大导致无法判断胜利或者失败,因此在回合结束的Reward中添加了极大数和极小数来表示胜利和失败。 | Reward/Penalty | Value/Symbol | Description | |----------------|---------------|-----------------------------------------------------| | NonTargetHit | 3 | 当Agent击中或者击倒不作为Target的敌人时所获得Reward | | TargetHit | 25 | 当Agent击中或者击倒作为Target的敌人时所获得Reward | | MovePenalty | -0.5 | 当Agent在场地中移动时给到的Penalty | | SpinPenalty | $P_s$ | 当Agent进行视角旋转时给到的Penalty | | WinReward | 999 | 当回合结束并且本回合Agent达成目标时给到的Reward | | LoseReward | 999 | 当回合结束并且本回合Agent失败时给到的Reward | 为了防止Agent在训练中出现无意义的旋转,和旋转过快,这里用下列函数来给到Agent一个`SpinPenalty`。当40个Action的旋转记录未达到阈值时会给到一个较小的Penalty,而当超过阈值时我们可以知道Agent正在向同一方向持续旋转,则给到一个较大的Penalty。其中Pst为在`t`时刻时的旋转的Penalty,mouseXt为`t`时刻Agent的`Contunuouse Action`中的旋转Action数值。 $$ P_{st} = \begin{cases} -|mouseX_t| \cdot 0.06 &, & \left| \sum_{t=0}^{-40} mouseX_t \cdot 0.08 \right| < 10 \\ -\left| \sum_{t=0}^{-40} mouseX_t \cdot 0.08 \right| & , & \left| \sum_{t=0}^{-40} mouseX_t \cdot 0.08 \right| \geq 10 \end{cases} $$ ### FreeTarget 在`FreeTarget`中,主要目标是消灭所有敌人,因此,追踪敌人的能力非常关键。本模式设置了两种奖励机制:当视野中心探测到敌人时给到Agent一个为2的Reward,当未探测到敌人时,则根据探测到的敌人距离视野中心的距离 $D$ 给到一个小于`2`的Reward。距离的计算仅考虑通过 Raycast 探测到的敌人,且当距离越接近0.5,即Enemy半径则Reward越接近`2`。例如,在图中,只计算了与`Enemy1`和`Enemy3`的距离,并且取用最短距离 $D_1$ 来计算Reward。未探测到的`Enemy2`和`Enemy4`被排除在外。在这个模式下,所有敌人都被视为目标,当敌人被击败或击中时,Agent 将获得`Common Reward`中的`TargetHit Reward`。
FacingReward | Reward/Penalty | Value/Symbol | Description | |----------------|----------------------|----------------------------------------| | FacingReward | $R_f$ | 视野中心与所探测到最近敌人距离的Reward | $$ R_{f} = \begin{cases} 2 &, &min(D) \leq 0.5 \\ \frac{1}{\sqrt{\frac{min(D)}{2}}} &,& min(D) \gt 0.5 \end{cases} $$ ### GotoTarget 在`GotoTarget`中,主要目标是向指定的`FireBase`移动。根据在`t`时刻和`t-1`时刻的玩家与目标间的距离 $D_t$ 和 $D_{t-1}$ 的差值,设置了每个 Step 可获得的距离奖励 $R_d$ 。当Agent接近目标时将获得正向Reward,远离目标时则获得小于0的Penalty。当玩家处于`FireBase`内时,可以获得更高的固定奖励,然而此时`DistanceReward`将不会生效。在此模式中,所有敌人均不被视为攻击目标,但在击中或击败敌人时,可以获得`Common Reward`中的`NonTargetHit`。 | Reward/Penalty | Value/Symbol | Description | |----------------|--------------|-----------------------------------------| | DistanceReward | $R_d$ | Agent不在目标内时,和目标的距离差Reward | | InAreaReward | 5 | Agent处于目标内时的持续奖励 | $$ R_{d} = (D_{t-1} - D_{t}) \cdot 20 $$ ### AttackTarget 在 Attack Mode 中,主要目的是消灭目标地点的敌人并鼓励Agent对目标地点进行设计。因此与`FreeTarget`类似,将视线移动到目标地点同样重要。当Agent面对目标地点并且进行攻击时,将会被视为压制行为,并获得较低的`SuppressiveReward`。面对目标地点但未进行攻击时,获得持续获得更低的`FacingAreaReward`。处于`FireBase`中的Enemy将会被视为目标,当敌人被击败或击中时,Agent 将获得`Common Reward`中的`TargetHit Reward`。而`FireBase`外的敌人则不会被视为目标,当敌人被击败或击中时,Agent 将只能获得`Common Reward`中的`NonTargetHit Reward`。 | Reward/Penalty | Value/Symbol | Description | |-------------------|--------------|-----------------------------------------| | SuppressiveReward | 5 | Agent对`FireBase`进行压制攻击时获得奖励 | | FacingAreaReward | 2 | Agent面朝目标时获得的持续奖励 | ## Side Channel 为了方便对于环境的调试和实现Unity与Python间非实时通信,本环境中使用了ML-Agents提供的Side Channel,用于向Python侧传递一些额外的信息。这里我参考了huggingface的[Custom Side Channels](https://github.com/huggingface/ml-agents-patch/blob/develop/docs/Custom-SideChannels.md)。在Unity端,Side Channel的实现位于[`AimbotSideChannel.cs`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/GameScript/SideChannel/AimbotSideChannel.cs)和[`AimBotSideChannelController.cs`](https://koha9.asuscomm.com/yurugit/Koha9/Aimbot-ParallelEnv/src/branch/main/Assets/Script/GameScript/SideChannel/AimBotSideChannelController.cs)中。每个Log信息将会以`|`来分隔字段,其中第一个字段为Log的类型,随后字段为自定义Log信息。在本环境中,Unity的SideChannel将会发送所有`LogType.Warning`和`LogType.Error`类型的Log到Python侧。其中`LogType.Warning`会传递一回合结束后的的胜败信息和从Unity发送至Python的指令。胜败信息将类似于`Warning|Result|Win`和`Warning|Result|Lose`。而指令则传递了在下一训练结束后保存模型的命令,它的内容为`Warning|Command`。