目录
1.3.1 public override void Initialize()
1.3.2 SoccerEnvController envController = GetComponentInParent()
1.3.4 m_BehaviorParameters = gameObject.GetComponent()
1.3.5 m_BehaviorParameters.TeamId == (int)Team.Blue
1.3.6 m_SoccerSettings = FindObjectOfType()
1.3.7 m_ResetParams = Academy.Instance.EnvironmentParameters
1.4.1 public void MoveAgent(ActionSegment act)
1.4.2 var forwardAxis = act[0];
1.4.6 transform.Rotate(rotateDir, Time.deltaTime * 100f);
1.5.1 public override void OnActionReceived(ActionBuffers actionBuffers)
1.5.2 AddReward(m_Existential)
1.5.3 MoveAgent(actionBuffers.DiscreteActions)
1.6.1 public override void Heuristic(in ActionBuffers actionsOut)
1.6.2 var discreteActionsOut = actionsOut.DiscreteActions
1.6.4 discreteActionsOut[0] = 1
1.7.2 var force = k_Power * m_KickPower
1.7.3 position == Position.Goalie
1.7.4 c.gameObject.CompareTag("ball")
1.7.5 var dir = c.contacts[0].point - transform.position
1.7.7 c.gameObject.GetComponent().AddForce(dir * force)
1.8.2 m_BallTouch = m_ResetParams.GetWithDefault("ball_touch", 0)
1. AgentSoccer.cs
1.1 导入命名空间
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Policies;
public enum Team
{
Blue = 0,
Purple = 1
}
这段代码是用于导入Unity ML-Agents的相关类库,包括ML-Agents的核心库以及行为决策相关的库。其中包括:
- UnityEngine:Unity引擎的核心类库。
- Unity.MLAgents:Unity ML-Agents的核心类库。
- Unity.MLAgents.Actuators:定义了Agent的动作类型,例如连续、离散等。
- Unity.MLAgents.Policies:定义了训练Agent的决策策略,例如随机策略、行为树策略等。
这段代码定义了一个枚举类型Team,枚举值为Blue和Purple,用于表示环境中的两个队伍。在Unity ML-Agents中,代理(Agent)之间通常被分成不同的队伍,例如在足球场景中,有蓝队和紫队。使用Team枚举类型来指定队伍,便于对代理进行分类和管理。
1.2 AgentSoccer类
public class AgentSoccer : Agent
{
// Note that that the detectable tags are different for the blue and purple teams. The order is
// * ball
// * own goal
// * opposing goal
// * wall
// * own teammate
// * opposing player
public enum Position
{
Striker,
Goalie,
Generic
}
[HideInInspector]
public Team team;
float m_KickPower;
// The coefficient for the reward for colliding with a ball. Set using curriculum.
float m_BallTouch;
public Position position;
const float k_Power = 2000f;
float m_Existential;
float m_LateralSpeed;
float m_ForwardSpeed;
[HideInInspector]
public Rigidbody agentRb;
SoccerSettings m_SoccerSettings;
BehaviorParameters m_BehaviorParameters;
public Vector3 initialPos;
public float rotSign;
EnvironmentParameters m_ResetParams;
......
}
这部分代码定义了一个叫做 AgentSoccer
的类,并声明了其中的一些变量和枚举类型。
team
变量是一个枚举类型 Team
,枚举成员包括 Blue
和 Purple
,用于表示代理所在的队伍。
m_KickPower
和 m_BallTouch
变量是 float
类型,分别用于表示代理的踢球力量和碰到球的次数(球被踢的次数)。m_BallTouch
变量的值可以通过课程设定进行修改。
position
变量是一个枚举类型 Position
,枚举成员包括 Striker
、Goalie
和 Generic
,用于表示代理的位置。前锋(Striker)、门将(Goalie)或者普通(Generic)。
[HideInInspector]
是一个用于Unity的C#脚本的属性修饰符。它的作用是隐藏Unity编辑器面板上的公共变量。在脚本中使用[HideInInspector]
修饰的变量仍然可以在代码中进行访问和修改,但是在编辑器中将不会显示该变量。这通常用于保护变量不被其他脚本修改,同时又不想让该变量在编辑器中显示出来。
k_Power常量是float类型,其值为2000.0f。在代码中,常量使用const关键字来声明,一旦声明并赋值后,在程序执行期间不会改变。
m_Existential
、m_LateralSpeed
和 m_ForwardSpeed
变量是 float
类型,分别用于表示代理获得的奖励、横向速度和前向速度。
agentRb
变量是一个 Rigidbody
类型,用于表示代理的物理属性。
m_SoccerSettings
变量是一个 SoccerSettings
类型,用于存储足球比赛的相关设置信息。
m_BehaviorParameters
变量是一个 BehaviorParameters
类型,用于表示代理的行为参数,例如它所使用的神经网络模型。
initialPos
变量是一个 Vector3
类型,表示代理的初始位置。
rotSign
变量是一个 float
类型,用于控制代理的旋转方向。
m_ResetParams
变量是一个 EnvironmentParameters
类型,用于存储重置环境的参数。
1.3 Initialize()
public override void Initialize()
{
// 获取环境控制器,计算当前代理在环境中的存在时间比例
SoccerEnvController envController = GetComponentInParent<SoccerEnvController>();
if (envController != null)
{
m_Existential = 1f / envController.MaxEnvironmentSteps;
}
else
{
m_Existential = 1f / MaxStep;
}
// 获取代理的BehaviorParameters组件,判断代理所在的队伍和初始位置
m_BehaviorParameters = gameObject.GetComponent<BehaviorParameters>();
if (m_BehaviorParameters.TeamId == (int)Team.Blue)
{
team = Team.Blue;
initialPos = new Vector3(transform.position.x - 5f, .5f, transform.position.z);
rotSign = 1f;
}
else
{
team = Team.Purple;
initialPos = new Vector3(transform.position.x + 5f, .5f, transform.position.z);
rotSign = -1f;
}
// 根据代理的位置属性设置代理的运动速度
if (position == Position.Goalie)
{
m_LateralSpeed = 1.0f;
m_ForwardSpeed = 1.0f;
}
else if (position == Position.Striker)
{
m_LateralSpeed = 0.3f;
m_ForwardSpeed = 1.3f;
}
else
{
m_LateralSpeed = 0.3f;
m_ForwardSpeed = 1.0f;
}
// 获取SoccerSettings对象和代理的刚体组件,并设置初始角速度和最大角速度
m_SoccerSettings = FindObjectOfType<SoccerSettings>();
agentRb = GetComponent<Rigidbody>();
agentRb.maxAngularVelocity = 500;
// 获取环境的重置参数对象
m_ResetParams = Academy.Instance.EnvironmentParameters;
}
在这段代码中,首先通过 GetComponentInParent<SoccerEnvController>() 获取当前 Agent 所在的 SoccerEnvController 组件,然后根据获取到的 SoccerEnvController 组件来设置 m_Existential 变量。如果获取不到 SoccerEnvController 组件,则直接使用 MaxStep(即训练步数)来设置 m_Existential 变量。
接下来,根据 BehaviorParameters 中设置的 TeamId 来确定 Agent 的队伍和初始位置。如果 TeamId 为 0,则为蓝队,初始位置在当前位置左边 5 个单位;如果 TeamId 为 1,则为紫队,初始位置在当前位置右边 5 个单位。同时,rotSign 变量也被设定为 1 或 -1,用于在训练中控制 Agent 的旋转方向。
然后根据 Agent 的位置属性来设定其速度属性。如果 Agent 是守门员,则设定其横向和纵向速度都为 1.0;如果是前锋,则横向速度为 0.3,纵向速度为 1.3;否则(位置为 Generic),则横向速度为 0.3,纵向速度为 1.0。
接下来,通过 FindObjectOfType<SoccerSettings>() 获取 SoccerSettings 组件,并将 agentRb 设定为当前 Agent 的 Rigidbody 组件。此外,还将 agentRb 的 maxAngularVelocity 设定为 500。
最后,使用 Academy.Instance.EnvironmentParameters 获取环境参数,并将其设定为 m_ResetParams。
1.3.1 public override void Initialize()
Initialize() 是 Agent 类中的一个虚函数,需要被重写。在该函数中,可以进行一些初始化的工作,比如初始化 Agent 的状态、设定 Agent 的动作空间和观察空间等。
1.3.2 SoccerEnvController envController = GetComponentInParent<SoccerEnvController>()
SoccerEnvController envController = GetComponentInParent<SoccerEnvController>();
这行代码的作用是获取当前Agent所在的父物体(Parent),并获取其上挂载的SoccerEnvController组件的引用,以便在后续的代码中获取该环境的最大步数。GetComponentInParent方法会在当前GameObject的父物体上查找该类型的组件。如果没有找到,则会继续在父物体上查找,直到找到该组件或者查找到达根节点为止。
1.3.3 envController != null
if (envController != null)
{
m_Existential = 1f / envController.MaxEnvironmentSteps;
}
else
{
m_Existential = 1f / MaxStep;
}
这段代码的作用是获取Agent所在的Soccer环境控制器(SoccerEnvController)的组件,判断其是否为空。如果不为空,则将m_Existential设置为1除以Soccer环境控制器的最大步数(MaxEnvironmentSteps);否则将m_Existential设置为1除以Agent的最大步数(MaxStep)。
如果envController不为空,说明Agent是被包含在SoccerEnvController中的一个子GameObject中,这时可以从父级对象中获取到环境控制器的组件。如果envController为空,说明Agent并不在SoccerEnvController中,而是单独作为一个对象存在,此时需要使用Agent自身的最大步数。
1.3.4 m_BehaviorParameters = gameObject.GetComponent<BehaviorParameters>()
m_BehaviorParameters = gameObject.GetComponent<BehaviorParameters>();
这一行代码是获取当前游戏对象(agent)上的BehaviorParameters组件,该组件包含了一些特定的行为参数和策略。这些参数和策略可以影响agent的决策和行为。在Unity ML-Agents中,BehaviorParameters组件是在训练开始时指定的。
1.3.5 m_BehaviorParameters.TeamId == (int)Team.Blue
m_BehaviorParameters.TeamId == (int)Team.Blue
这段代码判断了这个Agent的TeamId,如果它是Team.Blue(枚举值为0),则把它的team成员变量设为Team.Blue,否则设为Team.Purple。
TeamId
是 BehaviorParameters
类型的一个整数属性,它指定了智能体的团队,可以取任何整数,但在这个代码中,只有0和1是有意义的。这个属性值在 BehaviorParameters
组件中设置,可以设置为 0
或 1
,代表蓝队和紫队。
在这个代码段中,我们通过检查 m_BehaviorParameters.TeamId
是否等于 0
,来判断这个智能体属于蓝队还是紫队。如果 TeamId
等于 0
,我们就把 team
变量设置为 Team.Blue
,否则设置为 Team.Purple
。
问:rotSign ?
rotSign
是一个用于控制 Agent 的初始旋转方向的变量。在 Initialize() 方法中,如果 Agent 属于 Team.Blue
,则 rotSign
被设置为 1,否则设置为 -1。当 rotSign
为 1 时,Agent 的初始旋转方向为沿 Y 轴正方向(向上),当 rotSign
为 -1 时,Agent 的初始旋转方向为沿 Y 轴负方向(向下)。这个变量在后面的 Heuristic()
和 OnActionReceived()
方法中会被用到。
1.3.6 m_SoccerSettings = FindObjectOfType<SoccerSettings>()
m_SoccerSettings = FindObjectOfType<SoccerSettings>();
这行代码使用了 Unity 的 FindObjectOfType 函数,用于查找场景中指定类型的对象。这里查找的对象是 SoccerSettings,可能是一个包含了足球场地和比赛规则等相关信息的脚本或者游戏对象。这个对象被赋值给 m_SoccerSettings 变量,可能在后续的代码中被用来获取一些场地或规则相关的参数。
1.3.7 m_ResetParams = Academy.Instance.EnvironmentParameters
m_ResetParams = Academy.Instance.EnvironmentParameters;
这一行代码从Academy实例中获取了环境参数(m_ResetParams),这些参数可以在训练时被动态更改以改变训练过程的行为。例如,你可以通过更改重生时间或奖励的权重来调整模型的训练过程。
在Unity ML-Agents中,Academy是ML-Agents系统中的主要组件之一,它控制整个环境并提供训练所需的配置。每个场景都应该有一个Academy对象。在代码中,可以通过调用“Academy.Instance”来访问场景中的Academy实例。
1.4 MoveAgent()函数
public void MoveAgent(ActionSegment<int> act)
{
// 初始化行动方向与旋转方向为0,(0, 0, 0)
var dirToGo = Vector3.zero;
var rotateDir = Vector3.zero;
// 初始化踢球力量为0
m_KickPower = 0f;
// 获取前进方向的动作值和右侧方向的动作值
var forwardAxis = act[0];
var rightAxis = act[1];
// 获取旋转方向的动作值
var rotateAxis = act[2];
// 根据前进方向的动作值确定前进方向,并根据需要设置踢球力量
switch (forwardAxis)
{
case 1:
dirToGo = transform.forward * m_ForwardSpeed;
m_KickPower = 1f;
break;
case 2:
dirToGo = transform.forward * -m_ForwardSpeed;
break;
}
// 根据右侧方向的动作值确定右侧方向
switch (rightAxis)
{
case 1:
dirToGo = transform.right * m_LateralSpeed;
break;
case 2:
dirToGo = transform.right * -m_LateralSpeed;
break;
}
// 根据旋转方向的动作值确定旋转方向
switch (rotateAxis)
{
case 1:
rotateDir = transform.up * -1f;
break;
case 2:
rotateDir = transform.up * 1f;
break;
}
// 根据旋转方向和速度旋转Agent
transform.Rotate(rotateDir, Time.deltaTime * 100f);
// 根据速度和力量添加力到Agent的Rigidbody
agentRb.AddForce(dirToGo * m_SoccerSettings.agentRunSpeed,
ForceMode.VelocityChange);
}
1.4.1 public void MoveAgent(ActionSegment<int> act)
public void MoveAgent(ActionSegment<int> act)
这行代码是一个函数声明语句,函数名为MoveAgent,它接收一个ActionSegment类型的参数act。这个函数是一个代理(agent)与环境之间的接口,它接收神经网络输出的动作,然后执行对应的动作,将代理移动到新的位置。
问:ActionSegment<int> act ?
ActionSegment<int> act
是一个参数类型声明。在这个函数中,参数 act
是一个整数类型的动作向量。ActionSegment<T>
是一个通用的类,表示一个由类型为 T 的元素组成的动作向量。在这个特定的案例中,ActionSegment<int>
表示一个由整数组成的动作向量,其中每个整数都被映射到特定的动作。
1.4.2 var forwardAxis = act[0];
var forwardAxis = act[0];
var rightAxis = act[1];
这段代码中,我们获取到了Agent从神经网络中输出的动作,其中forwardAxis
对应前进方向的动作值,rightAxis
对应右侧方向的动作值。在此之后,我们可以根据这些动作值来决定我们的Agent要往哪个方向移动。
问:神经网络中输出的动作?
神经网络会输出一组动作的值,这些值通常会经过一定的处理后转化成具体的动作,如本例中的前进、后退、左移、右移、向上踢球、向下踢球等。在本例中,使用的是一组离散动作,即用整数表示不同的动作,因此模型的输出是一个整数数组。
问:var ?
var
是C#语言中的一个关键字,用于声明一个变量而不需要显式地指定其类型。编译器会根据右侧的值自动推断变量类型。例如,var num = 10;
声明了一个整数变量 num
,编译器会自动将其类型推断为 int
。
1.4.3 switch (forwardAxis)
switch (forwardAxis)
{
case 1:
dirToGo = transform.forward * m_ForwardSpeed;
m_KickPower = 1f;
break;
case 2:
dirToGo = transform.forward * -m_ForwardSpeed;
break;
}
这段代码是用来处理智能体向前移动的动作,它首先检查forwardAxis
的值,如果它的值为1,就表示智能体应该向前移动,此时就把dirToGo
设置为智能体当前朝向的向量乘以m_ForwardSpeed
,同时将m_KickPower
设置为1,表示智能体将踢球;如果forwardAxis
的值为2,表示智能体应该向后移动,此时就把dirToGo
设置为智能体当前朝向的相反向量乘以m_ForwardSpeed
。如果forwardAxis
的值为其他数字,则不进行任何操作,即智能体不进行前后移动。
1.4.4 switch (rightAxis)
switch (rightAxis)
{
case 1:
dirToGo = transform.right * m_LateralSpeed;
break;
case 2:
dirToGo = transform.right * -m_LateralSpeed;
break;
}
这里的代码处理了机器人的左右移动,根据神经网络输出的rightAxis动作值来决定机器人往左或往右移动,具体来说:
- 当 rightAxis 等于 1 时,机器人往右移动;
- 当 rightAxis 等于 2 时,机器人往左移动;
- 其他情况,机器人不进行左右移动。
其中,dirToGo 变量保存了机器人的移动方向,当 rightAxis 等于 1 时,机器人向右移动,此时 dirToGo 保存的是机器人的右侧方向(transform.right),并乘以 m_LateralSpeed,代表机器人的侧向移动速度;当 rightAxis 等于 2 时,机器人向左移动,此时 dirToGo 保存的是机器人的左侧方向(transform.right 取反),并乘以 m_LateralSpeed,代表机器人的侧向移动速度。
1.4.5 switch (rotateAxis)
switch (rotateAxis)
{
case 1:
rotateDir = transform.up * -1f;
break;
case 2:
rotateDir = transform.up * 1f;
break;
}
这段代码实现了旋转方向的处理,其中 rotateAxis
表示神经网络输出的关于旋转方向的动作,可以取值为 1 或 2。当 rotateAxis
的值为 1 时,表示向左旋转;当 rotateAxis
的值为 2 时,表示向右旋转。然后根据 rotateDir
的值对球员进行旋转。
问:transform.up ?
transform.up
是Transform
组件的一个属性,返回该对象相对于世界坐标系的上方向量,它返回的是一个向量,即从该游戏对象的当前位置向上的单位向量。通常是(0,1,0),在默认情况下,上方向向量的y轴分量为1,x轴和z轴分量都为0。。这里的transform
是指MonoBehaviour
的一个成员变量,它代表了该脚本所挂载的物体的变换组件。当我们对这个物体进行旋转时,它的上方向也会发生相应变化。
在此代码中,我们将 rotateDir
的值设置为 transform.up * -1f
,这意味着代理将绕其垂直于地面的轴(即y轴或up向量)向左旋转。
1.4.6 transform.Rotate(rotateDir, Time.deltaTime * 100f);
transform.Rotate(rotateDir, Time.deltaTime * 100f);
agentRb.AddForce(dirToGo * m_SoccerSettings.agentRunSpeed,
ForceMode.VelocityChange);
这段代码中,transform.Rotate()
方法用于改变代理的朝向,rotateDir
表示旋转的方向,Time.deltaTime * 100f
表示旋转的速度。
agentRb.AddForce()
方法用于给代理施加一个力,dirToGo * m_SoccerSettings.agentRunSpeed
表示力的方向和大小,m_SoccerSettings.agentRunSpeed
是配置的代理奔跑速度,ForceMode.VelocityChange
表示施加力的模式,这个模式会使代理立刻改变速度,而不是逐渐加速。
问:Time.deltaTime * 100f ?
Time.deltaTime
是每帧之间的时间差,表示上一帧到当前帧所经过的时间。因此,Time.deltaTime * 100f
就是将当前帧的时间差转化为秒,再乘以100,表示每秒钟旋转的角度。这里用来控制代理的旋转速度。
Time.deltaTime
是 Unity 引擎中的一个变量,表示上一帧和当前帧之间的时间间隔,以秒为单位。这个变量的作用是在游戏运行时,不受帧率影响,实现平滑的运动效果。
在这里,Time.deltaTime * 100f
用于控制代理的旋转速度,乘以 100f 是为了加快旋转速度。这个值可以根据具体情况进行调整,以实现合适的运动效果。
问:ForceMode.VelocityChange ?
ForceMode.VelocityChange
是一个枚举类型的值,表示对物体施加力时所使用的模式之一。使用这个模式,物体的速度将被更改为指定的值,而不考虑物体当前的速度。因此,如果物体的速度为10,然后使用ForceMode.VelocityChange
施加一个值为5的力,那么物体的速度将变为5。如果使用ForceMode.Force
施加一个值为5的力,物体的速度将增加5。
1.5 OnActionReceived()
public override void OnActionReceived(ActionBuffers actionBuffers)
{
// 如果位置是门将,就给予奖励
if (position == Position.Goalie)
{
AddReward(m_Existential);
}
// 如果位置是前锋,就给予惩罚
else if (position == Position.Striker)
{
AddReward(-m_Existential);
}
// 根据神经网络输出的动作移动代理
MoveAgent(actionBuffers.DiscreteActions);
}
这段代码是ML-Agents环境中Agent类的OnActionReceived方法的实现,它是Agent每一步接收到来自Brain的动作之后执行的函数。在这个实现中,首先根据当前代理的位置(守门员或前锋)增加或减少一个额外奖励(Existential bonus/penalty),这个奖励的大小由类成员变量m_Existential
决定。然后,调用MoveAgent方法,将接收到的动作传递给它,使代理根据动作执行相应的移动。
1.5.1 public override void OnActionReceived(ActionBuffers actionBuffers)
public override void OnActionReceived(ActionBuffers actionBuffers)
{
}
这行代码是Unity ML-Agents中的Agent类的一个重要方法,代表着当Agent接收到动作时要执行的操作。它接受一个ActionBuffers类型的参数actionBuffers,表示来自神经网络输出的动作信息。在这个方法中,我们可以对接收到的动作进行解析,例如将连续动作值转换为离散动作值,然后调用MoveAgent方法实现Agent的移动和操作。此外,我们还可以在这个方法中实现对Agent的奖励或惩罚。
1.5.2 AddReward(m_Existential)
AddReward(m_Existential)
AddReward(m_Existential)
是一个强化学习中的方法,用于为智能体(agent)分配奖励。在这个特定的例子中,如果智能体的位置为门将(goalie),则会给它分配一个额外的生存奖励(existential bonus)。如果智能体的位置为前锋(striker),则会给它分配一个相反的生存惩罚(existential penalty)
1.5.3 MoveAgent(actionBuffers.DiscreteActions)
MoveAgent(actionBuffers.DiscreteActions);
MoveAgent(actionBuffers.DiscreteActions)
是一个自定义的函数,用于控制游戏中代理的运动。在这个函数中,传入的参数是代理接收到的动作动作向量,函数会解析该向量并转换成代理的运动。
actionBuffers.DiscreteActions
是代理接收到的离散动作向量,由一些离散的动作组成,例如代理向前移动、向后移动、向左移动、向右移动、转身等。在该函数中,我们将离散动作向量解析为各个动作的具体数值,然后将其作为参数传递给 MoveAgent
函数,控制代理的运动。
1.6 Heuristic()方法
public override void Heuristic(in ActionBuffers actionsOut)
{
var discreteActionsOut = actionsOut.DiscreteActions;
// 获取 ActionBuffers 中的 DiscreteActions
//forward
if (Input.GetKey(KeyCode.W))
{
// 当 W 键按下时,前进方向的动作值设为 1
discreteActionsOut[0] = 1;
}
if (Input.GetKey(KeyCode.S))
{
// 当 S 键按下时,前进方向的动作值设为 2
discreteActionsOut[0] = 2;
}
//rotate
if (Input.GetKey(KeyCode.A))
{
// 当 A 键按下时,旋转方向的动作值设为 1,即向左旋转
discreteActionsOut[2] = 1;
}
if (Input.GetKey(KeyCode.D))
{
// 当 D 键按下时,旋转方向的动作值设为 2,即向右旋转
discreteActionsOut[2] = 2;
}
//right
if (Input.GetKey(KeyCode.E))
{
// 当 E 键按下时,右侧方向的动作值设为 1
discreteActionsOut[1] = 1;
}
if (Input.GetKey(KeyCode.Q))
{
// 当 Q 键按下时,右侧方向的动作值设为 2
discreteActionsOut[1] = 2;
}
}
1.6.1 public override void Heuristic(in ActionBuffers actionsOut)
public override void Heuristic(in ActionBuffers actionsOut)
{
}
这行代码是在Unity ML-Agents中的Agent类中的一个方法,用于实现一种简单的控制方式,该方式称为"启发式控制"。在每个决策步骤中,Unity ML-Agents会调用Heuristic()方法,将决策的结果传递给Agent的DiscreteActionBuffer。Heuristic()方法的作用是在没有神经网络或其他学习算法的情况下,手动为Agent提供决策。通常,这种方式用于测试或调试Agent,以确保它的行为符合期望。在该方法中,根据玩家的输入(按下哪些按键),手动将对应的动作值存储到输出的DiscreteActionBuffer中。
ActionBuffers
是一个包含智能体动作的数据结构,通常包含一个或多个动作。在ML-Agents中,动作可以是连续的(例如控制机器人的速度和方向),也可以是离散的(例如控制机器人向前走还是向后走)。在public override void Heuristic(in ActionBuffers actionsOut)
方法中,actionsOut
是输出参数,表示你需要生成的动作。在Heuristic Policy中,人类玩家控制智能体的行为,因此该方法用于将人类输入转换为智能体动作。
1.6.2 var discreteActionsOut = actionsOut.DiscreteActions
var discreteActionsOut = actionsOut.DiscreteActions;
这行代码声明了一个离散动作缓存数组 discreteActionsOut
,从 actionsOut
中获取。ActionBuffers
中包含多个类型的缓存数组(例如连续、离散动作等),而这里只使用了离散动作缓存数组。该缓存数组用于保存智能体的离散动作。
1.6.3 Input.GetKey(KeyCode.W)
//forward
if (Input.GetKey(KeyCode.W))
{
discreteActionsOut[0] = 1;
}
if (Input.GetKey(KeyCode.S))
{
discreteActionsOut[0] = 2;
}
//rotate
if (Input.GetKey(KeyCode.A))
{
discreteActionsOut[2] = 1;
}
if (Input.GetKey(KeyCode.D))
{
discreteActionsOut[2] = 2;
}
//right
if (Input.GetKey(KeyCode.E))
{
discreteActionsOut[1] = 1;
}
if (Input.GetKey(KeyCode.Q))
{
discreteActionsOut[1] = 2;
}
}
这段代码是实现了一个基于键盘输入的 Heuristic 控制方法。当按下 W 键时,将动作向量的第一项设为 1,表示代理向前移动。当按下 S 键时,将动作向量的第一项设为 2,表示代理向后移动。当按下 A 键时,将动作向量的第三项设为 1,表示代理向左旋转。当按下 D 键时,将动作向量的第三项设为 2,表示代理向右旋转。当按下 E 键时,将动作向量的第二项设为 1,表示代理向右移动。当按下 Q 键时,将动作向量的第二项设为 2,表示代理向左移动。
Input.GetKey(KeyCode.W)
Input.GetKey(KeyCode.W)
是一个 Unity 引擎的输入函数,用于检测当前是否按下了键盘上的 W 键。如果按下了 W 键,该函数返回 true,否则返回 false。在这里,我们将这个函数用于判断玩家是否想让代理前进。
问:Input.GetKey(KeyCode.W)
和Input.GetKeyDown(KeyCode.W) ?
二者是不同的函数,它们的区别在于:
Input.GetKeyDown(KeyCode.W)
只会在玩家按下W键的那一帧返回true
,在之后的帧都会返回false
,也就是只检测到一次按键事件。Input.GetKey(KeyCode.W)
在玩家按下W键时返回true
,在之后每一帧都会返回true
,也就是检测到按键一直被按下。
因此,Input.GetKey(KeyCode.W)
适用于需要持续响应按键的情况,例如玩家控制移动或持续射击等,而Input.GetKeyDown(KeyCode.W)
则适用于需要检测按键事件的情况,例如玩家按下键盘上的某个键启动某个事件。
1.6.4 discreteActionsOut[0] = 1
discreteActionsOut[0] = 1;
discreteActionsOut[0] = 1;
表示将第一个离散动作的取值设置为 1。
在 Unity ML-Agents 中,智能体输出的行动是离散或连续的值,根据不同的智能体和任务,输出可能有不同的数量和类型的行动。在这个特定的智能体中,有 3 个离散行动:前进、向右移动和旋转。因此,discreteActionsOut
是一个长度为 3 的一维数组,discreteActionsOut[0]
表示前进行动,discreteActionsOut[1]
表示向右移动行动,discreteActionsOut[2]
表示旋转行动。将 discreteActionsOut[0]
设置为 1,表示智能体将执行前进的行动。
1.7 OnCollisionEnter()
void OnCollisionEnter(Collision c)
{
var force = k_Power * m_KickPower;
if (position == Position.Goalie)
{
force = k_Power;
}
if (c.gameObject.CompareTag("ball"))
{
AddReward(.2f * m_BallTouch);
var dir = c.contacts[0].point - transform.position;
dir = dir.normalized;
c.gameObject.GetComponent<Rigidbody>().AddForce(dir * force);
}
}
这段代码是一个在Agent和球碰撞时触发的回调函数,函数内部会检查碰撞的对象是否为球,如果是,则会给Agent增加一个奖励,并将一定的力量施加到球上使其飞出去。
具体来说,函数中首先根据代理所处的位置来计算施加的力量大小,对于守门员,它只会施加最小力量,而对于其他位置的代理,力量大小为m_KickPower与一个常量k_Power的乘积。接着函数会计算出施加力量的方向,这里是从碰撞点指向代理的方向。最后,函数使用AddForce()方法将力量施加到球上,并增加一个奖励,表示代理与球进行了互动。
当球与脚本所在游戏对象相撞时,这段代码会被触发。首先,它计算出一个力量值,该值是常数k_Power和当前脚本实例的m_KickPower之积。如果脚本实例的position是Goalie,那么力量值会被设为一个常数k_Power。
接着,这段代码判断撞击的游戏对象是否是标签为"ball"的球体。如果是,那么它会给与这个智能体一定的奖励值(.2f * m_BallTouch)。然后,它计算出一个方向,该方向指向球与脚本所在游戏对象相撞的碰撞点。这个方向被规范化,乘以之前计算出的力量值,作为球体受到的力。最后,AddForce方法被调用,将力应用到球体的刚体上,使它开始运动。
1.7.1 OnCollisionEnter()
OnCollisionEnter
是 Unity 引擎的一个函数,用于检测游戏对象与其他游戏对象之间是否发生了碰撞。当游戏对象发生碰撞时,OnCollisionEnter
函数会自动被调用,可以在其中编写处理碰撞的逻辑。具体来说,该函数会传递一个 Collision
类型的参数,包含了与碰撞有关的信息,例如碰撞的对象、碰撞点、碰撞力等等。开发者可以利用这些信息来处理碰撞事件,例如修改游戏对象的状态、播放音效、添加得分等等。
在Unity中,当两个物体发生碰撞时,将触发OnCollisionEnter
方法,其中Collision
表示与当前物体发生碰撞的碰撞体,而c
则是开发者为该碰撞体指定的变量名。在这个方法中,开发者可以通过c
变量来获取有关碰撞体的信息并对其进行相应的处理。
1.7.2 var force = k_Power * m_KickPower
var force = k_Power * m_KickPower;
这行代码定义了一个名为 "force" 的变量,它的值是 "k_Power" 乘以 "m_KickPower" 的结果。 "k_Power" 和 "m_KickPower" 可能是在其他地方定义的常量或变量,因此需要查看完整的代码才能确定它们的值。
1.7.3 position == Position.Goalie
if (position == Position.Goalie)
{
force = k_Power;
}
如果当前角色的位置是守门员(goalie),则将 force
设为一个常量 k_Power
,否则保持之前的计算方式: force = k_Power * m_KickPower
。这里的 k_Power
和 m_KickPower
可能是用于计算球员踢球力量的一些参数或常量,具体取决于代码中的定义。
1.7.4 c.gameObject.CompareTag("ball")
if (c.gameObject.CompareTag("ball"))
{
AddReward(.2f * m_BallTouch);
var dir = c.contacts[0].point - transform.position;
dir = dir.normalized;
c.gameObject.GetComponent<Rigidbody>().AddForce(dir * force);
}
这段代码的作用是处理碰撞事件,当该游戏对象碰撞到tag为“ball”的游戏对象时,它会对“ball”的物理组件添加一个力以让它弹开,并且根据设置的参数计算奖励值。具体来说:
AddReward(.2f * m_BallTouch)
计算奖励值,这个奖励值取决于变量m_BallTouch
,表示球被踢了多少次。var dir = c.contacts[0].point - transform.position;
计算碰撞点和当前游戏对象的位置之间的向量。dir = dir.normalized;
将向量归一化,以便之后可以将其应用于球的物理力。c.gameObject.GetComponent<Rigidbody>().AddForce(dir * force);
对球的物理组件添加一个力以便将其弹开,这个力的大小是dir * force
,其中dir
是之前计算的向量,force
则根据之前的代码设置。
问:m_BallTouch ?
m_BallTouch是一个数值,表示球员与球接触时可以获得的奖励分值。在这段代码中,如果检测到球员与球接触,就会给予一定分值的奖励。这个分值是通过球员的m_BallTouch值乘以一个常数0.2f得到的。
1.7.5 var dir = c.contacts[0].point - transform.position
var dir = c.contacts[0].point - transform.position;
这行代码的作用是计算球与物体之间的接触点,并计算球的移动方向。它通过获取碰撞的接触点来确定球与物体的碰撞位置,然后从物体的位置中减去接触点的位置来计算球的移动方向。
这行代码的作用是计算出球与当前对象(通常是足球场上的球员)的接触点之间的向量,用于后续的球踢出力量计算。c.contacts[0].point
是球与当前对象接触的点,transform.position
是当前对象的位置,相减得到的是一个从当前对象指向接触点的向量。
问:c.contacts[0].point ?
c.contacts
返回的是所有碰撞点的信息,是一个数组,c.contacts[0]
获取了第一个碰撞点的信息,point
属性返回的是碰撞点的位置。所以 c.contacts[0].point
就是获取第一个碰撞点的位置。
问:c.contacts ?
c.contacts
是一个包含与碰撞相关的所有接触点信息的数组。每个接触点包含位置、法线和碰撞的两个物体等信息。在这里,我们使用c.contacts[0]
获取第一个接触点的信息。
1.7.6 dir = dir.normalized
dir = dir.normalized;
这行代码是将 dir
向量进行单位化,即将它转化为一个长度为1的向量,这样做的目的是使得后续对这个向量的操作更方便和准确。对于一个非零向量,将其单位化后就变成了指向相同方向的长度为1的向量。
问:单位化和归一化?
在计算机科学中,归一化和单位化通常指对向量进行操作。
归一化意味着将向量缩放到单位长度(即长度为1),而不改变其方向。可以通过将向量除以其长度来实现归一化。
单位化(也称为标准化)是指将向量转换为单位向量,其长度为1。单位化可以通过将向量除以其长度实现。
在实践中,这两个术语通常可以交替使用,具体取决于特定领域或应用程序的惯例。
1.7.7 c.gameObject.GetComponent<Rigidbody>().AddForce(dir * force)
c.gameObject.GetComponent<Rigidbody>().AddForce(dir * force)
这行代码是为了给球施加一个力,让它飞出去。dir * force
计算得到一个方向和力的乘积向量,作为施加到球上的力的大小和方向。dir
是球和碰撞物体接触点之间的方向向量,force
是计算出来的施加到球上的力的大小,这样就能保证球以正确的方向和力的大小飞出去了。这里用了 GetComponent<Rigidbody>()
来获取球的刚体组件,然后调用 AddForce()
方法来施加力。
1.8 OnEpisodeBegin()
public override void OnEpisodeBegin()
{
m_BallTouch = m_ResetParams.GetWithDefault("ball_touch", 0);
}
这段代码是一个Unity ML-Agents中的重置函数,用于在新的episode开始时初始化/重置环境状态。
在这个函数中,首先通过m_ResetParams.GetWithDefault()
方法获取了名为"ball_touch"的环境参数的值,如果没有设置则默认为0。然后将这个值赋给了m_BallTouch
变量,以便在后续训练过程中使用。
问:m_ResetParams.GetWithDefault()方法 ?
m_ResetParams
是一个ResetParameters
类型的变量,它代表了在Unity编辑器中针对agent设置的重置参数。GetWithDefault()
方法是ResetParameters
类中的一个方法,它用于获取指定名称的参数值,如果参数不存在则返回默认值。在AgentSoccer
类中,这个方法用于获取名为ball_touch
的重置参数值。如果在Unity编辑器中没有为该参数指定特定值,则会使用默认值0。
1.8.1 OnEpisodeBegin()
OnEpisodeBegin()
是Unity ML-Agent的一个方法,当agent开始新的episode时被调用。在这个方法中,通常会重置agent的状态,以便在下一个episode中重新开始。在这个例子中,OnEpisodeBegin()
方法会通过m_ResetParams
获取到ball_touch
参数的值,并将其赋值给m_BallTouch
。这个参数表示每个episode开始时球的初始接触次数,以确保训练的可重现性和一致性。
1.8.2 m_BallTouch = m_ResetParams.GetWithDefault("ball_touch", 0)
m_BallTouch = m_ResetParams.GetWithDefault("ball_touch", 0);
这行代码的作用是在每次 episode 开始时,从 reset 参数中获取键为 "ball_touch" 的值并将其存储在 m_BallTouch 变量中。如果 reset 参数中没有 "ball_touch" 键,则默认值为 0。在这个场景中,"ball_touch" 参数用于指定每次球被踢中的奖励值。