Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

Keep object in camera view

Started by davejones Jul 18, 2018 at 6:25 PM 31 replies 7.4k views
Original Post
davejones
davejones

I am using the rts cam script and was wondering if it was possible to stop the camera from going out of sight of an object. This set up is being developed in unity and is for a 3D game. The camera is being used to rotate, pan and zoom in/around an object. At the moment I can move out of sight of the object. Is there a way to block the camera from going out of sight of the object? I have seen multiple ways to keep an object in sight through the camera, however I am unsure which route to take.

I have attached the script being used below.


public class RTSCam : MonoBehaviour {

	private float dist;
	
	private float orbitSpeedX;
	private float orbitSpeedY;
	private float zoomSpeed;
	
	public float rotXSpeedModifier=0.25f;
	public float rotYSpeedModifier=0.25f;
	public float zoomSpeedModifier=5;
	
	public float minRotX=-20;
	public float maxRotX=70;
	
	//public float minRotY=45;
	//public float maxRotY=315;
	
	public float minZoom=3;
	public float maxZoom=15;
	
	public float panSpeedModifier=1f;
	
	// Use this for initialization
	void Start () {
		dist=transform.localPosition.z;
		
		DemoSceneUI.SetSceneTitle("RTS camera, the camera will orbit around a pivot point but the rotation in z-axis is locked");
		
		string instInfo="";
			instInfo+="- swipe or drag on screen to rotate the camera\n";
			instInfo+="- pinch or using mouse wheel to zoom in/out\n";
			instInfo+="- swipe or drag on screen with 2 fingers to move around\n";
			instInfo+="- single finger interaction can be simulate using left mosue button\n";
			instInfo+="- two fingers interacion can be simulate using right mouse button";
		DemoSceneUI.SetSceneInstruction(instInfo);
	}
	
	void OnEnable(){
		IT_Gesture.onDraggingE += OnDragging;
		IT_Gesture.onMFDraggingE += OnMFDragging;
		
		IT_Gesture.onPinchE += OnPinch;
		
		orbitSpeedX=0;
		orbitSpeedY=0;
		zoomSpeed=0;
	}
	
	void OnDisable(){
		IT_Gesture.onDraggingE -= OnDragging;
		IT_Gesture.onMFDraggingE -= OnMFDragging;
		
		IT_Gesture.onPinchE -= OnPinch;
	}

	
	// Update is called once per frame
	void Update () {
		//get the current rotation
		float x=transform.parent.rotation.eulerAngles.x;
		float y=transform.parent.rotation.eulerAngles.y;
		
		//make sure x is between -180 to 180 so we can clamp it propery later
		if(x>180) x-=360;
		
		//calculate the x and y rotation
		//Quaternion rotationY=Quaternion.Euler(0, Mathf.Clamp(y+orbitSpeedY, minRotY, maxRotY), 0);
		Quaternion rotationY=Quaternion.Euler(0, y+orbitSpeedY, 0);
		Quaternion rotationX=Quaternion.Euler(Mathf.Clamp(x+orbitSpeedX, minRotX, maxRotX), 0, 0);
		
		//apply the rotation
		transform.parent.rotation=rotationY*rotationX;
		
		//calculate the zoom and apply it
		dist+=Time.deltaTime*zoomSpeed*0.01f;
		dist=Mathf.Clamp(dist, -maxZoom, -minZoom);
		transform.localPosition=new Vector3(0, 0, dist);
		
		//reduce all the speed
		orbitSpeedX*=(1-Time.deltaTime*12);
		orbitSpeedY*=(1-Time.deltaTime*3);
		zoomSpeed*=(1-Time.deltaTime*4);
		
		//use mouse scroll wheel to simulate pinch, sorry I sort of cheated here
		zoomSpeed+=Input.GetAxis("Mouse ScrollWheel")*500*zoomSpeedModifier;
	}
	
