3rd Character Controller Dev - Camera problem

Hi everybody,

actually developing a third character controller,
I try to implement a collision/occlusion camera system.
(To avoid collision and character occlusion by other objects.)

The way I choose to do this system is by casting a virtual sphere from character head to camera position.
If the virtual sphere cast something then the camera move forward or backward to his initial position if anything is between camera and character. (If the virtual sphere cast nothing.)

And it works pretty well, but I have a little problem with this system.
(I already tried another system with 5 rays from character head to the camera 4 points near plane and the fifth one backward the camera position. But I don’t really like this system.)
So yes I have a problem when the player is really near some walls, the camera move forward and when she is at the character head position she move backward through the walls.

I hope there is a solution so this is my code.

using UnityEngine;
using System.Collections;

public class TP_CtrlCam_V2 : MonoBehaviour {
	
	private Vector3 cameraActualPosition;
	private Vector3 cameraPreviousPosition;
	private Vector3 playerActualPosition;
	private Vector3 playerPreviousPosition;
	
	private float rotSpeed = 2.0f;
	private float occlusionDistanceStep;
	private float preOccludedDistance;
	
	private int maxOcclusionChecks = 10;
	private int count;
	
	private Vector2 padDeadZone = new Vector2(0.1f, -0.1f);
	
	private GameObject playerRoot;
	private GameObject playerHead;
	private GameObject debugSphere;
	
	public static GameObject cameraRoot;
	public static GameObject cameraGO;
	public static float sphereRadius = 1.1f;
	
	void Start()
	{
		cameraRoot = GameObject.Find("Ctrl_Cam_World");
		playerRoot = GameObject.Find("Ctrl_Char_World");
		playerHead = GameObject.Find("Ctrl_Char_Head");
		cameraGO = GameObject.Find("GO_Camera");
		
		playerActualPosition = playerRoot.transform.position;
		playerPreviousPosition = playerRoot.transform.position;
		cameraActualPosition = cameraRoot.transform.position;
		//cameraPreviousPosition = cameraRoot.transform.position;
		
		debugSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
		debugSphere.transform.renderer.material.color = new Color(0.5f, 0.0f, 0.0f, 0.5f);
		debugSphere.transform.name = "debugSphere";
		debugSphere.transform.localScale = new Vector3(sphereRadius, sphereRadius, sphereRadius);
		debugSphere.transform.collider.enabled = false;
		debugSphere.transform.position = cameraRoot.transform.position;
		
		preOccludedDistance = Vector3.Distance(playerHead.transform.position, cameraRoot.transform.position);
		occlusionDistanceStep = preOccludedDistance / maxOcclusionChecks;
	}
	
	void FixedUpdate() 
	{
		float plLR = TP_PadManager.GamePadStick_B_Manager_LR();
		float plUD = TP_PadManager.GamePadStick_B_Manager_UD();

		///--Cam Follow--///
			playerActualPosition = playerRoot.transform.position;
			cameraActualPosition = cameraRoot.transform.position;
		
			cameraRoot.transform.position = cameraActualPosition + (playerActualPosition - playerPreviousPosition);
		
			playerPreviousPosition = playerActualPosition;
			//cameraPreviousPosition = cameraActualPosition;

		///--Cam Rotate--///
			if(plLR >= padDeadZone.x || plLR <= padDeadZone.y)
			{
				//Decentralize Quaternion Rotation Left/Right
				cameraRoot.transform.position = QRotateAround(playerHead.transform.position,cameraRoot.transform.position,plLR,rotSpeed,Vector3.up);
			}
		
			if(plUD >= padDeadZone.x || plUD <= padDeadZone.y)
			{
				//Decentralize Quaternion Rotation Up/Down
				cameraRoot.transform.position = QRotateAround(playerHead.transform.position,cameraRoot.transform.position,-plUD,rotSpeed,cameraRoot.transform.right);
			}
		
		///--Cam collision/occlusion--///
			//DebugSphere
			debugSphere.transform.position = cameraRoot.transform.position;
			//DebugRay
			Debug.DrawRay(playerHead.transform.position, -cameraRoot.transform.forward * preOccludedDistance, Color.white);
			//ApplyNewPosition;
			cameraRoot.transform.position = CalculateDesiredPosition();
		
		///--Cam LookAt--///
			Vector3 direction = playerHead.transform.position;
			Quaternion targetRotation = Quaternion.LookRotation(direction - cameraRoot.transform.position);
			float i = Time.deltaTime * 100.0f;
			cameraRoot.transform.rotation = Quaternion.Slerp(cameraRoot.transform.rotation, targetRotation, i);
	}
	
