A Camcorder Effect In Unity

I’m fairly new at Unity and scripting in general, and am having a considerable about of trouble making this script for this camera effect, similar to what Slender: The Arrival and Outlast did. I understand what I have to do, using overshooting, shaking the camera, and idle bobbing and head bobbing while in motion, and I have the HUD for the camera already set up, but the script never seems to work when I try to code it in.

What I’m looking for is a way to essentially give the impression that the player is viewing the events through the lens of a handheld camera. If anyone already has a script readily available, or can explain to me or give me suggestions and pointers on how to create my own, I would appreciate it more than you could imagine.

In order to shake the camera as well as the head bob, along with the effect of the camera being a camcorder, then you need to tackle:

  • Camera following character
  • If moving, periodically do a head bob
  • If (attacked / whatever) do the Camera shake
  • Show a full-screen camera lens GUI

That’s the basic gist of what you need to do. But you know that, already.

The tricky part is making it so that you have smooth Camera movement.

Ill point out that you ALWAYS should use LateUpdate() when dealing with Cameras. Update() causes a lot of stutter, and FixedUpdate() causes jitter (after dealing with unity for a couple of years I can honestly say that there’s a difference between stutter and jitter)

If you don’t know of them yet, then I would highly suggest looking at:

  • Vector3.Slerp / Lerp - Camera movement ( for following the character and the headbob)
  • Quaternion.Slerp / Lerp - Camera rotation (independent of the shaking / headbob)
  • Mathf.Clamp - Maintaining a correct Camera distance

These can be found in the scripting API. Don’t even think about using Transform.Translate() or Transform.Rotate(). They are not meant for Cameras.

I’m sorry I can’t help out further, but all of my cameras are fairly simple in nature, so most 2-second scripts suffice. All I can say is good luck, and follow those basic guidelines. If you need more help just ask; this community is awesome.

I’ve played Slender: The Arrival, so I know what you mean. Also, there are heaps of tutorials on YouTube for making horror games in Unity.

For the camera moving, make an animation for the camera that makes it move and rotate left and right. For the idle animation, just make an animation that makes the camera slightly move when idle like in real life. For the camera shaking, just make an animation the violently shakes the camera around.

As for the actual screen, if you’re having trouble scripting a GUI image for the camera (Time recording etc.) just make a GUI image with just the REC thing in the corner without the time.

I’m also pretty bad at scripting too, find some tutorials on YouTube. I recommend Brackeys (brackeys.com) he is really good with Unity and you can learn from him :slight_smile:

Below are four scripts that will help you make the camcorder effect, but to add the GUI like the REC thing, isn’t in here.

Once you are done adding the below scripts as I’ve written them, on your camera object, input the Inspector values as shown in the screenshot:

The link takes you to the image of the screenshot I uploaded online.
Then follow these instructions with these scripts:

First, have a script called “AbstractTargetFollower” and put it in your scripts folder. Do not attach it to any object. This is a parent type script. The code is as follows:

using UnityEngine;

public abstract class AbstractTargetFollower : MonoBehaviour
{
	public enum UpdateType                                  // The available methods of updating are:
	{
		Auto,                                               // Let the script decide how to update
		FixedUpdate,                                        // Update in FixedUpdate (for tracking rigidbodies).
		LateUpdate,                                         // Update in LateUpdate. (for tracking objects that are moved in Update)
	}
	
	[SerializeField] protected Transform target;              		// The target object to follow
	[SerializeField] private bool autoTargetPlayer = true; 			// Whether the rig should automatically target the player.
	[SerializeField] private UpdateType updateType;         		// stores the selected update type
	
	
	virtual protected void Start() {
		// if auto targeting is used, find the object tagged "Player"
		// any class inheriting from this should call base.Start() to perform this action!
		if (autoTargetPlayer) {
			FindAndTargetPlayer();
		}
		
	}
	
