-
-
Save ftvs/5822103 to your computer and use it in GitHub Desktop.
using UnityEngine; | |
using System.Collections; | |
public class CameraShake : MonoBehaviour | |
{ | |
// Transform of the camera to shake. Grabs the gameObject's transform | |
// if null. | |
public Transform camTransform; | |
// How long the object should shake for. | |
public float shakeDuration = 0f; | |
// Amplitude of the shake. A larger value shakes the camera harder. | |
public float shakeAmount = 0.7f; | |
public float decreaseFactor = 1.0f; | |
Vector3 originalPos; | |
void Awake() | |
{ | |
if (camTransform == null) | |
{ | |
camTransform = GetComponent(typeof(Transform)) as Transform; | |
} | |
} | |
void OnEnable() | |
{ | |
originalPos = camTransform.localPosition; | |
} | |
void Update() | |
{ | |
if (shakeDuration > 0) | |
{ | |
camTransform.localPosition = originalPos + Random.insideUnitSphere * shakeAmount; | |
shakeDuration -= Time.deltaTime * decreaseFactor; | |
} | |
else | |
{ | |
shakeDuration = 0f; | |
camTransform.localPosition = originalPos; | |
} | |
} | |
} |
Thanks @ixikos. I expanded on your script to calculate a delta time so the shake duration is retained even when the game gets paused (and Time.deltaTime stops).
using UnityEngine;
using System.Collections;
public class CameraShake : MonoBehaviour {
public static CameraShake instance;
private Vector3 _originalPos;
private float _timeAtCurrentFrame;
private float _timeAtLastFrame;
private float _fakeDelta;
void Awake() {
instance = this;
}
void Update() {
// Calculate a fake delta time, so we can Shake while game is paused.
_timeAtCurrentFrame = Time.realtimeSinceStartup;
_fakeDelta = _timeAtCurrentFrame - _timeAtLastFrame;
_timeAtLastFrame = _timeAtCurrentFrame;
}
public static void Shake (float duration, float amount) {
instance._originalPos = instance.gameObject.transform.localPosition;
instance.StopAllCoroutines();
instance.StartCoroutine(instance.cShake(duration, amount));
}
public IEnumerator cShake (float duration, float amount) {
float endTime = Time.time + duration;
while (duration > 0) {
transform.localPosition = _originalPos + Random.insideUnitSphere * amount;
duration -= _fakeDelta;
yield return null;
}
transform.localPosition = _originalPos;
}
}
And of course, call it from anywhere by
CameraShake.Shake(0.25f, 4f);
Thanks :)
Thanks @ixikos. I expanded on your script to calculate a delta time so the shake duration is retained even when the game gets paused (and Time.deltaTime stops).
Couldn't you just have replaced Time.deltaTime with Time.unscaledDeltaTime and had the same functionality?
Thanks man!
really freaking cool! modified it to run whenever an in-game explosion happens. Added tonnes of feeling to my project!
Thank you! I put your script in a small Coroutine. Makes my prototypes instantly more fun!
// used fields
private Camera mainCamera;
[SerializeField] private float cameraShakeDuration = 0.5f;
[SerializeField] private float cameraShakeDecreaseFactor = 1f;
[SerializeField] private float cameraShakeAmount = 1f;
// coroutine
IEnumerator ShakeCamera()
{
var originalPos = mainCamera.transform.localPosition;
var duration = cameraShakeDuration;
while(duration > 0)
{
mainCamera.transform.localPosition = originalPos + Random.insideUnitSphere * cameraShakeAmount;
duration -= Time.deltaTime * cameraShakeDecreaseFactor;
yield return null;
}
mainCamera.transform.localPosition = originalPos;
}
Nice script!
For anyone that wants it I made a version that allows you to skip frames to control how jerky the camera shake is, works on any gameObject, and lets you choose whether or not to run it whenever the script is enabled.
public class ObjectShaker : MonoBehaviour {
bool startOnEnable = true;
float shakeDuration = 1f;
float framesToSkip = 0f; //How many frames to we skip in order to slow down the jerkiness of the animation
public float shakeAmount = 1f;
void OnEnable()
{
if(startOnEnable)
{
Shake();
}
}
//Parameters are optional. Defaults to object variables.
public void Shake(float _duration = -1f, float _skip = -1f, float _amount = -1f)
{
if(_duration < 0)
{
_duration = shakeDuration;
}
if(_skip < 0)
{
_skip = framesToSkip;
}
if(_amount < 0)
{
_amount = shakeAmount;
}
StartCoroutine(ShakeCoroutine(_duration, _skip, _amount));
}
IEnumerator ShakeCoroutine(float _duration, float _framesToSkip, float _amount)
{
//For if you need special logic for Main Cameras (e.g. you have a script that follows the player around that you need to disable temporarily)
if(gameObject.tag == "MainCamera")
{
//Disable here
}
Vector3 originalPos = transform.position;
float currFramesSkipped = 0f;
bool locked = true;
while (_duration > 0)
{
if(currFramesSkipped <= _framesToSkip)
{
locked = true;
currFramesSkipped += 1;
} else
{
locked = false;
currFramesSkipped = 0f;
}
if(!locked)
{
transform.position = originalPos + Random.insideUnitSphere * _amount;
}
_duration -= Time.deltaTime;
yield return null;
}
//reenable your camera
if (gameObject.tag == "MainCamera")
{
//reenable camera logic here
}
transform.position = originalPos;
}
}
Here's my script to shake, feel free to use and modify.
using UnityEngine;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
namespace AKGaming
{
public class ShakeComponent : MonoBehaviour
{
[SerializeField] private Transform target_;
[SerializeField] private float defaultDuration_ = 0.5f;
[SerializeField] private float defaultAmplitude_ = 0.75f;
[SerializeField] private float defaultFinalAmplitude_ = 0.25f;
[SerializeField] private AnimationCurve defaultAmplitudeCurve_ = AnimationCurve.Linear(0, 0, 1, 1);
private float duration_;
private float remainingTime_;
private float amplitude_;
private float finalAmplitude_;
private AnimationCurve amplitudeCurve_;
private Vector3 initialPosition_;
private void Awake()
{
if (target_ == null)
{
target_ = GetComponent<Transform>();
}
initialPosition_ = target_.localPosition;
}
public void Shake(float duration = -1, float amplitude = -1, float finalAmplitude = -1, AnimationCurve amplitudeCurve = null)
{
duration_ = duration > 0 ? duration : defaultDuration_;
amplitude_ = amplitude >= 0 ? amplitude : defaultAmplitude_;
finalAmplitude_ = finalAmplitude >= 0 ? finalAmplitude : defaultFinalAmplitude_;
amplitudeCurve_ = amplitudeCurve != null ? amplitudeCurve : defaultAmplitudeCurve_;
remainingTime_ = duration_;
enabled = true;
}
private void Update()
{
if (remainingTime_ > 0)
{
float curveValue = amplitudeCurve_.Evaluate(1 - (remainingTime_ / duration_));
float amplitude = (1 - curveValue) * amplitude_ + curveValue * finalAmplitude_;
transform.localPosition = initialPosition_ + Random.insideUnitSphere * amplitude;
remainingTime_ -= Time.deltaTime;
}
else
{
remainingTime_ = 0f;
transform.localPosition = initialPosition_;
enabled = false;
}
}
#if UNITY_EDITOR
#if ODIN_INSPECTOR
[Button("Test Shake")]
#endif
[ContextMenu("Test Shake")]
private void EDITOR_TestShake()
{
Shake();
}
#endif
}
}
Here's the amplitude curve i'm using right now.
Please notice that the curve value is not the actual amplitude value so I recommend that it always goes from 0 to 1.
Thanks! It worked well. The only thing I added to the script was a line that turns CinemachineBrain false because camera shaker didn't work with cinemachine.
This is my modification, I made it completely generic. Just copy paste on any object and you're good to go.
How it works:
- Duration: the duration of the shake in seconds
- Amplitude: the amount of Unity units that the shake uses to move
- Soft Level: The higher it is, the slower will run the shake. Internally, the soft level int acts as "how many frames do I skip for the next shake"
- Decrease: If false, the shake will be constant. If true, the shake will linearly shift its amplitude.
- Animation: Same as decrease, but here you can choose which amplitude animation you want
Flaws:
- The soften parameter does its job, but the shake is not really smooth... I tried implementing the "Vector3.Lerp" option, but it does not work really well... If someone can come up with a solution to smooth the movement, so then we can acquire a kind of "frequency" parameter.
public void Shake(float duration, float amplitude, int softLevel = 0, bool decrease = false)
{
AnimationCurve animation = decrease ? AnimationCurve.Linear(0, 1, 1, 0) : AnimationCurve.Constant(0, 1, 1);
StartCoroutine(Shake_Internal(duration, amplitude, softLevel, animation));
}
public void Shake(float duration, float amplitude, int softLevel = 0, AnimationCurve animation = null)
{
if (animation == null) animation = AnimationCurve.Linear(0, 1, 1, 0);
StartCoroutine(Shake_Internal(duration, amplitude, softLevel, animation));
}
private IEnumerator Shake_Internal(float duration, float amplitude, int softLevel, AnimationCurve animation)
{
Vector3 initialPosition = transform.position;
float amp = amplitude;
if (softLevel < 0) softLevel = 0;
int softCount = 0;
for (float i = 0; i < duration; i += Time.deltaTime)
{
if (softLevel != 0 && softCount < softLevel)
softCount++;
else
{
transform.position = initialPosition + Random.insideUnitSphere * amp;
amp = amplitude * animation.Evaluate(i);
softCount = 0;
}
yield return null;
}
transform.position = initialPosition;
}
Good stuff!