/// <summary> /// Gets the platform that a ledge is a part of. /// </summary> /// <returns>The platform that a ledge is a part of.</returns> /// <param name="ledge">The ledge to get a platform for.</param> private GameObject GetLedgePlatform(LedgeNode ledge) { if (ledge.transform.parent.parent != null) { return(ledge.transform.parent.parent.gameObject); } else { return(ledge.transform.parent.gameObject); } }
/// <summary> /// Loads the next ledge node into the current node. /// </summary> private void LoadNextLedge() { if (currentPath == null || currentPath.Count == 0) { currentNode = null; replanTimer = 0; } else { currentNode = currentPath.First.Value; currentPath.RemoveFirst(); } }
/// <summary> /// Constructs a path from a start and end node. /// </summary> /// <returns>A path from the start to the end node.</returns> /// <param name="start">The node to start from.</param> /// <param name="end">The node to end at.</param> private LinkedList <LedgeNode> GetPath(LedgeNode start, LedgeNode end) { LinkedList <LedgeNode> path = new LinkedList <LedgeNode>(); if (start == null || end == null || next[start.index, end.index] == null) { return(path); } else { LedgeNode current = start; path.AddLast(start); while (current != end) { LedgeNode previous = current; current = next[current.index, end.index]; float offset = current.transform.position.y - previous.transform.position.y; if (Mathf.Abs(offset) > 0.2f) { if (offset > 0) { current.YOffset = offset; if (previous == start && offset < 5) { path.RemoveFirst(); } } else if (offset < 0) { previous.YOffset = offset; } } if (current != end || GetLedgePlatform(current) != GetLedgePlatform(previous)) { path.AddLast(current); } } return(path); } }
/// <summary> /// Gets the closest ledge node from a target. /// </summary> /// <returns>The closest node.</returns> /// <param name="target">Target.</param> private LedgeNode GetClosestLedgeNode(Transform target) { RaycastHit hit; LedgeNode[] currentLedges = ledges; if (Physics.Raycast(target.position + Vector3.up * 0.5f, Vector3.down, out hit, 30, AIController.LAYERMASK)) { currentLedges = GetPlatformLedges(hit); } LedgeNode closestLedge = null; float closestDistance = Mathf.Infinity; foreach (LedgeNode ledge in currentLedges) { float currentDistance = Vector3.Distance(target.position, ledge.transform.position); if (ledge.transform.position.y < target.position.y + 1.5f && currentDistance < closestDistance) { closestLedge = ledge; closestDistance = currentDistance; } } return(closestLedge); }
/// <summary> /// Gets the platform that a ledge is a part of. /// </summary> /// <returns>The platform that a ledge is a part of.</returns> /// <param name="ledge">The ledge to get a platform for.</param> private GameObject GetLedgePlatform(LedgeNode ledge) { if (ledge.transform.parent.parent != null) { return ledge.transform.parent.parent.gameObject; } else { return ledge.transform.parent.gameObject; } }
/// <summary> /// Picks an action for the character to do every tick. /// </summary> /// <param name="controller">The controller for the character.</param> public void ChooseAction(AIController controller) { if (DEBUGLINES) { foreach (LedgeNode ledge in ledges) { foreach (LedgeNode adjacent in ledge.adjacentEdges) { Debug.DrawLine(ledge.transform.position, adjacent.transform.position, Color.yellow); } } } if (target == null) { controller.SetRunInDirection(0); return; } Controller targetController = target.GetComponent<Controller>(); if (targetController != null && targetController.LifeComponent.Health <= 0) { controller.SetRunInDirection(0); return; } lastSpeed = controller.runSpeed; controller.jump = false; float currentTargetDistance = targetDistance; Vector3 opponentOffset = target.transform.position - controller.transform.position; Vector3 targetOffset = opponentOffset; float distanceTolerance = targetDistance - 1; Vector3 playerCenter = controller.transform.position + Vector3.up * 0.75f; RaycastHit under; Physics.Raycast(playerCenter, Vector3.down, out under, 30, AIController.LAYERMASK); // Check if there is a platform in the way of shooting. RaycastHit hit; // Find a ledge path to get to the target. replanTimer -= Time.deltaTime; if (pathTarget != target && currentNode == null) { replanTimer = 0; } if (OnSamePlatform(controller.transform, target.transform)) { if (Mathf.Abs(opponentOffset.y) < 5 && (Mathf.Abs(opponentOffset.y) < 0.1f || under.distance < 1)) { currentNode = null; } } else if (replanTimer <= 0) { replanTimer = REPLANTIME; pathTarget = target; currentPath = GetPath(GetClosestLedgeNode(controller.transform), GetClosestLedgeNode(target.transform)); if (currentPath.Count > 0) { currentPath.Last.Value.YOffset = target.transform.position.y - currentPath.Last.Value.transform.position.y; LoadNextLedge(); currentNode.YOffset = currentNode.transform.position.y - controller.transform.position.y; if (currentPath.Count > 0) { if (GetLedgePlatform(currentPath.First.Value) == GetPlatformUnderneath(controller.transform)) { LoadNextLedge(); } else if (currentPath.Count == 1 && Mathf.Abs(currentNode.transform.position.y - currentPath.First.Value.transform.position.y) < 0.2f) { currentNode = null; } } } else { currentNode = null; } } if (currentNode != null) { // Move towards the nearest ledge, jumping if needed. Vector3 currentOffset = currentNode.transform.position - controller.transform.position; // Go to the next ledge node if possible. if (currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 3 && currentOffset.y <= 0 || currentNode.YOffset < 0 && currentOffset.y >= 1.5f) { LoadNextLedge(); } if (currentNode != null) { currentOffset = currentNode.transform.position - controller.transform.position; currentTargetDistance = 0; distanceTolerance = 0.1f; if (Mathf.Abs(currentOffset.y) < distanceTolerance) { currentOffset.y = 0; } Vector3 platformOffset = currentNode.transform.position - GetLedgePlatform(currentNode).transform.position; float ledgeOffset = -0.1f; if (currentNode.YOffset > 0 && currentOffset.y > -0.5f || currentNode.YOffset < 0) { ledgeOffset = LEDGEGRABDISTANCE; } if (platformOffset.x > 0) { currentOffset.x += ledgeOffset; } else { currentOffset.x -= ledgeOffset; } controller.jump = currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 2; targetOffset = currentOffset; } } if (DEBUGLINES) { Debug.DrawRay(controller.transform.position, targetOffset, Color.red); if (currentNode != null) { LedgeNode c = currentNode; foreach (LedgeNode l in currentPath) { Debug.DrawLine(c.transform.position, l.transform.position, Color.red); c = l; } Debug.DrawLine(c.transform.position, target.transform.position, Color.red); } } LedgeNode closestLedge = null; // Check if the AI is falling to its death. if (under.collider == null) { // Find the closest ledge to go to. float closestLedgeDistance = Mathf.Infinity; foreach (LedgeNode ledge in ledges) { if (ledge != null) { float currentDistance = Mathf.Abs(ledge.transform.position.x - controller.transform.position.x); if (currentDistance < closestLedgeDistance && ledge.transform.position.y < controller.transform.position.y + 1) { closestLedge = ledge; closestLedgeDistance = currentDistance; } } } bool awayFromLedge = false; if (closestLedge == null) { controller.SetRunInDirection(-controller.transform.position.x); } else { float ledgeOffsetX = closestLedge.transform.position.x - controller.transform.position.x; if (Mathf.Abs(ledgeOffsetX) > LEDGEGRABDISTANCE) { controller.SetRunInDirection(ledgeOffsetX); awayFromLedge = true; } } controller.jump = true; if (awayFromLedge) { return; } } // Move towards the opponent. float horizontalDistance = Mathf.Abs(targetOffset.x); if (horizontalDistance > currentTargetDistance) { controller.SetRunInDirection(targetOffset.x); } else if (horizontalDistance < currentTargetDistance - distanceTolerance) { controller.SetRunInDirection(-targetOffset.x); } else if (opponentOffset == targetOffset && under.collider != null && (controller.ParkourComponent.FacingRight ^ opponentOffset.x > 0)) { controller.ParkourComponent.FacingRight = opponentOffset.x > 0; } else { controller.runSpeed = 0; } if (controller.runSpeed != 0) { // Don't chase an opponent off the map. Vector3 offsetPosition = controller.transform.position; offsetPosition.x += controller.runSpeed / 3; offsetPosition.y += 0.5f; Vector3 offsetPosition3 = offsetPosition; offsetPosition3.x += controller.runSpeed; if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 30, AIController.LAYERMASK) && !Physics.Raycast(offsetPosition3, Vector3.down, out hit, 30, AIController.LAYERMASK)) { if (controller.ParkourComponent.Sliding) { controller.SetRunInDirection(-opponentOffset.x); } else if (closestLedge == null) { controller.runSpeed = 0; } controller.slide = false; } else { // Slide if the opponent is far enough away for sliding to be useful. controller.ParkourComponent.FacingRight = opponentOffset.x > 0; controller.slide = horizontalDistance > 10 && currentNode == null && OnSamePlatform(controller.transform, target.transform); } } if (controller.runSpeed == 0 && Mathf.Abs(opponentOffset.x) < 1 && opponentOffset.y < 0 && target.GetComponent<Controller>() && controller.GetComponent<Rigidbody>().velocity.y <= Mathf.Epsilon) { // Don't sit on top of the opponent. controller.SetRunInDirection(-controller.transform.position.x); } if (controller.runSpeed > 0 && lastSpeed < 0 || controller.runSpeed < 0 && lastSpeed > 0) { // Check if the AI turned very recently to avoid thrashing. turnTimer -= Time.deltaTime; if (turnTimer <= 0) { turnTimer = TURNCOOLDOWN; } else { controller.runSpeed = 0; } } // Jump to reach some tokens. if (targetDistance == 0 && controller.runSpeed == 0 && target.GetComponent<ArrowToken>() && opponentOffset == targetOffset) { controller.jump = true; } controller.fastFall = targetOffset.y < -1f; }
/// <summary> /// Constructs a path from a start and end node. /// </summary> /// <returns>A path from the start to the end node.</returns> /// <param name="start">The node to start from.</param> /// <param name="end">The node to end at.</param> private LinkedList<LedgeNode> GetPath(LedgeNode start, LedgeNode end) { LinkedList<LedgeNode> path = new LinkedList<LedgeNode>(); if (start == null || end == null || next[start.index, end.index] == null) { return path; } else { LedgeNode current = start; path.AddLast(start); while (current != end) { LedgeNode previous = current; current = next[current.index, end.index]; float offset = current.transform.position.y - previous.transform.position.y; if (Mathf.Abs(offset) > 0.2f) { if (offset > 0) { current.YOffset = offset; if (previous == start && offset < 5) { path.RemoveFirst(); } } else if (offset < 0) { previous.YOffset = offset; } } if (current != end || GetLedgePlatform(current) != GetLedgePlatform(previous)) { path.AddLast(current); } } return path; } }
/// <summary> /// Picks an action for the character to do every tick. /// </summary> /// <param name="controller">The controller for the character.</param> public void ChooseAction(AIController controller) { if (DEBUGLINES) { foreach (LedgeNode ledge in ledges) { foreach (LedgeNode adjacent in ledge.adjacentEdges) { Debug.DrawLine(ledge.transform.position, adjacent.transform.position, Color.yellow); } } } if (target == null) { controller.SetRunInDirection(0); return; } Controller targetController = target.GetComponent <Controller>(); if (targetController != null && targetController.LifeComponent.Health <= 0) { controller.SetRunInDirection(0); return; } lastSpeed = controller.runSpeed; controller.jump = false; float currentTargetDistance = targetDistance; Vector3 opponentOffset = target.transform.position - controller.transform.position; Vector3 targetOffset = opponentOffset; float distanceTolerance = targetDistance - 1; Vector3 playerCenter = controller.transform.position + Vector3.up * 0.75f; RaycastHit under; Physics.Raycast(playerCenter, Vector3.down, out under, 30, AIController.LAYERMASK); // Check if there is a platform in the way of shooting. RaycastHit hit; // Find a ledge path to get to the target. replanTimer -= Time.deltaTime; if (pathTarget != target && currentNode == null) { replanTimer = 0; } if (OnSamePlatform(controller.transform, target.transform)) { if (Mathf.Abs(opponentOffset.y) < 5 && (Mathf.Abs(opponentOffset.y) < 0.1f || under.distance < 1)) { currentNode = null; } } else if (replanTimer <= 0) { replanTimer = REPLANTIME; pathTarget = target; currentPath = GetPath(GetClosestLedgeNode(controller.transform), GetClosestLedgeNode(target.transform)); if (currentPath.Count > 0) { currentPath.Last.Value.YOffset = target.transform.position.y - currentPath.Last.Value.transform.position.y; LoadNextLedge(); currentNode.YOffset = currentNode.transform.position.y - controller.transform.position.y; if (currentPath.Count > 0) { if (GetLedgePlatform(currentPath.First.Value) == GetPlatformUnderneath(controller.transform)) { LoadNextLedge(); } else if (currentPath.Count == 1 && Mathf.Abs(currentNode.transform.position.y - currentPath.First.Value.transform.position.y) < 0.2f) { currentNode = null; } } } else { currentNode = null; } } if (currentNode != null) { // Move towards the nearest ledge, jumping if needed. Vector3 currentOffset = currentNode.transform.position - controller.transform.position; // Go to the next ledge node if possible. if (currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 3 && currentOffset.y <= 0 || currentNode.YOffset < 0 && currentOffset.y >= 1.5f) { LoadNextLedge(); } if (currentNode != null) { currentOffset = currentNode.transform.position - controller.transform.position; currentTargetDistance = 0; distanceTolerance = 0.1f; if (Mathf.Abs(currentOffset.y) < distanceTolerance) { currentOffset.y = 0; } Vector3 platformOffset = currentNode.transform.position - GetLedgePlatform(currentNode).transform.position; float ledgeOffset = -0.1f; if (currentNode.YOffset > 0 && currentOffset.y > -0.5f || currentNode.YOffset < 0) { ledgeOffset = LEDGEGRABDISTANCE; } if (platformOffset.x > 0) { currentOffset.x += ledgeOffset; } else { currentOffset.x -= ledgeOffset; } controller.jump = currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 2; targetOffset = currentOffset; } } if (DEBUGLINES) { Debug.DrawRay(controller.transform.position, targetOffset, Color.red); if (currentNode != null) { LedgeNode c = currentNode; foreach (LedgeNode l in currentPath) { Debug.DrawLine(c.transform.position, l.transform.position, Color.red); c = l; } Debug.DrawLine(c.transform.position, target.transform.position, Color.red); } } LedgeNode closestLedge = null; // Check if the AI is falling to its death. if (under.collider == null) { // Find the closest ledge to go to. float closestLedgeDistance = Mathf.Infinity; foreach (LedgeNode ledge in ledges) { if (ledge != null) { float currentDistance = Mathf.Abs(ledge.transform.position.x - controller.transform.position.x); if (currentDistance < closestLedgeDistance && ledge.transform.position.y < controller.transform.position.y + 1) { closestLedge = ledge; closestLedgeDistance = currentDistance; } } } bool awayFromLedge = false; if (closestLedge == null) { controller.SetRunInDirection(-controller.transform.position.x); } else { float ledgeOffsetX = closestLedge.transform.position.x - controller.transform.position.x; if (Mathf.Abs(ledgeOffsetX) > LEDGEGRABDISTANCE) { controller.SetRunInDirection(ledgeOffsetX); awayFromLedge = true; } } controller.jump = true; if (awayFromLedge) { return; } } // Move towards the opponent. float horizontalDistance = Mathf.Abs(targetOffset.x); if (horizontalDistance > currentTargetDistance) { controller.SetRunInDirection(targetOffset.x); } else if (horizontalDistance < currentTargetDistance - distanceTolerance) { controller.SetRunInDirection(-targetOffset.x); } else if (opponentOffset == targetOffset && under.collider != null && (controller.ParkourComponent.FacingRight ^ opponentOffset.x > 0)) { controller.ParkourComponent.FacingRight = opponentOffset.x > 0; } else { controller.runSpeed = 0; } if (controller.runSpeed != 0) { // Don't chase an opponent off the map. Vector3 offsetPosition = controller.transform.position; offsetPosition.x += controller.runSpeed / 3; offsetPosition.y += 0.5f; Vector3 offsetPosition3 = offsetPosition; offsetPosition3.x += controller.runSpeed; if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 30, AIController.LAYERMASK) && !Physics.Raycast(offsetPosition3, Vector3.down, out hit, 30, AIController.LAYERMASK)) { if (controller.ParkourComponent.Sliding) { controller.SetRunInDirection(-opponentOffset.x); } else if (closestLedge == null) { controller.runSpeed = 0; } controller.slide = false; } else { // Slide if the opponent is far enough away for sliding to be useful. controller.ParkourComponent.FacingRight = opponentOffset.x > 0; controller.slide = horizontalDistance > 10 && currentNode == null && OnSamePlatform(controller.transform, target.transform); } } if (controller.runSpeed == 0 && Mathf.Abs(opponentOffset.x) < 1 && opponentOffset.y < 0 && target.GetComponent <Controller>() && controller.GetComponent <Rigidbody>().velocity.y <= Mathf.Epsilon) { // Don't sit on top of the opponent. controller.SetRunInDirection(-controller.transform.position.x); } if (controller.runSpeed > 0 && lastSpeed < 0 || controller.runSpeed < 0 && lastSpeed > 0) { // Check if the AI turned very recently to avoid thrashing. turnTimer -= Time.deltaTime; if (turnTimer <= 0) { turnTimer = TURNCOOLDOWN; } else { controller.runSpeed = 0; } } // Jump to reach some tokens. if (targetDistance == 0 && controller.runSpeed == 0 && target.GetComponent <ArrowToken>() && opponentOffset == targetOffset) { controller.jump = true; } controller.fastFall = targetOffset.y < -1f; }