Rotating around 3D cube Camera Controller

I am Unity game developer with more than 2 years experience. I am working in a personal project, a mobile game for Android in Unity.

I am trying to do a camera controller for a 3d Cube Game in Unity. I have a controller working, I can position the camera in the six faces of the cube. But The problem went when I move the camera several times. (See the image for more explanation)

For example, I always start the game on Top face, but after several moves the controller starts to move West, East when it should move North, South. I understand what the problem is, that the camera depends on the previous movements since it is rotating spherically. but I don’t know what data structures to use or how to fix this. description of the problem

I know is a complex problem that requires 3D Math, but maybe some of you have faced this before or have a better aproach, Thank you.

Here is some relevant code I wrote(with the help of chatGPT) :

``````public class TiltControl : MonoBehaviour
{

// Define the faces of the cube
public enum CubeFace
{
Front,
Back,
Left,
Right,
Top,
Bottom
}

// Define the cardinal directions
public enum CardinalDirection
{
North,
East,
South,
West
}

// Dictionary to store adjacent faces for each face and direction

private CubeFace currentCameraFace = CubeFace.Top;

private void Start()
{
// Initialize the adjacent faces dictionary based on the current face
adjacentFaces[CubeFace.Front] = new List<CubeFace> { CubeFace.Bottom, CubeFace.Left, CubeFace.Top, CubeFace.Right };
adjacentFaces[CubeFace.Back] = new List<CubeFace> { CubeFace.Top, CubeFace.Right,CubeFace.Bottom, CubeFace.Left};
adjacentFaces[CubeFace.Left] = new List<CubeFace> { CubeFace.Front, CubeFace.Top,  CubeFace.Back, CubeFace.Bottom };
adjacentFaces[CubeFace.Right] = new List<CubeFace> { CubeFace.Front, CubeFace.Bottom,CubeFace.Back, CubeFace.Top };
adjacentFaces[CubeFace.Top] = new List<CubeFace> { CubeFace.Front,  CubeFace.Right,CubeFace.Back, CubeFace.Left};
adjacentFaces[CubeFace.Bottom] = new List<CubeFace> { CubeFace.Back,  CubeFace.Left, CubeFace.Front, CubeFace.Right };
}

private void LateUpdate()
{
if (cameraJoystick.Horizontal < -0.25)
{
ChangeFaceToDir(CardinalDirection.West);
}
else if (cameraJoystick.Horizontal > 0.25)
{
ChangeFaceToDir(CardinalDirection.East);
}
else if (cameraJoystick.Vertical > 0.25)
{
ChangeFaceToDir(CardinalDirection.North);
}
else if (cameraJoystick.Vertical < -0.25)
{
ChangeFaceToDir(CardinalDirection.South);
}

}

private void ChangeFace(CubeFace face)
{
currentCameraFace = face;
cameraText.SetText(currentCameraFace.ToString());
newPosition = cameraPoints[(int)face];
}

private void ChangeFaceToDir(CardinalDirection dir)
{

Debug.Log("toDir" + dir);
ChangeFace(currentCameraFace);

}

private CubeFace GetAdjacentFace(CubeFace face, CardinalDirection direction)
{
{

Vector2Int dirCard = GetCardinalDirection(direction);
Vector3 rotated = (-1 * myCamera.right * dirCard.x) + (myCamera.forward * dirCard.y);

Transform newTransform = Instantiate(cameraPoints[0]);
newTransform.position = myCamera.position;
newTransform.Rotate(myCamera.forward,Vector3.Angle(myCamera.right,rotated));

Debug.Log("newTransform" + newTransform.right.normalized);

int dir;

dir = (int)GetCardinalFromDirection(new Vector2Int((int)newTransform.right.normalized.x,(int)newTransform.right.normalized.z));

Destroy(newTransform.gameObject);

Debug.Log("dir" + dir);

}

Debug.LogWarning("Invalid face: " + face);
return face;
}

private Vector2Int GetCardinalDirection(CardinalDirection direction)
{
switch (direction)
{
case CardinalDirection.North: return new Vector2Int(1, 0);
case CardinalDirection.East: return new Vector2Int(0, 1);
case CardinalDirection.South: return new Vector2Int(-1, 0);
case CardinalDirection.West: return new Vector2Int(0, -1);
default: return new Vector2Int();
}
}

private CardinalDirection GetCardinalFromDirection(Vector2Int direction)
{
if (direction == new Vector2Int(1, 0))
{
return CardinalDirection.North;
}
else if (direction == new Vector2Int(0, 1))
{
return CardinalDirection.East;

}
else if (direction == new Vector2Int(-1, 0))
{
return CardinalDirection.South;

}
else if (direction == new Vector2Int(0, -1))
{
return CardinalDirection.West;

}
return CardinalDirection.North;
}
``````

Code explanantion:

Firstly, I initialize the 4 possible faces that one face can have (maybe this is the problem, as is fixed positions and the camera rotates spherically changing the orientation) I check the input from touch Joysctick, that just have 4 directions valid N,S,E,W Then in GetAdacentFace I should get the correct face depending on the Joystick direction, but I get wrong, I do some calculations with the orientation of the main camera and the Joystick direction (and returns the wrong face) With this returned face I move the camera to the face

I am most likely oversimplifying things, but is it important to know the exact faces of the cube, or is it just a point of view. In other words instead of all the calculating, could it be as easy as rotating 90 or -90 on the desired axis?

It looks like a 3D maze, so I assume there is a ball/player somewhere which will prevent you from rotating the cube instead of the camere

No I can’t rotate the cube, I have to rotate the camera, because it’s a physics-based game and when you move the cube the ball gets pushed cause of inertial force.

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

public class TiltControl : MonoBehaviour
{
Vector3 goal;
Vector3 up;

const float distance=3;

void Start()
{
up=transform.up;
goal=Vector3.forward*distance;
}

void Update()
{
if (Vector3.Dot(transform.forward,-goal.normalized)>0.999f) // are we nearly there yet?
{
Vector3 d=new Vector3(Input.GetAxisRaw("Horizontal"),Input.GetAxisRaw("Vertical"),0);
if (d.x!=0)
{
up=Vector3Int.RoundToInt(transform.up);
goal=Vector3Int.RoundToInt(transform.right*d.x*distance);
}
else if (d.y!=0)
{
up=Vector3Int.RoundToInt(transform.forward*d.y);
goal=Vector3Int.RoundToInt(transform.up*d.y*distance);
}
}
transform.position=Vector3.Slerp(transform.position,goal,8f*Time.deltaTime);
transform.LookAt(Vector3.zero,up);
}
}
``````

Thank you I already found a solution:

Adding a camera rig inside the cube and rotating this rig:

``````private void ChangeFaceToDir(CardinalDirection dir) {

Debug.Log("toDir" + dir);
//cameraText.SetText(currentCameraFace.ToString());

switch (dir)
{
case CardinalDirection.North:
rigPoint.Rotate(Vector3.right, 90f,Space.Self);
break;
case CardinalDirection.South:
rigPoint.Rotate(Vector3.right, -90f, Space.Self);
break;
case CardinalDirection.East:
rigPoint.Rotate(Vector3.forward, -90f, Space.Self);

break;
case CardinalDirection.West:
rigPoint.Rotate(Vector3.forward, 90f, Space.Self);

break;
default:
break;
}
newPosition = focusPoint;

}
``````

Yep that is what I assumed.

Great to see you found a solution

I was curious about how I’d go about this myself, so I ended up writing this on a whim, namely to show how this can be done with quaternions:

``````using UnityEngine;

public class CameraOrbitRotator : MonoBehaviour
{
[SerializeField]
private float _rotationAmount = 90f;

[SerializeField]
private float _rotationSpeed = 10f;

private Quaternion _targetRotation;

private void Awake()
{
_targetRotation = transform.rotation;
}

private void Update()
{
UpdateTargetRotation();
RotateAroundToDirection();
}

private void UpdateTargetRotation()
{
if (!ReachedTarget())
{
return;
}

int hoz = Input.GetKeyDown(KeyCode.D) ? -1 : 0;
hoz += Input.GetKeyDown(KeyCode.A) ? 1 : 0;

int vert = Input.GetKeyDown(KeyCode.W) ? 1 : 0;
vert += Input.GetKeyDown(KeyCode.S) ? -1 : 0;

Vector3 axis = Vector3.zero;
int dir = 0;

if (hoz != 0)
{
axis = transform.up;
dir = hoz;
}
else if (vert != 0)
{
axis = transform.right;
dir = vert;
}

Quaternion rotation = Quaternion.AngleAxis(_rotationAmount * dir, axis);
_targetRotation = rotation * _targetRotation;
}

private void RotateAroundToDirection()
{
if (ReachedTarget())
{
return;
}

Quaternion cRotation = transform.rotation;
Quaternion rTowards = Quaternion.RotateTowards(cRotation, _targetRotation, _rotationSpeed);
transform.rotation = rTowards;
}

private bool ReachedTarget()
{
Quaternion cRotation = transform.rotation;
float rDot = Quaternion.Dot(cRotation, _targetRotation);
return Mathf.Approximately(rDot, 1);
}
}
``````

This would go onto an empty the camera is a child of. The main principle is that we just have a target rotation, and we figure out an axis to rotate around on, generate said rotation, and apply that to the target rotation.