using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Linq; // Class contains all necessary environment parameters // to be defined and sent to external agent #if ENABLE_TENSORFLOW public enum BrainType { Player, Heuristic, External, Internal } #else public enum BrainType { Player, Heuristic, External, } #endif public enum StateType { discrete, continuous } ; /** Only need to be modified in the brain's inpector. * Defines what is the resolution of the camera */ [System.Serializable] public struct resolution { public int width; /**< \brief The width of the observation in pixels */ public int height; /**< \brief The height of the observation in pixels */ public bool blackAndWhite; /**< \brief If true, the image will be in black and white. * If false, it will be in colors RGB */ } /** Should be modified via the Editor Inspector. * Defines brain-specific parameters */ [System.Serializable] public struct BrainParameters { public int stateSize; /**< \brief If continuous : The length of the float vector that represents * the state *
If discrete : The number of possible values the state can take*/ public int actionSize; /**< \brief If continuous : The length of the float vector that represents the action *
If discrete : The number of possible values the action can take*/ public int memorySize; /**< \brief The length of the float vector that holds the memory for the agent */ public resolution[] cameraResolutions; /**<\brief The list of observation resolutions for the brain */ public string[] actionDescriptions; /**< \brief The list of strings describing what the actions correpond to */ public StateType actionSpaceType; /**< \brief Defines if the action is discrete or continuous */ public StateType stateSpaceType; /**< \brief Defines if the state is discrete or continuous */ } /** * Contains all high-level Brain logic. * Add this component to an empty GameObject in your scene and drag this * GameObject into your Academy to make it a child in the hierarchy. * Contains a set of CoreBrains, which each correspond to a different method * for deciding actions. */ public class Brain : MonoBehaviour { public BrainParameters brainParameters; /**< \brief Defines brain specific parameters such as the state size*/ public BrainType brainType; /**< \brief Defines what is the type of the brain : * External / Internal / Player / Heuristic*/ [HideInInspector] public Dictionary agents = new Dictionary(); /**< \brief Keeps track of the agents which subscribe to this brain*/ [SerializeField] ScriptableObject[] CoreBrains; public CoreBrain coreBrain; /**< \brief Reference to the current CoreBrain used by the brain*/ //Ensures the coreBrains are not dupplicated with the brains [SerializeField] private int instanceID; /// Ensures the brain has an up to date array of coreBrains /** Is called when the inspector is modified and into InitializeBrain. * If the brain gameObject was just created, it generates a list of * coreBrains (one for each brainType) */ public void UpdateCoreBrains() { // If CoreBrains is null, this means the Brain object was just // instanciated and we create instances of each CoreBrain if (CoreBrains == null) { CoreBrains = new ScriptableObject[System.Enum.GetValues(typeof(BrainType)).Length]; foreach (BrainType bt in System.Enum.GetValues(typeof(BrainType))) { CoreBrains[(int)bt] = ScriptableObject.CreateInstance("CoreBrain" + bt.ToString()); } } // If the length of CoreBrains does not match the number of BrainTypes, // we increase the length of CoreBrains if (CoreBrains.Length < System.Enum.GetValues(typeof(BrainType)).Length) { ScriptableObject[] new_CoreBrains = new ScriptableObject[System.Enum.GetValues(typeof(BrainType)).Length]; foreach (BrainType bt in System.Enum.GetValues(typeof(BrainType))) { if ((int)bt < CoreBrains.Length) { new_CoreBrains[(int)bt] = CoreBrains[(int)bt]; } else { new_CoreBrains[(int)bt] = ScriptableObject.CreateInstance("CoreBrain" + bt.ToString()); } } CoreBrains = new_CoreBrains; } // If the stored instanceID does not match the current instanceID, // this means that the Brain GameObject was duplicated, and // we need to make a new copy of each CoreBrain if (instanceID != gameObject.GetInstanceID()) { foreach (BrainType bt in System.Enum.GetValues(typeof(BrainType))) { CoreBrains[(int)bt] = ScriptableObject.Instantiate(CoreBrains[(int)bt]); } instanceID = gameObject.GetInstanceID(); } // The coreBrain to display is the one defined in brainType coreBrain = (CoreBrain)CoreBrains[(int)brainType]; coreBrain.SetBrain(this); } /// This is called by the Academy at the start of the environemnt. public void InitializeBrain() { UpdateCoreBrains(); coreBrain.InitializeCoreBrain(); } /// Collects the states of all the agents which subscribe to this brain /// and returns a dictionary {id -> state} public Dictionary> CollectStates() { Dictionary> result = new Dictionary>(); foreach (KeyValuePair idAgent in agents) { List states = idAgent.Value.CollectState(); if (states.Count != brainParameters.stateSize) { throw new UnityAgentsException(string.Format(@"The number of states does not match for agent {0}: Was expecting {1} states but received {2}.", idAgent.Value.gameObject.name, brainParameters.stateSize, states.Count)); } result.Add(idAgent.Key, states); } return result; } /// Collects the observations of all the agents which subscribe to this /// brain and returns a dictionary {id -> Camera} public Dictionary> CollectObservations() { Dictionary> result = new Dictionary>(); foreach (KeyValuePair idAgent in agents) { List observations = idAgent.Value.observations; if (observations.Count < brainParameters.cameraResolutions.Count()) { throw new UnityAgentsException(string.Format(@"The number of observations does not match for agent {0}: Was expecting at least {1} observation but received {2}.", idAgent.Value.gameObject.name, brainParameters.cameraResolutions.Count(), observations.Count)); } result.Add(idAgent.Key, observations); } return result; } /// Collects the rewards of all the agents which subscribe to this brain /// and returns a dictionary {id -> reward} public Dictionary CollectRewards() { Dictionary result = new Dictionary(); foreach (KeyValuePair idAgent in agents) { result.Add(idAgent.Key, idAgent.Value.reward); } return result; } /// Collects the done flag of all the agents which subscribe to this brain /// and returns a dictionary {id -> done} public Dictionary CollectDones() { Dictionary result = new Dictionary(); foreach (KeyValuePair idAgent in agents) { result.Add(idAgent.Key, idAgent.Value.done); } return result; } /// Collects the actions of all the agents which subscribe to this brain /// and returns a dictionary {id -> action} public Dictionary CollectActions() { Dictionary result = new Dictionary(); foreach (KeyValuePair idAgent in agents) { result.Add(idAgent.Key, idAgent.Value.agentStoredAction); } return result; } /// Collects the memories of all the agents which subscribe to this brain /// and returns a dictionary {id -> memories} public Dictionary CollectMemories() { Dictionary result = new Dictionary(); foreach (KeyValuePair idAgent in agents) { result.Add(idAgent.Key, idAgent.Value.memory); } return result; } /// Takes a dictionary {id -> memories} and sends the memories to the /// corresponding agents public void SendMemories(Dictionary memories) { foreach (KeyValuePair idAgent in agents) { idAgent.Value.memory = memories[idAgent.Key]; } } /// Takes a dictionary {id -> actions} and sends the actions to the /// corresponding agents public void SendActions(Dictionary actions) { foreach (KeyValuePair idAgent in agents) { //Add a check here to see if the component was destroyed ? idAgent.Value.UpdateAction(actions[idAgent.Key]); } } /// Takes a dictionary {id -> values} and sends the values to the /// corresponding agents public void SendValues(Dictionary values) { foreach (KeyValuePair idAgent in agents) { //Add a check here to see if the component was destroyed ? idAgent.Value.value = values[idAgent.Key]; } } ///Sets all the agents which subscribe to the brain to done public void SendDone() { foreach (KeyValuePair idAgent in agents) { idAgent.Value.done = true; } } /// Uses coreBrain to call SendState on the CoreBrain public void SendState() { coreBrain.SendState(); } /// Uses coreBrain to call decideAction on the CoreBrain public void DecideAction() { coreBrain.DecideAction(); } /// \brief Is used by the Academy to send a step message to all the agents /// which are not done public void Step() { List agentsToIterate = agents.Values.ToList(); foreach (Agent agent in agentsToIterate) { if (!agent.done) { agent.Step(); } } } /// Is used by the Academy to reset the agents if they are done public void ResetIfDone() { List agentsToIterate = agents.Values.ToList(); foreach (Agent agent in agentsToIterate) { if (agent.done) { if (!agent.resetOnDone) { agent.AgentOnDone(); } else { agent.Reset(); } } } } /// Is used by the Academy to reset all agents public void Reset() { List agentsToIterate = agents.Values.ToList(); foreach (Agent agent in agentsToIterate) { agent.Reset(); agent.done = false; } } /// \brief Is used by the Academy reset the done flag and the rewards of the /// agents that subscribe to the brain public void ResetDoneAndReward() { foreach (Agent agent in agents.Values) { if (!agent.done || agent.resetOnDone) { agent.ResetReward(); agent.done = false; } } } /** Contains logic for coverting a camera component into a Texture2D. */ public Texture2D ObservationToTex(Camera camera, int width, int height) { Camera cam = camera; Rect oldRec = camera.rect; cam.rect = new Rect(0f, 0f, 1f, 1f); bool supportsAntialiasing = false; bool needsRescale = false; var depth = 24; var format = RenderTextureFormat.Default; var readWrite = RenderTextureReadWrite.Default; var antiAliasing = (supportsAntialiasing) ? Mathf.Max(1, QualitySettings.antiAliasing) : 1; var finalRT = RenderTexture.GetTemporary(width, height, depth, format, readWrite, antiAliasing); var renderRT = (!needsRescale) ? finalRT : RenderTexture.GetTemporary(width, height, depth, format, readWrite, antiAliasing); var tex = new Texture2D(width, height, TextureFormat.RGB24, false); var prevActiveRT = RenderTexture.active; var prevCameraRT = cam.targetTexture; // render to offscreen texture (readonly from CPU side) RenderTexture.active = renderRT; cam.targetTexture = renderRT; cam.Render(); if (needsRescale) { RenderTexture.active = finalRT; Graphics.Blit(renderRT, finalRT); RenderTexture.ReleaseTemporary(renderRT); } tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0); tex.Apply(); cam.targetTexture = prevCameraRT; cam.rect = oldRec; RenderTexture.active = prevActiveRT; RenderTexture.ReleaseTemporary(finalRT); return tex; } /// Contains logic to convert the agent's cameras into observation list /// (as list of float arrays) public List GetObservationMatrixList(List agent_keys) { List observation_matrix_list = new List(); Dictionary> observations = CollectObservations(); for (int obs_number = 0; obs_number < brainParameters.cameraResolutions.Length; obs_number++) { int width = brainParameters.cameraResolutions[obs_number].width; int height = brainParameters.cameraResolutions[obs_number].height; bool bw = brainParameters.cameraResolutions[obs_number].blackAndWhite; int pixels = 0; if (bw) pixels = 1; else pixels = 3; float[,,,] observation_matrix = new float[agent_keys.Count , height , width , pixels]; int i = 0; foreach (int k in agent_keys) { Camera agent_obs = observations[k][obs_number]; Texture2D tex = ObservationToTex(agent_obs, width, height); for (int w = 0; w < width; w++) { for (int h = 0; h < height; h++) { Color c = tex.GetPixel(w, h); if (!bw) { observation_matrix[i, tex.height - h - 1, w, 0] = c.r; observation_matrix[i, tex.height - h - 1, w, 1] = c.g; observation_matrix[i, tex.height - h - 1, w, 2] = c.b; } else { observation_matrix[i, tex.height - h - 1, w, 0] = (c.r + c.g + c.b) / 3; } } } UnityEngine.Object.DestroyImmediate(tex); Resources.UnloadUnusedAssets(); i++; } observation_matrix_list.Add(observation_matrix); } return observation_matrix_list; } }