Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Cinemachine Impulse Source -> Generate Impulse With Rotation?

Discussion in 'Cinemachine' started by Hallur90, Oct 24, 2022.

  1. Hallur90

    Hallur90

    Joined:
    Dec 4, 2017
    Posts:
    51
    Hi,

    I was hoping to use this method to generate some nice weapon recoil for an FPS game.

    upload_2022-10-24_1-57-44.png

    Weirdly enough it seems to only allow a position based impulse.

    Is there a way to do this but use rotation instead?

    Regards.
     
  2. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,233
    The impulse signal is just the stimulus. You put the camera reaction in the ImpulseListener attached to the vcam. That can include both positional and rotational reaction.
     
    Hallur90 and tsukimi like this.
  3. Hallur90

    Hallur90

    Joined:
    Dec 4, 2017
    Posts:
    51
    Thank you for the reply.

    I'm a bit confused, could you elaborate further?

    I have a script that calls:
    GetComponent<CinemachineImpulseSource>().GenerateImpulse(intensity);

    How do I specify whether its using rotation or position or both?
     
    Last edited: Oct 25, 2022
  4. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,233
    You don't. The impulse is a stimulus. You define the nature of the reaction to the stimulus n the ImpulseListener. That's where the rotation comes in. You want the camera to react to the stimulus by rotationally shaking.

    Add the CinemachineImpulseListener extension to your vcam and set the reaction to shake the way you want. You can use one of the predefined settings (you can tune it by adjusting the amplitude and frequency), or you can create your own

    upload_2022-10-25_9-12-40.png

    To create your own, click on the little gear icon:

    upload_2022-10-25_9-13-6.png
     
    Hallur90 likes this.
  5. Kaideu

    Kaideu

    Joined:
    Nov 6, 2018
    Posts:
    5
    Apologies, but this doesn't seem to grasp the actual question in my perspective, which is the same question I have.
    As I understand it, the noise profiles only determine the additional lingering shaking. But I believe the main question is how to get the Velocity to affect the camera rotation instead of the position.

    As it stands, the velocity seems to only manipulate the camera position, which doesn't make a good recoil effect without rotation.
    In my case, I don't want a noise profile for lingering shakes, I just want a single curve affecting camera rotation, not position.
     
  6. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,233
    In the noise profile, you can define independent waveforms for each of X, Y, Z, pitch, roll, and yaw. Most of the predefined signals define only rotational waveforms. 6D shake defines positional waves also. You can clone any of them and change them to behave the way you want, or you can roll your own from scratch. Click on Edit in the menu shown above to do that.
     
  7. Kaideu

    Kaideu

    Joined:
    Nov 6, 2018
    Posts:
    5
    It appears I may doing something wrong then. I've already attempted to clone like the example above, and even with a completely empty noise profile, it's still moving the position based on the velocity.
    My expectation, based on your information, is that if the noise profile is empty, there should be no movement.
    Am I thinking about this the wrong way?
     
  8. Kaideu

    Kaideu

    Joined:
    Nov 6, 2018
    Posts:
    5
    In order to show what I'm referencing, my goal is to get this velocity of 10 on the X axis to rotate the X axis by 10 degrees instead of moving it by 10 units on the X axis as the example I'm posting shows
    ezgif.com-video-to-gif.gif Listener.PNG Noise Profile.PNG Source.PNG
     
  9. jibeta281

    jibeta281

    Joined:
    Apr 18, 2023
    Posts:
    2
    The impulse signal is just the stimulus. You put the camera reaction in the ImpulseListener attached to the vcam. That can include both positional and rotational reaction.
     
  10. Kaideu

    Kaideu

    Joined:
    Nov 6, 2018
    Posts:
    5
    Right, that's what was said previously, but I can't figure out how to get JUST rotation without position. The noise profile doesn't seem to affect the initial velocity whatsoever. Only the additional shaking afterwards.
     
  11. KYL3R

    KYL3R

    Joined:
    Nov 16, 2012
    Posts:
    128
    I think your Noise should look like this when you want rotational rumble only.


    However, I kind of have the same problem. If you set a Velocity it moves AND rotates, but velocity "(0,0,0)" will not rotate at all.

    To demonstrate, I set the bump duration to 1s, and the reaction-duration to 5s. After the 1s bump-movement, the camera will rumble via rotation, as specified in the Noise Settings. But setting the "default velocity" to 0, nothing happens. Therefore, I cannot reduce the movement to 0 because I will also lose the rotational rumble...


    (alternative link: https://i.imgur.com/x6G4UHT.mp4)

    To achive this, I could create an AnimationCurve just like the Impulse curve here, and set "...AmplitudeGain = curve.Evaulate(t)..."


    But, is a "rotation only impulse" not possible by design, or are we using it wrong? It feels like the effect is multiplied by the magnitude of "default velocity" while the impulse curve should act as a multiplier for the amplitudes imo.
     
    Last edited: Apr 30, 2023
  12. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,233
    Thanks for the clarification, I understand now what the issue is.

    CinemachineImpulseListener combines the stimulus signal and the response effect, and applies both to the transform. The stimulus is always applied to the position, the response depends on your noise settings.

    In CinemachinImpulseListener.PostPipelineStageCallback you can see the code that does this. An easy fix for you would be to make a custom version of CinemachinImpulseListener by copying it into your project, renaming it, and modifying the code in PostPipelineStageCallback. The new class will appear in the extensions dropdown of the vcam. Use it instead of CinemachineImpulseListener.
     
  13. linh60bpm

    linh60bpm

    Joined:
    Oct 13, 2018
    Posts:
    8
    I took the post above and write this code, it work fine in my case.

    Code (CSharp):
    1. using System;
    2. using Cinemachine.Utility;
    3. using UnityEngine;
    4. namespace Cinemachine
    5. {
    6.     /// <summary>
    7.     /// An extension for Cinemachine Virtual Camera which post-processes
    8.     /// the final position of the virtual camera.  It listens for CinemachineImpulse
    9.     /// signals on the specified channels, and moves the camera in response to them.
    10.     /// </summary>
    11.     [SaveDuringPlay]
    12.     [AddComponentMenu("")] // Hide in menu
    13.     [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
    14.     [ExecuteAlways]
    15.     // [HelpURL(Documentation.BaseURL + "manual/CinemachineImpulseListener.html")]
    16.     public class CinemachineImpulseRotationListener : CinemachineExtension
    17.     {
    18.         /// <summary>
    19.         /// When to apply the impulse reaction.  Default is Noise.
    20.         /// Modify this if necessary to influence the ordering of extension effects
    21.         /// </summary>
    22.         [Tooltip("When to apply the impulse reaction.  Default is after the Noise stage.  "
    23.             + "Modify this if necessary to influence the ordering of extension effects")]
    24.         public CinemachineCore.Stage m_ApplyAfter = CinemachineCore.Stage.Aim; // legacy compatibility setting
    25.  
    26.         /// <summary>
    27.         /// Impulse events on channels not included in the mask will be ignored.
    28.         /// </summary>
    29.         [Tooltip("Impulse events on channels not included in the mask will be ignored.")]
    30.         [CinemachineImpulseChannelProperty]
    31.         public int m_ChannelMask;
    32.  
    33.         /// <summary>
    34.         /// Gain to apply to the Impulse signal.
    35.         /// </summary>
    36.         [Tooltip("Gain to apply to the Impulse signal.  1 is normal strength.  "
    37.             + "Setting this to 0 completely mutes the signal.")]
    38.         public float m_Gain;
    39.  
    40.         /// <summary>
    41.         /// Enable this to perform distance calculation in 2D (ignore Z).
    42.         /// </summary>
    43.         [Tooltip("Enable this to perform distance calculation in 2D (ignore Z)")]
    44.         public bool m_Use2DDistance;
    45.  
    46.         /// <summary>
    47.         /// Enable this to process all impulse signals in camera space.
    48.         /// </summary>
    49.         [Tooltip("Enable this to process all impulse signals in camera space")]
    50.         public bool m_UseCameraSpace;
    51.  
    52.         /// <summary>
    53.         /// This controls the secondary reaction of the listener to the incoming impulse.
    54.         /// The impulse might be for example a sharp shock, and the secondary reaction could
    55.         /// be a vibration whose amplitude and duration is controlled by the size of the
    56.         /// original impulse.  This allows different listeners to respond in different ways
    57.         /// to the same impulse signal.
    58.         /// </summary>
    59.         [Serializable]
    60.         public struct ImpulseReaction
    61.         {
    62.             /// <summary>
    63.             /// Secondary shake that will be triggered by the primary impulse
    64.             /// </summary>
    65.             [Tooltip("Secondary shake that will be triggered by the primary impulse.")]
    66.             [NoiseSettingsProperty]
    67.             public NoiseSettings m_SecondaryNoise;
    68.  
    69.             /// <summary>
    70.             /// Gain to apply to the amplitudes defined in the signal source asset.
    71.             /// </summary>
    72.             [Tooltip("Gain to apply to the amplitudes defined in the signal source.  "
    73.                 + "1 is normal.  Setting this to 0 completely mutes the signal.")]
    74.             public float m_AmplitudeGain;
    75.      
    76.             /// <summary>
    77.             /// Scale factor to apply to the time axis.
    78.             /// </summary>
    79.             [Tooltip("Scale factor to apply to the time axis.  1 is normal.  "
    80.                 + "Larger magnitudes will make the signal progress more rapidly.")]
    81.             public float m_FrequencyGain;
    82.  
    83.             /// <summary>
    84.             /// How long the secondary reaction lasts.
    85.             /// </summary>
    86.             [Tooltip("How long the secondary reaction lasts.")]
    87.             public float m_Duration;
    88.  
    89.             float m_CurrentAmount;
    90.             float m_CurrentTime;
    91.             float m_CurrentDamping;
    92.  
    93.             bool m_Initialized;
    94.  
    95.             [SerializeField, HideInInspector]
    96.             Vector3 m_NoiseOffsets;
    97.  
    98.             /// <summary>Generate a new random seed</summary>
    99.             public void ReSeed()
    100.             {
    101.                 m_NoiseOffsets = new Vector3(
    102.                         UnityEngine.Random.Range(-1000f, 1000f),
    103.                         UnityEngine.Random.Range(-1000f, 1000f),
    104.                         UnityEngine.Random.Range(-1000f, 1000f));
    105.             }
    106.  
    107.             /// <summary>
    108.             /// Get the rection effect for a given impulse at a given time.
    109.             /// </summary>
    110.             /// <param name="deltaTime">Current time interval</param>
    111.             /// <param name="impulsePos">The input impulse signal at this time</param>
    112.             /// <param name="pos">output reaction position delta</param>
    113.             /// <param name="rot">output reaction rotation delta</param>
    114.             /// <returns>True if thewre is a reaction effect, false otherwise</returns>
    115.             public bool GetReaction(
    116.                 float deltaTime, Vector3 impulsePos,
    117.                 out Vector3 pos, out Quaternion rot)
    118.             {
    119.                 if (!m_Initialized)
    120.                 {
    121.                     m_Initialized = true;
    122.                     m_CurrentAmount = 0;
    123.                     m_CurrentDamping = 0;
    124.                     m_CurrentTime = CinemachineCore.CurrentTime * m_FrequencyGain;
    125.                     if (m_NoiseOffsets == Vector3.zero)
    126.                         ReSeed();
    127.                 }
    128.  
    129.                 // Is there any reacting to do?
    130.                 pos = Vector3.zero;
    131.                 rot = Quaternion.identity;
    132.                 var sqrMag = impulsePos.sqrMagnitude;
    133.                 if (m_SecondaryNoise == null || (sqrMag < 0.001f && m_CurrentAmount < 0.0001f))
    134.                     return false;
    135.  
    136.                 // Advance the current reaction time
    137.                 // TODO[Linh]: What is this
    138.                 // if (TargetPositionCache.CacheMode == TargetPositionCache.Mode.Playback
    139.                 //         && TargetPositionCache.HasCurrentTime)
    140.                 //     m_CurrentTime = TargetPositionCache.CurrentTime * m_FrequencyGain;
    141.                 // else
    142.                 //     m_CurrentTime += deltaTime * m_FrequencyGain;
    143.  
    144.                 // Adjust the envelope height and duration of the secondary noise,
    145.                 // acording to the strength of the incoming signal
    146.                 m_CurrentAmount = Mathf.Max(m_CurrentAmount, Mathf.Sqrt(sqrMag));
    147.                 m_CurrentDamping = Mathf.Max(m_CurrentDamping, Mathf.Max(1, Mathf.Sqrt(m_CurrentAmount)) * m_Duration);
    148.  
    149.                 var gain = m_CurrentAmount * m_AmplitudeGain;
    150.                 pos = NoiseSettings.GetCombinedFilterResults(
    151.                         m_SecondaryNoise.PositionNoise, m_CurrentTime, m_NoiseOffsets) * gain;
    152.                 rot = Quaternion.Euler(NoiseSettings.GetCombinedFilterResults(
    153.                         m_SecondaryNoise.OrientationNoise, m_CurrentTime, m_NoiseOffsets) * gain);
    154.  
    155.                 m_CurrentAmount -= Damper.Damp(m_CurrentAmount, m_CurrentDamping, deltaTime);
    156.                 m_CurrentDamping -= Damper.Damp(m_CurrentDamping, m_CurrentDamping, deltaTime);
    157.                 return true;
    158.             }
    159.         }
    160.  
    161.         /// <summary>
    162.         /// This controls the secondary reaction of the listener to the incoming impulse.
    163.         /// The impulse might be for example a sharp shock, and the secondary reaction could
    164.         /// be a vibration whose amplitude and duration is controlled by the size of the
    165.         /// original impulse.  This allows different listeners to respond in different ways
    166.         /// to the same impulse signal.
    167.         /// </summary>
    168.         [Tooltip("This controls the secondary reaction of the listener to the incoming impulse.  "
    169.             + "The impulse might be for example a sharp shock, and the secondary reaction could "
    170.             + "be a vibration whose amplitude and duration is controlled by the size of the "
    171.             + "original impulse.  This allows different listeners to respond in different ways "
    172.             + "to the same impulse signal.")]
    173.         public ImpulseReaction m_ReactionSettings;
    174.  
    175.         private void Reset()
    176.         {
    177.             m_ApplyAfter = CinemachineCore.Stage.Noise; // this is the default setting
    178.             m_ChannelMask = 1;
    179.             m_Gain = 1;
    180.             m_Use2DDistance = false;
    181.             m_UseCameraSpace = true;
    182.             m_ReactionSettings = new ImpulseReaction
    183.             {
    184.                 m_AmplitudeGain = 1,
    185.                 m_FrequencyGain = 1,
    186.                 m_Duration = 1f
    187.             };
    188.         }
    189.  
    190.         /// <summary>React to any detected impulses</summary>
    191.         /// <param name="vcam">The virtual camera being processed</param>
    192.         /// <param name="stage">The current pipeline stage</param>
    193.         /// <param name="state">The current virtual camera state</param>
    194.         /// <param name="deltaTime">The current applicable deltaTime</param>
    195.         protected override void PostPipelineStageCallback(
    196.             CinemachineVirtualCameraBase vcam,
    197.             CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
    198.         {
    199.             if (stage == m_ApplyAfter && deltaTime >= 0)
    200.             {
    201.                 bool haveImpulse = CinemachineImpulseManager.Instance.GetImpulseAt(
    202.                     state.FinalPosition, m_Use2DDistance, m_ChannelMask,
    203.                     out var impulsePos, out var impulseRot);
    204.                 bool haveReaction = m_ReactionSettings.GetReaction(
    205.                     deltaTime, impulsePos, out var reactionPos, out var reactionRot);
    206.  
    207.                 if (haveImpulse)
    208.                 {
    209.                     impulseRot = Quaternion.SlerpUnclamped(Quaternion.identity, impulseRot, m_Gain);
    210.                     impulsePos *= m_Gain;
    211.                 }
    212.                 if (haveReaction)
    213.                 {
    214.                     impulsePos += reactionPos;
    215.                     impulseRot *= reactionRot;
    216.                 }
    217.                 if (haveImpulse || haveReaction)
    218.                 {
    219.                     if (m_UseCameraSpace)
    220.                         impulsePos = state.RawOrientation * impulsePos;
    221.                     // state.PositionCorrection += impulsePos;
    222.                     // Debug.Log();
    223.                     Vector3 rotCorrect = state.OrientationCorrection.eulerAngles;
    224.                     rotCorrect.x += impulsePos.y;
    225.                     rotCorrect.y += impulsePos.x;
    226.                     rotCorrect.z += impulsePos.z;
    227.  
    228.                     state.OrientationCorrection = Quaternion.Euler(rotCorrect);
    229.                 }
    230.             }
    231.         }
    232.     }
    233. }
    234.  
     
    Gregoryl likes this.