	void FixedUpdate() {
		
		// we update from here if updatetype is set to Fixed, or in auto mode,
		// if the target has a rigidbody, and isn't kinematic.
		if (autoTargetPlayer && (target == null || !target.gameObject.activeSelf)) {
			FindAndTargetPlayer();
		}
		if (updateType == UpdateType.FixedUpdate || updateType == UpdateType.Auto && target != null && (target.rigidbody != null && !target.rigidbody.isKinematic)) {
			FollowTarget(Time.deltaTime);
		}
	}
	
	
	void LateUpdate() {
		
		// we update from here if updatetype is set to Late, or in auto mode,
		// if the target does not have a rigidbody, or - does have a rigidbody but is set to kinematic.
		if (autoTargetPlayer && (target == null || !target.gameObject.activeSelf)) {
			FindAndTargetPlayer();
		}
		if (updateType == UpdateType.LateUpdate || updateType == UpdateType.Auto && target != null && (target.rigidbody == null || target.rigidbody.isKinematic)) {
			FollowTarget(Time.deltaTime);
		}
	}
	
	
	protected abstract void FollowTarget(float deltaTime);
	
	public void FindAndTargetPlayer() {
		
		// only target if we don't already have a target
		if (target == null) {
			// auto target an object tagged player, if no target has been assigned
			var targetObj = GameObject.FindGameObjectWithTag("Player");	
			if (targetObj) {
				SetTarget(targetObj.transform);
			}
		}
	}
	
	
	public virtual void SetTarget (Transform newTransform) {
		target = newTransform;
	}
	
	public Transform Target { get { return this.target; } }
	
	
}

Then, have a script called “LookatTarget”. The code is as follows:

using UnityEngine;
using System.Collections;

public class LookatTarget : AbstractTargetFollower {

	// A simple script to make one object look at another,
	// but with optional constraints which operate relative to
	// this gameobject's initial rotation.

	// Only rotates around local X and Y.

	// Works in local coordinates, so if this object is parented
	// to another moving gameobject, its local constraints will
	// operate correctly
	// (Think: looking out the side window of a car, or a gun turret
	// on a moving spaceship with a limited angular range)

	// to have no constraints on an axis, set the rotationRange greater than 360.
	
	[SerializeField] Vector2 rotationRange; 
	[SerializeField] float followSpeed = 1;

	Vector3 followAngles;
	protected Vector3 followVelocity;
	Quaternion originalRotation;

	// Use this for initialization
	protected override void Start () {
		base.Start();
		originalRotation = transform.localRotation;
	}

	protected override void FollowTarget (float deltaTime)
	{
		// we make initial calculations from the original local rotation
		transform.localRotation = originalRotation;

		// tackle rotation around Y first
		Vector3 localTarget = transform.InverseTransformPoint(target.position);
		float yAngle = Mathf.Atan2( localTarget.x, localTarget.z) * Mathf.Rad2Deg;

		yAngle = Mathf.Clamp ( yAngle, -rotationRange.y * 0.5f, rotationRange.y * 0.5f );
		transform.localRotation = originalRotation * Quaternion.Euler( 0, yAngle, 0 );

		// then recalculate new local target position for rotation around X
		localTarget = transform.InverseTransformPoint(target.position);
		float xAngle = Mathf.Atan2( localTarget.y, localTarget.z) * Mathf.Rad2Deg;
		xAngle = Mathf.Clamp ( xAngle, -rotationRange.x * 0.5f, rotationRange.x * 0.5f );
		var targetAngles = new Vector3( followAngles.x + Mathf.DeltaAngle( followAngles.x, xAngle ), followAngles.y + Mathf.DeltaAngle( followAngles.y, yAngle ) );

		// smoothly interpolate the current angles to the target angles
		followAngles = Vector3.SmoothDamp( followAngles, targetAngles, ref followVelocity, followSpeed );


		// and update the gameobject itself
		transform.localRotation = originalRotation * Quaternion.Euler( -followAngles.x, followAngles.y, 0 );

	}
}

Then have a script called “HandHeldCam”. This is the script that you will actually place on your camera. The code is as follows:

using UnityEngine;
using System.Collections;

