feat(subway): 添加车厢加速度驱动与扶手摆动
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -291,6 +291,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 88656235}
|
||||
- component: {fileID: 88656236}
|
||||
- component: {fileID: 9100002001}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BA (3)"
|
||||
m_TagString: Untagged
|
||||
@@ -571,6 +572,7 @@ MonoBehaviour:
|
||||
tvSlots: {fileID: 494482151}
|
||||
newsSpriteRenderer: {fileID: 5352226562362177808}
|
||||
arriveStation: {fileID: 969974277}
|
||||
carMotion: {fileID: 9100001001}
|
||||
--- !u!1 &226184625
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -924,6 +926,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 325261252}
|
||||
- component: {fileID: 325261253}
|
||||
- component: {fileID: 9100002002}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BB (3)"
|
||||
m_TagString: Untagged
|
||||
@@ -1008,6 +1011,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 347095461}
|
||||
- component: {fileID: 347095462}
|
||||
- component: {fileID: 9100002003}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BB (2)"
|
||||
m_TagString: Untagged
|
||||
@@ -1224,6 +1228,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 450826708}
|
||||
- component: {fileID: 450826709}
|
||||
- component: {fileID: 9100002004}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BB (1)"
|
||||
m_TagString: Untagged
|
||||
@@ -1602,6 +1607,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 497537573}
|
||||
- component: {fileID: 497537574}
|
||||
- component: {fileID: 9100002005}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BB (4)"
|
||||
m_TagString: Untagged
|
||||
@@ -1729,6 +1735,7 @@ GameObject:
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 546256082}
|
||||
- component: {fileID: 9100001001}
|
||||
m_Layer: 0
|
||||
m_Name: "\u628A\u624B\u7EC4"
|
||||
m_TagString: Untagged
|
||||
@@ -3153,6 +3160,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1702647399}
|
||||
- component: {fileID: 1702647400}
|
||||
- component: {fileID: 9100002006}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BA (4)"
|
||||
m_TagString: Untagged
|
||||
@@ -3471,6 +3479,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1901117876}
|
||||
- component: {fileID: 1901117877}
|
||||
- component: {fileID: 9100002007}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BA (1)"
|
||||
m_TagString: Untagged
|
||||
@@ -3555,6 +3564,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 1926948638}
|
||||
- component: {fileID: 1926948639}
|
||||
- component: {fileID: 9100002008}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BA (2)"
|
||||
m_TagString: Untagged
|
||||
@@ -4395,6 +4405,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 4103782223475585769}
|
||||
- component: {fileID: 8967103020905652160}
|
||||
- component: {fileID: 9100002009}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BB"
|
||||
m_TagString: Untagged
|
||||
@@ -4460,6 +4471,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 2050361239654858494}
|
||||
- component: {fileID: 8861695100661960901}
|
||||
- component: {fileID: 9100002010}
|
||||
m_Layer: 0
|
||||
m_Name: "\u6276\u624BA"
|
||||
m_TagString: Untagged
|
||||
@@ -5535,3 +5547,309 @@ SceneRoots:
|
||||
- {fileID: 163812961}
|
||||
- {fileID: 1929645612}
|
||||
- {fileID: 1767040865}
|
||||
--- !u!114 &9100001001
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 546256081}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 22b2f701b86b458fae30ddb4a612c419, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
calmIntervalMin: 0.7
|
||||
calmIntervalMax: 2.2
|
||||
joltPeakMin: 0.6
|
||||
joltPeakMax: 1.4
|
||||
joltDurationMin: 0.18
|
||||
joltDurationMax: 0.32
|
||||
joltBurstCount: {x: 1, y: 3}
|
||||
joltBurstGapMin: 0.12
|
||||
joltBurstGapMax: 0.28
|
||||
calmNoiseAmplitude: 0.03
|
||||
brakePeakAccel: 1.6
|
||||
brakeAccelCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 4
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 0.12
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 0.5
|
||||
value: 0.65
|
||||
inSlope: -0.5
|
||||
outSlope: -0.5
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: -1.5
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
accelSmoothTime: 0.025
|
||||
--- !u!114 &9100002001
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 88656234}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002002
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 325261251}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002003
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 347095460}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002004
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 450826707}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002005
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 497537572}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002006
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1702647398}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002007
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1901117875}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002008
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1926948637}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002009
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4324682127354924289}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9100002010
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4847544086954038309}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 982dbdad2132464f978518ffdf1737ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
carMotion: {fileID: 9100001001}
|
||||
length: 0.42
|
||||
gravity: 9.81
|
||||
dampingRatio: 0.16
|
||||
maxAngle: 25
|
||||
maxAngularSpeed: 180
|
||||
maxIntegrationStep: 0.008333334
|
||||
randomizeOnAwake: 1
|
||||
lengthVariation: 0.03
|
||||
dampingRatioRange: {x: 0.14, y: 0.18}
|
||||
initialAngleRange: 0.5
|
||||
hangPointLocalOffset: {x: 0, y: 0, z: 0}
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace AibisDream
|
||||
|
||||
public PlayableDirector arriveStation;
|
||||
|
||||
[Tooltip("车厢加速度驱动;到站时触发把手制动摆动")]
|
||||
public SubwayCarMotion carMotion;
|
||||
|
||||
public static StateMachine<OutSideState> StateMachine { get; private set; }
|
||||
|
||||
public bool IsDialogueReady { get; private set; }
|
||||
@@ -60,8 +63,26 @@ namespace AibisDream
|
||||
|
||||
public IEnumerator ArriveAtStation()
|
||||
{
|
||||
arriveStation.Play();
|
||||
yield return new WaitForSeconds((float) arriveStation.playableAsset.duration);
|
||||
float duration = arriveStation != null && arriveStation.playableAsset != null
|
||||
? (float)arriveStation.playableAsset.duration
|
||||
: 8.5f;
|
||||
|
||||
if (carMotion == null)
|
||||
{
|
||||
carMotion = FindFirstObjectByType<SubwayCarMotion>();
|
||||
}
|
||||
|
||||
if (carMotion != null)
|
||||
{
|
||||
carMotion.BeginBraking(duration);
|
||||
}
|
||||
|
||||
if (arriveStation != null)
|
||||
{
|
||||
arriveStation.Play();
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
|
||||
public IEnumerator SwitchNews(string spriteId)
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22b2f701b86b458fae30ddb4a612c419
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 982dbdad2132464f978518ffdf1737ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user