/* * Casts a ray from "start" to "direction" with distance "distance", considering all layers in "layerMask" and taking into * account the occlusion handling "handling". Adds all objects hit with a layer of the layer mask "layerMask" to * "objectsToFade". Additionally, adds the RaycastHit closest to "start" to the list "closestRaycastHits" */ private void ImprovedRaycastAll(Vector3 start, Vector3 direction, float distance, int layerMask, OcclusionHandling handling, ref SortedDictionary <int, GameObject> objectsToFade, ref List <RaycastHit> closestRaycastHits) { // Cast a ray and store all hits RaycastHit[] hitArray = Physics.RaycastAll(start, direction, distance, layerMask); if (hitArray.Length > 0) { // Objects got hit, sort the hits by their distance to "start" Array.Sort(hitArray, RaycastHitComparator); if (handling == OcclusionHandling.AlwaysZoomIn) { // Add the closest hit to "closestRaycastHits" as we consider all objects regardless of the tag closestRaycastHits.Add(hitArray[0]); } else if (handling == OcclusionHandling.TagDependent) { // Find the closest hit of an object with a camera affecting tag and add every object with no affecting tag // to the "objcetsToFade" list for (int i = 0; i < hitArray.Length; i++) { RaycastHit hit = hitArray[i]; int hitObjectID = hit.transform.GetInstanceID(); if (AffectingTags.Contains(hit.transform.tag)) { closestRaycastHits.Add(hitArray[i]); return; } else if (!objectsToFade.ContainsKey(hitObjectID)) { objectsToFade.Add(hitObjectID, hit.transform.gameObject); } } } } }
/* Checks for objects inside the view frustum and - depending on the handling - fades them out or lets the camera zoom in. * Returns -1 if there is no ambient occlusion, otherwise returns the closest possible distance so that the sight to the target is clear */ public float CheckForOcclusion(Vector3 atPosition, Vector3 target, Camera usedCamera) { OcclusionHandling handling = OcclusionHandling; if (handling == OcclusionHandling.DoNothing) { // All objects inside the view frustum should be ignored, hence there is no occlusion => return -1 return(-1); } // Return -1 if there was no collision with the view frustum float closestDistance = -1; // Set up the clipping planes setUpClippingPlanes(atPosition, target, usedCamera); // Compute the view frustum direction and length Vector3 rayDirection = _startPosition - _targetPosition; float rayDistance = rayDirection.magnitude; SortedDictionary <int, GameObject> objectsToFade = new SortedDictionary <int, GameObject>(); List <RaycastHit> closestRaycastHits /* of each ray */ = new List <RaycastHit>(); // Cast rays through center of the view frustum and along its edges. Add the closest hits to "closestRaycastHits" and hit objects to fade to "objectsToFade" ImprovedRaycastAll(_targetPosition, rayDirection, rayDistance, OccludingLayers, handling, ref objectsToFade, ref closestRaycastHits); ImprovedRaycastAll(_targetPosition + _shiftUpperLeft, rayDirection, rayDistance, OccludingLayers, handling, ref objectsToFade, ref closestRaycastHits); ImprovedRaycastAll(_targetPosition + _shiftUpperRight, rayDirection, rayDistance, OccludingLayers, handling, ref objectsToFade, ref closestRaycastHits); ImprovedRaycastAll(_targetPosition + _shiftLowerLeft, rayDirection, rayDistance, OccludingLayers, handling, ref objectsToFade, ref closestRaycastHits); ImprovedRaycastAll(_targetPosition + _shiftLowerRight, rayDirection, rayDistance, OccludingLayers, handling, ref objectsToFade, ref closestRaycastHits); RaycastHit[] closestRaycastHitsArray = closestRaycastHits.ToArray(); // Sort the hits by their distance Array.Sort(closestRaycastHitsArray, RaycastHitComparator); if (closestRaycastHitsArray.Length > 0) { // At least one object intersects with the view frustum => take the closest hit's distance closestDistance = closestRaycastHitsArray[0].distance; } if (handling == OcclusionHandling.TagDependent) { // OcclusionHandling is "TagDependent" => enable the fading of objects // Create lists for objects to fade in or out List <GameObject> fadeOut = new List <GameObject>(); List <GameObject> fadeIn = new List <GameObject>(); // The following lines do the following: // - Compare the objects to fade of the last frame and the objects hit in this frame // - If an object is in "_previousObjectsToFade" but not in "objectsToFade", fade it back in (as it is no longer inside the view frustum // - If an object is not in "_previousObjectsToFade" but in "objectsToFade", fade it out (as it is enters the view frustum this frame) // - If an object is in both lists, do nothing and continue (as the object was already inside the view frustum and still is) SortedDictionary <int, GameObject> .Enumerator i = _previousObjectsToFade.GetEnumerator(); SortedDictionary <int, GameObject> .Enumerator j = objectsToFade.GetEnumerator(); bool iFinished = !i.MoveNext(); bool jFinished = !j.MoveNext(); bool aListFinished = iFinished || jFinished; while (!aListFinished) { int iKey = i.Current.Key; int jKey = j.Current.Key; if (iKey == jKey) { iFinished = !i.MoveNext(); jFinished = !j.MoveNext(); aListFinished = iFinished || jFinished; } else if (iKey < jKey) { if (i.Current.Value != null) { fadeIn.Add(i.Current.Value); } aListFinished = !i.MoveNext(); iFinished = true; jFinished = false; } else { if (j.Current.Value != null) { fadeOut.Add(j.Current.Value); } aListFinished = !j.MoveNext(); iFinished = false; jFinished = true; } } if (iFinished && !jFinished) { do { if (j.Current.Value != null) { fadeOut.Add(j.Current.Value); } } while (j.MoveNext()); } else if (!iFinished && jFinished) { do { if (i.Current.Value != null) { fadeIn.Add(i.Current.Value); } } while (i.MoveNext()); } foreach (GameObject o in fadeOut) { int objectID = o.transform.GetInstanceID(); // Create a new coroutine for fading out the object IEnumerator coroutine = FadeObjectCoroutine(FadeOutAlpha, FadeOutDuration, o); // Check if there is a running fade in coroutine for this object IEnumerator runningCoroutine; if (_fadeInCoroutines.TryGetValue(objectID, out runningCoroutine)) { // Stop the already running coroutine StopCoroutine(runningCoroutine); // Remove it from the fade in coroutines _fadeInCoroutines.Remove(objectID); } // Add the new fade out coroutine to the list of fade out coroutines _fadeOutCoroutines.Add(objectID, coroutine); // Start the coroutine StartCoroutine(coroutine); } foreach (GameObject o in fadeIn) { int objectID = o.transform.GetInstanceID(); // Create a new coroutine for fading in the object IEnumerator coroutine = FadeObjectCoroutine(FadeInAlpha, FadeInDuration, o); // Check if there is a running fade out coroutine for this object IEnumerator runningCoroutine; if (_fadeOutCoroutines.TryGetValue(objectID, out runningCoroutine)) { // Stop the already running coroutine StopCoroutine(runningCoroutine); // Remove it from the fade out coroutines _fadeOutCoroutines.Remove(objectID); } // Add the new fade in coroutine to the list of fade in coroutines _fadeInCoroutines.Add(objectID, coroutine); // Start the coroutine StartCoroutine(coroutine); } // Set the "_previousObjectsToFade" for the next frame occlusion computations _previousObjectsToFade = objectsToFade; } if (EnableCharacterFading) { // Let the character fade in/out CharacterFade(usedCamera); } return(closestDistance); }
private void LateUpdate() { // Make AlwaysRotateCamera and AlignCameraWhenMoving mutual exclusive if (AlwaysRotateCamera) { AlignCameraWhenMoving = false; } // Check if the UsedSkybox variable has been changed through SetUsedSkybox() if (_skyboxChanged) { // Update the used camera's skybox _skybox.material = UsedSkybox; _skyboxChanged = false; } // Set the camera's pivot position in world coordinates _cameraPivotPosition = transform.position + transform.TransformVector(CameraPivotLocalPosition); // Check if the camera's Y rotation is contrained by terrain bool mouseYConstrained = false; OcclusionHandling occlusionHandling = _rpgViewFrustum.GetOcclusionHandling(); List <string> affectingTags = _rpgViewFrustum.GetAffectingTags(); if (occlusionHandling == OcclusionHandling.AlwaysZoomIn || occlusionHandling == OcclusionHandling.TagDependent) { RaycastHit hitInfo; mouseYConstrained = Physics.Raycast(UsedCamera.transform.position, Vector3.down, out hitInfo, 1.0f); // mouseYConstrained = "Did the ray hit something?" AND "Was it terrain?" AND "Is the camera's Y position under that of the pivot?" mouseYConstrained = mouseYConstrained && hitInfo.transform.GetComponent <Terrain>() && UsedCamera.transform.position.y < _cameraPivotPosition.y; if (occlusionHandling == OcclusionHandling.TagDependent) { // Additionally take into account if the hit terrain has a camera affecting tag mouseYConstrained = mouseYConstrained && affectingTags.Contains(hitInfo.transform.tag); } } #region Get inputs float smoothTime = MouseSmoothTime; // RPG MMO CAMERA required if (!(Input.GetButton("Fire1") && Input.GetAxis("Mouse X") != 0)) { smoothTime = 0; } float mouseYMinLimit = _mouseY; // Get mouse input if (ActivateCameraControl && (Input.GetButton("Fire1") || Input.GetButton("Fire2") || AlwaysRotateCamera)) { // Apply the prescribed cursor lock mode and visibility Cursor.lockState = CursorLockMode; Cursor.visible = !HideCursorWhenPressed; // Get mouse X axis input if (!LockMouseX) { float mouseXinput = 0; if (InvertMouseX) { mouseXinput = -Input.GetAxis("Mouse X"); } else { mouseXinput = Input.GetAxis("Mouse X"); } // Check the character alignment mode if (AlignCharacter == AlignCharacter.Always || (AlignCharacter == AlignCharacter.OnAlignmentInput && Input.GetButton(AlignmentInput))) { // Check if the character already has been aligned if (!_characterAligned) { // Align the character and set _characterAligned to true (so that the character only gets aligned on the first frame) float cameraYrotation = UsedCamera.transform.eulerAngles.y; transform.eulerAngles = new Vector3(transform.eulerAngles.x, cameraYrotation, transform.eulerAngles.z); _mouseX = 0; _mouseXSmooth = 0; _mouseXCurrentVelocity = 0; _characterAligned = true; } if (_rpgMotor != null) { // Let the character rotate according to the mouse X axis input _rpgMotor.SetLocalRotationFire2Input(mouseXinput * MouseXSensitivity); } } else { // No character alignment needed => allow the character to be aligned again and let the camera rotate normally as well _characterAligned = false; _mouseX += mouseXinput * MouseXSensitivity; if (_rpgMotor != null) { _rpgMotor.SetLocalRotationFire2Input(0); } } if (ConstrainMouseX) { // Clamp the rotation in X axis direction _mouseX = Mathf.Clamp(_mouseX, MouseXMin, MouseXMax); } } // Get mouse Y axis input if (!LockMouseY) { if (InvertMouseY) { _desiredMouseY -= Input.GetAxis("Mouse Y") * MouseYSensitivity; } else { _desiredMouseY += Input.GetAxis("Mouse Y") * MouseYSensitivity; } } // Check if the camera's Y rotation is constrained by terrain if (mouseYConstrained) { _mouseY = Mathf.Clamp(_desiredMouseY, Mathf.Max(mouseYMinLimit, MouseYMin), MouseYMax); // Set the desired mouse Y rotation to compute the degrees of looking up with the camera _desiredMouseY = Mathf.Max(_desiredMouseY, _mouseY - 90.0f); } else { // Clamp the mouse between the maximum values _mouseY = Mathf.Clamp(_desiredMouseY, MouseYMin, MouseYMax); } _desiredMouseY = Mathf.Clamp(_desiredMouseY, MouseYMin, MouseYMax); } else { // Unlock the cursor and make it visible again Cursor.lockState = CursorLockMode.None; Cursor.visible = true; if (_rpgMotor != null) { _rpgMotor.SetLocalRotationFire2Input(0); } } // Check if the camera shouldn't rotate with the character if (_rpgMotor != null && Input.GetAxisRaw("Horizontal") != 0 && !Input.GetButton("Fire2")) { // The character turns and doesn't strafe via Fire2 => Check the RotateWithCharacter value if (RotateWithCharacter == RotateWithCharacter.Never || (RotateWithCharacter == RotateWithCharacter.RotationStoppingInput && Input.GetButton(RotationStoppingInput))) { // Counter the character's rotation so that the camera stays in place _mouseX -= Input.GetAxisRaw("Horizontal") * _rpgMotor.GetRotatingSpeed() * 100.0f * Time.deltaTime; smoothTime = 0; } } if (ActivateCameraControl) { // Get scroll wheel input // zooming is not allowed in this game // _desiredDistance = _desiredDistance - Input.GetAxis("Mouse ScrollWheel") * MouseScrollSensitivity; // _desiredDistance = Mathf.Clamp(_desiredDistance, MinDistance, MaxDistance); // Check if one of the switch buttons is pressed if (Input.GetButton("First Person Zoom")) { _desiredDistance = MinDistance; } else if (Input.GetButton("Maximum Distance Zoom")) { _desiredDistance = MaxDistance; } } if (_rpgMotor != null) { // Align the camera with the character when moving forward or backwards Vector3 playerDirection = _rpgMotor.GetPlayerDirection(); // Set _alignCameraWithCharacter. If true, allow alignment of the camera with the character _alignCameraWithCharacter = SetAlignCameraWithCharacter(playerDirection.z != 0 || playerDirection.x != 0); if (AlignCameraWhenMoving && _alignCameraWithCharacter) { // Alignment is desired and an action occured which should result in an alignment => align the camera AlignCameraWithCharacter(!SupportWalkingBackwards || playerDirection.z > 0 || playerDirection.x != 0); } } #endregion #region Smooth the inputs if (AlignCameraWhenMoving && _alignCameraWithCharacter) { smoothTime = AlignCameraSmoothTime; } _mouseXSmooth = Mathf.SmoothDamp(_mouseXSmooth, _mouseX, ref _mouseXCurrentVelocity, smoothTime); _mouseYSmooth = Mathf.SmoothDamp(_mouseYSmooth, _mouseY, ref _mouseYCurrentVelocity, smoothTime); #endregion #region Compute the new camera position Vector3 newCameraPosition; // Compute the desired position _desiredPosition = GetCameraPosition(_mouseYSmooth, _mouseXSmooth, _desiredDistance); // Compute the closest possible camera distance by checking if there is something inside the view frustum float closestDistance = _rpgViewFrustum.CheckForOcclusion(_desiredPosition, _cameraPivotPosition, UsedCamera); if (closestDistance != -1) { // Camera view is constrained => set the camera distance to the closest possible distance closestDistance -= UsedCamera.nearClipPlane; if (_distanceSmooth < closestDistance) { // Smooth the distance if we move from a smaller constrained distance to a bigger constrained distance _distanceSmooth = Mathf.SmoothDamp(_distanceSmooth, closestDistance, ref _distanceCurrentVelocity, DistanceSmoothTime); } else { // Do not smooth if the new closest distance is smaller than the current distance _distanceSmooth = closestDistance; } } else { // The camera view at the desired position is not contrained but we have to check if it is when zooming to the desired position Vector3 currentCameraPosition = GetCameraPosition(_mouseYSmooth, _mouseXSmooth, _distanceSmooth); // Check again for occlusion. This time for the current camera position closestDistance = _rpgViewFrustum.CheckForOcclusion(currentCameraPosition, _cameraPivotPosition, UsedCamera); if (closestDistance != -1) { // The camera is/will be constrained on the way to the desired position => set the camera distance to the closest possible distance closestDistance -= UsedCamera.nearClipPlane; _distanceSmooth = closestDistance; } else { // The camera is not constrained on the way to the desired position => smooth the distance change _distanceSmooth = Mathf.SmoothDamp(_distanceSmooth, _desiredDistance, ref _distanceCurrentVelocity, DistanceSmoothTime); } } // Compute the new camera position newCameraPosition = GetCameraPosition(_mouseYSmooth, _mouseXSmooth, _distanceSmooth); #endregion #region Update the camera transform UsedCamera.transform.position = newCameraPosition; // Check if we are in third or first person and adjust the camera rotation behavior if (_distanceSmooth > 0.1f) { // In third person => orbit camera UsedCamera.transform.LookAt(_cameraPivotPosition); } else { // In first person => normal camera rotation with rotating the character as well Quaternion characterRotation = transform.rotation; Quaternion cameraRotation = Quaternion.Euler(new Vector3(_mouseYSmooth, _mouseXSmooth, 0)); UsedCamera.transform.rotation = characterRotation * cameraRotation; } if (mouseYConstrained /*|| _distanceSmooth <= 0.1f*/) { // Camera lies on terrain => enable looking up float lookUpDegrees = _desiredMouseY - _mouseY; UsedCamera.transform.Rotate(Vector3.right, lookUpDegrees); } #endregion }