/* Convert Brainiac XML to JSON */ private void ConvertStoryToJSON() { //Load XML from brainiac and setup our variables var gameDoc = XDocument.Load("elements/" + GetCurrentWorkspace().Attribute("folder").Value + "/main.xml"); XElement gameCoreXML = gameDoc.Element("Behavior").Element("Node").Element("Connector").Element("Node"); string finalOutput = "{"; List <string> prefixes = new List <string>(); //Load language XML XDocument languageDoc = XDocument.Load("elements/" + GetCurrentWorkspace().Attribute("folder").Value + "/" + CompileLanguage.SelectedItem + ".xml"); XElement languageData = languageDoc.Element("language"); //Start traversing XML... int levelCount = 0; foreach (var levelNode in gameCoreXML.Element("Connector").Elements()) { if (levelNode.FirstAttribute.Value == "TextAdventure.Nodes.GameLevel") //Level { finalOutput += "\"" + levelCount.ToString() + "\":{\"level_name\":\"" + levelNode.Attribute("Name").Value + "\", "; int zoneCount = 0; foreach (var ZoneNode in levelNode.Element("Connector").Elements()) { if (ZoneNode.FirstAttribute.Value == "TextAdventure.Nodes.LevelZone") //Zone in current level { finalOutput += "\"" + zoneCount.ToString() + "\":{\"zone_name\":\"" + ZoneNode.Attribute("Name").Value + "\", "; int stateCount = 0; foreach (var stateNode in ZoneNode.Element("Connector").Elements()) { if (stateNode.FirstAttribute.Value == "TextAdventure.Nodes.ZoneState") //State in current zone { finalOutput += "\"" + stateCount.ToString() + "\":{\"state_name\":\"" + stateNode.Attribute("Name").Value + "\", "; foreach (var inputActionNode in stateNode.Element("Connector").Elements()) { if (inputActionNode.FirstAttribute.Value == "TextAdventure.Nodes.ZoneIntro") //Zone intro text for state { finalOutput += "\"zone_intro\":\"" + GetLocalisedString(inputActionNode.Attribute("IntroText").Value, languageData) + "\", "; } if (inputActionNode.FirstAttribute.Value == "TextAdventure.Nodes.InputAction") //Input action in current zone { string thisAction = inputActionNode.Attribute("Action").Value; if (!prefixes.Contains(thisAction)) { prefixes.Add(thisAction); } finalOutput += "\"" + thisAction + "\":{"; foreach (var inputSubjectNode in inputActionNode.Element("Connector").Elements()) { if (inputSubjectNode.FirstAttribute.Value == "TextAdventure.Nodes.InputSubject") //Input subject for current action { //Here our logic is handled... string thisSubject = inputSubjectNode.Attribute("Subject").Value; if (thisSubject == "") { thisSubject = "*"; //Can't have blank object names in Unity } finalOutput += "\"" + thisSubject + "\":{"; List <string> gameDataToAdd = new List <string>(); List <string> gameDataToRemove = new List <string>(); foreach (var inputScriptNode in inputSubjectNode.Element("Connector").Elements()) { if (inputScriptNode.FirstAttribute.Value == "TextAdventure.Nodes.ReferencedInput") { //Action is referenced from another node group. finalOutput += "\"referenced\": [\"" + inputScriptNode.Attribute("InputAction").Value + "\",\"" + inputScriptNode.Attribute("InputSubject").Value + "\"]"; } else { if (inputScriptNode.FirstAttribute.Value == "TextAdventure.Nodes.GameDataExists") { //Optional/Required GameData Condition(s) if (inputScriptNode.Attribute("GameDataCondition").Value == "OPTIONAL:0") { finalOutput += "\"optional_gamedata\": "; } else { finalOutput += "\"required_gamedata\": "; } string[] conditionalGameData = inputScriptNode.Attribute("GameData").Value.Split(','); finalOutput += "[\""; foreach (string gameData in conditionalGameData) { if (gameData.Substring(0, 1) == " ") { finalOutput += gameData.Substring(1) + "\", \""; } else { finalOutput += gameData + "\", \""; } } finalOutput = finalOutput.Substring(0, finalOutput.Length - 3) + "], "; //Response of condition for true/false outcomes. //OPTIONAL and REQUIRED use these outcomes, however REQUIRED will ignore all following logic if false. foreach (var dataConditionResult in inputScriptNode.Elements()) { if (dataConditionResult.Attribute("Identifier").Value == "ConditionTrue") { if (dataConditionResult.Element("Node").LastAttribute.Name == "SystemResponse") { finalOutput += "\"system_reply_ok\": \"" + GetLocalisedString(dataConditionResult.Element("Node").LastAttribute.Value, languageData) + "\", "; } } else if (dataConditionResult.Attribute("Identifier").Value == "ConditionFalse") { if (dataConditionResult.Element("Node").LastAttribute.Name == "SystemResponse") { finalOutput += "\"system_reply_issue\": \"" + GetLocalisedString(dataConditionResult.Element("Node").LastAttribute.Value, languageData) + "\", "; } } } } if (inputScriptNode.FirstAttribute.Value == "TextAdventure.Nodes.ModifyGameData") { //Add/Remove game data if (inputScriptNode.Attribute("Action").Value == "ADD:0") { gameDataToAdd.Add(inputScriptNode.Attribute("ModifyGameDataLocationName").Value); } else { gameDataToRemove.Add(inputScriptNode.Attribute("ModifyGameDataLocationName").Value); } } if (inputScriptNode.FirstAttribute.Value == "TextAdventure.Nodes.Response") { //Basic response not in a context of condition finalOutput += "\"system_reply_ok\": \"" + GetLocalisedString(inputScriptNode.Attribute("SystemResponse").Value, languageData) + "\", "; } if (inputScriptNode.FirstAttribute.Value == "TextAdventure.Nodes.MoveTo") { //Move to... if (inputScriptNode.Attribute("MoveToLocationType").Value == "LEVEL:0") { //Level finalOutput += "\"new_level\": \""; } else if (inputScriptNode.Attribute("MoveToLocationType").Value == "ZONE:1") { //Zone finalOutput += "\"new_zone\": \""; } else { //State finalOutput += "\"new_state\": \""; } finalOutput += inputScriptNode.Attribute("MoveToLocationName").Value + "\", "; } if (inputScriptNode.FirstAttribute.Value == "TextAdventure.Nodes.GameOver") { //Game over! string winState = inputScriptNode.Attribute("EndCondition").Value; finalOutput += "\"game_over\": \"" + winState.Substring(0, winState.Length - 2) + "\", "; } } } if (finalOutput.Substring(finalOutput.Length - 2) == ", ") { //Tidy up finalOutput = finalOutput.Substring(0, finalOutput.Length - 2); } if (gameDataToAdd.Count() > 0) { //Concat gamedata to add (we can have multiple nodes of this) finalOutput += ", \"add_gamedata\": ["; for (int i = 0; i < gameDataToAdd.Count(); i++) { finalOutput += "\"" + gameDataToAdd[i] + "\","; } finalOutput = finalOutput.Substring(0, finalOutput.Length - 1); finalOutput += "]"; } if (gameDataToRemove.Count() > 0) { //Concat gamedata to remove (we can have multiple nodes of this) finalOutput += ", \"remove_gamedata\": ["; for (int i = 0; i < gameDataToRemove.Count(); i++) { finalOutput += "\"" + gameDataToRemove[i] + "\","; } finalOutput = finalOutput.Substring(0, finalOutput.Length - 1); finalOutput += "]"; } finalOutput += "}, "; } } //Tidy up finalOutput = finalOutput.Substring(0, finalOutput.Length - 2); finalOutput += "}, "; } } //Tidy up & increment finalOutput = finalOutput.Substring(0, finalOutput.Length - 2); finalOutput += "}, "; stateCount++; } } //Tidy up & increment finalOutput = finalOutput.Substring(0, finalOutput.Length - 2); finalOutput += "}, "; zoneCount++; } } //Tidy up & increment finalOutput = finalOutput.Substring(0, finalOutput.Length - 2); finalOutput += "}, "; levelCount++; } } //Work out all prefixes used in the game string prefixesString = ""; for (int i = 0; i < prefixes.Count(); i++) { prefixesString += prefixes[i] + "\",\""; } prefixesString = prefixesString.Substring(0, prefixesString.Length - 3); //Json-ify core game data finalOutput += "\"game_core\":{\"title\": \"" + gameCoreXML.Attribute("GameTitle").Value + "\", \"developer\": \"" + gameCoreXML.Attribute("GameDeveloper").Value + "\", \"invalid_input\": \"" + gameCoreXML.Element("Connector").Element("Node").Attribute("InvalidInput").Value + "\", \"fatal_error\": \"A FATAL ERROR OCCURRED!\\n\\nPLEASE CHECK YOUR SCRIPTING.\", \"input_disabled\": \"" + gameCoreXML.Element("Connector").Element("Node").Attribute("DisabledInput").Value + "\", \"prefixes\": [\"" + prefixesString + "\"]}}"; //Write out to file for use in game Directory.CreateDirectory("elements/games/2d_asge/data/"); File.WriteAllText("elements/games/2d_asge/data/story.json", finalOutput); File.WriteAllText("elements/games/2d_unity/TextAdventureGame_Data/StreamingAssets/story.json", finalOutput); File.WriteAllText("elements/games/3d_unreal/TextAdventureGame/story.json", finalOutput); //Success MessageBox.Show("Successfully compiled selected game!\nPlay it to try out the logic.", "Operation completed.", MessageBoxButtons.OK, MessageBoxIcon.Information); }
public string GetNextZoneInPath(string startZoneName, string targetZoneName) { if (startZoneName == targetZoneName) { return(targetZoneName); } if (nodeGraph == null) { ZonePathingData pathingData = Content.LoadImmediate(dataAssetKey); nodeGraph = GenerateNodeGraph(pathingData); } else { clearParentNodes(); } ZoneNode item = nodeGraph[startZoneName]; List <string> list = new List <string>(); Queue <ZoneNode> queue = new Queue <ZoneNode>(); queue.Enqueue(item); ZoneNode zoneNode = null; while (queue.Count != 0 && zoneNode == null) { ZoneNode zoneNode2 = queue.Dequeue(); list.Add(zoneNode2.ZoneName); for (int i = 0; i < zoneNode2.ZoneTransitions.Length; i++) { ZoneNode zoneNode3 = zoneNode2.ZoneTransitions[i]; if (zoneNode3 == null || list.Contains(zoneNode3.ZoneName)) { continue; } if (zoneNode3.ZoneName == targetZoneName) { if (zoneNode2.ParentNode == null) { zoneNode = zoneNode3; break; } zoneNode3.ParentNode = zoneNode2; while (zoneNode2.ParentNode != null) { zoneNode2 = zoneNode2.ParentNode; } zoneNode = zoneNode2.ZoneTransitions[0]; break; } ZoneNode zoneNode4 = new ZoneNode(); zoneNode4.ZoneName = zoneNode2.ZoneName; zoneNode4.ZoneTransitions = new ZoneNode[1] { zoneNode2.ZoneTransitions[i] }; zoneNode4.ParentNode = zoneNode2.ParentNode; ZoneNode zoneNode5 = new ZoneNode(); zoneNode5.ZoneName = zoneNode2.ZoneTransitions[i].ZoneName; zoneNode5.ParentNode = zoneNode4; zoneNode5.ZoneTransitions = zoneNode3.ZoneTransitions; queue.Enqueue(zoneNode5); } } string result = ""; if (zoneNode != null) { result = zoneNode.ZoneName; } return(result); }
/// <summary> /// Finds all the waypoints in the scenes and creates nodes on the node graph for them. /// </summary> public void GenerateNodes() { // Find all the relevant game objects in the scene GameObject[] waypointGOs = GameObject.FindGameObjectsWithTag(WaypointTag); GameObject[] zoneGOs = GameObject.FindGameObjectsWithTag(ZonesTag); GameObject[] transitionZoneGOs = GameObject.FindGameObjectsWithTag(TransitionZonesTag); // Gotta have something to work with if (zoneGOs == null || waypointGOs == null) { Debug.LogWarning("Zones or waypoints not found. Zone graph not generated."); _nodes = new ZoneNode[0]; return; } // We're going to start by keeping a list of all the nodes as we create them List <ZoneNode> waypointNodes = new List <ZoneNode>(); // Go through each object designated as a waypoint, and create nodes as appropriate for them foreach (GameObject waypointGO in waypointGOs) { // If the waypoint is on the ground, use the points above it if ((CollisionMask.value & 1 << waypointGO.layer) != 0) { List <Vector3> waypoints = GetGameObjectWaypoints(waypointGO, 2); for (int i = 0; i < waypoints.Count; i++) { Vector3 waypoint = waypoints [i]; ZoneNode newNode = new ZoneNode(AstarPath.active); newNode.position = (Int3)waypoint; newNode.Walkable = !Physics.CheckSphere(waypoint, 0.0001f, CollisionMask.value) && CanFit(waypoint); newNode.GO = waypointGO; newNode.Tag = 0; newNode.Tag |= (1 << 0); // Set ground // Note that we track of left and right ledges by ASSUMING that GetWaypoints returns the waypoint in order from left to right if (waypointGO.name.ToLower().Contains("ledge")) { if (i == 0) { newNode.Tag |= (1 << 1); } // Set Left Ledge if (i == waypoints.Count - 1) { newNode.Tag |= (1 << 2); } // Set Right Ledge } waypointNodes.Add(newNode); } } // Walls have extra waypoints to the sides else if (waypointGO.GetComponent <GrabbableObject>() != null) { List <Vector3> waypoints = GetGameObjectWaypoints(waypointGO, 1); waypoints.AddRange(GetGameObjectWaypoints(waypointGO, 3)); for (int i = 0; i < waypoints.Count; i++) { Vector3 waypoint = waypoints [i]; ZoneNode newNode = new ZoneNode(AstarPath.active); newNode.position = (Int3)waypoint; newNode.Walkable = !Physics.CheckSphere(waypoint, 0.0001f, CollisionMask.value) && CanFit(waypoint); newNode.GO = waypointGO; newNode.Tag = 0; newNode.Tag |= (1 << 4); // Set Wall waypointNodes.Add(newNode); } } // If it's a climbable object, just subdivide it and use those points else if (waypointGO.GetComponent <ClimbableObject>() != null) { List <Vector3> waypoints = GetGameObjectWaypoints(waypointGO, 4); for (int i = 0; i < waypoints.Count; i++) { Vector3 waypoint = waypoints [i]; ZoneNode newNode = new ZoneNode(AstarPath.active); newNode.position = (Int3)waypoint; newNode.Walkable = !Physics.CheckSphere(waypoint, 0.0001f, CollisionMask.value) && CanFit(waypoint); newNode.GO = waypointGO; newNode.Tag = 0; newNode.Tag |= (1 << 3); // Set Climbable waypointNodes.Add(newNode); } } // If it's not one of our three types of game objects, then we did something wrong else { Debug.LogWarning("Astar node generation found a gameobject that it doesn't know how to use!"); } } // Save all the nodes we found _nodes = waypointNodes.ToArray(); // Set up the mappings of nodes to zones _zonesWithWaypoints = new Dictionary <Bounds, List <ZoneNode> >(); foreach (GameObject zoneGO in zoneGOs) { if (!_zonesWithWaypoints.ContainsKey(zoneGO.GetComponent <Collider>().bounds)) { _zonesWithWaypoints.Add(zoneGO.GetComponent <Collider>().bounds, new List <ZoneNode>()); } } // Set up the mappings of nodes to transition zones _transitionZonesWithWaypoints = new Dictionary <Bounds, List <ZoneNode> >(); foreach (GameObject transitionZoneGO in transitionZoneGOs) { if (!_transitionZonesWithWaypoints.ContainsKey(transitionZoneGO.GetComponent <Collider>().bounds)) { _transitionZonesWithWaypoints.Add(transitionZoneGO.GetComponent <Collider>().bounds, new List <ZoneNode>()); } } // Organize all the nodes into their respective zones and transition zones foreach (ZoneNode node in _nodes) { Vector3 nodePos = (Vector3)node.position; foreach (Bounds zoneBounds in _zonesWithWaypoints.Keys) { if (zoneBounds.Contains(nodePos)) { // TODO: ACCOUNT FOR THE CASE WHERE A NODE LIES IN MORE THAN 1 ZONE nodePos.z = zoneBounds.center.z; node.position = (Int3)nodePos; node.Walkable = !Physics.CheckSphere(nodePos, 0.0001f, CollisionMask.value) && CanFit(nodePos); _zonesWithWaypoints [zoneBounds].Add(node); } } foreach (Bounds transitionZoneBounds in _transitionZonesWithWaypoints.Keys) { if (transitionZoneBounds.Contains(nodePos)) { _transitionZonesWithWaypoints [transitionZoneBounds].Add(node); } } } }
/// <summary> /// Checks if going from Node A to Node B is a valid movement for a character. /// </summary> /// <param name="A">The first node.</param> /// <param name="B">The second node.</param> /// <param name="dist">The distance between the two nodes.</param> /// <returns>Whether or not the connection between the nodes is valid.</returns> public bool IsValidConnection(ZoneNode A, ZoneNode B, out float dist) { dist = 0; // First check that both nodes are walkable if (!A.Walkable || !B.Walkable) { return(false); } // We'll be using these positions a lot, so cache them Vector3 posA = (Vector3)A.position; Vector3 posB = (Vector3)B.position; // Then check that the character is capable of jumping from the first node to the second if (!CanJump(posA, posB)) { return(false); } // Then do a basic check to see if there's any ground objects in the way Vector3 dir = posB - posA; dist = dir.magnitude; Ray ray = new Ray(posA, posB - posA); Ray invertRay = new Ray(posB, posA - posB); bool obstructedByGround = Physics.Raycast(ray, dist, CollisionMask) || Physics.Raycast(invertRay, dist, CollisionMask); if (obstructedByGround) { return(false); } // Let's avoid ridiculous situations where we have to jump in a circle to make the jump. bool bIsLeftLedge = (B.Tag & (1 << 1)) != 0; bool bIsRightLedge = (B.Tag & (1 << 2)) != 0; if ((bIsLeftLedge || bIsRightLedge) && Mathf.Abs(posB.x - posA.x) < _olympusAnimator.Radius) { return(false); } // Find out some values we'll be using bool canFall = CanFall(posA, posB) && FallClear(posA, posB); bool canJump = JumpClear(posA, posB); bool samePlatForm = (A.GO == B.GO) || A.GO.GetComponent <Collider>().bounds.Intersects(B.GO.GetComponent <Collider>().bounds); // If the waypoint are on two different platforms, make sure we are either capable of jumping over or falling over if (!samePlatForm && !canFall && !canJump) { return(false); } // avoid situations where there is already an existing path. // if (A.GO != null && B.GO != null) { // RaycastHit[] hits = Physics.RaycastAll(ray, dist); // // foreach (RaycastHit hit in hits) { // // If there's already a path, return false // if (hit.collider.CompareTag(WaypointTag) && hit.collider.gameObject != A.GO && hit.collider.gameObject != B.GO) { // return false; // } // } // // } /* * // TODO: We penalize required jumps * if(!samePlatForm && !canFall && canJump) * dist *= 2; */ bool isAWall = ((A.Tag & (1 << 4)) != 0); bool isBWall = ((B.Tag & (1 << 4)) != 0); if ((isAWall && isBWall) && posA.x != posB.x) { return(false); } // If we pass all the tests, return true return(true); }
// Make the AI input to the Animator the values that will make it reach it's defined A* Target public virtual void NavigateToAstarTarget(float speedRatio) { // We find the difference between the nodes path and the vectorpath (in case they're different), to find the nodes int nodeOffset = Path.vectorPath.Count - Path.path.Count; ZoneNode prevNode = null; if (CurrentPathWaypoint - (1 + nodeOffset) >= 0) { prevNode = (ZoneNode)Path.path [CurrentPathWaypoint - (1 + nodeOffset)]; } ZoneNode nextNode = null; if (CurrentPathWaypoint - nodeOffset < Path.path.Count) { nextNode = (ZoneNode)Path.path [CurrentPathWaypoint - nodeOffset]; } // We need to know some tag information about the nodes bool isNextNodeLeftLedge = nextNode != null && (nextNode.Tag & (1 << 1)) != 0; bool isNextNodeRightLedge = nextNode != null && (nextNode.Tag & (1 << 2)) != 0; bool isNextNodeLedge = isNextNodeLeftLedge || isNextNodeRightLedge; bool isNextNodeWall = nextNode != null && (nextNode.Tag & (1 << 4)) != 0; // Determine horizontal float horizontalDifference = NextWaypoint.x - transform.position.x; bool isNodeToRight = horizontalDifference > 0; bool isMidAir = !CharAnimator.IsGrounded; bool atTarget = AtTarget(NextWaypoint); bool shouldStayStillMidair = atTarget && isMidAir; float timeToJump = TimeToJump(transform.position + Vector3.down * (CharAnimator.Height * 0.5f), NextWaypoint); // TODO: CONFIRM THIS float desiredHorizontalJumpSpeed = horizontalDifference / timeToJump; // Try to stay still while midair and at our target location if (shouldStayStillMidair) { // Make sure we grab onto things when we can if (CharAnimator.CanHangOffObject) { if (isNextNodeRightLedge) { CharAnimator.CharInput.Horizontal = -speedRatio; } else if (isNextNodeLeftLedge) { CharAnimator.CharInput.Horizontal = speedRatio; } else { CharAnimator.CharInput.Horizontal = 0; } } else { // Get our speedratio down towards 0 (when it's less than 0.1, it's ignored) float currentSpeedRatio = CharAnimator.HorizontalSpeed / CharAnimator.Settings.MaxHorizontalSpeed; CharAnimator.CharInput.Horizontal = -currentSpeedRatio; } } // Basic horizontal movement while in the air else if (isMidAir) { // If it's in range, let's do a controlled jump if (timeToJump > 0) { float normalizedDesiredSpeed = desiredHorizontalJumpSpeed / _animator.Settings.MaxHorizontalSpeed; CharAnimator.CharInput.Horizontal = normalizedDesiredSpeed; } // But if it's too far, go all out else { CharAnimator.CharInput.Horizontal = isNodeToRight ? 1 : -1; } } // Basic horizontal movement while on the ground else { // Normally it's easy to just move left or right depending on location of next node CharAnimator.CharInput.Horizontal = isNodeToRight ? speedRatio : -speedRatio; // But it's important to stop moving while landing if (CharAnimator.IsLanding) { CharAnimator.CharInput.Horizontal = 0; } } // Determine vertical CharAnimator.CharInput.Vertical = NextWaypoint.y - transform.position.y; if (atTarget && nextNode != null && isNextNodeLedge) { CharAnimator.CharInput.Vertical = 1; } // Press up whenever we're on a ledge // Determine jump bool isClimbing = _animator.IsClimbing; bool isTurningAround = _animator.IsTurningAround; bool isNodeAbove = NextWaypoint.y - _animator.transform.position.y > 0; bool isNodeOnOtherPlatform = false; if (prevNode != null && nextNode != null) { isNodeOnOtherPlatform = (prevNode.GO != nextNode.GO) && !prevNode.GO.GetComponent <Collider>().bounds.Intersects(nextNode.GO.GetComponent <Collider>().bounds); } bool canFall = GameManager.AI.Graph.CanFall(transform.position, NextWaypoint); bool shouldJump = isNodeAbove || (isNodeOnOtherPlatform && !canFall); bool canJump = !isTurningAround && !CharAnimator.IsLanding && (!isMidAir || isClimbing); bool jump = canJump && shouldJump; // We need to determine how we jump on the one frame that we do decide to jump if (jump) { // Always clear out our horizontal input on the frame that we jump CharAnimator.CharInput.Horizontal = 0; // NOTE: WE HAVE THIS CHECK BECAUSE WE CAN GET A NEGATIVE TIME TO JUMP IF WE'RE GOING TO A LEDGE REALLY HIGH UP. // THIS THROWS OFF OUR HORIZONTAL SPEED CALCULATION. // WE ASSUME THAT THE LEDGE IS REALLY CLOSE. THIS MIGHT BE BAD. bool highJumpHasBadHorizontal = (timeToJump < 0) && isNextNodeLedge; // Do a normal jump if we can make the jump using our normal speedRatio if (!isNextNodeWall && (highJumpHasBadHorizontal || Mathf.Abs(desiredHorizontalJumpSpeed) < Mathf.Abs(speedRatio * CharAnimator.Settings.MaxHorizontalSpeed))) { CharAnimator.CharInput.Jump = Vector2.up; } // But sometimes we need to take big jumps else if (isNodeToRight) { CharAnimator.CharInput.Jump = new Vector2(1, 1); } else { CharAnimator.CharInput.Jump = new Vector2(-1, 1); } // Always make sure we face the direction of the ledge before we jump (since we can't change direction midair) if ((isNextNodeLeftLedge && CharAnimator.Direction.x < 0) || (isNextNodeRightLedge && CharAnimator.Direction.x > 0)) { CharAnimator.CharInput.Pickup = true; CharAnimator.CharInput.Jump = Vector2.zero; } } else { CharAnimator.CharInput.Jump = Vector2.zero; } // Account for things that might be in the way of our movement // This might include obstacles above our head and nodes that are below the ground under our feet Vector3 xExtension = Vector3.right * CharAnimator.Radius; bool isLeftClear = CheckClear(FootPosition - xExtension, NextWaypoint); bool isMiddleClear = CheckClear(FootPosition, NextWaypoint); bool isRightClear = CheckClear(FootPosition + xExtension, NextWaypoint); if (isLeftClear && (!isMiddleClear || !isRightClear)) { CharAnimator.CharInput.Horizontal = -speedRatio; } else if (isRightClear && (!isMiddleClear || !isLeftClear)) { CharAnimator.CharInput.Horizontal = speedRatio; } }