예제 #1
0
    /*
     *      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);
                    }
                }
            }
        }
    }
예제 #2
0
    /* 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);
    }
예제 #3
0
    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
    }