/// <summary> /// Does the actual rotation and height changes. This method is separate from FixedUpdate so the rotation/height can be determined /// during server reconciliation on the network. /// </summary> public void UpdateRotationHeight() { m_CharacterLocomotion.EnableColliderCollisionLayer(false); // The first end cap may change positions. if (m_FirstEndCapTarget.localPosition != m_FirstEndCapLocalPosition) { m_Transform.position = m_FirstEndCapTarget.position; m_FirstEndCapLocalPosition = m_FirstEndCapTarget.localPosition; } Vector3 localDirection; if (m_RotateCollider) { // Update the rotation of the CapsuleCollider so it is rotated in the same direction as the end cap targets. var direction = m_SecondEndCapTarget.position - m_FirstEndCapTarget.position; localDirection = MathUtility.InverseTransformDirection(direction, m_CharacterTransform.rotation); var targetRotation = m_Transform.localRotation * Quaternion.FromToRotation(m_Transform.localRotation * Vector3.up, localDirection.normalized); m_Transform.rotation = MathUtility.TransformQuaternion(m_CharacterTransform.rotation, targetRotation); } // After the CapsuleCollider has rotated determine the new height of the CapsuleCollider. This can be done by determining the current // end cap locations and then getting the offset from the start end cap offsets. Vector3 firstEndCap, secondEndCap; MathUtility.CapsuleColliderEndCaps(m_CapsuleCollider, m_Transform.position, m_Transform.rotation, out firstEndCap, out secondEndCap); var firstEndCapOffset = m_Transform.InverseTransformDirection(m_FirstEndCapTarget.position - firstEndCap); var secondEndCapOffset = m_Transform.InverseTransformDirection(m_SecondEndCapTarget.position - secondEndCap); var offset = m_SecondEndCapOffset - m_FirstEndCapOffset; localDirection = ((secondEndCapOffset - firstEndCapOffset) - offset); // Determine if the new height would cause any collisions. If it does not then apply the height changes. A negative height change will never cause any // collisions so the OverlapCapsule does not need to be checked. A valid capsule collider height is always greater than 2 times the radius of the collider. var heightMultiplier = MathUtility.CapsuleColliderHeightMultiplier(m_CapsuleCollider); var targetHeight = m_CapsuleCollider.height + localDirection.y / heightMultiplier; if (targetHeight >= m_CapsuleCollider.radius * 2 && (localDirection.y < 0 || !m_CharacterLocomotion.UsingVerticalCollisionDetection || Physics.OverlapCapsuleNonAlloc(firstEndCap, secondEndCap + m_CharacterLocomotion.Up * localDirection.y, m_CapsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(m_CapsuleCollider), m_OverlapColliders, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore) == 0)) { // Adjust the CapsuleCollider height and center to account for the new offset. m_CapsuleCollider.height = targetHeight; var center = m_CapsuleCollider.center; center.y += localDirection.y / (heightMultiplier * 2); m_CapsuleCollider.center = center; } m_CharacterLocomotion.EnableColliderCollisionLayer(true); }
/// <summary> /// Determines the values of the cast. /// </summary> /// <param name="index">The index of the target position to retrieve.</param> /// <param name="direction">A reference to the target direction.</param> /// <param name="position">A reference to the target position.</param> /// <param name="normal">A reference to the target normal.</param> /// <returns>True if the cast is valid.</returns> private bool DetermineCastValues(int index, ref Vector3 direction, ref Vector3 position, ref Vector3 normal) { if (m_Direction == CastDirection.Forward || m_Direction == CastDirection.Indicate) { Vector3 castPosition; if (m_Direction == CastDirection.Forward) { direction = m_UseLookSource ? m_LookSource.LookDirection(m_MagicItemPerspectiveProperties.OriginLocation.position, false, m_CharacterLayerManager.SolidObjectLayers, true, true) : m_CharacterTransform.forward; castPosition = m_UseLookSource ? m_LookSource.LookPosition(true) : m_CharacterTransform.position; } else { // Indicate. direction = m_LookSource.LookDirection(false); castPosition = m_LookSource.LookPosition(false); } m_CharacterLocomotion.EnableColliderCollisionLayer(false); if (Physics.SphereCast(castPosition - direction * m_Radius, m_Radius, direction, out var raycastHit, m_MaxDistance, m_DetectLayers, QueryTriggerInteraction.Ignore)) { // The Cast Actions may indicate that the position is invalid. if (m_CastActions != null) { for (int i = 0; i < m_CastActions.Length; ++i) { if (!m_CastActions[i].IsValidTargetPosition(raycastHit.point, raycastHit.normal)) { return false; } } } position = raycastHit.point; normal = raycastHit.normal; } m_CharacterLocomotion.EnableColliderCollisionLayer(true); return m_Direction == CastDirection.Indicate ? (raycastHit.distance > 0) : true; } else if (m_Direction == CastDirection.Target) { if (index >= m_TargetAngles.Length || m_TargetAngles[index] == int.MaxValue) { return false; } var targetTransform = m_TargetColliders[index].transform; PivotOffset pivotOffset; if ((pivotOffset = targetTransform.gameObject.GetCachedComponent<PivotOffset>()) != null) { position = targetTransform.TransformPoint(pivotOffset.Offset); } else { position = targetTransform.position; } direction = (position - m_MagicItemPerspectiveProperties.OriginLocation.position).normalized; normal = targetTransform.up; return true; } // None direction. direction = m_CharacterTransform.forward; position = m_CharacterTransform.position; normal = m_CharacterTransform.up; return true; }
/// <summary> /// Returns the direction that the character is looking. /// </summary> /// <param name="lookPosition">The position that the character is looking from.</param> /// <param name="characterLookDirection">Is the character look direction being retrieved?</param> /// <param name="layerMask">The LayerMask value of the objects that the look direction can hit.</param> /// <param name="useRecoil">Should recoil be included in the look direction?</param> /// <returns>The direction that the character is looking.</returns> public Vector3 LookDirection(Vector3 lookPosition, bool characterLookDirection, int layerMask, bool useRecoil) { var collisionLayerEnabled = m_CharacterLocomotion.CollisionLayerEnabled; m_CharacterLocomotion.EnableColliderCollisionLayer(false); // Cast a ray from the look source point in the forward direction. The look direction is then the vector from the look position to the hit point. RaycastHit hit; Vector3 direction; if (Physics.Raycast(m_LookPosition, m_LookDirection, out hit, m_LookDirectionDistance, layerMask, QueryTriggerInteraction.Ignore)) { direction = (hit.point - lookPosition).normalized; } else { direction = m_LookDirection; } m_CharacterLocomotion.EnableColliderCollisionLayer(collisionLayerEnabled); return(direction); }
/// <summary> /// Throws the throwable object. /// </summary> public void ThrowItem() { if (m_Thrown) { return; } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER // The object has been thrown. If the ItemAction is on the server then that object should be spawned on the network. // Non-server actions should disable the mesh renderers so the object can take its place. The mesh renderers will be enabled again in a separate call. if (m_NetworkInfo != null) { EnableObjectMeshRenderers(false); if (!m_NetworkInfo.IsServer()) { ObjectPool.Destroy(m_InstantiatedThrownObject); m_InstantiatedThrownObject = null; return; } } #endif m_InstantiatedThrownObject.transform.parent = null; // The collider was previously disabled. Enable it again when it is thrown. var collider = m_InstantiatedThrownObject.GetCachedComponent <Collider>(); collider.enabled = true; // When the item is used the trajectory object should start moving on its own. // The throwable item may be on the other side of an object (especially in the case of separate arms for the first person perspective). Perform a linecast // to ensure the throwable item doesn't move through any objects. var collisionEnabled = m_CharacterLocomotion.CollisionLayerEnabled; m_CharacterLocomotion.EnableColliderCollisionLayer(false); if (!m_CharacterLocomotion.ActiveMovementType.UseIndependentLook(false) && Physics.Linecast(m_CharacterLocomotion.LookSource.LookPosition(), m_InstantiatedTrajectoryObject.transform.position, out m_RaycastHit, m_ImpactLayers, QueryTriggerInteraction.Ignore)) { m_InstantiatedTrajectoryObject.transform.position = m_RaycastHit.point; } m_CharacterLocomotion.EnableColliderCollisionLayer(collisionEnabled); var trajectoryTransform = m_ThrowableItemPerpectiveProperties.TrajectoryLocation != null ? m_ThrowableItemPerpectiveProperties.TrajectoryLocation : m_CharacterTransform; var lookDirection = m_LookSource.LookDirection(trajectoryTransform.TransformPoint(m_TrajectoryOffset), false, m_ImpactLayers, true, true); #if ULTIMATE_CHARACTER_CONTROLLER_VR if (m_VRThrowableItem != null && m_CharacterLocomotion.FirstPersonPerspective) { m_Velocity = m_VRThrowableItem.GetVelocity(); } #endif var velocity = MathUtility.TransformDirection(m_Velocity, Quaternion.LookRotation(lookDirection, m_CharacterLocomotion.Up)); // Prevent the item from being thrown behind the character. This can happen if the character is looking straight up and there is a positive // y velocity. Gravity will cause the thrown object to go in the opposite direction. if (Vector3.Dot(velocity.normalized, m_CharacterTransform.forward) < 0 && m_CharacterTransform.InverseTransformDirection(velocity.normalized).y > 0) { velocity = m_CharacterTransform.up * velocity.magnitude; } m_InstantiatedTrajectoryObject.Initialize(m_CharacterLocomotion.Alive ? (velocity + (m_CharacterTransform.forward * m_CharacterLocomotion.LocalVelocity.z)) : Vector3.zero, Vector3.zero, m_Character, false); #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null) { NetworkObjectPool.NetworkSpawn(m_ThrownObject, m_InstantiatedThrownObject, true); } #endif // Optionally change the layer after the object has been thrown. This allows the object to change from the first person Overlay layer // to the Default layer after it has cleared the character's hands. if (m_StartLayer != m_ThrownLayer) { Scheduler.ScheduleFixed(m_LayerChangeDelay, ChangeThrownLayer, m_InstantiatedThrownObject); } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) { m_Thrown = true; } #endif }
/// <summary> /// Fade any objects that get in the way between the character and the camera. /// </summary> /// <param name="immediateFade">Should the fade material be changed immediately?</param> private void FadeObstructingObjects(bool immediateFade) { if (!m_ObstructingObjectsFade) { return; } // Disable any obstructing colliders so the sphere cast can detect which objects are obstructing. for (int i = 0; i < m_ObstructingCollidersCount; ++i) { m_ObstructingColliders[i].enabled = true; } m_ObstructingCollidersCount = 0; var characterPosition = m_CharacterTransform.TransformPoint(m_TransformOffset); var direction = (m_Transform.position - characterPosition); var start = characterPosition - direction.normalized * m_CollisionRadius; m_CharacterLocomotion.EnableColliderCollisionLayer(false); // Fire a sphere to prevent the camera from colliding with other objects. var hitCount = Physics.SphereCastNonAlloc(start, m_CollisionRadius, direction.normalized, m_RaycastsHit, direction.magnitude, m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore); m_CharacterLocomotion.EnableColliderCollisionLayer(true); m_ObstructionHitSet.Clear(); if (hitCount > 0) { // Loop through all of the hit colliders. For any collider that has been hit get all of the renderers. The materials that are used on the // renderers then need to be checked to determine if they can be faded. If the material can be faded place it in a set which will then // be checked in the next block to determine if the material should be faded. for (int i = 0; i < hitCount; ++i) { var renderers = m_RaycastsHit[i].transform.gameObject.GetCachedComponents <Renderer>(); var obstructing = false; for (int j = 0; j < renderers.Length; ++j) { var materials = renderers[j].materials; for (int k = 0; k < materials.Length; ++k) { if (!m_CanObstructionFade.TryGetValue(materials[k], out var canFade)) { // Objects can fade if they have the color property and can be transparent. canFade = materials[k].HasProperty(m_ColorID) && (m_AutoSetMode || materials[k].renderQueue >= (int)UnityEngine.Rendering.RenderQueue.Transparent); m_CanObstructionFade.Add(materials[k], canFade); } if (canFade) { var material = materials[k]; // Any material contained within the hit set should be faded. m_ObstructionHitSet.Add(material); if (m_DisableCollider && !obstructing) { obstructing = CanMaterialFade(material); } // The same material may be applied to multiple renderers. if (!m_OriginalMaterialValuesMap.ContainsKey(material)) { // Don't set the mode automatically just because it has the property - not all objects in the environment should fade. if (m_AutoSetMode && material.HasProperty(OriginalMaterialValue.ModeID)) { m_MaterialModeSet.Add(material); } var originalMaterialValues = GenericObjectPool.Get <OriginalMaterialValue>(); originalMaterialValues.Initialize(material, m_ColorID, m_MaterialModeSet.Contains(material)); m_OriginalMaterialValuesMap.Add(material, originalMaterialValues); m_ObstructingMaterials[m_ObstructingMaterialsCount] = material; m_ObstructingMaterialsCount++; EnableFadeMaterial(material); } } } } // If the object is faded then the collider has the option of being disabled to prevent it from causing collisions. if (m_DisableCollider && obstructing) { m_RaycastsHit[i].collider.enabled = false; m_ObstructingColliders[m_ObstructingCollidersCount] = m_RaycastsHit[i].collider; m_ObstructingCollidersCount++; } } } // Once the obstructing objects have been found they should be faded. Note that this can cause a lot of overdraw so the FadeObject method can be // overridden to provide a custom effect such as the one described on https://madewith.unity.com/stories/dissolving-the-world-part-1. for (int i = m_ObstructingMaterialsCount - 1; i >= 0; --i) { if (!FadeMaterial(m_ObstructingMaterials[i], m_ObstructionHitSet.Contains(m_ObstructingMaterials[i]), immediateFade)) { RemoveObstructingMaterial(i); } } }