	//called when one finger drag are detected
	void OnDragging(DragInfo dragInfo){
		//if the drag is perform using mouse2, use it as a two finger drag
		if(dragInfo.isMouse && dragInfo.index==1) OnMFDragging(dragInfo);
		//else perform normal orbiting
		else{
			//apply the DPI scaling
			dragInfo.delta/=IT_Gesture.GetDPIFactor();
			//vertical movement is corresponded to rotation in x-axis
			orbitSpeedX=-dragInfo.delta.y*rotXSpeedModifier;
			//horizontal movement is corresponded to rotation in y-axis
			orbitSpeedY=dragInfo.delta.x*rotYSpeedModifier;
		}
	}
	
	//called when pinch is detected
	void OnPinch(PinchInfo pinfo){
		zoomSpeed-=pinfo.magnitude*zoomSpeedModifier/IT_Gesture.GetDPIFactor();
	}
	
	//called when a dual finger or a right mouse drag is detected
	void OnMFDragging(DragInfo dragInfo){
		//apply the DPI scaling
		dragInfo.delta/=IT_Gesture.GetDPIFactor();
		
		//make a new direction, pointing horizontally at the direction of the camera y-rotation
		Quaternion direction=Quaternion.Euler(0, transform.parent.rotation.eulerAngles.y, 0);
		
		//calculate forward movement based on vertical input
		Vector3 moveDirZ=transform.parent.InverseTransformDirection(direction*Vector3.forward*-dragInfo.delta.y);
		//calculate sideway movement base on horizontal input
		Vector3 moveDirX=transform.parent.InverseTransformDirection(direction*Vector3.right*-dragInfo.delta.x);
		
		//move the cmera 
		transform.parent.Translate(moveDirZ * panSpeedModifier * Time.deltaTime);
		transform.parent.Translate(moveDirX * panSpeedModifier * Time.deltaTime);
	}
	
	
	
	//for the camera to auto rotate and focus on a predefined position
	/*
	public float targetRotX;
	public float targetRotY;
	public float targetRotZ;
	public Vector3 targetPos;
	public float targetZoom;
	
	IEnumerator LerpToPoint(){
		Quaternion startRot=transform.parent.rotation;
		Quaternion endRot=Quaternion.Euler(targetRotX, targetRotY, targetRotZ);
		
		Vector3 startPos=transform.parent.position;
		Vector3 startZoom=transform.localPosition;
		
		float duration=0;
		while(duration<1){
			transform.parent.rotation=Quaternion.Lerp(startRot, endRot, duration);
			transform.parent.position=Vector3.Lerp(startPos, targetPos, duration);
			transform.localPosition=Vector3.Lerp(startZoom, new Vector3(0, 0, -targetZoom), duration);
			duration+=Time.deltaTime;
			yield return null;
		}
		
		transform.parent.rotation=endRot;
		transform.parent.position=targetPos;
		transform.localPosition=new Vector3(0, 0, -targetZoom);
	}
	*/
	
	
	/*
	private bool instruction=false;
	void OnGUI(){
		string title="RTS camera, the camera will orbit around a pivot point but the rotation in z-axis is locked.";
		GUI.Label(new Rect(150, 10, 400, 60), title);
		
		if(!instruction){
			if(GUI.Button(new Rect(10, 55, 130, 35), "Instruction On")){
				instruction=true;
			}
		}
		else{
			if(GUI.Button(new Rect(10, 55, 130, 35), "Instruction Off")){
				instruction=false;
			}
			
			GUI.Box(new Rect(10, 100, 400, 100), "");
			
			string instInfo="";
			instInfo+="- swipe or drag on screen to rotate the camera\n";
			instInfo+="- pinch or using mouse wheel to zoom in/out\n";
			instInfo+="- swipe or drag on screen with 2 fingers to move around\n";
			instInfo+="- single finger interaction can be simulate using left mosue button\n";
			instInfo+="- two fingers interacion can be simulate using right mouse button";
			
			GUI.Label(new Rect(15, 105, 390, 90), instInfo);
		}
	}
	*/
	
}


Scouting Ninja
Scouting Ninja
5 minutes ago, davejones said:

is there a way to block the camera from going out of sight of the object?

Yes, you just need to define what "sight" is and what the rules are.

