Last active
November 20, 2023 13:39
-
-
Save SolarianZ/84068f7642262a52b0bce8e56a5509b3 to your computer and use it in GitHub Desktop.
{"category": "Unity/Runtime/Utility/Animation", "keywords": "Animation, Hit feedback, Bone Vibrate, Simple Harmonic Motion, SHM, Second Order System"} A simple single-skeleton vibrator for simple hit feedback.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NOTE: The 'SimpleHarmonicMotion.cs' is here: https://gist.github.com/SolarianZ/78f9b22d9663d77b6e6c1b0c60cc6322 | |
// You need to change `System.Numerics.Vector3` to `UnityEngine.Vector3` in SimpleHarmonicMotion.cs | |
using UnityEngine; | |
#if UNITY_EDITOR | |
using UnityEditor; | |
#endif | |
[DisallowMultipleComponent] | |
public class BoneVibrator : MonoBehaviour | |
{ | |
public enum Axis { None = 0, X, Y, Z, } | |
[Tooltip("The rotation axis in local space of the bone. " + | |
"After applying force, the bone will rotate clockwise around this axis.")] | |
public Axis localRotationAxis = Axis.None; | |
[Range(0, 10)] | |
public float frequency = 3.75f; | |
[Range(0, 2)] | |
public float damping = 0.35f; | |
[Range(-180, 0)] | |
public float minAngle = -10f; | |
[Range(0, 180)] | |
public float maxAngle = 10f; | |
public float forceToAngle = 1f; | |
public bool invertForce; | |
private SimpleHarmonicMotion _shm; | |
public void ApplyForce(float force) | |
{ | |
if (!enabled) | |
{ | |
return; | |
} | |
if (invertForce) | |
{ | |
force = -force; | |
} | |
var angle = Mathf.Clamp(force * forceToAngle, minAngle, maxAngle); | |
Vector3 rotation; | |
switch (localRotationAxis) | |
{ | |
case Axis.None: | |
Debug.LogError($"The rotation axis cannot be '{Axis.None}'.", this); | |
return; | |
case Axis.X: | |
rotation = new Vector3(angle, 0, 0); | |
break; | |
case Axis.Y: | |
rotation = new Vector3(0, angle, 0); | |
break; | |
case Axis.Z: | |
rotation = new Vector3(0, 0, angle); | |
break; | |
default: | |
throw new System.Exception($"Unknown rotation axis: {localRotationAxis}."); | |
} | |
_shm.Position = rotation; | |
} | |
private void Start() | |
{ | |
if (localRotationAxis == Axis.None) | |
{ | |
Debug.LogError($"The rotation axis cannot be '{Axis.None}'.", this); | |
} | |
_shm = new SimpleHarmonicMotion(frequency, damping, Vector3.zero); | |
} | |
// NOTE: This may conflict with IK, so adjust the execution order of script as needed | |
private void LateUpdate() | |
{ | |
_shm.Frequency = frequency; | |
_shm.Damping = damping; | |
var rotation = _shm.Tick(Time.deltaTime, Vector3.zero); | |
// NOTE: This assumes that the animation has updated the skeleton pose at this point | |
// For non-animated objects, toggle the comments below | |
// For animated object: | |
transform.Rotate(rotation, Space.Self); | |
// For non-animated object(Can be moved to the Update method): | |
//transform.localEulerAngles = rotation; | |
} | |
#if UNITY_EDITOR | |
[CustomEditor(typeof(BoneVibrator))] | |
class Editor : UnityEditor.Editor | |
{ | |
private const float GUI_AXIS_H = 10f; | |
private const float GUI_AXIS_V = 1f; | |
private BoneVibrator _target => (BoneVibrator)target; | |
private SimpleHarmonicMotion _debugShm; | |
private float _debugForce = 10; | |
private void OnEnable() | |
{ | |
_debugShm = new SimpleHarmonicMotion(_target.frequency, _target.damping, default); | |
} | |
public override void OnInspectorGUI() | |
{ | |
if (_target.localRotationAxis == Axis.None) | |
{ | |
EditorGUILayout.HelpBox($"The rotation axis cannot be '{Axis.None}'.", MessageType.Error); | |
} | |
base.OnInspectorGUI(); | |
if (!Application.isPlaying) | |
{ | |
return; | |
} | |
EditorGUILayout.Space(); | |
_debugForce = EditorGUILayout.FloatField("Debug Force", _debugForce); | |
if (GUILayout.Button("[Debug] Apply Force")) | |
{ | |
_target.ApplyForce(_debugForce); | |
} | |
} | |
public override bool HasPreviewGUI() | |
{ | |
return true; | |
} | |
public override GUIContent GetPreviewTitle() | |
{ | |
var title = base.GetPreviewTitle(); | |
title.text = ObjectNames.NicifyVariableName(nameof(BoneVibrator)) + " Preview"; | |
return title; | |
} | |
public override void OnPreviewGUI(Rect r, GUIStyle background) | |
{ | |
base.OnPreviewGUI(r, background); | |
DrawShmGraph(r); | |
} | |
private void DrawShmGraph(Rect area, float padding = 4f) | |
{ | |
// Padding | |
area.x += padding; | |
area.y += padding; | |
area.width -= padding * 2; | |
area.height -= padding * 2; | |
// Axes | |
Handles.color = Color.green; | |
Handles.DrawLine(area.position, area.position + new Vector2(0, area.height)); | |
Handles.DrawLine(area.position + new Vector2(0, area.height), area.position + new Vector2(area.width, area.height)); | |
// Points | |
const short STEPS = 2000; | |
var startPos = Vector3.zero; | |
var targetPos = startPos + Vector3.up * GUI_AXIS_V + Vector3.right * GUI_AXIS_H; | |
var lastPoint = WorldToShmGraphSpace(area, startPos); | |
_debugShm.Frequency = _target.frequency; | |
_debugShm.Damping = _target.damping; | |
_debugShm.ResetPosition(startPos); | |
var interval = GUI_AXIS_H / STEPS; | |
for (short i = 0; i < STEPS; i++) | |
{ | |
var linePos = _debugShm.Tick(interval, targetPos); | |
var x = interval * i * GUI_AXIS_H; | |
var y = (linePos - startPos).magnitude / (targetPos - startPos).magnitude; | |
var point = WorldToShmGraphSpace(area, new Vector3(x, y, 0)); | |
Handles.DrawLine(lastPoint, point); | |
lastPoint = point; | |
} | |
} | |
private Vector3 WorldToShmGraphSpace(Rect graphArea, Vector3 point) | |
{ | |
var ratioX = point.x / GUI_AXIS_H; | |
var ratioY = point.y / GUI_AXIS_V; | |
var posX = graphArea.position.x + graphArea.width * ratioX; | |
var posY = graphArea.position.y + graphArea.height - graphArea.height * ratioY / 2f; | |
var graphPoint = new Vector3(posX, posY, 0); | |
return graphPoint; | |
} | |
} | |
#endif | |
} |
Author
SolarianZ
commented
Nov 20, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment