Designing a system to switch between cameras without disabling any of them
Greetings,
I'm currently trying to get a system of 5 cameras through which I can cycle by choosing the one I want to look at through keyboard inputs (AlphaKey 1 to 5). The layout I want to obtain is depicted in the screenshot
But I cannot get my script to work (and I'm kind of ashamed by how ugly it looks) - the editor crashes when I test it and then press any key from 1 to 5. Here's what I've done so far, I apologize for the poor polishing and... everything else.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MasterController : MonoBehaviour {
public Camera[] camerasArray;
public int selectedNum = 1, length;
private void OnEnable()
{
InitializeCamerasArray(); //initialize cameras before startup
length = camerasArray.Length;
}
private void Update()
{
//change the camera if the input is given
if (AssignNum())
{
StartCoroutine(SwitchCamera());
}
}
private void InitializeCamerasArray() //inserts the cameras in the array
{
camerasArray = Camera.allCameras;
}
private bool AssignNum() //changes the camera number to the correspondent pressed key
{
if (!Input.anyKeyDown)
{
return false;
} else
if (Input.GetKey("1"))
{
selectedNum = 1;
return true;
} else
if (Input.GetKey("2"))
{
selectedNum = 2;
return true;
} else
if (Input.GetKey("3"))
{
selectedNum = 3;
return true;
} else
if (Input.GetKey("4"))
{
selectedNum = 4;
return true;
} else
if (Input.GetKey("5"))
{
selectedNum = 5;
return true;
}
return false;
}
IEnumerator SwitchCamera() //switch to the camera selected by attribute
{
int i = 0;
while (i < length)
{
if ((i + 1) != selectedNum)
{
BackGroundRect(i, camerasArray[i].rect);
i++;
}
}
ForeGroundRect(camerasArray[selectedNum - 1].rect);
yield return new WaitForSeconds(2);
}
private void BackGroundRect(int _i, Rect _r)
{
float x = (1 / (length - 1)) * _i;
float y = 0;
float w = (1 / (length - 1)) - 0.05f / length;
float h = (1 / (length - 1)) - 0.05f / length;
_r = new Rect(x, y, w, h);
}
private void ForeGroundRect(Rect _r)
{
float x = (1 - 1 / (length - 1)) / 2;
float y = 1 / (length - 1);
float w = 1 - 1 / (length - 1);
float h = 1 - 1 / (length - 1);
_r = new Rect(x, y, w, h);
}
}
Everything works but the part where SwitchCamera() starts. The idea is that I put all the non-selected cameras in the lower row, then put the selected one in the foreground. To do this I change each camera's rect to a new one, calculated based on the number of cameras I have (this is done with the intention to eventually put in a variable number of cameras, to do which I will also need to modify the AssignNum() method). Here the editor crashes, and I have no clue about why this happens.
If anyone's got a better way to achieve what I need I'm open to suggestions, but I would also really want to understand why my editor crashes.
Answer by Bunny83 · Apr 17, 2018 at 03:46 AM
Ohh man this is a huge collection of issues. First the reason for your hang (which btw is not a crash): In your while loop you only increase "i" inside your if statement body. That means the moment the loop hits the selected camera you will no longer enter the if statement and therefore i is no longer incremented and you're stuck in the while loop. This is a typical case for a "for" loop. The for loop has the advantage that the increment is unconditionally in the header.
Your two methods (BackGroundRect and ForeGroundRect) are pointless the way you designed the signatures. Rect is a struct. Structs are value types and therefore the method gets a copy of the struct. Changing the local copy inside the method won't change the Rect that was passed to the method. You should let the method return the Rect and assign it to the desired camera.
Inside your two methods you perform an integer division which would be rounded down to 0. If the operands of a division are both integers the result will be an integer as well. However "1 / 4" will be "0". That means all your calculated offsets and sizes are wrong. At least one operand has to be a float value in order to do a floating point division. Just use "1f" instead of "1".
As others have said there's no reason why your SwitchCamera method should be a coroutine. Any yield at the end of a coroutine is pointless since nothing else happens after the yield.
You really shouldn't use a 1 based system for storing the active camera. Just use a zero based value it makes all calculations and comparisons easier. So it reduces the chance for errors.
Answer by melsy · Apr 17, 2018 at 02:49 AM
First thing I notice is that the coroutine is allowed to stack multiple instances on itself. If the number key is held for more than one frame then the bool return true for that amount of frames. This will stack the coroutine and cause it to run several times .
Instead of a bool for key pressed make an int to get the index and send that to a check inside the coroutine for the array. then at the end of the coroutine change the bool back to false.
// All pseudo code so dont copy paste
bool keydown;
if(Input.GetKeyDown(Keycode.Alpha1))
{
keydown = true;
// run the coroutine
}
int AssignNum()
{
// All the GetKeyDown(KeyCode.// Reference here
// keyDown = true;
// return Keycode int
// Rest of the GetKeyDowns with the keycodes
// then set keyDown to true;
// then return the int number
}
IEnumerator CoroutineCo()
{
//Stuff
array[AssignNum()].rect;
// WaitForSeconds
// keyDown = false;
}
I would do this with renderTextures and a plane image. That will give you a camera that can change its own perimeters based on the render texture and you can do what you are wanting to pretty easy. But it is very resource heavy.
Answer by MarioSantoso · Apr 17, 2018 at 03:02 AM
I don't know why you need coroutine here. Anyway, apply this script to any game object and you are good to go.
I made this script based on your needs for 5 cameras. This will fail if there is less or more camera. So make adjustment if you need to.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MultipleCameras : MonoBehaviour
{
public Camera[] cameras;
private Rect _foreRect;
private Rect[] _backRect;
private int _activeID;
void Start()
{
InitializeCameras();
}
private void InitializeCameras()
{
cameras = Camera.allCameras;
// Define all cameras' rect
_foreRect = new Rect(0, 0.21f, 1, 1);
_backRect = new Rect[]
{
new Rect( 0, 0, 0.245f, 0.2f),
new Rect( 0.25f, 0, 0.245f, 0.2f),
new Rect( 0.50f, 0, 0.245f, 0.2f),
new Rect( 0.75f, 0, 0.25f, 0.2f)
};
// Reference to active camera ID
_activeID = 0;
// Assign rects to cameras
cameras[0].rect = _foreRect;
for (int i = 1; i < cameras.Length; i++)
cameras[i].rect = _backRect[i - 1];
}
void Update()
{
// Detect user inputs
if (Input.GetKeyUp(KeyCode.Alpha1))
FocusOnCamera(0);
else if (Input.GetKeyUp(KeyCode.Alpha2))
FocusOnCamera(1);
else if (Input.GetKeyUp(KeyCode.Alpha3))
FocusOnCamera(2);
else if (Input.GetKeyUp(KeyCode.Alpha4))
FocusOnCamera(3);
else if (Input.GetKeyUp(KeyCode.Alpha5))
FocusOnCamera(4);
}
private void FocusOnCamera(int id)
{
// Swith the camera
if (id == _activeID)
return;
cameras[_activeID].rect = cameras[id].rect;
cameras[id].rect = _foreRect;
_activeID = id;
}
}
Answer by OliverJackman · Sep 20, 2019 at 06:04 PM
it's super easy! just disable camera component on it ...