For example, if the object has to stay in the center of the view, you can just use a ray check to see that there isn't anything in between the camera and the object; then move the camera so there no longer is.

However if you mean the object needs to stay in view all the time, then you need to grab the rendering bounds: https://docs.unity3d.com/ScriptReference/Renderer-bounds.html and keep the object inside; this will work kind of like reversed collision.


Can you describe how the camera is going out of sight?

davejones
davejones
5 minutes ago, Scouting Ninja said:

Yes, you just need to define what "sight" is and what the rules are.

For example, if the object has to stay in the center of the view, you can just use a ray check to see that there isn't anything in between the camera and the object; then move the camera so there no longer is.

However if you mean the object needs to stay in view all the time, then you need to grab the rendering bounds: https://docs.unity3d.com/ScriptReference/Renderer-bounds.html and keep the object inside; this will work kind of like reversed collision.


Can you describe how the camera is going out of sight?

When I say going out of sight the target is a 3D mesh. So when I am rotating/panning, the camera can move completely out of sight of the mesh.

Scouting Ninja
Scouting Ninja
2 minutes ago, davejones said:

the camera can move completely out of sight of the mesh.

Yes, but how does it go out of sight. For example if you rotate around the object, does a object obscure your view, or does your camera move freely like a flying camera?

davejones
davejones
1 minute ago, Scouting Ninja said:

Yes, but how does it go out of sight. For example if you rotate around the object, does a object obscure your view, or does your camera move freely like a flying camera?

The camera moves freely in that I can move out of sight of the mesh easily.

Scouting Ninja
Scouting Ninja

OK, if you want to keep flying freely then you just need to keep the camera rotated towards the object. To do this you first calculate offset.

Offset = PointB - Point A. Offset is extremely useful because it gives both the direction and distance to a object. The problem is you have to "extract" the rotation and you do this by removing the distance.

Rotation = OffsetVector / magnitude. In Unity we can just use Vector.normalize Except unity uses Quaternions so there is one more step, you need to convert your vector rotation to a Quaternions rotation.

Vector rotation to Quaternions = Quaternion.LookRotation(Vector, a up vector); Using these 3 steps your camera will be "locked" to a target.


Working Unity look at target script under the spoiler:

Spoiler




using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UnityLookAt : MonoBehaviour {

    public GameObject targetObject;

    Vector3 offset= Vector3.zero;
    Vector3 vecRot = Vector3.zero;

    // Use this for initialization
    void Start () {
	}
	
	// Update is called once per frame
	void Update () {
        offset = targetObject.transform.position - this.transform.position;
        vecRot = offset.normalized;

        this.transform.rotation = Quaternion.LookRotation(vecRot, Vector3.up);
    }
}

Note, because the camera is not a 6DOF camera, we lock the look axis to the global up. If it was a missile or other free moving object we would use the local up direction.


This is Linear Algebra, it is what is used to make games. It is easy to learn so don't feel demotivated.

davejones
davejones
3 minutes ago, Scouting Ninja said:

OK, if you want to keep flying freely then you just need to keep the camera rotated towards the object. To do this you first calculate offset.

Offset = PointB - Point A. Offset is extremely useful because it gives both the direction and distance to a object. The problem is you have to "extract" the rotation and you do this by removing the distance.

Rotation = OffsetVector / magnitude. In Unity we can just use Vector.normalize Except unity uses Quaternions so there is one more step, you need to convert your vector rotation to a Quaternions rotation.

Vector rotation to Quaternions = Quaternion.LookRotation(Vector, a up vector); Using these 3 steps your camera will be "locked" to a target.


Working Unity look at target script under the spoiler:

Hide contents





using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UnityLookAt : MonoBehaviour {

    public GameObject targetObject;

    Vector3 offset= Vector3.zero;
    Vector3 vecRot = Vector3.zero;

    // Use this for initialization
    void Start () {
	}
	
	// Update is called once per frame
	void Update () {
        offset = targetObject.transform.position - this.transform.position;
        vecRot = offset.normalized;

        this.transform.rotation = Quaternion.LookRotation(vecRot, Vector3.up);
    }
}

