6 - Player Camera Work

This section will guide you to create the CameraWork script to follow along with your player as you play this game.

This section has nothing to do with networking, so it will be kept short.

Contents

Creating The CameraWork Script

  1. Create a new C# script called CameraWork
  2. Replace the content of CameraWork with the following:

        using UnityEngine;
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        /// <summary>
        /// Camera work. Follow a target
        /// </summary>
        public class CameraWork : MonoBehaviour
        {
    
    
            #region Public Properties
    
    
            [Tooltip("The distance in the local x-z plane to the target")]
            public float distance = 7.0f;
    
    
            [Tooltip("The height we want the camera to be above the target")]
            public float height = 3.0f;
    
    
            [Tooltip("The Smooth time lag for the height of the camera.")]
            public float heightSmoothLag = 0.3f;
    
    
            [Tooltip("Allow the camera to have a vertical from the target, for example giving more view of the scenery and less ground.")]
            public Vector3 centerOffset = Vector3.zero;
    
    
            [Tooltip("Set this as false if a component of a prefab being instantiated by Photon Network and manually call OnStartFollowing() when and if needed.")]
            public bool followOnStart = false;
    
    
            #endregion
    
    
            #region Private Properties
    
    
            // cached transform of the target
            Transform cameraTransform;
    
    
            // maintain a flag internally to reconnect if the target is lost or the camera is switched
            bool isFollowing;
    
    
            // Represents the current velocity, this value is modified by SmoothDamp() every time you call it.
            private float heightVelocity = 0.0f;
    
    
            // Represents the position we are trying to reach using SmoothDamp()
            private float targetHeight = 100000.0f;
    
    
            #endregion
    
    
            #region MonoBehaviour Messages
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase
            /// </summary>
            void Start()
            {
                // Start following the target if wanted.
                if (followOnStart)
                {
                    OnStartFollowing();
                }
    
    
            }
    
    
            /// <summary>
            /// MonoBehaviour method called after all Update functions have been called. This is useful to order script execution. For example a follow camera should always be implemented in LateUpdate because it tracks objects that might have moved inside Update.
            /// </summary>
            void LateUpdate()
            {
                // The transform target may not destroy on level load, 
                // so we need to cover corner cases where the Main Camera is different every time we load a new scene and reconnect when that happens
                if (cameraTransform == null && isFollowing)
                {
                    OnStartFollowing();
                }
    
    
                // only follow is explicitly declared
                if (isFollowing) 
                {
                    Apply ();
                }
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            /// <summary>
            /// Raises the start following event. 
            /// Use this when you don't know at the time of editing what to follow, typically instances managed by the photon network.
            /// </summary>
            public void OnStartFollowing()
            {         
                cameraTransform = Camera.main.transform;
                isFollowing = true;
                // we don't smooth anything, we go straight to the right camera shot
                Cut();
            }
    
    
            #endregion
    
    
            #region Private Methods
    
    
            /// <summary>
            /// Follow the target smoothly
            /// </summary>
            void Apply()
            {
                Vector3 targetCenter = transform.position + centerOffset;
    
    
                // Calculate the current & target rotation angles
                float originalTargetAngle = transform.eulerAngles.y;
                float currentAngle = cameraTransform.eulerAngles.y;
    
    
                // Adjust real target angle when camera is locked
                float targetAngle = originalTargetAngle;
    
    
                currentAngle = targetAngle;
    
    
                targetHeight = targetCenter.y + height;
    
    
                // Damp the height
                float currentHeight = cameraTransform.position.y;
                currentHeight = Mathf.SmoothDamp( currentHeight, targetHeight, ref heightVelocity, heightSmoothLag );
    
    
                // Convert the angle into a rotation, by which we then reposition the camera
                Quaternion currentRotation = Quaternion.Euler( 0, currentAngle, 0 );
    
    
                // Set the position of the camera on the x-z plane to:
                // distance meters behind the target
                cameraTransform.position = targetCenter;
                cameraTransform.position += currentRotation * Vector3.back * distance;
    
    
                // Set the height of the camera
                cameraTransform.position = new Vector3( cameraTransform.position.x, currentHeight, cameraTransform.position.z );
    
    
                // Always look at the target    
                SetUpRotation(targetCenter);
            }
    
    
            /// <summary>
            /// Directly position the camera to a the specified Target and center.
            /// </summary>
            void Cut( )
            {
                float oldHeightSmooth = heightSmoothLag;
                heightSmoothLag = 0.001f;
    
    
                Apply();
    
    
                heightSmoothLag = oldHeightSmooth;
            }
    
    
            /// <summary>
            /// Sets up the rotation of the camera to always be behind the target
            /// </summary>
            /// <param name="centerPos">Center position.</param>
            void SetUpRotation( Vector3 centerPos )
            {
                Vector3 cameraPos = cameraTransform.position;
                Vector3 offsetToCenter = centerPos - cameraPos;
    
    
                // Generate base rotation only around y-axis
                Quaternion yRotation = Quaternion.LookRotation( new Vector3( offsetToCenter.x, 0, offsetToCenter.z ) );
    
    
                Vector3 relativeOffset = Vector3.forward * distance + Vector3.down * height;
                cameraTransform.rotation = yRotation * Quaternion.LookRotation( relativeOffset );
    
    
            }
    
    
            #endregion
        }
    }
  3. Save the Script CameraWork

The maths behind following the player is tricky if you've just started with real-time 3D, Vectors and Quaternion based Mathematics. So I'll spare you with trying to explain this within this tutorial. However, if you are curious and want to learn, do not hesitate to contact us, We'll do our best to explain it.

However, this script is not just about crazy mathematics, something important is also set up; the ability to control when the behaviour should actively follow the player. And this is important to understand: why would we want to control when to follow a player.

Typically, let's imagine what would happen if it was always following the player. As you connect to a room full of players, each CameraWork script on the other players' instances would fight to control the "Main Camera" so that it looks at its player... Well, we don't want that, we want to only follow the local player that represents the user behind the computer.

Once we've defined the problem that we have only one camera but several Player instances, we can easily find several ways to go about it.

  1. Only attach the CameraWork script on the local player.
  2. Control the CameraWork behaviour by turning it off and on depending on whether the player it as to follow is the local player or not.
  3. Have the CameraWork attached to the Camera and watch out for when there is a local player instance in the scene and follow that one only.

These 3 options are not exhaustive, many more ways can be found, but out of these 3, we'll arbitrarily pick the second one. None of the above options are better or worse, however, this is the one that possibly requires the least amount of coding and the most flexible... "interesting..." I hear you say :)

  • We've exposed a public property followOnStart which we can set to true if we want to use this on a non-networked environment, for example in our test scene, or in completely different scenarios

  • When running in our networked based game, we'll call the public method OnStartFollowing() when we detect that the Player is a local player. This will be done in the Script PlayerManager that is created and explained in the chapter Player Prefab Networking

Back to Content

Previous Part.

 To Document Top