129 lines
4.2 KiB
C#
129 lines
4.2 KiB
C#
using UnityEngine;
|
|
|
|
namespace AibisDream
|
|
{
|
|
[RequireComponent(typeof(SpriteRenderer))]
|
|
public sealed class SubwayHandStrap : MonoBehaviour
|
|
{
|
|
[SerializeField] private SubwayCarMotion carMotion;
|
|
[SerializeField] private float length = 0.42f;
|
|
[SerializeField] private float gravity = 9.81f;
|
|
[SerializeField] private float dampingRatio = 0.16f;
|
|
[SerializeField] private float maxAngle = 25f;
|
|
[SerializeField] private float maxAngularSpeed = 180f;
|
|
[SerializeField] private float maxIntegrationStep = 1f / 120f;
|
|
|
|
[Header("Instance Variation")]
|
|
[SerializeField] private bool randomizeOnAwake = true;
|
|
[SerializeField] private float lengthVariation = 0.03f;
|
|
[SerializeField] private Vector2 dampingRatioRange = new Vector2(0.14f, 0.18f);
|
|
[SerializeField] private float initialAngleRange = 0.5f;
|
|
|
|
[Header("Pivot")]
|
|
[SerializeField] private Vector3 hangPointLocalOffset;
|
|
|
|
private SpriteRenderer spriteRenderer;
|
|
private Vector3 restLocalPosition;
|
|
private float angle;
|
|
private float angularVelocity;
|
|
private float baseLength;
|
|
|
|
public float Angle => angle;
|
|
|
|
public float Length => length;
|
|
|
|
private void Awake()
|
|
{
|
|
spriteRenderer = GetComponent<SpriteRenderer>();
|
|
restLocalPosition = transform.localPosition;
|
|
baseLength = length;
|
|
|
|
if (hangPointLocalOffset == Vector3.zero && spriteRenderer.sprite != null)
|
|
{
|
|
hangPointLocalOffset = new Vector3(0f, spriteRenderer.sprite.bounds.max.y, 0f);
|
|
}
|
|
|
|
if (carMotion == null)
|
|
{
|
|
carMotion = GetComponentInParent<SubwayCarMotion>();
|
|
}
|
|
|
|
if (randomizeOnAwake)
|
|
{
|
|
RandomizeInstance();
|
|
}
|
|
|
|
ApplyPose();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
float remainingTime = Time.deltaTime;
|
|
while (remainingTime > 0f)
|
|
{
|
|
float step = Mathf.Min(remainingTime, maxIntegrationStep);
|
|
Simulate(step);
|
|
remainingTime -= step;
|
|
}
|
|
|
|
ApplyPose();
|
|
}
|
|
|
|
private void RandomizeInstance()
|
|
{
|
|
Random.State previousState = Random.state;
|
|
Random.InitState(GetInstanceID());
|
|
|
|
length = baseLength * Random.Range(1f - lengthVariation, 1f + lengthVariation);
|
|
dampingRatio = Random.Range(dampingRatioRange.x, dampingRatioRange.y);
|
|
angle = Random.Range(-initialAngleRange, initialAngleRange);
|
|
angularVelocity = 0f;
|
|
|
|
Random.state = previousState;
|
|
}
|
|
|
|
private void Simulate(float deltaTime)
|
|
{
|
|
float safeLength = Mathf.Max(0.01f, length);
|
|
float angleRadians = angle * Mathf.Deg2Rad;
|
|
float angularVelocityRadians = angularVelocity * Mathf.Deg2Rad;
|
|
float naturalFrequency = Mathf.Sqrt(gravity / safeLength);
|
|
float accelX = carMotion != null ? carMotion.AccelX : 0f;
|
|
|
|
float angularAcceleration =
|
|
-(gravity / safeLength) * Mathf.Sin(angleRadians)
|
|
- (accelX / safeLength) * Mathf.Cos(angleRadians)
|
|
- 2f * dampingRatio * naturalFrequency * angularVelocityRadians;
|
|
|
|
angularVelocityRadians += angularAcceleration * deltaTime;
|
|
angularVelocity = Mathf.Clamp(angularVelocityRadians * Mathf.Rad2Deg, -maxAngularSpeed, maxAngularSpeed);
|
|
angle += angularVelocity * deltaTime;
|
|
|
|
ClampAngleLimit();
|
|
}
|
|
|
|
private void ClampAngleLimit()
|
|
{
|
|
if (Mathf.Abs(angle) <= maxAngle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float limitSign = Mathf.Sign(angle);
|
|
angle = limitSign * maxAngle;
|
|
|
|
if (Mathf.Sign(angularVelocity) == limitSign)
|
|
{
|
|
angularVelocity = 0f;
|
|
}
|
|
}
|
|
|
|
private void ApplyPose()
|
|
{
|
|
Quaternion rotation = Quaternion.Euler(0f, 0f, angle);
|
|
transform.localRotation = rotation;
|
|
transform.localPosition = restLocalPosition + hangPointLocalOffset - rotation * hangPointLocalOffset;
|
|
}
|
|
}
|
|
}
|