protected void DoClimbing() { float fHorAxis = 0f; float fVerAxis = 0f; if (GetActionState(eControllerActions.Right)) { fHorAxis += m_horSpeedScale; } if (GetActionState(eControllerActions.Left)) { fHorAxis -= m_horSpeedScale; } if (GetActionState(eControllerActions.Up)) { fVerAxis += m_verSpeedScale; } if (GetActionState(eControllerActions.Down)) { fVerAxis -= m_verSpeedScale; } if (m_isClimbing) { if ( GetIfActionHasChanged(eControllerActions.Jump) && GetActionState(eControllerActions.Jump) || GetIfActionHasChanged(eControllerActions.PlatformDropDown) && GetActionState(eControllerActions.PlatformDropDown)) { m_isClimbing = false; m_currentClimbingCollider = null; m_isGrounded = true; return; } // isLadder is true when the collider width is small enough to avoid moving horizontal and center the player in the center of the collider. // By default, it is true when collider width is less than two times the smart rect collider width bool isLadder = m_currentClimbingCollider.bounds.size.x < m_ladderWidthFactor * m_smartCollider.Size.x; Vector3 vDisp = new Vector3(isLadder ? 0f : fHorAxis, fVerAxis); //debug climbing area DebugEx.DebugDrawRect(Vector2.zero, new Rect(m_currentClimbingCollider.bounds.min, m_currentClimbingCollider.bounds.size), Color.blue); if (isLadder) { // Snap to ladder Vector3 center = m_currentClimbingCollider.bounds.center; Vector3 snapPos = Vector3.Project(transform.position - center, m_currentClimbingCollider.transform.up); // this allow rotated ladders, like in pirate ship demo snapPos += center; snapPos.z = transform.position.z; transform.position = Vector3.Lerp(transform.position, snapPos, 0.5f); } if (vDisp.magnitude > 0.2f) { transform.position += transform.rotation * vDisp * ClimbingSpeed * Time.deltaTime; } m_isGrounded = m_smartCollider.enabled && m_smartCollider.IsGrounded(); } // Check if going down and there is a climbing collider below float SkinBottomWidthFactor = 1.1f; //NOTE: set a value > 1f to allow climbing down when climb collision and platform collision are close Collider2D climbingColliderBelow = GetClimbingColliderBelow(SkinBottomWidthFactor); Collider2D climbingColliderAbove = GetClimbingColliderAbove(); if (fVerAxis < -0.5f && m_currentClimbingCollider == null) { if (climbingColliderBelow != null) { if (m_currentClimbingCollider == null && climbingColliderAbove == null) { //Teleport the player. TeleportTo will skip any collider in between the current position and the new position to skip any platform in between m_smartCollider.TeleportTo(transform.position - transform.up * m_smartCollider.SkinBottomWidth * SkinBottomWidthFactor); } m_currentClimbingCollider = climbingColliderBelow; StartClimbing(); } else { StopClimbing(); } } // Check if going up and it is inside a climbing collider else if (fVerAxis > 0.5f) { if (climbingColliderAbove != null && !GetIfActionHasChanged(eControllerActions.Jump)) { m_currentClimbingCollider = climbingColliderAbove; StartClimbing(); } else if (m_smartCollider.SkinBottomRayContacts.Contains(true) || climbingColliderBelow == null) { StopClimbing(); } } // Stop climbing once the top is reached else if (m_isGrounded || (climbingColliderBelow == null && climbingColliderAbove == null)) { StopClimbing(); } }
protected Vector3 _DoSolveStaticCollisionSide(IList <Vector3> vCheckPoints, IList <bool> vRayContacts, Vector3 vSkin, Vector3 vOffset, LayerMask layerMask, LayerMask movingPlatformLayerMask) { float collClosestDist = float.MaxValue; float skinMagnitude = vSkin.magnitude; Vector3 vClosestHitNorm = Vector3.zero; Vector3 vClosestHit = Vector3.zero; GameObject collidedObject = null; Vector3 vAppliedForce = Vector3.Project(m_instantVelocity, vSkin) / vCheckPoints.Count; float fOffsetDist = Vector3.Scale(vOffset, transform.localScale).magnitude; LayerMask oneWayLayerMask = (OneWayCollisionDown | OneWayCollisionUp | OneWayCollisionLeft | OneWayCollisionRight); for (int i = 0; i < vCheckPoints.Count; ++i) { vRayContacts[i] = false; Vector3 vCheckPos = vCheckPoints[i]; Ray ray = new Ray(transform.TransformPoint(vCheckPos + vOffset), vSkin); Debug.DrawRay(ray.origin, vSkin.normalized * (skinMagnitude + fOffsetDist), Color.magenta); // 3D if (EnableCollision3D) { RaycastHit hitInfo = _GetClosestHitInfo(ray.origin, ray.direction, skinMagnitude + fOffsetDist, layerMask); if (hitInfo.collider != null && hitInfo.collider.gameObject != gameObject) { if (hitInfo.collider.isTrigger) { hitInfo.collider.gameObject.SendMessageUpwards(k_messageOnSmartTriggerStay2D, new SmartContactPoint(hitInfo.normal, this, hitInfo.point), SendMessageOptions.DontRequireReceiver); } else { vRayContacts[i] = true; DebugEx.DebugDrawDot(hitInfo.point + new Vector3(0, 0, -0.1f), 0.01f, Color.red); if ((movingPlatformLayerMask & (1 << hitInfo.collider.gameObject.layer)) != 0) { m_movingPlatforms.Add(new KeyValuePair <Transform, Vector3>(hitInfo.transform, Vector3.zero)); //NOTE: the kvp.Value will be set at the end of DoSolveStaticCollisions } // This is pushing the collided object if it has a rigid body attached if (hitInfo.rigidbody != null) { hitInfo.rigidbody.AddForceAtPosition(vAppliedForce, hitInfo.point, ForceMode.Force); } if (hitInfo.distance < collClosestDist && // avoid collision when slope is higher than threshold and collider object is one way ((0 == ((1 << hitInfo.collider.gameObject.layer) & oneWayLayerMask)) || Vector3.Dot(hitInfo.normal, -vSkin.normalized) >= k_OneSideSlopeNormThreshold) ) { collClosestDist = hitInfo.distance; vClosestHit = hitInfo.point; vClosestHitNorm = hitInfo.normal; collidedObject = hitInfo.collider.gameObject; } } } } // 2D if (EnableCollision2D) { RaycastHit2D hitInfo2D = _GetClosestHitInfo2D(ray.origin, ray.direction, skinMagnitude + fOffsetDist, layerMask, true); if (hitInfo2D.collider != null && hitInfo2D.collider.gameObject != gameObject) { if (hitInfo2D.collider.isTrigger) { hitInfo2D.collider.gameObject.SendMessageUpwards(k_messageOnSmartTriggerStay2D, new SmartContactPoint(hitInfo2D.normal, this, hitInfo2D.point), SendMessageOptions.DontRequireReceiver); } else { vRayContacts[i] = true; DebugEx.DebugDrawDot((Vector3)hitInfo2D.point + new Vector3(0, 0, transform.position.z - 0.1f), 0.01f, Color.red); if ((movingPlatformLayerMask & (1 << hitInfo2D.collider.gameObject.layer)) != 0) { m_movingPlatforms.Add(new KeyValuePair <Transform, Vector3>(hitInfo2D.transform, Vector3.zero)); //NOTE: the kvp.Value will be set at the end of DoSolveStaticCollisions } // This is pushing the collided object if it has a rigid body attached if (hitInfo2D.rigidbody != null) { hitInfo2D.rigidbody.AddForceAtPosition(vAppliedForce, hitInfo2D.point, ForceMode2D.Force); } if (hitInfo2D.distance < collClosestDist && // avoid collision when slope is higher than threshold and collider object is one way ((0 == ((1 << hitInfo2D.collider.gameObject.layer) & oneWayLayerMask)) || Vector3.Dot(hitInfo2D.normal, -vSkin.normalized) >= k_OneSideSlopeNormThreshold) ) { collClosestDist = hitInfo2D.distance; vClosestHit = hitInfo2D.point; vClosestHitNorm = hitInfo2D.normal; collidedObject = hitInfo2D.collider.gameObject; } } } } } // check if there was a collision if (collClosestDist < float.MaxValue) { //Debug.DrawRay(vClosestHit, vClosestHitNorm * 0.1f, Color.yellow, 0.5f); float fImpulseDist = (skinMagnitude + fOffsetDist) - collClosestDist + k_colliderSeparation; Vector3 vImpulse = -vSkin.normalized * fImpulseDist; //TODO: Add smooth factor by multiplying vImpulse by this factor ( make climb steps smoother ) // TODO: if this is slow because of the gravity, a collision is going to be made each fixedUpdate, improve by keeping a list of the hit objects // so this message is only sent once until there is no collision for one update and then call the OnSmartCollisionExit2D SmartCollision2D smartCollision2D = new SmartCollision2D( this, new SmartContactPoint[] { new SmartContactPoint(vClosestHitNorm, this, vClosestHit) }, gameObject, vImpulse, m_instantVelocity, transform, m_rigidBody, m_rigidBody2D); collidedObject.SendMessageUpwards(k_messageOnSmartCollisionStay2D, smartCollision2D, SendMessageOptions.DontRequireReceiver); if (OnSideCollision != null) { OnSideCollision(smartCollision2D, collidedObject); } return(vImpulse); } return(Vector3.zero); }