	Vector3 QRotateAround(Vector3 centerRotation, Vector3 vector, float padAxis, float rotSpeed, Vector3 rotDirection)
	{
		Quaternion rotation = Quaternion.AngleAxis (padAxis * rotSpeed, rotDirection);
		Vector3 vector2 = (vector - centerRotation);
		vector2 = rotation * vector2;
		vector = centerRotation + vector2;
		return vector;
	}

	float CheckCameraPoints (Vector3 startPos, Vector3 endPos) 
	{
		RaycastHit hitInfo;
		float distance = Vector3.Distance(startPos, endPos);
		float nearDistance = -1.0f;
		
		if(Physics.SphereCast(startPos, sphereRadius/2.0f, -cameraRoot.transform.forward, out hitInfo, distance)  hitInfo.collider.tag != "Player")
		{
			nearDistance = hitInfo.distance;
			Debug.DrawRay(startPos, -cameraRoot.transform.forward * nearDistance, Color.blue);
		}
		return nearDistance;
	}
	
	bool CheckPreOccludedState(Vector3 startPos, Vector3 endPos, float distance)
	{
		RaycastHit hitInfo;
		float currentdistance = Vector3.Distance(startPos, endPos);
		bool preOccludedPosFree = true;

		if(Physics.SphereCast(startPos, sphereRadius/2.0f, -cameraRoot.transform.forward, out hitInfo, distance)  hitInfo.collider.tag != "Player")
		{
			if(currentdistance < distance)
				preOccludedPosFree = false;
		}
		return preOccludedPosFree;
	}
	
	Vector3 CalculateDesiredPosition()
	{
		bool preOccludedState = CheckPreOccludedState(playerHead.transform.position, cameraRoot.transform.position, preOccludedDistance);
		float currentDistance = CheckCameraPoints(playerHead.transform.position, cameraRoot.transform.position);
		Vector3 newPosition = cameraRoot.transform.position;
		
		//Move camera backward
		if(currentDistance == -1.0f  preOccludedState == true)
		{
			if(count > 0  count <= maxOcclusionChecks)
			{
				newPosition -= cameraRoot.transform.forward * occlusionDistanceStep;
				count--;
			}
		}
		
		//Move camera forward
		if(currentDistance != -1.0f)
		{
			if(count >= 0  count < maxOcclusionChecks)
			{
				newPosition += cameraRoot.transform.forward * occlusionDistanceStep;
				count++;
			}
		}
		return newPosition;
	}
}

I think that one problem is the following :

  • When the player is near a wall, the code try to send virtual spheres from character head to camera.

  • Then because the virtual sphere radius size is bigger than the player character controller collider, when the code try to send a sphere the first time, nothing happen because this first sphere is partially in the wall because his position is on the character centre in X and Z.
    (The virtual sphere must be a little bigger than the camera near plane, to protect camera frustum from collision)

But what can I do ? Change the character controller collider size ?
I don’t know if it’s a good idea.

It’s a problem that I don’t have with my other system, because the 5 rays are more precise.

Anybody knows if there is another solution that changing the character controller collider radius ?
It works but I don’t like this solution because if I change the character with another more bigger or more fat like a sumo :smile:,
the radius is no longer the same.

Look in the forum search for a topic called wowcam. The camera cd in that script is pretty good, but there is a second wowcam post that has an even better one, both in c# and Js.

(Note) The topic title may be close to wowcam (eg) wowcamera or Wow Cam.