Unity 机器学习代理工具包 (ML-Agents) 是一个开源项目,它使游戏和模拟能够作为训练智能代理的环境。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 

1180 行
44 KiB

using System.Collections.Generic;
using System.Linq;
using Google.Protobuf;
using MLAgents.CommunicatorObjects;
using UnityEngine;
namespace MLAgents
{
/// <summary>
/// Struct that contains all the information for an Agent, including its
/// observations, actions and current status, that is sent to the Brain.
/// </summary>
public struct AgentInfo
{
/// <summary>
/// Most recent agent vector (i.e. numeric) observation.
/// </summary>
public List<float> vectorObservation;
/// <summary>
/// The previous agent vector observations, stacked. The length of the
/// history (i.e. number of vector observations to stack) is specified
/// in the Brain parameters.
/// </summary>
public List<float> stackedVectorObservation;
/// <summary>
/// Most recent agent camera (i.e. texture) observation.
/// </summary>
public List<Texture2D> visualObservations;
/// <summary>
/// Most recent text observation.
/// </summary>
public string textObservation;
/// <summary>
/// Keeps track of the last vector action taken by the Brain.
/// </summary>
public float[] storedVectorActions;
/// <summary>
/// Keeps track of the last text action taken by the Brain.
/// </summary>
public string storedTextActions;
/// <summary>
/// For discrete control, specifies the actions that the agent cannot take. Is true if
/// the action is masked.
/// </summary>
public bool[] actionMasks;
/// <summary>
/// Used by the Trainer to store information about the agent. This data
/// structure is not consumed or modified by the agent directly, they are
/// just the owners of their trainier's memory. Currently, however, the
/// size of the memory is in the Brain properties.
/// </summary>
public List<float> memories;
/// <summary>
/// Current agent reward.
/// </summary>
public float reward;
/// <summary>
/// Whether the agent is done or not.
/// </summary>
public bool done;
/// <summary>
/// Whether the agent has reached its max step count for this episode.
/// </summary>
public bool maxStepReached;
/// <summary>
/// Unique identifier each agent receives at initialization. It is used
/// to separate between different agents in the environment.
/// </summary>
public int id;
/// <summary>
/// User-customizable object for sending structured output from Unity to Python in response
/// to an action in addition to a scalar reward.
/// </summary>
public CustomObservation customObservation;
/// <summary>
/// Converts a AgentInfo to a protobuffer generated AgentInfoProto
/// </summary>
/// <returns>The protobuf verison of the AgentInfo.</returns>
/// <param name="info">The AgentInfo to convert.</param>
public CommunicatorObjects.AgentInfoProto ToProto()
{
var agentInfoProto = new CommunicatorObjects.AgentInfoProto
{
StackedVectorObservation = {stackedVectorObservation},
StoredVectorActions = {storedVectorActions},
StoredTextActions = storedTextActions,
TextObservation = textObservation,
Reward = reward,
MaxStepReached = maxStepReached,
Done = done,
Id = id,
CustomObservation = customObservation
};
if (memories != null)
{
agentInfoProto.Memories.Add(memories);
}
if (actionMasks != null)
{
agentInfoProto.ActionMask.AddRange(actionMasks);
}
foreach (Texture2D obs in visualObservations)
{
agentInfoProto.VisualObservations.Add(
ByteString.CopyFrom(obs.EncodeToPNG())
);
}
return agentInfoProto;
}
}
/// <summary>
/// Struct that contains the action information sent from the Brain to the
/// Agent.
/// </summary>
public struct AgentAction
{
public float[] vectorActions;
public string textActions;
public List<float> memories;
public float value;
public CommunicatorObjects.CustomAction customAction;
}
/// <summary>
/// Struct that contains all the Agent-specific parameters provided in the
/// Editor. This excludes the Brain linked to the Agent since it can be
/// modified programmatically.
/// </summary>
[System.Serializable]
public class AgentParameters
{
/// <summary>
/// The list of the Camera GameObjects the agent uses for visual
/// observations.
/// </summary>
public List<Camera> agentCameras = new List<Camera>();
/// <summary>
/// The list of the RenderTextures the agent uses for visual
/// observations.
/// </summary>
public List<RenderTexture> agentRenderTextures = new List<RenderTexture>();
/// <summary>
/// The maximum number of steps the agent takes before being done.
/// </summary>
/// <remarks>
/// If set to 0, the agent can only be set to done programmatically (or
/// when the Academy is done).
/// If set to any positive integer, the agent will be set to done after
/// that many steps. Note that setting the max step to a value greater
/// than the academy max step value renders it useless.
/// </remarks>
public int maxStep;
/// <summary>
/// Determines the behaviour of the agent when done.
/// </summary>
/// <remarks>
/// If true, the agent will reset when done and start a new episode.
/// Otherwise, the agent will remain done and its behavior will be
/// dictated by the AgentOnDone method.
/// </remarks>
public bool resetOnDone = true;
/// <summary>
/// Whether to enable On Demand Decisions or make a decision at
/// every step.
/// </summary>
public bool onDemandDecision;
/// <summary>
/// Number of actions between decisions (used when On Demand Decisions
/// is turned off).
/// </summary>
public int numberOfActionsBetweenDecisions;
}
/// <summary>
/// Agent Monobehavior class that is attached to a Unity GameObject, making it
/// an Agent. An agent produces observations and takes actions in the
/// environment. Observations are determined by the cameras attached
/// to the agent in addition to the vector observations implemented by the
/// user in <see cref="CollectObservations"/>. On the other hand, actions
/// are determined by decisions produced by a linked Brain. Currently, this
/// class is expected to be extended to implement the desired agent behavior.
/// </summary>
/// <remarks>
/// Simply speaking, an agent roams through an environment and at each step
/// of the environment extracts its current observation, sends them to its
/// linked brain and in return receives an action from its brain. In practice,
/// however, an agent need not send its observation at every step since very
/// little may have changed between sucessive steps. Currently, how often an
/// agent updates its brain with a fresh observation is determined by the
/// Academy.
///
/// At any step, an agent may be considered <see cref="done"/>.
/// This could occur due to a variety of reasons:
/// - The agent reached an end state within its environment.
/// - The agent reached the maximum # of steps (i.e. timed out).
/// - The academy reached the maximum # of steps (forced agent to be done).
///
/// Here, an agent reaches an end state if it completes its task successfully
/// or somehow fails along the way. In the case where an agent is done before
/// the academy, it either resets and restarts, or just lingers until the
/// academy is done.
///
/// An important note regarding steps and episodes is due. Here, an agent step
/// corresponds to an academy step, which also corresponds to Unity
/// environment step (i.e. each FixedUpdate call). This is not the case for
/// episodes. The academy controls the global episode count and each agent
/// controls its own local episode count and can reset and start a new local
/// episode independently (based on its own experience). Thus an academy
/// (global) episode can be viewed as the upper-bound on an agents episode
/// length and that within a single global episode, an agent may have completed
/// multiple local episodes. Consequently, if an agent max step is
/// set to a value larger than the academy max steps value, then the academy
/// value takes precedence (since the agent max step will never be reached).
///
/// Lastly, note that at any step the brain linked to the agent is allowed to
/// change programmatically with <see cref="GiveBrain"/>.
///
/// Implementation-wise, it is required that this class is extended and the
/// virtual methods overridden. For sample implementations of agent behavior,
/// see the Examples/ directory within this Unity project.
/// </remarks>
[HelpURL("https://github.com/Unity-Technologies/ml-agents/blob/master/" +
"docs/Learning-Environment-Design-Agents.md")]
[System.Serializable]
public abstract class Agent : MonoBehaviour
{
/// <summary>
/// The Brain attached to this agent. A brain can be attached either
/// directly from the Editor through AgentEditor or
/// programmatically through <see cref="GiveBrain"/>. It is OK for an agent
/// to not have a brain, as long as no decision is requested.
/// </summary>
[HideInInspector] public Brain brain;
/// <summary>
/// Agent parameters specified within the Editor via AgentEditor.
/// </summary>
[HideInInspector] public AgentParameters agentParameters;
/// Current Agent information (message sent to Brain).
AgentInfo info;
/// Current Agent action (message sent from Brain).
AgentAction action;
/// Represents the reward the agent accumulated during the current step.
/// It is reset to 0 at the beginning of every step.
/// Should be set to a positive value when the agent performs a "good"
/// action that we wish to reinforce/reward, and set to a negative value
/// when the agent performs a "bad" action that we wish to punish/deter.
/// Additionally, the magnitude of the reward should not exceed 1.0
float reward;
/// Keeps track of the cumulative reward in this episode.
float cumulativeReward;
/// Whether or not the agent requests an action.
bool requestAction;
/// Whether or not the agent requests a decision.
bool requestDecision;
/// Whether or not the agent has completed the episode. This may be due
/// to either reaching a success or fail state, or reaching the maximum
/// number of steps (i.e. timing out).
bool done;
/// Whether or not the agent reached the maximum number of steps.
bool maxStepReached;
/// Keeps track of the number of steps taken by the agent in this episode.
/// Note that this value is different for each agent, and may not overlap
/// with the step counter in the Academy, since agents reset based on
/// their own experience.
int stepCount;
/// Flag to signify that an agent has been reset but the fact that it is
/// done has not been communicated (required for On Demand Decisions).
bool hasAlreadyReset;
/// Flag to signify that an agent is done and should not reset until
/// the fact that it is done has been communicated.
bool terminate;
/// Unique identifier each agent receives at initialization. It is used
/// to separate between different agents in the environment.
int id;
/// Keeps track of the actions that are masked at each step.
private ActionMasker actionMasker;
/// Array of Texture2D used to render to from render buffer before
/// transforming into float tensor.
Texture2D[] textureArray;
/// <summary>
/// Demonstration recorder.
/// </summary>
private DemonstrationRecorder recorder;
/// Monobehavior function that is called when the attached GameObject
/// becomes enabled or active.
void OnEnable()
{
var textureCount = agentParameters.agentCameras.Count+agentParameters.agentRenderTextures.Count;
textureArray = new Texture2D[textureCount];
for (int i = 0; i < textureCount; i++)
{
textureArray[i] = new Texture2D(1, 1, TextureFormat.RGB24, false);
}
id = gameObject.GetInstanceID();
Academy academy = Object.FindObjectOfType<Academy>() as Academy;
OnEnableHelper(academy);
recorder = GetComponent<DemonstrationRecorder>();
}
/// Helper method for the <see cref="OnEnable"/> event, created to
/// facilitate testing.
void OnEnableHelper(Academy academy)
{
info = new AgentInfo();
action = new AgentAction();
if (academy == null)
{
throw new UnityAgentsException(
"No Academy Component could be found in the scene.");
}
academy.AgentSetStatus += SetStatus;
academy.AgentResetIfDone += ResetIfDone;
academy.AgentSendState += SendInfo;
academy.AgentAct += AgentStep;
academy.AgentForceReset += _AgentReset;
if (brain != null)
{
ResetData();
}
else
{
Debug.Log(
string.Format(
"The Agent component attached to the " +
"GameObject {0} was initialized without a brain.",
gameObject.name));
}
InitializeAgent();
}
/// Monobehavior function that is called when the attached GameObject
/// becomes disabled or inactive.
void OnDisable()
{
Academy academy = Object.FindObjectOfType<Academy>() as Academy;
if (academy != null)
{
academy.AgentSetStatus -= SetStatus;
academy.AgentResetIfDone -= ResetIfDone;
academy.AgentSendState -= SendInfo;
academy.AgentAct -= AgentStep;
academy.AgentForceReset -= _AgentReset;
}
}
/// <summary>
/// Updates the Brain for the agent. Any brain currently assigned to the
/// agent will be replaced with the provided one.
/// </summary>
/// <remarks>
/// The agent unsubscribes from its current brain (if it has one) and
/// subscribes to the provided brain. This enables contextual brains, that
/// is, updating the behaviour (hence brain) of the agent depending on
/// the context of the game. For example, we may utilize one (wandering)
/// brain when an agent is randomly exploring an open world, but switch
/// to another (fighting) brain when it comes into contact with an enemy.
/// </remarks>
/// <param name="brain">New brain to subscribe this agent to</param>
public void GiveBrain(Brain brain)
{
this.brain = brain;
ResetData();
}
/// <summary>
/// Returns the current step counter (within the current epside).
/// </summary>
/// <returns>
/// Current episode number.
/// </returns>
public int GetStepCount()
{
return stepCount;
}
/// <summary>
/// Resets the step reward and possibly the episode reward for the agent.
/// </summary>
public void ResetReward()
{
reward = 0f;
if (done)
{
cumulativeReward = 0f;
}
}
/// <summary>
/// Overrides the current step reward of the agent and updates the episode
/// reward accordingly.
/// </summary>
/// <param name="reward">The new value of the reward.</param>
public void SetReward(float reward)
{
cumulativeReward += (reward - this.reward);
this.reward = reward;
}
/// <summary>
/// Increments the step and episode rewards by the provided value.
/// </summary>
/// <param name="increment">Incremental reward value.</param>
public void AddReward(float increment)
{
reward += increment;
cumulativeReward += increment;
}
/// <summary>
/// Retrieves the step reward for the Agent.
/// </summary>
/// <returns>The step reward.</returns>
public float GetReward()
{
return reward;
}
/// <summary>
/// Retrieves the episode reward for the Agent.
/// </summary>
/// <returns>The episode reward.</returns>
public float GetCumulativeReward()
{
return cumulativeReward;
}
/// <summary>
/// Sets the done flag to true.
/// </summary>
public void Done()
{
done = true;
}
/// <summary>
/// Is called when the agent must request the brain for a new decision.
/// </summary>
public void RequestDecision()
{
requestDecision = true;
RequestAction();
}
/// <summary>
/// Is called then the agent must perform a new action.
/// </summary>
public void RequestAction()
{
requestAction = true;
}
/// <summary>
/// Indicates if the agent has reached his maximum number of steps.
/// </summary>
/// <returns>
/// <c>true</c>, if max step reached was reached, <c>false</c> otherwise.
/// </returns>
public bool IsMaxStepReached()
{
return maxStepReached;
}
/// <summary>
/// Indicates if the agent is done
/// </summary>
/// <returns>
/// <c>true</c>, if the agent is done, <c>false</c> otherwise.
/// </returns>
public bool IsDone()
{
return done;
}
/// Helper function that resets all the data structures associated with
/// the agent. Typically used when the agent is being initialized or reset
/// at the end of an episode.
void ResetData()
{
if (brain == null)
{
return;
}
BrainParameters param = brain.brainParameters;
actionMasker = new ActionMasker(param);
if (param.vectorActionSpaceType == SpaceType.continuous)
{
action.vectorActions = new float[param.vectorActionSize[0]];
info.storedVectorActions = new float[param.vectorActionSize[0]];
}
else
{
action.vectorActions = new float[param.vectorActionSize.Length];
info.storedVectorActions = new float[param.vectorActionSize.Length];
}
if (info.textObservation == null)
info.textObservation = "";
action.textActions = "";
info.memories = new List<float>();
action.memories = new List<float>();
info.vectorObservation =
new List<float>(param.vectorObservationSize);
info.stackedVectorObservation =
new List<float>(param.vectorObservationSize
* brain.brainParameters.numStackedVectorObservations);
info.stackedVectorObservation.AddRange(
new float[param.vectorObservationSize
* param.numStackedVectorObservations]);
info.visualObservations = new List<Texture2D>();
info.customObservation = null;
}
/// <summary>
/// Initializes the agent, called once when the agent is enabled. Can be
/// left empty if there is no special, unique set-up behavior for the
/// agent.
/// </summary>
/// <remarks>
/// One sample use is to store local references to other objects in the
/// scene which would facilitate computing this agents observation.
/// </remarks>
public virtual void InitializeAgent()
{
}
/// <summary>
/// Sends the Agent info to the linked Brain.
/// </summary>
void SendInfoToBrain()
{
if (brain == null)
{
return;
}
info.memories = action.memories;
info.storedVectorActions = action.vectorActions;
info.storedTextActions = action.textActions;
info.vectorObservation.Clear();
actionMasker.ResetMask();
CollectObservations();
info.actionMasks = actionMasker.GetMask();
BrainParameters param = brain.brainParameters;
if (info.vectorObservation.Count != param.vectorObservationSize)
{
throw new UnityAgentsException(string.Format(
"Vector Observation size mismatch between continuous " +
"agent {0} and brain {1}. " +
"Was Expecting {2} but received {3}. ",
gameObject.name, brain.name,
brain.brainParameters.vectorObservationSize,
info.vectorObservation.Count));
}
info.stackedVectorObservation.RemoveRange(
0, param.vectorObservationSize);
info.stackedVectorObservation.AddRange(info.vectorObservation);
info.visualObservations.Clear();
var visualObservationCount = agentParameters.agentCameras.Count+agentParameters.agentRenderTextures.Count;
if (param.cameraResolutions.Length > visualObservationCount)
{
throw new UnityAgentsException(string.Format(
"Not enough cameras/renderTextures for agent {0} : Brain {1} expecting at " +
"least {2} cameras/renderTextures but only {3} were present.",
gameObject.name, brain.name,
brain.brainParameters.cameraResolutions.Length,
visualObservationCount));
}
//First add all cameras
for (int i = 0; i < agentParameters.agentCameras.Count; i++)
{
ObservationToTexture(
agentParameters.agentCameras[i],
param.cameraResolutions[i].width,
param.cameraResolutions[i].height,
ref textureArray[i]);
info.visualObservations.Add(textureArray[i]);
}
//Then add all renderTextures
var camCount = agentParameters.agentCameras.Count;
for (int i = 0; i < agentParameters.agentRenderTextures.Count; i++)
{
ObservationToTexture(
agentParameters.agentRenderTextures[i],
param.cameraResolutions[camCount+i].width,
param.cameraResolutions[camCount+i].height,
ref textureArray[i]);
info.visualObservations.Add(textureArray[i]);
}
info.reward = reward;
info.done = done;
info.maxStepReached = maxStepReached;
info.id = id;
brain.SendState(this, info);
if (recorder != null && recorder.record && Application.isEditor)
{
recorder.WriteExperience(info);
}
info.textObservation = "";
}
/// <summary>
/// Collects the (vector, visual, text) observations of the agent.
/// The agent observation describes the current environment from the
/// perspective of the agent.
/// </summary>
/// <remarks>
/// Simply, an agents observation is any environment information that helps
/// the Agent acheive its goal. For example, for a fighting Agent, its
/// observation could include distances to friends or enemies, or the
/// current level of ammunition at its disposal.
/// Recall that an Agent may attach vector, visual or textual observations.
/// Vector observations are added by calling the provided helper methods:
/// - <see cref="AddVectorObs(int)"/>
/// - <see cref="AddVectorObs(float)"/>
/// - <see cref="AddVectorObs(Vector3)"/>
/// - <see cref="AddVectorObs(Vector2)"/>
/// - <see cref="AddVectorObs(float[])"/>
/// - <see cref="AddVectorObs(List{float})"/>
/// - <see cref="AddVectorObs(Quaternion)"/>
/// - <see cref="AddVectorObs(bool)"/>
/// - <see cref="AddVectorObs(int, int)"/>
/// Depending on your environment, any combination of these helpers can
/// be used. They just need to be used in the exact same order each time
/// this method is called and the resulting size of the vector observation
/// needs to match the vectorObservationSize attribute of the linked Brain.
/// Visual observations are implicitly added from the cameras attached to
/// the Agent.
/// Lastly, textual observations are added using
/// <see cref="SetTextObs(string)"/>.
/// </remarks>
public virtual void CollectObservations()
{
}
/// <summary>
/// Sets an action mask for discrete control agents. When used, the agent will not be
/// able to perform the action passed as argument at the next decision. If no branch is
/// specified, the default branch will be 0. The actionIndex or actionIndices correspond
/// to the action the agent will be unable to perform.
/// </summary>
/// <param name="actionIndices">The indices of the masked actions on branch 0</param>
protected void SetActionMask(IEnumerable<int> actionIndices)
{
actionMasker.SetActionMask(0, actionIndices);
}
/// <summary>
/// Sets an action mask for discrete control agents. When used, the agent will not be
/// able to perform the action passed as argument at the next decision. If no branch is
/// specified, the default branch will be 0. The actionIndex or actionIndices correspond
/// to the action the agent will be unable to perform.
/// </summary>
/// <param name="actionIndex">The index of the masked action on branch 0</param>
protected void SetActionMask(int actionIndex)
{
actionMasker.SetActionMask(0, new int[1] { actionIndex });
}
/// <summary>
/// Sets an action mask for discrete control agents. When used, the agent will not be
/// able to perform the action passed as argument at the next decision. If no branch is
/// specified, the default branch will be 0. The actionIndex or actionIndices correspond
/// to the action the agent will be unable to perform.
/// </summary>
/// <param name="branch">The branch for which the actions will be masked</param>
/// <param name="actionIndex">The index of the masked action</param>
protected void SetActionMask(int branch, int actionIndex)
{
actionMasker.SetActionMask(branch, new int[1] { actionIndex });
}
/// <summary>
/// Modifies an action mask for discrete control agents. When used, the agent will not be
/// able to perform the action passed as argument at the next decision. If no branch is
/// specified, the default branch will be 0. The actionIndex or actionIndices correspond
/// to the action the agent will be unable to perform.
/// </summary>
/// <param name="branch">The branch for which the actions will be masked</param>
/// <param name="actionIndices">The indices of the masked actions</param>
protected void SetActionMask(int branch, IEnumerable<int> actionIndices)
{
actionMasker.SetActionMask(branch, actionIndices);
}
/// <summary>
/// Adds a float observation to the vector observations of the agent.
/// Increases the size of the agents vector observation by 1.
/// </summary>
/// <param name="observation">Observation.</param>
protected void AddVectorObs(float observation)
{
info.vectorObservation.Add(observation);
}
/// <summary>
/// Adds an integer observation to the vector observations of the agent.
/// Increases the size of the agents vector observation by 1.
/// </summary>
/// <param name="observation">Observation.</param>
protected void AddVectorObs(int observation)
{
info.vectorObservation.Add(observation);
}
/// <summary>
/// Adds an Vector3 observation to the vector observations of the agent.
/// Increases the size of the agents vector observation by 3.
/// </summary>
/// <param name="observation">Observation.</param>
protected void AddVectorObs(Vector3 observation)
{
info.vectorObservation.Add(observation.x);
info.vectorObservation.Add(observation.y);
info.vectorObservation.Add(observation.z);
}
/// <summary>
/// Adds an Vector2 observation to the vector observations of the agent.
/// Increases the size of the agents vector observation by 2.
/// </summary>
/// <param name="observation">Observation.</param>
protected void AddVectorObs(Vector2 observation)
{
info.vectorObservation.Add(observation.x);
info.vectorObservation.Add(observation.y);
}
/// <summary>
/// Adds a collection of float observations to the vector observations of the agent.
/// Increases the size of the agents vector observation by size of the collection.
/// </summary>
/// <param name="observation">Observation.</param>
protected void AddVectorObs(IEnumerable<float> observation)
{
info.vectorObservation.AddRange(observation);
}
/// <summary>
/// Adds a quaternion observation to the vector observations of the agent.
/// Increases the size of the agents vector observation by 4.
/// </summary>
/// <param name="observation">Observation.</param>
protected void AddVectorObs(Quaternion observation)
{
info.vectorObservation.Add(observation.x);
info.vectorObservation.Add(observation.y);
info.vectorObservation.Add(observation.z);
info.vectorObservation.Add(observation.w);
}
/// <summary>
/// Adds a boolean observation to the vector observation of the agent.
/// Increases the size of the agent's vector observation by 1.
/// </summary>
/// <param name="observation"></param>
protected void AddVectorObs(bool observation)
{
info.vectorObservation.Add(observation ? 1f : 0f);
}
protected void AddVectorObs(int observation, int range)
{
float[] oneHotVector = new float[range];
oneHotVector[observation] = 1;
info.vectorObservation.AddRange(oneHotVector);
}
/// <summary>
/// Sets the text observation.
/// </summary>
/// <param name="textObservation">The text observation.</param>
public void SetTextObs(string textObservation)
{
info.textObservation = textObservation;
}
/// <summary>
/// Specifies the agent behavior at every step based on the provided
/// action.
/// </summary>
/// <param name="vectorAction">
/// Vector action. Note that for discrete actions, the provided array
/// will be of length 1.
/// </param>
/// <param name="textAction">Text action.</param>
public virtual void AgentAction(float[] vectorAction, string textAction)
{
}
/// <summary>
/// Specifies the agent behavior at every step based on the provided
/// action.
/// </summary>
/// <param name="vectorAction">
/// Vector action. Note that for discrete actions, the provided array
/// will be of length 1.
/// </param>
/// <param name="textAction">Text action.</param>
/// <param name="customAction">
/// A custom action, defined by the user as custom protobuffer message. Useful if the action is hard to encode
/// as either a flat vector or a single string.
/// </param>
public virtual void AgentAction(float[] vectorAction, string textAction, CommunicatorObjects.CustomAction customAction)
{
// We fall back to not using the custom action if the subclassed Agent doesn't override this method.
AgentAction(vectorAction, textAction);
}
/// <summary>
/// Specifies the agent behavior when done and
/// <see cref="AgentParameters.resetOnDone"/> is false. This method can be
/// used to remove the agent from the scene.
/// </summary>
public virtual void AgentOnDone()
{
}
/// <summary>
/// Specifies the agent behavior when being reset, which can be due to
/// the agent or Academy being done (i.e. completion of local or global
/// episode).
/// </summary>
public virtual void AgentReset()
{
}
/// <summary>
/// An internal reset method that updates internal data structures in
/// addition to calling <see cref="AgentReset"/>.
/// </summary>
void _AgentReset()
{
ResetData();
stepCount = 0;
AgentReset();
}
/// <summary>
/// Updates the vector action.
/// </summary>
/// <param name="vectorActions">Vector actions.</param>
public void UpdateVectorAction(float[] vectorActions)
{
action.vectorActions = vectorActions;
}
/// <summary>
/// Updates the memories action.
/// </summary>
/// <param name="memories">Memories.</param>
public void UpdateMemoriesAction(List<float> memories)
{
action.memories = memories;
}
public void AppendMemoriesAction(List<float> memories)
{
action.memories.AddRange(memories);
}
/// <summary>
/// Updates the text action.
/// </summary>
/// <param name="textActions">Text actions.</param>
public void UpdateTextAction(string textActions)
{
action.textActions = textActions;
}
/// <summary>
/// Updates the custom action.
/// </summary>
/// <param name="customAction">Custom action.</param>
public void UpdateCustomAction(CommunicatorObjects.CustomAction customAction)
{
action.customAction = customAction;
}
/// <summary>
/// Updates the value of the agent.
/// </summary>
/// <param name="textActions">Text actions.</param>
public void UpdateValueAction(float value)
{
action.value = value;
}
protected float GetValueEstimate()
{
return action.value;
}
/// <summary>
/// Scales continous action from [-1, 1] to arbitrary range.
/// </summary>
/// <param name="rawAction"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
protected float ScaleAction(float rawAction, float min, float max)
{
var middle = (min + max) / 2;
var range = (max - min) / 2;
return rawAction * range + middle;
}
/// <summary>
/// Sets the status of the agent.
/// </summary>
/// <param name="academyMaxStep">If set to <c>true</c>
/// The agent must set maxStepReached.</param>
/// <param name="academyDone">If set to <c>true</c>
/// The agent must set done.</param>
/// <param name="academyStepCounter">Number of current steps in episode</param>
void SetStatus(bool academyMaxStep, bool academyDone, int academyStepCounter)
{
if (academyDone)
{
academyStepCounter = 0;
}
MakeRequests(academyStepCounter);
if (academyMaxStep)
{
maxStepReached = true;
}
// If the Academy needs to reset, the agent should reset
// even if it reseted recently.
if (academyDone)
{
Done();
hasAlreadyReset = false;
}
}
/// Signals the agent that it must reset if its done flag is set to true.
void ResetIfDone()
{
// If an agent is done, then it will also
// request for a decision and an action
if (IsDone())
{
if (agentParameters.resetOnDone)
{
if (agentParameters.onDemandDecision)
{
if (!hasAlreadyReset)
{
// If event based, the agent can reset as soon
// as it is done
_AgentReset();
hasAlreadyReset = true;
}
}
else if (requestDecision)
{
// If not event based, the agent must wait to request a
// decsion before reseting to keep multiple agents in sync.
_AgentReset();
}
}
else
{
terminate = true;
RequestDecision();
}
}
}
/// <summary>
/// Signals the agent that it must sent its decision to the brain.
/// </summary>
void SendInfo()
{
if (requestDecision)
{
SendInfoToBrain();
ResetReward();
done = false;
maxStepReached = false;
requestDecision = false;
hasAlreadyReset = false;
}
}
/// Used by the brain to make the agent perform a step.
void AgentStep()
{
if (terminate)
{
terminate = false;
ResetReward();
done = false;
maxStepReached = false;
requestDecision = false;
requestAction = false;
hasAlreadyReset = false;
OnDisable();
AgentOnDone();
}
if ((requestAction) && (brain != null))
{
requestAction = false;
AgentAction(action.vectorActions, action.textActions, action.customAction);
}
if ((stepCount >= agentParameters.maxStep)
&& (agentParameters.maxStep > 0))
{
maxStepReached = true;
Done();
}
stepCount += 1;
}
/// <summary>
/// Is called after every step, contains the logic to decide if the agent
/// will request a decision at the next step.
/// </summary>
void MakeRequests(int academyStepCounter)
{
agentParameters.numberOfActionsBetweenDecisions =
Mathf.Max(agentParameters.numberOfActionsBetweenDecisions, 1);
if (!agentParameters.onDemandDecision)
{
RequestAction();
if (academyStepCounter %
agentParameters.numberOfActionsBetweenDecisions == 0)
{
RequestDecision();
}
}
}
/// <summary>
/// Converts a camera and correspinding resolution to a 2D texture.
/// </summary>
/// <returns>The 2D texture.</returns>
/// <param name="obsCamera">Camera.</param>
/// <param name="width">Width of resulting 2D texture.</param>
/// <param name="height">Height of resulting 2D texture.</param>
/// <param name="texture2D">Texture2D to render to.</param>
public static void ObservationToTexture(Camera obsCamera, int width, int height, ref Texture2D texture2D)
{
Rect oldRec = obsCamera.rect;
obsCamera.rect = new Rect(0f, 0f, 1f, 1f);
var depth = 24;
var format = RenderTextureFormat.Default;
var readWrite = RenderTextureReadWrite.Default;
var tempRT =
RenderTexture.GetTemporary(width, height, depth, format, readWrite);
if (width != texture2D.width || height != texture2D.height)
{
texture2D.Resize(width, height);
}
var prevActiveRT = RenderTexture.active;
var prevCameraRT = obsCamera.targetTexture;
// render to offscreen texture (readonly from CPU side)
RenderTexture.active = tempRT;
obsCamera.targetTexture = tempRT;
obsCamera.Render();
texture2D.ReadPixels(new Rect(0, 0, texture2D.width, texture2D.height), 0, 0);
texture2D.Apply();
obsCamera.targetTexture = prevCameraRT;
obsCamera.rect = oldRec;
RenderTexture.active = prevActiveRT;
RenderTexture.ReleaseTemporary(tempRT);
}
/// <summary>
/// Converts a RenderTexture and correspinding resolution to a 2D texture.
/// </summary>
/// <returns>The 2D texture.</returns>
/// <param name="obsTexture">RenderTexture.</param>
/// <param name="width">Width of resulting 2D texture.</param>
/// <param name="height">Height of resulting 2D texture.</param>
/// <param name="texture2D">Texture2D to render to.</param>
public static void ObservationToTexture(RenderTexture obsTexture, int width, int height, ref Texture2D texture2D)
{
if (width != texture2D.width || height != texture2D.height)
{
texture2D.Resize(width, height);
}
if(width != obsTexture.width || height != obsTexture.height)
{
throw new UnityAgentsException(string.Format(
"RenderTexture {0} : width/height is {1}/{2} brain is expecting {3}/{4}.",
obsTexture.name, obsTexture.width, obsTexture.height, width, height));
}
var prevActiveRT = RenderTexture.active;
RenderTexture.active = obsTexture;
texture2D.ReadPixels(new Rect(0, 0, texture2D.width, texture2D.height), 0, 0);
texture2D.Apply();
RenderTexture.active = prevActiveRT;
}
/// <summary>
/// Sets the custom observation for the agent for this episode.
/// </summary>
/// <param name="customObservation">New value of the agent's custom observation.</param>
public void SetCustomObservation(CustomObservation customObservation)
{
info.customObservation = customObservation;
}
}
}