Hey guys, So I'm trying to make a 3rd person mouse aim camera, but I'm having a heck of a time trying to get my camera to stop going through the terrain whenever it goes too far down. I'm trying to constrain it's position on the Y axis based on a raycast like so: Code (CSharp): void LateUpdate() { float horizontal = Input.GetAxis("Mouse X") * rotateSpeed; vertical += (Input.GetAxis("Mouse Y") * rotateSpeed) * -1; vertical = Mathf.Clamp(vertical, yMin, 30); target.transform.Rotate(0, horizontal, 0); float desiredAngle = target.transform.eulerAngles.y; Quaternion rotation = Quaternion.Euler(vertical, desiredAngle, 0); transform.position = target.transform.position - (rotation * offset); Ray down = new Ray(this.transform.position, new Vector3(0, -1f, 0)); Debug.DrawRay(down.origin, down.direction, Color.green); // For visualization RaycastHit downInfo; if (Physics.Raycast(down, out downInfo)) { if (downInfo.distance < 0.6f) yMin = vertical; else yMin = -100f; } transform.LookAt(target.transform); } This works very well if I move the camera slowly toward the ground. It will constrain yMin and I won't be able to move the camera any further down. But if I move the camera fast it breaks the raycast and the Y axis is no longer constrained. If I put all of my camera movement into FixedUpdate(), it will catch the raycast no matter how fast I move the camera, but the movement of the camera becomes extremely choppy... Does anyone know how to overcome this issue?
How come you are using vertical += rather than just vertical =, any movement in the vertical plane is going to exponentially grow isn't it? I actually have no idea if it's correct or not, just curious as you don't do it for horizontal. Does the horizontal plane work fine?
Input.GetAxis("Mouse Y") returns a positive or negative number depending on which direction the mouse is going. It is okay keep horizontal as it is since I'm doing target.transform.Rotate(). For my vertical, I'm not rotating the target but the camera itself, which requires me to set the rotation each LateUpdate instead of relying on Transform.Rotate. This functionality is working perfectly.. I'm just wondering why my Raycast breaks and how I can fix it.
What about casting the ray from the camera's last position in fixed update, and then setting the camera in late update?
Hmm, this sounds like a good idea. Where would I populate the last position of the camera? Here's the modified script I've tried, but still not working for fast camera movement: Code (CSharp): private void FixedUpdate() { Ray down = new Ray(lastPos, new Vector3(0, -1f, 0)); RaycastHit downInfo; if (Physics.Raycast(down, out downInfo)) { Debug.DrawLine(down.origin, downInfo.point, Color.green); if (downInfo.distance < 1f) yMin = vertical; else yMin = -100f; } lastPos = this.transform.position; } void LateUpdate() { float horizontal = Input.GetAxis("Mouse X") * rotateSpeed; vertical += (Input.GetAxis("Mouse Y") * rotateSpeed) * -1; vertical = Mathf.Clamp(vertical, yMin, 30); target.transform.Rotate(0, horizontal, 0); float desiredAngle = target.transform.eulerAngles.y; Quaternion rotation = Quaternion.Euler(vertical, desiredAngle, 0); Vector3 targetPos = target.transform.position - (rotation * offset); transform.position = target.transform.position - (rotation * offset); transform.LookAt(target.transform); } I think I'm doing it how you suggested, but just want to make sure.
Also, are you sure you should be getting the y field of transform.eulerAngles? in Unity y axis would give yaw axis, where as x field would give pitch, and z gives roll (I'm pretty sure, correct if I'm wrong). As z is into the screen (foward), any angle around z would roll, y is skyward (up), so any rotation around y would give yaw (e.g looking left right), and x is to the right, so and angle around that axis would give pitch (e.g looking up and down). EDIT: Strike that just saw youre using it as a parameter to Euler(), so its more of a misnamed variable than anything. EDIT #2: I would suggest using a slerp on the camera rotation, currently if you set the rotation too far "downwards" the ray cast will detect the ground but then set the minimum to whatever the current incorrect "downwards" angle is, so next time it can still go as "downwards" as it did previously. If you slerp the camera rotation between current and target each update, the camera wont be able to jump over the boundary condition (although making it slerp at a linear speed may be difficult). EDIT #3: Also doesn't look like you are even using lastPosition?
I would get the last position in late update, the last thing. Also, have you tried this without Debug.Drawline? It's pretty slow. You can also just set the min distance in the ray and then just check for a hit, just set the ray to that length.
Also I would raycast using target.transform.position, and if it's valid update the camera to use target, otherwise set it to whatever is the closest valid. Another option is to not set the minimum angle to -100f ever, if you get a valid angle don't reset the minimum valid angle, why wold you do that? if you look down and get an invalid angle, you set the minimum valid angle to that, if it looks up and get a valid angle, you lose all the information you just obtained previously by overwriting it with an invalid minimum angle -100f.
Hmm, still not working... I tested it without the else statement that resets the yMin to -100 and it works.. I'm wondering how I could calculate what "vertical" would be at the raycast hit position? If I can figure that out then I can continuously set yMin and I believe the problem would go away.
Simple trig, you the know the Cameras vertical height/distance from the ground right (the y field in the transform position)? You know the minimum hypotenuse, which is 1f? just solve for angle. Which is minimum distance= 1f height = camera height from ground minimum angle = inverse cos (height/ minimum distance) or minimum angle = 90 - inverse cos (height/ minimum distance) depending on if you want angle from perpendicular to ground or angle from parallel to ground EDIT: Here is link explaining : http://www.mathwords.com/s/sohcahtoa.htm EDIT #2: If ground isn't always at y = SOME_CONSTANT (e.g 0), you'll have to do a raycast to determine the height straight down from camera to determine height and use that distance instead of camera y position.
I'm a little lost, but I think this is the right track... Here's what I've done: Code (CSharp): float min = Mathf.Acos( downInfo.distance); yMin = min; This is actually working, but every time it hits the minimum position it jumps back up.
I GOT IT!!!!! This is working perfectly!! Code (CSharp): Ray down = new Ray(this.transform.position, new Vector3(0, -1f, 0)); RaycastHit downInfo; if (Physics.Raycast(down, out downInfo)) { Debug.DrawLine(down.origin, downInfo.point, Color.green); Vector3 target = downInfo.point - transform.position; float angle = Vector3.Angle(transform.forward, target) + 10; float min = (Mathf.Rad2Deg * Mathf.Acos(downInfo.distance * 0.1f)) - angle; yMin = min; }