展前冒死重构

This commit is contained in:
FloatGaming
2026-07-24 04:43:13 +08:00
parent d09597472b
commit 4eb70b0ac9
61 changed files with 74951 additions and 328 deletions
-24
View File
@@ -1,24 +0,0 @@
<linker>
<assembly fullname="Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.AddressableAssets.Addressables" preserve="all" />
</assembly>
<assembly fullname="Unity.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Localization.Locale" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.SharedTableData" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.StringTable" preserve="all" />
<type fullname="UnityEngine.Localization.LocaleIdentifier" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.MetadataCollection" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.TableEntryData" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.DistributedUIDGenerator" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.SharedTableData/SharedTableEntry" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.SceneProvider" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Object" preserve="all" />
</assembly>
</linker>
+3 -3
View File
@@ -721,7 +721,7 @@ MonoBehaviour:
m_HandleRect: {fileID: 3809938165616928137}
m_Direction: 0
m_Value: 1
m_Size: 1
m_Size: 0.9999999
m_NumberOfSteps: 0
m_OnValueChanged:
m_PersistentCalls:
@@ -3148,7 +3148,7 @@ RectTransform:
m_Father: {fileID: 2889347862386961338}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
@@ -4543,7 +4543,7 @@ MonoBehaviour:
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 3
m_Spacing: 100
m_Spacing: 15
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
@@ -82,7 +82,7 @@ MonoBehaviour:
levelProgressForThisDifficulty: 0
experienceProvidedForThisDifficulty: 0
- difficulty: 2
difficultyLEVEL: 18
difficultyLEVEL: 20
enemyTotalHP_Multiplier: 40
enemyConfigList:
- enemyID: 31202
@@ -96,7 +96,7 @@ MonoBehaviour:
- enemyID: 31202
hpPercentage: 29
difficultyName:
chartFile: {fileID: 4900000, guid: 1333012d19842c24c945e004b06c3be8, type: 3}
chartFile: {fileID: 4900000, guid: 9b669f41f328e6b43a6375f90b02cfed, type: 3}
difficultyDescription:
lastScoreForThisDifficulty: 0
chartPersonalRecordForThisDifficulty: 0
@@ -108,6 +108,6 @@ MonoBehaviour:
current_levelProgress: 0
_if_level_is_EASE: 0
max_comboRecord: 0
thisLevel_selectedDifficultyID: 2
thisLevel_selectedDifficultyID: 0
thisLevel_addDateString: 2026-01-01
thisLevel_overallDescription: "\u201C\u54D7\u5566\u5566......\u201D\n\u7834\u788E\u7684\u955C\u9762\u6620\u51FA\u5979\u7684\u53CC\u76EE\uFF0C\u5728\u90A3\u4E00\u523B\uFF0C\u89C6\u91CE\u91CC\u51FA\u73B0\u4E86\u591A\u4E2A\u4EE5\u5979\u4E3A\u540D\u7684\u5076\u50CF......"
@@ -96,7 +96,7 @@ MonoBehaviour:
- enemyID: 31202
hpPercentage: 40
difficultyName:
chartFile: {fileID: 4900000, guid: dc27d9d67f3698646b7b7c3d926eea3c, type: 3}
chartFile: {fileID: 4900000, guid: 8ab483e7c47c97b41b2fa048fa1d27f9, type: 3}
difficultyDescription:
lastScoreForThisDifficulty: 0
chartPersonalRecordForThisDifficulty: 0
+10 -10
View File
@@ -2212,7 +2212,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 36708243816153243, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_IsActive
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3829753781754538968, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_AnchorMax.y
@@ -17747,7 +17747,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 36708243816153243, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_IsActive
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3829753781754538968, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_AnchorMax.y
@@ -18848,7 +18848,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 36708243816153243, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_IsActive
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3829753781754538968, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_AnchorMax.y
@@ -23757,7 +23757,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 36708243816153243, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_IsActive
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3829753781754538968, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_AnchorMax.y
@@ -25527,7 +25527,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
m_IsActive: 1
--- !u!224 &887418840
RectTransform:
m_ObjectHideFlags: 0
@@ -26344,7 +26344,7 @@ MonoBehaviour:
m_HandleRect: {fileID: 1616564962}
m_Direction: 0
m_Value: 0
m_Size: 0.99999994
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:
m_PersistentCalls:
@@ -52681,7 +52681,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -200.95, y: 53.759}
m_SizeDelta: {x: 282, y: 40}
m_SizeDelta: {x: 279.75, y: 39.75}
m_Pivot: {x: 0, y: 0.5}
--- !u!114 &1826730530
MonoBehaviour:
@@ -54965,7 +54965,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -0.000030517578}
m_SizeDelta: {x: 0, y: 330}
m_SizeDelta: {x: 0, y: 10}
m_Pivot: {x: 0, y: 1}
--- !u!114 &1887597342
MonoBehaviour:
@@ -55294,7 +55294,7 @@ MonoBehaviour:
m_TargetGraphic: {fileID: 1219932954}
m_HandleRect: {fileID: 1219932953}
m_Direction: 0
m_Value: 1
m_Value: 0
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:
@@ -66242,7 +66242,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 36708243816153243, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_IsActive
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3829753781754538968, guid: f18dd7043de95ee4381aadceab744d50, type: 3}
propertyPath: m_AnchorMax.y
+229 -70
View File
@@ -899,7 +899,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 2658d847700da0541942ef5fdcc378b5, type: 3}
m_Sprite: {fileID: 21300000, guid: 440e0141c6eb0144c9e9162f0f278d6b, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -2822,7 +2822,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u7231\u7433\u8FBE\u96C5"
m_text: "\u9ECE\u5999\u5999"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
m_sharedMaterial: {fileID: 6312708493749940865, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
@@ -3772,7 +3772,7 @@ Transform:
m_GameObject: {fileID: 154923898}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: -150, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -12828,7 +12828,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &278391251
Transform:
m_ObjectHideFlags: 0
@@ -14063,6 +14063,7 @@ MonoBehaviour:
great_judge_prefab: {fileID: 2415619590294459312, guid: 5464dbc0e44a5d94a88575c4f00d2977, type: 3}
good_judge_prefab: {fileID: 9004018932644019605, guid: 5f0f778a23a4b7343a7cfc40a7d35fc7, type: 3}
miss_judge_prefab: {fileID: 2647521509301950334, guid: 448c2d848f56b6f42bb169f0be36fd65, type: 3}
trackSkill_prefab: {fileID: 4447276195903411839, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
baseScale: 0.5
useRandomScale: 0
minScaleMultiplier: 0.5
@@ -14076,6 +14077,8 @@ MonoBehaviour:
fadeInTime: 0.25
fadeOutStartTime: 0.25
fadeOutDuration: 0.25
trackSkillUiScaleMultiplier: 0.01
trackSkillSortingOrder: 9000
--- !u!114 &312501159
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -15799,11 +15802,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 96.525
value: 95.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 95.85
value: 96.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -15819,7 +15822,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -15919,11 +15922,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 8.1
value: 8
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -17548,7 +17551,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: 590/590
m_text: 1630/1630
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
m_sharedMaterial: {fileID: -346136068272202111, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
@@ -18208,6 +18211,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!114 &397604453
MonoBehaviour:
@@ -18401,6 +18407,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 826006369}
pausePanel: {fileID: 146358648}
animations: {fileID: 1397448124}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 1
judgeConfig: {fileID: 11400000, guid: f870aa419242e72438b57ac9659f61e9, type: 2}
--- !u!1 &400656413
GameObject:
@@ -18729,6 +18738,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &411632596
BoxCollider2D:
@@ -19115,7 +19127,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: c8f373c5ea610a345a513f33ba768314, type: 3}
m_Sprite: {fileID: 21300000, guid: 7df561841c29f7c4495809fa26c0fd87, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -19377,7 +19389,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 1482ec2642664674f9ec31a7cb9fec90, type: 3}
m_Sprite: {fileID: 21300000, guid: 2658d847700da0541942ef5fdcc378b5, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -19730,7 +19742,7 @@ Transform:
m_GameObject: {fileID: 424277304}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: -150, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -20285,7 +20297,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: 1500/1500
m_text: 590/590
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
m_sharedMaterial: {fileID: -346136068272202111, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
@@ -21012,7 +21024,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &480679343
Transform:
m_ObjectHideFlags: 0
@@ -21140,7 +21152,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u6E29\u59AE"
m_text: "\u8DC3\u6843"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
m_sharedMaterial: {fileID: 6312708493749940865, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
@@ -23762,7 +23774,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &614786766
Transform:
m_ObjectHideFlags: 0
@@ -32337,11 +32349,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 96.525
value: 95.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 95.85
value: 96.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -32357,7 +32369,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -32457,11 +32469,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 8.1
value: 8
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -33430,7 +33442,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &766553121
Transform:
m_ObjectHideFlags: 0
@@ -37044,7 +37056,7 @@ Transform:
m_GameObject: {fileID: 825327586}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: -150, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -38142,7 +38154,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &856873761
Transform:
m_ObjectHideFlags: 0
@@ -38884,7 +38896,7 @@ Transform:
m_GameObject: {fileID: 882707961}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: -150, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -39698,7 +39710,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &919628105
Transform:
m_ObjectHideFlags: 0
@@ -41190,7 +41202,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7df561841c29f7c4495809fa26c0fd87, type: 3}
m_Sprite: {fileID: 21300000, guid: 1482ec2642664674f9ec31a7cb9fec90, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -41370,8 +41382,8 @@ MonoBehaviour:
m_TargetGraphic: {fileID: 1501988772}
m_HandleRect: {fileID: 1501988771}
m_Direction: 0
m_Value: -0.11386139
m_Size: 0.9999692
m_Value: -0.109836064
m_Size: 0.99996895
m_NumberOfSteps: 0
m_OnValueChanged:
m_PersistentCalls:
@@ -41571,7 +41583,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &997867646
Transform:
m_ObjectHideFlags: 0
@@ -43469,6 +43481,11 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1044607013}
m_CullTransparentMesh: 1
--- !u!224 &1052701310 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
m_PrefabInstance: {fileID: 4536750610831610918}
m_PrefabAsset: {fileID: 0}
--- !u!1 &1054652914 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 3022102558446658867, guid: cd3d579a2d65b3241a1812f639db37ec, type: 3}
@@ -44815,6 +44832,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &1090508772
BoxCollider2D:
@@ -47153,6 +47173,7 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 865113943}
- {fileID: 1052701310}
m_Father: {fileID: 1989375500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -47280,7 +47301,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: 620/620
m_text: 570/570
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
m_sharedMaterial: {fileID: -346136068272202111, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
@@ -47377,11 +47398,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 96.525
value: 95.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 95.85
value: 96.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -47397,7 +47418,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -47497,11 +47518,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 8.1
value: 8
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -48824,7 +48845,7 @@ Transform:
m_GameObject: {fileID: 1204483344}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: -150, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -50438,7 +50459,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &1269063488
Transform:
m_ObjectHideFlags: 0
@@ -52482,14 +52503,14 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
slotIndex: 3
maxHP: 1500
currentHP: 1500
maxMana: 300
maxHP: 590
currentHP: 590
maxMana: 400
currentMana: 0
damageResistance: 0
scoreEfficiency: 0.09
scoreEfficiency: 0.01
bmm: {fileID: 1261342020}
attack: 38
attack: 9
baseTrackScore: 1000
perfectRatio: 1
greatRatio: 0.75
@@ -53788,6 +53809,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &1370459979
BoxCollider2D:
@@ -57078,6 +57102,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &1486355885
BoxCollider2D:
@@ -57409,14 +57436,14 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
slotIndex: 2
maxHP: 620
currentHP: 620
maxHP: 570
currentHP: 570
maxMana: 200
currentMana: 0
damageResistance: 0
scoreEfficiency: 0.04
scoreEfficiency: 0.02
bmm: {fileID: 1261342020}
attack: 7
attack: 8
baseTrackScore: 1000
perfectRatio: 1
greatRatio: 0.75
@@ -59395,11 +59422,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 96.525
value: 95.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 95.85
value: 96.5
objectReference: {fileID: 0}
- target: {fileID: 682261594771255014, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -59415,7 +59442,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 4266895856353985475, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -59515,11 +59542,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_SizeDelta.x
value: 8.1
value: 8
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.x
value: 87.75
value: 88.5
objectReference: {fileID: 0}
- target: {fileID: 6759631360171910247, guid: 7e80d4de8ab2ff04a9bf1c54e5a064b1, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -59646,6 +59673,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &1572820069
BoxCollider2D:
@@ -60620,14 +60650,14 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
slotIndex: 1
maxHP: 1630
currentHP: 1630
maxMana: 300
maxHP: 620
currentHP: 620
maxMana: 200
currentMana: 0
damageResistance: 0
scoreEfficiency: 0.09
scoreEfficiency: 0.04
bmm: {fileID: 1261342020}
attack: 38
attack: 7
baseTrackScore: 1000
perfectRatio: 1
greatRatio: 0.75
@@ -62780,7 +62810,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u9ECE\u5999\u5999"
m_text: "\u6E29\u59AE"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
m_sharedMaterial: {fileID: 6312708493749940865, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
@@ -65367,7 +65397,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &1814450260
Transform:
m_ObjectHideFlags: 0
@@ -66739,7 +66769,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: 1630/1630
m_text: 620/620
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
m_sharedMaterial: {fileID: -346136068272202111, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2}
@@ -67386,14 +67416,14 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
slotIndex: 4
maxHP: 590
currentHP: 590
maxMana: 400
maxHP: 1630
currentHP: 1630
maxMana: 300
currentMana: 0
damageResistance: 0
scoreEfficiency: 0.01
scoreEfficiency: 0.09
bmm: {fileID: 1261342020}
attack: 9
attack: 38
baseTrackScore: 1000
perfectRatio: 1
greatRatio: 0.75
@@ -67791,7 +67821,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &1899720854
Transform:
m_ObjectHideFlags: 0
@@ -69106,6 +69136,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &1938112567
BoxCollider2D:
@@ -70307,7 +70340,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u8DC3\u6843"
m_text: "\u58A8\u5F69\u79BB"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
m_sharedMaterial: {fileID: 6312708493749940865, guid: 47babadf44fc728438ae9a924c42227e, type: 2}
@@ -71051,8 +71084,9 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
cameraDashDuration: 0.075
cameraWaveEnterDuration: 0.05
cameraWaveRecoverDuration: 0.05
cameraWaveIntensity: 1
cameraWaveEnterDuration: 0.075
cameraWaveRecoverDuration: 0.075
cameraGlitchEnterDuration: 0.05
cameraGlitchRecoverDuration: 0.05
cameraMonochromeEnterDuration: 0.1
@@ -72696,6 +72730,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &2072610047
BoxCollider2D:
@@ -74077,6 +74114,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &2112693515
BoxCollider2D:
@@ -75537,6 +75577,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!114 &176508373887267660
MonoBehaviour:
@@ -76535,6 +76578,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!61 &2411783544221426195
BoxCollider2D:
@@ -77344,6 +77390,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!114 &3721014538685850233
MonoBehaviour:
@@ -77979,6 +78028,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!114 &4363530835118640843
MonoBehaviour:
@@ -78247,6 +78299,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!114 &4387330747821330999
MonoBehaviour:
@@ -78335,6 +78390,107 @@ RectTransform:
m_AnchoredPosition: {x: -154.3, y: 0}
m_SizeDelta: {x: 29, y: 29}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1001 &4536750610831610918
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 1159013136}
m_Modifications:
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_AnchorMax.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_AnchorMax.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_AnchorMin.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_AnchorMin.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_SizeDelta.x
value: 100
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_SizeDelta.y
value: 100
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2454380874622603429, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4447276195903411839, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_Name
value: trackSkillPrefab
objectReference: {fileID: 0}
- target: {fileID: 4447276195903411839, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 023b66fc78c17124d9840cf8d728b0c7, type: 3}
--- !u!1001 &4666275742349675508
PrefabInstance:
m_ObjectHideFlags: 0
@@ -80587,6 +80743,9 @@ MonoBehaviour:
immediateSettlementButton: {fileID: 0}
pausePanel: {fileID: 0}
animations: {fileID: 0}
useBpmForSpeed: 0
referenceBpm: 120
useMultiSegmentFill: 0
judgeConfig: {fileID: 0}
--- !u!114 &9221553951272782130
MonoBehaviour:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b72cf0d289c3f72498b163dbcbe86dd5
guid: 8895a1a9a0f42cb449dae9edc7ba338e
TextScriptImporter:
externalObjects: {}
userData:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9b669f41f328e6b43a6375f90b02cfed
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8ab483e7c47c97b41b2fa048fa1d27f9
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1eff0b560e1e7b14f95669d4bc24a553
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
using UnityEngine;
using UnityEngine.UI;
public class trackSkillPrefab : MonoBehaviour
{
public Text track_skillName;
public Image track_skillIcon;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e84c9d75ff70c954a9e1828a00a18498
@@ -0,0 +1,301 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &2052636663938950983
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 328570594430980671}
- component: {fileID: 4030790630745708841}
- component: {fileID: 307577278921986757}
m_Layer: 0
m_Name: GameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &328570594430980671
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2052636663938950983}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 9011044283025364914}
- {fileID: 1307227743765364543}
m_Father: {fileID: 2454380874622603429}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &4030790630745708841
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2052636663938950983}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 4
m_Spacing: 0
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &307577278921986757
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2052636663938950983}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalFit: 2
m_VerticalFit: 0
--- !u!1 &4447276195903411839
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2454380874622603429}
- component: {fileID: 5611750753715808825}
m_Layer: 0
m_Name: trackSkillPrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2454380874622603429
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4447276195903411839}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 328570594430980671}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &5611750753715808825
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4447276195903411839}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e84c9d75ff70c954a9e1828a00a18498, type: 3}
m_Name:
m_EditorClassIdentifier:
track_skillName: {fileID: 727773236840584476}
track_skillIcon: {fileID: 4645656031428478769}
--- !u!1 &6674845584449045048
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 9011044283025364914}
- component: {fileID: 4286554360502884719}
- component: {fileID: 4645656031428478769}
m_Layer: 0
m_Name: skill_icon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &9011044283025364914
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6674845584449045048}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 328570594430980671}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 80, y: 80}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4286554360502884719
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6674845584449045048}
m_CullTransparentMesh: 1
--- !u!114 &4645656031428478769
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6674845584449045048}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &7202114771598519337
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1307227743765364543}
- component: {fileID: 20899202308215794}
- component: {fileID: 727773236840584476}
- component: {fileID: 3909682056884181069}
m_Layer: 0
m_Name: skillname
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1307227743765364543
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7202114771598519337}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 328570594430980671}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 231.5, y: -50}
m_SizeDelta: {x: 303, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &20899202308215794
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7202114771598519337}
m_CullTransparentMesh: 1
--- !u!114 &727773236840584476
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7202114771598519337}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: d254d20d93651ae448ff13d10dff30ab, type: 3}
m_FontSize: 70
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 0
m_MaxSize: 70
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: New Text
--- !u!114 &3909682056884181069
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7202114771598519337}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalFit: 2
m_VerticalFit: 2
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 023b66fc78c17124d9840cf8d728b0c7
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -146,7 +146,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 3.5, z: 1}
m_LocalScale: {x: 1, y: 3.75, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 806226407933355351}
@@ -27,7 +27,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: -8.76, z: 0}
m_LocalScale: {x: 1, y: 3.5, z: 1}
m_LocalScale: {x: 1, y: 3.75, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8162722342572194734}
@@ -115,7 +115,7 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 1
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6792274648119312710}
m_Father: {fileID: 0}
@@ -27,7 +27,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 3.5, z: 1}
m_LocalScale: {x: 1, y: 3.75, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8805100122384193708}
@@ -27,7 +27,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 3.5, z: 1}
m_LocalScale: {x: 1, y: 3.75, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6650826276162497469}
@@ -146,7 +146,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 255.5, y: 52.7, z: 0}
m_LocalScale: {x: 1, y: 3.5, z: 1}
m_LocalScale: {x: 1, y: 3.75, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 777335502243705040}
Binary file not shown.
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 158aceb99959cf24aaadd70fe5fe16a9
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -669,7 +669,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_AnchoredPosition: {x: -37.00002, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &7230413747790136415
+20 -1
View File
@@ -562,6 +562,11 @@ public class UI_Idols : MonoBehaviour
SetText(idolBackNameText, hero.ally_heroName);
SetText(idol_chenghaoText, hero.ally_heroDesignation);
SetText(idol_nameText, hero.ally_heroName);
// 名字文本挂 ContentSizeFitter(H+V=Preferred):改文字后布局要到帧末才 rebuild,
// 期间会以旧尺寸显示一帧、下一帧才修正,表现为"名字偏移后恢复"。这里立即强制重建,消除该帧。
ForceRebuildText(idol_nameText);
ForceRebuildText(idolBackNameText);
ForceRebuildText(idol_chenghaoText);
SetIdolDocumentText(hero.ally_heroDescription);
bool animateExpBar = pendingGrowthAnimationHeroId > 0 && hero.ally_heroID == pendingGrowthAnimationHeroId;
SetSliderVisual(idol_expBar, snapshot.sliderValue, snapshot.sliderColor, animateExpBar);
@@ -579,7 +584,8 @@ public class UI_Idols : MonoBehaviour
ApplyBehaviourRadar(hero);
PopulateSpecialSkills(hero);
UI_OrderedEntryAnimator.PlayRefresh(rightPanel, 0.16f, -8f, 0.99f, true);
// 改用只淡入的动画,避免位移导致名字跳动
UI_OrderedEntryAnimator.PlayFadeOnly(rightPanel, 0, 0.2f, 0f, true);
}
private void SetIdolDocumentText(string value)
@@ -889,6 +895,19 @@ public class UI_Idols : MonoBehaviour
}
}
// 改完文字后立即重建其(及自身)布局,让 ContentSizeFitter 当帧算好尺寸,
// 避免"旧尺寸显示一帧→下一帧修正"的偏移跳动。仅对挂了 ContentSizeFitter 的文本有意义,
// 对普通文本也无害(RebuildLayoutImmediate 开销很小、每次切换偶像才调用几次)。
private static void ForceRebuildText(Text target)
{
if (target == null) return;
RectTransform rt = target.rectTransform;
if (rt != null)
{
UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(rt);
}
}
private static void SetImageSprite(Image target, Sprite sprite)
{
if (target == null)
+1
View File
@@ -2053,6 +2053,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
else
{
SkillBuilder.Instance?.TriggerOnAdjacentAllySkillCast(slotIndex);
GameplayLevelRuleEventBus.NotifyAllySkillCast(slotIndex, GameplayClock.NowSongTime);
}
return anyTriggered;
}
+55 -15
View File
@@ -90,19 +90,46 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
SkillBuilder.Instance.TriggerOnEnemyRevive(this);
}
// Trigger primary skill on revive if defined and trigger condition matches
TryTriggerPrimarySkill(SkillDefinition.SkillTrigger.OnEnemyRevive);
// Trigger configured enemy skills on revive if defined and trigger condition matches.
TryTriggerConfiguredSkills(SkillDefinition.SkillTrigger.OnEnemyRevive);
}
// Documentation text normalized.
private void TryTriggerPrimarySkill(SkillDefinition.SkillTrigger when)
private void TryTriggerConfiguredSkills(SkillDefinition.SkillTrigger when)
{
if (sourceData == null) return;
var def = sourceData.GetPrimarySkill();
if (def == null) return;
HashSet<SkillDefinition> executed = new HashSet<SkillDefinition>();
SkillDefinition primary = sourceData.GetPrimarySkill();
ExecuteEnemySkillIfMatched(primary, when, executed);
int[] groupIds = sourceData.GetEffectiveEquippedSkillGroupIDs();
if (groupIds == null || groupIds.Length == 0)
return;
for (int i = 0; i < groupIds.Length; i++)
{
int groupId = groupIds[i];
if (groupId == 0) continue;
SkillGroup group = sourceData.GetSkillGroupByID(groupId);
if (group == null || group.skills == null)
continue;
for (int j = 0; j < group.skills.Length; j++)
{
ExecuteEnemySkillIfMatched(group.skills[j], when, executed);
}
}
}
private void ExecuteEnemySkillIfMatched(SkillDefinition def, SkillDefinition.SkillTrigger when, HashSet<SkillDefinition> executed)
{
if (def == null || executed == null) return;
if (executed.Contains(def)) return;
if (def.triggerCondition != when) return;
// Evaluate formula into amount using enemy fields as variables
executed.Add(def);
float perTick = 0f;
var vars = new Dictionary<string, float>();
vars["maxHP"] = maxHP;
@@ -135,7 +162,6 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
totalAmount = perTick * ticks;
}
// Use SkillBuilder to execute effect, using this Enemy GameObject as caster
if (SkillBuilder.Instance != null)
{
SkillBuilder.Instance.ExecuteSkill(def.displayName ?? def.skillId, def.effectType, totalAmount, def.defaultSelector, this.gameObject, null, def.defaultDuration, def.defaultTickInterval);
@@ -190,6 +216,11 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
}
}
if (actual > 0)
{
GameplayLevelRuleEventBus.NotifyEnemyDamaged(this, actual, source);
}
// grant mana based on sourceData setting
if (sourceData != null && sourceData.manaGainOnDamagePercent > 0f && maxMana > 0)
{
@@ -352,7 +383,16 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
var type = actual > 0 ? iNumberPrefabController.InstantNumberType.Heal : iNumberPrefabController.InstantNumberType.Damage;
iNumberPrefabController.SpawnForEnemyStatic(type, actual);
}
if (currentHP <= 0) Die();
if (currentHP <= 0)
{
if (GameplayLevelRuleEventBus.ShouldPreventEnemyDeath(this))
{
currentHP = 1;
return;
}
Die();
}
}
public void ModifyMana(int delta, bool animate = true)
@@ -371,18 +411,18 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
}
}
// If mana reached full, trigger primary skill and reset mana
// If mana reached full, trigger configured skills and reset mana
if (currentMana >= maxMana && maxMana > 0 && Application.isPlaying)
{
Debug.Log($"[EnemyCombatant] Mana full for {gameObject.name} (slot) - triggering OnManaFull");
try
{
// Trigger primary skill configured for OnManaFull
TryTriggerPrimarySkill(SkillDefinition.SkillTrigger.OnManaFull);
TryTriggerConfiguredSkills(SkillDefinition.SkillTrigger.OnManaFull);
GameplayLevelRuleEventBus.NotifyEnemyManaFull(this);
}
catch (Exception ex)
{
Debug.LogError($"[EnemyCombatant] TryTriggerPrimarySkill threw: {ex}");
Debug.LogError($"[EnemyCombatant] OnManaFull handling threw: {ex}");
}
// reset mana after casting
@@ -457,8 +497,8 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
}
}
// Trigger primary skill on death if configured (enemy's own skill)
TryTriggerPrimarySkill(SkillDefinition.SkillTrigger.OnEnemyDead);
// Trigger configured enemy skills on death if configured.
TryTriggerConfiguredSkills(SkillDefinition.SkillTrigger.OnEnemyDead);
// notify listeners (UI/manager) that enemy died AFTER triggering skills so they operate on the current instance
OnEnemyDied?.Invoke(this);
+23 -2
View File
@@ -1369,6 +1369,10 @@ ResolvedGroup:
}
catch { /* never break gameplay because of optional UI */ }
// Spawn the track skill popup as soon as the skill is confirmed for this frame.
// This avoids later target-resolution branches swallowing the popup on early returns.
SpawnTrackSkillPopup(slotIndex, smallSkillName, smallSkillIcon);
GameObject caster = GetAllyObjectBySlot(slotIndex);
if (caster == null)
{
@@ -1926,7 +1930,10 @@ ResolvedGroup:
}
// For listed skills: do not stack when retriggered, only refresh duration.
if (appliedRefreshOnly) return;
if (appliedRefreshOnly)
{
return;
}
}
// For non-direct path, EffectSystem expects 'amount' to be total amount for sustained effects
@@ -1986,7 +1993,6 @@ ResolvedGroup:
GameplaySkillLogger.RecordSkillRelease(casterDisplayName, skillDisplayName, def.effectType.ToString(), effectSummary, targetSummary);
}
catch { }
return true;
}
@@ -2111,6 +2117,21 @@ ResolvedGroup:
}
}
private static readonly string[] s_trackSkillPopupColors = { "red", "green", "yellow", "purple", "blue" };
private void SpawnTrackSkillPopup(int slotIndex, string skillName, Sprite skillIcon)
{
if (slotIndex < 0 || slotIndex >= s_trackSkillPopupColors.Length)
return;
var fx = Animation_GenerateJudgementSituationPrefab.Instance
?? SceneObjectLookupCache.FindAny<Animation_GenerateJudgementSituationPrefab>();
if (fx == null)
return;
fx.SpawnTrackSkillPrefab(s_trackSkillPopupColors[slotIndex], skillIcon, skillName ?? string.Empty);
}
private bool HandleEquipSkillGhoulSchool(int slotIndex)
{
_equipGhoulSchoolActiveSlots.Add(slotIndex);
+20
View File
@@ -123,6 +123,26 @@ public class EnemyData_SO : ScriptableObject
return availableSkills[primarySkillIndex];
}
public SkillGroup GetSkillGroupByID(int groupId)
{
if (skillGroups == null || skillGroups.Length == 0)
return null;
for (int i = 0; i < skillGroups.Length; i++)
{
SkillGroup group = skillGroups[i];
if (group != null && group.skillGroupID == groupId)
return group;
}
return null;
}
public int[] GetEffectiveEquippedSkillGroupIDs()
{
return equippedSkillGroupIDs ?? System.Array.Empty<int>();
}
// New: groups of skills. Each group contains a numeric skillGroupID and a variable-length list of SkillDefinitions.
[Tooltip("Skill groups defined on this enemy. Each SkillGroup has a numeric skillGroupID which can be equipped into slots to allow activation of the entire group via that ID.")]
public SkillGroup[] skillGroups = new SkillGroup[0];
+18 -4
View File
@@ -24,17 +24,21 @@ public sealed class UICanvasMatchFitter : MonoBehaviour
private void OnEnable()
{
scaler = GetComponent<CanvasScaler>();
Apply();
// 切换 UI 时 Canvas 激活会走到这里。若只是设 match 而不立即同步,
// 序列化的旧 match(=1) 会在激活当帧先生效、下一帧才被本组件改成正确值,
// 造成偏心元素(如偶像名字 anchoredX≈-201)"先偏后正"的一帧跳动。
// 故激活时同步应用并强制刷新 Canvas,消除这一帧时序。
Apply(forceCanvasUpdate: true);
}
private void Update()
{
// 分辨率/窗口变化时才重算。
float aspect = (float)Screen.width / Mathf.Max(1, Screen.height);
if (Mathf.Abs(aspect - lastAspect) > 0.0001f) Apply();
if (Mathf.Abs(aspect - lastAspect) > 0.0001f) Apply(forceCanvasUpdate: false);
}
private void Apply()
private void Apply(bool forceCanvasUpdate)
{
if (scaler == null) scaler = GetComponent<CanvasScaler>();
if (scaler == null) return;
@@ -45,6 +49,16 @@ public sealed class UICanvasMatchFitter : MonoBehaviour
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
// 更宽→锁高度(1);更窄→锁宽度(0)。等于始终匹配“更受限的一侧”,设计区域完整可见。
scaler.matchWidthOrHeight = aspect >= DesignAspect ? 1f : 0f;
float target = aspect >= DesignAspect ? 1f : 0f;
bool changed = !Mathf.Approximately(scaler.matchWidthOrHeight, target);
scaler.matchWidthOrHeight = target;
if (forceCanvasUpdate && changed)
{
// 让 CanvasScaler 立即按新 match 重算 scaleFactor、并在同一帧完成布局,
// 避免"旧 scale 定位一帧 → 下一帧修正"的可见跳动。
// ForceUpdateCanvases 会驱动 CanvasScaler 重算并刷新布局。
Canvas.ForceUpdateCanvases();
}
}
}
@@ -63,6 +63,7 @@ public sealed class UIResolutionAutoFitter : MonoBehaviour
_enemyFollowAttached = false;
_trackStabilizerAttached = false;
_cameraTunerAttached = false;
_trackScalerAttached = false;
StartCoroutine(FitAfterFrame());
}
@@ -301,9 +302,22 @@ public sealed class UIResolutionAutoFitter : MonoBehaviour
_cameraTunerAttached = true;
}
}
// 轨道缩放器:挂到 effectEventController(已是单例集中点)或任意对象,运行时找 track1-5 拉长。
if (!_trackScalerAttached)
{
var eff = effectEventController.Instance;
if (eff != null)
{
if (eff.GetComponent<TrackScaler>() == null)
eff.gameObject.AddComponent<TrackScaler>();
_trackScalerAttached = true;
}
}
}
private bool _trackStabilizerAttached;
private bool _cameraTunerAttached;
private bool _trackScalerAttached;
// gameplay 敌人容器(普通 Transform,非 RectTransform):让其跟随屏幕左缘。
// 敌人显示对象是 enemyObject_toWiggle,其定位父级是名为 empty 的普通 Transform
@@ -425,6 +425,7 @@ public static class FirstRunFactoryResetService
string[] keysToDelete =
{
"noteSpeedMultiplier",
"noteSpeedMultiplierDefaultVersion",
"UserGlobalDelaySeconds",
"screenMode",
"resolutionIndex",
@@ -1,5 +1,7 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
{
@@ -23,16 +25,21 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
public GameObject good_judge_prefab;
public GameObject miss_judge_prefab;
[Header("Inspector")]
public GameObject trackSkill_prefab;
[Header("Inspector")]
public float baseScale = 1.0f;
public bool useRandomScale = true;
public float minScaleMultiplier = 0.8f;
public float maxScaleMultiplier = 1.2f;
[Header("Inspector")]
public float jumpForce = 5.0f; // Documentation text normalized.
public float gravity = -16f; // Documentation text normalized.
[Tooltip("Horizontal drift distance range for judge popup text. Adds a real arc instead of a straight pop.")]
[System.Serializable]
public class PopupMotionSettings
{
public float jumpForce = 5.0f;
public float gravity = -16f;
[Tooltip("Horizontal drift distance range for popup text. Adds a real arc instead of a straight pop.")]
public Vector2 horizontalDriftRange = new Vector2(0.25f, 0.65f);
[Tooltip("Random Z rotation range applied over the popup lifetime.")]
public float rotationRange = 8f;
@@ -40,34 +47,60 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
public float scalePunch = 0.18f;
[Tooltip("How long the initial scale punch takes before settling back.")]
public float scalePunchDuration = 0.18f;
public float fadeInTime = 0.1f;
public float fadeOutStartTime = 0.6f;
public float fadeOutDuration = 0.35f;
}
[Header("Inspector")]
public float fadeInTime = 0.1f; // Documentation text normalized.
public float fadeOutStartTime = 0.6f; // Documentation text normalized.
public float fadeOutDuration = 0.35f; // Documentation text normalized.
[Header("Judge Popup")]
public PopupMotionSettings judgePopup = new PopupMotionSettings()
{
jumpForce = 7.2f,
gravity = -16f,
horizontalDriftRange = new Vector2(0.25f, 0.65f),
rotationRange = 8f,
scalePunch = 0.18f,
scalePunchDuration = 0.18f,
fadeInTime = 0.1f,
fadeOutStartTime = 0.3f,
fadeOutDuration = 0.1f
};
[Header("Track Skill Popup")]
public PopupMotionSettings trackSkillPopup = new PopupMotionSettings()
{
jumpForce = 6.8f,
gravity = -12.5f,
horizontalDriftRange = new Vector2(0.18f, 0.48f),
rotationRange = 6f,
scalePunch = 0.14f,
scalePunchDuration = 0.16f,
fadeInTime = 0.08f,
fadeOutStartTime = 0.36f,
fadeOutDuration = 0.1f
};
[Header("Track Skill UI")]
[Tooltip("UI prefab spawned under world-space Effect nodes needs a small world scale. Judge SpriteRenderer prefabs still use baseScale directly.")]
public float trackSkillUiScaleMultiplier = 0.01f;
public int trackSkillSortingOrder = 9000;
// Cache spawn transforms to avoid accessing destroyed GameObject references.
private Transform redSpawn;
private Transform greenSpawn;
private Transform yellowSpawn;
private Transform purpleSpawn;
private Transform blueSpawn;
// Track last scene to detect scene changes
private int lastSceneIndex = -1;
private Coroutine prewarmJudgeCoroutine;
private readonly System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<GameObject>> judgePools
= new System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<GameObject>>();
private readonly System.Collections.Generic.HashSet<GameObject> rentedJudges
= new System.Collections.Generic.HashSet<GameObject>();
private readonly Dictionary<GameObject, Stack<GameObject>> judgePools = new Dictionary<GameObject, Stack<GameObject>>();
private readonly HashSet<GameObject> rentedJudges = new HashSet<GameObject>();
private void Awake()
{
if (Instance == null)
{
Instance = this;
// Documentation text normalized.
// Documentation text normalized.
}
else if (Instance != this)
{
@@ -81,13 +114,11 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
private void Start()
{
// Ensure spawn points are cached after all scene objects are initialized
CacheSpawnPointsIfNeeded();
}
private void OnEnable()
{
// Detect scene reload and refresh cache
int currentSceneIndex = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex;
if (currentSceneIndex != lastSceneIndex)
{
@@ -103,10 +134,8 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
private void CacheSpawnPointsIfNeeded()
{
// Only recache if any of the references appear to be invalid (destroyed)
if (redSpawn == null || greenSpawn == null || yellowSpawn == null || purpleSpawn == null || blueSpawn == null)
{
// Check if the effect GameObjects are still valid
bool anyInvalid = (redEffect == null || redEffect.transform == null) ||
(greenEffect == null || greenEffect.transform == null) ||
(yellowEffect == null || yellowEffect.transform == null) ||
@@ -122,7 +151,6 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
private void CacheSpawnPoints()
{
// Important: check Unity "fake null" before accessing .transform
redSpawn = redEffect != null ? redEffect.transform : null;
greenSpawn = greenEffect != null ? greenEffect.transform : null;
yellowSpawn = yellowEffect != null ? yellowEffect.transform : null;
@@ -131,7 +159,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
if (JudgeManager.IsDebugEnabled)
{
Debug.Log($"[Animation_Generate] Cached spawn points: red={redSpawn!=null}, green={greenSpawn!=null}, yellow={yellowSpawn!=null}, purple={purpleSpawn!=null}, blue={blueSpawn!=null}");
Debug.Log($"[Animation_Generate] Cached spawn points: red={redSpawn != null}, green={greenSpawn != null}, yellow={yellowSpawn != null}, purple={purpleSpawn != null}, blue={blueSpawn != null}");
}
}
@@ -172,13 +200,37 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
instance.transform.localPosition = Vector3.zero;
instance.transform.localRotation = Quaternion.identity;
// Documentation text normalized.
float finalScale = baseScale;
if (useRandomScale) finalScale *= Random.Range(minScaleMultiplier, maxScaleMultiplier);
instance.transform.localScale = new Vector3(finalScale, finalScale, 1f);
// Documentation text normalized.
StartCoroutine(AnimateSprite(instance));
StartCoroutine(AnimatePopup(instance, judgePopup, false));
}
public void SpawnTrackSkillPrefab(string color, Sprite skillIcon, string skillName)
{
if (string.IsNullOrEmpty(color) || trackSkill_prefab == null)
return;
Transform spawnPoint = GetSpawnPoint(color);
if (spawnPoint == null)
return;
GameObject instance = Instantiate(trackSkill_prefab, spawnPoint, false);
if (instance == null)
return;
EnsureRenderableTrackSkillInstance(instance);
BindTrackSkillContent(instance, skillIcon, skillName);
instance.transform.localPosition = Vector3.zero;
instance.transform.localRotation = Quaternion.identity;
float finalScale = baseScale;
if (useRandomScale) finalScale *= Random.Range(minScaleMultiplier, maxScaleMultiplier);
ApplyTrackSkillScale(instance, finalScale);
instance.SetActive(true);
StartCoroutine(AnimatePopup(instance, trackSkillPopup, true));
}
public void PrewarmJudgePrefabs(int perPrefab = 1)
@@ -231,78 +283,98 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
prewarmJudgeCoroutine = null;
}
private IEnumerator AnimateSprite(GameObject obj)
private IEnumerator AnimatePopup(GameObject obj, PopupMotionSettings settings, bool destroyAtEnd = false)
{
if (obj == null) yield break;
if (settings == null) settings = judgePopup;
SpriteRenderer[] renderers = obj.GetComponentsInChildren<SpriteRenderer>(true);
Graphic[] graphics = obj.GetComponentsInChildren<Graphic>(true);
CanvasGroup canvasGroup = obj.GetComponent<CanvasGroup>();
float elapsed = 0f;
float vVelocity = jumpForce;
float totalLifeTime = fadeOutStartTime + fadeOutDuration;
float vVelocity = settings.jumpForce;
float fadeDuration = Mathf.Max(0.001f, settings.fadeOutDuration);
float estimatedApexTime = settings.gravity < -0.001f ? Mathf.Max(0f, settings.jumpForce / -settings.gravity) : settings.fadeOutStartTime;
float totalLifeTime = Mathf.Max(settings.fadeInTime + fadeDuration, estimatedApexTime + fadeDuration);
float driftSign = Random.value < 0.5f ? -1f : 1f;
float driftDistance = Random.Range(horizontalDriftRange.x, horizontalDriftRange.y) * driftSign;
float driftDistance = Random.Range(settings.horizontalDriftRange.x, settings.horizontalDriftRange.y) * driftSign;
float horizontalVelocity = totalLifeTime > 0.001f ? driftDistance / totalLifeTime : 0f;
float targetRotation = Random.Range(-rotationRange, rotationRange);
float targetRotation = Random.Range(-settings.rotationRange, settings.rotationRange);
Vector3 currentLocalPos = Vector3.zero;
Vector3 baseScaleValue = obj.transform.localScale;
bool fadeOutStarted = false;
float fadeOutElapsed = 0f;
float fadeOutStartAlpha = 1f;
float currentAlpha = 0f;
SetRenderersAlpha(renderers, 0f);
SetGraphicsAlpha(graphics, canvasGroup, 0f);
// Documentation text normalized.
while (elapsed < totalLifeTime)
while (!fadeOutStarted || fadeOutElapsed < fadeDuration)
{
if (obj == null) yield break;
float dt = Time.deltaTime;
elapsed += dt;
// Documentation text normalized.
vVelocity += gravity * dt;
float previousVelocity = vVelocity;
vVelocity += settings.gravity * dt;
currentLocalPos.x += horizontalVelocity * dt;
currentLocalPos.y += vVelocity * dt;
obj.transform.localPosition = currentLocalPos;
obj.transform.localRotation = Quaternion.Euler(0f, 0f, Mathf.Lerp(0f, targetRotation, Mathf.Clamp01(elapsed / totalLifeTime)));
float scaleT = scalePunchDuration > 0.001f ? Mathf.Clamp01(elapsed / scalePunchDuration) : 1f;
float punch = Mathf.Sin(scaleT * Mathf.PI) * scalePunch;
float scaleT = settings.scalePunchDuration > 0.001f ? Mathf.Clamp01(elapsed / settings.scalePunchDuration) : 1f;
float punch = Mathf.Sin(scaleT * Mathf.PI) * settings.scalePunch;
obj.transform.localScale = baseScaleValue * (1f + punch);
// Documentation text normalized.
float alpha;
bool reachedApex = previousVelocity > 0f && vVelocity <= 0f;
if (!fadeOutStarted && (reachedApex || elapsed >= totalLifeTime - fadeDuration))
{
fadeOutStarted = true;
fadeOutElapsed = 0f;
fadeOutStartAlpha = currentAlpha;
}
// Documentation text normalized.
if (elapsed < fadeInTime)
float alpha;
if (fadeOutStarted)
{
alpha = Mathf.InverseLerp(0f, fadeInTime, elapsed);
fadeOutElapsed += dt;
alpha = Mathf.Lerp(fadeOutStartAlpha, 0f, Mathf.Clamp01(fadeOutElapsed / fadeDuration));
}
// Documentation text normalized.
else if (elapsed > fadeOutStartTime)
else if (elapsed < settings.fadeInTime)
{
// Documentation text normalized.
alpha = Mathf.InverseLerp(totalLifeTime, fadeOutStartTime, elapsed);
alpha = Mathf.InverseLerp(0f, settings.fadeInTime, elapsed);
}
// Documentation text normalized.
else
{
alpha = 1f;
}
SetRenderersAlpha(renderers, Mathf.Clamp01(alpha));
alpha = Mathf.Clamp01(alpha);
currentAlpha = alpha;
SetRenderersAlpha(renderers, alpha);
SetGraphicsAlpha(graphics, canvasGroup, alpha);
yield return null;
}
// Documentation text normalized.
if (obj != null)
{
obj.transform.localScale = baseScaleValue;
obj.transform.localRotation = Quaternion.identity;
SetRenderersAlpha(renderers, 0f);
SetGraphicsAlpha(graphics, canvasGroup, 0f);
}
// Documentation text normalized.
if (obj != null) ReturnJudgeToPool(obj);
if (obj != null)
{
if (destroyAtEnd)
Destroy(obj);
else
ReturnJudgeToPool(obj);
}
}
private static void SetRenderersAlpha(SpriteRenderer[] renderers, float alpha)
@@ -322,6 +394,104 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
}
}
private static void SetGraphicsAlpha(Graphic[] graphics, CanvasGroup canvasGroup, float alpha)
{
if (canvasGroup != null)
{
canvasGroup.alpha = alpha;
return;
}
if (graphics == null)
return;
for (int i = 0; i < graphics.Length; i++)
{
Graphic graphic = graphics[i];
if (graphic == null)
continue;
Color c = graphic.color;
c.a = alpha;
graphic.color = c;
}
}
private void EnsureRenderableTrackSkillInstance(GameObject instance)
{
if (instance == null)
return;
if (instance.GetComponentInChildren<Graphic>(true) == null)
return;
Canvas canvas = instance.GetComponent<Canvas>();
if (canvas == null)
canvas = instance.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
canvas.overrideSorting = true;
canvas.sortingOrder = trackSkillSortingOrder;
canvas.pixelPerfect = false;
CanvasGroup canvasGroup = instance.GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = instance.AddComponent<CanvasGroup>();
canvasGroup.alpha = 1f;
}
private void ApplyTrackSkillScale(GameObject instance, float finalScale)
{
if (instance == null)
return;
bool isUiPrefab = instance.GetComponentInChildren<Graphic>(true) != null;
float appliedScale = isUiPrefab ? finalScale * trackSkillUiScaleMultiplier : finalScale;
instance.transform.localScale = new Vector3(appliedScale, appliedScale, 1f);
}
private void BindTrackSkillContent(GameObject instance, Sprite skillIcon, string skillName)
{
if (instance == null)
return;
Image targetImage = null;
Text targetText = null;
TrackSkillPrefab modern = instance.GetComponent<TrackSkillPrefab>();
if (modern != null)
{
modern.Bind(skillIcon, skillName);
targetImage = modern.skillImage;
targetText = modern.skillText;
}
else
{
trackSkillPrefab legacy = instance.GetComponent<trackSkillPrefab>();
if (legacy != null)
{
targetImage = legacy.track_skillIcon;
targetText = legacy.track_skillName;
}
}
if (targetImage == null)
targetImage = instance.GetComponentInChildren<Image>(true);
if (targetText == null)
targetText = instance.GetComponentInChildren<Text>(true);
if (targetImage != null)
{
targetImage.sprite = skillIcon;
targetImage.enabled = skillIcon != null;
}
if (targetText != null)
{
targetText.text = string.IsNullOrWhiteSpace(skillName) ? string.Empty : skillName;
}
}
private GameObject GetPooledJudge(GameObject prefab, Transform parent)
{
if (prefab == null || parent == null)
@@ -329,7 +499,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
if (!judgePools.TryGetValue(prefab, out var pool))
{
pool = new System.Collections.Generic.Stack<GameObject>();
pool = new Stack<GameObject>();
judgePools[prefab] = pool;
}
@@ -384,7 +554,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
if (!judgePools.TryGetValue(tag.prefabSource, out var pool))
{
pool = new System.Collections.Generic.Stack<GameObject>();
pool = new Stack<GameObject>();
judgePools[tag.prefabSource] = pool;
}
@@ -399,8 +569,6 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
{
if (string.IsNullOrEmpty(color)) return null;
// Documentation text normalized.
Transform point = null;
switch (color.ToLower())
{
@@ -1,55 +1,55 @@
using UnityEngine;
/// <summary>
/// 根据屏幕宽高比,对 gameplay 相机做"适当微调":叠加一点 X 旋转(俯角)与 Z 距离增量
/// 目的:不同分辨率下让轨道透视观感更协调(如超宽屏略微拉远/调俯角),与 contain 式 FOV 适配互补。
/// 根据屏幕宽高比,对 gameplay 相机做分辨率适配,让宽屏下轨道观感更接近设计(16:9)
///
/// 安全性(核心):**绝不直接写相机 transform**。所有微调通过 effectEventController.SetResolutionCameraAdjust
/// 施加到"漂移 pivot"(顶层基准节点),并由该方法同步漂移基准 z——因此与相机漂移、SmoothShake 抖动、
/// 技能偏移、冲刺特效等所有"写相机位姿"的业务逻辑完全不冲突(它们叠加在 pivot 之下或相机自身)。
/// 需求(用户指定):越宽的屏幕(21:9/32:9)轨道越缩在中间、显细,左右与 UI 空出大黑缝。
/// 解决方案:宽屏时把相机从设计基准(z=-8, X=-40°)逐步插值到"z 更高 + X 更朝水平"的目标位姿,
/// 让轨道透视放大、铺满视野,同时保持判定线距离/视距/业务逻辑不变(音符/判定用世界坐标,不受相机缩放影响)。
///
/// 用一个 bool(enableTuning)开关。关闭时把增量清零(恢复设计基准),行为与未加此脚本一致。
/// 增量相对"设计基准"计算,不累积;16:9 时增量为 0(与原设计逐像素一致)。
/// 操作对象:**直接改相机本体(Main Camera 子物体)自身的 local z 高度与 local X 旋转**
/// 不动其父/祖父 pivot(drift/skill pivot)。通过 effectEventController.SetResolutionCameraSelf 施加,
/// 由该方法处理与 SmoothShake(写相机 local)、冲刺(写相机 local z)的共享——空闲时写入持久生效,
/// 抖动/冲刺触发时会以当前 local 重新捕获基准。不碰任何 UI:UI 贴边逻辑独立,本脚本只改相机位姿。
///
/// 用一个 bool(enableTuning)开关。关闭时恢复设计基准(与未加此脚本逐像素一致)。
/// 16:9 及更窄(平板 1.6、竖屏)保持设计基准不动;宽屏线性插值;21:9 及更宽夹在最大偏移(用户指定的上限)。
/// </summary>
[DisallowMultipleComponent]
public sealed class CameraResolutionTuner : MonoBehaviour
{
[Tooltip("是否启用按分辨率微调相机(X旋转/Z)。关闭则恢复设计基准。")]
[Tooltip("是否启用按分辨率微调相机。关闭则恢复设计基准。")]
public bool enableTuning = true;
[Tooltip("设计基准宽高比。16:9 = 1.7778。此比例下增量为 0。")]
[Tooltip("设计基准宽高比。16:9 = 1.7778。此比例及更窄时无微调。")]
public float designAspect = 16f / 9f;
[Header("宽屏(aspect > 设计)微调")]
[Tooltip("每偏离设计宽高比 1.0,额外叠加的 X 旋转角度(正=更俯视)。建议很小。")]
public float widePerAspectRotX = 3f;
[Tooltip("每偏离设计宽高比 1.0,额外叠加的 Z 距离(负=相机后退拉远)。")]
public float widePerAspectZ = -1.0f;
[Tooltip("宽屏微调的绝对上限,避免极端超宽屏过度。")]
public float wideMaxRotX = 6f;
public float wideMaxZ = -2.0f;
[Tooltip("最大偏移的宽高比上限。21:9 = 2.3333。达到或超过此比例时夹在最大偏移。")]
public float maxOffsetAspect = 21f / 9f;
[Header("窄屏(aspect < 设计)微调")]
[Tooltip("每偏离设计宽高比 1.0,额外叠加的 X 旋转角度。")]
public float narrowPerAspectRotX = 0f;
[Tooltip("每偏离设计宽高比 1.0,额外叠加的 Z 距离。")]
public float narrowPerAspectZ = 0f;
public float narrowMaxRotX = 4f;
public float narrowMaxZ = 2.0f;
[Header("21:9 时相机本体(Main Camera)的目标 local 位姿(最大偏移)")]
[Tooltip("21:9 时相机本体的目标 local Z 高度。用户指定 2.5(相机自身 z=2.5)。")]
public float targetCameraZ = 2.5f;
[Tooltip("21:9 时相机本体的目标 local X 旋转角度。用户指定 5(相机自身 X=5°)。")]
public float targetCameraRotX = 5f;
private float _lastAspect = -1f;
private bool _lastEnabled;
private bool _applied; // 上次 Apply 是否成功写入相机(相机/单例未就绪时为 false,下一帧重试)
private void OnEnable()
{
_lastAspect = -1f;
_applied = false;
Apply();
}
private void Update()
{
float aspect = (float)Screen.width / Mathf.Max(1, Screen.height);
if (Mathf.Abs(aspect - _lastAspect) > 0.0001f || enableTuning != _lastEnabled)
// 宽高比/开关变化,或此前尚未成功写入(相机/单例未就绪),则重试。
if (!_applied || Mathf.Abs(aspect - _lastAspect) > 0.0001f || enableTuning != _lastEnabled)
{
Apply();
}
@@ -58,7 +58,7 @@ public sealed class CameraResolutionTuner : MonoBehaviour
private void Apply()
{
var eff = effectEventController.Instance;
if (eff == null) return;
if (eff == null) { _applied = false; return; }
float aspect = (float)Screen.width / Mathf.Max(1, Screen.height);
_lastAspect = aspect;
@@ -66,26 +66,30 @@ public sealed class CameraResolutionTuner : MonoBehaviour
if (!enableTuning)
{
eff.SetResolutionCameraAdjust(0f, 0f); // 恢复设计基准
// 关闭:t=0 → 相机本体回到 local 设计基准(运行时为 0/identity)。
_applied = eff.SetResolutionCameraSelf(0f, targetCameraRotX, targetCameraZ);
return;
}
float rotX = 0f;
float z = 0f;
// 16:9 及更窄:t=0(无微调,与设计逐像素一致)。
// 16:9 ~ 21:9:线性插值 t ∈ [0,1]。
// 21:9 及更宽:t=1(夹在最大偏移上限)。
float t = 0f;
if (aspect > designAspect)
{
float dev = aspect - designAspect; // 宽屏偏离量
rotX = Mathf.Clamp(widePerAspectRotX * dev, Mathf.Min(0f, wideMaxRotX), Mathf.Max(0f, wideMaxRotX));
z = Mathf.Clamp(widePerAspectZ * dev, Mathf.Min(0f, wideMaxZ), Mathf.Max(0f, wideMaxZ));
}
else if (aspect < designAspect)
if (aspect >= maxOffsetAspect)
{
float dev = designAspect - aspect; // 窄屏偏离量
rotX = Mathf.Clamp(narrowPerAspectRotX * dev, Mathf.Min(0f, narrowMaxRotX), Mathf.Max(0f, narrowMaxRotX));
z = Mathf.Clamp(narrowPerAspectZ * dev, Mathf.Min(0f, narrowMaxZ), Mathf.Max(0f, narrowMaxZ));
t = 1f; // 超宽屏:夹在最大偏移
}
// 16:9rotX=z=0,无微调。
else
{
// 插值区间:designAspect(16:9≈1.778) → maxOffsetAspect(21:9≈2.333)
t = (aspect - designAspect) / Mathf.Max(0.0001f, maxOffsetAspect - designAspect);
t = Mathf.Clamp01(t);
}
}
// aspect <= designAspectt=0(无微调)。
eff.SetResolutionCameraAdjust(rotX, z);
_applied = eff.SetResolutionCameraSelf(t, targetCameraRotX, targetCameraZ);
}
}
+77 -11
View File
@@ -12,6 +12,23 @@ public class HoldNote : BaseNote
private string noteID = string.Empty;
private NoteSegment segment = NoteSegment.None;
// 获取用于生成判定 prefab 的颜色。如果 noteColor 为空(初始化异常),从 trackIndex 推导兜底。
private string GetColorForPrefab()
{
if (!string.IsNullOrEmpty(noteColor)) return noteColor;
if (trackIndex >= 0 && trackIndex < 5)
{
string[] trackColors = { "red", "green", "yellow", "purple", "blue" };
string fallback = trackColors[trackIndex];
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[HoldNote] noteColor was null, using trackIndex {trackIndex} -> {fallback}");
return fallback;
}
Debug.LogError($"[HoldNote] Cannot derive color: noteColor is null and trackIndex {trackIndex} is invalid");
return null;
}
private float releaseTime = 0f;
private bool hasReleased = true;
private bool hasEnteredLine = false;
@@ -810,19 +827,20 @@ public class HoldNote : BaseNote
return;
}
// If the key is still being held down when end segment enters judge zone, immediately judge.
// Autoplay must ignore physical input to guarantee Perfect at scheduledEndTime.
if (!GameConfig.autoPlayEnabled && IsHeld() && JudgeManager.Instance.IsStartJudged(noteID) && !isJudged)
// END 段到达判定区时,如果头部已判定且玩家还没松手,立即自动结束(不等松手)。
// 让玩家"长按到尾部"即视为完成,无需精确松手时机——更符合直觉且降低难度。
// Autoplay 必须忽略物理输入,保证在 scheduledEndTime 精确判定 Perfect。
if (JudgeManager.Instance.IsStartJudged(noteID) && !JudgeManager.Instance.HasNoteReleased(noteID) && !isJudged)
{
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] End entered while key still held: {noteColor}, forcing Perfect");
// Record release time as current time (player is still holding)
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] End entered while note not yet released: {noteColor}, auto-finishing hold as Perfect");
// 记录松手时间为当前时刻(玩家按到尾部视为完美松手)
releaseTime = GameplayClock.NowSongTime;
hasReleased = true;
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
// Evaluate and judge the hold end - force Perfect since we reached the end while holding
// 判定并生成 prefab——force Perfect(按到尾部=完美完成)
EvaluateHoldEnd(releaseTime, false, true);
isJudged = true;
// Terminate input - mark key as released
// 终止输入:标记键已松开,停止持续特效
isHoldActive = false;
StopHoldFxLoop();
return;
@@ -1056,6 +1074,7 @@ public class HoldNote : BaseNote
if (result != "Miss")
{
TryRewriteNonMissJudgeToPerfect(trackIndex, ref result);
GameplayLevelRuleEventBus.NotifyHoldStarted(trackIndex, result, pressTime, noteData);
}
// show judge result and update combo/UI like short notes
@@ -1079,10 +1098,30 @@ public class HoldNote : BaseNote
TrackJudgeHitEffectController.PlayTrackHitFx(trackIndex);
}
// Do not spawn the judgement animation prefab for Start (head) presses to avoid duplicated prefabs
if (segment != NoteSegment.Start)
// Spawn judgement prefab:
// - START 段:只有 Miss 时才生成(点中的 Perfect/Good 不生成,避免头尾都弹;但 Miss 必须立即反馈)
// - END/BODY 段:总是生成(包括 Miss)
if (segment != NoteSegment.Start || result == "Miss")
{
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, result);
string colorForPrefab = GetColorForPrefab();
if (JudgeManager.IsDebugEnabled)
{
Debug.Log($"[HoldNote] Attempting to spawn prefab: segment={segment}, result={result}, noteColor={noteColor}, trackIndex={trackIndex}, derivedColor={colorForPrefab}, Instance={(Animation_GenerateJudgementSituationPrefab.Instance != null ? "EXISTS" : "NULL")}");
}
if (string.IsNullOrEmpty(colorForPrefab))
{
Debug.LogError($"[HoldNote] Cannot spawn prefab: colorForPrefab is null! noteColor={noteColor}, trackIndex={trackIndex}, segment={segment}, result={result}");
}
else if (Animation_GenerateJudgementSituationPrefab.Instance == null)
{
Debug.LogError($"[HoldNote] Cannot spawn prefab: Animation_GenerateJudgementSituationPrefab.Instance is NULL!");
}
else
{
Animation_GenerateJudgementSituationPrefab.Instance.SpawnJudgePrefab(colorForPrefab, result);
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] SpawnJudgePrefab called successfully for {colorForPrefab}, {result}");
}
}
if (segment != NoteSegment.Start)
@@ -1239,6 +1278,15 @@ public class HoldNote : BaseNote
AdjustCountsAfterRewriteToPerfect(trackIndex, originalEndJudge);
}
if (result == "Miss")
{
GameplayLevelRuleEventBus.NotifyHoldBroken(trackIndex, result, actualReleaseTime, noteData);
}
else
{
GameplayLevelRuleEventBus.NotifyHoldCompleted(trackIndex, result, actualReleaseTime, noteData);
}
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] END result for {noteID}: {result} (color={noteColor})");
// show UI/audio
@@ -1257,7 +1305,25 @@ public class HoldNote : BaseNote
}
// Ensure END always spawns judgement prefab (including Miss)
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, result);
string colorForPrefab = GetColorForPrefab();
if (JudgeManager.IsDebugEnabled)
{
Debug.Log($"[HoldNote.EvaluateHoldEnd] Attempting to spawn END prefab: result={result}, noteColor={noteColor}, trackIndex={trackIndex}, derivedColor={colorForPrefab}, Instance={(Animation_GenerateJudgementSituationPrefab.Instance != null ? "EXISTS" : "NULL")}");
}
if (string.IsNullOrEmpty(colorForPrefab))
{
Debug.LogError($"[HoldNote.EvaluateHoldEnd] Cannot spawn END prefab: colorForPrefab is null! noteColor={noteColor}, trackIndex={trackIndex}, result={result}");
}
else if (Animation_GenerateJudgementSituationPrefab.Instance == null)
{
Debug.LogError($"[HoldNote.EvaluateHoldEnd] Cannot spawn END prefab: Animation_GenerateJudgementSituationPrefab.Instance is NULL!");
}
else
{
Animation_GenerateJudgementSituationPrefab.Instance.SpawnJudgePrefab(colorForPrefab, result);
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote.EvaluateHoldEnd] SpawnJudgePrefab called successfully for {colorForPrefab}, {result}");
}
// --- Add scoring for End like short notes ---
try
@@ -12,7 +12,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 088ed1ed6b6731f43ad3166781e3e931, type: 3}
m_Name: NoteJudgeConfig
m_EditorClassIdentifier:
perfectRange: 0.15
greatRange: 0.2
goodRange: 0.25
missRange: 0.3
perfectRange: 0.1
greatRange: 0.15
goodRange: 0.2
missRange: 0.25
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2c5b2c74f439e604e8ffdbc589075131
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
using System;
using UnityEngine;
public static class GameplayLevelRuleEventBus
{
public static event Action<int, string, float, NoteData> TapJudged;
public static event Action<int, string, float, NoteData> HoldStarted;
public static event Action<int, string, float, NoteData> HoldCompleted;
public static event Action<int, string, float, NoteData> HoldBroken;
public static event Action<int, float> AllySkillCast;
public static event Action<EnemyCombatant, int, EnemyData_SO> EnemySpawned;
public static event Action<EnemyCombatant, int, EnemyData_SO> EnemyDied;
public static event Action<EnemyCombatant, float, GameObject> EnemyDamaged;
public static event Action<EnemyCombatant> EnemyManaFull;
public static void NotifyTapJudged(int trackIndex, string result, float songTime, NoteData noteData)
{
TapJudged?.Invoke(trackIndex, result, songTime, noteData);
}
public static void NotifyHoldStarted(int trackIndex, string result, float songTime, NoteData noteData)
{
HoldStarted?.Invoke(trackIndex, result, songTime, noteData);
}
public static void NotifyHoldCompleted(int trackIndex, string result, float songTime, NoteData noteData)
{
HoldCompleted?.Invoke(trackIndex, result, songTime, noteData);
}
public static void NotifyHoldBroken(int trackIndex, string result, float songTime, NoteData noteData)
{
HoldBroken?.Invoke(trackIndex, result, songTime, noteData);
}
public static void NotifyAllySkillCast(int slotIndex, float songTime)
{
AllySkillCast?.Invoke(slotIndex, songTime);
}
public static void NotifyEnemySpawned(EnemyCombatant enemy, int stageIndex, EnemyData_SO data)
{
EnemySpawned?.Invoke(enemy, stageIndex, data);
}
public static void NotifyEnemyDied(EnemyCombatant enemy, int stageIndex, EnemyData_SO data)
{
EnemyDied?.Invoke(enemy, stageIndex, data);
}
public static void NotifyEnemyDamaged(EnemyCombatant enemy, float actualDamage, GameObject source)
{
EnemyDamaged?.Invoke(enemy, actualDamage, source);
}
public static void NotifyEnemyManaFull(EnemyCombatant enemy)
{
EnemyManaFull?.Invoke(enemy);
}
public static bool ShouldPreventEnemyDeath(EnemyCombatant enemy)
{
return LevelRuleController.Instance != null && LevelRuleController.Instance.ShouldPreventEnemyDeath(enemy);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: eaf68fc7a5ecfce41bb49bc77dfc89cf
@@ -0,0 +1,142 @@
using System;
using UnityEngine;
[CreateAssetMenu(fileName = "NewLevelRuleConfig", menuName = "Bansonic/Gameplay/Level Rule Config")]
public class LevelRuleConfig_SO : ScriptableObject
{
[Header("Match")]
public int songID = 0;
public string songName = string.Empty;
[Tooltip("-1 means any difficulty.")]
public int difficulty = -1;
[Header("Support Track")]
public bool useLeastNoteTrackAsSupportTrack = false;
[Tooltip("Used when useLeastNoteTrackAsSupportTrack is false. 0-4. -1 disables support-track logic.")]
public int supportTrackIndex = -1;
public int maxSupportCharges = 3;
public int adjacentSkillCastsPerSupportCharge = 3;
public float adjacentSkillCastWindow = 5f;
[Header("Echo")]
public bool enableAdjacentSkillEcho = false;
public float echoWindow = 1.5f;
public int echoThreshold = 3;
public int maxEchoStacks = 4;
public float echoResistancePerStack = 0.06f;
public float echoResistanceDuration = 4f;
public int echoManaPenalty = 0;
[Header("Conditional Rewards")]
public int allAlliesAliveScoreOnEnemyDeath = 0;
public bool awardAllAlliesAliveScorePerEnemy = false;
[Tooltip("At settlement: average living HP percent * this value is added as team score.")]
public int finalAverageHpPercentScore = 0;
[Header("Stages")]
public LevelRuleStage[] stages = Array.Empty<LevelRuleStage>();
public bool Matches(SongData song, int currentDifficulty)
{
if (song == null)
return false;
bool idMatches = songID != 0 && song.songID == songID;
bool nameMatches = !string.IsNullOrWhiteSpace(songName) && string.Equals(song.songName, songName, StringComparison.OrdinalIgnoreCase);
if (!idMatches && !nameMatches)
return false;
return difficulty < 0 || difficulty == currentDifficulty;
}
}
[Serializable]
public class LevelRuleStage
{
[Tooltip("-1 means match by current stage index. If enemyID > 0, enemy ID also has to match.")]
public int stageIndex = -1;
public int enemyID = 0;
[Tooltip("If > 0, this enemy is held at 1 HP before this song time.")]
public float minimumDeathSongTime = -1f;
public bool clearEchoOnSpawn = true;
public bool clearSupportChargesOnSpawn = false;
public LevelRuleAction[] onSpawn = Array.Empty<LevelRuleAction>();
public LevelRuleAction[] onManaFull = Array.Empty<LevelRuleAction>();
public LevelRuleAction[] onDeath = Array.Empty<LevelRuleAction>();
public LevelRuleAction[] onDamaged = Array.Empty<LevelRuleAction>();
public LevelRuleHoldAction[] onHoldStart = Array.Empty<LevelRuleHoldAction>();
public LevelRuleHoldAction[] onHoldComplete = Array.Empty<LevelRuleHoldAction>();
public LevelRuleHoldAction[] onHoldBreak = Array.Empty<LevelRuleHoldAction>();
public LevelRuleHpPhase[] hpPhases = Array.Empty<LevelRuleHpPhase>();
public bool Matches(int currentStageIndex, EnemyData_SO data)
{
if (stageIndex >= 0 && stageIndex != currentStageIndex)
return false;
if (enemyID > 0 && (data == null || data.enemyID != enemyID))
return false;
return stageIndex >= 0 || enemyID > 0;
}
}
[Serializable]
public class LevelRuleHpPhase
{
[Range(0f, 1f)]
public float hpPercentThreshold = 0.5f;
public bool triggerOnce = true;
public LevelRuleAction[] actions = Array.Empty<LevelRuleAction>();
}
[Serializable]
public class LevelRuleHoldAction
{
public LevelRuleAction[] actions = Array.Empty<LevelRuleAction>();
}
[Serializable]
public class LevelRuleAction
{
public string actionId = string.Empty;
public LevelRuleActionType actionType = LevelRuleActionType.None;
public LevelRuleTarget target = LevelRuleTarget.CurrentEnemy;
public float amount = 0f;
public bool amountScalesByDifficultyLevel = false;
public bool amountIsPercentOfMaxHp = false;
public float duration = 0f;
public int maxTriggersPerEnemy = 0;
public bool onlyIfAllAlliesAlive = false;
public bool requireSupportCharge = false;
public int scoreReward = 0;
}
public enum LevelRuleActionType
{
None,
Damage,
Heal,
ManaDelta,
AttackDelta,
ResistanceDeltaTimed,
HealReceivedMultiplierTimed,
AddTeamScore,
DamageCurrentEnemyByOwnAttack,
SpendSupportChargeForEnemyVulnerability,
ClearEnemyMana
}
public enum LevelRuleTarget
{
CurrentEnemy,
AllLivingAllies,
TriggerTrackAlly,
HighestCurrentManaAlly,
HighestAttackAlly,
HighestRecentHitTrackAlly,
HighestRecentSkillCastAlly
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0795ac37be9814449ab3d2b48aa3fdad
@@ -0,0 +1,921 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelRuleController : MonoBehaviour
{
public static LevelRuleController Instance { get; private set; }
[Header("Rule Configs")]
public string resourcesPath = "so/levelRules";
public LevelRuleConfig_SO[] configOverrides = Array.Empty<LevelRuleConfig_SO>();
[Header("Runtime")]
[SerializeField] private bool verboseLogs = false;
private readonly Queue<float>[] recentHitTimes = new Queue<float>[5];
private readonly Queue<float>[] recentSkillCastTimes = new Queue<float>[5];
private readonly Dictionary<string, Coroutine> timedRoutines = new Dictionary<string, Coroutine>();
private readonly Dictionary<string, int> actionTriggerCounts = new Dictionary<string, int>();
private readonly HashSet<string> firedHpPhases = new HashSet<string>();
private LevelRuleConfig_SO activeConfig;
private LevelRuleStage currentStageRule;
private EnemyCombatant currentEnemy;
private EnemyData_SO currentEnemyData;
private SongData currentSong;
private int currentDifficulty = -1;
private int currentStageIndex = -1;
private int supportTrackIndex = -1;
private int supportCharges;
private int adjacentSupportCastCount;
private int lastSupportCastSlot = -1;
private float lastSupportCastTime = -999f;
private int echoStacks;
private int lastEchoCastSlot = -1;
private float lastEchoCastTime = -999f;
private float nextConfigResolveTime;
private int pendingSettlementScoreBonus;
private bool handlingEnemyDamagedActions;
public int PendingSettlementScoreBonus => Mathf.Max(0, pendingSettlementScoreBonus);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void Bootstrap()
{
EnsureInstance();
}
public static LevelRuleController EnsureInstance()
{
if (Instance != null)
return Instance;
LevelRuleController existing = SceneObjectLookupCache.FindAny<LevelRuleController>();
if (existing != null)
{
Instance = existing;
return existing;
}
GameObject runtimeObject = new GameObject("__level_rule_controller_runtime");
DontDestroyOnLoad(runtimeObject);
Instance = runtimeObject.AddComponent<LevelRuleController>();
return Instance;
}
public static int ConsumePendingSettlementScoreBonus()
{
if (Instance == null)
return 0;
if (GameConfig.autoPlayEnabled)
{
Instance.pendingSettlementScoreBonus = 0;
return 0;
}
Instance.AddFinalAverageHpScoreIfConfigured();
int value = Mathf.Max(0, Instance.pendingSettlementScoreBonus);
Instance.pendingSettlementScoreBonus = 0;
return value;
}
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else if (Instance != this)
{
Destroy(gameObject);
return;
}
for (int i = 0; i < recentHitTimes.Length; i++)
{
recentHitTimes[i] = new Queue<float>();
recentSkillCastTimes[i] = new Queue<float>();
}
}
private void OnEnable()
{
SceneManager.sceneLoaded += HandleSceneLoaded;
GameplayLevelRuleEventBus.TapJudged += HandleTapJudged;
GameplayLevelRuleEventBus.HoldStarted += HandleHoldStarted;
GameplayLevelRuleEventBus.HoldCompleted += HandleHoldCompleted;
GameplayLevelRuleEventBus.HoldBroken += HandleHoldBroken;
GameplayLevelRuleEventBus.AllySkillCast += HandleAllySkillCast;
GameplayLevelRuleEventBus.EnemySpawned += HandleEnemySpawned;
GameplayLevelRuleEventBus.EnemyDied += HandleEnemyDied;
GameplayLevelRuleEventBus.EnemyDamaged += HandleEnemyDamaged;
GameplayLevelRuleEventBus.EnemyManaFull += HandleEnemyManaFull;
TryResolveActiveConfig(true);
}
private void OnDisable()
{
SceneManager.sceneLoaded -= HandleSceneLoaded;
GameplayLevelRuleEventBus.TapJudged -= HandleTapJudged;
GameplayLevelRuleEventBus.HoldStarted -= HandleHoldStarted;
GameplayLevelRuleEventBus.HoldCompleted -= HandleHoldCompleted;
GameplayLevelRuleEventBus.HoldBroken -= HandleHoldBroken;
GameplayLevelRuleEventBus.AllySkillCast -= HandleAllySkillCast;
GameplayLevelRuleEventBus.EnemySpawned -= HandleEnemySpawned;
GameplayLevelRuleEventBus.EnemyDied -= HandleEnemyDied;
GameplayLevelRuleEventBus.EnemyDamaged -= HandleEnemyDamaged;
GameplayLevelRuleEventBus.EnemyManaFull -= HandleEnemyManaFull;
}
private void Update()
{
if (Time.unscaledTime < nextConfigResolveTime)
return;
nextConfigResolveTime = Time.unscaledTime + 0.5f;
TryResolveActiveConfig(false);
}
private void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
{
ResetRuntimeState();
nextConfigResolveTime = 0f;
TryResolveActiveConfig(true);
}
private void TryResolveActiveConfig(bool force)
{
BeatmapManager beatmapManager = BeatmapManager.Instance;
if (beatmapManager == null || beatmapManager.assignedSongData == null)
{
if (force)
SetActiveConfig(null, null, -1);
return;
}
SongData song = beatmapManager.assignedSongData;
int difficulty = beatmapManager.assignedDifficulty;
if (!force && activeConfig != null && currentSong == song && currentDifficulty == difficulty)
return;
LevelRuleConfig_SO matched = FindConfig(song, difficulty);
SetActiveConfig(matched, song, difficulty);
}
private LevelRuleConfig_SO FindConfig(SongData song, int difficulty)
{
if (configOverrides != null)
{
for (int i = 0; i < configOverrides.Length; i++)
{
LevelRuleConfig_SO config = configOverrides[i];
if (config != null && config.Matches(song, difficulty))
return config;
}
}
LevelRuleConfig_SO[] resources = Resources.LoadAll<LevelRuleConfig_SO>(resourcesPath);
if (resources != null)
{
for (int i = 0; i < resources.Length; i++)
{
LevelRuleConfig_SO config = resources[i];
if (config != null && config.Matches(song, difficulty))
return config;
}
}
return null;
}
private void SetActiveConfig(LevelRuleConfig_SO config, SongData song, int difficulty)
{
if (activeConfig == config && currentSong == song && currentDifficulty == difficulty)
return;
activeConfig = config;
currentSong = song;
currentDifficulty = difficulty;
ResetRuntimeState();
if (activeConfig != null)
{
supportTrackIndex = ResolveSupportTrackIndex();
if (verboseLogs)
Debug.Log($"[LevelRuleController] Active rule: {activeConfig.name}, supportTrack={supportTrackIndex}");
}
}
private void ResetRuntimeState()
{
currentStageRule = null;
currentEnemy = null;
currentEnemyData = null;
currentStageIndex = -1;
supportTrackIndex = -1;
supportCharges = 0;
adjacentSupportCastCount = 0;
lastSupportCastSlot = -1;
lastSupportCastTime = -999f;
echoStacks = 0;
lastEchoCastSlot = -1;
lastEchoCastTime = -999f;
pendingSettlementScoreBonus = 0;
actionTriggerCounts.Clear();
firedHpPhases.Clear();
for (int i = 0; i < recentHitTimes.Length; i++)
{
recentHitTimes[i].Clear();
recentSkillCastTimes[i].Clear();
}
foreach (Coroutine routine in timedRoutines.Values)
{
if (routine != null)
StopCoroutine(routine);
}
timedRoutines.Clear();
}
private int ResolveSupportTrackIndex()
{
if (activeConfig == null)
return -1;
if (!activeConfig.useLeastNoteTrackAsSupportTrack)
return activeConfig.supportTrackIndex >= 0 && activeConfig.supportTrackIndex < 5 ? activeConfig.supportTrackIndex : -1;
Beatmap beatmap = BeatmapManager.Instance != null ? BeatmapManager.Instance.beatmap : null;
if (beatmap == null || beatmap.notes == null || beatmap.notes.Length == 0)
return activeConfig.supportTrackIndex >= 0 && activeConfig.supportTrackIndex < 5 ? activeConfig.supportTrackIndex : -1;
int[] counts = new int[5];
for (int i = 0; i < beatmap.notes.Length; i++)
{
int track = beatmap.notes[i] != null ? beatmap.notes[i].trackIndex : -1;
if (track >= 0 && track < counts.Length)
counts[track]++;
}
int bestTrack = 0;
int bestCount = int.MaxValue;
for (int i = 0; i < counts.Length; i++)
{
if (counts[i] < bestCount)
{
bestCount = counts[i];
bestTrack = i;
}
}
return bestTrack;
}
private void HandleTapJudged(int trackIndex, string result, float songTime, NoteData noteData)
{
if (activeConfig == null)
return;
if (IsHit(result))
RecordRecent(recentHitTimes, trackIndex, songTime, 10f);
}
private void HandleHoldStarted(int trackIndex, string result, float songTime, NoteData noteData)
{
if (activeConfig == null || currentStageRule == null)
return;
if (IsHit(result))
RecordRecent(recentHitTimes, trackIndex, songTime, 10f);
ExecuteHoldActions(currentStageRule.onHoldStart, trackIndex);
}
private void HandleHoldCompleted(int trackIndex, string result, float songTime, NoteData noteData)
{
if (activeConfig == null || currentStageRule == null)
return;
if (IsHit(result))
RecordRecent(recentHitTimes, trackIndex, songTime, 10f);
ExecuteHoldActions(currentStageRule.onHoldComplete, trackIndex);
}
private void HandleHoldBroken(int trackIndex, string result, float songTime, NoteData noteData)
{
if (activeConfig == null || currentStageRule == null)
return;
ExecuteHoldActions(currentStageRule.onHoldBreak, trackIndex);
}
private void HandleAllySkillCast(int slotIndex, float songTime)
{
if (activeConfig == null)
return;
RecordRecent(recentSkillCastTimes, slotIndex, songTime, 10f);
UpdateSupportCharge(slotIndex, songTime);
UpdateEcho(slotIndex, songTime);
}
private void HandleEnemySpawned(EnemyCombatant enemy, int stageIndex, EnemyData_SO data)
{
if (activeConfig == null)
return;
currentEnemy = enemy;
currentEnemyData = data;
currentStageIndex = stageIndex;
currentStageRule = ResolveStageRule(stageIndex, data);
firedHpPhases.Clear();
actionTriggerCounts.Clear();
if (currentStageRule == null)
return;
if (currentStageRule.clearEchoOnSpawn)
ClearEcho();
if (currentStageRule.clearSupportChargesOnSpawn)
supportCharges = 0;
ExecuteActions(currentStageRule.onSpawn, -1, enemy);
}
private void HandleEnemyDied(EnemyCombatant enemy, int stageIndex, EnemyData_SO data)
{
if (activeConfig == null)
return;
LevelRuleStage stage = ResolveStageRule(stageIndex, data);
if (stage != null)
ExecuteActions(stage.onDeath, -1, enemy);
if (activeConfig.awardAllAlliesAliveScorePerEnemy && activeConfig.allAlliesAliveScoreOnEnemyDeath > 0 && AreAllConfiguredAlliesAlive())
AddPendingScore(activeConfig.allAlliesAliveScoreOnEnemyDeath);
}
private void HandleEnemyDamaged(EnemyCombatant enemy, float actualDamage, GameObject source)
{
if (activeConfig == null || currentStageRule == null || enemy == null || enemy != currentEnemy)
return;
if (!handlingEnemyDamagedActions)
{
handlingEnemyDamagedActions = true;
try
{
ExecuteActions(currentStageRule.onDamaged, -1, enemy);
}
finally
{
handlingEnemyDamagedActions = false;
}
}
EvaluateHpPhases(enemy);
}
private void HandleEnemyManaFull(EnemyCombatant enemy)
{
if (activeConfig == null || currentStageRule == null || enemy == null || enemy != currentEnemy)
return;
ExecuteActions(currentStageRule.onManaFull, -1, enemy);
}
public bool ShouldPreventEnemyDeath(EnemyCombatant enemy)
{
if (activeConfig == null || currentStageRule == null || enemy == null || enemy != currentEnemy)
return false;
if (currentStageRule.minimumDeathSongTime <= 0f)
return false;
return GameplayClock.NowSongTime < currentStageRule.minimumDeathSongTime;
}
private LevelRuleStage ResolveStageRule(int stageIndex, EnemyData_SO data)
{
if (activeConfig == null || activeConfig.stages == null)
return null;
for (int i = 0; i < activeConfig.stages.Length; i++)
{
LevelRuleStage stage = activeConfig.stages[i];
if (stage != null && stage.Matches(stageIndex, data))
return stage;
}
return null;
}
private void ExecuteHoldActions(LevelRuleHoldAction[] groups, int triggerTrack)
{
if (groups == null)
return;
for (int i = 0; i < groups.Length; i++)
{
LevelRuleHoldAction group = groups[i];
if (group == null)
continue;
ExecuteActions(group.actions, triggerTrack, currentEnemy);
}
}
private void ExecuteActions(LevelRuleAction[] actions, int triggerTrack, EnemyCombatant eventEnemy)
{
if (actions == null)
return;
for (int i = 0; i < actions.Length; i++)
ExecuteAction(actions[i], triggerTrack, eventEnemy);
}
private void ExecuteAction(LevelRuleAction action, int triggerTrack, EnemyCombatant eventEnemy)
{
if (action == null || action.actionType == LevelRuleActionType.None)
return;
if (action.onlyIfAllAlliesAlive && !AreAllConfiguredAlliesAlive())
return;
string key = BuildActionKey(action, eventEnemy);
if (action.maxTriggersPerEnemy > 0)
{
actionTriggerCounts.TryGetValue(key, out int count);
if (count >= action.maxTriggersPerEnemy)
return;
actionTriggerCounts[key] = count + 1;
}
if (action.requireSupportCharge && supportCharges <= 0)
return;
if (action.actionType == LevelRuleActionType.AddTeamScore)
{
AddPendingScore(Mathf.RoundToInt(ResolveRawAmount(action)));
if (action.scoreReward > 0)
AddPendingScore(action.scoreReward);
return;
}
if (action.actionType == LevelRuleActionType.SpendSupportChargeForEnemyVulnerability)
{
if (supportCharges <= 0 || currentEnemy == null)
return;
supportCharges--;
ApplyTimedResistance(currentEnemy, -Mathf.Abs(ResolveRawAmount(action)), Mathf.Max(0.01f, action.duration), key);
if (action.scoreReward > 0)
AddPendingScore(action.scoreReward);
return;
}
List<GameObject> targets = ResolveTargets(action.target, triggerTrack);
for (int i = 0; i < targets.Count; i++)
{
GameObject target = targets[i];
if (target == null)
continue;
ApplyActionToTarget(action, target, key);
}
}
private void ApplyActionToTarget(LevelRuleAction action, GameObject target, string key)
{
AllyCombatant ally = target.GetComponent<AllyCombatant>();
EnemyCombatant enemy = target.GetComponent<EnemyCombatant>();
float amount = ResolveAmountForTarget(action, ally, enemy);
switch (action.actionType)
{
case LevelRuleActionType.Damage:
if (ally != null) ally.ReceiveDamage(amount, currentEnemy != null ? currentEnemy.gameObject : null);
else if (enemy != null) enemy.ReceiveDamage(amount, currentEnemy != null ? currentEnemy.gameObject : null);
break;
case LevelRuleActionType.Heal:
if (ally != null) ally.ReceiveHeal(amount, currentEnemy != null ? currentEnemy.gameObject : null);
else if (enemy != null) enemy.ReceiveHeal(amount, currentEnemy != null ? currentEnemy.gameObject : null);
break;
case LevelRuleActionType.ManaDelta:
if (ally != null) ally.ModifyMana(Mathf.RoundToInt(amount), true, true);
else if (enemy != null) enemy.ModifyMana(Mathf.RoundToInt(amount), true);
break;
case LevelRuleActionType.AttackDelta:
if (ally != null) ally.ModifyAttack(Mathf.RoundToInt(amount));
else if (enemy != null) enemy.ModifyAttack(Mathf.RoundToInt(amount));
break;
case LevelRuleActionType.ResistanceDeltaTimed:
if (enemy != null) ApplyTimedResistance(enemy, amount, Mathf.Max(0.01f, action.duration), key);
break;
case LevelRuleActionType.HealReceivedMultiplierTimed:
ApplyTimedHealMultiplier(target, amount, Mathf.Max(0.01f, action.duration), key);
break;
case LevelRuleActionType.DamageCurrentEnemyByOwnAttack:
if (currentEnemy != null) currentEnemy.ReceiveDamage(Mathf.Max(0, currentEnemy.attack), currentEnemy.gameObject);
break;
case LevelRuleActionType.ClearEnemyMana:
if (enemy != null) enemy.ModifyMana(-enemy.currentMana, true);
break;
}
}
private float ResolveRawAmount(LevelRuleAction action)
{
if (action == null)
return 0f;
float amount = action.amount;
if (action.amountScalesByDifficultyLevel)
amount *= GetCurrentDifficultyLevel();
return amount;
}
private float ResolveAmountForTarget(LevelRuleAction action, AllyCombatant ally, EnemyCombatant enemy)
{
float amount = ResolveRawAmount(action);
if (!action.amountIsPercentOfMaxHp)
return amount;
if (ally != null)
return ally.maxHP * amount;
if (enemy != null)
return enemy.maxHP * amount;
return amount;
}
private List<GameObject> ResolveTargets(LevelRuleTarget target, int triggerTrack)
{
List<GameObject> targets = new List<GameObject>();
teamUIController ui = teamUIController.Instance;
switch (target)
{
case LevelRuleTarget.CurrentEnemy:
if (currentEnemy != null) targets.Add(currentEnemy.gameObject);
break;
case LevelRuleTarget.AllLivingAllies:
AddAllLivingAllies(targets, ui);
break;
case LevelRuleTarget.TriggerTrackAlly:
AddAllyBySlot(targets, ui, triggerTrack);
break;
case LevelRuleTarget.HighestCurrentManaAlly:
AddAllyBySlot(targets, ui, FindBestAllySlotByMana(ui));
break;
case LevelRuleTarget.HighestAttackAlly:
AddAllyBySlot(targets, ui, FindBestAllySlotByAttack(ui));
break;
case LevelRuleTarget.HighestRecentHitTrackAlly:
AddAllyBySlot(targets, ui, FindMostRecentCountTrack(recentHitTimes, 8f));
break;
case LevelRuleTarget.HighestRecentSkillCastAlly:
AddAllyBySlot(targets, ui, FindMostRecentCountTrack(recentSkillCastTimes, 8f));
break;
}
return targets;
}
private void AddAllLivingAllies(List<GameObject> targets, teamUIController ui)
{
if (ui == null)
return;
for (int i = 0; i < 5; i++)
{
GameObject go = ui.GetAllyObjectBySlot(i);
AllyCombatant ally = go != null ? go.GetComponent<AllyCombatant>() : null;
if (ally != null && !ally.IsDead && ally.maxHP > 0)
targets.Add(go);
}
}
private void AddAllyBySlot(List<GameObject> targets, teamUIController ui, int slot)
{
if (ui == null || slot < 0 || slot >= 5)
return;
GameObject go = ui.GetAllyObjectBySlot(slot);
if (go != null)
targets.Add(go);
}
private int FindBestAllySlotByMana(teamUIController ui)
{
int bestSlot = -1;
int bestMana = int.MinValue;
if (ui == null)
return -1;
for (int i = 0; i < 5; i++)
{
AllyCombatant ally = ui.GetAllyObjectBySlot(i)?.GetComponent<AllyCombatant>();
if (ally == null || ally.IsDead || ally.maxHP <= 0)
continue;
if (ally.currentMana > bestMana)
{
bestMana = ally.currentMana;
bestSlot = i;
}
}
return bestSlot;
}
private int FindBestAllySlotByAttack(teamUIController ui)
{
int bestSlot = -1;
int bestAttack = int.MinValue;
if (ui == null)
return -1;
for (int i = 0; i < 5; i++)
{
AllyCombatant ally = ui.GetAllyObjectBySlot(i)?.GetComponent<AllyCombatant>();
if (ally == null || ally.IsDead || ally.maxHP <= 0)
continue;
if (ally.attack > bestAttack)
{
bestAttack = ally.attack;
bestSlot = i;
}
}
return bestSlot;
}
private void UpdateSupportCharge(int slotIndex, float songTime)
{
if (activeConfig == null || supportTrackIndex < 0 || activeConfig.adjacentSkillCastsPerSupportCharge <= 0)
return;
if (lastSupportCastSlot >= 0 && Mathf.Abs(slotIndex - lastSupportCastSlot) == 1 && songTime - lastSupportCastTime <= activeConfig.adjacentSkillCastWindow)
adjacentSupportCastCount++;
else
adjacentSupportCastCount = 1;
lastSupportCastSlot = slotIndex;
lastSupportCastTime = songTime;
if (adjacentSupportCastCount >= activeConfig.adjacentSkillCastsPerSupportCharge)
{
supportCharges = Mathf.Clamp(supportCharges + 1, 0, Mathf.Max(0, activeConfig.maxSupportCharges));
adjacentSupportCastCount = 0;
if (verboseLogs) Debug.Log($"[LevelRuleController] Support charge +1 => {supportCharges}");
}
}
private void UpdateEcho(int slotIndex, float songTime)
{
if (activeConfig == null || !activeConfig.enableAdjacentSkillEcho)
return;
if (lastEchoCastSlot >= 0 && Mathf.Abs(slotIndex - lastEchoCastSlot) == 1 && songTime - lastEchoCastTime <= activeConfig.echoWindow)
echoStacks = Mathf.Clamp(echoStacks + 1, 0, Mathf.Max(1, activeConfig.maxEchoStacks));
else
echoStacks = 1;
lastEchoCastSlot = slotIndex;
lastEchoCastTime = songTime;
if (echoStacks >= activeConfig.echoThreshold && currentEnemy != null)
{
float delta = Mathf.Clamp(activeConfig.echoResistancePerStack * echoStacks, 0f, 0.6f);
ApplyTimedResistance(currentEnemy, delta, Mathf.Max(0.01f, activeConfig.echoResistanceDuration), "echo_resistance");
if (activeConfig.echoManaPenalty > 0)
{
GameObject allyGo = teamUIController.Instance != null ? teamUIController.Instance.GetAllyObjectBySlot(slotIndex) : null;
AllyCombatant ally = allyGo != null ? allyGo.GetComponent<AllyCombatant>() : null;
ally?.ModifyMana(-activeConfig.echoManaPenalty, true, true);
}
}
}
private void ClearEcho()
{
echoStacks = 0;
lastEchoCastSlot = -1;
lastEchoCastTime = -999f;
}
private void EvaluateHpPhases(EnemyCombatant enemy)
{
if (currentStageRule.hpPhases == null || enemy.maxHP <= 0)
return;
float hpPercent = Mathf.Clamp01((float)enemy.currentHP / enemy.maxHP);
for (int i = 0; i < currentStageRule.hpPhases.Length; i++)
{
LevelRuleHpPhase phase = currentStageRule.hpPhases[i];
if (phase == null)
continue;
string key = $"{currentStageIndex}:{enemy.GetInstanceID()}:phase:{i}";
if (phase.triggerOnce && firedHpPhases.Contains(key))
continue;
if (hpPercent <= phase.hpPercentThreshold)
{
firedHpPhases.Add(key);
ExecuteActions(phase.actions, -1, enemy);
}
}
}
private void ApplyTimedResistance(EnemyCombatant enemy, float delta, float duration, string key)
{
if (enemy == null)
return;
string routineKey = $"res:{enemy.GetInstanceID()}:{key}";
StopTimedRoutine(routineKey);
timedRoutines[routineKey] = StartCoroutine(TimedResistanceRoutine(enemy, delta, duration, routineKey));
}
private IEnumerator TimedResistanceRoutine(EnemyCombatant enemy, float delta, float duration, string routineKey)
{
if (enemy == null)
yield break;
enemy.damageResistance = Mathf.Clamp(enemy.damageResistance + delta, -0.95f, 0.6f);
yield return GameplayClock.WaitForSeconds(duration);
if (enemy != null)
enemy.damageResistance = Mathf.Clamp(enemy.damageResistance - delta, -0.95f, 0.6f);
timedRoutines.Remove(routineKey);
}
private void ApplyTimedHealMultiplier(GameObject target, float multiplier, float duration, string key)
{
ICombatant combatant = target != null ? target.GetComponent<ICombatant>() : null;
if (combatant == null)
return;
string routineKey = $"heal:{target.GetInstanceID()}:{key}";
StopTimedRoutine(routineKey);
timedRoutines[routineKey] = StartCoroutine(TimedHealMultiplierRoutine(target, combatant, multiplier, duration, routineKey));
}
private IEnumerator TimedHealMultiplierRoutine(GameObject target, ICombatant combatant, float multiplier, float duration, string routineKey)
{
if (target == null || combatant == null)
yield break;
Buff buff = new Buff(routineKey)
{
description = "Level rule heal received multiplier",
duration = duration,
healReceivedMultiplier = Mathf.Max(0f, multiplier)
};
combatant.ApplyBuff(buff, currentEnemy != null ? currentEnemy.gameObject : null);
yield return GameplayClock.WaitForSeconds(duration);
if (target != null)
combatant.RemoveBuff(routineKey);
timedRoutines.Remove(routineKey);
}
private void StopTimedRoutine(string key)
{
if (timedRoutines.TryGetValue(key, out Coroutine routine) && routine != null)
StopCoroutine(routine);
timedRoutines.Remove(key);
}
private void RecordRecent(Queue<float>[] buffers, int slot, float songTime, float keepWindow)
{
if (slot < 0 || slot >= buffers.Length)
return;
Queue<float> queue = buffers[slot];
queue.Enqueue(songTime);
while (queue.Count > 0 && songTime - queue.Peek() > keepWindow)
queue.Dequeue();
}
private int FindMostRecentCountTrack(Queue<float>[] buffers, float window)
{
float now = GameplayClock.NowSongTime;
int bestTrack = -1;
int bestCount = -1;
for (int i = 0; i < buffers.Length; i++)
{
Queue<float> queue = buffers[i];
while (queue.Count > 0 && now - queue.Peek() > window)
queue.Dequeue();
if (queue.Count > bestCount)
{
bestCount = queue.Count;
bestTrack = i;
}
}
return bestTrack;
}
private bool AreAllConfiguredAlliesAlive()
{
teamUIController ui = teamUIController.Instance;
if (ui == null)
return false;
bool hasAny = false;
for (int i = 0; i < 5; i++)
{
AllyCombatant ally = ui.GetAllyObjectBySlot(i)?.GetComponent<AllyCombatant>();
if (ally == null || ally.maxHP <= 0)
continue;
hasAny = true;
if (ally.IsDead)
return false;
}
return hasAny;
}
private int GetCurrentDifficultyLevel()
{
BeatmapManager beatmapManager = BeatmapManager.Instance;
SongData song = beatmapManager != null ? beatmapManager.assignedSongData : currentSong;
int difficulty = beatmapManager != null ? beatmapManager.assignedDifficulty : currentDifficulty;
if (song != null && song.chartFiles != null)
{
for (int i = 0; i < song.chartFiles.Count; i++)
{
ChartFileEntry entry = song.chartFiles[i];
if (entry != null && entry.difficulty == difficulty)
return Mathf.Max(1, Mathf.RoundToInt(entry.difficultyLEVEL));
}
}
return Mathf.Max(1, difficulty);
}
private void AddPendingScore(int amount)
{
if (amount <= 0 || GameConfig.autoPlayEnabled)
return;
pendingSettlementScoreBonus += amount;
if (verboseLogs) Debug.Log($"[LevelRuleController] Pending score +{amount}, total={pendingSettlementScoreBonus}");
}
private void AddFinalAverageHpScoreIfConfigured()
{
if (activeConfig == null || activeConfig.finalAverageHpPercentScore <= 0)
return;
teamUIController ui = teamUIController.Instance;
if (ui == null)
return;
float sumPercent = 0f;
int count = 0;
for (int i = 0; i < 5; i++)
{
AllyCombatant ally = ui.GetAllyObjectBySlot(i)?.GetComponent<AllyCombatant>();
if (ally == null || ally.maxHP <= 0)
continue;
sumPercent += Mathf.Clamp01((float)Mathf.Max(0, ally.currentHP) / ally.maxHP);
count++;
}
if (count <= 0)
return;
AddPendingScore(Mathf.RoundToInt((sumPercent / count) * activeConfig.finalAverageHpPercentScore));
}
private static bool IsHit(string result)
{
return !string.Equals(result, "Miss", StringComparison.OrdinalIgnoreCase);
}
private string BuildActionKey(LevelRuleAction action, EnemyCombatant enemy)
{
string id = !string.IsNullOrWhiteSpace(action.actionId) ? action.actionId : action.actionType.ToString();
int enemyId = enemy != null ? enemy.GetInstanceID() : 0;
return $"{currentStageIndex}:{enemyId}:{id}";
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8a0b6ecf635b99f40b1e485ca8180e6e
+65 -12
View File
@@ -5,6 +5,23 @@ public class Note : BaseNote
{
private KeyCode keyToPress;
private string noteColor;
// 获取用于生成判定 prefab 的颜色。如果 noteColor 为空(初始化异常),从 TrackIndex 推导兜底。
private string GetColorForPrefab()
{
if (!string.IsNullOrEmpty(noteColor)) return noteColor;
if (TrackIndex >= 0 && TrackIndex < 5)
{
string[] trackColors = { "red", "green", "yellow", "purple", "blue" };
string fallback = trackColors[TrackIndex];
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[Note] noteColor was null, using trackIndex {TrackIndex} -> {fallback}");
return fallback;
}
Debug.LogError($"[Note] Cannot derive color: noteColor is null and TrackIndex {TrackIndex} is invalid");
return null;
}
private AnimationController anim;
private NoteController controller;
private bool isJudged = false;
@@ -233,12 +250,15 @@ public class Note : BaseNote
}
// Force miss if we've passed the deadline without being judged
if (!isJudged && GameplayClock.NowSongTime > missDeadlineTime && gameObject.activeSelf)
if (!isJudged && gameObject.activeSelf)
{
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {GameplayClock.NowSongTime:F3}, forcing Miss");
if (GameplayClock.NowSongTime >= missDeadlineTime)
{
Debug.Log($"[Note.Update] Forcing miss for {noteColor} on track {TrackIndex}: now={GameplayClock.NowSongTime:F3}, deadline={missDeadlineTime:F3}");
JudgeMiss();
}
}
}
private void TryAutoJudgePerfect()
{
@@ -284,8 +304,12 @@ public class Note : BaseNote
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
teamUIController.Instance?.OnJudgeResult(judgeResult);
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.AutoPlay] Spawning judge prefab: color={noteColor}, result={judgeResult}");
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, judgeResult);
string colorForPrefab = GetColorForPrefab();
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.AutoPlay] Spawning judge prefab: color={colorForPrefab}, result={judgeResult}");
if (!string.IsNullOrEmpty(colorForPrefab))
{
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(colorForPrefab, judgeResult);
}
try
{
@@ -303,6 +327,7 @@ public class Note : BaseNote
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge(judgeResult, 0f, false, true, pressTime);
GameplayLevelRuleEventBus.NotifyTapJudged(TrackIndex, judgeResult, pressTime, noteData);
if (isSyncNote)
{
@@ -454,9 +479,13 @@ public class Note : BaseNote
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
teamUIController.Instance?.OnJudgeResult(judgeResult);
// Spawn judgment prefab - ensure noteColor is passed correctly
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Spawning judge prefab: color={noteColor}, result={judgeResult}");
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, judgeResult);
// Spawn judgment prefab
string colorForPrefab = GetColorForPrefab();
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Spawning judge prefab: color={colorForPrefab}, result={judgeResult}");
if (!string.IsNullOrEmpty(colorForPrefab))
{
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(colorForPrefab, judgeResult);
}
try
{
@@ -474,6 +503,7 @@ public class Note : BaseNote
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge(judgeResult, rawOffsetMs, rewrittenToPerfect, false, pressTime);
GameplayLevelRuleEventBus.NotifyTapJudged(TrackIndex, judgeResult, pressTime, noteData);
if (isSyncNote && judgeResult != "Miss")
{
@@ -533,8 +563,14 @@ public class Note : BaseNote
public void JudgeMiss()
{
Debug.Log($"[Note.JudgeMiss] Called for {noteColor} on track {TrackIndex}, isJudged={isJudged}");
// idempotent: this can be called from multiple paths
if (isJudged) return;
if (isJudged)
{
Debug.Log($"[Note.JudgeMiss] Already judged, skipping");
return;
}
isJudged = true;
ScoreManager.Instance.countMiss += 1;
@@ -550,9 +586,23 @@ public class Note : BaseNote
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
teamUIController.Instance?.OnJudgeResult("Miss");
// Spawn judgment prefab for Miss - ensure noteColor is passed correctly
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.JudgeMiss] Spawning miss judge prefab: color={noteColor}");
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
// 生成 Miss 判定 prefab(调试日志无条件输出,便于排查问题)
string colorForPrefab = GetColorForPrefab();
Debug.Log($"[Note.JudgeMiss] Attempting to spawn miss prefab: noteColor={noteColor}, TrackIndex={TrackIndex}, derivedColor={colorForPrefab}, Instance={(Animation_GenerateJudgementSituationPrefab.Instance != null ? "EXISTS" : "NULL")}");
if (string.IsNullOrEmpty(colorForPrefab))
{
Debug.LogError($"[Note.JudgeMiss] Cannot spawn miss prefab: colorForPrefab is null! noteColor={noteColor}, TrackIndex={TrackIndex}");
}
else if (Animation_GenerateJudgementSituationPrefab.Instance == null)
{
Debug.LogError($"[Note.JudgeMiss] Cannot spawn miss prefab: Animation_GenerateJudgementSituationPrefab.Instance is NULL!");
}
else
{
Animation_GenerateJudgementSituationPrefab.Instance.SpawnJudgePrefab(colorForPrefab, "Miss");
Debug.Log($"[Note.JudgeMiss] SpawnJudgePrefab called successfully for {colorForPrefab}");
}
try
{
@@ -570,6 +620,7 @@ public class Note : BaseNote
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge("Miss", 0f, false, false, GameplayClock.NowSongTime);
GameplayLevelRuleEventBus.NotifyTapJudged(TrackIndex, "Miss", GameplayClock.NowSongTime, noteData);
// notify global judge manager that this note has been finally judged (miss)
JudgeManager.Instance?.NotifyNoteJudged();
@@ -623,6 +674,8 @@ public class Note : BaseNote
/// </summary>
public void SetJudgeZone(bool inZone)
{
Debug.Log($"[Note.SetJudgeZone] {noteColor} on track {TrackIndex}: inZone={inZone}, isJudged={isJudged}");
// leaving judge zone
if (!inZone)
{
@@ -634,7 +687,7 @@ public class Note : BaseNote
// Calling ReturnToPool (which deactivates the object) inside a physics callback
// causes "GameObject is already being activated or deactivated" errors.
// Instead, mark for miss and let Update handle it next frame.
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.SetJudgeZone] Note {noteColor} left judge zone on track {TrackIndex}, will force Miss next frame");
Debug.Log($"[Note.SetJudgeZone] Note {noteColor} left judge zone on track {TrackIndex}, setting deadline to now");
missDeadlineTime = GameplayClock.NowSongTime; // Force deadline to now so Update will handle it
}
}
+70 -15
View File
@@ -35,8 +35,8 @@ public class NoteSpawner : MonoBehaviour
// Documentation text normalized.
[Tooltip("Multiplier applied to visual fall speed. Changing this will automatically adjust spawn timing so notes still arrive at their original beat times.")]
[Range(0.5f, 2f)]
public float speedMultiplier = 1f;
[Range(1f, 3f)]
public float speedMultiplier = 2f;
[Header("Calibration")]
[Tooltip("Tolerance (world units) for snapping middle segments to expected position after spawn.")]
@@ -84,8 +84,9 @@ public class NoteSpawner : MonoBehaviour
public GameObject animations;
// optional: constants for runtime clamping (kept for internal use)
private const float SpeedMultiplierMin = 0.5f;
private const float SpeedMultiplierMax = 2f;
// Player-facing speed is clamped to 1-3; FlowSpeedGlobalMultiplier makes the real range 2-6.
private const float SpeedMultiplierMin = 1f;
private const float SpeedMultiplierMax = 3f;
// 全局流速系数:与用户可调流速(speedMultiplier)相乘的独立系数,加快音符下落。
// 为什么用它而非 bpm 倍率:noteSpeed = 10.75 * sm * bpm / 240,若靠 ×bpm 提速,
// 长条音符的视觉长度由 ApplyVisualScale(sm) 决定(只随 sm 不随 bpm)、且分段数 segmentInterval=(60/bpm)/4
@@ -100,6 +101,28 @@ public class NoteSpawner : MonoBehaviour
get { return Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax) * FlowSpeedGlobalMultiplier; }
}
[Header("Note Fall Speed Source")]
[Tooltip("true=沿用谱面 BPM 决定下落速度(旧逻辑,高 BPM 歌音符更快);false=用固定参考 BPM" +
"下落速度不再随歌曲 BPM 变化,仅由流速 speedMultiplier 控制(全曲统一手感)。默认 false。")]
public bool useBpmForSpeed = false;
[Tooltip("useBpmForSpeed=false 时用作速度基准的固定参考 BPM。等价于“所有歌曲都按此 BPM 的手感下落”。" +
"建议 ≥60:地面粒子速度对 BPM 有 60 下限,低于 60 会使音符与粒子略微不同步。")]
public float referenceBpm = 120f;
// 速度/长条分段公式统一使用的“有效 BPM”。开=真实谱面 bpm;关=固定参考 bpm。
// 二者都同时喂给 noteSpeed 与 segmentInterval,故长条段间距(∝sm,bpm 相消)与地面粒子同步天然保持不变。
public float EffectiveBpm
{
get { return useBpmForSpeed ? bpm : Mathf.Max(1f, referenceBpm); }
}
[Header("Hold Note Body Fill")]
[Tooltip("true=新逻辑(默认):长条中段不拉伸(保持 prefab 原始大小),改为按流速生成更多中段密铺填满(流速越快段越多)。" +
"false=旧逻辑:中段数量固定,用 ApplyVisualScale(流速) 拉伸每段填满(流速越大越拉长)。" +
"两种方案下长条视觉长度一致、判定完全不变(中段纯视觉、end 段到达时间不变)。")]
public bool useMultiSegmentFill = true;
public NoteJudgeConfig judgeConfig; // Documentation text normalized.
private Beatmap beatmap;
private float startTime; // Absolute chart time anchor.
@@ -113,7 +136,9 @@ public class NoteSpawner : MonoBehaviour
private Dictionary<int, int> noteIndexToHoldId = new Dictionary<int, int>();
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
private const float NoteSpeedDefault = 1f;
private const string NoteSpeedDefaultVersionKey = "noteSpeedMultiplierDefaultVersion";
private const int NoteSpeedDefaultVersion = 2;
private const float NoteSpeedDefault = 2f;
private const float BaseTravelDistance = 10.75f;
private Coroutine spawnCoroutine;
@@ -146,10 +171,22 @@ public class NoteSpawner : MonoBehaviour
{
if (PlayerPrefs.HasKey(NoteSpeedPrefKey))
{
int version = PlayerPrefs.GetInt(NoteSpeedDefaultVersionKey, 0);
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
if (version < NoteSpeedDefaultVersion && Mathf.Approximately(saved, 3f))
{
saved = NoteSpeedDefault;
}
saved = Mathf.Clamp(saved, SpeedMultiplierMin, SpeedMultiplierMax);
PlayerPrefs.SetFloat(NoteSpeedPrefKey, saved);
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
PlayerPrefs.Save();
return;
}
PlayerPrefs.SetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
PlayerPrefs.Save();
}
@@ -280,10 +317,13 @@ public class NoteSpawner : MonoBehaviour
// Cache parameters that don't change within the loop
float sm = EffectiveSpeedMultiplier;
float baseTravelTime = (60f / bpm) * 4f;
// 下落速度基准:useBpmForSpeed=true 用谱面 bpm(旧逻辑)false 用固定参考 bpm(速度不随歌曲变)。
// 判定时间不受影响:到达时刻只由 note.time 决定,speed 仅改视觉下落快慢与出现时机。
float speedBpm = EffectiveBpm;
float baseTravelTime = (60f / speedBpm) * 4f;
float baseNoteTravelTime = baseTravelTime / Mathf.Max(0.0001f, sm);
float noteSpeed = CalculateSpeed(baseNoteTravelTime);
float segmentInterval = (60f / bpm) / 4f;
float segmentInterval = (60f / speedBpm) / 4f;
EnsureJudgmentLine();
float[] laneTravelTimes = BuildLaneTravelTimes(noteSpeed);
@@ -453,9 +493,24 @@ public class NoteSpawner : MonoBehaviour
return -1;
}
int segmentCount = Mathf.CeilToInt(noteData.length / segmentInterval);
// 段间隔决定"每隔多少时间放一段"。段间距(世界坐标)= noteSpeed × 段间隔,而 noteSpeed ∝ 流速 sm。
// 旧逻辑(useMultiSegmentFill=false):段间隔不随 sm 变 → 段间距 ∝ sm,段数固定,
// 靠 ApplyVisualScale(sm) 把每段拉伸 sm 倍去填满变大的段间距(流速越大越拉长)。
// 新逻辑(useMultiSegmentFill=true):段间隔 ÷ sm → 段数 ∝ sm,而段间距 = noteSpeed × (段间隔/sm) = 恒定,
// 恰好等于不拉伸(scale=1)时 prefab 的原始段高,于是用等大的原始段密铺填满(流速越大段越多)。
// 两种方案下:end 段到达时间 = baseHit + length 完全不变(段数变但 actualSegmentInterval=length/count 抵消)
// 长条整体视觉长度一致;中段纯视觉、不参与计分/连击,故判定业务逻辑完全不受影响。
float effectiveSegmentInterval = segmentInterval;
if (useMultiSegmentFill)
{
effectiveSegmentInterval = segmentInterval / Mathf.Max(0.01f, sm);
}
int segmentCount = Mathf.CeilToInt(noteData.length / effectiveSegmentInterval);
if (segmentCount < 2) segmentCount = 2; // Force at least one middle segment between start and end.
float actualSegmentInterval = segmentCount > 0 ? noteData.length / segmentCount : segmentInterval;
float actualSegmentInterval = segmentCount > 0 ? noteData.length / segmentCount : effectiveSegmentInterval;
// 新逻辑下不拉伸(每段保持 prefab 原始大小);旧逻辑下按流速 sm 拉伸每段。
float holdVisualScale = useMultiSegmentFill ? 1f : sm;
Transform spawnPoint = spawnPoints[noteData.trackIndex];
// Documentation text normalized.
@@ -501,8 +556,8 @@ public class NoteSpawner : MonoBehaviour
// pass realtime hit time (startTime + note.time) and delay 0, include globalHitDelay
holdNote.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, 0f, false, scheduledEndTime, noteData.color, key, "start", travelTime, judgeConfig, noteData, isSync);
// 使用流速倍率 sm 直接进行缩放,确保长条音符在 0.5-2.0 范围内依然能完美衔接
float visualScale = sm;
// 旧逻辑=按流速 sm 拉伸;新逻辑(多段填充)=不拉伸(holdVisualScale=1)。
float visualScale = holdVisualScale;
holdNote.ApplyVisualScale(visualScale);
// inform hold note of visual speed so it can adapt judgement windows if needed
holdNote.visualSpeedMultiplier = sm;
@@ -533,8 +588,8 @@ public class NoteSpawner : MonoBehaviour
// Documentation text normalized.
holdSeg.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", travelTime, judgeConfig, noteData, false);
// 应用与开始段一致的流速缩放
float visualScaleMid = sm;
// 与开始段一致:旧逻辑拉伸、新逻辑不拉伸(靠更多段密铺填满)。
float visualScaleMid = holdVisualScale;
holdSeg.ApplyVisualScale(visualScaleMid);
holdSeg.visualSpeedMultiplier = sm;
@@ -565,8 +620,8 @@ public class NoteSpawner : MonoBehaviour
{
holdEnd.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", travelTime, judgeConfig, noteData, false);
// 同样应用流速缩放
float visualScaleEnd = sm;
// 与开始段一致:旧逻辑拉伸、新逻辑不拉伸。
float visualScaleEnd = holdVisualScale;
holdEnd.ApplyVisualScale(visualScaleEnd);
holdEnd.visualSpeedMultiplier = sm;
@@ -0,0 +1,120 @@
using UnityEngine;
/// <summary>
/// 按屏幕宽高比动态拉长轨道视觉物体(track1-5)的 Y 缩放,让宽屏下轨道更饱满。
///
/// 原理:宽屏时水平视野大、轨道相对缩在中间显细。降相机高度+抬头能让轨道透视放大,
/// 但仍不够饱满。再叠加"拉长轨道 Y 缩放"填满纵向视野,视觉更满。
///
/// 关键:轨道只是背景视觉,判定是几何驱动(音符世界 Y vs 判定线世界 Y)。
/// 缩放轨道 localScale 不影响音符生成/下落/判定——它们全用世界坐标,与轨道视觉无关。
///
/// 用法:挂到场景任意物体(会自动找 track1-5 或指定 trackParent)。运行时自动扫描并缩放。
/// </summary>
[DisallowMultipleComponent]
public sealed class TrackScaler : MonoBehaviour
{
[Tooltip("是否启用轨道缩放。关闭则恢复基准值 2.5。")]
public bool enableScaling = true;
[Tooltip("设计基准宽高比。16:9 = 1.7778。此比例下缩放为基准值。")]
public float designAspect = 16f / 9f;
[Tooltip("轨道 Y 缩放的基准值(场景序列化的初始值)。从这个值开始按宽高比放大。")]
public float baseScaleY = 2.5f;
[Header("宽屏(aspect > 设计)缩放")]
[Tooltip("每偏离设计宽高比 1.0,额外叠加的 Y 缩放倍数。宽屏拉长轨道让视觉更满。")]
public float widePerAspectScaleY = 0.8f;
[Tooltip("Y 缩放的绝对上限。避免极端超宽屏拉得过长。")]
public float maxScaleY = 4.5f;
[Tooltip("(可选)轨道的父节点。留空则自动全场景查找 track1-5。")]
public Transform trackParent;
private Transform[] _tracks;
private float _lastAspect = -1f;
private bool _lastEnabled;
private void OnEnable()
{
FindTracks();
_lastAspect = -1f;
Apply();
}
private void Update()
{
float aspect = (float)Screen.width / Mathf.Max(1, Screen.height);
if (Mathf.Abs(aspect - _lastAspect) > 0.0001f || enableScaling != _lastEnabled)
{
Apply();
}
}
private void FindTracks()
{
if (trackParent != null)
{
// 从指定父节点下找
_tracks = new Transform[5];
for (int i = 0; i < trackParent.childCount; i++)
{
Transform child = trackParent.GetChild(i);
if (child.name == "track1") _tracks[0] = child;
else if (child.name == "track2") _tracks[1] = child;
else if (child.name == "track3") _tracks[2] = child;
else if (child.name == "track4") _tracks[3] = child;
else if (child.name == "track5") _tracks[4] = child;
}
}
else
{
// 全场景查找(慢,但兜底)
_tracks = new Transform[5];
GameObject go1 = GameObject.Find("track1");
GameObject go2 = GameObject.Find("track2");
GameObject go3 = GameObject.Find("track3");
GameObject go4 = GameObject.Find("track4");
GameObject go5 = GameObject.Find("track5");
if (go1 != null) _tracks[0] = go1.transform;
if (go2 != null) _tracks[1] = go2.transform;
if (go3 != null) _tracks[2] = go3.transform;
if (go4 != null) _tracks[3] = go4.transform;
if (go5 != null) _tracks[4] = go5.transform;
}
}
private void Apply()
{
if (_tracks == null || _tracks.Length == 0)
{
FindTracks(); // 重试
if (_tracks == null || _tracks.Length == 0) return;
}
float aspect = (float)Screen.width / Mathf.Max(1, Screen.height);
_lastAspect = aspect;
_lastEnabled = enableScaling;
float targetScaleY = baseScaleY;
if (enableScaling && aspect > designAspect)
{
// 宽屏:额外拉长 Y
float dev = aspect - designAspect;
float extra = widePerAspectScaleY * dev;
targetScaleY = Mathf.Min(baseScaleY + extra, maxScaleY);
}
// aspect <= designAspect 或关闭:保持基准值
// 应用到所有轨道
for (int i = 0; i < _tracks.Length; i++)
{
if (_tracks[i] == null) continue;
Vector3 scale = _tracks[i].localScale;
scale.y = targetScaleY;
_tracks[i].localScale = scale;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 29e9752cc9128104bad251941d81ffa2
@@ -26,7 +26,9 @@ using UnityEngine;
public sealed class TrackScreenRatioStabilizer : MonoBehaviour
{
[Tooltip("是否启用轨道屏占比稳定(按宽高比补偿相机垂直FOV)。关闭则恢复设计FOV。")]
public bool enableStabilization = true;
// 关闭:contain 式补偿会在窄屏增大垂直FOV(镜头拉远)=轨道变细+视距变长,二者都不符合需求。
// 改由 CameraResolutionTuner(降Z+增X俯角)在保持垂直FOV恒定(视距恒定)的前提下让轨道铺开。
public bool enableStabilization = false;
[Tooltip("设计基准宽高比(轨道占比在此比例下为设计值)。16:9 = 1.7778。")]
public float designAspect = 16f / 9f;
@@ -0,0 +1,43 @@
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class TrackSkillPrefab : MonoBehaviour
{
[Header("UI")]
public Text skillText;
public Image skillImage;
private CanvasGroup _canvasGroup;
private void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
if (_canvasGroup == null) _canvasGroup = gameObject.AddComponent<CanvasGroup>();
_canvasGroup.alpha = 0f;
}
public void Bind(Sprite icon, string skillName)
{
ResetForReuse();
if (skillImage != null)
{
skillImage.sprite = icon;
skillImage.enabled = icon != null;
}
if (skillText != null)
{
skillText.text = string.IsNullOrWhiteSpace(skillName) ? string.Empty : skillName;
}
}
private void ResetForReuse()
{
if (_canvasGroup != null) _canvasGroup.alpha = 0f;
transform.localRotation = Quaternion.identity;
transform.localScale = Vector3.one;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a51f5b8ae798a1e42a55f1654ed4237e
@@ -38,10 +38,11 @@ public class effectEventController : MonoBehaviour
// 分辨率相机微调:改的是"漂移 pivot"(顶层节点,承载 -40°/-8 基准),不碰相机自身,
// 因此不与 SmoothShake(写相机 local)、技能偏移(写技能 pivot)、冲刺(写相机 z)冲突。
// 改 pivot 的 X 旋转无任何系统覆盖;改 pivot 的 Z 必须同步 cameraDriftBaseLocalPosition.z
// 否则 UpdateCameraDrift/ResetCameraDriftIfNeeded 会用旧基准把 z 拉回。
// 改 pivot 的 X 旋转无任何系统覆盖;改 pivot 的 Y/Z 必须同步 cameraDriftBaseLocalPosition
// 否则 UpdateCameraDrift/ResetCameraDriftIfNeeded 会用旧基准把 Y/Z 拉回。
private bool _cameraBaseCaptured;
private float _designPivotRotX; // pivot 初始 X 旋转(如 -40)
private float _designPivotY; // pivot 初始 Y(如 -6,相机高度)
private float _designPivotZ; // pivot 初始 Z(如 -8)
private void Awake()
@@ -117,16 +118,17 @@ public class effectEventController : MonoBehaviour
if (cachedCameraDriftPivot == null) return;
}
_designPivotRotX = cachedCameraDriftPivot.localEulerAngles.x;
_designPivotY = cachedCameraDriftPivot.localPosition.y;
_designPivotZ = cachedCameraDriftPivot.localPosition.z;
_cameraBaseCaptured = true;
}
/// <summary>
/// 按分辨率微调相机:在设计基准上叠加 X 旋转增量Z 位置增量。
/// 改的是漂移 pivot(不动相机本身),且同步 cameraDriftBaseLocalPosition.z
/// 按分辨率微调相机:在设计基准上叠加 X 旋转增量、Y 高度增量、Z 位置增量。
/// 改的是漂移 pivot(不动相机本身),且同步 cameraDriftBaseLocalPosition 的 y/z
/// 与漂移/抖动/技能偏移/冲刺全部不冲突。可重复调用(总是相对设计基准,不累积)。
/// </summary>
public void SetResolutionCameraAdjust(float extraRotX, float extraZ)
public void SetResolutionCameraAdjust(float extraRotX, float extraZ, float extraY = 0f)
{
CaptureCameraBaseIfNeeded();
if (cachedCameraDriftPivot == null) return;
@@ -136,12 +138,91 @@ public class effectEventController : MonoBehaviour
euler.x = _designPivotRotX + extraRotX;
cachedCameraDriftPivot.localEulerAngles = euler;
// Z:设计值 + 增量,并同步漂移基准 z(漂移只改 x,y,z 恒取基准)。
// Y(高度)与 Z:设计值 + 增量。漂移每帧写 base + (perlinX, perlinY, 0)
// 故必须同步 cameraDriftBaseLocalPosition 的 y/z,否则下一帧漂移会用旧基准拉回。
float targetY = _designPivotY + extraY;
float targetZ = _designPivotZ + extraZ;
Vector3 lp = cachedCameraDriftPivot.localPosition;
lp.y = targetY;
lp.z = targetZ;
cachedCameraDriftPivot.localPosition = lp;
cameraDriftBaseLocalPosition.z = targetZ; // 关键:否则会被 UpdateCameraDrift/Reset 拉回
cameraDriftBaseLocalPosition.y = targetY; // 关键:否则会被 UpdateCameraDrift/Reset 拉回
cameraDriftBaseLocalPosition.z = targetZ;
}
// 相机本体(Main Camera)分辨率适配的 local 基准。运行时相机被重挂到 skill_pivot 之下、
// local 位姿被清零(见 EnsureCameraDriftPivot/EnsureCameraSkillPivot),故基准 z≈0、X≈0。
// 首次调用 SetResolutionCameraSelf 时惰性捕获,供插值 t=0 回到该基准。
private bool _cameraSelfBaseCaptured;
private float _cameraSelfBaseLocalZ;
private float _cameraSelfBaseLocalRotX;
/// <summary>
/// 按分辨率**直接调整相机本体(Main Camera 子物体)**的 local z 高度与 local X 旋转,
/// 不动其父/祖父 pivot。t=0 → 回到相机 local 设计基准(运行时为 0/identity,与未调整一致)
/// t=1 → 相机 local 到达 (targetLocalRotX, targetLocalZ)。始终相对基准插值,不累积。
///
/// 冲突处理:相机本体的 local 由 SmoothShake(抖动时每帧写)与冲刺(动画 local z)共享。
/// - 空闲时二者不写相机(SmoothShake 无活动协程即不 ApplySum),本次写入持久生效;
/// - 抖动/冲刺触发时会以"当前 local"为基准重新捕获(SaveDefaultValues / CacheCameraTransform)
/// 故结束后回到本方法设定的值。为兼容"抖动进行中改分辨率"的罕见情形,
/// 若相机上已有 SmoothShake 且正在抖动,同步其 startPosition/startRotation 基准。
/// </summary>
public bool SetResolutionCameraSelf(float t, float targetLocalRotX, float targetLocalZ)
{
if (cachedShakeTarget == null)
{
CacheShakeTarget();
if (cachedShakeTarget == null) return false; // 相机(Camera.main)尚未就绪,稍后重试
}
t = Mathf.Clamp01(t);
if (!_cameraSelfBaseCaptured)
{
_cameraSelfBaseLocalZ = cachedShakeTarget.localPosition.z;
_cameraSelfBaseLocalRotX = NormalizeSignedAngle(cachedShakeTarget.localEulerAngles.x);
_cameraSelfBaseCaptured = true;
}
float z = Mathf.Lerp(_cameraSelfBaseLocalZ, targetLocalZ, t);
float rotX = Mathf.Lerp(_cameraSelfBaseLocalRotX, targetLocalRotX, t);
Vector3 lp = cachedShakeTarget.localPosition;
lp.z = z;
cachedShakeTarget.localPosition = lp;
Vector3 euler = cachedShakeTarget.localEulerAngles;
euler.x = rotX;
cachedShakeTarget.localEulerAngles = euler;
SyncShakeBaseIfActive(lp, euler);
return true;
}
// 若相机本体上的 SmoothShake 正在抖动,同步其 start 基准,避免抖动进行中改分辨率被回弹覆盖。
private void SyncShakeBaseIfActive(Vector3 localPos, Vector3 localEuler)
{
if (cachedShakeTarget == null) return;
var shake = cachedShakeTarget.GetComponent<SmoothShakeFree.SmoothShake>();
if (shake == null) return;
if (shake.activeShakeRoutines == null || shake.activeShakeRoutines.Count == 0) return;
var type = typeof(SmoothShakeFree.SmoothShake);
const System.Reflection.BindingFlags flags =
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
var startPosField = type.GetField("startPosition", flags);
var startRotField = type.GetField("startRotation", flags);
if (startPosField != null) startPosField.SetValue(shake, localPos);
if (startRotField != null) startRotField.SetValue(shake, localEuler);
}
// Unity eulerAngles.x 存 [0,360)-40 存成 320;归一到 [-180,180) 便于插值(负=朝上)。
private static float NormalizeSignedAngle(float angle)
{
angle %= 360f;
if (angle > 180f) angle -= 360f;
else if (angle < -180f) angle += 360f;
return angle;
}
private void PrewarmShakeComponent()
@@ -20,6 +20,8 @@ public class globalNoteEffect : MonoBehaviour
public float cameraDashDuration = 0.12f;
[Header("Camera Wave FX")]
[Range(0f, 1f)]
public float cameraWaveIntensity = 1f;
public float cameraWaveEnterDuration = 0.12f;
public float cameraWaveRecoverDuration = 0.12f;
@@ -319,6 +321,7 @@ public class globalNoteEffect : MonoBehaviour
private void TriggerCameraWaveFx()
{
GlobalDistortionRuntimeController.TriggerWaveEffect(
Mathf.Clamp01(cameraWaveIntensity),
Mathf.Max(0.0001f, cameraWaveEnterDuration),
Mathf.Max(0.0001f, cameraWaveRecoverDuration)
);
@@ -357,9 +357,11 @@ public class groundParticularController : MonoBehaviour
float currentBPM = GetCurrentBPM();
float noteSpawnerSpeedMultiplier = 1.0f;
// 粒子速度须与音符下落同速:跟随 NoteSpawner 的“有效 BPM”(开关决定用谱面 bpm 还是固定参考 bpm)。
if (beatmapManager != null && beatmapManager.noteSpawner != null)
{
noteSpawnerSpeedMultiplier = beatmapManager.noteSpawner.EffectiveSpeedMultiplier;
currentBPM = beatmapManager.noteSpawner.EffectiveBpm;
}
float effectiveBPM = Mathf.Max(currentBPM, 60f);
@@ -576,7 +576,8 @@ public class settlementController : MonoBehaviour
}
targetPmScore = sm.allSum_pmScore;
targetIdolScore = sm.allSum_idolScore;
int levelRuleScoreBonus = LevelRuleController.ConsumePendingSettlementScoreBonus();
targetIdolScore = sm.allSum_idolScore + levelRuleScoreBonus;
targetTotalScore = targetPmScore + targetIdolScore;
targetTotalPercent = (float)targetTotalScore / Mathf.Max(1, _1000000) * 100f;
@@ -804,6 +804,11 @@ public class teamUIController : MonoBehaviour
return (EnemyData_SO[])currentEnemySOs.Clone();
}
public EnemyCombatant GetCurrentEnemyCombatant()
{
return enemyCombatantInstance;
}
// Debug helper to print key fields of a TeamCharacterDataInfo
private void DebugCharacterSO(TeamCharacterDataInfo so, int slotIndex, bool isEditor)
{
@@ -1161,6 +1166,12 @@ public class teamUIController : MonoBehaviour
PopulateAllySOsFromIds();
// 初始化角色头像:根据槽位 ID 刷新 teammate0X_characterImage,空槽位会被禁用
for (int i = 0; i < 5; i++)
{
RefreshTeammateCharacterImageBySlotId(i);
}
// initialize previous flags so Update can detect changes
prevAllyActive[0] = isAlly01_active;
prevAllyActive[1] = isAlly02_active;
@@ -1518,16 +1529,31 @@ public class teamUIController : MonoBehaviour
}
// helper to set teammate character image based on slot index
// 若 sprite 为 null,则禁用对应 Image GameObject(槽位无角色);否则启用并设 sprite。
private void SetTeammateCharacterImage(int slotIndex, Sprite sprite)
{
if (sprite == null) return;
Image img = null;
switch (slotIndex)
{
case 0: if (teammate01_characterImage != null) teammate01_characterImage.sprite = sprite; break;
case 1: if (teammate02_characterImage != null) teammate02_characterImage.sprite = sprite; break;
case 2: if (teammate03_characterImage != null) teammate03_characterImage.sprite = sprite; break;
case 3: if (teammate04_characterImage != null) teammate04_characterImage.sprite = sprite; break;
case 4: if (teammate05_characterImage != null) teammate05_characterImage.sprite = sprite; break;
case 0: img = teammate01_characterImage; break;
case 1: img = teammate02_characterImage; break;
case 2: img = teammate03_characterImage; break;
case 3: img = teammate04_characterImage; break;
case 4: img = teammate05_characterImage; break;
}
if (img == null) return;
if (sprite == null)
{
// 槽位无角色:禁用 Image
img.gameObject.SetActive(false);
}
else
{
// 槽位有角色:启用 Image 并设置 sprite
img.sprite = sprite;
img.gameObject.SetActive(true);
}
}
@@ -1547,7 +1573,13 @@ public class teamUIController : MonoBehaviour
{
if (slotIndex < 0 || slotIndex >= allySlotIds.Count) return;
int id = allySlotIds[slotIndex];
if (id <= 0) return;
// 槽位无角色(id <= 0):禁用对应 Image
if (id <= 0)
{
SetTeammateCharacterImage(slotIndex, null);
return;
}
AllyHero_SO[] all = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < all.Length; i++)
@@ -1555,12 +1587,12 @@ public class teamUIController : MonoBehaviour
AllyHero_SO so = all[i];
if (so == null || so.ally_heroID != id) continue;
Sprite sprite = so.ally_heroProfile != null ? so.ally_heroProfile : so.ally_heroImage;
if (sprite != null)
{
SetTeammateCharacterImage(slotIndex, sprite);
}
SetTeammateCharacterImage(slotIndex, sprite); // sprite 可能仍为 null,SetTeammateCharacterImage 会禁用
return;
}
// 找不到对应 SO:也禁用 Image
SetTeammateCharacterImage(slotIndex, null);
}
public void SwapAllyWithLowerAdjacent(int slotIndex)
@@ -1984,6 +2016,7 @@ public class teamUIController : MonoBehaviour
InitializeEnemyInstance();
int difficultyID = BeatmapManager.Instance != null ? BeatmapManager.Instance.assignedDifficulty : 0;
enemyCombatantInstance.InitializeFromSO(so, difficultyID);
GameplayLevelRuleEventBus.NotifyEnemySpawned(enemyCombatantInstance, enemyCurrentCount, so);
// set UI visuals (name / sprites)
string enemyName = so.enemyName ?? so.name;
@@ -2176,6 +2209,7 @@ public class teamUIController : MonoBehaviour
so = recognizedEnemySOs[enemyCurrentCount];
if (so != null) eName = so.enemyName ?? so.name;
}
GameplayLevelRuleEventBus.NotifyEnemyDied(e, enemyCurrentCount, so);
SkillTriggerFeedUI.PushEnemyDeath(eName);
// notify enemy ball UI
@@ -235,4 +235,9 @@ public class GlobalDistortionRuntimeController : MonoBehaviour
{
EnsureInstance().TriggerWave(1f, enterDurationSeconds, recoverDurationSeconds);
}
public static void TriggerWaveEffect(float intensity, float enterDurationSeconds, float recoverDurationSeconds)
{
EnsureInstance().TriggerWave(intensity, enterDurationSeconds, recoverDurationSeconds);
}
}
+18 -3
View File
@@ -34,10 +34,13 @@ public class controllerSettings : MonoBehaviour
private bool previousKeyValid = false;
// slider range
private const float NoteSpeedMin = 0.5f;
private const float NoteSpeedMax = 2f;
private const float NoteSpeedMin = 1f;
// Player-facing range is 1-3. NoteSpawner multiplies it by 2, so the real range is 2-6.
private const float NoteSpeedMax = 3f;
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
private const float NoteSpeedDefault = 1.0f;
private const string NoteSpeedDefaultVersionKey = "noteSpeedMultiplierDefaultVersion";
private const int NoteSpeedDefaultVersion = 2;
private const float NoteSpeedDefault = 2.0f;
// continuous change
private Coroutine continuousChangeCoroutine = null;
@@ -505,10 +508,22 @@ public class controllerSettings : MonoBehaviour
{
if (PlayerPrefs.HasKey(NoteSpeedPrefKey))
{
int version = PlayerPrefs.GetInt(NoteSpeedDefaultVersionKey, 0);
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
if (version < NoteSpeedDefaultVersion && Mathf.Approximately(saved, 3f))
{
saved = NoteSpeedDefault;
}
saved = Mathf.Clamp(saved, NoteSpeedMin, NoteSpeedMax);
PlayerPrefs.SetFloat(NoteSpeedPrefKey, saved);
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
PlayerPrefs.Save();
return;
}
PlayerPrefs.SetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
PlayerPrefs.Save();
}
+9 -9
View File
@@ -6083,7 +6083,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 2.0
m_Text: 300%
--- !u!1 &1283681621046949199
GameObject:
m_ObjectHideFlags: 0
@@ -13447,7 +13447,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 3840, y: 2160}
m_SizeDelta: {x: 19200, y: 10800}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4461991833650980158
CanvasRenderer:
@@ -14913,10 +14913,10 @@ MonoBehaviour:
m_FillRect: {fileID: 2478940357038280624}
m_HandleRect: {fileID: 4123488008762013331}
m_Direction: 0
m_MinValue: 0
m_MaxValue: 1
m_MinValue: 1
m_MaxValue: 3
m_WholeNumbers: 0
m_Value: 1
m_Value: 2
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
@@ -26841,10 +26841,10 @@ MonoBehaviour:
m_FillRect: {fileID: 4302310117072831067}
m_HandleRect: {fileID: 1819070857763714964}
m_Direction: 0
m_MinValue: 0
m_MaxValue: 1
m_MinValue: 1
m_MaxValue: 3
m_WholeNumbers: 0
m_Value: 1
m_Value: 2
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
@@ -32449,7 +32449,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 0.5
m_Text: 100%
--- !u!1 &8286336829220767699
GameObject:
m_ObjectHideFlags: 0
View File