// Copyright (C) 2023 Nicholas Maltbie // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using nickmaltbie.OpenKCC.CameraControls.Config; using nickmaltbie.OpenKCC.Character; using nickmaltbie.OpenKCC.Utils; using UnityEngine; namespace nickmaltbie.OpenKCC.CameraControls { /// /// Camera utility functions for managing camera controls. /// public static class CameraUtils { /// /// Update a camera controller based on current configuration /// and inputs. /// /// Configuration for the camera controller. /// GameObject associated with the camera controller. /// Camera controls object. /// delta time for updating the camera controller. public static void UpdateCameraController( CameraConfig config, GameObject go, IManagedCamera cameraControls, float deltaTime) { Vector2 look = config.LookAction?.ReadValue() ?? Vector2.zero; look *= PlayerInputUtils.mouseSensitivity; float yawChange = look.x; float pitchChange = look.y; float zoomAxis = config.ZoomAction?.ReadValue() ?? 0; // bound pitch between -180 and 180 float zoomChange = 0; cameraControls.Pitch = (cameraControls.Pitch % 360 + 180) % 360 - 180; // Only allow rotation if player is allowed to move if (PlayerInputUtils.playerMovementState == PlayerInputState.Allow) { yawChange = config.rotationRate * deltaTime * yawChange; cameraControls.Yaw += yawChange; cameraControls.Pitch += config.rotationRate * deltaTime * -1 * pitchChange; zoomChange = config.zoomSpeed * deltaTime * -1 * zoomAxis; } // Clamp rotation of camera between minimum and maximum specified pitch cameraControls.Pitch = Mathf.Clamp(cameraControls.Pitch, config.minPitch, config.maxPitch); // Change camera zoom by desired level // Bound the current distance between minimum and maximum config.currentDistance = Mathf.Clamp(config.currentDistance + zoomChange, config.minCameraDistance, config.maxCameraDistance); if (config.rotatePlayer) { cameraControls.PlayerBase.transform.rotation = Quaternion.Euler(0, cameraControls.Yaw, 0); } if (config.cameraTransform != null) { // Set the player's rotation to be that of the camera's yaw // transform.rotation = Quaternion.Euler(0, yaw, 0); // Set pitch to be camera's rotation config.cameraTransform.rotation = Quaternion.Euler(cameraControls.Pitch, cameraControls.Yaw, 0); // Set the local position of the camera to be the current rotation projected // backwards by the current distance of the camera from the player Vector3 cameraDirection = -config.cameraTransform.forward * config.currentDistance; Vector3 cameraSource = config.CameraSource(go.transform); // Draw a line from our camera source in the camera direction. If the line hits anything that isn't us // Limit the distance by how far away that object is // If we hit something if (PhysicsUtils.SphereCastFirstHitIgnore(config.IgnoreObjects, cameraSource, 0.01f, cameraDirection, cameraDirection.magnitude, config.cameraRaycastMask, QueryTriggerInteraction.Ignore, out RaycastHit hit)) { // limit the movement by that hit cameraDirection = cameraDirection.normalized * hit.distance; } config.CameraDistance = cameraDirection.magnitude; config.cameraTransform.position = cameraSource + cameraDirection; if (config.thirdPersonCharacterBase != null) { CameraUtils.UpdateThirdPersonCameraBase(cameraDirection, config, go, cameraControls, deltaTime); } } cameraControls.Yaw %= 360; } /// /// Update the third person camera base for a camera controller. /// /// Direction the camera is relative to the player. /// Config associated with the player. /// Game object for the character position. /// Camera controls associated with the player. /// delta time for updating player opacity. public static void UpdateThirdPersonCameraBase( Vector3 cameraDirection, CameraConfig config, GameObject go, IManagedCamera cameraControls, float deltaTime) { bool hittingSelf = PhysicsUtils.SphereCastAllow(go, config.CameraSource(go.transform) + cameraDirection, 0.01f, -cameraDirection.normalized, cameraDirection.magnitude, ~0, QueryTriggerInteraction.Ignore, out RaycastHit selfHit); float actualDistance = hittingSelf ? selfHit.distance : cameraDirection.magnitude; config.thirdPersonCharacterBase.transform.localRotation = Quaternion.Euler(0, cameraControls.Yaw, 0); if (actualDistance < config.shadowOnlyDistance) { MaterialUtils.RecursiveSetShadowCastingMode(config.thirdPersonCharacterBase, UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly); } else { MaterialUtils.RecursiveSetShadowCastingMode(config.thirdPersonCharacterBase, UnityEngine.Rendering.ShadowCastingMode.On); } if (actualDistance > config.shadowOnlyDistance && actualDistance < config.ditherDistance) { float newOpacity = (actualDistance - config.shadowOnlyDistance) / (config.ditherDistance - config.minCameraDistance); float lerpPosition = config.transitionTime > 0 ? deltaTime * 1 / config.transitionTime : 1; cameraControls.PreviousOpacity = Mathf.Lerp(cameraControls.PreviousOpacity, newOpacity, lerpPosition); // Set opacity of character based on how close the camera is MaterialUtils.RecursiveSetFloatProperty(config.thirdPersonCharacterBase, "_Opacity", cameraControls.PreviousOpacity); } else { // Set opacity of character based on how close the camera is MaterialUtils.RecursiveSetFloatProperty(config.thirdPersonCharacterBase, "_Opacity", 1); cameraControls.PreviousOpacity = actualDistance > config.shadowOnlyDistance ? 1 : 0; } } } }