public class HandHeldCam : LookatTarget {

	[SerializeField] float swaySpeed = .5f;
	[SerializeField] float baseSwayAmount = .5f;
	[SerializeField] float trackingSwayAmount = .5f;
	[Range(-1,1)][SerializeField] float trackingBias = 0;

	// Use this for initialization
	protected override void Start () {
		base.Start();
	}
	
	protected override void FollowTarget (float deltaTime)
	{
		base.FollowTarget(deltaTime);

		float bx = (Mathf.PerlinNoise(0,Time.time*swaySpeed)-0.5f);
		float by = (Mathf.PerlinNoise(0,(Time.time*swaySpeed)+100))-0.5f;

		bx *= baseSwayAmount;
		by *= baseSwayAmount;

		float tx = (Mathf.PerlinNoise(0,Time.time*swaySpeed)-0.5f)+trackingBias;
		float ty = ((Mathf.PerlinNoise(0,(Time.time*swaySpeed)+100))-0.5f)+trackingBias;

		tx *= -trackingSwayAmount * followVelocity.x;
		ty *= trackingSwayAmount * followVelocity.y;

		transform.Rotate( bx+tx, by+ty, 0 );

	}
}

Have one last script called “TargetFieldOfView” and attach it to your camera as well. The code is as follows:

using UnityEngine;
using System.Collections;

public class TargetFieldOfView : AbstractTargetFollower {

	// This script is primarily designed to be used with the "LookAtTarget" script to enable a
	// CCTV style camera looking at a target to also adjust its field of view (zoom) to fit the
	// target (so that it zooms in as the target becomes further away).
	// When used with a follow cam, it will automatically use the same target.	

	[SerializeField] float fovAdjustTime = 1;			// the time taken to adjust the current FOV to the desired target FOV amount.
	[SerializeField] float zoomAmountMultiplier = 2;	// a multiplier for the FOV amount. The default of 2 makes the field of view twice as wide as required to fit the target.
	[SerializeField] bool includeEffectsInSize = false;	  // changing this only takes effect on startup, or when new target is assigned.


	float boundSize;
	float fovAdjustVelocity;
	Camera cam;
	Transform lastTarget;

	// Use this for initialization
	protected override void Start ()
	{
		base.Start ();
	
		boundSize = MaxBoundsExtent( target, includeEffectsInSize );

		// get a reference to the actual camera component:
		cam = GetComponentInChildren<Camera>();
	}

	protected override void FollowTarget (float deltaTime)
	{

		// calculate the correct field of view to fit the bounds size at the current distance
		float dist = (target.position-transform.position).magnitude;
		float requiredFOV = Mathf.Atan2(boundSize,dist) * Mathf.Rad2Deg * zoomAmountMultiplier;
	
		cam.fieldOfView = Mathf.SmoothDamp(cam.fieldOfView, requiredFOV, ref fovAdjustVelocity, fovAdjustTime);
	}

	public override void SetTarget (Transform newTransform)
	{
		base.SetTarget (newTransform);
		boundSize = MaxBoundsExtent( newTransform, includeEffectsInSize );
	}
	
	public static float MaxBoundsExtent(Transform obj, bool includeEffects)
	{

		// get the maximum bounds extent of object, including all child renderers, 
		// but excluding particles and trails, for FOV zooming effect.
		
		Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();
		
		Bounds bounds = new Bounds();
		bool initBounds = false;
		foreach(Renderer r in renderers)
		{
			if (!((r is TrailRenderer) || (r is ParticleRenderer) || (r is ParticleSystemRenderer)))
			{
				if (!initBounds)
				{
					initBounds = true;
					bounds = r.bounds;
				} else {
					bounds.Encapsulate(r.bounds);
				}
			}
		}
		float max = Mathf.Max(bounds.extents.x, bounds.extents.y, bounds.extents.z);
		return max;
		
	}


}

There are many variables to tweak the perlin noise head bobbing etc. All the scripts came from Unity’s own Sample Assets Beta.