185 lines
5.6 KiB
C#
185 lines
5.6 KiB
C#
using UnityEngine;
|
|
|
|
namespace AibisDream
|
|
{
|
|
public enum SubwayCarMotionPhase
|
|
{
|
|
Stopped,
|
|
Cruise,
|
|
Braking
|
|
}
|
|
|
|
public sealed class SubwayCarMotion : MonoBehaviour
|
|
{
|
|
[Header("Cruise Jolts")]
|
|
[SerializeField] private float calmIntervalMin = 0.7f;
|
|
[SerializeField] private float calmIntervalMax = 2.2f;
|
|
[SerializeField] private float joltPeakMin = 0.6f;
|
|
[SerializeField] private float joltPeakMax = 1.4f;
|
|
[SerializeField] private float joltDurationMin = 0.18f;
|
|
[SerializeField] private float joltDurationMax = 0.32f;
|
|
[SerializeField] private Vector2Int joltBurstCount = new Vector2Int(1, 3);
|
|
[SerializeField] private float joltBurstGapMin = 0.12f;
|
|
[SerializeField] private float joltBurstGapMax = 0.28f;
|
|
[SerializeField] private float calmNoiseAmplitude = 0.03f;
|
|
|
|
[Header("Braking")]
|
|
[SerializeField] private float brakePeakAccel = 1.6f;
|
|
[SerializeField] private AnimationCurve brakeAccelCurve = new AnimationCurve(
|
|
new Keyframe(0f, 0f, 0f, 4f),
|
|
new Keyframe(0.12f, 1f),
|
|
new Keyframe(0.5f, 0.65f, -0.5f, -0.5f),
|
|
new Keyframe(1f, 0f, -1.5f, 0f));
|
|
|
|
[Header("Signal Filtering")]
|
|
[SerializeField] private float accelSmoothTime = 0.025f;
|
|
|
|
private float targetAccelX;
|
|
private float accelVelocity;
|
|
private float brakeDuration = 1f;
|
|
private float brakeTimer;
|
|
private float nextCruiseEventTime;
|
|
private int burstRemaining;
|
|
private float joltTimer;
|
|
private float joltDuration = 0.2f;
|
|
private float joltPeak = 1f;
|
|
private float joltSign = 1f;
|
|
private float noiseSeed;
|
|
|
|
public float AccelX { get; private set; }
|
|
|
|
public SubwayCarMotionPhase Phase { get; private set; } = SubwayCarMotionPhase.Cruise;
|
|
|
|
private void Awake()
|
|
{
|
|
noiseSeed = Random.value * 1000f;
|
|
ScheduleNextCruiseEvent();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
switch (Phase)
|
|
{
|
|
case SubwayCarMotionPhase.Cruise:
|
|
UpdateCruise();
|
|
break;
|
|
case SubwayCarMotionPhase.Braking:
|
|
UpdateBraking();
|
|
break;
|
|
default:
|
|
targetAccelX = 0f;
|
|
break;
|
|
}
|
|
|
|
AccelX = Mathf.SmoothDamp(AccelX, targetAccelX, ref accelVelocity, accelSmoothTime);
|
|
}
|
|
|
|
public void BeginBraking(float duration)
|
|
{
|
|
ClearCruiseJolt();
|
|
Phase = SubwayCarMotionPhase.Braking;
|
|
brakeDuration = Mathf.Max(0.01f, duration);
|
|
brakeTimer = 0f;
|
|
}
|
|
|
|
public void SetCruise()
|
|
{
|
|
ClearCruiseJolt();
|
|
Phase = SubwayCarMotionPhase.Cruise;
|
|
ScheduleNextCruiseEvent();
|
|
}
|
|
|
|
public void SetStopped()
|
|
{
|
|
ClearCruiseJolt();
|
|
Phase = SubwayCarMotionPhase.Stopped;
|
|
targetAccelX = 0f;
|
|
AccelX = 0f;
|
|
accelVelocity = 0f;
|
|
brakeTimer = 0f;
|
|
}
|
|
|
|
private void UpdateCruise()
|
|
{
|
|
if (joltTimer > 0f)
|
|
{
|
|
joltTimer -= Time.deltaTime;
|
|
float normalizedTime = Mathf.Clamp01(1f - joltTimer / joltDuration);
|
|
targetAccelX = EvaluateBiphasicPulse(normalizedTime) * joltPeak * joltSign;
|
|
|
|
if (joltTimer <= 0f)
|
|
{
|
|
if (burstRemaining > 0)
|
|
{
|
|
nextCruiseEventTime = Time.time + Random.Range(joltBurstGapMin, joltBurstGapMax);
|
|
}
|
|
else
|
|
{
|
|
ScheduleNextCruiseEvent();
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
float noise = (Mathf.PerlinNoise(noiseSeed, Time.time * 0.45f) - 0.5f) * 2f;
|
|
targetAccelX = noise * calmNoiseAmplitude;
|
|
|
|
if (Time.time >= nextCruiseEventTime)
|
|
{
|
|
StartJolt();
|
|
}
|
|
}
|
|
|
|
private void UpdateBraking()
|
|
{
|
|
brakeTimer += Time.deltaTime;
|
|
float t = Mathf.Clamp01(brakeTimer / brakeDuration);
|
|
targetAccelX = -brakePeakAccel * brakeAccelCurve.Evaluate(t);
|
|
|
|
if (brakeTimer >= brakeDuration)
|
|
{
|
|
SetStopped();
|
|
}
|
|
}
|
|
|
|
private static float EvaluateBiphasicPulse(float t)
|
|
{
|
|
if (t < 0.5f)
|
|
{
|
|
return Mathf.Sin(t * Mathf.PI * 2f);
|
|
}
|
|
|
|
return -Mathf.Sin((t - 0.5f) * Mathf.PI * 2f);
|
|
}
|
|
|
|
private void StartJolt()
|
|
{
|
|
if (burstRemaining <= 0)
|
|
{
|
|
int maxExclusive = Mathf.Max(joltBurstCount.x + 1, joltBurstCount.y + 1);
|
|
burstRemaining = Random.Range(joltBurstCount.x, maxExclusive);
|
|
}
|
|
|
|
burstRemaining--;
|
|
joltTimer = Random.Range(joltDurationMin, joltDurationMax);
|
|
joltDuration = Mathf.Max(0.01f, joltTimer);
|
|
joltPeak = Random.Range(joltPeakMin, joltPeakMax);
|
|
joltSign = Random.value < 0.5f ? -1f : 1f;
|
|
}
|
|
|
|
private void ScheduleNextCruiseEvent()
|
|
{
|
|
burstRemaining = 0;
|
|
nextCruiseEventTime = Time.time + Random.Range(calmIntervalMin, calmIntervalMax);
|
|
}
|
|
|
|
private void ClearCruiseJolt()
|
|
{
|
|
burstRemaining = 0;
|
|
joltTimer = 0f;
|
|
targetAccelX = 0f;
|
|
}
|
|
}
|
|
}
|