Created
October 25, 2021 10:20
-
-
Save kyuskoj/43095fcf1b417dd2153271cedaa4ed88 to your computer and use it in GitHub Desktop.
Hair simulation with Unity 2D Animation
This file contains hidden or 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
using System; | |
using System.Collections.Generic; | |
using Sirenix.OdinInspector; | |
using UnityEngine; | |
using UnityEngine.U2D; | |
namespace Son.Modules | |
{ | |
public class Hair : MonoBehaviour | |
{ | |
[SerializeField] private Transform origin; | |
[SerializeField] private SpriteRenderer _renderer; | |
[SerializeField, Range(0f, 10f)] private float gravity; | |
[SerializeField, Range(0f, 1f)] private float gravityFallout; | |
[SerializeField] private Vector2 damp; | |
[SerializeField, Range(0f, 1f)] private float updateInterval = .125f; | |
[SerializeField, FoldoutGroup("Data", order: 1)] private List<Transform> bones = new List<Transform>(); | |
[SerializeField, FoldoutGroup("Data")] private List<Point> points = new List<Point>(8); | |
private float lastUpdate; | |
private bool flipped; | |
private bool onFlip; | |
private bool lastFlipped; | |
private void Start() | |
{ | |
foreach (var point in points) | |
point.Position = origin.TransformPoint(point.StartOffset); | |
} | |
private void Update() | |
{ | |
flipped = origin.forward.z < 0f; | |
onFlip = flipped != lastFlipped; | |
lastFlipped = flipped; | |
if (updateInterval < Time.time - lastUpdate || onFlip) | |
{ | |
Simulate(onFlip ? 1f : Time.time - lastUpdate); | |
lastUpdate = Time.time; | |
} | |
} | |
[Button(size: ButtonSizes.Medium)] | |
public void Initialized() | |
{ | |
bones.Clear(); | |
points.Clear(); | |
var spriteBones = _renderer.sprite.GetBones(); | |
var spriteBoneByName = new Dictionary<string, SpriteBone>(); | |
foreach (var bone in spriteBones) | |
spriteBoneByName[bone.name] = bone; | |
foreach (var child in origin.GetChildRecursive()) | |
{ | |
if (spriteBoneByName.ContainsKey(child.name)) | |
bones.Add(child); | |
} | |
for (int i = 0; i < bones.Count; i++) | |
{ | |
var spriteBone = spriteBoneByName[bones[i].name]; | |
var startOffset = i < bones.Count - 1 ? bones[i + 1].position : bones[i].position + Vector3.down * spriteBone.length; | |
points.Add(new Point() { StartOffset = origin.InverseTransformPoint(startOffset), Gap = spriteBone.length }); | |
} | |
} | |
private void Simulate(float interval) | |
{ | |
Vector2 parentPos = origin.position; | |
for (int i = 0; i < points.Count; i++) | |
{ | |
var point = points[i]; | |
var gravity = this.gravity * (1f - (float)i / points.Count * gravityFallout); // 꼬리 쪽으로 갈수록 더 펄럭거리도록 조정. | |
var newPos = point.Position + point.Velocity * damp + gravity * interval * Vector2.down; | |
var clampedOffset = Vector2.ClampMagnitude(newPos - parentPos, point.Gap); | |
var lastPos = point.Position; | |
point.Position = parentPos + clampedOffset; | |
point.Velocity = point.Position - lastPos; | |
if (onFlip) // 좌우반전 되었을 때, 너무 펄럭이지 않도록 조정. | |
point.Velocity = Vector2.ClampMagnitude(point.Velocity, .1f); | |
parentPos = point.Position; | |
} | |
for (int i = 0; i < points.Count; i++) | |
{ | |
Vector2 direction = points[i].Position - bones[i].position.V2(); | |
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; | |
bones[i].rotation = flipped ? Quaternion.Euler(180f, 0f, -angle) : Quaternion.Euler(0f, 0f, angle); | |
} | |
} | |
[Serializable] | |
public class Point | |
{ | |
public Vector2 Position { get; set; } | |
[field: SerializeField] public Vector2 StartOffset { get; set; } | |
[field: SerializeField] public float Gap { get; set; } | |
public Vector2 Velocity { get; set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment