🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Third person camera with free look

Started by
0 comments, last by ferrous 8 years, 4 months ago

I have been trying to implement a third person camera syste: m in Unity, but not based on the default system. My goal is something more like Unreal Engine system, used also in Mass Effect or The Witcher 2/3. This system provides a camera free look, and then the character moves towards the camera direction.

My current approach consists of having a pivot GameObject, that follows the playecr GameObject. The pivot handles camera free look and other things like selecting objects in front of the camera (which right no, is not working well), and the crosshair (totally missing):


public class PivotControl : MonoBehaviour {
	float rotationY = 0F;
	Camera cam;
	Text label;

	// Use this for initialization
	void Start () {
		cam = gameObject.GetComponentInChildren<Camera> ();
		label = GameObject.Find ("Label").GetComponent<Text>();;
	}
	
	// Update is called once per frame
	void Update () {		
		if (Input.GetAxis ("Mouse X") != 0 || Input.GetAxis ("Mouse Y") != 0) {
			
			float rotationX = gameObject.transform.localEulerAngles.y + Input.GetAxis("Mouse X") * 2f;

			rotationY += Input.GetAxis("Mouse Y") * 5f;		
			gameObject.transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);
			cam.transform.LookAt(cam.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, cam.farClipPlane)));
			RaycastHit hit;					
			Ray ray =  cam.ViewportPointToRay(new Vector3(0.5F, 0.5F, 0));
			if (Physics.Raycast (ray, out hit, 100)) {
					 
				if (hit.collider.tag=="Interactable")
					label.text = "Press E to interact with "+hit.collider.name;
			} else {
				label.text = "";
			}
		}
	}
}

The player script handles character movement and I have noticed that the rotatio towards camera direction works fine for small angles, when the difference between the player direction and the camera direction is not too large:


public class PlayerControl : MonoBehaviour {

	public GameObject pivot;
	Animator anim;
	int direction = 0;
	int FORWARD  = 1;
	int BACKWARD = 2;
	int LEFT = 3;
	int RIGHT = 4;

	// Use this for initialization
	void Start () {
		//cam = gameObject.GetComponent<Camera> ();
		//Cursor.visible = false;
		//Cursor.lockState = CursorLockMode.Locked;
		anim = gameObject.GetComponent<Animator> ();
	}
	
	// Update is called once per frame
	void Update () {
		Vector3 rot;

		if (Input.GetKeyDown (KeyCode.W)) {
			direction = FORWARD;
		} else if (Input.GetKeyDown (KeyCode.S)) {
			//go back
			direction = BACKWARD;
		} else if (Input.GetKeyDown (KeyCode.A)) {
			direction = LEFT;
		} else if (Input.GetKeyDown (KeyCode.D)) {
			direction = RIGHT;
		} else if (Input.GetKeyUp (KeyCode.W) || Input.GetKeyUp (KeyCode.S) || Input.GetKeyUp (KeyCode.A) || Input.GetKeyUp (KeyCode.D)) {
			direction = 0;
		}

		switch (direction) {
			case 0: //do nothing
				anim.SetInteger("State",0);
				break;
			case 1:		//forward
				rot = gameObject.transform.localEulerAngles;
				rot.y = pivot.transform.localEulerAngles.y;
				gameObject.transform.rotation = Quaternion.Slerp (gameObject.transform.rotation, Quaternion.Euler (rot), 5f * Time.deltaTime);
				anim.SetInteger("State",1);
				gameObject.transform.Translate (0, 0, 20 * Time.deltaTime);				
			pivot.transform.position = Vector3.Slerp(pivot.transform.position, gameObject.transform.position, 5f *Time.deltaTime);
				break;
			case 2: //
				rot = gameObject.transform.localEulerAngles;
				rot.y = pivot.transform.localEulerAngles.y+180f;
				gameObject.transform.rotation = Quaternion.Slerp (gameObject.transform.rotation, Quaternion.Euler (rot), 5f * Time.deltaTime);
				anim.SetInteger("State",1);
				gameObject.transform.Translate (0, 0, 20 * Time.deltaTime);
				pivot.transform.position = Vector3.Slerp(pivot.transform.position, gameObject.transform.position, 5f *Time.deltaTime);
				break;
			case 3:
				rot = gameObject.transform.localEulerAngles;
				rot.y = pivot.transform.localEulerAngles.y-90f;
				gameObject.transform.rotation = Quaternion.Slerp (gameObject.transform.rotation, Quaternion.Euler (rot), 5f * Time.deltaTime);
				anim.SetInteger("State",1);
				gameObject.transform.Translate (0, 0, 20 * Time.deltaTime);
				pivot.transform.position = Vector3.Slerp(pivot.transform.position, gameObject.transform.position, 5f *Time.deltaTime);
			break;
			case 4:
				rot = gameObject.transform.localEulerAngles;
				rot.y = pivot.transform.localEulerAngles.y+90f;
				gameObject.transform.rotation = Quaternion.Slerp (gameObject.transform.rotation, Quaternion.Euler (rot), 5f * Time.deltaTime);
				anim.SetInteger("State",1);
				gameObject.transform.Translate (0, 0, 20 * Time.deltaTime);				
				pivot.transform.position = Vector3.Slerp(pivot.transform.position, gameObject.transform.position, 5f *Time.deltaTime);
			break;
		}
	}
}

Can somebody, please, suggest some improvements for this code? Im specially interested in solving the selection problem and implementing the crosshair.

Advertisement

I would break out the label and code for interactive object checking into a different component, they aren't really related to a third person camera look. You could easily do the raycasting from the camera in another component attached to the same object. It'll keep things cleaner, and more re-usable.

Your movement code is odd. I would avoid looking at keys, and look at input controls. That would make it trivial to swap to using WASD or a twin stick controller. I'd also set the a vertical & horizontal movement axis separately, your else ifs block holding both W and A, for example. Granted it's been ages since I've played a Mass Effect, so maybe that's by design. Rather than switching off direction and rotating, you could project the camera's forward and right(or left) vectors onto a 2d plane, and then scale those by the vertical and horizontal movement axis to move camera relative.

Some Nits:

Be consistent, if you are setting Direction as LEFT, RIGHT, etc, use LEFT, RIGHT in your switch-case, not 0, 1, 2, 3. If you're not checking for null or asserting, you should probably stick a [requirecomponent] trait on your classes for the things you require to exist. Try not to stick magic numbers in your code, add some [serializefield] floats for those vertical and horiztonal look axis scalars.

EDIT: For selection, I think I would do it in two stages, I would get all the interactive objects that are within X distance to the player, get rid of any the camera is not facing (dot check), and if it's only one, then I would show that one. If there is more than one, I would create a line from the camera, do a distance-line check, and take the closest one. That makes it less fiddly for the player, if they're standing in the elevator, they probably want to activate it. If they are near multiple corpses, or are standing in the elevator but there is a dead corpse to loot, if they're looking close to the corpse, they probably want to loot it. (You'll notice that exact same logic would work for a first person game too -- and strengthens my argument for ripping it out into it's own component)

This topic is closed to new replies.

Advertisement