//Called for every fixedupdate that this module is active public override void FixedUpdateModule() { if (!m_ControlledCollider.GetSideCastInfo().m_HasHitSide || (!m_ControlledCollider.IsPartiallyTouchingWall())) { return; } m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); float distance = m_ControlledCollider.GetSideCastInfo().GetDistance(); m_ControlledCollider.GetCapsuleTransform().Move(-m_WallNormal * distance); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); m_ControlledCollider.RotateToAlignWithNormal(m_UpDirection, m_RotateMethod); Vector2 currentVel = m_ControlledCollider.GetVelocity(); Vector2 fGravity = -m_UpDirection *m_WallRunGravity *Vector2.Dot(-m_UpDirection, Vector2.down); //Gravity along wall, but with correct velocity Vector2 fDrag = -0.5f * (currentVel.sqrMagnitude) * m_CharacterController.GetDragConstant() * currentVel.normalized; if (!m_ApplyDrag) { fDrag = Vector2.zero; } Vector2 summedF = fGravity + fDrag; Vector2 newVel = currentVel + (summedF * Time.fixedDeltaTime); m_ControlledCollider.UpdateWithVelocity(newVel); }
//Query whether this module can be active, given the current state of the character controller (velocity, isGrounded etc.) //Called every frame when inactive (to see if it could be) and when active (to see if it should not be) public override bool IsApplicable() { if (m_ControlledCollider.IsGrounded()) { return(false); } //Wall needs to be hit for the module to be active if (m_ControlledCollider.GetSideCastInfo().m_HasHitSide) { if (!m_ControlledCollider.IsPartiallyTouchingWall()) { return(false); } m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); if (!m_ControlledCollider.CanAlignWithNormal(m_UpDirection)) { return(false); } return(true); } return(false); }
//Easy way to get a direction along the ground public override Vector2 GetWalkDirection(Vector2 a_Speed) { if (a_Speed.x == 0 || !m_IsGrounded) { return(Vector2.zero); } return(CState.GetDirectionAlongNormal(a_Speed, GetNormal())); }
//This takes the results of a sidecast and tries to determine if a wall is found. //Using the sidecast information, it sends out three probes (from the center of the top and bottom hemispheres and the center of the capsule). //It counts the amount of hits that are valid and stores this for other classes to access. void WallCast(RaycastHit a_SidecastHit) { if (Vector3.Angle(Vector3.up, a_SidecastHit.normal) > m_CapsuleCollider.GetMaxWallAngle() || Vector3.Angle(Vector3.up, a_SidecastHit.normal) < m_CapsuleCollider.GetMaxGroundedAngle()) { m_WallCastCount = 0; return; } float wallCastMargin = m_CapsuleCollider.GetWallCastMargin(); float wallCastDistance = m_CapsuleCollider.GetWallCastDistance(); Vector3 hitPos = a_SidecastHit.point; Vector3 normal = a_SidecastHit.normal; Vector3 currentUp = m_CapsuleCollider.GetUpDirection(); Vector3 direction = CState.GetDirectionAlongNormal(Vector3.up, normal); Vector3 startHitPos1 = Vector3.zero; Vector3 startHitPos2 = Vector3.zero; Vector3 startHitPos3 = Vector3.zero; Vector3 normalOff = normal * (m_CapsuleCollider.GetRadius() - wallCastMargin); //Determines where the sidecast has hit. This to orient the probes in the correct way. if (Vector3.Dot(currentUp, hitPos) <= Vector3.Dot(currentUp, m_CapsuleCollider.GetDownCenter()))//Hit on the lower hemisphere { startHitPos1 = m_CapsuleCollider.GetDownCenter() + direction * m_CapsuleCollider.GetDefaultLength() - normalOff; startHitPos2 = m_CapsuleCollider.GetDownCenter() - normalOff; startHitPos3 = m_CapsuleCollider.GetDownCenter() + direction * m_CapsuleCollider.GetDefaultLength() * 0.5f - normalOff; } else if (Vector3.Dot(currentUp, hitPos) >= Vector3.Dot(currentUp, m_CapsuleCollider.GetUpCenter()))//Hit on the upper hemisphere { startHitPos1 = m_CapsuleCollider.GetUpCenter() - direction * m_CapsuleCollider.GetDefaultLength() - normalOff; startHitPos2 = m_CapsuleCollider.GetUpCenter() - normalOff; startHitPos3 = m_CapsuleCollider.GetUpCenter() - direction * m_CapsuleCollider.GetDefaultLength() * 0.5f - normalOff; } else //Hit somewhere in the middle { startHitPos1 = m_CapsuleCollider.GetDownCenter(true) + direction * m_CapsuleCollider.GetDefaultLength() - normalOff; startHitPos2 = m_CapsuleCollider.GetDownCenter(true) - normalOff; startHitPos3 = m_CapsuleCollider.GetDownCenter(true) + direction * m_CapsuleCollider.GetDefaultLength() * 0.5f - normalOff; } RaycastHit newHit; m_WallCastCount = 0; if (Physics.Raycast(startHitPos1, -normal, out newHit, wallCastDistance + wallCastMargin, m_CapsuleCollider.GetLayerMask())) { m_WallCastCount++; } if (Physics.Raycast(startHitPos2, -normal, out newHit, wallCastDistance + wallCastMargin, m_CapsuleCollider.GetLayerMask())) { m_WallCastCount++; } if (Physics.Raycast(startHitPos3, -normal, out newHit, wallCastDistance + wallCastMargin, m_CapsuleCollider.GetLayerMask())) { m_WallCastCount++; } }
//Called for every fixedupdate that this module is active public override void FixedUpdateModule() { m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); m_ControlledCollider.RotateToAlignWithNormal(m_UpDirection, m_RotateMethod); float distance = m_ControlledCollider.GetSideCastInfo().GetDistance(); m_ControlledCollider.GetCapsuleTransform().Move(-m_WallNormal * distance); m_ControlledCollider.UpdateWithVelocity(Vector2.zero); }
void FixedUpdate() { CSideCastInfo sideCastInfo = m_Collider.GetSideCastInfo(); if (sideCastInfo.m_WallCastCount >= 2) { Vector2 currentVel = m_Collider.GetVelocity(); float dot = Vector3.Dot(currentVel, CState.GetDirectionAlongNormal(currentVel, sideCastInfo.GetSideNormal())); if (dot >= m_Threshold) { m_ParticleSystem.transform.position = sideCastInfo.GetSidePoint(); m_ParticleSystem.transform.LookAt(sideCastInfo.GetSidePoint() + new Vector3(sideCastInfo.GetSideNormal().x, sideCastInfo.GetSideNormal().y, 0.0f), Vector3.back); m_ParticleSystem.Emit(m_EmissionCount); } } }
//Called for every fixedupdate that this module is active public override void FixedUpdateModule() { m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); m_ControlledCollider.RotateToAlignWithNormal(m_UpDirection, m_RotateMethod); //if touching a wall during a jump, cut it short when jump is released if (m_HonorJumpCut) { m_CharacterController.UpdateJumpCut(); } Vector2 currentVel = m_ControlledCollider.GetVelocity(); Vector2 fInput = m_CharacterController.GetDirectedInputMovement() * m_EscapeVelocity; if (m_WallNormal.y >= 0.1f) { fInput = Vector2.zero; } float inputDotToNormal = Vector2.Dot(fInput, m_WallNormal); if (inputDotToNormal <= 0)//Moving into wall, or not moving at all { fInput = Vector2.zero; float distance = m_ControlledCollider.GetSideCastInfo().GetDistance(); m_ControlledCollider.GetCapsuleTransform().Move(-m_WallNormal * distance); } Vector2 fGravity = -m_UpDirection * m_SlideGravity;// if (!m_UseSameGravityForAllSlopes) { fGravity *= Vector2.Dot(-m_UpDirection, Vector2.down);//Gravity along wall, but with corrected gravity } Vector2 fDrag = -0.5f * (currentVel.sqrMagnitude) * m_CharacterController.GetDragConstant() * currentVel.normalized; Vector2 summedF = fInput + fGravity + fDrag; Vector2 newVel = currentVel + (summedF * Time.fixedDeltaTime); newVel += GetFrictionAlongWall(newVel); m_ControlledCollider.UpdateWithVelocity(newVel); }
protected override void GeneratePath() { CEdgeCastInfo info = m_ControlledCollider.GetEdgeCastInfo(); m_Path.Clear(); CapsuleTransform copy = m_ControlledCollider.GetCapsuleTransformCopy(); //First node is in edgehang alignment, moving away from the edge mildly CapsuleMovementPathNode newNode = m_Path.CreateFirstNode(copy); Vector3 upCenter = info.GetProposedHeadPoint(); upCenter += m_ControlledCollider.GetEdgeCastInfo().GetWallNormal() * 0.03f; copy.SetUpCenter(upCenter); copy.Rotate(info.GetUpDirection(), RotateMethod.FromTop); newNode = m_Path.DuplicateAndAddLastNode(); newNode.CopyFromTransform(copy); //Second node moves up along local up, until the bottom can slide over the edge newNode = m_Path.DuplicateAndAddLastNode(); newNode.m_Duration = m_MoveUpTime; float contactDot = Vector3.Dot(info.GetEdgeNormal(), info.GetEdgePoint()); float bottomDot = Vector3.Dot(info.GetEdgeNormal(), copy.GetDownCenter()) - m_ControlledCollider.GetRadius() - m_MoveUpMargin; float normalDot = Vector3.Dot(info.GetEdgeNormal(), copy.GetUpDirection()); float rawDistance = contactDot - bottomDot; float distance = rawDistance / normalDot; newNode.m_Position += copy.GetUpDirection() * distance; newNode.ApplyEntireMovement(copy); //Third node snaps the capsule to the "upright" position for the ground it's going to stand on newNode = m_Path.DuplicateAndAddLastNode(); newNode.m_Duration = 0.0f; newNode.m_UpDirection = (m_CharacterController.GetAlignsToGround()) ? info.GetEdgeNormal() : Vector3.up; newNode.m_RotateMethod = RotateMethod.FromBottom; newNode.ApplyEntireMovement(copy); newNode.m_Position = copy.GetPosition(); //Final node moves the capsule over the ground newNode = m_Path.DuplicateAndAddLastNode(); newNode.m_Duration = m_MoveSideTime; Vector3 direction = CState.GetDirectionAlongNormal(-info.GetWallNormal(), info.GetEdgeNormal()); newNode.m_Position += direction * m_MoveSideDistance; newNode.ApplyEntireMovement(copy); }
//Called for every fixedupdate that this module is active public override void FixedUpdateModule() { m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); m_ControlledCollider.RotateToAlignWithNormal(m_UpDirection, m_RotateMethod); float distance = m_ControlledCollider.GetSideCastInfo().GetDistance(); m_ControlledCollider.GetCapsuleTransform().Move(-m_WallNormal * distance); Vector2 newVel = Vector2.zero; if (GetDirInput("Move").m_Direction == DirectionInput.Direction.Down || GetDirInput("Move").m_Direction == DirectionInput.Direction.Up) { newVel = m_UpDirection * GetDirInput("Move").m_ClampedInput.y *m_Speed; } m_ControlledCollider.UpdateWithVelocity(newVel); }
void FixedUpdate() { if (m_Collider.IsGrounded()) { CGroundedInfo groundedInfo = m_Collider.GetGroundedInfo(); Vector2 currentVel = m_Collider.GetVelocity(); float dot = Vector3.Dot(currentVel, CState.GetDirectionAlongNormal(currentVel, groundedInfo.GetNormal())); if (dot >= m_LowThreshold) { int emission = m_LowEmissionCount; if (dot >= m_HighThreshold) { emission = m_HighEmissionCount; } m_ParticleSystem.transform.position = groundedInfo.GetPoint(); m_ParticleSystem.transform.LookAt(groundedInfo.GetPoint() + new Vector3(groundedInfo.GetNormal().x, groundedInfo.GetNormal().y, 0.0f), Vector3.back); m_ParticleSystem.Emit(emission); } } }
//Query whether this module can be active, given the current state of the character controller (velocity, isGrounded etc.) //Called every frame when inactive (to see if it could be) and when active (to see if it should not be) public override bool IsApplicable() { if (m_ControlledCollider.IsGrounded()) { return(false); } //Wall needs to be hit for the module to be active if (m_ControlledCollider.GetSideCastInfo().m_HasHitSide) { if (m_IsActive) { if (!m_ControlledCollider.IsPartiallyTouchingWall()) { return(false); } } else { if (!m_ControlledCollider.IsCompletelyTouchingWall()) { return(false); } } m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); if (!m_ControlledCollider.CanAlignWithNormal(m_UpDirection)) { return(false); } RaycastHit hit; //If moving down, and possibly entering a position where it cannot rotate back (and therefore escape), stop before this happens if (Vector3.Dot(m_ControlledCollider.GetVelocity(), m_UpDirection) < 0 && Physics.Raycast(m_ControlledCollider.GetDownCenter(), -m_UpDirection, out hit, m_ControlledCollider.GetRadius() + m_EscapeCornerRaycastDistance, m_ControlledCollider.GetLayerMask())) { return(false); } return(true); } return(false); }
//Execute jump (lasts one update) //Called for every fixedupdate that this module is active public override void FixedUpdateModule() { Vector2 currentVel = m_ControlledCollider.GetVelocity(); //Make sure that current velocity is aligned to the side wall currentVel = CState.GetDirectionAlongNormal(currentVel, m_SideNormal) * currentVel.magnitude; //If sliding down the wall when jumping, reset that velocity if (m_ResetVerticalSpeedIfFalling) { currentVel.y = Mathf.Clamp(currentVel.y, 0.0f, float.MaxValue); } //Possibly inherit some velocity from before jumping, leading to increased velocity on jump currentVel.y *= m_VerticalVelocityInheritanceFactor; currentVel.x *= m_HorizontalVelocityInheritanceFactor; Vector2 up = (m_UseWallUpDirection) ? CState.GetDirectionAlongNormal(Vector2.up, m_SideNormal) : Vector2.up; Vector2 jumpDirection = Vector2.Lerp(up, m_SideNormal, m_JumpAlignedToWallFactor).normalized; Vector2 jumpVelocity = jumpDirection * m_JumpVelocity; m_CharacterController.Jump(currentVel + jumpVelocity); m_ControlledCollider.UpdateWithVelocity(m_ControlledCollider.GetVelocity()); }
Vector2 GetFrictionAlongWall(Vector2 a_Velocity) { Vector2 directionAlongWall = CState.GetDirectionAlongNormal(a_Velocity, new Vector2(m_WallNormal.x, m_WallNormal.y)); if (!m_ApplyFrictionIfGoingUpwards && directionAlongWall.y > 0)//Going upwards { return(Vector2.zero); } Vector2 frictionDirection = -directionAlongWall; Vector2 maxFrictionSpeedChange = frictionDirection * m_SlideFriction * Time.fixedDeltaTime; Vector2 velInDirection = Mathf.Abs(Vector2.Dot(a_Velocity, frictionDirection)) * frictionDirection; if (velInDirection.magnitude > maxFrictionSpeedChange.magnitude) { return(maxFrictionSpeedChange); } else { return(velInDirection); } }
//Side cast receives the results of two capsulecasts (to the left and the right of the capsule). //It uses this first to determine whether there is a hit to the side of the capsule, and then whether or not this hit could signify a wall. //Mostvalidhit stores information on this sidecast. Sidecast information can sometimes be used without wallcast information public void UpdateWithCollisions(List <RaycastHit> leftHitResults, List <RaycastHit> rightHitResults) { m_HasHitSide = false; m_WallCastCount = 0; if (leftHitResults.Count == 0 && rightHitResults.Count == 0) { return; } List <RaycastHit> resultsToUse = null; //First determine which side to use (based on distance) if (leftHitResults.Count == 0) { resultsToUse = rightHitResults; } else if (rightHitResults.Count == 0) { resultsToUse = leftHitResults; } else { float lowestSideDistance = float.MaxValue; for (int i = 0; i < leftHitResults.Count; i++) { if (leftHitResults[i].point.y < lowestSideDistance) { lowestSideDistance = leftHitResults[i].distance; resultsToUse = leftHitResults; } } for (int i = 0; i < rightHitResults.Count; i++) { if (rightHitResults[i].point.y < lowestSideDistance) { lowestSideDistance = rightHitResults[i].distance; resultsToUse = rightHitResults; } } } m_HasHitSide = true; m_MostValidHit = resultsToUse[0]; float lowestDistance = float.MaxValue; for (int i = 0; i < resultsToUse.Count; i++) { if (resultsToUse[i].distance < lowestDistance) { lowestDistance = resultsToUse[i].distance; m_MostValidHit = resultsToUse[i]; } } //Capsulecasts can give unusable results on corners. //Cast a little bit up and down from the initial point to get a better normal. RaycastHit correctingCast1, correctingCast2; Vector3 dirAlongNormal = CState.GetDirectionAlongNormal(Vector3.up, m_MostValidHit.normal); bool correctCast1Hit = Physics.Raycast(m_MostValidHit.point + m_MostValidHit.normal * 0.02f + dirAlongNormal * 0.01f, -m_MostValidHit.normal, out correctingCast1, 0.03f, m_CapsuleCollider.GetLayerMask()); bool correctCast2Hit = Physics.Raycast(m_MostValidHit.point + m_MostValidHit.normal * 0.02f - dirAlongNormal * 0.01f, -m_MostValidHit.normal, out correctingCast2, 0.03f, m_CapsuleCollider.GetLayerMask()); if (correctCast1Hit || correctCast2Hit) { RaycastHit correctCastToUse = m_MostValidHit; if (correctCast1Hit && correctCast2Hit) { float dot1 = Vector3.Dot(m_MostValidHit.normal, correctingCast1.normal); float dot2 = Vector3.Dot(m_MostValidHit.normal, correctingCast2.normal); if (dot1 < 0.95f && dot2 < 0.95f) { if (dot1 > dot2) { correctCastToUse = correctingCast1; } else { correctCastToUse = correctingCast2; } } } else if (correctCast1Hit) { if (Vector3.Dot(m_MostValidHit.normal, correctingCast1.normal) < 0.95f) { correctCastToUse = correctingCast1; } } else { if (Vector3.Dot(m_MostValidHit.normal, correctingCast2.normal) < 0.95f) { correctCastToUse = correctingCast2; } } m_MostValidHit = correctCastToUse; } m_Distance = lowestDistance - m_CapsuleCollider.GetSideCastMargin(); WallCast(m_MostValidHit); }
void Update() { if (m_CharacterController == null || m_CharacterController.GetCollider() == null) { Debug.LogError("Sprite animator can't find properly set-up character"); this.enabled = false; return; } if (Mathf.Abs(m_CharacterController.GetCollider().GetVelocity().x) >= m_MoveSpeedThreshhold) { m_LastGoodDirection = m_CharacterController.GetCollider().GetVelocity().normalized; } else if (Mathf.Abs(m_CharacterController.GetInputMovement().x) > 0.0f) { m_LastGoodDirection = Vector2.right * m_CharacterController.GetInputMovement().x; } //Rotate and position the capsule according to character velocity and context float zRot = 0.0f; float xScale = 1.0f; if (m_CurrentAnimationName != "") { if (m_CharacterController.GetCollider().IsGrounded()) { Vector2 normal = m_CharacterController.GetCollider().GetGroundedInfo().GetNormal(); xScale = (m_LastGoodDirection.x >= 0.0f) ? 1.0f : -1.0f; zRot = -Mathf.Atan2(normal.x, normal.y) * Mathf.Rad2Deg; } else if (m_CharacterController.GetCollider().IsPartiallyTouchingWall()) { Vector2 normal = m_CharacterController.GetCollider().GetSideCastInfo().GetSideNormal(); Vector2 up = CState.GetDirectionAlongNormal(Vector2.up, normal); xScale = ((normal.x <= 0.0f) ? 1.0f : -1.0f); zRot = -Mathf.Atan2(up.x, up.y) * Mathf.Rad2Deg; m_LastGoodDirection = -normal; } else if (m_CharacterController.GetCollider().IsTouchingEdge()) { Vector2 normal = m_CharacterController.GetCollider().GetEdgeCastInfo().GetWallNormal(); Vector2 up = CState.GetDirectionAlongNormal(Vector2.up, normal); xScale = ((normal.x <= 0.0f) ? 1.0f : -1.0f); zRot = -Mathf.Atan2(up.x, up.y) * Mathf.Rad2Deg; m_LastGoodDirection = -normal; } else { zRot = 0.0f; xScale = (m_LastGoodDirection.x >= 0.0f) ? 1.0f : -1.0f; Vector2 up = m_CharacterController.GetCurrentVisualUp(); if (up != Vector2.up) { zRot = -Mathf.Atan2(up.x, up.y) * Mathf.Rad2Deg; } } } if (!m_BlockRotation) { zRot = Mathf.Lerp(m_LastZRot, zRot, 0.1f); m_SpriteTransformHook.transform.rotation = Quaternion.Euler(0.0f, 0.0f, zRot); } m_SpriteTransformHook.transform.localScale = new Vector3(xScale, 1.0f, 1.0f); m_LastZRot = zRot; Vector3 startPosition = Vector3.zero; //We are being controlled by a different entity, block interpolation if (m_CharacterController.IsMovementLocked()) { startPosition = m_CharacterController.transform.position; } else { startPosition = GetInterpolatedPosition(); } //Take the bottom of the capsule collider to rotate from //Animations themselves might adjust position via localposition Vector3 heightAdjustment = m_CapsuleCollider.GetUpDirection() * (-m_CapsuleCollider.GetLength() * 0.5f); m_SpriteTransformHook.transform.position = startPosition + heightAdjustment; StartAnimation(m_CharacterController.GetCurrentSpriteState()); }
//This context uses raycasts to determine if a ledge is present. //The raycasts originate from above and to the left/right of the character, and are casted downwards public void UpdateWithCollisions() { RaycastHit rightHit; RaycastHit leftHit; bool rightHasHit = false; bool leftHasHit = false; m_HasHitEdge = false; Vector3 upCenter = m_CapsuleCollider.GetUpCenter(); Vector3 upDirection = m_CapsuleCollider.GetUpDirection(); Vector3 transformRight = m_CapsuleCollider.transform.right; //Orient against wall to the side, even if not able to wallsliding //This allows the character to grab onto ledges on slanted walls CSideCastInfo sideInfo = m_CapsuleCollider.GetSideCastInfo(); if (sideInfo.m_HasHitSide) { Vector3 sideNormal = sideInfo.GetSideNormal(); Vector3 sidePoint = sideInfo.GetSidePoint(); Vector3 sideUp = CState.GetDirectionAlongNormal(Vector3.up, sideNormal); RaycastHit properSideHit; if (Physics.Raycast(sidePoint + sideNormal * 0.2f + sideUp * 0.05f, -sideNormal, out properSideHit, 0.3f, m_CapsuleCollider.GetLayerMask())) { Vector3 newPoint = properSideHit.point; Vector3 newNormal = properSideHit.normal; Vector3 newUp = CState.GetDirectionAlongNormal(Vector3.up, newNormal); Vector3 pivot = upCenter - newPoint; Quaternion rotation = Quaternion.FromToRotation(upDirection, newUp); Vector3 newUpCenter = newPoint + rotation * pivot; CapsuleTransform transform = m_CapsuleCollider.GetCapsuleTransformCopy(); transform.SetUpDirection(newUp); transform.SetUpCenter(newUpCenter); upCenter = newUpCenter; upDirection = newUp; transformRight = transform.GetRightDirection(); } } //Determine the starting positions of the raycasts Vector3 centerStart = upCenter + upDirection * (m_CapsuleCollider.GetRadius() + m_CapsuleCollider.GetEdgeCastVerticalMargin()); Vector3 sideOffSet = transformRight * (m_CapsuleCollider.GetRadius() + m_CapsuleCollider.GetEdgeCastHorizontalDistance()); Vector3 grabLength = -upDirection * (m_CapsuleCollider.GetEdgeCastVerticalDistance() + m_CapsuleCollider.GetEdgeCastVerticalMargin()); //The raycasts originate from above and to the left/right of the character, and are casted downwards //The angle of the slope they hit can't be too steep, or the character will slide off. if (Physics.Raycast(centerStart + sideOffSet, -upDirection, out rightHit, grabLength.magnitude, m_CapsuleCollider.GetLayerMask())) { if (Vector3.Angle(rightHit.normal, upDirection) < m_CapsuleCollider.GetMaxGrabAngle()) { rightHasHit = true; } } if (Physics.Raycast(centerStart - sideOffSet, -upDirection, out leftHit, grabLength.magnitude, m_CapsuleCollider.GetLayerMask())) { if (Vector3.Angle(leftHit.normal, upDirection) < m_CapsuleCollider.GetMaxGrabAngle()) { leftHasHit = true; } } if (rightHasHit || leftHasHit) { RaycastHit castToUse; Vector3 wallProbeDirection; if (rightHasHit && leftHasHit) { if (leftHit.distance < rightHit.distance) { castToUse = leftHit; wallProbeDirection = -transformRight; } else { castToUse = rightHit; wallProbeDirection = transformRight; } } else if (rightHasHit) { castToUse = rightHit; wallProbeDirection = transformRight; } else { castToUse = leftHit; wallProbeDirection = -transformRight; } //Check if the character can even hold on. The distance between the character and the start of the raycastpoint should not be blocked by a collider (no grabbing on ledges on the other side of walls if (Physics.Raycast(centerStart, wallProbeDirection, m_CapsuleCollider.GetRadius() + m_CapsuleCollider.GetEdgeCastHorizontalDistance(), m_CapsuleCollider.GetLayerMask())) { return; } wallProbeDirection = CState.GetDirectionAlongNormal(wallProbeDirection, castToUse.normal); //After detecting that collider can hang onto edge, orient properly against surface RaycastHit probeHit; Vector3 probedNormal = CState.GetDirectionAlongNormal(-wallProbeDirection, Vector3.up); if (Physics.Raycast(upCenter, wallProbeDirection, out probeHit, m_CapsuleCollider.GetRadius() + m_CapsuleCollider.GetEdgeAlignProbeDistance(), m_CapsuleCollider.GetLayerMask())) { float angle = Vector3.Angle(probeHit.normal, Vector3.up); if (angle < m_CapsuleCollider.GetMaxEdgeAlignAngle()) { probedNormal = probeHit.normal; } } Vector3 newUpDirection = CState.GetDirectionAlongNormal(Vector3.up, probedNormal); Vector3 headPoint = castToUse.point + probedNormal * (m_CapsuleCollider.GetRadius() + m_CapsuleCollider.GetEdgeCastHorizontalDistance()) - newUpDirection * m_CapsuleCollider.GetRadius(); //Block check //Check if something is obstructing the proposed headposition (and fix if so) RaycastHit blockHit; if (Physics.Raycast(headPoint, -probedNormal, out blockHit, m_CapsuleCollider.GetRadius(), m_CapsuleCollider.GetLayerMask())) { if (blockHit.distance < m_CapsuleCollider.GetRadius()) { Vector3 alongNormal = -CState.GetDirectionAlongNormal(wallProbeDirection, castToUse.normal); castToUse.point += alongNormal * (m_CapsuleCollider.GetRadius() - blockHit.distance) / Vector3.Dot(alongNormal, blockHit.normal); headPoint = castToUse.point + probedNormal * (m_CapsuleCollider.GetRadius() + m_CapsuleCollider.GetEdgeCastHorizontalDistance()) - newUpDirection * m_CapsuleCollider.GetRadius(); } } //Rotation check, check if character is aligned to wall or dangling //then check if that rotation can be achieved CapsuleTransform copy = m_CapsuleCollider.GetCapsuleTransformCopy(); copy.SetUpCenter(headPoint); if (Vector3.Angle(Vector3.up, probedNormal) > m_CapsuleCollider.GetMaxWallAngle() || Vector3.Angle(Vector3.up, probedNormal) < m_CapsuleCollider.GetMaxGroundedAngle()) { return; } if (copy.CanRotate(newUpDirection, RotateMethod.FromTop)) { m_EdgePoint = castToUse.point; m_EdgeNormal = castToUse.normal; m_EdgeTransform = castToUse.transform; m_WallNormal = probedNormal; m_UpDirection = newUpDirection; m_ProposedHeadPosition = headPoint; m_HasHitEdge = true; } } }
//Query whether this module can be active, given the current state of the character controller (velocity, isGrounded etc.) //Called every frame when inactive (to see if it could be) and when active (to see if it should not be) public override bool IsApplicable() { if (m_ControlledCollider.IsGrounded()) { return(false); } //Do allow if we just jumped up if (m_CharacterController.DidJustJump()) { m_IsAlreadyTouchingWall = false; } if ((DoesInputExist("Crouch") && GetButtonInput("Crouch").m_IsPressed) || GetDirInput("Move").m_Direction == DirectionInput.Direction.Down) { return(false); } //Disable when already touching wall beforehand, except when coming from standing position if (!m_IsActive && m_AllowOnlyWhenJustTouchingWall && (Time.time - m_CharacterController.GetLastGroundedTime() >= 0.02f)) { if (m_ControlledCollider.GetSideCastInfo().m_HasHitSide&& (m_ControlledCollider.IsCompletelyTouchingWall())) { if (m_IsAlreadyTouchingWall) { return(false); } m_IsAlreadyTouchingWall = true; } else { m_IsAlreadyTouchingWall = false; return(false); } } if (m_ControlledCollider.GetSideCastInfo().m_HasHitSide) { if (m_IsActive) { if (!m_ControlledCollider.IsPartiallyTouchingWall()) { return(false); } } else { if (!m_ControlledCollider.IsCompletelyTouchingWall()) { return(false); } } m_WallNormal = m_ControlledCollider.GetSideCastInfo().GetSideNormal(); m_RotateMethod = (m_WallNormal.y < 0) ? RotateMethod.FromTop : RotateMethod.FromBottom; m_UpDirection = CState.GetDirectionAlongNormal(Vector3.up, m_WallNormal); float upVelDot = Vector2.Dot(m_UpDirection, m_ControlledCollider.GetVelocity()); if (m_DisableWhenBelowVelocity && upVelDot <= m_MinimumUpVelocityOnStart) //Already going downwards, not applicable { return(false); } if (!m_ControlledCollider.CanAlignWithNormal(m_UpDirection)) { return(false); } return(true); } return(false); }