Note, because the camera is not a 6DOF camera, we lock the look axis to the global up. If it was a missile or other free moving object we would use the local up direction.


This is Linear Algebra, it is what is used to make games.

Thanks for the reply. I have added this script to the camera and I can still pan out of the objects sight. The target gameobject I have used is located in the centre of the 3D mesh.

Scouting Ninja
Scouting Ninja
2 hours ago, davejones said:

I have added this script to the camera and I can still pan out of the objects sight.

I don't understand what you mean by this. Panning means rotating the camera from it's own center point, there is no possible way in the universe to pan a camera and keep a object in focus.

Image result for camera panning

This is how pan works, you will see that panning will always result in the camera rotating away from a object. The code I gave you only looks at a target point.

To rotate around a fixed object you need to either learn Quaternions or 3D rotation matrices.

davejones
davejones
11 hours ago, Scouting Ninja said:

I don't understand what you mean by this. Panning means rotating the camera from it's own center point, there is no possible way in the universe to pan a camera and keep a object in focus.

Image result for camera panning

This is how pan works, you will see that panning will always result in the camera rotating away from a object. The code I gave you only looks at a target point.

To rotate around a fixed object you need to either learn Quaternions or 3D rotation matrices.

I am looking into keeping the panning within a bounded area so that the camera can't be panned out of sight of the object. Effectively use collisions. So when the camera is being panned it can only move so far until it is blocked.


     private void LateUpdate()
        {
            if (VisibleArea == null)
            {
                return;
            }

            Bounds b = VisibleArea.bounds;
            Vector3 world1 = _camera.ViewportToWorldPoint(Vector3.zero);
            Vector3 world2 = _camera.ViewportToWorldPoint(Vector3.one);
            Vector3 pos = transform.position;

            // move the camera so that the visible area is visible, if necessary

            // x axis
            if (world1.x > b.max.x)
            {
                pos.x -= (world1.x - b.max.x);
            }
            else if (world2.x < b.min.x)
            {
                pos.x += (b.min.x - world2.x);
            }

            // y axis
            if (world1.y > b.max.y)
            {
                pos.y -= (world1.y - b.max.y);
            }
            else if (world2.y < b.min.y)
            {
                pos.y += (b.min.y - world2.y);
            }

            transform.position = pos + (velocity * Time.deltaTime);
            velocity *= Dampening;
        }

Looking at implementing the code above.

davejones
davejones


	private void LateUpdate()
	{
		if (VisibleArea == null)
		{
			return;
		}

		Bounds b = VisibleArea.bounds;
		Vector3 world1 = _camera.ViewportToWorldPoint(Vector3.zero);
		Vector3 world2 = _camera.ViewportToWorldPoint(Vector3.one);
		Vector3 pos = transform.position;

		// move the camera so that the visible area is visible, if necessary

		// x axis
		if (world1.x > b.max.x)
		{
			pos.x -= (world1.x - b.max.x);
		}
		else if (world2.x < b.min.x)
		{
			pos.x += (b.min.x - world2.x);
		}

		// y axis
		if (world1.y > b.max.y)
		{
			pos.y -= (world1.y - b.max.y);
		}
		else if (world2.y < b.min.y)
		{
			pos.y += (b.min.y - world2.y);
		}

		transform.position = pos + (velocity * Time.deltaTime);
		velocity *= Dampening;
	}
	
	

Looking at using this code as a way to keep a certain area visible at all times.

Kryzon
Kryzon

Without looking at the code, here's an idea: on every update you want to transform the camera in some way, like moving, rotating or scaling it. If the camera + that transformation will cause the object to go out of the screen, then don't transform the camera on that update.
I think this type of logic is called "filtering". Using any tests you want, you are filtering out any transformations that will cause the camera to look away from one or more objects before applying the transformation.
(This works only if the objects are static.)

Another solution is to compensate \ adjust after the fact: do move the camera every update, but in case one or more objects go out of frame, compute a movement or rotation for that camera that will cause it to bring them all back into view (eg aim the camera at the median* of all object locations, then dolly the camera back until all objects fit within the visible volume).

