/Scripts/SmoothFollowCamera.js
JavaScript | 236 lines | 140 code | 52 blank | 44 comment | 14 complexity | 55d73773fdc784e581b39a6c0210a4a8 MD5 | raw file
1// This camera is similar to the one used in Super Mario 64 2// Edited from SmoothFollowCamera script from the 3DPlatformer Tutorial 3 4/* 5 6"Fire2" snaps the camera 7 8*/ 9 10// The target we are following 11var target : Transform; 12 13// The distance in the x-z plane to the target 14var distance = 10.0; 15 16// the height we want the camera to be above the target 17var height = 10.0; 18 19var angularSmoothLag = 0.3; 20var angularMaxSpeed = 150.0; 21 22var heightSmoothLag = 0.3; 23 24var snapSmoothLag = 0.2; 25var snapMaxSpeed = 7200.0; 26 27var clampHeadPositionScreenSpace = 0.75; 28 29var lockCameraTimeout = 0.0; 30 31private var headOffset = Vector3.zero; 32private var centerOffset = Vector3.zero; 33 34private var heightVelocity = 0.0; 35private var angleVelocity = 0.0; 36private var snap = false; 37private var controller : ThirdPersonController; 38private var targetHeight = 100000.0; 39 40//This rotates the camera so that it's always behind the player, default off to keep that isometric feel 41private var followRotate = false; 42 43function Awake () 44{ 45 if (target) 46 { 47 controller = target.GetComponent(ThirdPersonController); 48 } 49 50 if (controller) 51 { 52 var characterController : CharacterController = target.collider; 53 centerOffset = characterController.bounds.center - target.position; 54 headOffset = centerOffset; 55 headOffset.y = characterController.bounds.max.y - target.position.y; 56 } 57 else 58 Debug.Log("Please assign a target to the camera that has a ThirdPersonController script attached."); 59 60 61 Cut(target, centerOffset); 62} 63 64function DebugDrawStuff () 65{ 66 Debug.DrawLine(target.position, target.position + headOffset); 67 68} 69 70function AngleDistance (a : float, b : float) 71{ 72 a = Mathf.Repeat(a, 360); 73 b = Mathf.Repeat(b, 360); 74 75 return Mathf.Abs(b - a); 76} 77 78function Apply (dummyTarget : Transform, dummyCenter : Vector3) 79{ 80 // Early out if we don't have a target 81 if (!controller) 82 return; 83 84 var targetCenter = target.position + centerOffset; 85 var targetHead = target.position + headOffset; 86 87// DebugDrawStuff(); 88 89 // Calculate the current & target rotation angles 90 var originalTargetAngle = target.eulerAngles.y; 91 var currentAngle = transform.eulerAngles.y; 92 93 // Adjust real target angle when camera is locked 94 var targetAngle = originalTargetAngle; 95 96 // When pressing Fire2 (alt) the camera will snap to the target direction real quick. 97 // It will stop snapping when it reaches the target 98 if(followRotate) 99 { 100 if (Input.GetButton("Fire2")) 101 snap = true; 102 103 if (snap) 104 { 105 // We are close to the target, so we can stop snapping now! 106 if (AngleDistance (currentAngle, originalTargetAngle) < 3.0) 107 snap = false; 108 109 currentAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, angleVelocity, snapSmoothLag, snapMaxSpeed); 110 } 111 // Normal camera motion 112 else 113 { 114 if (controller.GetLockCameraTimer () < lockCameraTimeout) 115 { 116 targetAngle = currentAngle; 117 } 118 119 // Lock the camera when moving backwards! 120 // * It is really confusing to do 180 degree spins when turning around. 121 if (AngleDistance (currentAngle, targetAngle) > 160 && controller.IsMovingBackwards ()) 122 targetAngle += 180; 123 124 currentAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, angleVelocity, angularSmoothLag, angularMaxSpeed); 125 } 126 127 128 } 129 // When jumping don't move camera upwards but only down! 130 if (controller.IsJumping ()) 131 { 132 // We'd be moving the camera upwards, do that only if it's really high 133 var newTargetHeight = targetCenter.y + height; 134 if (newTargetHeight < targetHeight || newTargetHeight - targetHeight > 5) 135 targetHeight = targetCenter.y + height; 136 } 137 // When walking always update the target height 138 else 139 { 140 targetHeight = targetCenter.y + height; 141 } 142 143 // Damp the height 144 currentHeight = transform.position.y; 145 currentHeight = Mathf.SmoothDamp (currentHeight, targetHeight, heightVelocity, heightSmoothLag); 146 147 // Convert the angle into a rotation, by which we then reposition the camera 148 currentRotation = Quaternion.Euler (0, currentAngle, 0); 149 150 // Set the position of the camera on the x-z plane to: 151 // distance meters behind the target 152 transform.position = targetCenter; 153 transform.position += currentRotation * Vector3.back * distance; 154 155 // Set the height of the camera 156 transform.position.y = currentHeight; 157 158 // Always look at the target 159 SetUpRotation(targetCenter, targetHead); 160} 161 162function LateUpdate () { 163 Apply (transform, Vector3.zero); 164} 165 166function Cut (dummyTarget : Transform, dummyCenter : Vector3) 167{ 168 var oldHeightSmooth = heightSmoothLag; 169 var oldSnapMaxSpeed = snapMaxSpeed; 170 var oldSnapSmooth = snapSmoothLag; 171 172 snapMaxSpeed = 10000; 173 snapSmoothLag = 0.001; 174 heightSmoothLag = 0.001; 175 176 snap = true; 177 Apply (transform, Vector3.zero); 178 179 heightSmoothLag = oldHeightSmooth; 180 snapMaxSpeed = oldSnapMaxSpeed; 181 snapSmoothLag = oldSnapSmooth; 182} 183 184function SetUpRotation (centerPos : Vector3, headPos : Vector3) 185{ 186 // Now it's getting hairy. The devil is in the details here, the big issue is jumping of course. 187 // * When jumping up and down we don't want to center the guy in screen space. 188 // This is important to give a feel for how high you jump and avoiding large camera movements. 189 // 190 // * At the same time we dont want him to ever go out of screen and we want all rotations to be totally smooth. 191 // 192 // So here is what we will do: 193 // 194 // 1. We first find the rotation around the y axis. Thus he is always centered on the y-axis 195 // 2. When grounded we make him be centered 196 // 3. When jumping we keep the camera rotation but rotate the camera to get him back into view if his head is above some threshold 197 // 4. When landing we smoothly interpolate towards centering him on screen 198 var cameraPos = transform.position; 199 var offsetToCenter = centerPos - cameraPos; 200 201 // Generate base rotation only around y-axis 202 var yRotation = Quaternion.LookRotation(Vector3(offsetToCenter.x, 0, offsetToCenter.z)); 203 204 var relativeOffset = Vector3.forward * distance + Vector3.down * height; 205 transform.rotation = yRotation * Quaternion.LookRotation(relativeOffset); 206 207 // Calculate the projected center position and top position in world space 208 var centerRay = camera.ViewportPointToRay(Vector3(.5, 0.5, 1)); 209 var topRay = camera.ViewportPointToRay(Vector3(.5, clampHeadPositionScreenSpace, 1)); 210 211 var centerRayPos = centerRay.GetPoint(distance); 212 var topRayPos = topRay.GetPoint(distance); 213 214 var centerToTopAngle = Vector3.Angle(centerRay.direction, topRay.direction); 215 216 var heightToAngle = centerToTopAngle / (centerRayPos.y - topRayPos.y); 217 218 var extraLookAngle = heightToAngle * (centerRayPos.y - centerPos.y); 219 if (extraLookAngle < centerToTopAngle) 220 { 221 extraLookAngle = 0; 222 } 223 else 224 { 225 extraLookAngle = extraLookAngle - centerToTopAngle; 226 transform.rotation *= Quaternion.Euler(-extraLookAngle, 0, 0); 227 } 228} 229 230function GetCenterOffset () 231{ 232 return centerOffset; 233} 234 235@script AddComponentMenu ("Third Person Camera/Smooth Follow Camera") 236@script RequireComponent (Camera)