I am working on the camera control for Ross3 and it’s going pretty well. So well, in fact, that I think it is almost there. Of course it will have to be tested in play and adjustments made, but the base is there… I think.
The Goal
Make a camera script that will follow the player position and rotation, while maintaining a visual. In essence, I don’t want a wall or ceiling to block the view of the player.
The Plan
Steal other people’s hard work. And then, before you get all huffy-puffy, alter it to suit my needs.
The Implementation
I decided to start with the SmoothFollowWithCameraBumper script from the unitycommunity wiki page – written (I think – there is no name on the .js file) by Daniel P. Rossi, so thanks go to him.
This was a great start, but as I ran around a test level I found that I needed more. I decided that more Raycasting was needed (isn’t this just typical?). After playing around a bit I am casting a ray from the target to the default position of the camera. If the ray hits anything that isn’t the camera, the camera height is set to hit.point.y (the height of the point hit by the ray) minus a bumper hight offset (in this case 1). I didn’t want to change the x and z of the position of the camera, just the height.
Great. This works, but now if something is behind the player and lowering the camera by interfering with the raycast, we often can’t see the player. Solution? Raycast backward from the target, like the original Rossi file did. I am raycasting as far as the “distance” variable and if I hit something I adjust the x,y and z positions of the camera. If I don’t hit something then I just set the y position as above.
I also found an issue with standing in front of a bad guy. While I admit that this won’t happen much, it will (most likely) be possible to do and I don’t want the camera bouncing around as the bad guy turns and his corners interfere with the ray for a few frames. To solve this I started using the Physics.Raycast() overload with a LayerMask. If you look at the code you will see that there is one in there and in the editor this is exposed as a list from which you can select. I’m not sure if I am going to have the camera ray ignore the bad guys or not, but I can with a few clicks of the mouse, should I desire to do so.
That’s pretty much the gist of it. Below is the actual code, with a few comments in it. You may notice two lines setting the targetLookOffset commented out, this is because I’m not sure if I am going to move it around or not. Play testing…
The Outcome
Play a small demo to see how it works.
The Code
var target : Transform;
var layerMask : LayerMask; // which layers to interact whith while checking for things between the cam and target
var distance : float = 7.0;
var height : float = 4.0;
var damping : float = 5.0;
var smoothRotation : boolean = true;
var rotationDamping : float = 10.0;
var sideOffset : float = 0.0; // used to position the camera to the left or right of the target
// similar to an over the shoulder view of a third person shooter
// set the following public if you need to access them or want them visible in the editor
private var targetLookAtOffset : Vector3; // allows offsetting of camera lookAt, very useful for low bumper heights
private var bumperDistanceCheck : float = 2.5; // length of bumper ray
private var bumperCameraHeight : float = 1.0; // adjust camera height while bumping
private var bumperRayOffset : Vector3; // allows offset of the bumper ray from target origin
function Awake() {
target = GameObject.FindWithTag("Player").transform;
targetLookAtOffset.x = sideOffset;
// comment the next line out if you want to specify the length of the bumper ray manually
bumperDistanceCheck = distance;
}
function setTarget(newTarget : Transform) {
target = newTarget;
}
function FixedUpdate() {
var checkForCamDirection = target.TransformPoint(sideOffset, height, -distance) - target.position;
var wantedPosition = target.TransformPoint(sideOffset, height, -distance);
var back = target.transform.TransformDirection(-1 * Vector3.forward);
// check to see if there is anything between the target and the camera
var hit : RaycastHit;
Debug.DrawRay(target.position, checkForCamDirection, Color.green);
if(Physics.Raycast(target.position, checkForCamDirection, hit, distance, layerMask) && hit.transform != this.transform){
if (Physics.Raycast(target.TransformPoint(bumperRayOffset), back, hit, bumperDistanceCheck, layerMask)) {
// clamp wanted position to hit position
wantedPosition.x = hit.point.x;
wantedPosition.z = hit.point.z;
wantedPosition.y = Mathf.Lerp(hit.point.y + bumperCameraHeight, wantedPosition.y, Time.deltaTime * damping);
// targetLookAtOffset = Vector3(0,0,2);
}
else{
wantedPosition.y = Mathf.Max(target.position.y, hit.point.y - bumperCameraHeight);
//targetLookAtOffset = Vector3.zero;
}
}
targetLookAtOffset = Vector3(0,0,2);
transform.position = Vector3.Lerp(transform.position, wantedPosition, Time.deltaTime * damping);
var lookPosition : Vector3 = target.TransformPoint(targetLookAtOffset);
if (smoothRotation) {
var wantedRotation : Quaternion = Quaternion.LookRotation(lookPosition - transform.position, target.up);
transform.rotation = Quaternion.Slerp(transform.rotation, wantedRotation, Time.deltaTime * rotationDamping);
}
else {
transform.rotation = Quaternion.LookRotation(lookPosition - transform.position, target.up);
}
}