Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice
  2. Ever participated in one our Game Jams? Want pointers on your project? Our Evangelists will be available on Friday to give feedback. Come share your games with us!
    Dismiss Notice

Any way to clamp rotation?

Discussion in 'Cinemachine' started by homer_3, Jun 30, 2020 at 2:36 AM.

  1. homer_3

    homer_3

    Joined:
    Jun 19, 2011
    Posts:
    81
    If I give a virtual camera a target to look at, as I move the target around, the camera automatically rotates to track it right? Is there a way to clamp the max X/Y angles it will rotate about while tracking the target?
     
  2. gaborkb

    gaborkb

    Unity Technologies

    Joined:
    Nov 7, 2019
    Posts:
    144
    I am not sure if I fully understand the question. Could you clarify it by giving an example use case?
     
  3. homer_3

    homer_3

    Joined:
    Jun 19, 2011
    Posts:
    81
    Imagine you are standing on a beach staring out into the ocean and your gaze is perpendicular to the shore line. A boat is sailing across the horizon from left to right. Based on average peripheral vision range, when the boat just enters your peripheral visual on your left, you'd have to rotate your head ~85 degrees in order to put the boat in the dead center of your vision. But what if you didn't want to rotate your head that far? Maybe its uncomfortable to turn your neck that far. What if you only wanted to turn your head a max of 45 degrees? Then once the boat is less than 45 degrees to your left, you continue tracking it from 44 to 0, then 0-45, in which case you stop tracking it again.
     
    gaborkb likes this.
  4. gaborkb

    gaborkb

    Unity Technologies

    Joined:
    Nov 7, 2019
    Posts:
    144
    I don't think, CM has a built-in feature for this, but you can achieve this with a script.

    For example, you could have an empty game object that can only move between [A, B]. If your character looks at A, its head turns -45 degrees, and if your character looks at B, its head turns 45 degrees.
    If you want this to work regardless of your character's orientation, then you have to specify A and B in your character's local coordinate system. You can use the character's forward vector to define A and B this way. For example, if v = character's forward vector, left = character's left vector, right = character's right vector. Then A = v * multiplier1 + left * multiplier2, and B = v * multiplier1 + right * multiplier2.

    Also, have a look at DeadZone and SoftZone on Composer.
     
  5. homer_3

    homer_3

    Joined:
    Jun 19, 2011
    Posts:
    81
    Yea, I implemented a script that kind of does something like that last night. It sets up a fixed, virtual camera that looks at A or B (it really just saves the last orientation of the tracking camera that was considered "good") that is given the highest priority when the tracking camera looks out of bounds. Then when the tracking camera looks back in bounds, its given the highest priority again. Unfortunately, determining if you're looking out of bounds isn't that easy and its only working in 1 orientation, despite using local coordinates like you have there. And even then, it could work better.

    A built it feature for a camera that can only turn so far would be really nice to have though. I don't think it's all that uncommon to see in games. The PS4 Spider-man game, for example, has that minigame where you look through a telescope to find the hidden black cat toy and the telescope only turns so far.

    Even the new Unity animation rigging feature lets you put constraints on bones when it's doing IK so the bones can only rotate so far. I actually wonder if maybe I can combine that with the cameras to get what I need more easily. Have the bone track the object, make the camera's forward the bone's. Hmmm...
     
  6. gaborkb

    gaborkb

    Unity Technologies

    Joined:
    Nov 7, 2019
    Posts:
    144
    You can achieve this kind of behaviour by the solution discussed above. I would set it up so that when you look inside the box, you switch to the box camera, which tracks a target that is confined inside the box.


    That could work.
     
  7. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    3,811
    Another - perhaps simpler - solution would be to implement a custom CinemachineExtension that clamps the rotation.
     
  8. homer_3

    homer_3

    Joined:
    Jun 19, 2011
    Posts:
    81
    Any examples on how to make an extension?
     
  9. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    3,811
    Here is one that locks the vcam's Y to some value. You can start with that, and modify it to clamp state.RawOrientation instead.

    Code (CSharp):
    1. using UnityEngine;
    2. using Cinemachine;
    3.  
    4. /// <summary>
    5. /// An add-on module for Cinemachine Virtual Camera that locks the camera's Y co-ordinate
    6. /// </summary>
    7. [SaveDuringPlay] [AddComponentMenu("")] // Hide in menu
    8. public class LockCameraY : CinemachineExtension
    9. {
    10.     [Tooltip("Lock the camera's Y position to this value")]
    11.     public float m_YPosition = 10;
    12.  
    13.     protected override void PostPipelineStageCallback(
    14.         CinemachineVirtualCameraBase vcam,
    15.         CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
    16.     {
    17.         if (stage == CinemachineCore.Stage.Body)
    18.         {
    19.             var pos = state.RawPosition;
    20.             pos.y = m_YPosition;
    21.             state.RawPosition = pos;
    22.         }
    23.     }
    24. }
    25.  
    EDIT: You'll want to change the conditional to
    if (stage == CinemachineCore.Stage.Aim)
    , if you're modifying the rotation.
     
    homer_3 and gaborkb like this.
  10. homer_3

    homer_3

    Joined:
    Jun 19, 2011
    Posts:
    81
    Awesome, thanks! That worked perfectly. Probably could be better, but this clamps it very nicely now

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Cinemachine;
    5.  
    6. [SaveDuringPlay]
    7. [AddComponentMenu("")]
    8. public class CinCamClampExt : CinemachineExtension
    9. {
    10.     [Tooltip("Camera will clamp relative to this transform")]
    11.     public Transform refOrientation;
    12.     [Tooltip("Max X/Y angle the camera can turn")]
    13.     public Vector2 angleBounds;
    14.  
    15.     protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
    16.     {
    17.         if (stage == CinemachineCore.Stage.Aim)
    18.         {
    19.             Quaternion yCamOnly = Quaternion.Euler(0, state.RawOrientation.eulerAngles.y, 0);
    20.             Quaternion yRefOnly = Quaternion.Euler(0, refOrientation.rotation.eulerAngles.y, 0);
    21.             float yAngle = Quaternion.Angle(yCamOnly, yRefOnly);
    22.             if(yAngle > angleBounds.y)
    23.             {
    24.                 yCamOnly = Quaternion.Euler(0, state.RawOrientation.eulerAngles.y+1, 0);
    25.                 if (yAngle < Quaternion.Angle(yCamOnly, yRefOnly))
    26.                 {
    27.                     state.RawOrientation = Quaternion.Euler(state.RawOrientation.eulerAngles.x, state.RawOrientation.eulerAngles.y - (yAngle - angleBounds.y), state.RawOrientation.eulerAngles.z);
    28.                 }
    29.                 else
    30.                 {
    31.                     state.RawOrientation = Quaternion.Euler(state.RawOrientation.eulerAngles.x, state.RawOrientation.eulerAngles.y + (yAngle - angleBounds.y), state.RawOrientation.eulerAngles.z);
    32.                 }
    33.             }
    34.  
    35.             Quaternion xCamOnly = Quaternion.Euler(state.RawOrientation.eulerAngles.x, 0, 0);
    36.             Quaternion xRefOnly = Quaternion.Euler(refOrientation.rotation.eulerAngles.x, 0, 0);
    37.             float xAngle = Quaternion.Angle(xCamOnly, xRefOnly);
    38.             if (xAngle > angleBounds.x)
    39.             {
    40.                 xCamOnly = Quaternion.Euler(state.RawOrientation.eulerAngles.x+1, 0, 0);
    41.                 if(xAngle < Quaternion.Angle(xCamOnly, xRefOnly))
    42.                 {
    43.                     state.RawOrientation = Quaternion.Euler(state.RawOrientation.eulerAngles.x - (xAngle - angleBounds.x), state.RawOrientation.eulerAngles.y, state.RawOrientation.eulerAngles.z);
    44.                 }else
    45.                 {
    46.                     state.RawOrientation = Quaternion.Euler(state.RawOrientation.eulerAngles.x + (xAngle - angleBounds.x), state.RawOrientation.eulerAngles.y, state.RawOrientation.eulerAngles.z);
    47.                 }
    48.             }
    49.         }
    50.     }
    51. }
     
    gaborkb and Gregoryl like this.
unityunity