*Use the median of their locations (the center of the sum of their bounds), rather than using their average locations (sum locations and divide by n), because if you use the average the camera will always focus on clustered objects rather than every object equally.

Scouting Ninja
Scouting Ninja
7 hours ago, davejones said:

So when the camera is being panned it can only move so far until it is blocked.

You will want to find the distance from the camera to the side of the render border. Then using very basic triangulation you will limit how much the camera can turn. This is something used a lot in CGI.


If you don't know the math then you can manually calibrate it. In Unity duplicate your object in focus, then move it till it reaches the edge of the screen. The angle from the object to the camera is your rotation limit. In Unity the bounds will also give you this rotation, just checked it is 45 degrees rotation by default.

So by limiting the rotation to say 40 degrees to can always have the object on screen.

davejones
davejones
43 minutes ago, Scouting Ninja said:

You will want to find the distance from the camera to the side of the render border. Then using very basic triangulation you will limit how much the camera can turn. This is something used a lot in CGI.


If you don't know the math then you can manually calibrate it. In Unity duplicate your object in focus, then move it till it reaches the edge of the screen. The angle from the object to the camera is your rotation limit. In Unity the bounds will also give you this rotation, just checked it is 45 degrees rotation by default.

So by limiting the rotation to say 40 degrees to can always have the object on screen.

When I say panning I mean dragging/swiping the camera out of view of the object I want to keep visible. I am trying to prevent the ability to drag/Swipe out of sight of the object in focus.

Zakwayda
Zakwayda
20 minutes ago, davejones said:

When I say panning I mean dragging/swiping the camera out of view of the object I want to keep visible. I am trying to prevent the ability to drag/Swipe out of sight of the object in focus.

So by panning do you mean strictly linear motion, with no angular/rotational motion involved?

lawnjelly
lawnjelly

Am tired now but basically this is what I'd be thinking about:

  1. Let the original rotation of the camera be quaternion B.
  2. Use a lookat function to get a quaternion A from the camera position to the object centre.
  3. Use a slerp with a limit function to slerp from A to B. If B is within the limit it will stay at B, as the object will still be in view, otherwise it will move the camera view towards A.

I believe there are built-in versions of these functions in Unity, I'm pretty sure I used them in my Tower Defence game.

Quaternion.LookRotation, Quaternion.Slerp

Note that this won't take account of the size of the mesh at different magnifications, you might have to account for that.

davejones
davejones
14 hours ago, Zakwayda said:

So by panning do you mean strictly linear motion, with no angular/rotational motion involved?

When I say panning I mean when the user uses 2 fingers to slide in addition to angular/rotational motion from when the user drags one finger across the screen. So 1 finger on touch input = rotational motion

2 finger sliding touch input = panning

The issue I am trying to solve is to try and limit the panning to a specific area. So the user can only pan within certain area meaning the target object never goes out of sight. The link attached shows the kind of panning I am trying to add to my project in addition to having the ability to carry out rotational movements.


https://gfycat.com/gifs/detail/GlisteningUnselfishLeonberger

lawnjelly
lawnjelly
1 hour ago, davejones said:

When I say panning I mean when the user uses 2 fingers to slide in addition to angular/rotational motion from when the user drags one finger across the screen. So 1 finger on touch input = rotational motion

2 finger sliding touch input = panning

Do you mean 'tracking' (is this the confusion?):

https://en.wikipedia.org/wiki/Panning_(camera)

Quote

Panning should never be confused with tracking or "travelling," in which the camera is not just swivelled but is physically displaced left or right, generally by being rolled parallel to its subject.



davejones
davejones
32 minutes ago, lawnjelly said:

Do you mean 'tracking' (is this the confusion?):

https://en.wikipedia.org/wiki/Panning_(camera)



I mean tracking/travelling yes. I do apologise for the confusion in terminology. So effectively have the camera tracking/travelling only within a bounded area as well as being able to rotate and zoom.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.