Is there a way to change a gameobject from perspective to orthographic seamlessly in Unity?

65 Views Asked by At

I need help with transitioning between 2 view angles. I've searched but couldn't find a solution for this.

I'm working on a board game with checkers-like pieces. I have the main camera mode set to perspective. When a piece needs to leave the game I have it so that it is dragged over to the bottom of the screen where I have a stack of all the pieces taken out at which point it should fly over and rotate to the correct place. The problem is that the stack is on another layer only viewed by a second camera in orthographic mode (the stack doesn't look right in perspective). I can switch layers on the piece but then it jumps to a different position on the screen (or off the screen) and its appeared rotation changes drastically.

I'd like to be able to adjust the piece's position and rotation so that it appears to not move or rotate as I change the layer.

Once it changes I can animate it to the correct position and rotation seamlessly. But that change over to orthographic is very jerky. Only solution I have is to do it off screen but it looks hackey. Is there a way to change perspectives but have the piece appear unchanged? Thanks

edit my setup is like so:

public class Stack : MonoBehaviour
{
    [SerializeField] private Camera perspectiveCamera, orthogonalCamera;
    [SerializeField] private Vector3 startPosition;
    [SerializeField] private Vector3 startRotation;
    [SerializeField] private float duration;    
    
    public void AddToStack(Transform piece)
    {
        piece.transform.SetParent(transform);
        //here piece is where it is dropped. It is seen by the perspectiveCamera
        
        
        //method sets new position and rotation of piece and sets piece.layer
        ChangeOver(piece);
        
        //here piece should be where it was dropped (no movement on screen) but it is seen by the orthogonalCamera
        
        StartCoroutine(LerpBetweenPositionsAndRotations(piece));
        
        
    }
    
    private void ChangeOver(Transform piece)
    {
        //missing here: move piece in 3D without moving it on screen (have the new coordinates match from orthogonal perspective)
        piece.position = //?
        piece.rotation = //?
        
        //set start position and rotation for IEnumerator
        startPosition = piece.localPosition;
        startRotation = piece.localRotation.eulerAngles;
        
        //sets the piece to the stack layer only seen from orthogonalCamera
        piece.gameObject.layer = gameObject.layer;
        
        
    }
    
    private IEnumerator LerpBetweenPositionsAndRotations(Transform piece)
    {
    for(float t = 0f; t < duration; t += Time.deltaTime)
    {
        piece.localPosition = Vector3.Lerp(startPosition, new Vector3(transform.childCount - 1, 0f, 0f), t / duration);
        piece.localRotation = Vector3.Lerp(startRotation, Quaternion.Euler(0f, 0f, 0f), t / duration);
        
        yield return null;
    }

    piece.localPosition = new Vector3(transform.childCount - 1, 0f, 0f);
    piece.localRotation = Quaternion.Euler(0f, 0f, 0f);
}

this script is attached to my stack gameObject which is on layer only orthogonalCamera can see. I was looking for a way to fill in the first couple of code lines in the ChangeOver() method. (doesn't have to be done in 2 lines of course)

1

There are 1 best solutions below

4
derHugo On

If I understand correctly you have two cameras always rendering, the one with the 3D perspective pieces and one for the already beaten pieces in orthographic.

Now you want to transition a piece from the perspective board into the stack but also transition from perspective to orthographic while/after it moves.

This sounds rather tricky to achieve tbh.

I can imagine a bit more complex solution using a third camera + additional dedicated transition layer

  • you move the perspective piece to the target position
  • then there you switch it to a dedicated transition layer
  • enable a third camera wich only renders your transition layer and that transitions from perspective to orthographic (can achieve this via Camera.projectionMatrix)
  • then you disable the third camera again

You probably could even combine the movement and projection fading in the same step to make the effect even cooler ^^

So something like maybe

private IEnumerator RemovePiece(
    // the piece being removed from the board
    Transform pieceToRemove,
    // Your main camera 
    Camera perspectiveCamera,
    // your stack camera
    Camera orthographicCamera,
    // final position of the removed piece 
    Vector3 pieceTargetPositionInStack,
    // final rotation of the removed piece
    Quaternion pieceTargetRotstionInStack,
    // the camera used for the transition
    Camera transitionCamera,
    // how long the transition takes
    float transitionDuration,
    // the layer only rendered by the transitionCamera
    int transitionLayer,
    // the layer rendered by the orthographicCamera
    int stackLayer)
{
    // Gather initial values
    var perspectiveMatrix = perspectiveCamera.projectionMatrix;
    perspectiveCamera.transform.GetPositionAndRotation(out var perspectiveCameraPosition, out var perspectiveCameraRotation);
    var perspectiveCameraPose = new Pose(perspectiveCameraPosition, perspectiveCameraRotation);
    var orthographicMatrix = orthographicCamera.projectionMatrix;
    orthographicCamera.transform.GetPositionAndRotation(out var orthographicCameraPosition, out var orthographicCameraRotation);
    var orthographicCameraPose = new Pose(orthographicCameraPosition, orthographicCameraRotation);
    var initialPosition = pieceToRemove.position;
    var initialRotation = pieceToRemove.rotation;

    // Setup initial transition Camera
    transitionCamera.transform.SetPositionAndRotation(perspectiveCameraPosition, perspectiveCameraRotation);
    transitionCamera.projectionMatrix = perspectiveMatrix;
    pieceToRemove.gameObject.layer = transitionLayer;
    transitionCamera.gameObject.SetActive(true);

    // Transition
    for (var timePassed = 0f; timePassed < transitionDuration; timePassed += Time.deltaTime)
    {
        var factor = timePassed / transitionDuration;

        // optional add easing
        factor = Mathf.SmoothStep(0, 1, factor);

        var position = Vector3.Lerp(initialPosition, pieceTargetPositionInStack, factor);
        var rotation = Quaternion.Slerp(initialRotation, pieceTargetRotstionInStack, factor);

        pieceToRemove.SetPositionAndRotation(position, rotation);

        var pose = Tools.LerpPose(perspectiveCameraPose, orthographicCameraPose, factor);
        transitionCamera.transform.SetPositionAndRotation(pose.position, pose.rotation);

        // if using a linear interpolation you will note that towards the orthographic 
        // the transition becomes slower - so use an adjusted factor
        // play around and adjust the 8 to a value that fits you better
        var matrixFactor = 1 - Mathf.Pow(1 - factor, 8);
        transitionCamera.projectionMatrix = Tools.LerpMatrix(perspectiveMatrix, orthographicMatrix, matrixFactor);

        yield return null;
    }

    // Clean values and disable camera
    pieceToRemove.position = pieceTargetPositionInStack;
    pieceToRemove.rotation = pieceTargetRotstionInStack;
    pieceToRemove.gameObject.layer = stackLayer;
    transitionCamera.gameObject.SetActive(false);
}

public static class Tools
{
    public static Matrix4x4 LerpMatrix(Matrix4x4 a, Matrix4x4 b, float factor)
    {
        Matrix4x4 result = default;

        for (var i = 0; i < 16; i++)
        {
            result[i] = Mathf.Lerp(a[i], b[i], factor);
        }

        return result;
    }

    public static Pose LerpPose(Pose a, Pose b, float factor)
    {
        return new Pose(Vector3.Lerp(a.position, b.position, factor), Quaternion.Slerp(a.rotation, b.rotation, factor));
    }
}

This is untested (typed on a phone) but I hope that should at least get you started ;)

If you rather want to separate the movement of the piece from the transition you can do so of course and have separate loops.