diff --git a/Assets/000_assets/material/M_SquareFrame 1.mat b/Assets/000_assets/material/M_SquareFrame 1.mat index bb148dd5..5e8be18b 100644 --- a/Assets/000_assets/material/M_SquareFrame 1.mat +++ b/Assets/000_assets/material/M_SquareFrame 1.mat @@ -213,7 +213,7 @@ Material: - _Dst: 10 - _DstBlend: 0 - _DstBlendAlpha: 0 - - _EdgeValue: 0.9624716 + - _EdgeValue: 0.59140563 - _EnvironmentReflections: 1 - _FNLfanxiangkaiguan: 0 - _Face: 1 @@ -258,7 +258,7 @@ Material: - _Mask_scale: 1 - _Metallic: 0 - _OcclusionStrength: 1 - - _Opacity: 0.037528396 + - _Opacity: 0.40859437 - _Parallax: 0.005 - _Pass: 0 - _QueueOffset: 0 diff --git a/Assets/AddressableAssetsData/iOS.meta b/Assets/AddressableAssetsData/iOS.meta new file mode 100644 index 00000000..031a41ae --- /dev/null +++ b/Assets/AddressableAssetsData/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ee86eb680edff0441812416293403139 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/iOS/addressables_content_state.bin b/Assets/AddressableAssetsData/iOS/addressables_content_state.bin new file mode 100644 index 00000000..293986e2 Binary files /dev/null and b/Assets/AddressableAssetsData/iOS/addressables_content_state.bin differ diff --git a/Assets/AddressableAssetsData/iOS/addressables_content_state.bin.meta b/Assets/AddressableAssetsData/iOS/addressables_content_state.bin.meta new file mode 100644 index 00000000..7212e80f --- /dev/null +++ b/Assets/AddressableAssetsData/iOS/addressables_content_state.bin.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a792caa2a59b77543b25793ed326a5bc +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/link.xml.meta b/Assets/AddressableAssetsData/link.xml.meta index 47526a9b..c2219464 100644 --- a/Assets/AddressableAssetsData/link.xml.meta +++ b/Assets/AddressableAssetsData/link.xml.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7c82452eec95d2140ac081f8c18042f3 +guid: 0955e9cb35e5f834abe8a52400d9f81a TextScriptImporter: externalObjects: {} userData: diff --git a/Assets/Dreamteck/Splines/Components/ParticleController.cs b/Assets/Dreamteck/Splines/Components/ParticleController.cs index 211cb8e4..a35413e4 100644 --- a/Assets/Dreamteck/Splines/Components/ParticleController.cs +++ b/Assets/Dreamteck/Splines/Components/ParticleController.cs @@ -24,6 +24,7 @@ namespace Dreamteck.Splines public enum EmitPoint { Beginning, Ending, Random, Ordered } public enum MotionType { None, UseParticleSystem, FollowForward, FollowBackward, ByNormal, ByNormalRandomized } public enum Wrap { Default, Loop } + public enum PathNodeSizeMode { LegacyOffset, ApplyPathNodeSize } public bool is3D; public float width = 10f; @@ -51,6 +52,8 @@ namespace Dreamteck.Splines public float minCycles = 1f; [HideInInspector] public float maxCycles = 1f; + [HideInInspector] + public PathNodeSizeMode pathNodeSizeMode = PathNodeSizeMode.LegacyOffset; private Dictionary _particleDataMap = new Dictionary(); private ParticleSystem.Particle[] _particles = new ParticleSystem.Particle[0]; diff --git a/Assets/FR2_Cache.asset b/Assets/FR2_Cache.asset index 7204f8a9..34f8d60f 100644 --- a/Assets/FR2_Cache.asset +++ b/Assets/FR2_Cache.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe0dfbab5808f71e29a2d0677b06dc7f88a0ee7de546f2c477b654d9d84293fd -size 8762633 +oid sha256:5335509c0c2303e8b12dcb96a7033f9cc731aa5725e3027b9d18b494f893b8d9 +size 8763904 diff --git a/Assets/Prefabs/GameElements/Note/NoteJudgeHint/AreaJudgeHInt.prefab b/Assets/Prefabs/GameElements/Note/NoteJudgeHint/AreaJudgeHInt.prefab index 68310cf3..cce2ca30 100644 --- a/Assets/Prefabs/GameElements/Note/NoteJudgeHint/AreaJudgeHInt.prefab +++ b/Assets/Prefabs/GameElements/Note/NoteJudgeHint/AreaJudgeHInt.prefab @@ -27,7 +27,7 @@ RectTransform: m_GameObject: {fileID: 7671065637303904002} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 2, y: 2, z: 2} + m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} diff --git a/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/Beatmap.bytes b/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/Beatmap.bytes index b0770205..6bf10b7a 100644 Binary files a/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/Beatmap.bytes and b/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/Beatmap.bytes differ diff --git a/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/CommandScripts.bytes b/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/CommandScripts.bytes index 6be800f4..bf735124 100644 --- a/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/CommandScripts.bytes +++ b/Assets/Resources/Beatmaps/Chapter0/Chapter 0 Tutorial/Easy/CommandScripts.bytes @@ -1 +1,2 @@ -虎hDA a{PgP@wY2hHU^Dmw roVr'U'(hB4-hRa'rY*E1X\qiBy4?±FUy'KlVGRPkeOִ"xIT~v v_H$:2MH'ә#pp@ \ No newline at end of file +96yS(˥` YQ$H/-uP'8nG_07 ï7nj'S;б/ԈgPE4Y[V=EL5#rfpW5`]:}r1ƴ +VP${3T杬)n4V0bOy@7MƝ&H}KJI|6 qΆ-|;&X -2n.t3Iǫ]~9Ň>U"2PpvX~" j+=Z'y f \ No newline at end of file + s@O[VAjOdnf''l |QJK ^(e#⻀zmJIk0e :402Ӭuv_‡oe5]DoG%y5x"7!: Jlm|LcU̠7$X/u/k;pCQw582l24ͯig \ No newline at end of file diff --git a/Assets/Resources/Chapter0.asset b/Assets/Resources/Chapter0.asset index c9fdc6b2..bfca2570 100644 --- a/Assets/Resources/Chapter0.asset +++ b/Assets/Resources/Chapter0.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42bdc03de85e3424cbe4cd22d4e9338f224a7a8d7ac33e11c4a1ab5092eb8c96 +oid sha256:913cebeab6dbdae244d95c3e9994c2c5ca2f58fc7bec6a30de058d84658fe5db size 18498 diff --git a/Assets/Scenes/GameScene.unity b/Assets/Scenes/GameScene.unity index f5213dcf..d2f39aa5 100644 --- a/Assets/Scenes/GameScene.unity +++ b/Assets/Scenes/GameScene.unity @@ -11790,7 +11790,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 99971dd6462223c4596d435e8acdcfb8, type: 3} m_Name: m_EditorClassIdentifier: - minSwipeDistance: 100 + minSwipeDistance: 50 swipeAngleThreshold: 1 playerInput: {fileID: 1332204029} holdingTouch0: 0 diff --git a/Assets/Scenes/GameScene_DEV.unity b/Assets/Scenes/GameScene_DEV.unity index 196d6b8b..fcbe23a2 100644 --- a/Assets/Scenes/GameScene_DEV.unity +++ b/Assets/Scenes/GameScene_DEV.unity @@ -11875,7 +11875,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 99971dd6462223c4596d435e8acdcfb8, type: 3} m_Name: m_EditorClassIdentifier: - minSwipeDistance: 100 + minSwipeDistance: 25 swipeAngleThreshold: 1 playerInput: {fileID: 1332204029} holdingTouch0: 0 diff --git a/Assets/Scenes/MenuScene.unity b/Assets/Scenes/MenuScene.unity index b957c344..b3bf65d8 100644 --- a/Assets/Scenes/MenuScene.unity +++ b/Assets/Scenes/MenuScene.unity @@ -25327,7 +25327,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 0 + m_IsActive: 1 --- !u!224 &1589396086 RectTransform: m_ObjectHideFlags: 0 @@ -25358,9 +25358,9 @@ CanvasGroup: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1589396085} m_Enabled: 1 - m_Alpha: 1 - m_Interactable: 1 - m_BlocksRaycasts: 1 + m_Alpha: 0 + m_Interactable: 0 + m_BlocksRaycasts: 0 m_IgnoreParentGroups: 0 --- !u!1001 &1609000271 PrefabInstance: @@ -30066,7 +30066,7 @@ ParticleSystem: ringBufferLoopRange: {x: 0, y: 1} emitterVelocityMode: 1 looping: 1 - prewarm: 1 + prewarm: 0 playOnAwake: 1 useUnscaledTime: 0 autoRandomSeed: 1 @@ -34825,9 +34825,9 @@ CanvasGroup: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2019519601} m_Enabled: 1 - m_Alpha: 0 - m_Interactable: 0 - m_BlocksRaycasts: 0 + m_Alpha: 1 + m_Interactable: 1 + m_BlocksRaycasts: 1 m_IgnoreParentGroups: 0 --- !u!114 &2019519604 MonoBehaviour: @@ -34861,7 +34861,7 @@ MonoBehaviour: m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 - m_ReferenceResolution: {x: 1920, y: 1080} + m_ReferenceResolution: {x: 2560, y: 1440} m_ScreenMatchMode: 0 m_MatchWidthOrHeight: 0 m_PhysicalUnit: 3 diff --git a/Assets/Scripts/Game/GameElements/GeneralEffects/EffectBase.cs b/Assets/Scripts/Game/GameElements/GeneralEffects/EffectBase.cs index 269fdcb2..2fccc412 100644 --- a/Assets/Scripts/Game/GameElements/GeneralEffects/EffectBase.cs +++ b/Assets/Scripts/Game/GameElements/GeneralEffects/EffectBase.cs @@ -58,9 +58,18 @@ namespace Ichni.RhythmGame #region [基础逻辑] Main Update Logic public virtual void UpdateEffect(float judgeTime) { - EffectState state = CheckEffectState(judgeTime); float songTime = CoreServices.TimeProvider.SongTime; + // 零时长特效必须在到达触发时间的当帧执行。 + // 若继续进入 Middle 分支,不仅会把 Adjust() 延迟到下一帧,还会产生 0 / 0 的 NaN 进度。 + if (isInstantEffect) + { + UpdateInstantEffect(judgeTime, songTime); + return; + } + + EffectState state = CheckEffectState(judgeTime); + if (state == EffectState.Before && nowEffectState != EffectState.Before) { nowEffectState = EffectState.Before; @@ -86,6 +95,27 @@ namespace Ichni.RhythmGame } } + private void UpdateInstantEffect(float triggerTime, float songTime) + { + if (songTime < triggerTime) + { + if (nowEffectState != EffectState.Before) + { + nowEffectState = EffectState.Before; + effectProgressPercent = 0; + Recover(); + } + + return; + } + + if (nowEffectState == EffectState.After) return; + + nowEffectState = EffectState.After; + effectProgressPercent = 1; + Adjust(); + } + protected virtual EffectState CheckEffectState(float triggerTime) { float songTime = CoreServices.TimeProvider.SongTime; diff --git a/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/FullScreenNearTimeJudgeUnit.cs b/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/FullScreenNearTimeJudgeUnit.cs index 216427cb..30379402 100644 --- a/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/FullScreenNearTimeJudgeUnit.cs +++ b/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/FullScreenNearTimeJudgeUnit.cs @@ -27,8 +27,9 @@ namespace Ichni.RhythmGame } } - public override bool CheckJudgeAvailability(InputUnit inputUnit) + public override bool CheckJudgeAvailability(InputJudgeContext context) { + InputUnit inputUnit = context.InputUnit; if (inputUnit is InputUnitSwipe swipe && note is Flick flick) { return flick.CheckSwipeDirection(swipe); @@ -40,4 +41,4 @@ namespace Ichni.RhythmGame } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/NoteJudgeUnit.cs b/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/NoteJudgeUnit.cs index 6031367a..96621237 100644 --- a/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/NoteJudgeUnit.cs +++ b/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/NoteJudgeUnit.cs @@ -44,7 +44,15 @@ namespace Ichni.RhythmGame } - public virtual bool CheckJudgeAvailability(InputUnit inputUnit) + public virtual void UpdateSpatialState(float songTime, Vector2 screenPosition) + { + } + + public virtual void ResetSpatialState() + { + } + + public virtual bool CheckJudgeAvailability(InputJudgeContext context) { throw new NotImplementedException(); } @@ -54,4 +62,4 @@ namespace Ichni.RhythmGame #endregion } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/TouchAreaJudgeUnit.cs b/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/TouchAreaJudgeUnit.cs index 276284d7..5025420d 100644 --- a/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/TouchAreaJudgeUnit.cs +++ b/Assets/Scripts/Game/GameElements/Notes/JudgeSubmodules/TouchAreaJudgeUnit.cs @@ -1,5 +1,3 @@ -using System.Collections; -using Ichni.RhythmGame.Beatmap; using UnityEngine; namespace Ichni.RhythmGame @@ -7,11 +5,42 @@ namespace Ichni.RhythmGame public class TouchAreaJudgeUnit : NoteJudgeUnit { #region [暴露属性字段] Properties - public float ellipseXMultiplier = 1.25f; - public float ellipseYMultiplier = 1.0f; + // Global tuning values. Speed uses 1080p-reference pixels per second; + // sample distance is normalized by the current screen height. + public static float DirectionalMajorAxisMultiplier = 1.10f; + public static float DirectionalMinorAxisMultiplier = 0.80f; + public static float FallbackCircleMultiplier = 0.94f; + // 面积缩放为 75% 时,长短轴半径需要统一乘以 sqrt(0.75),才能保持原有椭圆比例。 + public static float JudgeAreaLinearScale = 0.866f; + public static float MinimumDirectionalSpeed = 30f; + public static float DirectionPersistenceDuration = 0.15f; + public static float MaximumSampleInterval = 0.10f; + public static float MaximumSampleDistanceRatio = 0.25f; + public static float MaximumInputSampleExtrapolation = 0.05f; + public float areaRadius; - private static float CurrentScreenRatio => Screen.height / 1080f; // 以1080p为基准的屏幕缩放比例 + private ScreenSample _previousSample; + private ScreenSample _currentSample; + private bool _hasPreviousSample; + private bool _hasCurrentSample; + private Vector2 _lastStableDirection; + private float _lastStableDirectionSongTime; + private bool _hasStableDirection; + + private static float CurrentScreenRatio => Mathf.Max(Screen.height / 1080f, 0.0001f); + + private struct ScreenSample + { + public float songTime; + public Vector2 position; + + public ScreenSample(float songTime, Vector2 position) + { + this.songTime = songTime; + this.position = position; + } + } #endregion #region [初始化] Initialization @@ -29,26 +58,97 @@ namespace Ichni.RhythmGame public override void UpdateJudge() { - if ((note is not Hold && note.isFirstJudged)||(note is Hold && note.isFinalJudged)) return; - Vector2 noteScreenPosition = note.noteScreenPosition; - RectTransform canvasRect = GameManager.Instance.judgeHintCanvas.GetComponent(); - if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, noteScreenPosition, null, out Vector2 uiPosition)) + if (judgeHintImage == null || + (note is not Hold && note.isFirstJudged) || + (note is Hold && note.isFinalJudged)) return; + + GetJudgeGeometry(CoreServices.TimeProvider.SongTime, out Vector2 center, out Vector2 majorDirection, + out float majorRadius, out float minorRadius); + Canvas canvas = GameManager.Instance.judgeHintCanvas; + RectTransform canvasRect = canvas.GetComponent(); + Camera eventCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera; + Vector2 minorDirection = new Vector2(-majorDirection.y, majorDirection.x); + + if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, center, eventCamera, + out Vector2 uiPosition) && + RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, + center + majorDirection * majorRadius, eventCamera, out Vector2 uiMajorPoint) && + RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, + center + minorDirection * minorRadius, eventCamera, out Vector2 uiMinorPoint)) { + Vector2 uiMajorAxis = uiMajorPoint - uiPosition; + Vector2 uiMinorAxis = uiMinorPoint - uiPosition; judgeHintImage.anchoredPosition = uiPosition; - judgeHintImage.sizeDelta = new Vector2(areaRadius * 2 * ellipseXMultiplier, areaRadius * 2 * ellipseYMultiplier) * CurrentScreenRatio; + judgeHintImage.sizeDelta = new Vector2(uiMajorAxis.magnitude * 2f, uiMinorAxis.magnitude * 2f); + judgeHintImage.localRotation = Quaternion.Euler(0f, 0f, + Mathf.Atan2(uiMajorAxis.y, uiMajorAxis.x) * Mathf.Rad2Deg); } } - public override bool CheckJudgeAvailability(InputUnit inputUnit) + public override void ResetSpatialState() { - Vector2 inputScreenPosition = inputUnit.inputPosition; - Vector2 noteScreenPosition = note.noteScreenPosition != Vector2.zero ? note.noteScreenPosition : note.GetScreenPosition(); - float scaledBaseRadius = areaRadius * CurrentScreenRatio; - - float dx = Mathf.Abs(inputScreenPosition.x - noteScreenPosition.x) / ellipseXMultiplier; - float dy = Mathf.Abs(inputScreenPosition.y - noteScreenPosition.y) / ellipseYMultiplier; + _previousSample = default; + _currentSample = default; + _hasPreviousSample = false; + _hasCurrentSample = false; + _lastStableDirection = Vector2.zero; + _lastStableDirectionSongTime = 0f; + _hasStableDirection = false; + } - if ((dx * dx) + (dy * dy) <= (scaledBaseRadius * scaledBaseRadius)) + public override void UpdateSpatialState(float songTime, Vector2 screenPosition) + { + if (!IsFinite(songTime) || !IsFinite(screenPosition)) return; + + ScreenSample newSample = new ScreenSample(songTime, screenPosition); + if (!_hasCurrentSample) + { + _currentSample = newSample; + _hasCurrentSample = true; + return; + } + + if (Mathf.Approximately(songTime, _currentSample.songTime)) + { + _currentSample = newSample; + return; + } + + if (!IsValidTransition(_currentSample, newSample)) + { + _currentSample = newSample; + _hasPreviousSample = false; + _hasStableDirection = false; + return; + } + + _previousSample = _currentSample; + _currentSample = newSample; + _hasPreviousSample = true; + + Vector2 displacement = _currentSample.position - _previousSample.position; + float duration = _currentSample.songTime - _previousSample.songTime; + float referenceSpeed = displacement.magnitude / CurrentScreenRatio / duration; + if (displacement.sqrMagnitude > Mathf.Epsilon && referenceSpeed >= MinimumDirectionalSpeed) + { + _lastStableDirection = displacement.normalized; + _lastStableDirectionSongTime = _currentSample.songTime; + _hasStableDirection = true; + } + } + + public override bool CheckJudgeAvailability(InputJudgeContext context) + { + InputUnit inputUnit = context.InputUnit; + GetJudgeGeometry(context.InputSongTime, out Vector2 center, out Vector2 majorDirection, + out float majorRadius, out float minorRadius); + + Vector2 inputOffset = context.InputPosition - center; + Vector2 minorDirection = new Vector2(-majorDirection.y, majorDirection.x); + float along = Vector2.Dot(inputOffset, majorDirection) / Mathf.Max(majorRadius, Mathf.Epsilon); + float across = Vector2.Dot(inputOffset, minorDirection) / Mathf.Max(minorRadius, Mathf.Epsilon); + + if ((along * along) + (across * across) <= 1f) { if (inputUnit is InputUnitSwipe swipe && note is Flick flick) { @@ -59,8 +159,95 @@ namespace Ichni.RhythmGame return false; } + + public Vector2 GetJudgeCenter(float songTime) + { + return ResolveCenter(songTime); + } + + private void GetJudgeGeometry(float songTime, out Vector2 center, out Vector2 majorDirection, + out float majorRadius, out float minorRadius) + { + center = ResolveCenter(songTime); + float scaledBaseRadius = Mathf.Max(areaRadius, 0f) * CurrentScreenRatio * JudgeAreaLinearScale; + + if (TryResolveDirection(songTime, out majorDirection)) + { + majorRadius = scaledBaseRadius * DirectionalMajorAxisMultiplier; + minorRadius = scaledBaseRadius * DirectionalMinorAxisMultiplier; + return; + } + + majorDirection = Vector2.right; + majorRadius = scaledBaseRadius * FallbackCircleMultiplier; + minorRadius = majorRadius; + } + + private Vector2 ResolveCenter(float songTime) + { + if (!_hasCurrentSample) + { + return note.noteScreenPosition != Vector2.zero + ? note.noteScreenPosition + : note.GetScreenPosition(); + } + + if (!_hasPreviousSample) return _currentSample.position; + if (songTime <= _previousSample.songTime) return _previousSample.position; + if (songTime >= _currentSample.songTime) return _currentSample.position; + + float interpolation = Mathf.InverseLerp(_previousSample.songTime, _currentSample.songTime, songTime); + return Vector2.LerpUnclamped(_previousSample.position, _currentSample.position, interpolation); + } + + private bool TryResolveDirection(float songTime, out Vector2 direction) + { + if (_hasPreviousSample && + songTime >= _previousSample.songTime - MaximumInputSampleExtrapolation && + songTime <= _currentSample.songTime + MaximumInputSampleExtrapolation) + { + Vector2 displacement = _currentSample.position - _previousSample.position; + float duration = _currentSample.songTime - _previousSample.songTime; + float referenceSpeed = displacement.magnitude / CurrentScreenRatio / duration; + if (displacement.sqrMagnitude > Mathf.Epsilon && referenceSpeed >= MinimumDirectionalSpeed) + { + direction = displacement.normalized; + return true; + } + } + + if (_hasStableDirection && + Mathf.Abs(songTime - _lastStableDirectionSongTime) <= DirectionPersistenceDuration) + { + direction = _lastStableDirection; + return true; + } + + direction = Vector2.right; + return false; + } + + private static bool IsValidTransition(ScreenSample from, ScreenSample to) + { + float duration = to.songTime - from.songTime; + if (duration <= Mathf.Epsilon || duration > MaximumSampleInterval) return false; + + float screenHeight = Mathf.Max(Screen.height, 1f); + float distanceRatio = Vector2.Distance(from.position, to.position) / screenHeight; + return distanceRatio <= MaximumSampleDistanceRatio; + } + + private static bool IsFinite(float value) + { + return !float.IsNaN(value) && !float.IsInfinity(value); + } + + private static bool IsFinite(Vector2 value) + { + return IsFinite(value.x) && IsFinite(value.y); + } #endregion } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Game/GameElements/Notes/NoteEffects/NoteEffectBase.cs b/Assets/Scripts/Game/GameElements/Notes/NoteEffects/NoteEffectBase.cs index ac4aef8c..0da2076c 100644 --- a/Assets/Scripts/Game/GameElements/Notes/NoteEffects/NoteEffectBase.cs +++ b/Assets/Scripts/Game/GameElements/Notes/NoteEffects/NoteEffectBase.cs @@ -12,7 +12,15 @@ namespace Ichni.RhythmGame public NoteBase note; public NoteVisualBase noteVisual; #endregion + + /// + /// 在 Note 缓存最终判定姿态之前执行。 + /// 只有确实需要调整打击位置的主题特效才应重写此方法,避免改变其他主题的 Note 表现。 + /// + public virtual void PrepareJudgedEffectPose() + { + } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Flick.cs b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Flick.cs index 017a456e..3c856777 100644 --- a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Flick.cs +++ b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Flick.cs @@ -51,8 +51,10 @@ namespace Ichni.RhythmGame #endregion #region [主循环阶段] Main Update - public override bool ManualUpdate(float currentSongTime) + public override void ManualPrepareForJudge(float currentSongTime) { + // 这里按“本帧 SongTime”把 Flick 放入可检查列表;真正消费 Swipe 时, + // 还必须用 InputJudgeContext.InputSongTime 再检查一次输入发生时间。 if (!isFirstJudged && !isDuringJudging && currentSongTime >= exactJudgeTime + judgeIntervals.beforeMiss.intervalStart && !GameManager.Instance.noteJudgeManager.checkingFlickList.Contains(this)) @@ -60,9 +62,14 @@ namespace Ichni.RhythmGame isDuringJudging = true; GameManager.Instance.noteJudgeManager.checkingFlickList.Add(this); } - + + base.ManualPrepareForJudge(currentSongTime); + } + + public override bool ManualResolveAfterJudge(float currentSongTime) + { ExecuteFinalJudge(currentSongTime); - return base.ManualUpdate(currentSongTime); + return base.ManualResolveAfterJudge(currentSongTime); } protected override void RemoveFromCheckingList() @@ -73,11 +80,16 @@ namespace Ichni.RhythmGame #endregion #region [核心判定逻辑] Judgement Logic - public bool CheckJudgeAvailability(InputUnitSwipe inputUnitSwipe) + public bool CheckJudgeAvailability(InputJudgeContext context) { + if (context.InputUnit is not InputUnitSwipe) return false; + + // 不能让窗口外的早期 Swipe 抢先把 Flick 以 Miss 状态移出 checkingFlickList。 + if (!IsWithinStartJudgeWindow(context.InputSongTime)) return false; + foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) { - if (!judgeUnit.CheckJudgeAvailability(inputUnitSwipe)) return false; + if (!judgeUnit.CheckJudgeAvailability(context)) return false; } return true; } @@ -150,9 +162,6 @@ namespace Ichni.RhythmGame } } #endregion - - #region [谱面功能] Beatmap Method - #endregion } } diff --git a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Hold.cs b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Hold.cs index a66f3a26..3de3dcd4 100644 --- a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Hold.cs +++ b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Hold.cs @@ -73,10 +73,8 @@ namespace Ichni.RhythmGame #endregion #region [主循环阶段] Main Update - public override bool ManualUpdate(float currentSongTime) + public override void ManualPrepareForJudge(float currentSongTime) { - if (!base.ManualUpdate(currentSongTime)) return false; - if (!isFirstJudged && !isDuringJudging && currentSongTime >= exactJudgeTime + judgeIntervals.beforeMiss.intervalStart && !GameManager.Instance.noteJudgeManager.checkingHoldList.Contains(this)) @@ -84,7 +82,44 @@ namespace Ichni.RhythmGame isDuringJudging = true; GameManager.Instance.noteJudgeManager.checkingHoldList.Add(this); } - + + // isHolding 会在上一帧末尾重置。已经开始的 Hold 必须保留上一帧收缩后的 + // Visual,供本帧 Touch 判定使用;否则 UpdateNoteInTrack 会先把头部展开回起点。 + if (isFirstJudged && !isFinalJudged) + { + UpdateJudgeSpatialState(currentSongTime); + SetJudgeArea(); + return; + } + + base.ManualPrepareForJudge(currentSongTime); + } + + protected override void UpdateSpatialStateBeforeJudgeScreenSample(float currentSongTime) + { + if (!isFirstJudged && exactJudgeTime < currentSongTime) + { + SlowOffsetAfterExactJudgeTime(); + } + } + + protected override void UpdateJudgeSpatialState(float currentSongTime) + { + if (isDuringJudging) + { + UpdateJudgeScreenPosition(currentSongTime); + } + + if (perfectNoteScreenPosition == -Vector2.one && exactJudgeTime <= currentSongTime) + { + perfectNoteScreenPosition = noteScreenPosition; + } + } + + public override bool ManualResolveAfterJudge(float currentSongTime) + { + if (!base.ManualResolveAfterJudge(currentSongTime)) return false; + if (!GameManager.Instance.songPlayer.isUpdating || isFinalJudged) return true; if (SettingsManager.instance.settingsSaveData.autoPlay && isFirstJudged) ExecuteProcessJudge(); @@ -98,22 +133,15 @@ namespace Ichni.RhythmGame { bufferTimer -= Time.deltaTime; } - + + // Touch 判定已经在本帧设置 isHolding。现在使用最新 holdingTime 收缩 Hold, + // 再采样实际头部位置,保证下一帧判定区域与 Visual 保持一致。 if (isOnTrack) UpdateNoteInTrack(currentSongTime); - - if (isDuringJudging) noteScreenPosition = GetScreenPosition(); - + if (isDuringJudging) UpdateJudgeScreenPosition(currentSongTime); SetJudgeArea(); - - if (!isFirstJudged && exactJudgeTime < currentSongTime) SlowOffsetAfterExactJudgeTime(); - + UpdateHoldEffects(); - - if (SettingsManager.instance.settingsSaveData.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime) - { - ExecuteStartJudge(currentSongTime); - } - + CheckHoldLifeCycle(currentSongTime); return true; @@ -154,22 +182,18 @@ namespace Ichni.RhythmGame #endregion #region [核心判定逻辑] Judgement Logic - public bool CheckJudgeAvailability(InputUnitTap inputUnitTap) + public bool CheckJudgeAvailability(InputJudgeContext context) { - if (isFirstJudged) return false; + if (context.InputUnit is InputUnitTap && isFirstJudged) return false; + if (context.InputUnit is InputUnitTouch && !isFirstJudged) return false; + if (context.InputUnit is not InputUnitTap && context.InputUnit is not InputUnitTouch) return false; + + // 只限制 Hold 头部的 Tap;起判成功后的 Touch 需要在整段 Hold 生命周期持续生效。 + if (context.InputUnit is InputUnitTap && !IsWithinStartJudgeWindow(context.InputSongTime)) return false; + foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) { - if (!judgeUnit.CheckJudgeAvailability(inputUnitTap)) return false; - } - return true; - } - - public bool CheckJudgeAvailability(InputUnitTouch inputUnitTouch) - { - if (!isFirstJudged) return false; - foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) - { - if (!judgeUnit.CheckJudgeAvailability(inputUnitTouch)) return false; + if (!judgeUnit.CheckJudgeAvailability(context)) return false; } return true; } diff --git a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/NoteBase.cs b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/NoteBase.cs index 16dad60d..105d0576 100644 --- a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/NoteBase.cs +++ b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/NoteBase.cs @@ -52,6 +52,8 @@ namespace Ichni.RhythmGame protected NoteJudgeType judgedType; protected bool isJudgedAndDestroying; protected float destroyTimer; + protected Pose judgedEffectPose; + protected bool hasJudgedEffectPose; // 用于记录是否离开屏幕的最后一帧有效坐标 protected Vector2 lastValidScreenPosition = -Vector2.one; @@ -75,8 +77,19 @@ namespace Ichni.RhythmGame missEffects = GetEffectListSafe("Miss"); afterJudgeEffects = GetEffectListSafe("AfterJudge"); + noteScreenPosition = Vector2.zero; perfectNoteScreenPosition = -Vector2.one; lastValidScreenPosition = -Vector2.one; // 初始化 + judgedEffectPose = default; + hasJudgedEffectPose = false; + + if (NoteJudgeSubmodule?.judgeUnitList != null) + { + for (int i = 0; i < NoteJudgeSubmodule.judgeUnitList.Count; i++) + { + NoteJudgeSubmodule.judgeUnitList[i].ResetSpatialState(); + } + } float beyondTime = 0f; @@ -147,7 +160,37 @@ namespace Ichni.RhythmGame #endregion #region [轮询更新] Main Update & Visual Calculate - public virtual bool ManualUpdate(float currentSongTime) + public virtual void ManualPrepareForJudge(float currentSongTime) + { + if (isJudgedAndDestroying || isFinalJudged) return; + + // 判定前必须先完成本帧的轨迹、屏幕坐标与判定区域采样。 + UpdateNoteInTrack(currentSongTime); + UpdateSpatialStateBeforeJudgeScreenSample(currentSongTime); + UpdateJudgeSpatialState(currentSongTime); + SetJudgeArea(); + } + + protected virtual void UpdateSpatialStateBeforeJudgeScreenSample(float currentSongTime) + { + } + + protected virtual void UpdateJudgeSpatialState(float currentSongTime) + { + if (perfectNoteScreenPosition != -Vector2.one) return; + + if (isDuringJudging) + { + UpdateJudgeScreenPosition(currentSongTime); + } + + if (exactJudgeTime <= currentSongTime) + { + perfectNoteScreenPosition = noteScreenPosition; + } + } + + public virtual bool ManualResolveAfterJudge(float currentSongTime) { // 若被 Judge 判定击中进入到特效摧毁倒计时 if (isJudgedAndDestroying) @@ -168,21 +211,6 @@ namespace Ichni.RhythmGame if (isFinalJudged) return true; - // 1. 轨迹更新 - UpdateNoteInTrack(currentSongTime); - // 2. 屏幕坐标系计算缓存 - if (perfectNoteScreenPosition == -Vector2.one) - { - if (isDuringJudging) - { - noteScreenPosition = GetScreenPosition(); - } - if (exactJudgeTime <= currentSongTime) // 代替 GameManager.Instance.songTime - { - perfectNoteScreenPosition = noteScreenPosition; - } - } - SetJudgeArea(); if (generateEffects != null) { for (int i = 0; i < generateEffects.Count; i++) @@ -236,6 +264,17 @@ namespace Ichni.RhythmGame return new Vector2(screenPoint.x, screenPoint.y); } } + + protected void UpdateJudgeScreenPosition(float songTime) + { + noteScreenPosition = GetScreenPosition(); + if (NoteJudgeSubmodule?.judgeUnitList == null) return; + + for (int i = 0; i < NoteJudgeSubmodule.judgeUnitList.Count; i++) + { + NoteJudgeSubmodule.judgeUnitList[i].UpdateSpatialState(songTime, noteScreenPosition); + } + } protected virtual void SetJudgeArea() { @@ -332,12 +371,77 @@ namespace Ichni.RhythmGame return judgeIntervals.GetNoteJudgeType(timeDifference); } + public NoteJudgeType PreviewStartJudgeType(float triggerTime) + { + // Keep candidate resolution pure. The actual Note state changes only after one candidate is selected. + return GetStartJudgeType(triggerTime - exactJudgeTime); + } + + /// + /// 判断输入的真实发生时间是否位于该 Note 的起始判定窗口内。 + /// checking 列表使用本帧 SongTime 维护,而输入时间可能比本帧时间早几毫秒; + /// 因此必须单独过滤窗口外输入,不能把它当成可 fallback 的 Miss 候选。 + /// + public bool IsWithinStartJudgeWindow(float inputSongTime) + { + float timeDifference = inputSongTime - exactJudgeTime; + return timeDifference >= judgeIntervals.beforeMiss.intervalStart && + timeDifference <= judgeIntervals.afterMiss; + } + + /// + /// 读取最终判定发生当帧缓存的 JudgeEffect 世界姿态。 + /// 特效应使用该姿态,而不是在延迟执行时重新读取 SongTime 或已经切换后的轨道/摄像机状态。 + /// + public bool TryGetJudgedEffectPose(out Pose pose) + { + pose = judgedEffectPose; + return hasJudgedEffectPose; + } + + private void CaptureJudgedEffectPose() + { + hasJudgedEffectPose = false; + + Transform judgeEffectTransform = noteVisual?.judgeEffect?.transform; + if (judgeEffectTransform == null) return; + + judgedEffectPose = new Pose(judgeEffectTransform.position, judgeEffectTransform.rotation); + hasJudgedEffectPose = true; + } + + private void PrepareJudgedEffectPose(NoteJudgeType judgeType) + { + List judgedEffects = judgeType switch + { + NoteJudgeType.Perfect => perfectEffects, + NoteJudgeType.Good => goodEffects, + NoteJudgeType.Bad => badEffects, + NoteJudgeType.Miss => missEffects, + _ => null + }; + + if (judgedEffects == null) return; + + for (int i = 0; i < judgedEffects.Count; i++) + { + if (judgedEffects[i] is NoteEffectBase noteEffect) + { + noteEffect.PrepareJudgedEffectPose(); + } + } + } + protected virtual void ExecuteJudge(NoteJudgeType judgeType, float triggerTime) { isDuringJudging = false; isFirstJudged = true; judgedTriggerTime = triggerTime; judgedType = judgeType; + + // 判定之后轨道、摄像机或 Note 层级都可能立即变化,必须先冻结本次打击特效的世界姿态。 + PrepareJudgedEffectPose(judgeType); + CaptureJudgedEffectPose(); switch (judgeType) { diff --git a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Stay.cs b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Stay.cs index 7e8e515e..9ca351ed 100644 --- a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Stay.cs +++ b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Stay.cs @@ -48,7 +48,7 @@ namespace Ichni.RhythmGame #endregion #region [主循环阶段] Main Update - public override bool ManualUpdate(float currentSongTime) + public override void ManualPrepareForJudge(float currentSongTime) { if (!isFirstJudged && !isDuringJudging && currentSongTime >= exactJudgeTime + judgeIntervals.beforeMiss.intervalStart && @@ -58,8 +58,13 @@ namespace Ichni.RhythmGame GameManager.Instance.noteJudgeManager.checkingStayList.Add(this); } + base.ManualPrepareForJudge(currentSongTime); + } + + public override bool ManualResolveAfterJudge(float currentSongTime) + { ExecuteFinalJudge(currentSongTime); - return base.ManualUpdate(currentSongTime); + return base.ManualResolveAfterJudge(currentSongTime); } protected override void RemoveFromCheckingList() @@ -70,11 +75,14 @@ namespace Ichni.RhythmGame #endregion #region [核心判定逻辑] Judgement Logic - public bool CheckJudgeAvailability(InputUnitTouch inputUnitTouch) + public bool CheckJudgeAvailability(InputJudgeContext context) { + if (context.InputUnit is not InputUnitTouch) return false; + if (!IsWithinStartJudgeWindow(context.InputSongTime)) return false; + foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) { - if (!judgeUnit.CheckJudgeAvailability(inputUnitTouch)) return false; + if (!judgeUnit.CheckJudgeAvailability(context)) return false; } return true; } diff --git a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Tap.cs b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Tap.cs index bee68e67..290d744a 100644 --- a/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Tap.cs +++ b/Assets/Scripts/Game/GameElements/Notes/NoteObjects/Tap.cs @@ -43,7 +43,7 @@ namespace Ichni.RhythmGame #endregion #region [主循环阶段] Main Update - public override bool ManualUpdate(float currentSongTime) + public override void ManualPrepareForJudge(float currentSongTime) { if (!isFirstJudged && !isDuringJudging && currentSongTime >= exactJudgeTime + judgeIntervals.beforeMiss.intervalStart && @@ -52,7 +52,8 @@ namespace Ichni.RhythmGame isDuringJudging = true; GameManager.Instance.noteJudgeManager.checkingTapList.Add(this); } - return base.ManualUpdate(currentSongTime); + + base.ManualPrepareForJudge(currentSongTime); } protected override void RemoveFromCheckingList() @@ -63,11 +64,14 @@ namespace Ichni.RhythmGame #endregion #region [核心判定逻辑] Judgement Logic - public bool CheckJudgeAvailability(InputUnitTap inputUnitTap) + public bool CheckJudgeAvailability(InputJudgeContext context) { + if (context.InputUnit is not InputUnitTap) return false; + if (!IsWithinStartJudgeWindow(context.InputSongTime)) return false; + foreach (var judgeUnit in NoteJudgeSubmodule.judgeUnitList) { - if (!judgeUnit.CheckJudgeAvailability(inputUnitTap)) return false; + if (!judgeUnit.CheckJudgeAvailability(context)) return false; } return true; } diff --git a/Assets/Scripts/Manager/ElementUpdateScheduler.cs b/Assets/Scripts/Manager/ElementUpdateScheduler.cs index 4cb807e5..806514e5 100644 --- a/Assets/Scripts/Manager/ElementUpdateScheduler.cs +++ b/Assets/Scripts/Manager/ElementUpdateScheduler.cs @@ -23,7 +23,7 @@ namespace Ichni /// Phase 4 TrackCore — 更新轨道时间、裁剪区间(Track 通过 IScheduledElement 注册) /// Phase 5 TrackFollower — CrossTrackPoint / HeadPoint / PercentPoint /// [TrackManager.ManualLateUpdate — 清除 refreshedThisFrame 标记] - /// Phase 6 Note — NoteManager 内部驱动(激活 + 更新 + 重置) + /// Phase 6 Note — Note Prepare → Input Judge → Note Resolve → Hold Reset /// Phase 7 Effect — ParticleEmitter / TimeEffectsCollection / ParticleTracker / DTM 元素 / LookAt 旋转覆盖 /// Phase 8 Misc — SkyboxSubsetter / JudgeTrigger / LowPriorityActions /// @@ -198,7 +198,11 @@ namespace Ichni public void TickEarly(float songTime) { _isUpdating = GameManager.Instance.songPlayer.isUpdating; - if (!_isUpdating) return; + if (!_isUpdating) + { + GameManager.Instance.noteJudgeManager.DiscardPendingInputs(); + return; + } _cachedSongTime = songTime; @@ -257,11 +261,13 @@ namespace Ichni GameManager.Instance.trackManager.ManualLateUpdate(songTime); } - // ─── Phase 6: Note(由 NoteManager 内部驱动)───────────────────────── + // ─── Phase 6: Note(空间准备 → 输入判定 → 生命周期结算 → Hold 重置)── using (PhaseMarkers[PhaseIndex(UpdatePhase.Note)].Auto()) { TickPhaseElements(UpdatePhase.Note, songTime); - GameManager.Instance.noteManager.ManualUpdate(songTime); + GameManager.Instance.noteManager.ManualPrepareForJudge(songTime); + GameManager.Instance.noteJudgeManager.ProcessPendingInputs(songTime); + GameManager.Instance.noteManager.ManualResolveAfterJudge(songTime); GameManager.Instance.noteManager.ManualLateUpdate(songTime); } diff --git a/Assets/Scripts/Manager/GameInputManager.cs b/Assets/Scripts/Manager/GameInputManager.cs index ed26f6fc..ed37d4cd 100644 --- a/Assets/Scripts/Manager/GameInputManager.cs +++ b/Assets/Scripts/Manager/GameInputManager.cs @@ -10,6 +10,7 @@ using Sirenix.OdinInspector; using TMPro; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.EnhancedTouch; +using UnityEngine.InputSystem.LowLevel; using UnityEngine.UI; using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; using TouchPhase = UnityEngine.InputSystem.TouchPhase; @@ -27,7 +28,7 @@ namespace Ichni.RhythmGame // ===================================================================== [Header("划动设置 (Swipe Settings)")] [Tooltip("识别为划动的最小移动距离(像素)")] [SerializeField] - private float minSwipeDistance = 100f; + private float minSwipeDistance = 50f; [SerializeField] private float swipeAngleThreshold = 1f; @@ -42,12 +43,13 @@ namespace Ichni.RhythmGame { public int TouchId; public Vector2 StartPosition; - public float StartTime; + public Vector2 LastPosition; public Vector2 LastSwipeDirection = Vector2.zero; public bool isFirstSwipe = true; } - private readonly Dictionary _activeTouches = new Dictionary(); + private readonly Dictionary _activeTouches = new Dictionary(10); + private readonly Stack _touchStatePool = new Stack(10); // 为鼠标模拟专门设置一个固定的Touch ID private const int MOUSE_TOUCH_ID = 999; @@ -70,7 +72,20 @@ namespace Ichni.RhythmGame RegisterActionsInputs(); #else - Debug.Log("已启用真实触摸输入"); + Debug.Log("已启用真实触摸输入"); +#endif + } + + private void OnEnable() + { + // GameInputManager owns its EnhancedTouch dependency instead of relying on CwInput initialization. + if (!EnhancedTouchSupport.enabled) + { + EnhancedTouchSupport.Enable(); + } + +#if UNITY_EDITOR || UNITY_STANDALONE + SetDesktopInputActionsEnabled(Touchscreen.current == null); #endif } @@ -83,44 +98,64 @@ namespace Ichni.RhythmGame // 使用预处理指令区分平台 #if UNITY_EDITOR || UNITY_STANDALONE - HandleHolding(); + // Device Simulator and physical desktop touchscreens expose a Touchscreen device. + // In that case use the same EnhancedTouch path as mobile builds. + bool useTouchInput = Touchscreen.current != null; + SetDesktopInputActionsEnabled(!useTouchInput); + if (useTouchInput) + { + ProcessRealTouchInput(); + } + else + { + HandleHolding(); + } #else - ProcessRealTouchInput(); + ProcessRealTouchInput(); #endif } private void OnDisable() { -#if UNITY_EDITOR || UNITY_STANDALONE + foreach (TouchState state in _activeTouches.Values) + { + RecycleTouchState(state); + } + _activeTouches.Clear(); +#if UNITY_EDITOR || UNITY_STANDALONE + SetDesktopInputActionsEnabled(false); #else #endif } - private void OnTap(int id, Vector2 position) + private void OnTap(int id, Vector2 position, double inputEventTime) { if (SettingsManager.instance.settingsSaveData.debugMode) { GenerateTapMark(id, position); } - GameManager.Instance.noteJudgeManager.SetNewInputUnitTap(id, position); + GameManager.Instance.noteJudgeManager.SetNewInputUnitTap(id, position, inputEventTime); } - private void OnTouch(int id, Vector2 position) + private void OnTouch(int id, Vector2 position, double inputEventTime) { if (SettingsManager.instance.settingsSaveData.debugMode) { GenerateTouchMark(id, position); } - GameManager.Instance.noteJudgeManager.SetNewInputUnitTouch(id, position); + GameManager.Instance.noteJudgeManager.SetNewInputUnitTouch(id, position, inputEventTime); } - private void OnSwipe(int id, Vector2 position, bool isGeneric, bool isFirst, Vector2 direction) + private void OnSwipe(int id, Vector2 position, bool isGeneric, bool isFirst, Vector2 direction, + double inputEventTime, bool showDebugFeedback = true) { - if (SettingsManager.instance.settingsSaveData.debugMode) + // 此处只负责把已经识别出的 Swipe 记录为输入样本。白色/红色箭头是采集层反馈, + // 是否命中 Flick 要等 LateUpdate 中 Note 完成空间采样后再由 NoteJudgeManager 决定。 + if (showDebugFeedback && SettingsManager.instance.settingsSaveData.debugMode) { GenerateSwipeMark(id, position, isGeneric, isFirst, direction); @@ -128,7 +163,7 @@ namespace Ichni.RhythmGame else Debug.Log($"划动更新 - ID: {id}, 位置: {position}, 方向: {direction}"); } - GameManager.Instance.noteJudgeManager.SetNewInputUnitSwipe(id, position, isGeneric, isFirst, direction); + GameManager.Instance.noteJudgeManager.SetNewInputUnitSwipe(id, position, isGeneric, isFirst, direction, inputEventTime); } #if UNITY_EDITOR || UNITY_STANDALONE @@ -140,47 +175,48 @@ namespace Ichni.RhythmGame if (Mouse.current == null) return; Vector2 position = Mouse.current.position.ReadValue(); + double inputEventTime = InputState.currentTime; if (Mouse.current.leftButton.wasPressedThisFrame) { - ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Began, position); + ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Began, position, inputEventTime); } else if (Mouse.current.leftButton.isPressed) { // 如果鼠标位置有变化,则为Moved,否则为Stationary if (Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f) { - ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Moved, position); + ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Moved, position, inputEventTime); } else { - ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Stationary, position); + ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Stationary, position, inputEventTime); } } else if (Mouse.current.leftButton.wasReleasedThisFrame) { - ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Ended, position); + ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Ended, position, inputEventTime); } if (Mouse.current.rightButton.wasPressedThisFrame) { - ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Began, position); + ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Began, position, inputEventTime); } else if (Mouse.current.rightButton.isPressed) { // 如果鼠标位置有变化,则为Moved,否则为Stationary if (Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f) { - ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Moved, position); + ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Moved, position, inputEventTime); } else { - ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Stationary, position); + ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Stationary, position, inputEventTime); } } else if (Mouse.current.rightButton.wasReleasedThisFrame) { - ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Ended, position); + ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Ended, position, inputEventTime); } } #endif @@ -201,7 +237,7 @@ namespace Ichni.RhythmGame if (ctx.performed) { Vector2 inputPosition = new Vector2(-600 + Screen.width * 0.5f, 200f); - OnTap(0, inputPosition); + OnTap(0, inputPosition, ctx.time); holdingTouch0 = true; } }; @@ -211,7 +247,7 @@ namespace Ichni.RhythmGame if (ctx.performed) { Vector2 inputPosition = new Vector2(-200 + Screen.width * 0.5f, 200f); - OnTap(1, inputPosition); + OnTap(1, inputPosition, ctx.time); holdingTouch1 = true; } }; @@ -221,7 +257,7 @@ namespace Ichni.RhythmGame if (ctx.performed) { Vector2 inputPosition = new Vector2(200 + Screen.width * 0.5f, 200f); - OnTap(2, inputPosition); + OnTap(2, inputPosition, ctx.time); holdingTouch2 = true; } }; @@ -231,7 +267,7 @@ namespace Ichni.RhythmGame if (ctx.performed) { Vector2 inputPosition = new Vector2(600 + Screen.width * 0.5f, 200f); - OnTap(3, inputPosition); + OnTap(3, inputPosition, ctx.time); holdingTouch3 = true; } }; @@ -285,36 +321,52 @@ namespace Ichni.RhythmGame }; } + private void SetDesktopInputActionsEnabled(bool shouldEnable) + { + if (gameInput == null || gameInput.Game.enabled == shouldEnable) return; + + if (shouldEnable) + { + gameInput.Game.Enable(); + } + else + { + gameInput.Game.Disable(); + } + } + private void HandleHolding() { + double inputEventTime = InputState.currentTime; + if (holdingTouch0) { Vector2 inputPosition = new Vector2(-600 + Screen.width * 0.5f, 200f); - OnTouch(0, inputPosition); + OnTouch(0, inputPosition, inputEventTime); } if (holdingTouch1) { Vector2 inputPosition = new Vector2(-200 + Screen.width * 0.5f, 200f); - OnTouch(1, inputPosition); + OnTouch(1, inputPosition, inputEventTime); } if (holdingTouch2) { Vector2 inputPosition = new Vector2(200 + Screen.width * 0.5f, 200f); - OnTouch(2, inputPosition); + OnTouch(2, inputPosition, inputEventTime); } if (holdingTouch3) { Vector2 inputPosition = new Vector2(600 + Screen.width * 0.5f, 200f); - OnTouch(3, inputPosition); + OnTouch(3, inputPosition, inputEventTime); } if (holdingSwipe0) { Vector2 inputPosition = new Vector2(Screen.width * 0.5f, 200f); - OnSwipe(0, inputPosition, true, false, Vector2.zero); + OnSwipe(0, inputPosition, true, false, Vector2.zero, inputEventTime); } } #endif @@ -328,10 +380,14 @@ namespace Ichni.RhythmGame foreach (Touch touch in Touch.activeTouches) { + double inputEventTime = touch.phase == TouchPhase.Stationary + ? InputState.currentTime + : touch.time; ProcessInputEvent( touch.touchId, touch.phase, - touch.screenPosition + touch.screenPosition, + inputEventTime ); } } @@ -339,29 +395,31 @@ namespace Ichni.RhythmGame /// /// 所有输入事件的核心处理函数,无论是真实触摸还是鼠标模拟都会调用它。 /// - private void ProcessInputEvent(int touchId, TouchPhase phase, Vector2 position) + private void ProcessInputEvent(int touchId, TouchPhase phase, Vector2 position, double inputEventTime) { switch (phase) { case TouchPhase.Began: - var newState = new TouchState + if (_activeTouches.TryGetValue(touchId, out TouchState existingState)) { - TouchId = touchId, - StartPosition = position, - StartTime = CoreServices.TimeProvider.SongTime, - LastSwipeDirection = Vector2.zero, - isFirstSwipe = true - }; + RecycleTouchState(existingState); + } + + TouchState newState = GetTouchState(touchId, position); _activeTouches[touchId] = newState; - OnTap(touchId, position); - OnTouch(touchId, position); + OnTap(touchId, position, inputEventTime); + OnTouch(touchId, position, inputEventTime); break; case TouchPhase.Moved: if (_activeTouches.TryGetValue(touchId, out TouchState movedState)) { - OnTouch(touchId, position); - DetectSwipe(movedState, position); + OnTouch(touchId, position, inputEventTime); + bool emittedNewSwipeSegment = DetectSwipe(movedState, position, inputEventTime); + if (!emittedNewSwipeSegment && !movedState.isFirstSwipe) + { + EmitContinuingSwipe(movedState, position, inputEventTime, true); + } } break; @@ -369,15 +427,21 @@ namespace Ichni.RhythmGame case TouchPhase.Stationary: if (_activeTouches.TryGetValue(touchId, out TouchState stationaryState)) { - OnTouch(touchId, position); + OnTouch(touchId, position, inputEventTime); + if (!stationaryState.isFirstSwipe) + { + // Stationary keeps the Swipe available for judgment, but it is not a new movement sample. + EmitContinuingSwipe(stationaryState, position, inputEventTime, false); + } } break; case TouchPhase.Ended: - if (_activeTouches.ContainsKey(touchId)) + if (_activeTouches.TryGetValue(touchId, out TouchState endedState)) { _activeTouches.Remove(touchId); + RecycleTouchState(endedState); if (SettingsManager.instance.settingsSaveData.debugMode) { GenerateEndMark(position); @@ -387,9 +451,10 @@ namespace Ichni.RhythmGame break; case TouchPhase.Canceled: - if (_activeTouches.ContainsKey(touchId)) + if (_activeTouches.TryGetValue(touchId, out TouchState canceledState)) { _activeTouches.Remove(touchId); + RecycleTouchState(canceledState); if (SettingsManager.instance.settingsSaveData.debugMode) { GenerateCanceledMark(position); @@ -401,24 +466,67 @@ namespace Ichni.RhythmGame } /// - /// 检测划动逻辑 (无需修改) + /// 检测新的划动段。首次超过阈值后,该触点会保持 Swipe 状态直到 Ended/Canceled。 /// - private void DetectSwipe(TouchState state, Vector2 currentPosition) + private bool DetectSwipe(TouchState state, Vector2 currentPosition, double inputEventTime) { - Vector2 swipeVector = currentPosition - state.StartPosition; - if (swipeVector.magnitude < minSwipeDistance) return; + Vector2 movementDelta = currentPosition - state.LastPosition; + state.LastPosition = currentPosition; - Vector2 direction = swipeVector.normalized; - - // 检查是否是新的划动方向 - if (Vector2.Dot(direction, state.LastSwipeDirection) <= swipeAngleThreshold) + if (state.isFirstSwipe) { - OnSwipe(state.TouchId, state.StartPosition, false, state.isFirstSwipe, direction); + Vector2 initialSwipeVector = currentPosition - state.StartPosition; + if (initialSwipeVector.sqrMagnitude < minSwipeDistance * minSwipeDistance) return false; + + Vector2 direction = initialSwipeVector.normalized; + OnSwipe(state.TouchId, currentPosition, false, true, direction, inputEventTime); state.LastSwipeDirection = direction; - state.StartPosition = currentPosition; - state.StartTime = CoreServices.TimeProvider.SongTime; state.isFirstSwipe = false; + return true; } + + if (movementDelta.sqrMagnitude > Mathf.Epsilon) + { + Vector2 direction = movementDelta.normalized; + if (Vector2.Dot(direction, state.LastSwipeDirection) <= swipeAngleThreshold) + { + state.LastSwipeDirection = direction; + } + } + + // Swipe 激活后,每个移动采样都使用当前触点位置;不再等待新的距离阈值。 + EmitContinuingSwipe(state, currentPosition, inputEventTime, true); + return true; + } + + private void EmitContinuingSwipe(TouchState state, Vector2 currentPosition, double inputEventTime, + bool showDebugFeedback) + { + OnSwipe(state.TouchId, currentPosition, false, false, state.LastSwipeDirection, inputEventTime, + showDebugFeedback); + } + + private TouchState GetTouchState(int touchId, Vector2 startPosition) + { + TouchState state = _touchStatePool.Count > 0 ? _touchStatePool.Pop() : new TouchState(); + state.TouchId = touchId; + state.StartPosition = startPosition; + state.LastPosition = startPosition; + state.LastSwipeDirection = Vector2.zero; + state.isFirstSwipe = true; + return state; + } + + private void RecycleTouchState(TouchState state) + { + if (state == null) return; + + state.TouchId = 0; + state.StartPosition = Vector2.zero; + state.LastPosition = Vector2.zero; + state.LastSwipeDirection = Vector2.zero; + state.isFirstSwipe = true; + _touchStatePool.Push(state); } private void GenerateTapMark(int id, Vector2 pos) diff --git a/Assets/Scripts/Manager/GameInputManager.cs.meta b/Assets/Scripts/Manager/GameInputManager.cs.meta index 9460bc44..48ac3488 100644 --- a/Assets/Scripts/Manager/GameInputManager.cs.meta +++ b/Assets/Scripts/Manager/GameInputManager.cs.meta @@ -4,7 +4,7 @@ MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] - executionOrder: -13 + executionOrder: 15 icon: {instanceID: 0} userData: assetBundleName: diff --git a/Assets/Scripts/Manager/NoteJudgeManager.cs b/Assets/Scripts/Manager/NoteJudgeManager.cs index d1e8244b..de20a3e9 100644 --- a/Assets/Scripts/Manager/NoteJudgeManager.cs +++ b/Assets/Scripts/Manager/NoteJudgeManager.cs @@ -1,9 +1,9 @@ -using System.Collections; using System.Collections.Generic; -using System.Linq; using Ichni.RhythmGame; using Sirenix.OdinInspector; using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; namespace Ichni { @@ -18,224 +18,99 @@ namespace Ichni public List inputUnitTouchList; public List inputUnitSwipeList; - private List _availableTapsBuffer = new List(30); + private readonly List _startCandidatesBuffer = new List(40); + private readonly NoteCandidateComparer _candidateComparer = new NoteCandidateComparer(); private List _availableHoldsBuffer = new List(10); - private List _availableFlicksBuffer = new List(10); private List _availableStaysBuffer = new List(30); + private readonly Stack _tapInputPool = new Stack(10); + private readonly Stack _touchInputPool = new Stack(10); + private readonly Stack _swipeInputPool = new Stack(10); + private readonly InputUnitSwipe _assumedSwipeInput = new InputUnitSwipe(); - private List _minTapsBuffer = new List(5); - private List _minHoldsBuffer = new List(5); - private List _minFlicksBuffer = new List(5); - - private InputUnitSwipe _cachedAssumedSwipe = new InputUnitSwipe(0, Vector2.zero, true, true, Vector2.zero); - private void Start() { checkingTapList = new List(); checkingStayList = new List(); checkingHoldList = new List(); checkingFlickList = new List(); - inputUnitTapList = new List(); - inputUnitTouchList = new List(); - inputUnitSwipeList = new List(); + inputUnitTapList = new List(10); + inputUnitTouchList = new List(10); + inputUnitSwipeList = new List(10); } - public void Update() + public void ProcessPendingInputs(float songTime) { if (!GameManager.Instance.songPlayer.isPlaying) { + ClearInputUnits(); return; } - - float songTime = CoreServices.TimeProvider.SongTime; - + + double inputTime = InputState.currentTime; + + // GameInputManager 在 Update 中只入队;这里位于 LateUpdate 的 Note 空间准备之后, + // 使用同一帧已经更新好的判定区域消费 Swipe。 foreach (var swipe in inputUnitSwipeList) { - _availableFlicksBuffer.Clear(); + InputJudgeContext inputContext = CreateInputJudgeContext(swipe, songTime, inputTime); + _startCandidatesBuffer.Clear(); foreach (Flick flick in checkingFlickList) { - if (flick.CheckJudgeAvailability(swipe)) + if (flick.CheckJudgeAvailability(inputContext)) { - _availableFlicksBuffer.Add(flick); - } - } - - if (_availableFlicksBuffer.Count > 0) - { - if (swipe.isFirst) // 原 tapSwipeList 逻辑 - { - GetAllMinNotes(_availableFlicksBuffer, _minFlicksBuffer); // 提醒:内部也需防 new List - if (_minFlicksBuffer.Count == 1) _minFlicksBuffer[0].ExecuteStartJudge(songTime); - else if (_minFlicksBuffer.Count > 1) GetNearestNote(_minFlicksBuffer, swipe.inputPosition).ExecuteTapJudge(songTime); - } - else // 原 holdSwipeList 逻辑 - { - _availableFlicksBuffer.Sort(); - _availableFlicksBuffer[0].ExecuteStartJudge(songTime); + _startCandidatesBuffer.Add(flick); } } + + ExecuteSwipeCandidates(swipe, inputContext); } - /*foreach (InputUnitSwipe tapSwipe in tapSwipeList) - { - List availableFlicks = new List(); - - foreach (Flick flick in checkingFlickList) - { - if (flick.CheckJudgeAvailability(tapSwipe)) - { - availableFlicks.Add(flick); - } - } - - List minFlicks = GetAllMinNotes(availableFlicks); - - if (minFlicks.Count == 1) - { - minFlicks[0].ExecuteStartJudge(); - } - else if (minFlicks.Count > 1) - { - GetNearestNote(minFlicks, tapSwipe.inputPosition).ExecuteTapJudge(); - } - } - - foreach (InputUnitSwipe inputUnitSwipe in holdSwipeList) - { - List availableFlicks = new List(); - - foreach (Flick flick in checkingFlickList) - { - if (flick.CheckJudgeAvailability(inputUnitSwipe)) - { - availableFlicks.Add(flick); - } - } - - if (availableFlicks.Count > 0) - { - availableFlicks.Sort(); - Flick closestFlick = availableFlicks[0]; - closestFlick.ExecuteStartJudge(); - } - }*/ - foreach (InputUnitTap inputUnitTap in inputUnitTapList) { - _availableTapsBuffer.Clear(); - _availableHoldsBuffer.Clear(); + InputJudgeContext inputContext = CreateInputJudgeContext(inputUnitTap, songTime, inputTime); + _startCandidatesBuffer.Clear(); foreach (Tap tap in checkingTapList) { - if (tap.CheckJudgeAvailability(inputUnitTap)) _availableTapsBuffer.Add(tap); + if (tap.CheckJudgeAvailability(inputContext)) _startCandidatesBuffer.Add(tap); } foreach (Hold hold in checkingHoldList) { - if (hold.CheckJudgeAvailability(inputUnitTap)) _availableHoldsBuffer.Add(hold); + if (hold.CheckJudgeAvailability(inputContext)) _startCandidatesBuffer.Add(hold); } - bool haveTap = _availableTapsBuffer.Count > 0; - bool haveHold = _availableHoldsBuffer.Count > 0; + NoteBase selectedCandidate = SelectStartCandidate(_startCandidatesBuffer, inputContext, + fallbackToFirst: true); + if (selectedCandidate == null) continue; - Flick closestFlick = null; - InputUnitSwipe assumedSwipe = new InputUnitSwipe(inputUnitTap.fingerId, inputUnitTap.inputPosition, true, true, Vector2.zero); + _assumedSwipeInput.Set(inputUnitTap.fingerId, inputUnitTap.inputPosition, + true, true, Vector2.zero, inputUnitTap.inputEventTime); + InputJudgeContext assumedSwipeContext = inputContext.WithInputUnit(_assumedSwipeInput); + _startCandidatesBuffer.Clear(); foreach (Flick flick in checkingFlickList) { - if (flick.CheckJudgeAvailability(assumedSwipe)) + if (flick.CheckJudgeAvailability(assumedSwipeContext)) { - if (closestFlick == null || flick.exactJudgeTime < closestFlick.exactJudgeTime) - { - closestFlick = flick; - } + _startCandidatesBuffer.Add(flick); } } - if (haveHold && haveTap) - { - GetAllMinNotes(_availableHoldsBuffer, _minHoldsBuffer); - GetAllMinNotes(_availableTapsBuffer, _minTapsBuffer); - Hold closestHold = _minHoldsBuffer[0]; - Tap closestTap = _minTapsBuffer[0]; - bool holdBlockedByFlick = false; - bool tapBlockedByFlick = false; + Flick blockingFlick = SelectStartCandidate(_startCandidatesBuffer, assumedSwipeContext, + fallbackToFirst: false) as Flick; + if (blockingFlick != null && + blockingFlick.exactJudgeTime < selectedCandidate.exactJudgeTime - 0.05f) continue; - if (closestFlick != null) - { - if (closestFlick.exactJudgeTime < closestHold.exactJudgeTime - 0.05f) - { - holdBlockedByFlick = true; - } - - if (closestFlick.exactJudgeTime < closestTap.exactJudgeTime - 0.05f) - { - tapBlockedByFlick = true; - } - } - - if (!holdBlockedByFlick && closestHold.exactJudgeTime < closestTap.exactJudgeTime - 0.05f) - { - if (_minHoldsBuffer.Count == 1) - { - _minHoldsBuffer[0].ExecuteStartJudge(songTime); - } - else - { - GetNearestNote(_minHoldsBuffer, inputUnitTap.inputPosition).ExecuteStartJudge(songTime); - } - } - else if (!tapBlockedByFlick) - { - if (_minTapsBuffer.Count == 1) - { - _minTapsBuffer[0].ExecuteStartJudge(songTime); - } - else - { - GetNearestNote(_minTapsBuffer, inputUnitTap.inputPosition).ExecuteStartJudge(songTime); - } - } - } - else if (haveHold) - { - GetAllMinNotes(_availableHoldsBuffer, _minHoldsBuffer); - bool holdBlockedByFlick = closestFlick != null && closestFlick.exactJudgeTime < _minHoldsBuffer[0].exactJudgeTime - 0.05f; - if (!holdBlockedByFlick) - { - if (_minHoldsBuffer.Count == 1) - { - _minHoldsBuffer[0].ExecuteStartJudge(songTime); - } - else - { - GetNearestNote(_minHoldsBuffer, inputUnitTap.inputPosition).ExecuteStartJudge(songTime); - } - } - } - else if (haveTap) - { - GetAllMinNotes(_availableTapsBuffer, _minTapsBuffer); - bool tapBlockedByFlick = closestFlick != null && closestFlick.exactJudgeTime < _minTapsBuffer[0].exactJudgeTime - 0.05f; - if (!tapBlockedByFlick) - { - if (_minTapsBuffer.Count == 1) - { - _minTapsBuffer[0].ExecuteStartJudge(songTime); - } - else - { - GetNearestNote(_minTapsBuffer, inputUnitTap.inputPosition).ExecuteStartJudge(songTime); - } - } - } + selectedCandidate.ExecuteStartJudge(inputContext.InputSongTime); } foreach (InputUnitTouch inputUnitTouch in inputUnitTouchList) { + InputJudgeContext inputContext = CreateInputJudgeContext(inputUnitTouch, songTime, inputTime); _availableStaysBuffer.Clear(); foreach (Stay stay in checkingStayList) { - if (stay.CheckJudgeAvailability(inputUnitTouch)) + if (stay.CheckJudgeAvailability(inputContext)) { _availableStaysBuffer.Add(stay); } @@ -244,7 +119,7 @@ namespace Ichni _availableHoldsBuffer.Clear(); foreach (Hold hold in checkingHoldList) { - if (hold.CheckJudgeAvailability(inputUnitTouch)) + if (hold.CheckJudgeAvailability(inputContext)) { _availableHoldsBuffer.Add(hold); } @@ -252,7 +127,7 @@ namespace Ichni foreach (Stay stay in _availableStaysBuffer) { - stay.ExecuteStartJudge(songTime); + stay.ExecuteStartJudge(inputContext.InputSongTime); } foreach (Hold hold in _availableHoldsBuffer) @@ -261,100 +136,290 @@ namespace Ichni } } - inputUnitTapList.Clear(); - inputUnitTouchList.Clear(); - inputUnitSwipeList.Clear(); + ClearInputUnits(); } - + + public void DiscardPendingInputs() + { + ClearInputUnits(); + } + + private void ClearInputUnits() + { + if (inputUnitTapList != null) + { + for (int i = 0; i < inputUnitTapList.Count; i++) _tapInputPool.Push(inputUnitTapList[i]); + inputUnitTapList.Clear(); + } + + if (inputUnitTouchList != null) + { + for (int i = 0; i < inputUnitTouchList.Count; i++) _touchInputPool.Push(inputUnitTouchList[i]); + inputUnitTouchList.Clear(); + } + + if (inputUnitSwipeList != null) + { + for (int i = 0; i < inputUnitSwipeList.Count; i++) _swipeInputPool.Push(inputUnitSwipeList[i]); + inputUnitSwipeList.Clear(); + } + } + + private static float ResolveInputSongTime(InputUnit inputUnit, float currentSongTime, double currentInputTime) + { + // Input System 时间戳与 currentTime 使用同一时钟。用队列年龄回推 SongTime, + // 避免 LateUpdate 消费时把输入时刻错误替换为更晚的当前歌曲时间。 + double inputAge = currentInputTime - inputUnit.inputEventTime; + if (double.IsNaN(inputAge) || double.IsInfinity(inputAge) || inputAge < 0d) + { + inputAge = 0d; + } + + return currentSongTime - (float)inputAge; + } + + private static InputJudgeContext CreateInputJudgeContext(InputUnit inputUnit, float currentSongTime, + double currentInputTime) + { + return new InputJudgeContext(inputUnit, + ResolveInputSongTime(inputUnit, currentSongTime, currentInputTime)); + } + public void SetNewInputUnitTap(int fingerId, Vector2 inputPosition) { - InputUnitTap inputUnitTap = new InputUnitTap(fingerId, inputPosition); - if(!inputUnitTapList.Exists(x => x.fingerId == fingerId)) - { - inputUnitTapList.Add(inputUnitTap); - } + SetNewInputUnitTap(fingerId, inputPosition, InputState.currentTime); } - + + public void SetNewInputUnitTap(int fingerId, Vector2 inputPosition, double inputEventTime) + { + if (ContainsFinger(inputUnitTapList, fingerId)) return; + + InputUnitTap inputUnitTap = _tapInputPool.Count > 0 ? _tapInputPool.Pop() : new InputUnitTap(); + inputUnitTap.Set(fingerId, inputPosition, inputEventTime); + inputUnitTapList.Add(inputUnitTap); + } + public void SetNewInputUnitTouch(int fingerId, Vector2 inputPosition) { - InputUnitTouch inputUnitTouch = new InputUnitTouch(fingerId, inputPosition); - if(!inputUnitTouchList.Exists(x => x.fingerId == fingerId)) - { - inputUnitTouchList.Add(inputUnitTouch); - } + SetNewInputUnitTouch(fingerId, inputPosition, InputState.currentTime); + } + + public void SetNewInputUnitTouch(int fingerId, Vector2 inputPosition, double inputEventTime) + { + if (ContainsFinger(inputUnitTouchList, fingerId)) return; + + InputUnitTouch inputUnitTouch = _touchInputPool.Count > 0 ? _touchInputPool.Pop() : new InputUnitTouch(); + inputUnitTouch.Set(fingerId, inputPosition, inputEventTime); + inputUnitTouchList.Add(inputUnitTouch); } public void SetNewInputUnitSwipe(int fingerId, Vector2 inputPosition, bool isGeneric, bool isFirst, Vector2 delta) { - InputUnitSwipe inputUnitSwipe = new InputUnitSwipe(fingerId, inputPosition, isGeneric, isFirst, delta); - if(!inputUnitSwipeList.Exists(x => x.fingerId == fingerId)) + SetNewInputUnitSwipe(fingerId, inputPosition, isGeneric, isFirst, delta, InputState.currentTime); + } + + public void SetNewInputUnitSwipe(int fingerId, Vector2 inputPosition, bool isGeneric, bool isFirst, Vector2 delta, double inputEventTime) + { + // 每根手指每帧只保留一个 Swipe 样本;下一帧队列清空后,同一条长划动仍可继续触发 Flick。 + if (ContainsFinger(inputUnitSwipeList, fingerId)) return; + + InputUnitSwipe inputUnitSwipe = _swipeInputPool.Count > 0 ? _swipeInputPool.Pop() : new InputUnitSwipe(); + inputUnitSwipe.Set(fingerId, inputPosition, isGeneric, isFirst, delta, inputEventTime); + inputUnitSwipeList.Add(inputUnitSwipe); + } + + private static bool ContainsFinger(List inputs, int fingerId) where TInput : InputUnit + { + for (int i = 0; i < inputs.Count; i++) { - inputUnitSwipeList.Add(inputUnitSwipe); + if (inputs[i].fingerId == fingerId) return true; } + + return false; } } public partial class NoteJudgeManager { - private void GetAllMinNotes(List availableFlicks, List destination) where T : NoteBase + private void ExecuteSwipeCandidates(InputUnitSwipe swipe, InputJudgeContext context) { - destination.Clear(); - float minTime = float.MaxValue; - foreach (T note in availableFlicks) + // 单个 Swipe 样本只选择一个候选;长划动会在后续帧继续产生样本, + // 因而可以依次触发后续 Flick,而不会让同一输入广播命中全部重叠 Note。 + Flick selectedFlick = SelectStartCandidate(_startCandidatesBuffer, context, + fallbackToFirst: true) as Flick; + if (selectedFlick == null) return; + + ExecuteSwipeJudge(selectedFlick, swipe, context.InputSongTime); + } + + private void ExecuteSwipeJudge(Flick flick, InputUnitSwipe swipe, float inputSongTime) + { + if (swipe.isFirst && CountCandidatesAtJudgeTime(flick.exactJudgeTime) > 1) { - if (note.exactJudgeTime < minTime) - { - minTime = note.exactJudgeTime; - destination.Clear(); - destination.Add(note); - } - else if (Mathf.Approximately(note.exactJudgeTime, minTime)) - { - destination.Add(note); - } + flick.ExecuteTapJudge(inputSongTime); + } + else + { + flick.ExecuteStartJudge(inputSongTime); } } - - - private T GetNearestNote(List notes, Vector2 inputPosition) where T : NoteBase + + private NoteBase SelectStartCandidate(List candidates, InputJudgeContext context, + bool fallbackToFirst) { - float minDistance = float.MaxValue; - T closestNote = null; - foreach (T note in notes) + if (candidates.Count == 0) return null; + + _candidateComparer.SetContext(context); + candidates.Sort(_candidateComparer); + + for (int i = 0; i < candidates.Count; i++) { - float distance = Vector2.Distance(inputPosition, note.noteScreenPosition); - if (distance < minDistance) + // 这里只预览判定等级,不修改 Combo、音频、特效或 Note 生命周期。 + NoteBase.NoteJudgeType previewType = candidates[i].PreviewStartJudgeType(context.InputSongTime); + if (previewType is NoteBase.NoteJudgeType.Perfect or NoteBase.NoteJudgeType.Good) { - minDistance = distance; - closestNote = note; + return candidates[i]; } } - return closestNote; + return fallbackToFirst ? candidates[0] : null; + } + + private int CountCandidatesAtJudgeTime(float exactJudgeTime) + { + int count = 0; + for (int i = 0; i < _startCandidatesBuffer.Count; i++) + { + if (Mathf.Approximately(_startCandidatesBuffer[i].exactJudgeTime, exactJudgeTime)) + { + count++; + } + } + + return count; + } + + private sealed class NoteCandidateComparer : IComparer + { + private Vector2 _inputPosition; + private float _inputSongTime; + + public void SetContext(InputJudgeContext context) + { + _inputPosition = context.InputPosition; + _inputSongTime = context.InputSongTime; + } + + public int Compare(NoteBase left, NoteBase right) + { + if (ReferenceEquals(left, right)) return 0; + if (left == null) return 1; + if (right == null) return -1; + + int timeComparison = left.exactJudgeTime.CompareTo(right.exactJudgeTime); + if (timeComparison != 0) return timeComparison; + + float leftDistance = (GetCandidateScreenPosition(left, _inputSongTime) - _inputPosition).sqrMagnitude; + float rightDistance = (GetCandidateScreenPosition(right, _inputSongTime) - _inputPosition).sqrMagnitude; + int distanceComparison = leftDistance.CompareTo(rightDistance); + if (distanceComparison != 0) return distanceComparison; + + return left.elementGuid.CompareTo(right.elementGuid); + } + + private static Vector2 GetCandidateScreenPosition(NoteBase note, float inputSongTime) + { + if (note.NoteJudgeSubmodule?.judgeUnitList != null) + { + for (int i = 0; i < note.NoteJudgeSubmodule.judgeUnitList.Count; i++) + { + if (note.NoteJudgeSubmodule.judgeUnitList[i] is TouchAreaJudgeUnit touchArea) + { + return touchArea.GetJudgeCenter(inputSongTime); + } + } + } + + return note.noteScreenPosition; + } } } + public readonly struct InputJudgeContext + { + // InputUnit 保存设备事件;InputSongTime 保存该事件映射到谱面时间轴后的发生时刻。 + // JudgeUnit 必须使用本上下文,不能在判定途中重新读取当前 SongTime。 + public InputUnit InputUnit { get; } + public float InputSongTime { get; } + + public int FingerId => InputUnit.fingerId; + public Vector2 InputPosition => InputUnit.inputPosition; + public double InputEventTime => InputUnit.inputEventTime; + + public InputJudgeContext(InputUnit inputUnit, float inputSongTime) + { + InputUnit = inputUnit ?? throw new System.ArgumentNullException(nameof(inputUnit)); + InputSongTime = inputSongTime; + } + + public InputJudgeContext WithInputUnit(InputUnit inputUnit) + { + return new InputJudgeContext(inputUnit, InputSongTime); + } + } + public class InputUnit { public int fingerId; public Vector2 inputPosition; + public double inputEventTime; } public class InputUnitTap : InputUnit { + public InputUnitTap() + { + } + public InputUnitTap(int fingerId, Vector2 inputPosition) + : this(fingerId, inputPosition, InputState.currentTime) + { + } + + public InputUnitTap(int fingerId, Vector2 inputPosition, double inputEventTime) + { + Set(fingerId, inputPosition, inputEventTime); + } + + public void Set(int fingerId, Vector2 inputPosition, double inputEventTime) { this.fingerId = fingerId; this.inputPosition = inputPosition; + this.inputEventTime = inputEventTime; } } public class InputUnitTouch : InputUnit { + public InputUnitTouch() + { + } + public InputUnitTouch(int fingerId, Vector2 inputPosition) + : this(fingerId, inputPosition, InputState.currentTime) + { + } + + public InputUnitTouch(int fingerId, Vector2 inputPosition, double inputEventTime) + { + Set(fingerId, inputPosition, inputEventTime); + } + + public void Set(int fingerId, Vector2 inputPosition, double inputEventTime) { this.fingerId = fingerId; this.inputPosition = inputPosition; + this.inputEventTime = inputEventTime; } } @@ -363,13 +428,30 @@ namespace Ichni public Vector2 swipeDirection; public bool isGeneric; public bool isFirst; + public InputUnitSwipe() + { + } + public InputUnitSwipe(int fingerId, Vector2 inputPosition, bool isGeneric, bool isFirst, Vector2 swipeDirection) + : this(fingerId, inputPosition, isGeneric, isFirst, swipeDirection, InputState.currentTime) + { + } + + public InputUnitSwipe(int fingerId, Vector2 inputPosition, bool isGeneric, bool isFirst, Vector2 swipeDirection, + double inputEventTime) + { + Set(fingerId, inputPosition, isGeneric, isFirst, swipeDirection, inputEventTime); + } + + public void Set(int fingerId, Vector2 inputPosition, bool isGeneric, bool isFirst, Vector2 swipeDirection, + double inputEventTime) { this.fingerId = fingerId; this.inputPosition = inputPosition; + this.inputEventTime = inputEventTime; this.isGeneric = isGeneric; this.isFirst = isFirst; this.swipeDirection = swipeDirection.normalized; } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Manager/NoteJudgeManager.cs.meta b/Assets/Scripts/Manager/NoteJudgeManager.cs.meta index d9b71271..9a6810c0 100644 --- a/Assets/Scripts/Manager/NoteJudgeManager.cs.meta +++ b/Assets/Scripts/Manager/NoteJudgeManager.cs.meta @@ -4,7 +4,7 @@ MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] - executionOrder: -12 + executionOrder: 18 icon: {instanceID: 0} userData: assetBundleName: diff --git a/Assets/Scripts/Manager/NoteManager.cs b/Assets/Scripts/Manager/NoteManager.cs index d093cb72..1b758dae 100644 --- a/Assets/Scripts/Manager/NoteManager.cs +++ b/Assets/Scripts/Manager/NoteManager.cs @@ -28,7 +28,7 @@ namespace Ichni.RhythmGame #endregion #region [中央集权主循环] Manager-Driven Tick - public void ManualUpdate(float currentSongTime) + public void ManualPrepareForJudge(float currentSongTime) { if (!GameManager.Instance.songPlayer.isUpdating) return; @@ -42,10 +42,21 @@ namespace Ichni.RhythmGame _nextNoteIndex++; } - // 2. 集中处理场内活跃 Note (倒序遍历以防越界) + // 2. 判定前更新场内 Note 的最终空间状态与候选列表。 for (int i = _activeNotes.Count - 1; i >= 0; i--) { - if (!_activeNotes[i].ManualUpdate(currentSongTime)) + _activeNotes[i].ManualPrepareForJudge(currentSongTime); + } + } + + public void ManualResolveAfterJudge(float currentSongTime) + { + if (!GameManager.Instance.songPlayer.isUpdating) return; + + // 输入判定完成后再处理 Miss、Hold buffer、特效与销毁。 + for (int i = _activeNotes.Count - 1; i >= 0; i--) + { + if (!_activeNotes[i].ManualResolveAfterJudge(currentSongTime)) { _activeNotes.RemoveAt(i); } diff --git a/Assets/Scripts/Manager/SongPlayer.cs.meta b/Assets/Scripts/Manager/SongPlayer.cs.meta index 77a810ae..17bd3291 100644 --- a/Assets/Scripts/Manager/SongPlayer.cs.meta +++ b/Assets/Scripts/Manager/SongPlayer.cs.meta @@ -4,7 +4,7 @@ MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] - executionOrder: 0 + executionOrder: -14 icon: {instanceID: 0} userData: assetBundleName: diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/IOS b/Assets/StreamingAssets/ThemeBundles/IOS/IOS index b07cc4a7..4061a045 100644 Binary files a/Assets/StreamingAssets/ThemeBundles/IOS/IOS and b/Assets/StreamingAssets/ThemeBundles/IOS/IOS differ diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/IOS.manifest b/Assets/StreamingAssets/ThemeBundles/IOS/IOS.manifest index 6282e2aa..9c1e92b9 100644 --- a/Assets/StreamingAssets/ThemeBundles/IOS/IOS.manifest +++ b/Assets/StreamingAssets/ThemeBundles/IOS/IOS.manifest @@ -1,6 +1,6 @@ ManifestFileVersion: 0 UnityVersion: 6000.3.7f1 -CRC: 3109658480 +CRC: 53704479 HashAppended: 0 AssetBundleManifest: AssetBundleInfos: diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/basic b/Assets/StreamingAssets/ThemeBundles/IOS/basic index 6b23f337..8527c0e5 100644 Binary files a/Assets/StreamingAssets/ThemeBundles/IOS/basic and b/Assets/StreamingAssets/ThemeBundles/IOS/basic differ diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/basic.manifest b/Assets/StreamingAssets/ThemeBundles/IOS/basic.manifest index 09e02e56..3a764003 100644 --- a/Assets/StreamingAssets/ThemeBundles/IOS/basic.manifest +++ b/Assets/StreamingAssets/ThemeBundles/IOS/basic.manifest @@ -1,16 +1,16 @@ ManifestFileVersion: 0 UnityVersion: 6000.3.7f1 -CRC: 2950436852 +CRC: 3236759806 Hashes: AssetFileHash: serializedVersion: 2 - Hash: d96a269b1a7c59262f46b605d1f572d6 + Hash: 83fbc969fd57eaab900477c21478720c TypeTreeHash: serializedVersion: 2 Hash: dfed41e3b38758e5f34df30221d719dc IncrementalBuildHash: serializedVersion: 2 - Hash: debfd94a19fd386044ef29acc180950b + Hash: 5415c53667acfa3eee32fdad43ac1d0a HashAppended: 0 ClassTypes: - Class: 1 diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse b/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse index 3e8b430e..7c701b39 100644 --- a/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse +++ b/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6955e3c221e0ae2c1ee978bba160a58a0445137708eef8d40b087bfa0b1eea44 -size 1189270 +oid sha256:8b5eae4a502fa6629851eda0ddd221659bbd0def3eb3d4675487a11a829c9183 +size 1290285 diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse.manifest b/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse.manifest index 20894a0c..4d82866f 100644 --- a/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse.manifest +++ b/Assets/StreamingAssets/ThemeBundles/IOS/departure_to_multiverse.manifest @@ -1,16 +1,16 @@ ManifestFileVersion: 0 UnityVersion: 6000.3.7f1 -CRC: 3768715475 +CRC: 4192970564 Hashes: AssetFileHash: serializedVersion: 2 - Hash: cee2c3b5a44828ab368863b9490257c3 + Hash: d290c68c3266302b3a1da71ea21801c4 TypeTreeHash: serializedVersion: 2 - Hash: 8b453097cf816e7e6fe791950bd1564f + Hash: 6d9f9b8bc1074349261a0dbbf7f0f9f8 IncrementalBuildHash: serializedVersion: 2 - Hash: 7a3537e33b1ecab07e9a0bb7956f55bd + Hash: 98fb70226d12a3b6d725a6203ec4c3bc HashAppended: 0 ClassTypes: - Class: 1 @@ -37,6 +37,10 @@ ClassTypes: Script: {fileID: 11500000, guid: 0def5156137c6d14082064fa7b5d5247, type: 3} - Class: 114 Script: {fileID: 11500000, guid: 77ab80dc0820f9d4a8cd9fddbbc89057, type: 3} +- Class: 114 + Script: {fileID: 11500000, guid: 2cc178b2441bca14eb1321c83761ab6f, type: 3} +- Class: 114 + Script: {fileID: 11500000, guid: 8ba3055fc57abcb41811796e6a0ed3aa, type: 3} - Class: 114 Script: {fileID: 11500000, guid: e8bab02f6d349f6418ccfe0674b93013, type: 3} - Class: 114 @@ -47,10 +51,14 @@ ClassTypes: Script: {fileID: 11500000, guid: 677671f1d5140b2489dc52c18792b8f5, type: 3} - Class: 114 Script: {fileID: 11500000, guid: f853de2eedbaded4489d7b898fef5926, type: 3} +- Class: 114 + Script: {fileID: 11500000, guid: 23b750fa4c234eb995b6be5e65b56dba, type: 3} - Class: 114 Script: {fileID: 11500000, guid: 638fbd0e919667f47a93485dd230c71f, type: 3} - Class: 114 Script: {fileID: 11500000, guid: eb907071d10d7324995ff3aefbc2f1df, type: 3} +- Class: 114 + Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3} - Class: 115 Script: {instanceID: 0} - Class: 135 @@ -81,6 +89,7 @@ SerializeReferenceClassIdentifiers: - AssemblyName: Sirenix.Serialization ClassName: Sirenix.Serialization.SerializationData Assets: +- Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Environment/DTM_CircleSpinTrails.prefab - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/DTM_NoteVisualTap.prefab - Assets/ThemeBundles/DepartureToMultiverse/Materials/Skybox/DTM_Skybox_Starry.mat - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Environment/DTM_RandomGridTube.prefab @@ -88,8 +97,10 @@ Assets: - Assets/ThemeBundles/DepartureToMultiverse/Textures/Skybox/DTM_SunMask0.png - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/DTM_NoteVisualFlick.prefab - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/DTM_Trail2.prefab +- Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Decorations/DTM_ConstellationCube.prefab - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Environment/DTM_GlobalFog.prefab - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/DTM_Trail.prefab +- Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Decorations/Constellation.prefab - Assets/ThemeBundles/DepartureToMultiverse/Textures/Skybox/DTM_SunMask1.png - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Environment/DTM_StarrySkybox.prefab - Assets/ThemeBundles/DepartureToMultiverse/Prefabs/DTM_NoteVisualHold.prefab diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit b/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit index aec797a1..fea58992 100644 Binary files a/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit and b/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit differ diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit.manifest b/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit.manifest index 9214c77e..b464dd48 100644 --- a/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit.manifest +++ b/Assets/StreamingAssets/ThemeBundles/IOS/metropolis_on_orbit.manifest @@ -1,16 +1,16 @@ ManifestFileVersion: 0 UnityVersion: 6000.3.7f1 -CRC: 3125059414 +CRC: 1833553277 Hashes: AssetFileHash: serializedVersion: 2 - Hash: da0193547f358bc1bf0378e49649aa75 + Hash: e08a9beb2e31cec7d5d5c5f2ed2bcccf TypeTreeHash: serializedVersion: 2 Hash: 8ab77e54d824a64c30bc20496c5f5926 IncrementalBuildHash: serializedVersion: 2 - Hash: 284ba86ec2440e4ef2177b0e314946a9 + Hash: 93d69c94859430eb482017e88174278c HashAppended: 0 ClassTypes: - Class: 1 diff --git a/Assets/StreamingAssets/ThemeBundles/IOS/shapes.manifest b/Assets/StreamingAssets/ThemeBundles/IOS/shapes.manifest index de0f19c0..6853cd0c 100644 --- a/Assets/StreamingAssets/ThemeBundles/IOS/shapes.manifest +++ b/Assets/StreamingAssets/ThemeBundles/IOS/shapes.manifest @@ -10,7 +10,7 @@ Hashes: Hash: bc9f87c7e64ff656997db0f2b2a91703 IncrementalBuildHash: serializedVersion: 2 - Hash: 02555d72e66a23410baf71e006ec9a39 + Hash: bac15f27dfb157ff22724a110e8702fe HashAppended: 0 ClassTypes: - Class: 28 diff --git a/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteBadBurst.cs b/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteBadBurst.cs index e287590b..7024efff 100644 --- a/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteBadBurst.cs +++ b/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteBadBurst.cs @@ -28,6 +28,12 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse { effectParticle = LeanPool.Spawn(noteVisual.effectPrefabList[2], noteVisual.judgeEffect.transform).GetComponent(); effectParticle.transform.SetParent(GameManager.Instance.cameraManager.gameCamera.transform); + + if (note.TryGetJudgedEffectPose(out Pose pose)) + { + effectParticle.transform.SetPositionAndRotation(pose.position, pose.rotation); + } + effectParticle.Play(); noteVisual.noteMain.SetActive(false); diff --git a/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteGoodBurst.cs b/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteGoodBurst.cs index e504990e..2adb4216 100644 --- a/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteGoodBurst.cs +++ b/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNoteGoodBurst.cs @@ -28,6 +28,12 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse { effectParticle = LeanPool.Spawn(noteVisual.effectPrefabList[1], noteVisual.judgeEffect.transform).GetComponent(); effectParticle.transform.SetParent(GameManager.Instance.cameraManager.gameCamera.transform); + + if (note.TryGetJudgedEffectPose(out Pose pose)) + { + effectParticle.transform.SetPositionAndRotation(pose.position, pose.rotation); + } + effectParticle.Play(); noteVisual.noteMain.SetActive(false); diff --git a/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNotePerfectBurst.cs b/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNotePerfectBurst.cs index 465f9576..3dd3aa25 100644 --- a/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNotePerfectBurst.cs +++ b/Assets/ThemeBundles/DepartureToMultiverse/Scripts/RuntimeCore/Note/NoteEffect/DTMNotePerfectBurst.cs @@ -25,12 +25,24 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse noteVisual.noteMain.SetActive(true); } + public override void PrepareJudgedEffectPose() + { + // DTM 的 Perfect 打击点需要与判定当帧的轨道头对齐,并在移动后立即由 Note 缓存世界姿态。 + note.SetPerfectPosition(); + } + public override void Adjust() { - note.SetPerfectPosition(); //effectParticle = LeanPool.Spawn(noteVisual.effectPrefabList[0], noteVisual.judgeEffect.transform).GetComponent(); effectParticle = LeanPool.Spawn(noteVisual.effectPrefabList[0], noteVisual.judgeEffect.transform).GetComponent(); effectParticle.transform.SetParent(GameManager.Instance.cameraManager.gameCamera.transform); + + // 摄像机或 Track 可能与判定发生在同一时间点切换,粒子必须回到判定当帧缓存的位置。 + if (note.TryGetJudgedEffectPose(out Pose pose)) + { + effectParticle.transform.SetPositionAndRotation(pose.position, pose.rotation); + } + effectParticle.Play(); noteVisual.noteMain.SetActive(false); diff --git a/UserSettings/Layouts/default-6000.dwlt b/UserSettings/Layouts/default-6000.dwlt index ed3bbd15..c508190c 100644 --- a/UserSettings/Layouts/default-6000.dwlt +++ b/UserSettings/Layouts/default-6000.dwlt @@ -19,7 +19,7 @@ MonoBehaviour: width: 1706.6667 height: 869.3334 m_ShowMode: 4 - m_Title: Hierarchy + m_Title: Inspector m_RootView: {fileID: 2} m_MinSize: {x: 875, y: 300} m_MaxSize: {x: 10000, y: 10000} @@ -118,7 +118,7 @@ MonoBehaviour: m_MinSize: {x: 300, y: 112} m_MaxSize: {x: 24288, y: 16192} vertical: 1 - controlID: 71 + controlID: 54 draggingID: 0 --- !u!114 &6 MonoBehaviour: @@ -144,7 +144,7 @@ MonoBehaviour: m_MinSize: {x: 300, y: 112} m_MaxSize: {x: 24288, y: 16192} vertical: 0 - controlID: 72 + controlID: 55 draggingID: 0 --- !u!114 &7 MonoBehaviour: @@ -170,7 +170,7 @@ MonoBehaviour: m_MinSize: {x: 200, y: 112} m_MaxSize: {x: 16192, y: 16192} vertical: 1 - controlID: 73 + controlID: 56 draggingID: 0 --- !u!114 &8 MonoBehaviour: @@ -196,7 +196,7 @@ MonoBehaviour: m_MinSize: {x: 200, y: 56} m_MaxSize: {x: 16192, y: 8096} vertical: 0 - controlID: 26 + controlID: 57 draggingID: 0 --- !u!114 &9 MonoBehaviour: @@ -217,11 +217,11 @@ MonoBehaviour: y: 0 width: 268.66666 height: 571.3333 - m_MinSize: {x: 201, y: 226} - m_MaxSize: {x: 4001, y: 4026} - m_ActualView: {fileID: 16} + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_ActualView: {fileID: 15} m_Panes: - - {fileID: 16} + - {fileID: 15} m_Selected: 0 m_LastSelected: 0 --- !u!114 &10 @@ -234,7 +234,7 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 1 m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: SceneView + m_Name: GameView m_EditorClassIdentifier: m_Children: [] m_Position: @@ -245,14 +245,14 @@ MonoBehaviour: height: 571.3333 m_MinSize: {x: 200, y: 200} m_MaxSize: {x: 4000, y: 4000} - m_ActualView: {fileID: 17} + m_ActualView: {fileID: 27} m_Panes: + - {fileID: 16} - {fileID: 17} - - {fileID: 15} - - {fileID: 18} - {fileID: 13} - m_Selected: 0 - m_LastSelected: 1 + - {fileID: 27} + m_Selected: 3 + m_LastSelected: 0 --- !u!114 &11 MonoBehaviour: m_ObjectHideFlags: 52 @@ -274,12 +274,12 @@ MonoBehaviour: height: 242 m_MinSize: {x: 231, y: 276} m_MaxSize: {x: 10001, y: 10026} - m_ActualView: {fileID: 19} + m_ActualView: {fileID: 18} m_Panes: + - {fileID: 18} - {fileID: 19} - {fileID: 20} - {fileID: 21} - - {fileID: 22} m_Selected: 0 m_LastSelected: 1 --- !u!114 &12 @@ -303,13 +303,13 @@ MonoBehaviour: height: 813.3333 m_MinSize: {x: 275, y: 50} m_MaxSize: {x: 4000, y: 4000} - m_ActualView: {fileID: 24} + m_ActualView: {fileID: 23} m_Panes: + - {fileID: 22} - {fileID: 23} - {fileID: 24} - {fileID: 25} - {fileID: 26} - - {fileID: 27} m_Selected: 1 m_LastSelected: 2 --- !u!114 &13 @@ -333,9 +333,9 @@ MonoBehaviour: m_TextWithWhitespace: "Addressables Groups\u200B" m_Pos: serializedVersion: 2 - x: 269.33334 + x: 268.6667 y: 78.66667 - width: 860 + width: 860.66675 height: 545.3333 m_SerializedDataModeController: m_DataMode: 0 @@ -394,7 +394,7 @@ MonoBehaviour: sortedAscending: 0 headerContent: m_Text: - m_Image: {fileID: -7048650731427229155, guid: 0000000000000000d000000000000000, + m_Image: {fileID: 6818279631426641284, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: Notifications m_TextWithWhitespace: @@ -427,8 +427,7 @@ MonoBehaviour: sortedAscending: 0 headerContent: m_Text: - m_Image: {fileID: 8625061006665994266, guid: 0000000000000000d000000000000000, - type: 0} + m_Image: {fileID: 0} m_Tooltip: Asset type m_TextWithWhitespace: contextMenuText: @@ -785,112 +784,6 @@ MonoBehaviour: m_OverlaysVisible: 1 m_DynamicPanelBehavior: 0 --- !u!114 &15 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Game - m_Image: {fileID: 4621777727084837110, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_TextWithWhitespace: "Game\u200B" - m_Pos: - serializedVersion: 2 - x: 268.6667 - y: 78.66667 - width: 860.66675 - height: 545.3333 - m_SerializedDataModeController: - m_DataMode: 0 - m_PreferredDataMode: 0 - m_SupportedDataModes: - isAutomatic: 1 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_ContainerData: [] - m_DynamicPanelContainerData: [] - m_OverlaysVisible: 1 - m_DynamicPanelBehavior: 0 - m_SerializedViewNames: - - UnityEditor.DeviceSimulation.SimulatorWindow - m_SerializedViewValues: - - D:\Projects\ichni Official\Library\PlayModeViewStates\cb35746b7305de745a489f7d790ee0b7 - m_PlayModeViewName: GameView - m_ShowGizmos: 0 - m_TargetDisplay: 0 - m_ClearColor: {r: 0, g: 0, b: 0, a: 0} - m_TargetSize: {x: 3840, y: 2160} - m_TextureFilterMode: 0 - m_TextureHideFlags: 61 - m_RenderIMGUI: 1 - m_EnterPlayModeBehavior: 0 - m_UseMipMap: 0 - m_VSyncEnabled: 0 - m_Gizmos: 0 - m_Stats: 0 - m_SelectedSizes: 06000000000000001200000006000000000000000000000000000000000000000000000000000000 - m_ZoomArea: - m_HRangeLocked: 0 - m_VRangeLocked: 0 - hZoomLockedByDefault: 0 - vZoomLockedByDefault: 0 - m_HBaseRangeMin: -1280 - m_HBaseRangeMax: 1280 - m_VBaseRangeMin: -720 - m_VBaseRangeMax: 720 - m_HAllowExceedBaseRangeMin: 1 - m_HAllowExceedBaseRangeMax: 1 - m_VAllowExceedBaseRangeMin: 1 - m_VAllowExceedBaseRangeMax: 1 - m_ScaleWithWindow: 0 - m_HSlider: 0 - m_VSlider: 0 - m_IgnoreScrollWheelUntilClicked: 0 - m_EnableMouseInput: 0 - m_EnableSliderZoomHorizontal: 0 - m_EnableSliderZoomVertical: 0 - m_UniformScale: 1 - m_UpDirection: 1 - m_DrawArea: - serializedVersion: 2 - x: 0 - y: 21 - width: 860.66675 - height: 524.3333 - m_Scale: {x: 0.33619794, y: 0.33619794} - m_Translation: {x: 430.33337, y: 262.16666} - m_MarginLeft: 0 - m_MarginRight: 0 - m_MarginTop: 0 - m_MarginBottom: 0 - m_LastShownAreaInsideMargins: - serializedVersion: 2 - x: -1280 - y: -779.7985 - width: 2560 - height: 1559.597 - m_MinimalGUI: 1 - m_defaultScale: 0.33619794 - m_LastWindowPixelSize: {x: 1291.0001, y: 818} - m_ClearInEditMode: 1 - m_NoCameraWarning: 1 - m_LowResolutionForAspectRatios: 00000000000000000000 - m_XRRenderMode: 0 - m_RenderTexture: {fileID: 0} - m_showToolbar: 1 ---- !u!114 &16 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -936,24 +829,19 @@ MonoBehaviour: m_LastClickedID: m_Data: 0 m_ExpandedIDs: - - m_Data: -191216 - - m_Data: -190792 - - m_Data: -188452 - - m_Data: -184458 - - m_Data: -171200 - - m_Data: -170772 - - m_Data: -168428 - - m_Data: -164434 - - m_Data: -93312 - - m_Data: -92884 - - m_Data: -90540 - - m_Data: -86546 - - m_Data: -80070 - - m_Data: -71632 - - m_Data: -71208 - - m_Data: -68884 - - m_Data: -64906 - - m_Data: -1342 + - m_Data: -277250 + - m_Data: -276928 + - m_Data: -274604 + - m_Data: -270622 + - m_Data: -247942 + - m_Data: -247598 + - m_Data: -245258 + - m_Data: -241264 + - m_Data: -236672 + - m_Data: -2814 + - m_Data: -1334 + - m_Data: 174900 + - m_Data: 177030 m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -979,7 +867,7 @@ MonoBehaviour: m_IsLocked: 0 m_CurrentSortingName: TransformSorting m_WindowGUID: 4c969a2b90040154d917609493e03593 ---- !u!114 &17 +--- !u!114 &16 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -1673,9 +1561,9 @@ MonoBehaviour: m_AudioPlay: 0 m_DebugDrawModesUseInteractiveLightBakingData: 0 m_Position: - m_Target: {x: 12.81906, y: 12.520589, z: 2236.83} + m_Target: {x: -9.387334, y: -4.940602, z: 1552.3064} speed: 2 - m_Value: {x: 12.81906, y: 12.520589, z: 2236.83} + m_Value: {x: -9.387334, y: -4.940602, z: 1552.3064} m_RenderMode: 0 m_CameraMode: drawMode: 0 @@ -1721,9 +1609,9 @@ MonoBehaviour: m_GridAxis: 1 m_gridOpacity: 0.5 m_Rotation: - m_Target: {x: 0.1383886, y: -0.40541148, z: 0.06271537, w: 0.9016005} + m_Target: {x: 0.24885446, y: -0.62571377, z: 0.22137791, w: 0.7056009} speed: 2 - m_Value: {x: 0.13836603, y: -0.40534532, z: 0.06270514, w: 0.9014534} + m_Value: {x: 0.24881376, y: -0.6256114, z: 0.22134171, w: 0.7054855} m_Size: m_Target: 8.783796 speed: 2 @@ -1745,10 +1633,10 @@ MonoBehaviour: m_FarClip: 1000 m_DynamicClip: 1 m_OcclusionCulling: 0 - m_LastSceneViewRotation: {x: -0.034498498, y: -0.002649789, z: 0.00032535294, w: 0.9995643} + m_LastSceneViewRotation: {x: 0.24885446, y: -0.62571377, z: 0.22137791, w: 0.7056009} m_LastSceneViewOrtho: 0 m_Viewpoint: - m_SceneView: {fileID: 17} + m_SceneView: {fileID: 16} m_CameraOverscanSettings: m_Opacity: 50 m_Scale: 1 @@ -1761,7 +1649,7 @@ MonoBehaviour: name: Light Overlap section: Baked Global Illumination m_ViewIsLockedToObject: 0 ---- !u!114 &18 +--- !u!114 &17 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -1783,9 +1671,9 @@ MonoBehaviour: m_TextWithWhitespace: "Animator\u200B" m_Pos: serializedVersion: 2 - x: 269.33334 + x: 268.6667 y: 78.66667 - width: 860 + width: 860.66675 height: 545.3333 m_SerializedDataModeController: m_DataMode: 0 @@ -1889,7 +1777,7 @@ MonoBehaviour: m_CurrentEditor: 0 m_LayerEditor: m_SelectedLayerIndex: 0 ---- !u!114 &19 +--- !u!114 &18 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -1956,128 +1844,132 @@ MonoBehaviour: m_IsLocked: 0 m_LastLocalAssetsSearchArea: 0 m_FolderTreeState: - scrollPos: {x: 0, y: 308} + scrollPos: {x: 0, y: 280} m_SelectedIDs: - - m_Data: 85038 + - m_Data: 82098 m_LastClickedID: - m_Data: 85038 + m_Data: 82098 m_ExpandedIDs: - m_Data: 0 - - m_Data: 84816 - - m_Data: 84818 - - m_Data: 84820 - - m_Data: 84822 - - m_Data: 84824 - - m_Data: 84826 - - m_Data: 84828 - - m_Data: 84830 - - m_Data: 84832 - - m_Data: 84834 - - m_Data: 84836 - - m_Data: 84838 - - m_Data: 84840 - - m_Data: 84842 - - m_Data: 84844 - - m_Data: 84846 - - m_Data: 84848 - - m_Data: 84850 - - m_Data: 84852 - - m_Data: 84854 - - m_Data: 84856 - - m_Data: 84858 - - m_Data: 84860 - - m_Data: 84862 - - m_Data: 84864 - - m_Data: 84866 - - m_Data: 84868 - - m_Data: 84870 - - m_Data: 84872 - - m_Data: 84874 - - m_Data: 84876 - - m_Data: 84878 - - m_Data: 84880 - - m_Data: 84882 - - m_Data: 84884 - - m_Data: 84886 - - m_Data: 84888 - - m_Data: 84890 - - m_Data: 84892 - - m_Data: 84894 - - m_Data: 84896 - - m_Data: 84898 - - m_Data: 84900 - - m_Data: 84902 - - m_Data: 84904 - - m_Data: 84906 - - m_Data: 84908 - - m_Data: 84910 - - m_Data: 84912 - - m_Data: 84914 - - m_Data: 84916 - - m_Data: 84918 - - m_Data: 84920 - - m_Data: 84922 - - m_Data: 84924 - - m_Data: 84926 - - m_Data: 84928 - - m_Data: 84930 - - m_Data: 84932 - - m_Data: 84934 - - m_Data: 84936 - - m_Data: 84938 - - m_Data: 84940 - - m_Data: 84942 - - m_Data: 84944 - - m_Data: 84946 - - m_Data: 84948 - - m_Data: 84950 - - m_Data: 84952 - - m_Data: 84954 - - m_Data: 84956 - - m_Data: 84958 - - m_Data: 84960 - - m_Data: 84962 - - m_Data: 84964 - - m_Data: 84966 - - m_Data: 84968 - - m_Data: 84970 - - m_Data: 84972 - - m_Data: 84974 - - m_Data: 84976 - - m_Data: 84978 - - m_Data: 84980 - - m_Data: 84982 - - m_Data: 84984 - - m_Data: 84986 - - m_Data: 84988 - - m_Data: 84990 - - m_Data: 84992 - - m_Data: 84994 - - m_Data: 84996 - - m_Data: 84998 - - m_Data: 85000 - - m_Data: 85002 - - m_Data: 85004 - - m_Data: 85006 - - m_Data: 85008 - - m_Data: 85010 - - m_Data: 85012 - - m_Data: 85014 - - m_Data: 85016 - - m_Data: 85018 - - m_Data: 85020 - - m_Data: 85022 - - m_Data: 85024 - - m_Data: 85026 - - m_Data: 85028 - - m_Data: 85030 - - m_Data: 85032 - - m_Data: 85034 - - m_Data: 85036 - - m_Data: 85038 - - m_Data: 85040 - - m_Data: 85486 - - m_Data: 85488 + - m_Data: 81414 + - m_Data: 81416 + - m_Data: 81418 + - m_Data: 81420 + - m_Data: 81422 + - m_Data: 81424 + - m_Data: 81426 + - m_Data: 81428 + - m_Data: 81430 + - m_Data: 81432 + - m_Data: 81434 + - m_Data: 81436 + - m_Data: 81438 + - m_Data: 81440 + - m_Data: 81442 + - m_Data: 81444 + - m_Data: 81446 + - m_Data: 81448 + - m_Data: 81450 + - m_Data: 81452 + - m_Data: 81454 + - m_Data: 81456 + - m_Data: 81458 + - m_Data: 81460 + - m_Data: 81462 + - m_Data: 81464 + - m_Data: 81466 + - m_Data: 81468 + - m_Data: 81470 + - m_Data: 81472 + - m_Data: 81474 + - m_Data: 81476 + - m_Data: 81478 + - m_Data: 81480 + - m_Data: 81482 + - m_Data: 81484 + - m_Data: 81486 + - m_Data: 81488 + - m_Data: 81490 + - m_Data: 81492 + - m_Data: 81494 + - m_Data: 81496 + - m_Data: 81498 + - m_Data: 81500 + - m_Data: 81502 + - m_Data: 81504 + - m_Data: 81506 + - m_Data: 81508 + - m_Data: 81510 + - m_Data: 81512 + - m_Data: 81514 + - m_Data: 81516 + - m_Data: 81518 + - m_Data: 81520 + - m_Data: 81522 + - m_Data: 81524 + - m_Data: 81526 + - m_Data: 81528 + - m_Data: 81530 + - m_Data: 81532 + - m_Data: 81534 + - m_Data: 81536 + - m_Data: 81538 + - m_Data: 81540 + - m_Data: 81542 + - m_Data: 81544 + - m_Data: 81546 + - m_Data: 81548 + - m_Data: 81550 + - m_Data: 81552 + - m_Data: 81554 + - m_Data: 81556 + - m_Data: 81558 + - m_Data: 81560 + - m_Data: 81562 + - m_Data: 81564 + - m_Data: 81566 + - m_Data: 81568 + - m_Data: 81570 + - m_Data: 81572 + - m_Data: 81574 + - m_Data: 81576 + - m_Data: 81578 + - m_Data: 81580 + - m_Data: 81582 + - m_Data: 81584 + - m_Data: 81586 + - m_Data: 81588 + - m_Data: 81590 + - m_Data: 81592 + - m_Data: 81594 + - m_Data: 81596 + - m_Data: 81598 + - m_Data: 81600 + - m_Data: 81602 + - m_Data: 81604 + - m_Data: 81606 + - m_Data: 81608 + - m_Data: 81610 + - m_Data: 81612 + - m_Data: 81614 + - m_Data: 81616 + - m_Data: 81618 + - m_Data: 81620 + - m_Data: 81622 + - m_Data: 81624 + - m_Data: 81626 + - m_Data: 81628 + - m_Data: 81630 + - m_Data: 81632 + - m_Data: 81634 + - m_Data: 81636 + - m_Data: 81640 + - m_Data: 81646 + - m_Data: 81648 + - m_Data: 81650 + - m_Data: 82162 + - m_Data: 82196 + - m_Data: 82206 - m_Data: 1000000000 m_RenameOverlay: m_UserAcceptedRename: 0 @@ -2111,120 +2003,125 @@ MonoBehaviour: m_Data: 0 m_ExpandedIDs: - m_Data: 0 - - m_Data: 84816 - - m_Data: 84818 - - m_Data: 84820 - - m_Data: 84822 - - m_Data: 84824 - - m_Data: 84826 - - m_Data: 84828 - - m_Data: 84830 - - m_Data: 84832 - - m_Data: 84834 - - m_Data: 84836 - - m_Data: 84838 - - m_Data: 84840 - - m_Data: 84842 - - m_Data: 84844 - - m_Data: 84846 - - m_Data: 84848 - - m_Data: 84850 - - m_Data: 84852 - - m_Data: 84854 - - m_Data: 84856 - - m_Data: 84858 - - m_Data: 84860 - - m_Data: 84862 - - m_Data: 84864 - - m_Data: 84866 - - m_Data: 84868 - - m_Data: 84870 - - m_Data: 84872 - - m_Data: 84874 - - m_Data: 84876 - - m_Data: 84878 - - m_Data: 84880 - - m_Data: 84882 - - m_Data: 84884 - - m_Data: 84886 - - m_Data: 84888 - - m_Data: 84890 - - m_Data: 84892 - - m_Data: 84894 - - m_Data: 84896 - - m_Data: 84898 - - m_Data: 84900 - - m_Data: 84902 - - m_Data: 84904 - - m_Data: 84906 - - m_Data: 84908 - - m_Data: 84910 - - m_Data: 84912 - - m_Data: 84914 - - m_Data: 84916 - - m_Data: 84918 - - m_Data: 84920 - - m_Data: 84922 - - m_Data: 84924 - - m_Data: 84926 - - m_Data: 84928 - - m_Data: 84930 - - m_Data: 84932 - - m_Data: 84934 - - m_Data: 84936 - - m_Data: 84938 - - m_Data: 84940 - - m_Data: 84942 - - m_Data: 84944 - - m_Data: 84946 - - m_Data: 84948 - - m_Data: 84950 - - m_Data: 84952 - - m_Data: 84954 - - m_Data: 84956 - - m_Data: 84958 - - m_Data: 84960 - - m_Data: 84962 - - m_Data: 84964 - - m_Data: 84966 - - m_Data: 84968 - - m_Data: 84970 - - m_Data: 84972 - - m_Data: 84974 - - m_Data: 84976 - - m_Data: 84978 - - m_Data: 84980 - - m_Data: 84982 - - m_Data: 84984 - - m_Data: 84986 - - m_Data: 84988 - - m_Data: 84990 - - m_Data: 84992 - - m_Data: 84994 - - m_Data: 84996 - - m_Data: 84998 - - m_Data: 85000 - - m_Data: 85002 - - m_Data: 85004 - - m_Data: 85006 - - m_Data: 85008 - - m_Data: 85010 - - m_Data: 85012 - - m_Data: 85014 - - m_Data: 85016 - - m_Data: 85018 - - m_Data: 85020 - - m_Data: 85022 - - m_Data: 85024 - - m_Data: 85026 - - m_Data: 85028 - - m_Data: 85030 - - m_Data: 85032 - - m_Data: 85034 - - m_Data: 85036 - - m_Data: 85038 - - m_Data: 85040 - - m_Data: 85042 + - m_Data: 81414 + - m_Data: 81416 + - m_Data: 81418 + - m_Data: 81420 + - m_Data: 81422 + - m_Data: 81424 + - m_Data: 81426 + - m_Data: 81428 + - m_Data: 81430 + - m_Data: 81432 + - m_Data: 81434 + - m_Data: 81436 + - m_Data: 81438 + - m_Data: 81440 + - m_Data: 81442 + - m_Data: 81444 + - m_Data: 81446 + - m_Data: 81448 + - m_Data: 81450 + - m_Data: 81452 + - m_Data: 81454 + - m_Data: 81456 + - m_Data: 81458 + - m_Data: 81460 + - m_Data: 81462 + - m_Data: 81464 + - m_Data: 81466 + - m_Data: 81468 + - m_Data: 81470 + - m_Data: 81472 + - m_Data: 81474 + - m_Data: 81476 + - m_Data: 81478 + - m_Data: 81480 + - m_Data: 81482 + - m_Data: 81484 + - m_Data: 81486 + - m_Data: 81488 + - m_Data: 81490 + - m_Data: 81492 + - m_Data: 81494 + - m_Data: 81496 + - m_Data: 81498 + - m_Data: 81500 + - m_Data: 81502 + - m_Data: 81504 + - m_Data: 81506 + - m_Data: 81508 + - m_Data: 81510 + - m_Data: 81512 + - m_Data: 81514 + - m_Data: 81516 + - m_Data: 81518 + - m_Data: 81520 + - m_Data: 81522 + - m_Data: 81524 + - m_Data: 81526 + - m_Data: 81528 + - m_Data: 81530 + - m_Data: 81532 + - m_Data: 81534 + - m_Data: 81536 + - m_Data: 81538 + - m_Data: 81540 + - m_Data: 81542 + - m_Data: 81544 + - m_Data: 81546 + - m_Data: 81548 + - m_Data: 81550 + - m_Data: 81552 + - m_Data: 81554 + - m_Data: 81556 + - m_Data: 81558 + - m_Data: 81560 + - m_Data: 81562 + - m_Data: 81564 + - m_Data: 81566 + - m_Data: 81568 + - m_Data: 81570 + - m_Data: 81572 + - m_Data: 81574 + - m_Data: 81576 + - m_Data: 81578 + - m_Data: 81580 + - m_Data: 81582 + - m_Data: 81584 + - m_Data: 81586 + - m_Data: 81588 + - m_Data: 81590 + - m_Data: 81592 + - m_Data: 81594 + - m_Data: 81596 + - m_Data: 81598 + - m_Data: 81600 + - m_Data: 81602 + - m_Data: 81604 + - m_Data: 81606 + - m_Data: 81608 + - m_Data: 81610 + - m_Data: 81612 + - m_Data: 81614 + - m_Data: 81616 + - m_Data: 81618 + - m_Data: 81620 + - m_Data: 81622 + - m_Data: 81624 + - m_Data: 81626 + - m_Data: 81628 + - m_Data: 81630 + - m_Data: 81632 + - m_Data: 81634 + - m_Data: 81636 + - m_Data: 81638 + - m_Data: 81640 + - m_Data: 81642 + - m_Data: 81644 + - m_Data: 81646 + - m_Data: 81648 + - m_Data: 81650 m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -2299,7 +2196,7 @@ MonoBehaviour: m_GridSize: 16 m_SkipHiddenPackages: 0 m_DirectoriesAreaWidth: 209 ---- !u!114 &20 +--- !u!114 &19 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2338,7 +2235,7 @@ MonoBehaviour: m_DynamicPanelContainerData: [] m_OverlaysVisible: 1 m_DynamicPanelBehavior: 0 ---- !u!114 &21 +--- !u!114 &20 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2380,7 +2277,7 @@ MonoBehaviour: m_LockTracker: m_IsLocked: 0 m_LastSelectedObjectID: 75236 ---- !u!114 &22 +--- !u!114 &21 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2418,7 +2315,7 @@ MonoBehaviour: m_DynamicPanelContainerData: [] m_OverlaysVisible: 1 m_DynamicPanelBehavior: 0 ---- !u!114 &23 +--- !u!114 &22 MonoBehaviour: m_ObjectHideFlags: 61 m_CorrespondingSourceObject: {fileID: 0} @@ -2460,7 +2357,7 @@ MonoBehaviour: m_Keys: [] m_Values: [] m_Settings: {fileID: 0} ---- !u!114 &24 +--- !u!114 &23 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2512,7 +2409,7 @@ MonoBehaviour: m_LockTracker: m_IsLocked: 0 m_PreviewWindow: {fileID: 0} ---- !u!114 &25 +--- !u!114 &24 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2557,14 +2454,14 @@ MonoBehaviour: m_CachedPref: 184.33334 m_ControlHash: 1412526313 m_PrefName: Preview_InspectorPreview - m_LastInspectedObjectInstanceID: -1 + m_LastInspectedObjectInstanceID: 174900 m_LastVerticalScrollValue: 0 m_GlobalObjectId: m_InspectorMode: 0 m_LockTracker: m_IsLocked: 0 m_PreviewWindow: {fileID: 0} ---- !u!114 &26 +--- !u!114 &25 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2609,14 +2506,14 @@ MonoBehaviour: m_CachedPref: 165.66669 m_ControlHash: 1412526313 m_PrefName: Preview_InspectorPreview - m_LastInspectedObjectInstanceID: -1 + m_LastInspectedObjectInstanceID: 174900 m_LastVerticalScrollValue: 0 m_GlobalObjectId: m_InspectorMode: 0 m_LockTracker: m_IsLocked: 0 m_PreviewWindow: {fileID: 0} ---- !u!114 &27 +--- !u!114 &26 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -2655,3 +2552,109 @@ MonoBehaviour: m_DynamicPanelContainerData: [] m_OverlaysVisible: 1 m_DynamicPanelBehavior: 0 +--- !u!114 &27 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Game + m_Image: {fileID: 4621777727084837110, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_TextWithWhitespace: "Game\u200B" + m_Pos: + serializedVersion: 2 + x: 268.6667 + y: 78.66667 + width: 860.66675 + height: 545.3333 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_ContainerData: [] + m_DynamicPanelContainerData: [] + m_OverlaysVisible: 1 + m_DynamicPanelBehavior: 0 + m_SerializedViewNames: + - UnityEditor.DeviceSimulation.SimulatorWindow + m_SerializedViewValues: + - D:\Projects\ichni Official\Library\PlayModeViewStates\caeeed37cf7dab247941b809dad3cabe + m_PlayModeViewName: GameView + m_ShowGizmos: 0 + m_TargetDisplay: 0 + m_ClearColor: {r: 0, g: 0, b: 0, a: 0} + m_TargetSize: {x: 1291.0001, y: 726} + m_TextureFilterMode: 0 + m_TextureHideFlags: 61 + m_RenderIMGUI: 1 + m_EnterPlayModeBehavior: 0 + m_UseMipMap: 0 + m_VSyncEnabled: 0 + m_Gizmos: 0 + m_Stats: 0 + m_SelectedSizes: 06000000000000002000000006000000000000000000000000000000000000000000000000000000 + m_ZoomArea: + m_HRangeLocked: 0 + m_VRangeLocked: 0 + hZoomLockedByDefault: 0 + vZoomLockedByDefault: 0 + m_HBaseRangeMin: -430.33337 + m_HBaseRangeMax: 430.33337 + m_VBaseRangeMin: -242 + m_VBaseRangeMax: 242 + m_HAllowExceedBaseRangeMin: 1 + m_HAllowExceedBaseRangeMax: 1 + m_VAllowExceedBaseRangeMin: 1 + m_VAllowExceedBaseRangeMax: 1 + m_ScaleWithWindow: 0 + m_HSlider: 0 + m_VSlider: 0 + m_IgnoreScrollWheelUntilClicked: 0 + m_EnableMouseInput: 1 + m_EnableSliderZoomHorizontal: 0 + m_EnableSliderZoomVertical: 0 + m_UniformScale: 1 + m_UpDirection: 1 + m_DrawArea: + serializedVersion: 2 + x: 0 + y: 21 + width: 860.66675 + height: 524.3333 + m_Scale: {x: 1, y: 1} + m_Translation: {x: 430.33337, y: 262.16666} + m_MarginLeft: 0 + m_MarginRight: 0 + m_MarginTop: 0 + m_MarginBottom: 0 + m_LastShownAreaInsideMargins: + serializedVersion: 2 + x: -430.33337 + y: -262.16666 + width: 860.66675 + height: 524.3333 + m_MinimalGUI: 1 + m_defaultScale: 1 + m_LastWindowPixelSize: {x: 1291.0001, y: 818} + m_ClearInEditMode: 1 + m_NoCameraWarning: 1 + m_LowResolutionForAspectRatios: 00000000000000000000 + m_XRRenderMode: 0 + m_RenderTexture: {fileID: 0} + m_showToolbar: 1