//Put this script on your blue cube. using System.Collections; using UnityEngine; using Unity.MLAgents; using Unity.Barracuda; using Unity.MLAgents.Actuators; using Unity.MLAgents.Sensors; using Unity.MLAgentsExamples; public class WallJumpAgent : Agent { // Depending on this value, the wall will have different height protected int m_Configuration; // Brain to use when no wall is present public NNModel noWallBrain; // Brain to use when a jumpable wall is present public NNModel smallWallBrain; // Brain to use when a wall requiring a block to jump over is present public NNModel bigWallBrain; public GameObject ground; public GameObject spawnArea; protected Bounds m_SpawnAreaBounds; public GameObject goal; public GameObject shortBlock; public GameObject wall; protected Rigidbody m_ShortBlockRb; protected Rigidbody m_AgentRb; protected Material m_GroundMaterial; protected Renderer m_GroundRenderer; protected WallJumpSettings m_WallJumpSettings; public float jumpingTime; public float jumpTime; // This is a downward force applied when falling to make jumps look // less floaty public float fallingForce; // Use to check the coliding objects public Collider[] hitGroundColliders = new Collider[3]; Vector3 m_JumpTargetPos; Vector3 m_JumpStartingPos; string m_NoWallBehaviorName = "SmallWallJump"; string m_SmallWallBehaviorName = "SmallWallJump"; string m_BigWallBehaviorName = "BigWallJump"; protected EnvironmentParameters m_ResetParams; public override void Initialize() { m_WallJumpSettings = FindObjectOfType(); m_Configuration = Random.Range(0, 5); m_AgentRb = GetComponent(); m_ShortBlockRb = shortBlock.GetComponent(); m_SpawnAreaBounds = spawnArea.GetComponent().bounds; m_GroundRenderer = ground.GetComponent(); m_GroundMaterial = m_GroundRenderer.material; spawnArea.SetActive(false); m_ResetParams = Academy.Instance.EnvironmentParameters; // Update model references if we're overriding var modelOverrider = GetComponent(); if (modelOverrider.HasOverrides) { noWallBrain = modelOverrider.GetModelForBehaviorName(m_NoWallBehaviorName); m_NoWallBehaviorName = ModelOverrider.GetOverrideBehaviorName(m_NoWallBehaviorName); smallWallBrain = modelOverrider.GetModelForBehaviorName(m_SmallWallBehaviorName); m_SmallWallBehaviorName = ModelOverrider.GetOverrideBehaviorName(m_SmallWallBehaviorName); bigWallBrain = modelOverrider.GetModelForBehaviorName(m_BigWallBehaviorName); m_BigWallBehaviorName = ModelOverrider.GetOverrideBehaviorName(m_BigWallBehaviorName); } } // Begin the jump sequence public void Jump() { jumpingTime = 0.2f; m_JumpStartingPos = m_AgentRb.position; } /// /// Does the ground check. /// /// true, if the agent is on the ground, /// false otherwise. /// public bool DoGroundCheck(bool smallCheck) { if (!smallCheck) { hitGroundColliders = new Collider[3]; var o = gameObject; Physics.OverlapBoxNonAlloc( o.transform.position + new Vector3(0, -0.05f, 0), new Vector3(0.95f / 2f, 0.5f, 0.95f / 2f), hitGroundColliders, o.transform.rotation); var grounded = false; foreach (var col in hitGroundColliders) { if (col != null && col.transform != transform && (col.CompareTag("walkableSurface") || col.CompareTag("block") || col.CompareTag("wall"))) { grounded = true; //then we're grounded break; } } return grounded; } else { RaycastHit hit; Physics.Raycast(transform.position + new Vector3(0, -0.05f, 0), -Vector3.up, out hit, 1f); if (hit.collider != null && (hit.collider.CompareTag("walkableSurface") || hit.collider.CompareTag("block") || hit.collider.CompareTag("wall")) && hit.normal.y > 0.95f) { return true; } return false; } } /// /// Moves a rigidbody towards a position smoothly. /// /// Target position. /// The rigidbody to be moved. /// The velocity to target during the /// motion. /// The maximum velocity posible. void MoveTowards( Vector3 targetPos, Rigidbody rb, float targetVel, float maxVel) { var moveToPos = targetPos - rb.worldCenterOfMass; var velocityTarget = Time.fixedDeltaTime * targetVel * moveToPos; if (float.IsNaN(velocityTarget.x) == false) { rb.velocity = Vector3.MoveTowards( rb.velocity, velocityTarget, maxVel); } } public override void CollectObservations(VectorSensor sensor) { var agentPos = m_AgentRb.position - ground.transform.position; sensor.AddObservation(agentPos / 20f); sensor.AddObservation(DoGroundCheck(true) ? 1 : 0); } /// /// Gets a random spawn position in the spawningArea. /// /// The random spawn position. public Vector3 GetRandomSpawnPos() { var randomPosX = Random.Range(-m_SpawnAreaBounds.extents.x, m_SpawnAreaBounds.extents.x); var randomPosZ = Random.Range(-m_SpawnAreaBounds.extents.z, m_SpawnAreaBounds.extents.z); var randomSpawnPos = spawnArea.transform.position + new Vector3(randomPosX, 0.45f, randomPosZ); return randomSpawnPos; } /// /// Changes the color of the ground for a moment. /// /// The Enumerator to be used in a Coroutine. /// The material to be swapped. /// The time the material will remain. protected IEnumerator GoalScoredSwapGroundMaterial(Material mat, float time) { m_GroundRenderer.material = mat; yield return new WaitForSeconds(time); //wait for 2 sec m_GroundRenderer.material = m_GroundMaterial; } public void MoveAgent(ActionSegment act) { AddReward(-0.0005f); var smallGrounded = DoGroundCheck(true); var largeGrounded = DoGroundCheck(false); var dirToGo = Vector3.zero; var rotateDir = Vector3.zero; var dirToGoForwardAction = act[0]; var rotateDirAction = act[1]; var dirToGoSideAction = act[2]; var jumpAction = act[3]; if (dirToGoForwardAction == 1) dirToGo = (largeGrounded ? 1f : 0.5f) * 1f * transform.forward; else if (dirToGoForwardAction == 2) dirToGo = (largeGrounded ? 1f : 0.5f) * -1f * transform.forward; if (rotateDirAction == 1) rotateDir = transform.up * -1f; else if (rotateDirAction == 2) rotateDir = transform.up * 1f; if (dirToGoSideAction == 1) dirToGo = (largeGrounded ? 1f : 0.5f) * -0.6f * transform.right; else if (dirToGoSideAction == 2) dirToGo = (largeGrounded ? 1f : 0.5f) * 0.6f * transform.right; if (jumpAction == 1) if ((jumpingTime <= 0f) && smallGrounded) { Jump(); } transform.Rotate(rotateDir, Time.fixedDeltaTime * 300f); m_AgentRb.AddForce(dirToGo * m_WallJumpSettings.agentRunSpeed, ForceMode.VelocityChange); if (jumpingTime > 0f) { m_JumpTargetPos = new Vector3(m_AgentRb.position.x, m_JumpStartingPos.y + m_WallJumpSettings.agentJumpHeight, m_AgentRb.position.z) + dirToGo; MoveTowards(m_JumpTargetPos, m_AgentRb, m_WallJumpSettings.agentJumpVelocity, m_WallJumpSettings.agentJumpVelocityMaxChange); } if (!(jumpingTime > 0f) && !largeGrounded) { m_AgentRb.AddForce( Vector3.down * fallingForce, ForceMode.Acceleration); } jumpingTime -= Time.fixedDeltaTime; } public override void OnActionReceived(ActionBuffers actionBuffers) { MoveAgent(actionBuffers.DiscreteActions); if ((!Physics.Raycast(m_AgentRb.position, Vector3.down, 20)) || (!Physics.Raycast(m_ShortBlockRb.position, Vector3.down, 20))) { SetReward(-1f); EndEpisode(); ResetBlock(m_ShortBlockRb); StartCoroutine( GoalScoredSwapGroundMaterial(m_WallJumpSettings.failMaterial, .5f)); } } public override void Heuristic(in ActionBuffers actionsOut) { var discreteActionsOut = actionsOut.DiscreteActions; discreteActionsOut.Clear(); if (Input.GetKey(KeyCode.D)) { discreteActionsOut[1] = 2; } if (Input.GetKey(KeyCode.W)) { discreteActionsOut[0] = 1; } if (Input.GetKey(KeyCode.A)) { discreteActionsOut[1] = 1; } if (Input.GetKey(KeyCode.S)) { discreteActionsOut[0] = 2; } discreteActionsOut[3] = Input.GetKey(KeyCode.Space) ? 1 : 0; } // Detect when the agent hits the goal protected virtual void OnTriggerStay(Collider col) { if (col.gameObject.CompareTag("goal") && DoGroundCheck(true)) { SetReward(1f); EndEpisode(); StartCoroutine( GoalScoredSwapGroundMaterial(m_WallJumpSettings.goalScoredMaterial, 2)); } } //Reset the orange block position void ResetBlock(Rigidbody blockRb) { blockRb.transform.position = GetRandomSpawnPos(); blockRb.velocity = Vector3.zero; blockRb.angularVelocity = Vector3.zero; } public override void OnEpisodeBegin() { ResetBlock(m_ShortBlockRb); transform.localPosition = new Vector3( 18 * (Random.value - 0.5f), 1, -12); m_Configuration = Random.Range(0, 5); m_AgentRb.velocity = default(Vector3); } void FixedUpdate() { if (m_Configuration != -1) { ConfigureAgent(m_Configuration); m_Configuration = -1; } } /// /// Configures the agent. Given an integer config, the wall will have /// different height and a different brain will be assigned to the agent. /// /// Config. /// If 0 : No wall and noWallBrain. /// If 1: Small wall and smallWallBrain. /// Other : Tall wall and BigWallBrain. /// protected virtual void ConfigureAgent(int config) { var localScale = wall.transform.localScale; if (config == 0) { localScale = new Vector3( localScale.x, m_ResetParams.GetWithDefault("no_wall_height", 0), localScale.z); wall.transform.localScale = localScale; SetModel(m_NoWallBehaviorName, noWallBrain); } else if (config == 1) { localScale = new Vector3( localScale.x, m_ResetParams.GetWithDefault("small_wall_height", 4), localScale.z); wall.transform.localScale = localScale; SetModel(m_SmallWallBehaviorName, smallWallBrain); } else { var height = m_ResetParams.GetWithDefault("big_wall_height", 8); localScale = new Vector3( localScale.x, height, localScale.z); wall.transform.localScale = localScale; SetModel(m_BigWallBehaviorName, bigWallBrain); } } }