feat(platforms): add new platform types and enhance player interactions
This commit is contained in:
parent
abe90fcfce
commit
47b43cf122
@ -2,6 +2,7 @@
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>event'ов</w>
|
||||
<w>renderer'ы</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
@ -4478,4 +4478,18 @@ AnimationClip:
|
||||
flags: 0
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
m_Events:
|
||||
- time: 0
|
||||
functionName: OnDeathBegin
|
||||
data:
|
||||
objectReferenceParameter: {fileID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
- time: 0.6666667
|
||||
functionName: OnDeathEnd
|
||||
data:
|
||||
objectReferenceParameter: {fileID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
|
||||
@ -387,7 +387,7 @@ AnimationClip:
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopTime: 0
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
|
||||
@ -1003,7 +1003,7 @@ AnimationClip:
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopTime: 0
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
|
||||
@ -328,7 +328,7 @@ AnimationClip:
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopTime: 0
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
|
||||
@ -724,7 +724,7 @@ AnimationClip:
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopTime: 0
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
@ -2934,4 +2934,11 @@ AnimationClip:
|
||||
flags: 0
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
m_Events:
|
||||
- time: 0
|
||||
functionName: Jump
|
||||
data:
|
||||
objectReferenceParameter: {fileID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
|
||||
@ -25,34 +25,6 @@ AnimatorStateTransition:
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &-6839088151083392861
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: VSpeed
|
||||
m_EventTreshold: 10
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Jump
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 2683883220764761602}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &-6437098828268326780
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
@ -70,11 +42,11 @@ AnimatorStateTransition:
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.25
|
||||
m_HasExitTime: 1
|
||||
m_HasFixedDuration: 1
|
||||
m_ExitTime: 0
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
@ -86,7 +58,7 @@ AnimatorState:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Jump_Fall
|
||||
m_Speed: 1
|
||||
m_Speed: 2
|
||||
m_CycleOffset: 0
|
||||
m_Transitions:
|
||||
- {fileID: 5962895747233769529}
|
||||
@ -105,6 +77,31 @@ AnimatorState:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1101 &-5665309061377477553
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Grounded
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: -3989795846899303455}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.25
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &-3989795846899303455
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
@ -145,6 +142,7 @@ AnimatorState:
|
||||
m_Transitions:
|
||||
- {fileID: 7699157278345549200}
|
||||
- {fileID: -9048563795446365488}
|
||||
- {fileID: -1366041084176649225}
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
@ -160,6 +158,34 @@ AnimatorState:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1101 &-2496720488816828561
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: VSpeed
|
||||
m_EventTreshold: 10
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Jump
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 2683883220764761602}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &-2059045626719700312
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
@ -201,34 +227,86 @@ AnimatorStateMachine:
|
||||
m_Position: {x: 890, y: 220, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -3208104225031276435}
|
||||
m_Position: {x: 890, y: 340, z: 0}
|
||||
m_Position: {x: 900, y: 320, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -2059045626719700312}
|
||||
m_Position: {x: 100, y: 50, z: 0}
|
||||
m_Position: {x: 560, y: -160, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 2683883220764761602}
|
||||
m_Position: {x: 330, y: 100, z: 0}
|
||||
m_Position: {x: 330, y: 170, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 3056242929772600144}
|
||||
m_Position: {x: 330, y: 180, z: 0}
|
||||
m_Position: {x: 190, y: 320, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -6196007508092385579}
|
||||
m_Position: {x: 330, y: 260, z: 0}
|
||||
m_Position: {x: 500, y: 320, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -3989795846899303455}
|
||||
m_Position: {x: 330, y: 340, z: 0}
|
||||
m_Position: {x: 330, y: 450, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions:
|
||||
- {fileID: 2884326239320347019}
|
||||
- {fileID: -6839088151083392861}
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 350, y: -60, z: 0}
|
||||
m_AnyStatePosition: {x: 580, y: -260, z: 0}
|
||||
m_EntryPosition: {x: 1140, y: 220, z: 0}
|
||||
m_ExitPosition: {x: 640, y: 50, z: 0}
|
||||
m_ExitPosition: {x: 870, y: -150, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: 8507286312630441258}
|
||||
--- !u!1101 &-1366041084176649225
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: VSpeed
|
||||
m_EventTreshold: 10
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Jump
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 2683883220764761602}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.75
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &-505540794586266171
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: VSpeed
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: -6196007508092385579}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &-426421497963076622
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
@ -328,6 +406,31 @@ AnimatorStateTransition:
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &1564265989192692461
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: VSpeed
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 3056242929772600144}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &2683883220764761602
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
@ -339,8 +442,8 @@ AnimatorState:
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions:
|
||||
- {fileID: 5820879251909814290}
|
||||
- {fileID: 3010298919337203651}
|
||||
- {fileID: 1564265989192692461}
|
||||
- {fileID: -505540794586266171}
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
@ -381,34 +484,6 @@ AnimatorStateTransition:
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &3010298919337203651
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 2
|
||||
m_ConditionEvent: Jump
|
||||
m_EventTreshold: 0
|
||||
- m_ConditionMode: 1
|
||||
m_ConditionEvent: Grounded
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 0}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 1
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0
|
||||
m_HasExitTime: 1
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1102 &3056242929772600144
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
@ -417,10 +492,11 @@ AnimatorState:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Jump_Mid-Air
|
||||
m_Speed: 1
|
||||
m_Speed: 2
|
||||
m_CycleOffset: 0
|
||||
m_Transitions:
|
||||
- {fileID: -6437098828268326780}
|
||||
- {fileID: -5665309061377477553}
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
@ -453,36 +529,11 @@ AnimatorStateTransition:
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.25
|
||||
m_HasExitTime: 1
|
||||
m_HasFixedDuration: 1
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
--- !u!1101 &5820879251909814290
|
||||
AnimatorStateTransition:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: VSpeed
|
||||
m_EventTreshold: 0
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 3056242929772600144}
|
||||
m_Solo: 0
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0
|
||||
m_HasExitTime: 1
|
||||
m_HasFixedDuration: 1
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
@ -503,11 +554,11 @@ AnimatorStateTransition:
|
||||
m_Mute: 0
|
||||
m_IsExit: 0
|
||||
serializedVersion: 3
|
||||
m_TransitionDuration: 0.25
|
||||
m_TransitionDuration: 0
|
||||
m_TransitionOffset: 0
|
||||
m_ExitTime: 0.5
|
||||
m_HasExitTime: 1
|
||||
m_HasFixedDuration: 1
|
||||
m_HasExitTime: 0
|
||||
m_HasFixedDuration: 0
|
||||
m_InterruptionSource: 0
|
||||
m_OrderedInterruption: 1
|
||||
m_CanTransitionToSelf: 1
|
||||
@ -548,6 +599,7 @@ AnimatorState:
|
||||
m_CycleOffset: 0
|
||||
m_Transitions:
|
||||
- {fileID: 144947006081674025}
|
||||
- {fileID: -2496720488816828561}
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
|
||||
@ -9065,4 +9065,25 @@ AnimationClip:
|
||||
flags: 0
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
m_Events:
|
||||
- time: 0.16666667
|
||||
functionName: Step
|
||||
data:
|
||||
objectReferenceParameter: {fileID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
- time: 0.5
|
||||
functionName: Step
|
||||
data:
|
||||
objectReferenceParameter: {fileID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
- time: 0.8333333
|
||||
functionName: Step
|
||||
data:
|
||||
objectReferenceParameter: {fileID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2625,8 +2625,8 @@ GameObject:
|
||||
- component: {fileID: 6550761235523395915}
|
||||
- component: {fileID: -592641019219869902}
|
||||
- component: {fileID: 683509761572695280}
|
||||
- component: {fileID: 5723917688524288437}
|
||||
- component: {fileID: 9068111837403598740}
|
||||
- component: {fileID: 5723917688524288437}
|
||||
- component: {fileID: 6106630833600239313}
|
||||
- component: {fileID: -953906007136484429}
|
||||
m_Layer: 0
|
||||
@ -2762,6 +2762,20 @@ PolygonCollider2D:
|
||||
- {x: 0.922113, y: 0.029278636}
|
||||
- {x: 1.135083, y: 3.0278835}
|
||||
m_UseDelaunayMesh: 0
|
||||
--- !u!114 &9068111837403598740
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6550761235523395909}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1a9fe08f70754d1fa12c13ac4cd190e3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.Health
|
||||
maxHealth: 3
|
||||
currentHealth: 0
|
||||
--- !u!114 &5723917688524288437
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -2791,20 +2805,6 @@ MonoBehaviour:
|
||||
stepSounds: {fileID: 11400000, guid: 37fe07ddcac109a46a86ba44df16d551, type: 2}
|
||||
hurtSounds: {fileID: 11400000, guid: 89e7f713913aecb40ada8c2f27c69303, type: 2}
|
||||
jumpSounds: {fileID: 11400000, guid: 9a63566742cc2d84c89928c8b988e4eb, type: 2}
|
||||
--- !u!114 &9068111837403598740
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6550761235523395909}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1a9fe08f70754d1fa12c13ac4cd190e3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.Health
|
||||
maxHealth: 3
|
||||
currentHealth: 0
|
||||
--- !u!114 &6106630833600239313
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -1284,8 +1284,8 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 1a9fe08f70754d1fa12c13ac4cd190e3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.Health
|
||||
maxHealth: 1
|
||||
currentHealth: 1
|
||||
maxHealth: 3
|
||||
currentHealth: 0
|
||||
--- !u!114 &8121578573349881196
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -1311,7 +1311,7 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
m_Bits: 64
|
||||
animator: {fileID: 6400385618584986155}
|
||||
maxHealth: 3
|
||||
maxHealth: 1
|
||||
stepSounds: {fileID: 11400000, guid: 37fe07ddcac109a46a86ba44df16d551, type: 2}
|
||||
hurtSounds: {fileID: 11400000, guid: 89e7f713913aecb40ada8c2f27c69303, type: 2}
|
||||
jumpSounds: {fileID: 11400000, guid: 9a63566742cc2d84c89928c8b988e4eb, type: 2}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -214,7 +214,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &28542763
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -1591,9 +1591,9 @@ RectTransform:
|
||||
m_Father: {fileID: 2054206196}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 1, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 20, y: -17}
|
||||
m_SizeDelta: {x: 20, y: 0}
|
||||
m_Pivot: {x: 1, y: 1}
|
||||
--- !u!114 &273493570
|
||||
MonoBehaviour:
|
||||
@ -1883,9 +1883,9 @@ RectTransform:
|
||||
m_Father: {fileID: 2054206196}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -17, y: 20}
|
||||
m_SizeDelta: {x: 0, y: 20}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!114 &328533400
|
||||
MonoBehaviour:
|
||||
@ -2156,7 +2156,7 @@ RectTransform:
|
||||
m_Father: {fileID: 112549352}
|
||||
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}
|
||||
@ -3285,9 +3285,9 @@ RectTransform:
|
||||
m_Father: {fileID: 265801313}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -17, y: 20}
|
||||
m_SizeDelta: {x: 0, y: 20}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!114 &787002084
|
||||
MonoBehaviour:
|
||||
@ -4106,7 +4106,7 @@ RectTransform:
|
||||
m_Father: {fileID: 38989454}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -4583,7 +4583,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1819180688}
|
||||
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}
|
||||
@ -4954,7 +4954,7 @@ RectTransform:
|
||||
m_Father: {fileID: 96234448}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 40, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -5029,7 +5029,7 @@ RectTransform:
|
||||
m_Father: {fileID: 47570575}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -5273,7 +5273,7 @@ RectTransform:
|
||||
m_Father: {fileID: 837630039}
|
||||
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}
|
||||
@ -6345,9 +6345,9 @@ RectTransform:
|
||||
m_Father: {fileID: 265801313}
|
||||
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: -17, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &1461740380
|
||||
MonoBehaviour:
|
||||
@ -6433,7 +6433,7 @@ RectTransform:
|
||||
m_Father: {fileID: 462618672}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -6680,7 +6680,7 @@ RectTransform:
|
||||
m_Father: {fileID: 244828024}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -6948,9 +6948,9 @@ RectTransform:
|
||||
m_Father: {fileID: 2054206196}
|
||||
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: -17, y: -17}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &1549457647
|
||||
MonoBehaviour:
|
||||
@ -8536,7 +8536,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0.0005065672}
|
||||
m_AnchoredPosition: {x: 0, y: 0.00004450919}
|
||||
m_SizeDelta: {x: 0, y: 300}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &1792218764
|
||||
@ -9686,7 +9686,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1932206569
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -10329,7 +10329,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1071857129}
|
||||
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}
|
||||
@ -10549,7 +10549,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1638546234}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 40, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -10624,7 +10624,7 @@ RectTransform:
|
||||
m_Father: {fileID: 2055356562}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 40, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -10835,7 +10835,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1411940924}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 40, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -15316,8 +15316,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 27
|
||||
m_Sprite: {fileID: -3144633359940803573, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -15721,8 +15721,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 15
|
||||
m_Sprite: {fileID: 4013115251250039491, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -15833,8 +15833,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 8
|
||||
m_Sprite: {fileID: 5688498731606456266, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -15927,8 +15927,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 13
|
||||
m_Sprite: {fileID: -7314516758093373832, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -16166,8 +16166,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 29
|
||||
m_Sprite: {fileID: -5122409871508779338, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -16224,8 +16224,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 19
|
||||
m_Sprite: {fileID: -1334230109140272425, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -16300,8 +16300,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 26
|
||||
m_Sprite: {fileID: 3129511523439360540, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -16711,8 +16711,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 2
|
||||
m_Sprite: {fileID: -8320496513077812493, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -16871,8 +16871,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 23
|
||||
m_Sprite: {fileID: -4410586660626731876, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -17343,8 +17343,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 6
|
||||
m_Sprite: {fileID: -962209174622309214, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -17886,8 +17886,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 10
|
||||
m_Sprite: {fileID: -164839347033225330, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -24756,8 +24756,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 3
|
||||
m_Sprite: {fileID: 4915747386952914504, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -24889,8 +24889,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 31
|
||||
m_Sprite: {fileID: -6961950492574641117, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -25066,8 +25066,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 20
|
||||
m_Sprite: {fileID: -1549096108764380935, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -33172,8 +33172,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 7
|
||||
m_Sprite: {fileID: -5661695156105813953, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -33375,8 +33375,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 5
|
||||
m_Sprite: {fileID: 3846603968152241411, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -33512,8 +33512,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 28
|
||||
m_Sprite: {fileID: 150988668789867834, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -37628,8 +37628,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 24
|
||||
m_Sprite: {fileID: -921071596155124139, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -38243,8 +38243,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 17
|
||||
m_Sprite: {fileID: 5546884723567340426, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -38317,8 +38317,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 30
|
||||
m_Sprite: {fileID: 1727280087122091534, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -38556,8 +38556,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 11
|
||||
m_Sprite: {fileID: 9202984404932711791, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -41034,8 +41034,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 18
|
||||
m_Sprite: {fileID: 4343387480515338132, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -41257,8 +41257,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 21
|
||||
m_Sprite: {fileID: 3753856189432605465, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
@ -41484,8 +41484,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingLayerID: 1159882347
|
||||
m_SortingLayer: -1
|
||||
m_SortingOrder: 14
|
||||
m_Sprite: {fileID: 6783752571112214151, guid: c0941254ff0f017419c681f7d6d44584, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
|
||||
13
Assets/Scripts/Abstract/Enums.cs
Normal file
13
Assets/Scripts/Abstract/Enums.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Game2D.Core
|
||||
{
|
||||
public enum Direction2D
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Abstract/Enums.cs.meta
Normal file
2
Assets/Scripts/Abstract/Enums.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a5aaacda43707540828f858a46a6a6d
|
||||
@ -3,31 +3,34 @@ using UnityEngine;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
public class CinemachineFindPlayer : MonoBehaviour
|
||||
namespace RR.Game2D.Behaviour.Camera
|
||||
{
|
||||
|
||||
private GameObject _tPlayer;
|
||||
private Transform _tFollowTarget;
|
||||
private CinemachineCamera _virtualCamera;
|
||||
|
||||
void Start()
|
||||
public class CinemachineFindPlayer : MonoBehaviour
|
||||
{
|
||||
_virtualCamera = GetComponent<CinemachineCamera>();
|
||||
|
||||
if (_tPlayer != null) return;
|
||||
|
||||
_tPlayer = GameManager.Instance.PlayerObj;
|
||||
|
||||
if (_tPlayer == null) _tPlayer = GameObject.FindWithTag("Player");
|
||||
private GameObject _tPlayer;
|
||||
private Transform _tFollowTarget;
|
||||
private CinemachineCamera _virtualCamera;
|
||||
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
_virtualCamera = GetComponent<CinemachineCamera>();
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (_tPlayer == null) return;
|
||||
|
||||
_tFollowTarget = _tPlayer.transform;
|
||||
_virtualCamera.LookAt = _tFollowTarget;
|
||||
_virtualCamera.Follow = _tFollowTarget;
|
||||
if (_tPlayer != null) return;
|
||||
|
||||
_tPlayer = GameManager.Instance.PlayerObj;
|
||||
|
||||
if (_tPlayer == null) _tPlayer = GameObject.FindWithTag("Player");
|
||||
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (_tPlayer == null) return;
|
||||
|
||||
_tFollowTarget = _tPlayer.transform;
|
||||
_virtualCamera.LookAt = _tFollowTarget;
|
||||
_virtualCamera.Follow = _tFollowTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ namespace RR.Game2D.Entities
|
||||
|
||||
private string PrefKey => $"CP_{SceneManager.GetActiveScene().name}";
|
||||
|
||||
void Awake()
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance && Instance != this)
|
||||
{
|
||||
|
||||
@ -11,7 +11,7 @@ namespace RR.Game2D.Entities
|
||||
[Tooltip("Направление/вектор взгляда по умолчанию")]
|
||||
public Vector2 defaultForward = Vector2.right;
|
||||
|
||||
void Start()
|
||||
private void Start()
|
||||
{
|
||||
// Пытаемся получить позицию из CheckpointSystem, если он есть.
|
||||
Vector3 spawnPos = defaultSpawnPosition;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
using RR.Common.Interfaces;
|
||||
using RR.Common.Utilities;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Game2D.Behaviour.ScriptableObjects;
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ namespace RR.Game2D.Behaviour
|
||||
{
|
||||
|
||||
[Header("Управление")]
|
||||
[SerializeField] private InputActionReference moveAction; // Value(Vector2), например "Move" (A/D, ←/→, stick)
|
||||
[SerializeField] private InputActionReference moveAction;
|
||||
[SerializeField] private InputActionReference jumpAction;
|
||||
|
||||
[Header("Горизонтальное движение")]
|
||||
@ -64,19 +64,19 @@ namespace RR.Game2D.Behaviour
|
||||
private static readonly int GroundedAnimator = Animator.StringToHash("Grounded");
|
||||
private static readonly int Death = Animator.StringToHash("Death");
|
||||
|
||||
void OnEnable()
|
||||
private void OnEnable()
|
||||
{
|
||||
moveAction.action.Enable();
|
||||
jumpAction.action.Enable();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
private void OnDisable()
|
||||
{
|
||||
moveAction.action.Disable();
|
||||
jumpAction.action.Disable();
|
||||
}
|
||||
|
||||
void Awake()
|
||||
private void Awake()
|
||||
{
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
//col = GetComponent<Collider2D>();
|
||||
@ -102,7 +102,7 @@ namespace RR.Game2D.Behaviour
|
||||
_health.SetMaxHealth(maxHealth);
|
||||
}
|
||||
|
||||
void Update()
|
||||
private void Update()
|
||||
{
|
||||
// --- Ввод ---
|
||||
Vector2 move = moveAction ? moveAction.action.ReadValue<Vector2>() : Vector2.zero;
|
||||
@ -196,7 +196,7 @@ namespace RR.Game2D.Behaviour
|
||||
/// Метод для проверки соприкосновения с землей
|
||||
/// </summary>
|
||||
/// <returns>bool isGrounded - на земле ли игрок</returns>
|
||||
bool Grounded()
|
||||
private bool Grounded()
|
||||
{
|
||||
Collider2D hit = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundMask);
|
||||
bool isGrounded = hit != null
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
{
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
public class JumpPad2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float verticalBand = 0.5f;
|
||||
private BoxCollider2D _col;
|
||||
|
||||
public Action<GameObject> PlayerJumped;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_col = GetComponent<BoxCollider2D>();
|
||||
|
||||
if (!_col.isTrigger)
|
||||
_col.isTrigger = true;
|
||||
}
|
||||
|
||||
//private void OnTriggerEnter2D(Collider2D other)
|
||||
//{
|
||||
// var d = col.Distance(other);
|
||||
|
||||
// Vector2 contactMid = (d.pointA + d.pointB) * 0.5f;
|
||||
|
||||
// bool topByNormal = IsTopByNormal(col, d.normal, maxAngleDeg: 30f);
|
||||
|
||||
// bool topByBox = false;
|
||||
// if (col is BoxCollider2D box)
|
||||
// topByBox = IsTopByLocalY(box, contactMid, bandPercent: bandPercent);
|
||||
|
||||
// bool isTopContact = topByNormal || topByBox;
|
||||
|
||||
// if (isTopContact)
|
||||
// {
|
||||
// if (other.CompareTag("Player"))
|
||||
// {
|
||||
// PlayerController playerController = other.GetComponent<PlayerController>();
|
||||
// Rigidbody2D playerRB = other.GetComponent<Rigidbody2D>();
|
||||
|
||||
// Vector2 newPlayerVelocity = playerRB.linearVelocity;
|
||||
// newPlayerVelocity.y = playerController.jumpVelocity;
|
||||
// playerRB.linearVelocity = newPlayerVelocity;
|
||||
// }
|
||||
// }
|
||||
// //var distance = col.Distance(collision);
|
||||
// //Vector2 localPoint = col.transform.InverseTransformPoint(distance.pointA);
|
||||
// //float halfHeight = col.size.y * 0.5f;
|
||||
// //float topY = col.offset.y * halfHeight;
|
||||
|
||||
// //float band = Mathf.Max(0.001f, col.size.y * bandPercent);
|
||||
//}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (other.CompareTag("Player"))
|
||||
{
|
||||
PlayerController playerController = other.GetComponent<PlayerController>();
|
||||
Rigidbody2D playerRB = other.GetComponent<Rigidbody2D>();
|
||||
|
||||
if (Mathf.Abs(other.transform.position.y - _col.bounds.max.y) <= verticalBand)
|
||||
{
|
||||
Vector2 newPlayerVelocity = playerRB.linearVelocity;
|
||||
newPlayerVelocity.y = playerController.jumpVelocity;
|
||||
playerRB.linearVelocity = newPlayerVelocity;
|
||||
|
||||
PlayerJumped?.Invoke(other.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//public static bool IsTopByNormal(Collider2D myCol, Vector2 contactNormal, float maxAngleDeg = 30f)
|
||||
//{
|
||||
// var up = (Vector2)myCol.transform.up;
|
||||
// float cos = Vector2.Dot(contactNormal.normalized, up);
|
||||
// float cosThresh = Mathf.Cos(maxAngleDeg * Mathf.Deg2Rad);
|
||||
// return cos >= cosThresh;
|
||||
//}
|
||||
|
||||
//public static bool IsTopByLocalY(BoxCollider2D box, Vector2 worldPoint, float bandPercent = 0.1f)
|
||||
//{
|
||||
// // Локальная точка
|
||||
// Vector2 pLocal = box.transform.InverseTransformPoint(worldPoint);
|
||||
// // Верхняя кромка в локальных координатах с учётом offset
|
||||
// float halfH = box.size.y * 0.5f;
|
||||
// float topY = box.offset.y + halfH;
|
||||
|
||||
// // Ширина допустимого пояска (процент от высоты коробки)
|
||||
// float band = Mathf.Max(0.001f, box.size.y * bandPercent);
|
||||
|
||||
// return pLocal.y >= (topY - band);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@ -1,260 +1,263 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Адаптер между системой ввода COM и Unity Input System.
|
||||
/// Создаёт виртуальный Gamepad и транслирует состояние кнопок из:
|
||||
/// - действий игроков (PlayerAction)
|
||||
/// - физических пинов (int индексы)
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-100)]
|
||||
public class ComInputToInputSystemAdapter : MonoBehaviour
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
private const string VirtualGamepadProduct = "COM Bridge Gamepad";
|
||||
private const string VirtualGamepadManufacturer = "Robotic Retailers";
|
||||
|
||||
[Header("Источник состояния")]
|
||||
[Tooltip("Если true — использовать In.GetButtonAnyPlayer, иначе — In.GetButtonCurrentPlayer")]
|
||||
[SerializeField] private bool useAnyPlayer = true;
|
||||
|
||||
[Header("Маппинг действий (PlayerAction -> Gamepad)")]
|
||||
[SerializeField] private List<ActionBinding> actionBindings = new();
|
||||
|
||||
[Header("Маппинг пинов (индекс пина -> Gamepad)")]
|
||||
[SerializeField] private List<PinBinding> pinBindings = new();
|
||||
|
||||
[Header("Отладка")]
|
||||
[SerializeField] private bool logCreateDevice = true;
|
||||
|
||||
private static Gamepad _virtualPad;
|
||||
private static bool _deviceCreated;
|
||||
|
||||
private GamepadState _currentState;
|
||||
|
||||
#region Структуры маппинга
|
||||
|
||||
[Serializable]
|
||||
public struct ActionBinding
|
||||
/// <summary>
|
||||
/// Адаптер между системой ввода COM и Unity Input System.
|
||||
/// Создаёт виртуальный Gamepad и транслирует состояние кнопок из:
|
||||
/// - действий игроков (PlayerAction)
|
||||
/// - физических пинов (int индексы)
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-100)]
|
||||
public class ComInputToInputSystemAdapter : MonoBehaviour
|
||||
{
|
||||
public PlayerAction action;
|
||||
public BindingTarget target;
|
||||
public GamepadButton button;
|
||||
public DpadDirection dpadDirection;
|
||||
}
|
||||
private const string VirtualGamepadProduct = "COM Bridge Gamepad";
|
||||
private const string VirtualGamepadManufacturer = "Robotic Retailers";
|
||||
|
||||
[Serializable]
|
||||
public struct PinBinding
|
||||
{
|
||||
public int pinIndex;
|
||||
public BindingTarget target;
|
||||
public GamepadButton button;
|
||||
public DpadDirection dpadDirection;
|
||||
}
|
||||
[Header("Источник состояния")]
|
||||
[Tooltip("Если true — использовать In.GetButtonAnyPlayer, иначе — In.GetButtonCurrentPlayer")]
|
||||
[SerializeField] private bool useAnyPlayer = true;
|
||||
|
||||
public enum BindingTarget { Button, Dpad }
|
||||
public enum DpadDirection { Up, Down, Left, Right }
|
||||
[Header("Маппинг действий (PlayerAction -> Gamepad)")]
|
||||
[SerializeField] private List<ActionBinding> actionBindings = new();
|
||||
|
||||
#endregion
|
||||
[Header("Маппинг пинов (индекс пина -> Gamepad)")]
|
||||
[SerializeField] private List<PinBinding> pinBindings = new();
|
||||
|
||||
#region Unity Lifecycle
|
||||
[Header("Отладка")]
|
||||
[SerializeField] private bool logCreateDevice = true;
|
||||
|
||||
// Если нет общего инициализатора, можно раскомментировать Awake
|
||||
// private void Awake()
|
||||
// {
|
||||
// Initialize();
|
||||
// }
|
||||
private static Gamepad _virtualPad;
|
||||
private static bool _deviceCreated;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EnsureVirtualGamepadExists();
|
||||
}
|
||||
private GamepadState _currentState;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_virtualPad == null) return;
|
||||
#region Структуры маппинга
|
||||
|
||||
UpdateGamepadState();
|
||||
InputSystem.QueueStateEvent(_virtualPad, _currentState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
EnsureVirtualGamepadExists();
|
||||
InitializeDefaultBindings();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Update
|
||||
|
||||
private void UpdateGamepadState()
|
||||
{
|
||||
_currentState = default;
|
||||
|
||||
ProcessActionBindings();
|
||||
ProcessPinBindings();
|
||||
}
|
||||
|
||||
private void ProcessActionBindings()
|
||||
{
|
||||
foreach (ActionBinding binding in actionBindings)
|
||||
[Serializable]
|
||||
public struct ActionBinding
|
||||
{
|
||||
bool isPressed = useAnyPlayer
|
||||
? In.GetButtonAnyPlayer(binding.action)
|
||||
: In.GetButtonCurrentPlayer(binding.action);
|
||||
public PlayerAction action;
|
||||
public BindingTarget target;
|
||||
public GamepadButton button;
|
||||
public DpadDirection dpadDirection;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
[Serializable]
|
||||
public struct PinBinding
|
||||
{
|
||||
public int pinIndex;
|
||||
public BindingTarget target;
|
||||
public GamepadButton button;
|
||||
public DpadDirection dpadDirection;
|
||||
}
|
||||
|
||||
public enum BindingTarget { Button, Dpad }
|
||||
public enum DpadDirection { Up, Down, Left, Right }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
// Если нет общего инициализатора, можно раскомментировать Awake
|
||||
// private void Awake()
|
||||
// {
|
||||
// Initialize();
|
||||
// }
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EnsureVirtualGamepadExists();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_virtualPad == null) return;
|
||||
|
||||
UpdateGamepadState();
|
||||
InputSystem.QueueStateEvent(_virtualPad, _currentState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
EnsureVirtualGamepadExists();
|
||||
InitializeDefaultBindings();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Update
|
||||
|
||||
private void UpdateGamepadState()
|
||||
{
|
||||
_currentState = default;
|
||||
|
||||
ProcessActionBindings();
|
||||
ProcessPinBindings();
|
||||
}
|
||||
|
||||
private void ProcessActionBindings()
|
||||
{
|
||||
foreach (ActionBinding binding in actionBindings)
|
||||
{
|
||||
ApplyBinding(ref _currentState, binding.target, binding.button, binding.dpadDirection);
|
||||
bool isPressed = useAnyPlayer
|
||||
? In.GetButtonAnyPlayer(binding.action)
|
||||
: In.GetButtonCurrentPlayer(binding.action);
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
ApplyBinding(ref _currentState, binding.target, binding.button, binding.dpadDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPinBindings()
|
||||
{
|
||||
foreach (PinBinding binding in pinBindings)
|
||||
private void ProcessPinBindings()
|
||||
{
|
||||
if (In.GetButton(binding.pinIndex))
|
||||
foreach (PinBinding binding in pinBindings)
|
||||
{
|
||||
ApplyBinding(ref _currentState, binding.target, binding.button, binding.dpadDirection);
|
||||
if (In.GetButton(binding.pinIndex))
|
||||
{
|
||||
ApplyBinding(ref _currentState, binding.target, binding.button, binding.dpadDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Binding Logic
|
||||
#endregion
|
||||
|
||||
private static void ApplyBinding(
|
||||
ref GamepadState state,
|
||||
BindingTarget target,
|
||||
GamepadButton button,
|
||||
DpadDirection direction)
|
||||
{
|
||||
if (target == BindingTarget.Button)
|
||||
#region Binding Logic
|
||||
|
||||
private static void ApplyBinding(
|
||||
ref GamepadState state,
|
||||
BindingTarget target,
|
||||
GamepadButton button,
|
||||
DpadDirection direction)
|
||||
{
|
||||
state = state.WithButton(button);
|
||||
}
|
||||
else // Dpad
|
||||
{
|
||||
GamepadButton dpadButton = direction switch
|
||||
if (target == BindingTarget.Button)
|
||||
{
|
||||
DpadDirection.Up => GamepadButton.DpadUp,
|
||||
DpadDirection.Down => GamepadButton.DpadDown,
|
||||
DpadDirection.Left => GamepadButton.DpadLeft,
|
||||
DpadDirection.Right => GamepadButton.DpadRight,
|
||||
_ => GamepadButton.DpadUp
|
||||
state = state.WithButton(button);
|
||||
}
|
||||
else // Dpad
|
||||
{
|
||||
GamepadButton dpadButton = direction switch
|
||||
{
|
||||
DpadDirection.Up => GamepadButton.DpadUp,
|
||||
DpadDirection.Down => GamepadButton.DpadDown,
|
||||
DpadDirection.Left => GamepadButton.DpadLeft,
|
||||
DpadDirection.Right => GamepadButton.DpadRight,
|
||||
_ => GamepadButton.DpadUp
|
||||
};
|
||||
state = state.WithButton(dpadButton);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Gamepad Management
|
||||
|
||||
private void EnsureVirtualGamepadExists()
|
||||
{
|
||||
if (_deviceCreated && _virtualPad != null)
|
||||
return;
|
||||
|
||||
// Проверяем, не создан ли уже геймпад
|
||||
if (TryFindExistingVirtualGamepad(out Gamepad existingGamepad))
|
||||
{
|
||||
_virtualPad = existingGamepad;
|
||||
_deviceCreated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CreateVirtualGamepad();
|
||||
}
|
||||
|
||||
private static bool TryFindExistingVirtualGamepad(out Gamepad gamepad)
|
||||
{
|
||||
gamepad = null;
|
||||
|
||||
foreach (InputDevice device in InputSystem.devices)
|
||||
{
|
||||
if (device is not Gamepad { description: { product: VirtualGamepadProduct } } gp) continue;
|
||||
gamepad = gp;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateVirtualGamepad()
|
||||
{
|
||||
InputDeviceDescription description = new()
|
||||
{
|
||||
interfaceName = "Virtual",
|
||||
deviceClass = "Gamepad",
|
||||
product = VirtualGamepadProduct,
|
||||
manufacturer = VirtualGamepadManufacturer
|
||||
};
|
||||
state = state.WithButton(dpadButton);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Gamepad Management
|
||||
|
||||
private void EnsureVirtualGamepadExists()
|
||||
{
|
||||
if (_deviceCreated && _virtualPad != null)
|
||||
return;
|
||||
|
||||
// Проверяем, не создан ли уже геймпад
|
||||
if (TryFindExistingVirtualGamepad(out Gamepad existingGamepad))
|
||||
{
|
||||
_virtualPad = existingGamepad;
|
||||
_virtualPad = (Gamepad)InputSystem.AddDevice(description);
|
||||
_deviceCreated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CreateVirtualGamepad();
|
||||
}
|
||||
|
||||
private static bool TryFindExistingVirtualGamepad(out Gamepad gamepad)
|
||||
{
|
||||
gamepad = null;
|
||||
|
||||
foreach (InputDevice device in InputSystem.devices)
|
||||
{
|
||||
if (device is not Gamepad { description: { product: VirtualGamepadProduct } } gp) continue;
|
||||
gamepad = gp;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateVirtualGamepad()
|
||||
{
|
||||
InputDeviceDescription description = new()
|
||||
{
|
||||
interfaceName = "Virtual",
|
||||
deviceClass = "Gamepad",
|
||||
product = VirtualGamepadProduct,
|
||||
manufacturer = VirtualGamepadManufacturer
|
||||
};
|
||||
|
||||
_virtualPad = (Gamepad)InputSystem.AddDevice(description);
|
||||
_deviceCreated = true;
|
||||
|
||||
if (logCreateDevice)
|
||||
{
|
||||
Debug.Log($"[ComInputAdapter] Виртуальный Gamepad создан: {_virtualPad.name}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default Bindings
|
||||
|
||||
private void InitializeDefaultBindings()
|
||||
{
|
||||
if (actionBindings.Count > 0)
|
||||
return;
|
||||
|
||||
AddDefaultActionBindings();
|
||||
}
|
||||
|
||||
private void AddDefaultActionBindings()
|
||||
{
|
||||
// Маппинг стрелок на D-Pad
|
||||
TryAddActionBinding(PlayerAction.up, BindingTarget.Dpad, GamepadButton.North, DpadDirection.Up);
|
||||
TryAddActionBinding(PlayerAction.down, BindingTarget.Dpad, GamepadButton.South, DpadDirection.Down);
|
||||
TryAddActionBinding(PlayerAction.left, BindingTarget.Dpad, GamepadButton.West, DpadDirection.Left);
|
||||
TryAddActionBinding(PlayerAction.right, BindingTarget.Dpad, GamepadButton.East, DpadDirection.Right);
|
||||
|
||||
// Кнопки действий
|
||||
TryAddActionBinding(PlayerAction.action, BindingTarget.Button, GamepadButton.South, DpadDirection.Up);
|
||||
}
|
||||
|
||||
private void TryAddActionBinding(
|
||||
PlayerAction action,
|
||||
BindingTarget target,
|
||||
GamepadButton button,
|
||||
DpadDirection direction)
|
||||
{
|
||||
try
|
||||
{
|
||||
actionBindings.Add(new ActionBinding
|
||||
if (logCreateDevice)
|
||||
{
|
||||
action = action,
|
||||
target = target,
|
||||
button = button,
|
||||
dpadDirection = direction
|
||||
});
|
||||
Debug.Log($"[ComInputAdapter] Виртуальный Gamepad создан: {_virtualPad.name}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[ComInputAdapter] Не удалось добавить привязку для {action}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Default Bindings
|
||||
|
||||
private void InitializeDefaultBindings()
|
||||
{
|
||||
if (actionBindings.Count > 0)
|
||||
return;
|
||||
|
||||
AddDefaultActionBindings();
|
||||
}
|
||||
|
||||
private void AddDefaultActionBindings()
|
||||
{
|
||||
// Маппинг стрелок на D-Pad
|
||||
TryAddActionBinding(PlayerAction.up, BindingTarget.Dpad, GamepadButton.North, DpadDirection.Up);
|
||||
TryAddActionBinding(PlayerAction.down, BindingTarget.Dpad, GamepadButton.South, DpadDirection.Down);
|
||||
TryAddActionBinding(PlayerAction.left, BindingTarget.Dpad, GamepadButton.West, DpadDirection.Left);
|
||||
TryAddActionBinding(PlayerAction.right, BindingTarget.Dpad, GamepadButton.East, DpadDirection.Right);
|
||||
|
||||
// Кнопки действий
|
||||
TryAddActionBinding(PlayerAction.action, BindingTarget.Button, GamepadButton.South, DpadDirection.Up);
|
||||
}
|
||||
|
||||
private void TryAddActionBinding(
|
||||
PlayerAction action,
|
||||
BindingTarget target,
|
||||
GamepadButton button,
|
||||
DpadDirection direction)
|
||||
{
|
||||
try
|
||||
{
|
||||
actionBindings.Add(new ActionBinding
|
||||
{
|
||||
action = action,
|
||||
target = target,
|
||||
button = button,
|
||||
dpadDirection = direction
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[ComInputAdapter] Не удалось добавить привязку для {action}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,43 +1,46 @@
|
||||
/// <summary>
|
||||
/// Соответствие между кнопками игрового ввода и цифровыми выходами периферийной платы (пины 0-15).
|
||||
/// Содержит mapping для управления двух игроков, служебных кнопок и датчиков хопера.
|
||||
/// </summary>
|
||||
public enum ButtonPinMapping
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
/// <summary>Кнопка движения вверх для первого игрока</summary>
|
||||
up1 = 0,
|
||||
/// <summary>Кнопка движения вправо для первого игрока</summary>
|
||||
right1 = 1,
|
||||
/// <summary>Кнопка движения вниз для первого игрока</summary>
|
||||
down1 = 2,
|
||||
/// <summary>Кнопка движения влево для первого игрока</summary>
|
||||
left1 = 3,
|
||||
/// <summary>Действие/огонь для первого игрока</summary>
|
||||
action1 = 4,
|
||||
/// <summary>
|
||||
/// Соответствие между кнопками игрового ввода и цифровыми выходами периферийной платы (пины 0-15).
|
||||
/// Содержит mapping для управления двух игроков, служебных кнопок и датчиков хопера.
|
||||
/// </summary>
|
||||
public enum ButtonPinMapping
|
||||
{
|
||||
/// <summary>Кнопка движения вверх для первого игрока</summary>
|
||||
up1 = 0,
|
||||
/// <summary>Кнопка движения вправо для первого игрока</summary>
|
||||
right1 = 1,
|
||||
/// <summary>Кнопка движения вниз для первого игрока</summary>
|
||||
down1 = 2,
|
||||
/// <summary>Кнопка движения влево для первого игрока</summary>
|
||||
left1 = 3,
|
||||
/// <summary>Действие/огонь для первого игрока</summary>
|
||||
action1 = 4,
|
||||
|
||||
/// <summary>Кнопка движения вверх для второго игрока</summary>
|
||||
up2 = 5,
|
||||
/// <summary>Кнопка движения вправо для второго игрока</summary>
|
||||
right2 = 6,
|
||||
/// <summary>Кнопка движения вниз для второго игрока</summary>
|
||||
down2 = 7,
|
||||
/// <summary>Кнопка движения влево для второго игрока</summary>
|
||||
left2 = 8,
|
||||
/// <summary>Действие/огонь для второго игрока</summary>
|
||||
action2 = 9,
|
||||
/// <summary>Кнопка движения вверх для второго игрока</summary>
|
||||
up2 = 5,
|
||||
/// <summary>Кнопка движения вправо для второго игрока</summary>
|
||||
right2 = 6,
|
||||
/// <summary>Кнопка движения вниз для второго игрока</summary>
|
||||
down2 = 7,
|
||||
/// <summary>Кнопка движения влево для второго игрока</summary>
|
||||
left2 = 8,
|
||||
/// <summary>Действие/огонь для второго игрока</summary>
|
||||
action2 = 9,
|
||||
|
||||
/// <summary>Запуск игры для одного игрока</summary>
|
||||
startOnePlayer = 10,
|
||||
/// <summary>Запуск игры для двух игроков</summary>
|
||||
startTwoPlayers = 11,
|
||||
/// <summary>Секретная/служебная кнопка для захода в настройки</summary>
|
||||
secret = 12,
|
||||
/// <summary>Запуск игры для одного игрока</summary>
|
||||
startOnePlayer = 10,
|
||||
/// <summary>Запуск игры для двух игроков</summary>
|
||||
startTwoPlayers = 11,
|
||||
/// <summary>Секретная/служебная кнопка для захода в настройки</summary>
|
||||
secret = 12,
|
||||
|
||||
/// <summary>Дополнительная/резервная кнопка</summary>
|
||||
extra = 13,
|
||||
/// <summary>Дополнительная/резервная кнопка</summary>
|
||||
extra = 13,
|
||||
|
||||
/// <summary>Датчик наличия приза в хопере</summary>
|
||||
hasPrize = 14,
|
||||
/// <summary>Датчик выдачи приза из хопера</summary>
|
||||
prizeGiven = 15
|
||||
}
|
||||
/// <summary>Датчик наличия приза в хопере</summary>
|
||||
hasPrize = 14,
|
||||
/// <summary>Датчик выдачи приза из хопера</summary>
|
||||
prizeGiven = 15
|
||||
}
|
||||
}
|
||||
@ -2,253 +2,256 @@
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
public class COMInput : InputSource
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
//Количество = цифровые + 2 датчика.
|
||||
//На момент написания количество цифровых входов 7*N.
|
||||
//Настроить количество пинов под свою настройку платы
|
||||
private const int PIN_COUNT = 16;
|
||||
|
||||
private const char POSITIVE = '0';
|
||||
private const char NEGATIVE = '1';
|
||||
|
||||
private bool[] buttonState = new bool[PIN_COUNT];
|
||||
private bool[] buttonDown = new bool[PIN_COUNT];
|
||||
private bool[] buttonUp = new bool[PIN_COUNT];
|
||||
|
||||
//Свойство для определения текущего игрока
|
||||
//Должно возвращать 1 или 2
|
||||
//Используется для получения состояния кнопок с пометкой ...CurrentPlayer
|
||||
private int currentPlayer => 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
|
||||
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
|
||||
|
||||
public override bool Initialize()
|
||||
public class COMInput : InputSource
|
||||
{
|
||||
try
|
||||
//Количество = цифровые + 2 датчика.
|
||||
//На момент написания количество цифровых входов 7*N.
|
||||
//Настроить количество пинов под свою настройку платы
|
||||
private const int PIN_COUNT = 16;
|
||||
|
||||
private const char POSITIVE = '0';
|
||||
private const char NEGATIVE = '1';
|
||||
|
||||
private bool[] buttonState = new bool[PIN_COUNT];
|
||||
private bool[] buttonDown = new bool[PIN_COUNT];
|
||||
private bool[] buttonUp = new bool[PIN_COUNT];
|
||||
|
||||
//Свойство для определения текущего игрока
|
||||
//Должно возвращать 1 или 2
|
||||
//Используется для получения состояния кнопок с пометкой ...CurrentPlayer
|
||||
private int currentPlayer => 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
|
||||
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
|
||||
|
||||
public override bool Initialize()
|
||||
{
|
||||
gm = GameManager.Instance;
|
||||
|
||||
//ОБЯЗАТЕЛЬНЫЙ БЛОК
|
||||
ComWrapper.OnInputReceived.AddListener(UpdateInputState);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GameManager.Logger.Log("COMInput", $"Что то в COMInput {e.ToString()}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#region PUBLIC_API
|
||||
|
||||
//ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping)
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние конкретной кнопки по её физическому пину.
|
||||
/// Возвращает true если кнопка в данный момент нажата.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>True если кнопка нажата в текущем кадре</returns>
|
||||
public override bool GetButton(ButtonPinMapping button) => buttonState[(int)button];
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
|
||||
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>True если кнопка была нажата в этом кадре</returns>
|
||||
public override bool GetButtonDown(ButtonPinMapping button) => ConsumeButtonEvent(buttonDown, button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
|
||||
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>True если кнопка была отпущена в этом кадре</returns>
|
||||
public override bool GetButtonUp(ButtonPinMapping button) => ConsumeButtonEvent(buttonUp, button);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние кнопки по числовому индексу пина напрямую из буфера состояний.
|
||||
/// Обходит перечисление ButtonPinMapping для прямого доступа к пинам по индексу.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||||
/// <remarks>
|
||||
/// Используется когда необходим прямой доступ к пинам без приведения к перечислению.
|
||||
/// Подходит для сценариев где количество пинов может превышать размер перечисления.
|
||||
/// </remarks>
|
||||
public override bool GetButton(int pinIndex) => buttonState[pinIndex];
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка нажата в начале текущего кадра по числовому индексу пина.
|
||||
/// Возвращает true только в первом кадре после нажатия и сбрасывает флаг события.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||||
/// <returns>true если кнопка была нажата в этом кадре</returns>
|
||||
/// <remarks>
|
||||
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
|
||||
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
|
||||
/// </remarks>
|
||||
public override bool GetButtonDown(int pinIndex) => ConsumeButtonEvent(buttonDown, pinIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра по числовому индексу пина.
|
||||
/// Возвращает true только в первом кадре после отпускания и сбрасывает флаг события.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||||
/// <returns>true если кнопка была отпущена в этом кадре</returns>
|
||||
/// <remarks>
|
||||
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
|
||||
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
|
||||
/// </remarks>
|
||||
public override bool GetButtonUp(int pinIndex) => ConsumeButtonEvent(buttonUp, pinIndex);
|
||||
// ------------------------------------------------------------------------------------
|
||||
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК АКТИВНОГО ИГРОКА (проверка по enum PlayerAction)
|
||||
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние действия для текущего игрока.
|
||||
/// Возвращает true если действие активно (кнопка нажата) у текущего игрока.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока (up, down, left, right, action, extra)</param>
|
||||
/// <returns>True если действие активно у текущего игрока</returns>
|
||||
public override bool GetButtonCurrentPlayer(PlayerAction button)
|
||||
=> GetButton(GetButtonForCurrentPlayer(button));
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие начато текущим игроком в этом кадре.
|
||||
/// Возвращает true только в первом кадре после начала действия.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было начато в этом кадре</returns>
|
||||
public override bool GetButtonDownCurrentPlayer(PlayerAction button)
|
||||
=> GetButtonDown(GetButtonForCurrentPlayer(button));
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие завершено текущим игроком в этом кадре.
|
||||
/// Возвращает true только в первом кадре после завершения действия.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было завершено в этом кадре</returns>
|
||||
public override bool GetButtonUpCurrentPlayer(PlayerAction button)
|
||||
=> GetButtonUp(GetButtonForCurrentPlayer(button));
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК ЛЮБОГО ИГРОКА (проверка по enum PlayerAction)
|
||||
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние действия для любого из игроков.
|
||||
/// Возвращает true если действие активно у первого ИЛИ второго игрока.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие активно у любого игрока</returns>
|
||||
public override bool GetButtonAnyPlayer(PlayerAction button)
|
||||
=> CheckBothPlayers(button, GetButton);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие начато любым игроком в этом кадре.
|
||||
/// Возвращает true если первый ИЛИ второй игрок начали действие в этом кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было начато любым игроком в этом кадре</returns>
|
||||
public override bool GetButtonDownAnyPlayer(PlayerAction button)
|
||||
=> CheckBothPlayers(button, GetButtonDown);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие завершено любым игроком в этом кадре.
|
||||
/// Возвращает true если первый ИЛИ второй игрок завершили действие в этом кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было завершено любым игроком в этом кадре</returns>
|
||||
public override bool GetButtonUpAnyPlayer(PlayerAction button)
|
||||
=> CheckBothPlayers(button, GetButtonUp);
|
||||
#endregion
|
||||
|
||||
|
||||
//==========ХЕЛПЕРЫ==========
|
||||
// Проверка нажатия с обоих контроллеров игроков
|
||||
private bool CheckBothPlayers(PlayerAction action, Func<ButtonPinMapping, bool> buttonFunc)
|
||||
{
|
||||
var buttons = _buttonByAction(action);
|
||||
return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button);
|
||||
}
|
||||
|
||||
// Проверка нажатия с контроллера текущего игрока
|
||||
private ButtonPinMapping GetButtonForCurrentPlayer(PlayerAction button)
|
||||
{
|
||||
var buttons = _buttonByAction(button);
|
||||
return currentPlayer == 1 ? buttons.player1button : buttons.player2button;
|
||||
}
|
||||
|
||||
// Маппер из действий игрока => пины платы
|
||||
private (ButtonPinMapping player1button, ButtonPinMapping player2button) _buttonByAction(PlayerAction playerAction) =>
|
||||
playerAction switch
|
||||
{
|
||||
PlayerAction.up => (ButtonPinMapping.up1, ButtonPinMapping.up2),
|
||||
PlayerAction.down => (ButtonPinMapping.down1, ButtonPinMapping.down2),
|
||||
PlayerAction.left => (ButtonPinMapping.left1, ButtonPinMapping.left2),
|
||||
PlayerAction.right => (ButtonPinMapping.right1, ButtonPinMapping.right2),
|
||||
PlayerAction.action => (ButtonPinMapping.action1, ButtonPinMapping.action2),
|
||||
PlayerAction.extra => (ButtonPinMapping.extra, ButtonPinMapping.extra), // одинаковая кнопка
|
||||
_ => throw new ArgumentException($"Unknown player action: {playerAction}")
|
||||
|
||||
};
|
||||
|
||||
// Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием.
|
||||
private void UpdateInputState(string input)
|
||||
{
|
||||
// Будем принимать в нативном формате by Pasha. 0 - нажато 1 - не нажато (чтобы преобразовывать циклом)
|
||||
// В параметрприходит строка вида "1111011111011111"
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
// Получаем новое состояние пина
|
||||
bool newState = input[i] == POSITIVE;
|
||||
|
||||
// Состояние пина изменилось => пишем в буферы
|
||||
if (newState != buttonState[i])
|
||||
try
|
||||
{
|
||||
if(newState) // Пин был зажат
|
||||
{
|
||||
buttonDown[i] = true;
|
||||
buttonUp[i] = false;
|
||||
}
|
||||
else // Пин был отжат
|
||||
{
|
||||
buttonDown[i] = false;
|
||||
buttonUp[i] = true;
|
||||
}
|
||||
gm = GameManager.Instance;
|
||||
|
||||
//ОБЯЗАТЕЛЬНЫЙ БЛОК
|
||||
ComWrapper.OnInputReceived.AddListener(UpdateInputState);
|
||||
|
||||
}
|
||||
|
||||
buttonState[i] = newState;
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
GameManager.Logger.Log("COMInput", $"Что то в COMInput {e.ToString()}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (input[^1] == POSITIVE) PlayerPrefs.SetInt("PrizeGivenBuffer", 1); //пишем в буфер чтобы между кадрами и порядком вызова не проебать
|
||||
|
||||
// DebugWindow.Message("Инпуты: " + string.Join("", buttonState));//можно вырезать
|
||||
}
|
||||
|
||||
// Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера
|
||||
private bool ConsumeButtonEvent(bool[] buffer, ButtonPinMapping button)
|
||||
{
|
||||
bool result = buffer[(int)button];
|
||||
buffer[(int)button] = false;
|
||||
return result;
|
||||
}
|
||||
#region PUBLIC_API
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу.
|
||||
/// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Буфер событий (buttonDown или buttonUp)</param>
|
||||
/// <param name="pinIndex">Числовой индекс пина</param>
|
||||
/// <returns>Состояние флага события до сброса</returns>
|
||||
private bool ConsumeButtonEvent(bool[] buffer, int pinIndex)
|
||||
{
|
||||
bool result = buffer[pinIndex];
|
||||
buffer[pinIndex] = false;
|
||||
return result;
|
||||
//ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping)
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние конкретной кнопки по её физическому пину.
|
||||
/// Возвращает true если кнопка в данный момент нажата.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>True если кнопка нажата в текущем кадре</returns>
|
||||
public override bool GetButton(ButtonPinMapping button) => buttonState[(int)button];
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
|
||||
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>True если кнопка была нажата в этом кадре</returns>
|
||||
public override bool GetButtonDown(ButtonPinMapping button) => ConsumeButtonEvent(buttonDown, button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
|
||||
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>True если кнопка была отпущена в этом кадре</returns>
|
||||
public override bool GetButtonUp(ButtonPinMapping button) => ConsumeButtonEvent(buttonUp, button);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние кнопки по числовому индексу пина напрямую из буфера состояний.
|
||||
/// Обходит перечисление ButtonPinMapping для прямого доступа к пинам по индексу.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||||
/// <remarks>
|
||||
/// Используется когда необходим прямой доступ к пинам без приведения к перечислению.
|
||||
/// Подходит для сценариев где количество пинов может превышать размер перечисления.
|
||||
/// </remarks>
|
||||
public override bool GetButton(int pinIndex) => buttonState[pinIndex];
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка нажата в начале текущего кадра по числовому индексу пина.
|
||||
/// Возвращает true только в первом кадре после нажатия и сбрасывает флаг события.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||||
/// <returns>true если кнопка была нажата в этом кадре</returns>
|
||||
/// <remarks>
|
||||
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
|
||||
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
|
||||
/// </remarks>
|
||||
public override bool GetButtonDown(int pinIndex) => ConsumeButtonEvent(buttonDown, pinIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра по числовому индексу пина.
|
||||
/// Возвращает true только в первом кадре после отпускания и сбрасывает флаг события.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-15)</param>
|
||||
/// <returns>true если кнопка была отпущена в этом кадре</returns>
|
||||
/// <remarks>
|
||||
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
|
||||
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
|
||||
/// </remarks>
|
||||
public override bool GetButtonUp(int pinIndex) => ConsumeButtonEvent(buttonUp, pinIndex);
|
||||
// ------------------------------------------------------------------------------------
|
||||
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК АКТИВНОГО ИГРОКА (проверка по enum PlayerAction)
|
||||
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние действия для текущего игрока.
|
||||
/// Возвращает true если действие активно (кнопка нажата) у текущего игрока.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока (up, down, left, right, action, extra)</param>
|
||||
/// <returns>True если действие активно у текущего игрока</returns>
|
||||
public override bool GetButtonCurrentPlayer(PlayerAction button)
|
||||
=> GetButton(GetButtonForCurrentPlayer(button));
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие начато текущим игроком в этом кадре.
|
||||
/// Возвращает true только в первом кадре после начала действия.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было начато в этом кадре</returns>
|
||||
public override bool GetButtonDownCurrentPlayer(PlayerAction button)
|
||||
=> GetButtonDown(GetButtonForCurrentPlayer(button));
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие завершено текущим игроком в этом кадре.
|
||||
/// Возвращает true только в первом кадре после завершения действия.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было завершено в этом кадре</returns>
|
||||
public override bool GetButtonUpCurrentPlayer(PlayerAction button)
|
||||
=> GetButtonUp(GetButtonForCurrentPlayer(button));
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК ЛЮБОГО ИГРОКА (проверка по enum PlayerAction)
|
||||
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние действия для любого из игроков.
|
||||
/// Возвращает true если действие активно у первого ИЛИ второго игрока.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие активно у любого игрока</returns>
|
||||
public override bool GetButtonAnyPlayer(PlayerAction button)
|
||||
=> CheckBothPlayers(button, GetButton);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие начато любым игроком в этом кадре.
|
||||
/// Возвращает true если первый ИЛИ второй игрок начали действие в этом кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было начато любым игроком в этом кадре</returns>
|
||||
public override bool GetButtonDownAnyPlayer(PlayerAction button)
|
||||
=> CheckBothPlayers(button, GetButtonDown);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, было ли действие завершено любым игроком в этом кадре.
|
||||
/// Возвращает true если первый ИЛИ второй игрок завершили действие в этом кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Действие игрока</param>
|
||||
/// <returns>True если действие было завершено любым игроком в этом кадре</returns>
|
||||
public override bool GetButtonUpAnyPlayer(PlayerAction button)
|
||||
=> CheckBothPlayers(button, GetButtonUp);
|
||||
#endregion
|
||||
|
||||
|
||||
//==========ХЕЛПЕРЫ==========
|
||||
// Проверка нажатия с обоих контроллеров игроков
|
||||
private bool CheckBothPlayers(PlayerAction action, Func<ButtonPinMapping, bool> buttonFunc)
|
||||
{
|
||||
var buttons = _buttonByAction(action);
|
||||
return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button);
|
||||
}
|
||||
|
||||
// Проверка нажатия с контроллера текущего игрока
|
||||
private ButtonPinMapping GetButtonForCurrentPlayer(PlayerAction button)
|
||||
{
|
||||
var buttons = _buttonByAction(button);
|
||||
return currentPlayer == 1 ? buttons.player1button : buttons.player2button;
|
||||
}
|
||||
|
||||
// Маппер из действий игрока => пины платы
|
||||
private (ButtonPinMapping player1button, ButtonPinMapping player2button) _buttonByAction(PlayerAction playerAction) =>
|
||||
playerAction switch
|
||||
{
|
||||
PlayerAction.up => (ButtonPinMapping.up1, ButtonPinMapping.up2),
|
||||
PlayerAction.down => (ButtonPinMapping.down1, ButtonPinMapping.down2),
|
||||
PlayerAction.left => (ButtonPinMapping.left1, ButtonPinMapping.left2),
|
||||
PlayerAction.right => (ButtonPinMapping.right1, ButtonPinMapping.right2),
|
||||
PlayerAction.action => (ButtonPinMapping.action1, ButtonPinMapping.action2),
|
||||
PlayerAction.extra => (ButtonPinMapping.extra, ButtonPinMapping.extra), // одинаковая кнопка
|
||||
_ => throw new ArgumentException($"Unknown player action: {playerAction}")
|
||||
|
||||
};
|
||||
|
||||
// Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием.
|
||||
private void UpdateInputState(string input)
|
||||
{
|
||||
// Будем принимать в нативном формате by Pasha. 0 - нажато 1 - не нажато (чтобы преобразовывать циклом)
|
||||
// В параметрприходит строка вида "1111011111011111"
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
// Получаем новое состояние пина
|
||||
bool newState = input[i] == POSITIVE;
|
||||
|
||||
// Состояние пина изменилось => пишем в буферы
|
||||
if (newState != buttonState[i])
|
||||
{
|
||||
if (newState) // Пин был зажат
|
||||
{
|
||||
buttonDown[i] = true;
|
||||
buttonUp[i] = false;
|
||||
}
|
||||
else // Пин был отжат
|
||||
{
|
||||
buttonDown[i] = false;
|
||||
buttonUp[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
buttonState[i] = newState;
|
||||
|
||||
}
|
||||
if (input[^1] == POSITIVE) PlayerPrefs.SetInt("PrizeGivenBuffer", 1); //пишем в буфер чтобы между кадрами и порядком вызова не проебать
|
||||
|
||||
// DebugWindow.Message("Инпуты: " + string.Join("", buttonState));//можно вырезать
|
||||
}
|
||||
|
||||
// Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера
|
||||
private bool ConsumeButtonEvent(bool[] buffer, ButtonPinMapping button)
|
||||
{
|
||||
bool result = buffer[(int)button];
|
||||
buffer[(int)button] = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу.
|
||||
/// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Буфер событий (buttonDown или buttonUp)</param>
|
||||
/// <param name="pinIndex">Числовой индекс пина</param>
|
||||
/// <returns>Состояние флага события до сброса</returns>
|
||||
private bool ConsumeButtonEvent(bool[] buffer, int pinIndex)
|
||||
{
|
||||
bool result = buffer[pinIndex];
|
||||
buffer[pinIndex] = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,56 +2,59 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
public static class ComNativeWrapper
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
private const string Dll = "ComNative"; // имя файла в Plugins/x86_64 без .dll
|
||||
|
||||
// --- Порты (Unicode) ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_ListPortsLength();
|
||||
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_ListPorts(StringBuilder buffer, int bufferChars);
|
||||
|
||||
// --- Открытие/закрытие ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_Open(string portName, int baud, int dataBits, int parity, int stopBits,
|
||||
int readTimeoutMs, int writeTimeoutMs);
|
||||
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Serial_Close(int handleId);
|
||||
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Serial_CloseAll(); // NEW
|
||||
|
||||
// --- Запись/чтение (строкой) ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int Serial_WriteStringUTF8(int handleId, string utf8, int appendNewline);
|
||||
|
||||
// byte[]-вариант к той же native-функции (EntryPoint), чтобы избежать маршаллинга StringBuilder
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Serial_ReadLineUTF8")]
|
||||
public static extern int Serial_ReadLineUTF8_bytes(int handleId, byte[] buffer, int bufferSize);
|
||||
|
||||
// --- Ошибка (Unicode) ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_GetLastErrorW(StringBuilder buffer, int bufferChars);
|
||||
|
||||
public static string GetLastError()
|
||||
public static class ComNativeWrapper
|
||||
{
|
||||
int need = Serial_GetLastErrorW(null, 0);
|
||||
var sb = new StringBuilder(Math.Max(need, 1));
|
||||
int r = Serial_GetLastErrorW(sb, sb.Capacity);
|
||||
return (r < 0) ? $"err({r})" : sb.ToString();
|
||||
}
|
||||
private const string Dll = "ComNative"; // имя файла в Plugins/x86_64 без .dll
|
||||
|
||||
public static string[] ListPorts()
|
||||
{
|
||||
int need = Serial_ListPortsLength();
|
||||
if (need <= 1) return Array.Empty<string>();
|
||||
var sb = new StringBuilder(need);
|
||||
int r = Serial_ListPorts(sb, sb.Capacity);
|
||||
if (r < 0) return Array.Empty<string>();
|
||||
string joined = sb.ToString();
|
||||
return string.IsNullOrWhiteSpace(joined) ? Array.Empty<string>() : joined.Split(';');
|
||||
// --- Порты (Unicode) ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_ListPortsLength();
|
||||
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_ListPorts(StringBuilder buffer, int bufferChars);
|
||||
|
||||
// --- Открытие/закрытие ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_Open(string portName, int baud, int dataBits, int parity, int stopBits,
|
||||
int readTimeoutMs, int writeTimeoutMs);
|
||||
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Serial_Close(int handleId);
|
||||
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Serial_CloseAll(); // NEW
|
||||
|
||||
// --- Запись/чтение (строкой) ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int Serial_WriteStringUTF8(int handleId, string utf8, int appendNewline);
|
||||
|
||||
// byte[]-вариант к той же native-функции (EntryPoint), чтобы избежать маршаллинга StringBuilder
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Serial_ReadLineUTF8")]
|
||||
public static extern int Serial_ReadLineUTF8_bytes(int handleId, byte[] buffer, int bufferSize);
|
||||
|
||||
// --- Ошибка (Unicode) ---
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
public static extern int Serial_GetLastErrorW(StringBuilder buffer, int bufferChars);
|
||||
|
||||
public static string GetLastError()
|
||||
{
|
||||
int need = Serial_GetLastErrorW(null, 0);
|
||||
var sb = new StringBuilder(Math.Max(need, 1));
|
||||
int r = Serial_GetLastErrorW(sb, sb.Capacity);
|
||||
return (r < 0) ? $"err({r})" : sb.ToString();
|
||||
}
|
||||
|
||||
public static string[] ListPorts()
|
||||
{
|
||||
int need = Serial_ListPortsLength();
|
||||
if (need <= 1) return Array.Empty<string>();
|
||||
var sb = new StringBuilder(need);
|
||||
int r = Serial_ListPorts(sb, sb.Capacity);
|
||||
if (r < 0) return Array.Empty<string>();
|
||||
string joined = sb.ToString();
|
||||
return string.IsNullOrWhiteSpace(joined) ? Array.Empty<string>() : joined.Split(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,340 +4,343 @@ using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Основной класс-обертка для работы с COM-портом и периферийной платой.
|
||||
/// Управляет всей коммуникацией с аппаратным обеспечением: прием монет, чтение кнопок, управление подсветкой, отправка сигналов.
|
||||
/// </summary>
|
||||
public class ComWrapper : MonoBehaviour
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
//Настраиваем из инспектора
|
||||
[SerializeField] private bool saveMoneyBetweenSessions;
|
||||
[SerializeField] private bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||||
[SerializeField] private bool keepAlive = true;
|
||||
|
||||
|
||||
public static UnityEvent<int> OnCreditAdded;
|
||||
public static UnityEvent<int> OnCreditRemoved;
|
||||
public static UnityEvent<string> OnInputReceived;
|
||||
|
||||
#region Приватные поля
|
||||
private const string REQUEST_INPUT = "91";
|
||||
private const string DEFAULT_INPUT = "INPUT1111111111111111";
|
||||
private const string LIGHTSIGNAL_PREFIX = "80";
|
||||
|
||||
private static int h; // идентификатор хэндла
|
||||
private static byte[] buf = new byte[1024]; // Байт-буфер куда будут приходить данные с порта
|
||||
|
||||
private static int CreditsCount;
|
||||
|
||||
private bool boardEncrypted = false;
|
||||
// private string inputString = DEFAULT_INPUT;
|
||||
#endregion
|
||||
|
||||
//===========================start: PUBLIC API===========================//
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему работы с COM-портом.
|
||||
/// Открывает соединение, настраивает события и запускает периодические запросы состояния.
|
||||
/// Основной класс-обертка для работы с COM-портом и периферийной платой.
|
||||
/// Управляет всей коммуникацией с аппаратным обеспечением: прием монет, чтение кнопок, управление подсветкой, отправка сигналов.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Обязателен к вызову при старте приложения. Выполняет:
|
||||
/// - Создание UnityEvent'ов для уведомлений
|
||||
/// - Загрузку количества кредитов из PlayerPrefs
|
||||
/// - Открытие COM-порта
|
||||
/// - Запуск периодического опроса пинов (если включена проверка призов)
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
public class ComWrapper : MonoBehaviour
|
||||
{
|
||||
//Создаем ивенты
|
||||
OnCreditAdded = new UnityEvent<int>();
|
||||
OnCreditRemoved = new UnityEvent<int>();
|
||||
OnInputReceived = new UnityEvent<string>();
|
||||
//Настраиваем из инспектора
|
||||
[SerializeField] private bool saveMoneyBetweenSessions;
|
||||
[SerializeField] private bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||||
[SerializeField] private bool keepAlive = true;
|
||||
|
||||
//Начальная настройка
|
||||
if (!saveMoneyBetweenSessions) PlayerPrefs.SetInt("CoinsCount", 0);
|
||||
CreditsCount = PlayerPrefs.GetInt("CoinsCount", 0);
|
||||
|
||||
StartCoroutine(SetupBoard());
|
||||
}
|
||||
public static UnityEvent<int> OnCreditAdded;
|
||||
public static UnityEvent<int> OnCreditRemoved;
|
||||
public static UnityEvent<string> OnInputReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет один кредит в систему и сохраняет обновленное количество.
|
||||
/// Вызывается автоматически событием при получении сигнала "COIN" из COM-порта.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Увеличивает счетчик кредитов и вызывает событие onCoinAdded для уведомления подписчиков.
|
||||
/// Автоматически сохраняет состояние в PlayerPrefs.
|
||||
/// </remarks>
|
||||
public static void AddCredit()
|
||||
{
|
||||
CreditsCount++;
|
||||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||||
OnCreditAdded?.Invoke(CreditsCount);
|
||||
}
|
||||
#region Приватные поля
|
||||
private const string REQUEST_INPUT = "91";
|
||||
private const string DEFAULT_INPUT = "INPUT1111111111111111";
|
||||
private const string LIGHTSIGNAL_PREFIX = "80";
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет произвольную строковую команду на периферийную плату через COM-порт.
|
||||
/// </summary>
|
||||
/// <param name="message">Команда для отправки</param>
|
||||
/// <remarks>
|
||||
/// Используется для отправки пользовательских команд на плату.
|
||||
/// Команда должна быть в формате, ожидаемом прошивкой платы.
|
||||
/// В случае ошибки отправки выводит предупреждение в DebugWindow.
|
||||
/// </remarks>
|
||||
public static void Send(string message)
|
||||
{
|
||||
try
|
||||
private static int h; // идентификатор хэндла
|
||||
private static byte[] buf = new byte[1024]; // Байт-буфер куда будут приходить данные с порта
|
||||
|
||||
private static int CreditsCount;
|
||||
|
||||
private bool boardEncrypted = false;
|
||||
// private string inputString = DEFAULT_INPUT;
|
||||
#endregion
|
||||
|
||||
//===========================start: PUBLIC API===========================//
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему работы с COM-портом.
|
||||
/// Открывает соединение, настраивает события и запускает периодические запросы состояния.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Обязателен к вызову при старте приложения. Выполняет:
|
||||
/// - Создание UnityEvent'ов для уведомлений
|
||||
/// - Загрузку количества кредитов из PlayerPrefs
|
||||
/// - Открытие COM-порта
|
||||
/// - Запуск периодического опроса пинов (если включена проверка призов)
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
{
|
||||
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
|
||||
//GameManager.Logger.Log("Wrote: " + w);
|
||||
//Создаем ивенты
|
||||
OnCreditAdded = new UnityEvent<int>();
|
||||
OnCreditRemoved = new UnityEvent<int>();
|
||||
OnInputReceived = new UnityEvent<string>();
|
||||
|
||||
//Начальная настройка
|
||||
if (!saveMoneyBetweenSessions) PlayerPrefs.SetInt("CoinsCount", 0);
|
||||
CreditsCount = PlayerPrefs.GetInt("CoinsCount", 0);
|
||||
|
||||
StartCoroutine(SetupBoard());
|
||||
}
|
||||
catch
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет один кредит в систему и сохраняет обновленное количество.
|
||||
/// Вызывается автоматически событием при получении сигнала "COIN" из COM-порта.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Увеличивает счетчик кредитов и вызывает событие onCoinAdded для уведомления подписчиков.
|
||||
/// Автоматически сохраняет состояние в PlayerPrefs.
|
||||
/// </remarks>
|
||||
public static void AddCredit()
|
||||
{
|
||||
// DebugWindow.Message("Ошибка отправки строкового сигнала.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет команду управления подсветкой на периферийную плату.
|
||||
/// </summary>
|
||||
/// <param name="lightMode">Режим подсветки из перечисления LightMode</param>
|
||||
/// <remarks>
|
||||
/// Формирует команду вида "80XXX" где XXX - код режима подсветки.
|
||||
/// Для добавления новых режимов необходимо обновить вложенный метод getLightString + добавить режим в перечисление.
|
||||
/// </remarks>
|
||||
public static void Send(LightMode lightMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
string signal = LIGHTSIGNAL_PREFIX + getLightString(lightMode);
|
||||
Send(signal);
|
||||
|
||||
//ВОТЭТОТ МЕТОД ПОД СВОИ ПОДСВЕТКИ НАДО ПОДГОНЯТЬ
|
||||
string getLightString(LightMode mode) =>
|
||||
|
||||
mode switch
|
||||
{
|
||||
LightMode.Idle1 => "500",
|
||||
LightMode.Idle2 => "507",
|
||||
LightMode.Orange => "501",
|
||||
LightMode.Blue => "502",
|
||||
LightMode.Win => "503",
|
||||
LightMode.Lose => "504",
|
||||
LightMode.Prize => "505",
|
||||
LightMode.Tama => "513",
|
||||
LightMode.Dog => "514",
|
||||
LightMode.Sam => "515",
|
||||
LightMode.Mistress => "516",
|
||||
|
||||
_ => "500"
|
||||
};
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// DebugWindow.Message("Ошибка отправки светового сигнала.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет списание платы за запуск игры.
|
||||
/// Проверяет наличие доступных кредитов или режим бесплатной игры.
|
||||
/// При успешном списании увеличивает счетчики запусков.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true - если списание прошло успешно (бесплатный режим или есть кредиты),
|
||||
/// false - если кредитов недостаточно и платный режим
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// В случае успеха:
|
||||
/// - Увеличивает общие счетчики запусков (key_starts и key_startsNul)
|
||||
/// - При платном режиме уменьшает количество кредитов на 1
|
||||
/// - Вызывает событие onCoinRemoved при списании кредита
|
||||
/// </remarks>
|
||||
public static bool Pay(int amount = 1)
|
||||
{
|
||||
// блок проверки оплаты
|
||||
// TODO: Сделать свою систему для проверки оплаты и бесплатной игры
|
||||
// if (SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл")
|
||||
// {
|
||||
// //Счетчики пробега
|
||||
// PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||||
// PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
|
||||
if (CreditsCount >= amount)
|
||||
{
|
||||
CreditsCount -= amount;
|
||||
CreditsCount++;
|
||||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||||
|
||||
//счетчики пробега
|
||||
PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||||
PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||||
|
||||
OnCreditRemoved?.Invoke(CreditsCount);
|
||||
return true;
|
||||
OnCreditAdded?.Invoke(CreditsCount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//===========================end: PUBLIC API===========================//
|
||||
|
||||
private void Update() => pollPort();
|
||||
private void OnDisable() => ClosePort();
|
||||
private void OnApplicationQuit() => ClosePort();
|
||||
|
||||
private IEnumerator KeepPortAliveProcess()
|
||||
{
|
||||
OnInputReceived?.AddListener(InputReceivedCather);
|
||||
GameManager.Logger.Log("Подключаю проверку порта.");
|
||||
|
||||
while (true)
|
||||
/// <summary>
|
||||
/// Отправляет произвольную строковую команду на периферийную плату через COM-порт.
|
||||
/// </summary>
|
||||
/// <param name="message">Команда для отправки</param>
|
||||
/// <remarks>
|
||||
/// Используется для отправки пользовательских команд на плату.
|
||||
/// Команда должна быть в формате, ожидаемом прошивкой платы.
|
||||
/// В случае ошибки отправки выводит предупреждение в DebugWindow.
|
||||
/// </remarks>
|
||||
public static void Send(string message)
|
||||
{
|
||||
requestInputs();
|
||||
yield return new WaitForSeconds(5f);
|
||||
|
||||
//нет инпута
|
||||
if (GetInputToggle() == 0)
|
||||
try
|
||||
{
|
||||
GameManager.Logger.Log("Плата отвалилась. Запускаю переподключение.");
|
||||
initPort();
|
||||
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
|
||||
//GameManager.Logger.Log("Wrote: " + w);
|
||||
|
||||
}
|
||||
//есть инпут
|
||||
catch
|
||||
{
|
||||
// DebugWindow.Message("Ошибка отправки строкового сигнала.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет команду управления подсветкой на периферийную плату.
|
||||
/// </summary>
|
||||
/// <param name="lightMode">Режим подсветки из перечисления LightMode</param>
|
||||
/// <remarks>
|
||||
/// Формирует команду вида "80XXX" где XXX - код режима подсветки.
|
||||
/// Для добавления новых режимов необходимо обновить вложенный метод getLightString + добавить режим в перечисление.
|
||||
/// </remarks>
|
||||
public static void Send(LightMode lightMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
string signal = LIGHTSIGNAL_PREFIX + getLightString(lightMode);
|
||||
Send(signal);
|
||||
|
||||
//ВОТЭТОТ МЕТОД ПОД СВОИ ПОДСВЕТКИ НАДО ПОДГОНЯТЬ
|
||||
string getLightString(LightMode mode) =>
|
||||
|
||||
mode switch
|
||||
{
|
||||
LightMode.Idle1 => "500",
|
||||
LightMode.Idle2 => "507",
|
||||
LightMode.Orange => "501",
|
||||
LightMode.Blue => "502",
|
||||
LightMode.Win => "503",
|
||||
LightMode.Lose => "504",
|
||||
LightMode.Prize => "505",
|
||||
LightMode.Tama => "513",
|
||||
LightMode.Dog => "514",
|
||||
LightMode.Sam => "515",
|
||||
LightMode.Mistress => "516",
|
||||
|
||||
_ => "500"
|
||||
};
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// DebugWindow.Message("Ошибка отправки светового сигнала.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет списание платы за запуск игры.
|
||||
/// Проверяет наличие доступных кредитов или режим бесплатной игры.
|
||||
/// При успешном списании увеличивает счетчики запусков.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true - если списание прошло успешно (бесплатный режим или есть кредиты),
|
||||
/// false - если кредитов недостаточно и платный режим
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// В случае успеха:
|
||||
/// - Увеличивает общие счетчики запусков (key_starts и key_startsNul)
|
||||
/// - При платном режиме уменьшает количество кредитов на 1
|
||||
/// - Вызывает событие onCoinRemoved при списании кредита
|
||||
/// </remarks>
|
||||
public static bool Pay(int amount = 1)
|
||||
{
|
||||
// блок проверки оплаты
|
||||
// TODO: Сделать свою систему для проверки оплаты и бесплатной игры
|
||||
// if (SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл")
|
||||
// {
|
||||
// //Счетчики пробега
|
||||
// PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||||
// PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
|
||||
if (CreditsCount >= amount)
|
||||
{
|
||||
CreditsCount -= amount;
|
||||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||||
|
||||
//счетчики пробега
|
||||
PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||||
PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||||
|
||||
OnCreditRemoved?.Invoke(CreditsCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//===========================end: PUBLIC API===========================//
|
||||
|
||||
private void Update() => pollPort();
|
||||
private void OnDisable() => ClosePort();
|
||||
private void OnApplicationQuit() => ClosePort();
|
||||
|
||||
private IEnumerator KeepPortAliveProcess()
|
||||
{
|
||||
OnInputReceived?.AddListener(InputReceivedCather);
|
||||
GameManager.Logger.Log("Подключаю проверку порта.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
requestInputs();
|
||||
yield return new WaitForSeconds(5f);
|
||||
|
||||
//нет инпута
|
||||
if (GetInputToggle() == 0)
|
||||
{
|
||||
GameManager.Logger.Log("Плата отвалилась. Запускаю переподключение.");
|
||||
initPort();
|
||||
|
||||
}
|
||||
//есть инпут
|
||||
else
|
||||
{
|
||||
ToggleInputReceived(0);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToggleInputReceived(int value) => PlayerPrefs.SetInt("inputToggle", value);
|
||||
int GetInputToggle() => PlayerPrefs.GetInt("inputToggle", 0);
|
||||
|
||||
private static void InputReceivedCather(string input)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
//пришел инпут
|
||||
ToggleInputReceived(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Приватные методы
|
||||
//Ну тут по дебаг логам все должно быть понятно
|
||||
//Если интересно, то h - это хэндл. Что то на плюсовом, типа ID соединения или чтото. Если 0 то нету соед. если меньше то ошибка, больше 0 - норм.
|
||||
private void initPort()
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper", "Старт программы");
|
||||
ComNativeWrapper.Serial_CloseAll();
|
||||
var ports = ComNativeWrapper.ListPorts();
|
||||
GameManager.Logger.Log("ComWrapper", "Ports: " + string.Join(", ", ports));
|
||||
if (ports.Length == 0) { GameManager.Logger.Log("Нет портов, завершаю"); return; }
|
||||
|
||||
string port = ports[^1];
|
||||
GameManager.Logger.Log("ComWrapper", "Открываю порт: " + port);
|
||||
|
||||
|
||||
//вот эти настройки лучше не ковырять, но если очень хочется можно поменять readTimeoutMs и writeTimeoutMs
|
||||
//Если read поставить слишком маленьким оно проебует кредиты когда они идут большой пачкой например при оплате через вендисту.
|
||||
//Там в порт спамит "COIN" в больших количествах и процентов 10 из этих строк не успевают прочитаться при таймауте 1мс, соотв. монеты проебуются.
|
||||
//Слишком большой таймаут будет тормозить игру потому что оно все выполняется синхронно и каждый кадр забирает столько мс сколько в переменной указано.
|
||||
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 5, 10);
|
||||
|
||||
|
||||
GameManager.Logger.Log("ComWrapper", $"handle: {h}");
|
||||
if (h <= 0)
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper", "Open failed: " + ComNativeWrapper.GetLastError());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleInputReceived(0);
|
||||
GameManager.Logger.Log("ComWrapper", $"Открыл порт: {port}");
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(5);
|
||||
}
|
||||
|
||||
private static void ClosePort()
|
||||
{
|
||||
Debug.Log("Закрываю все порты");
|
||||
if (h > 0)
|
||||
{
|
||||
ComNativeWrapper.Serial_Close(h);
|
||||
h = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Тут чтение, не трогаем(опционально)
|
||||
private static string ReadBufferLine()
|
||||
{
|
||||
|
||||
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
|
||||
if (read > 0)
|
||||
{
|
||||
string s = Encoding.UTF8.GetString(buf, 0, read);
|
||||
//GameManager.Logger.Log($"ReadLine len={read} data=[{s}]");
|
||||
return s;
|
||||
}
|
||||
else if (read < 0)
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper", $"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
//просим прислать состояние цифровых входов. Приходит что то вроде "INPUT111111101"
|
||||
private void requestInputs() => Send(REQUEST_INPUT);
|
||||
|
||||
private void pollPort()
|
||||
{
|
||||
//Тут все просто, читаем что нам приходит с порта и сравниваем.
|
||||
string comInput = ReadBufferLine();
|
||||
if (comInput != "")
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper", "COM: " + comInput);
|
||||
|
||||
if (comInput == "COIN") AddCredit();
|
||||
if (comInput.StartsWith("INPUT")) OnInputReceived?.Invoke(comInput.Substring(5)); //инвокаю сразу без префикса "INPUT"
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator SetupBoard()
|
||||
{
|
||||
initPort();
|
||||
requestInputs();
|
||||
//проверяем вдруг не зашифрованная версия
|
||||
|
||||
|
||||
|
||||
|
||||
//Каждые 5 сек запрашиваем инпуты у платы. Надо потому что при заходе в игру с пинов еще не пришла инфа
|
||||
if (keepAlive) StartCoroutine(KeepPortAliveProcess());
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
private static void ToggleInputReceived(int value) => PlayerPrefs.SetInt("inputToggle", value);
|
||||
int GetInputToggle() => PlayerPrefs.GetInt("inputToggle", 0);
|
||||
|
||||
private static void InputReceivedCather(string input)
|
||||
public enum LightMode
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
//пришел инпут
|
||||
ToggleInputReceived(1);
|
||||
}
|
||||
|
||||
Idle1,
|
||||
Idle2,
|
||||
Orange,
|
||||
Blue,
|
||||
Win,
|
||||
Lose,
|
||||
Prize,
|
||||
Tama,
|
||||
Dog,
|
||||
Sam,
|
||||
Mistress
|
||||
}
|
||||
|
||||
#region Приватные методы
|
||||
//Ну тут по дебаг логам все должно быть понятно
|
||||
//Если интересно, то h - это хэндл. Что то на плюсовом, типа ID соединения или чтото. Если 0 то нету соед. если меньше то ошибка, больше 0 - норм.
|
||||
private void initPort()
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper", "Старт программы");
|
||||
ComNativeWrapper.Serial_CloseAll();
|
||||
var ports = ComNativeWrapper.ListPorts();
|
||||
GameManager.Logger.Log("ComWrapper","Ports: " + string.Join(", ", ports));
|
||||
if (ports.Length == 0) { GameManager.Logger.Log("Нет портов, завершаю"); return; }
|
||||
|
||||
string port = ports[^1];
|
||||
GameManager.Logger.Log("ComWrapper","Открываю порт: " + port);
|
||||
|
||||
|
||||
//вот эти настройки лучше не ковырять, но если очень хочется можно поменять readTimeoutMs и writeTimeoutMs
|
||||
//Если read поставить слишком маленьким оно проебует кредиты когда они идут большой пачкой например при оплате через вендисту.
|
||||
//Там в порт спамит "COIN" в больших количествах и процентов 10 из этих строк не успевают прочитаться при таймауте 1мс, соотв. монеты проебуются.
|
||||
//Слишком большой таймаут будет тормозить игру потому что оно все выполняется синхронно и каждый кадр забирает столько мс сколько в переменной указано.
|
||||
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 5, 10);
|
||||
|
||||
|
||||
GameManager.Logger.Log("ComWrapper",$"handle: {h}");
|
||||
if (h <= 0)
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper","Open failed: " + ComNativeWrapper.GetLastError());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper",$"Открыл порт: {port}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClosePort()
|
||||
{
|
||||
Debug.Log("Закрываю все порты");
|
||||
if (h > 0)
|
||||
{
|
||||
ComNativeWrapper.Serial_Close(h);
|
||||
h = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Тут чтение, не трогаем(опционально)
|
||||
private static string ReadBufferLine()
|
||||
{
|
||||
|
||||
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
|
||||
if (read > 0)
|
||||
{
|
||||
string s = Encoding.UTF8.GetString(buf, 0, read);
|
||||
//GameManager.Logger.Log($"ReadLine len={read} data=[{s}]");
|
||||
return s;
|
||||
}
|
||||
else if (read < 0)
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper",$"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
//просим прислать состояние цифровых входов. Приходит что то вроде "INPUT111111101"
|
||||
private void requestInputs() => Send(REQUEST_INPUT);
|
||||
|
||||
private void pollPort()
|
||||
{
|
||||
//Тут все просто, читаем что нам приходит с порта и сравниваем.
|
||||
string comInput = ReadBufferLine();
|
||||
if (comInput != "")
|
||||
{
|
||||
GameManager.Logger.Log("ComWrapper", "COM: " + comInput);
|
||||
|
||||
if (comInput == "COIN") AddCredit();
|
||||
if (comInput.StartsWith("INPUT")) OnInputReceived?.Invoke(comInput.Substring(5)); //инвокаю сразу без префикса "INPUT"
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator SetupBoard()
|
||||
{
|
||||
initPort();
|
||||
requestInputs();
|
||||
//проверяем вдруг не зашифрованная версия
|
||||
|
||||
|
||||
|
||||
|
||||
//Каждые 5 сек запрашиваем инпуты у платы. Надо потому что при заходе в игру с пинов еще не пришла инфа
|
||||
if (keepAlive) StartCoroutine(KeepPortAliveProcess());
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
public enum LightMode
|
||||
{
|
||||
Idle1,
|
||||
Idle2,
|
||||
Orange,
|
||||
Blue,
|
||||
Win,
|
||||
Lose,
|
||||
Prize,
|
||||
Tama,
|
||||
Dog,
|
||||
Sam,
|
||||
Mistress
|
||||
}
|
||||
@ -3,127 +3,130 @@ using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Система управления выдачей призов и ведения статистики игры.
|
||||
/// Отвечает за процесс выдачи призов, подсчет запусков и наград.
|
||||
/// </summary>
|
||||
public class PrizeSystem : MonoBehaviour
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
private static PrizeSystem Instance { get; set; }
|
||||
|
||||
private static Coroutine prizeCoroutine;
|
||||
private static bool _prizing = false;
|
||||
|
||||
public static readonly string key_starts = "STARTS";
|
||||
public static readonly string key_startsNul = "STARTSNUL";
|
||||
public static readonly string key_prizesgiven = "PRIZESGIVEN";
|
||||
public static readonly string key_prizesgivenNul = "PRIZESGIVENNUL";
|
||||
|
||||
[SerializeField] AudioClip prizeErrorSound;
|
||||
|
||||
//===========================start: PUBLIC API===========================//
|
||||
/// <summary>
|
||||
/// Инициализирует систему призов и счетчики статистики.
|
||||
/// Должен быть вызван один раз при запуске приложения.
|
||||
/// Система управления выдачей призов и ведения статистики игры.
|
||||
/// Отвечает за процесс выдачи призов, подсчет запусков и наград.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Выполняет:
|
||||
/// - Установку статического экземпляра (singleton)
|
||||
/// - Инициализацию счетчиков в PlayerPrefs если они отсутствуют
|
||||
/// - Подготовку системы к работе
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
public class PrizeSystem : MonoBehaviour
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
private static PrizeSystem Instance { get; set; }
|
||||
|
||||
private static Coroutine prizeCoroutine;
|
||||
private static bool _prizing = false;
|
||||
|
||||
public static readonly string key_starts = "STARTS";
|
||||
public static readonly string key_startsNul = "STARTSNUL";
|
||||
public static readonly string key_prizesgiven = "PRIZESGIVEN";
|
||||
public static readonly string key_prizesgivenNul = "PRIZESGIVENNUL";
|
||||
|
||||
[SerializeField] AudioClip prizeErrorSound;
|
||||
|
||||
//===========================start: PUBLIC API===========================//
|
||||
/// <summary>
|
||||
/// Инициализирует систему призов и счетчики статистики.
|
||||
/// Должен быть вызван один раз при запуске приложения.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Выполняет:
|
||||
/// - Установку статического экземпляра (singleton)
|
||||
/// - Инициализацию счетчиков в PlayerPrefs если они отсутствуют
|
||||
/// - Подготовку системы к работе
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
InitCounters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запускает процесс выдачи физического приза игроку.
|
||||
/// Проверяет настройки и текущее состояние перед началом выдачи.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Условия для выдачи приза:
|
||||
/// - В настройках включена выдача призов при игре с ботом
|
||||
/// - В данный момент не выполняется другая выдача приза
|
||||
///
|
||||
/// Процесс выдачи:
|
||||
/// - Включает мотор хопера
|
||||
/// - Ожидает подтверждения выдачи от датчиков (10 сек таймаут)
|
||||
/// - Выключает мотор по завершению
|
||||
/// - Обновляет счетчики выданных призов
|
||||
/// - Воспроизводит звук ошибки при неудаче
|
||||
/// </remarks>
|
||||
public static void GivePrize()
|
||||
{
|
||||
// bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать";
|
||||
//
|
||||
// if (!settingEnabled) { DebugWindow.Message("Выдача приза отключена в настройках"); return; }
|
||||
if (_prizing)
|
||||
{
|
||||
GameManager.Logger.Log("PrizeSystem", "Уже идет выдача приза. Не спамь кнопку.");
|
||||
return;
|
||||
}
|
||||
//
|
||||
// DebugWindow.Message("Выдаю приз");
|
||||
if (prizeCoroutine != null) Instance.StopCoroutine(prizeCoroutine);
|
||||
prizeCoroutine = Instance.StartCoroutine(Instance.Prize());
|
||||
}
|
||||
//===========================end: PUBLIC API===========================//
|
||||
|
||||
#region Приватные методы
|
||||
private IEnumerator Prize()
|
||||
{
|
||||
_prizing = true;
|
||||
// DebugWindow.Message("Начинаю выдачу");
|
||||
|
||||
const float timeout = 10; //сек
|
||||
ComWrapper.Send("301");//включаем мотор
|
||||
float startTime = Time.time;
|
||||
bool success = false;
|
||||
|
||||
PlayerPrefs.SetInt("PrizeGivenBuffer", 0);//На всякий случай подготовка буфера
|
||||
//проверить где он пишется
|
||||
|
||||
while (Time.time - startTime < timeout)
|
||||
{
|
||||
if (PlayerPrefs.GetInt("PrizeGivenBuffer", 0) == 1)
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
success = true;
|
||||
IncreasePrizeCounters();
|
||||
break;
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
yield return null;
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
InitCounters();
|
||||
}
|
||||
|
||||
//Выключаем мотор
|
||||
ComWrapper.Send("300");
|
||||
// DebugWindow.Message("Закончил. Успех: " + success);
|
||||
_prizing = false;
|
||||
/// <summary>
|
||||
/// Запускает процесс выдачи физического приза игроку.
|
||||
/// Проверяет настройки и текущее состояние перед началом выдачи.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Условия для выдачи приза:
|
||||
/// - В настройках включена выдача призов при игре с ботом
|
||||
/// - В данный момент не выполняется другая выдача приза
|
||||
///
|
||||
/// Процесс выдачи:
|
||||
/// - Включает мотор хопера
|
||||
/// - Ожидает подтверждения выдачи от датчиков (10 сек таймаут)
|
||||
/// - Выключает мотор по завершению
|
||||
/// - Обновляет счетчики выданных призов
|
||||
/// - Воспроизводит звук ошибки при неудаче
|
||||
/// </remarks>
|
||||
public static void GivePrize()
|
||||
{
|
||||
// bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать";
|
||||
//
|
||||
// if (!settingEnabled) { DebugWindow.Message("Выдача приза отключена в настройках"); return; }
|
||||
if (_prizing)
|
||||
{
|
||||
GameManager.Logger.Log("PrizeSystem", "Уже идет выдача приза. Не спамь кнопку.");
|
||||
return;
|
||||
}
|
||||
//
|
||||
// DebugWindow.Message("Выдаю приз");
|
||||
if (prizeCoroutine != null) Instance.StopCoroutine(prizeCoroutine);
|
||||
prizeCoroutine = Instance.StartCoroutine(Instance.Prize());
|
||||
}
|
||||
//===========================end: PUBLIC API===========================//
|
||||
|
||||
if (!success) AudioManager.Instance.PlaySfx(prizeErrorSound);
|
||||
}
|
||||
#region Приватные методы
|
||||
private IEnumerator Prize()
|
||||
{
|
||||
_prizing = true;
|
||||
// DebugWindow.Message("Начинаю выдачу");
|
||||
|
||||
private static void IncreasePrizeCounters()
|
||||
{
|
||||
PlayerPrefs.SetInt(key_prizesgiven, PlayerPrefs.GetInt(key_prizesgiven) + 1);
|
||||
PlayerPrefs.SetInt(key_prizesgivenNul, PlayerPrefs.GetInt(key_prizesgivenNul) + 1);
|
||||
}
|
||||
const float timeout = 10; //сек
|
||||
ComWrapper.Send("301");//включаем мотор
|
||||
float startTime = Time.time;
|
||||
bool success = false;
|
||||
|
||||
private static void InitCounters()
|
||||
{
|
||||
if (!PlayerPrefs.HasKey(key_starts)) PlayerPrefs.SetInt(key_starts, 0);
|
||||
if (!PlayerPrefs.HasKey(key_startsNul)) PlayerPrefs.SetInt(key_startsNul, 0);
|
||||
if (!PlayerPrefs.HasKey(key_prizesgiven)) PlayerPrefs.SetInt(key_prizesgiven, 0);
|
||||
if (!PlayerPrefs.HasKey(key_prizesgivenNul)) PlayerPrefs.SetInt(key_prizesgivenNul, 0);
|
||||
PlayerPrefs.SetInt("PrizeGivenBuffer", 0);//На всякий случай подготовка буфера
|
||||
//проверить где он пишется
|
||||
|
||||
while (Time.time - startTime < timeout)
|
||||
{
|
||||
if (PlayerPrefs.GetInt("PrizeGivenBuffer", 0) == 1)
|
||||
{
|
||||
success = true;
|
||||
IncreasePrizeCounters();
|
||||
break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
|
||||
//Выключаем мотор
|
||||
ComWrapper.Send("300");
|
||||
// DebugWindow.Message("Закончил. Успех: " + success);
|
||||
_prizing = false;
|
||||
|
||||
if (!success) AudioManager.Instance.PlaySfx(prizeErrorSound);
|
||||
}
|
||||
|
||||
private static void IncreasePrizeCounters()
|
||||
{
|
||||
PlayerPrefs.SetInt(key_prizesgiven, PlayerPrefs.GetInt(key_prizesgiven) + 1);
|
||||
PlayerPrefs.SetInt(key_prizesgivenNul, PlayerPrefs.GetInt(key_prizesgivenNul) + 1);
|
||||
}
|
||||
|
||||
private static void InitCounters()
|
||||
{
|
||||
if (!PlayerPrefs.HasKey(key_starts)) PlayerPrefs.SetInt(key_starts, 0);
|
||||
if (!PlayerPrefs.HasKey(key_startsNul)) PlayerPrefs.SetInt(key_startsNul, 0);
|
||||
if (!PlayerPrefs.HasKey(key_prizesgiven)) PlayerPrefs.SetInt(key_prizesgiven, 0);
|
||||
if (!PlayerPrefs.HasKey(key_prizesgivenNul)) PlayerPrefs.SetInt(key_prizesgivenNul, 0);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,92 +1,95 @@
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Статический фасад для унифицированного доступа к системе ввода.
|
||||
/// Предоставляет глобальный интерфейс для работы с управлением независимо от выбранного источника.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Этот класс является точкой входа для всей игровой логики, работающей с вводом.
|
||||
/// Автоматически инициализирует выбранный источник управления (клавиатура или COM-порт)
|
||||
/// и предоставляет статические методы для удобного доступа из любого места кода.
|
||||
///
|
||||
/// Использование:
|
||||
/// 1. Настройте тип ввода в инспекторе
|
||||
/// 2. Вызовите Initialize() при старте игры
|
||||
/// 3. Используйте статические методы для проверки ввода
|
||||
/// </remarks>
|
||||
public class In : MonoBehaviour
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
[Header("Выбор источника управления")]
|
||||
[SerializeField] private InputSourceType inputType;
|
||||
|
||||
private static InputSource source;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему ввода на основе выбранного типа.
|
||||
/// Должен быть вызван один раз при запуске приложения.
|
||||
/// Статический фасад для унифицированного доступа к системе ввода.
|
||||
/// Предоставляет глобальный интерфейс для работы с управлением независимо от выбранного источника.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Создает соответствующий экземпляр InputSource:
|
||||
/// - KeyboardInput для тестирования на клавиатуре
|
||||
/// - COMinput для работы с периферийной платой
|
||||
/// Этот класс является точкой входа для всей игровой логики, работающей с вводом.
|
||||
/// Автоматически инициализирует выбранный источник управления (клавиатура или COM-порт)
|
||||
/// и предоставляет статические методы для удобного доступа из любого места кода.
|
||||
///
|
||||
/// Использование:
|
||||
/// 1. Настройте тип ввода в инспекторе
|
||||
/// 2. Вызовите Initialize() при старте игры
|
||||
/// 3. Используйте статические методы для проверки ввода
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
public class In : MonoBehaviour
|
||||
{
|
||||
source = inputType switch
|
||||
[Header("Выбор источника управления")]
|
||||
[SerializeField] private InputSourceType inputType;
|
||||
|
||||
private static InputSource source;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему ввода на основе выбранного типа.
|
||||
/// Должен быть вызван один раз при запуске приложения.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Создает соответствующий экземпляр InputSource:
|
||||
/// - KeyboardInput для тестирования на клавиатуре
|
||||
/// - COMinput для работы с периферийной платой
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
{
|
||||
InputSourceType.Keyboard => new KeyboardInput(),
|
||||
InputSourceType.COM => new COMInput(),
|
||||
// _ => throw new System.NotImplementedException()
|
||||
_ => null
|
||||
};
|
||||
source = inputType switch
|
||||
{
|
||||
InputSourceType.Keyboard => new KeyboardInput(),
|
||||
InputSourceType.COM => new COMInput(),
|
||||
// _ => throw new System.NotImplementedException()
|
||||
_ => null
|
||||
};
|
||||
|
||||
|
||||
var success = source?.Initialize() ?? false;
|
||||
if (!success) GameManager.Logger.LogWarning("In.cs","Не удалось проинициалзировать источник управления.");
|
||||
|
||||
var success = source?.Initialize() ?? false;
|
||||
if (!success) GameManager.Logger.LogWarning("In.cs", "Не удалось проинициалзировать источник управления.");
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// СТАТИЧЕСКИЕ МЕТОДЫ ДЛЯ РАБОТЫ С ФИЗИЧЕСКИМИ ПИНАМИ
|
||||
// ================================================================================
|
||||
|
||||
/// <summary>Проверяет состояние конкретной кнопки по её физическому пину</summary>
|
||||
public static bool GetButton(ButtonPinMapping button) => source.GetButton(button);
|
||||
|
||||
/// <summary>Проверяет нажатие кнопки по пину в текущем кадре</summary>
|
||||
public static bool GetButtonDown(ButtonPinMapping button) => source.GetButtonDown(button);
|
||||
|
||||
/// <summary>Проверяет отпускание кнопки по пину в текущем кадре</summary>
|
||||
public static bool GetButtonUp(ButtonPinMapping button) => source.GetButtonUp(button);
|
||||
|
||||
/// <summary>Проверяет состояние кнопки по числовому индексу пина</summary>
|
||||
public static bool GetButton(int pinIndex) => source.GetButton(pinIndex);
|
||||
|
||||
/// <summary>Проверяет нажатие кнопки по индексу пина в текущем кадре</summary>
|
||||
public static bool GetButtonDown(int pinIndex) => source.GetButtonDown(pinIndex);
|
||||
|
||||
/// <summary>Проверяет отпускание кнопки по индексу пина в текущем кадре</summary>
|
||||
public static bool GetButtonUp(int pinIndex) => source.GetButtonUp(pinIndex);
|
||||
|
||||
// ================================================================================
|
||||
// СТАТИЧЕСКИЕ МЕТОДЫ ДЛЯ РАБОТЫ С АБСТРАКТНЫМИ ДЕЙСТВИЯМИ ИГРОКОВ
|
||||
// ================================================================================
|
||||
|
||||
/// <summary>Проверяет состояние действия для любого из игроков</summary>
|
||||
public static bool GetButtonAnyPlayer(PlayerAction button) => source.GetButtonAnyPlayer(button);
|
||||
|
||||
/// <summary>Проверяет начало действия любым игроком в текущем кадре</summary>
|
||||
public static bool GetButtonDownAnyPlayer(PlayerAction button) => source.GetButtonDownAnyPlayer(button);
|
||||
|
||||
/// <summary>Проверяет завершение действия любым игроком в текущем кадре</summary>
|
||||
public static bool GetButtonUpAnyPlayer(PlayerAction button) => source.GetButtonUpAnyPlayer(button);
|
||||
|
||||
/// <summary>Проверяет состояние действия для текущего активного игрока</summary>
|
||||
public static bool GetButtonCurrentPlayer(PlayerAction button) => source.GetButtonCurrentPlayer(button);
|
||||
|
||||
/// <summary>Проверяет начало действия текущим игроком в текущем кадре</summary>
|
||||
public static bool GetButtonDownCurrentPlayer(PlayerAction button) => source.GetButtonDownCurrentPlayer(button);
|
||||
|
||||
/// <summary>Проверяет завершение действия текущим игроком в текущем кадре</summary>
|
||||
public static bool GetButtonUpCurrentPlayer(PlayerAction button) => source.GetButtonUpCurrentPlayer(button);
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// СТАТИЧЕСКИЕ МЕТОДЫ ДЛЯ РАБОТЫ С ФИЗИЧЕСКИМИ ПИНАМИ
|
||||
// ================================================================================
|
||||
|
||||
/// <summary>Проверяет состояние конкретной кнопки по её физическому пину</summary>
|
||||
public static bool GetButton(ButtonPinMapping button) => source.GetButton(button);
|
||||
|
||||
/// <summary>Проверяет нажатие кнопки по пину в текущем кадре</summary>
|
||||
public static bool GetButtonDown(ButtonPinMapping button) => source.GetButtonDown(button);
|
||||
|
||||
/// <summary>Проверяет отпускание кнопки по пину в текущем кадре</summary>
|
||||
public static bool GetButtonUp(ButtonPinMapping button) => source.GetButtonUp(button);
|
||||
|
||||
/// <summary>Проверяет состояние кнопки по числовому индексу пина</summary>
|
||||
public static bool GetButton(int pinIndex) => source.GetButton(pinIndex);
|
||||
|
||||
/// <summary>Проверяет нажатие кнопки по индексу пина в текущем кадре</summary>
|
||||
public static bool GetButtonDown(int pinIndex) => source.GetButtonDown(pinIndex);
|
||||
|
||||
/// <summary>Проверяет отпускание кнопки по индексу пина в текущем кадре</summary>
|
||||
public static bool GetButtonUp(int pinIndex) => source.GetButtonUp(pinIndex);
|
||||
|
||||
// ================================================================================
|
||||
// СТАТИЧЕСКИЕ МЕТОДЫ ДЛЯ РАБОТЫ С АБСТРАКТНЫМИ ДЕЙСТВИЯМИ ИГРОКОВ
|
||||
// ================================================================================
|
||||
|
||||
/// <summary>Проверяет состояние действия для любого из игроков</summary>
|
||||
public static bool GetButtonAnyPlayer(PlayerAction button) => source.GetButtonAnyPlayer(button);
|
||||
|
||||
/// <summary>Проверяет начало действия любым игроком в текущем кадре</summary>
|
||||
public static bool GetButtonDownAnyPlayer(PlayerAction button) => source.GetButtonDownAnyPlayer(button);
|
||||
|
||||
/// <summary>Проверяет завершение действия любым игроком в текущем кадре</summary>
|
||||
public static bool GetButtonUpAnyPlayer(PlayerAction button) => source.GetButtonUpAnyPlayer(button);
|
||||
|
||||
/// <summary>Проверяет состояние действия для текущего активного игрока</summary>
|
||||
public static bool GetButtonCurrentPlayer(PlayerAction button) => source.GetButtonCurrentPlayer(button);
|
||||
|
||||
/// <summary>Проверяет начало действия текущим игроком в текущем кадре</summary>
|
||||
public static bool GetButtonDownCurrentPlayer(PlayerAction button) => source.GetButtonDownCurrentPlayer(button);
|
||||
|
||||
/// <summary>Проверяет завершение действия текущим игроком в текущем кадре</summary>
|
||||
public static bool GetButtonUpCurrentPlayer(PlayerAction button) => source.GetButtonUpCurrentPlayer(button);
|
||||
}
|
||||
@ -1,117 +1,120 @@
|
||||
/// <summary>
|
||||
/// Абстрактный базовый класс для всех источников ввода в системе.
|
||||
/// Определяет унифицированный интерфейс для работы с аппаратными кнопками и виртуальными действиями игроков.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Наследники этого класса реализуют конкретные способы ввода:
|
||||
/// - COMinput: работа с периферийной платой через COM-порт
|
||||
/// - KeyboardInput: эмуляция ввода с клавиатуры для тестирования
|
||||
///
|
||||
/// Класс предоставляет три уровня абстракции:
|
||||
/// 1. Низкоуровневый доступ к физическим пинам
|
||||
/// 2. Доступ к абстрактным игровым действиям
|
||||
/// 3. Мультиплеерные методы для работы с несколькими игроками
|
||||
/// </remarks>
|
||||
public abstract class InputSource
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Инициализирует источник ввода. Должен быть вызван перед использованием других методов.
|
||||
/// Абстрактный базовый класс для всех источников ввода в системе.
|
||||
/// Определяет унифицированный интерфейс для работы с аппаратными кнопками и виртуальными действиями игроков.
|
||||
/// </summary>
|
||||
/// <returns>true если инициализация прошла успешно, false в случае ошибки</returns>
|
||||
public abstract bool Initialize();
|
||||
/// <remarks>
|
||||
/// Наследники этого класса реализуют конкретные способы ввода:
|
||||
/// - COMinput: работа с периферийной платой через COM-порт
|
||||
/// - KeyboardInput: эмуляция ввода с клавиатуры для тестирования
|
||||
///
|
||||
/// Класс предоставляет три уровня абстракции:
|
||||
/// 1. Низкоуровневый доступ к физическим пинам
|
||||
/// 2. Доступ к абстрактным игровым действиям
|
||||
/// 3. Мультиплеерные методы для работы с несколькими игроками
|
||||
/// </remarks>
|
||||
public abstract class InputSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Инициализирует источник ввода. Должен быть вызван перед использованием других методов.
|
||||
/// </summary>
|
||||
/// <returns>true если инициализация прошла успешно, false в случае ошибки</returns>
|
||||
public abstract bool Initialize();
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Получает текущее состояние конкретной кнопки по её физическому пину.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||||
public abstract bool GetButton(ButtonPinMapping button);
|
||||
/// <summary>
|
||||
/// Получает текущее состояние конкретной кнопки по её физическому пину.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||||
public abstract bool GetButton(ButtonPinMapping button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>true только в первом кадре после нажатия кнопки</returns>
|
||||
public abstract bool GetButtonDown(ButtonPinMapping button);
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>true только в первом кадре после нажатия кнопки</returns>
|
||||
public abstract bool GetButtonDown(ButtonPinMapping button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>true только в первом кадре после отпускания кнопки</returns>
|
||||
public abstract bool GetButtonUp(ButtonPinMapping button);
|
||||
/// <summary>
|
||||
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
|
||||
/// </summary>
|
||||
/// <param name="button">Пин кнопки на периферийной плате</param>
|
||||
/// <returns>true только в первом кадре после отпускания кнопки</returns>
|
||||
public abstract bool GetButtonUp(ButtonPinMapping button);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние кнопки по числовому индексу пина.
|
||||
/// Перегрузка для работы с системами, где количество пинов превышает размер перечисления.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-based)</param>
|
||||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||||
public abstract bool GetButton(int pinIndex);
|
||||
/// <summary>
|
||||
/// Получает состояние кнопки по числовому индексу пина.
|
||||
/// Перегрузка для работы с системами, где количество пинов превышает размер перечисления.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-based)</param>
|
||||
/// <returns>true если кнопка нажата в текущий момент</returns>
|
||||
public abstract bool GetButton(int pinIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет нажатие кнопки по числовому индексу пина в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-based)</param>
|
||||
/// <returns>true только в первом кадре после нажатия</returns>
|
||||
public abstract bool GetButtonDown(int pinIndex);
|
||||
/// <summary>
|
||||
/// Проверяет нажатие кнопки по числовому индексу пина в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-based)</param>
|
||||
/// <returns>true только в первом кадре после нажатия</returns>
|
||||
public abstract bool GetButtonDown(int pinIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет отпускание кнопки по числовому индексу пина в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-based)</param>
|
||||
/// <returns>true только в первом кадре после отпускания</returns>
|
||||
public abstract bool GetButtonUp(int pinIndex);
|
||||
/// <summary>
|
||||
/// Проверяет отпускание кнопки по числовому индексу пина в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="pinIndex">Числовой индекс пина (0-based)</param>
|
||||
/// <returns>true только в первом кадре после отпускания</returns>
|
||||
public abstract bool GetButtonUp(int pinIndex);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние игрового действия для любого из игроков.
|
||||
/// Используется для мультиплеерных сценариев где важно действие любого игрока.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие активно у первого ИЛИ второго игрока</returns>
|
||||
public abstract bool GetButtonAnyPlayer(PlayerAction button);
|
||||
/// <summary>
|
||||
/// Получает состояние игрового действия для любого из игроков.
|
||||
/// Используется для мультиплеерных сценариев где важно действие любого игрока.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие активно у первого ИЛИ второго игрока</returns>
|
||||
public abstract bool GetButtonAnyPlayer(PlayerAction button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет начало игрового действия любым игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие начато любым игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonDownAnyPlayer(PlayerAction button);
|
||||
/// <summary>
|
||||
/// Проверяет начало игрового действия любым игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие начато любым игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonDownAnyPlayer(PlayerAction button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет завершение игрового действия любым игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие завершено любым игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonUpAnyPlayer(PlayerAction button);
|
||||
/// <summary>
|
||||
/// Проверяет завершение игрового действия любым игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие завершено любым игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonUpAnyPlayer(PlayerAction button);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Получает состояние игрового действия для текущего активного игрока.
|
||||
/// Используется для одиночной игры или поочередного управления.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие активно у текущего игрока</returns>
|
||||
public abstract bool GetButtonCurrentPlayer(PlayerAction button);
|
||||
/// <summary>
|
||||
/// Получает состояние игрового действия для текущего активного игрока.
|
||||
/// Используется для одиночной игры или поочередного управления.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие активно у текущего игрока</returns>
|
||||
public abstract bool GetButtonCurrentPlayer(PlayerAction button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет начало игрового действия текущим игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие начато текущим игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonDownCurrentPlayer(PlayerAction button);
|
||||
/// <summary>
|
||||
/// Проверяет начало игрового действия текущим игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие начато текущим игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonDownCurrentPlayer(PlayerAction button);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет завершение игрового действия текущим игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие завершено текущим игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonUpCurrentPlayer(PlayerAction button);
|
||||
/// <summary>
|
||||
/// Проверяет завершение игрового действия текущим игроком в текущем кадре.
|
||||
/// </summary>
|
||||
/// <param name="button">Абстрактное игровое действие</param>
|
||||
/// <returns>true если действие завершено текущим игроком в этом кадре</returns>
|
||||
public abstract bool GetButtonUpCurrentPlayer(PlayerAction button);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,11 @@
|
||||
public enum InputSourceType
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
COM,
|
||||
Keyboard
|
||||
}
|
||||
/// <summary>
|
||||
/// Типы источников ввода для управления игрой.
|
||||
/// </summary>
|
||||
public enum InputSourceType
|
||||
{
|
||||
COM,
|
||||
Keyboard
|
||||
}
|
||||
}
|
||||
@ -9,262 +9,264 @@ using RR.Game2D.Core.Managers;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация системы ввода с клавиатуры для тестирования и разработки.
|
||||
/// Эмулирует работу периферийной платы через стандартные клавиши клавиатуры.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Соответствие клавиш и пинов:
|
||||
/// - Игрок 1: WASD + Space
|
||||
/// - Игрок 2: Стрелки + RightControl
|
||||
/// - Служебные: 1, 2, 3, F5
|
||||
/// - Датчики хопера: O, P
|
||||
/// </remarks>
|
||||
public class KeyboardInput : InputSource
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
private int currentPlayer = 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
|
||||
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
|
||||
|
||||
// Кэш Key -> KeyControl + ссылка на то, для какой клавиатуры он построен
|
||||
private static readonly Dictionary<Key, KeyControl> _keyCache = new();
|
||||
private static Keyboard _cachedKeyboard;
|
||||
|
||||
private static void EnsureKeyboardCache()
|
||||
/// <summary>
|
||||
/// Реализация системы ввода с клавиатуры для тестирования и разработки.
|
||||
/// Эмулирует работу периферийной платы через стандартные клавиши клавиатуры.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Соответствие клавиш и пинов:
|
||||
/// - Игрок 1: WASD + Space
|
||||
/// - Игрок 2: Стрелки + RightControl
|
||||
/// - Служебные: 1, 2, 3, F5
|
||||
/// - Датчики хопера: O, P
|
||||
/// </remarks>
|
||||
public class KeyboardInput : InputSource
|
||||
{
|
||||
var kb = Keyboard.current;
|
||||
if (kb == null)
|
||||
private int currentPlayer = 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
|
||||
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
|
||||
|
||||
// Кэш Key -> KeyControl + ссылка на то, для какой клавиатуры он построен
|
||||
private static readonly Dictionary<Key, KeyControl> _keyCache = new();
|
||||
private static Keyboard _cachedKeyboard;
|
||||
|
||||
private static void EnsureKeyboardCache()
|
||||
{
|
||||
var kb = Keyboard.current;
|
||||
if (kb == null)
|
||||
{
|
||||
_keyCache.Clear();
|
||||
_cachedKeyboard = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Если клавиатура сменилась (или кэш пуст) — перестроить
|
||||
if (_cachedKeyboard == kb && _keyCache.Count != 0) return;
|
||||
_cachedKeyboard = kb;
|
||||
_keyCache.Clear();
|
||||
_cachedKeyboard = null;
|
||||
return;
|
||||
var keys = kb.allKeys;
|
||||
if (keys.Count <= 0) return;
|
||||
foreach (var kc in keys.Where(kc => kc != null))
|
||||
{
|
||||
_keyCache[kc.keyCode] = kc;
|
||||
}
|
||||
}
|
||||
|
||||
// Если клавиатура сменилась (или кэш пуст) — перестроить
|
||||
if (_cachedKeyboard == kb && _keyCache.Count != 0) return;
|
||||
_cachedKeyboard = kb;
|
||||
_keyCache.Clear();
|
||||
var keys = kb.allKeys;
|
||||
if (keys.Count <= 0) return;
|
||||
foreach (var kc in keys.Where(kc => kc != null))
|
||||
private static KeyControl GetKeyControl(Key key)
|
||||
{
|
||||
_keyCache[kc.keyCode] = kc;
|
||||
if (key == Key.None) return null;
|
||||
|
||||
// Можем спокойно вызываться даже в самом раннем кадре
|
||||
EnsureKeyboardCache();
|
||||
return _cachedKeyboard == null ? null : _keyCache.GetValueOrDefault(key);
|
||||
}
|
||||
|
||||
|
||||
// --- адаптер: из твоего старого KeyCode -> в новый Key ---
|
||||
private static Key ToNewKey(UnityEngine.KeyCode old)
|
||||
{
|
||||
// Пытаемся по имени (у многих совпадают)
|
||||
if (Enum.TryParse<Key>(old.ToString(), true, out var k)) return k;
|
||||
|
||||
// Несовпадающие названия — руками:
|
||||
return old switch
|
||||
{
|
||||
KeyCode.Return => Key.Enter,
|
||||
KeyCode.KeypadEnter => Key.NumpadEnter,
|
||||
|
||||
KeyCode.LeftControl => Key.LeftCtrl,
|
||||
KeyCode.RightControl => Key.RightCtrl,
|
||||
|
||||
KeyCode.LeftShift => Key.LeftShift,
|
||||
KeyCode.RightShift => Key.RightShift,
|
||||
|
||||
KeyCode.LeftAlt => Key.LeftAlt,
|
||||
KeyCode.RightAlt => Key.RightAlt,
|
||||
|
||||
KeyCode.LeftCommand => Key.LeftMeta,
|
||||
KeyCode.RightCommand => Key.RightMeta,
|
||||
|
||||
KeyCode.None => Key.None,
|
||||
|
||||
_ => Key.None // если нужен редкий кей — добавь сюда
|
||||
};
|
||||
}
|
||||
|
||||
// Удобные обёртки
|
||||
private static bool IsPressed(Key k)
|
||||
{
|
||||
if (k == Key.None) return false;
|
||||
var c = GetKeyControl(k);
|
||||
return c is { isPressed: true };
|
||||
}
|
||||
|
||||
private static bool WasPressedThisFrame(Key k)
|
||||
{
|
||||
if (k == Key.None) return false;
|
||||
var c = GetKeyControl(k);
|
||||
return c is { wasPressedThisFrame: true };
|
||||
}
|
||||
|
||||
private static bool WasReleasedThisFrame(Key k)
|
||||
{
|
||||
if (k == Key.None) return false;
|
||||
var c = GetKeyControl(k);
|
||||
return c is { wasReleasedThisFrame: true };
|
||||
}
|
||||
|
||||
/// Проверяет состояние кнопки по её физическому пину через клавиатуру (Input System)
|
||||
public override bool GetButton(ButtonPinMapping button) => IsPressed(ToNewKey(getKeyCode(button)));
|
||||
|
||||
/// Проверяет нажатие кнопки по пину в текущем кадре (Input System)
|
||||
public override bool GetButtonDown(ButtonPinMapping button) => WasPressedThisFrame(ToNewKey(getKeyCode(button)));
|
||||
|
||||
/// Проверяет отпускание кнопки по пину в текущем кадре (Input System)
|
||||
public override bool GetButtonUp(ButtonPinMapping button) => WasReleasedThisFrame(ToNewKey(getKeyCode(button)));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// По числовому индексу пина
|
||||
public override bool GetButton(int pinIndex) => IsPressed(ToNewKey(getKeyCode(pinIndex)));
|
||||
|
||||
public override bool GetButtonDown(int pinIndex) => WasPressedThisFrame(ToNewKey(getKeyCode(pinIndex)));
|
||||
|
||||
public override bool GetButtonUp(int pinIndex) => WasReleasedThisFrame(ToNewKey(getKeyCode(pinIndex)));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// Действия текущего игрока (для клавиатуры — это по сути игрок 1)
|
||||
public override bool GetButtonCurrentPlayer(PlayerAction button) => GetButton(GetButtonForCurrentPlayer(button));
|
||||
|
||||
public override bool GetButtonDownCurrentPlayer(PlayerAction button) => GetButtonDown(GetButtonForCurrentPlayer(button));
|
||||
|
||||
public override bool GetButtonUpCurrentPlayer(PlayerAction button) => GetButtonUp(GetButtonForCurrentPlayer(button));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// Любой игрок (для клавиатуры — эквивалент текущего)
|
||||
public override bool GetButtonAnyPlayer(PlayerAction button) => GetButtonCurrentPlayer(button);
|
||||
public override bool GetButtonDownAnyPlayer(PlayerAction button) => GetButtonDownCurrentPlayer(button);
|
||||
public override bool GetButtonUpAnyPlayer(PlayerAction button) => GetButtonUpCurrentPlayer(button);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует клавиатурный ввод. Всегда возвращает true так как клавиатура всегда доступна.
|
||||
/// </summary>
|
||||
/// <returns>Всегда true</returns>
|
||||
public override bool Initialize()
|
||||
{
|
||||
gm = GameManager.Instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ
|
||||
// ================================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Определяет кнопку текущего игрока для указанного действия.
|
||||
/// Для клавиатуры всегда возвращает кнопку первого игрока.
|
||||
/// </summary>
|
||||
private ButtonPinMapping GetButtonForCurrentPlayer(PlayerAction button)
|
||||
{
|
||||
var buttons = _buttonByAction(button);
|
||||
return currentPlayer == 1 ? buttons.player1button : buttons.player2button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет действие для обоих игроков используя указанную функцию проверки.
|
||||
/// </summary>
|
||||
private bool CheckBothPlayers(PlayerAction action, System.Func<ButtonPinMapping, bool> buttonFunc)
|
||||
{
|
||||
var buttons = _buttonByAction(action);
|
||||
return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает соответствие между игровыми действиями и физическими кнопками игроков.
|
||||
/// </summary>
|
||||
private (ButtonPinMapping player1button, ButtonPinMapping player2button) _buttonByAction(PlayerAction playerAction) =>
|
||||
playerAction switch
|
||||
{
|
||||
PlayerAction.up => (ButtonPinMapping.up1, ButtonPinMapping.up2),
|
||||
PlayerAction.down => (ButtonPinMapping.down1, ButtonPinMapping.down2),
|
||||
PlayerAction.left => (ButtonPinMapping.left1, ButtonPinMapping.left2),
|
||||
PlayerAction.right => (ButtonPinMapping.right1, ButtonPinMapping.right2),
|
||||
PlayerAction.action => (ButtonPinMapping.action1, ButtonPinMapping.action2),
|
||||
PlayerAction.extra => (ButtonPinMapping.extra, ButtonPinMapping.extra),
|
||||
_ => throw new System.ArgumentException($"Unknown player action: {playerAction}")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует физический пин в соответствующую клавишу клавиатуры.
|
||||
/// </summary>
|
||||
private KeyCode getKeyCode(ButtonPinMapping button)
|
||||
{
|
||||
return button switch
|
||||
{
|
||||
ButtonPinMapping.up1 => KeyCode.W,
|
||||
ButtonPinMapping.right1 => KeyCode.D,
|
||||
ButtonPinMapping.down1 => KeyCode.S,
|
||||
ButtonPinMapping.left1 => KeyCode.A,
|
||||
ButtonPinMapping.action1 => KeyCode.Space,
|
||||
|
||||
ButtonPinMapping.up2 => KeyCode.UpArrow,
|
||||
ButtonPinMapping.right2 => KeyCode.RightArrow,
|
||||
ButtonPinMapping.down2 => KeyCode.DownArrow,
|
||||
ButtonPinMapping.left2 => KeyCode.LeftArrow,
|
||||
ButtonPinMapping.action2 => KeyCode.RightControl,
|
||||
|
||||
ButtonPinMapping.startOnePlayer => KeyCode.Alpha1,
|
||||
ButtonPinMapping.startTwoPlayers => KeyCode.Alpha2,
|
||||
|
||||
ButtonPinMapping.secret => KeyCode.F5,
|
||||
|
||||
ButtonPinMapping.extra => KeyCode.Alpha3,
|
||||
|
||||
ButtonPinMapping.hasPrize => KeyCode.O,
|
||||
ButtonPinMapping.prizeGiven => KeyCode.P,
|
||||
|
||||
_ => KeyCode.None,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует числовой индекс пина в соответствующую клавишу клавиатуры.
|
||||
/// Используется для методов с прямым доступом по индексу.
|
||||
/// </summary>
|
||||
private KeyCode getKeyCode(int pinIndex)
|
||||
{
|
||||
return pinIndex switch
|
||||
{
|
||||
0 => KeyCode.W, // up1
|
||||
1 => KeyCode.D, // right1
|
||||
2 => KeyCode.S, // down1
|
||||
3 => KeyCode.A, // left1
|
||||
4 => KeyCode.Space, // action1
|
||||
|
||||
5 => KeyCode.UpArrow, // up2
|
||||
6 => KeyCode.RightArrow, // right2
|
||||
7 => KeyCode.DownArrow, // down2
|
||||
8 => KeyCode.LeftArrow, // left2
|
||||
9 => KeyCode.RightControl, // action2
|
||||
|
||||
10 => KeyCode.Alpha1, // startOnePlayer
|
||||
11 => KeyCode.Alpha2, // startTwoPlayers
|
||||
|
||||
12 => KeyCode.F5, // secret
|
||||
|
||||
13 => KeyCode.Alpha3, // extra
|
||||
|
||||
14 => KeyCode.O, // hasPrize
|
||||
15 => KeyCode.P, // prizeGiven
|
||||
|
||||
_ => KeyCode.None, // невалидный индекс
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyControl GetKeyControl(Key key)
|
||||
{
|
||||
if (key == Key.None) return null;
|
||||
|
||||
// Можем спокойно вызываться даже в самом раннем кадре
|
||||
EnsureKeyboardCache();
|
||||
return _cachedKeyboard == null ? null : _keyCache.GetValueOrDefault(key);
|
||||
}
|
||||
|
||||
|
||||
// --- адаптер: из твоего старого KeyCode -> в новый Key ---
|
||||
private static Key ToNewKey(UnityEngine.KeyCode old)
|
||||
{
|
||||
// Пытаемся по имени (у многих совпадают)
|
||||
if (Enum.TryParse<Key>(old.ToString(), true, out var k)) return k;
|
||||
|
||||
// Несовпадающие названия — руками:
|
||||
return old switch
|
||||
{
|
||||
KeyCode.Return => Key.Enter,
|
||||
KeyCode.KeypadEnter => Key.NumpadEnter,
|
||||
|
||||
KeyCode.LeftControl => Key.LeftCtrl,
|
||||
KeyCode.RightControl => Key.RightCtrl,
|
||||
|
||||
KeyCode.LeftShift => Key.LeftShift,
|
||||
KeyCode.RightShift => Key.RightShift,
|
||||
|
||||
KeyCode.LeftAlt => Key.LeftAlt,
|
||||
KeyCode.RightAlt => Key.RightAlt,
|
||||
|
||||
KeyCode.LeftCommand => Key.LeftMeta,
|
||||
KeyCode.RightCommand => Key.RightMeta,
|
||||
|
||||
KeyCode.None => Key.None,
|
||||
|
||||
_ => Key.None // если нужен редкий кей — добавь сюда
|
||||
};
|
||||
}
|
||||
|
||||
// Удобные обёртки
|
||||
private static bool IsPressed(Key k)
|
||||
{
|
||||
if (k == Key.None) return false;
|
||||
var c = GetKeyControl(k);
|
||||
return c is { isPressed: true };
|
||||
}
|
||||
|
||||
private static bool WasPressedThisFrame(Key k)
|
||||
{
|
||||
if (k == Key.None) return false;
|
||||
var c = GetKeyControl(k);
|
||||
return c is { wasPressedThisFrame: true };
|
||||
}
|
||||
|
||||
private static bool WasReleasedThisFrame(Key k)
|
||||
{
|
||||
if (k == Key.None) return false;
|
||||
var c = GetKeyControl(k);
|
||||
return c is { wasReleasedThisFrame: true };
|
||||
}
|
||||
|
||||
/// Проверяет состояние кнопки по её физическому пину через клавиатуру (Input System)
|
||||
public override bool GetButton(ButtonPinMapping button) => IsPressed(ToNewKey(getKeyCode(button)));
|
||||
|
||||
/// Проверяет нажатие кнопки по пину в текущем кадре (Input System)
|
||||
public override bool GetButtonDown(ButtonPinMapping button) => WasPressedThisFrame(ToNewKey(getKeyCode(button)));
|
||||
|
||||
/// Проверяет отпускание кнопки по пину в текущем кадре (Input System)
|
||||
public override bool GetButtonUp(ButtonPinMapping button) => WasReleasedThisFrame(ToNewKey(getKeyCode(button)));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// По числовому индексу пина
|
||||
public override bool GetButton(int pinIndex) => IsPressed(ToNewKey(getKeyCode(pinIndex)));
|
||||
|
||||
public override bool GetButtonDown(int pinIndex) => WasPressedThisFrame(ToNewKey(getKeyCode(pinIndex)));
|
||||
|
||||
public override bool GetButtonUp(int pinIndex) => WasReleasedThisFrame(ToNewKey(getKeyCode(pinIndex)));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// Действия текущего игрока (для клавиатуры — это по сути игрок 1)
|
||||
public override bool GetButtonCurrentPlayer(PlayerAction button) => GetButton(GetButtonForCurrentPlayer(button));
|
||||
|
||||
public override bool GetButtonDownCurrentPlayer(PlayerAction button) => GetButtonDown(GetButtonForCurrentPlayer(button));
|
||||
|
||||
public override bool GetButtonUpCurrentPlayer(PlayerAction button) => GetButtonUp(GetButtonForCurrentPlayer(button));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// Любой игрок (для клавиатуры — эквивалент текущего)
|
||||
public override bool GetButtonAnyPlayer(PlayerAction button) => GetButtonCurrentPlayer(button);
|
||||
public override bool GetButtonDownAnyPlayer(PlayerAction button) => GetButtonDownCurrentPlayer(button);
|
||||
public override bool GetButtonUpAnyPlayer(PlayerAction button) => GetButtonUpCurrentPlayer(button);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует клавиатурный ввод. Всегда возвращает true так как клавиатура всегда доступна.
|
||||
/// </summary>
|
||||
/// <returns>Всегда true</returns>
|
||||
public override bool Initialize()
|
||||
{
|
||||
gm = GameManager.Instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ
|
||||
// ================================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Определяет кнопку текущего игрока для указанного действия.
|
||||
/// Для клавиатуры всегда возвращает кнопку первого игрока.
|
||||
/// </summary>
|
||||
private ButtonPinMapping GetButtonForCurrentPlayer(PlayerAction button)
|
||||
{
|
||||
var buttons = _buttonByAction(button);
|
||||
return currentPlayer == 1 ? buttons.player1button : buttons.player2button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет действие для обоих игроков используя указанную функцию проверки.
|
||||
/// </summary>
|
||||
private bool CheckBothPlayers(PlayerAction action, System.Func<ButtonPinMapping, bool> buttonFunc)
|
||||
{
|
||||
var buttons = _buttonByAction(action);
|
||||
return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает соответствие между игровыми действиями и физическими кнопками игроков.
|
||||
/// </summary>
|
||||
private (ButtonPinMapping player1button, ButtonPinMapping player2button) _buttonByAction(PlayerAction playerAction) =>
|
||||
playerAction switch
|
||||
{
|
||||
PlayerAction.up => (ButtonPinMapping.up1, ButtonPinMapping.up2),
|
||||
PlayerAction.down => (ButtonPinMapping.down1, ButtonPinMapping.down2),
|
||||
PlayerAction.left => (ButtonPinMapping.left1, ButtonPinMapping.left2),
|
||||
PlayerAction.right => (ButtonPinMapping.right1, ButtonPinMapping.right2),
|
||||
PlayerAction.action => (ButtonPinMapping.action1, ButtonPinMapping.action2),
|
||||
PlayerAction.extra => (ButtonPinMapping.extra, ButtonPinMapping.extra),
|
||||
_ => throw new System.ArgumentException($"Unknown player action: {playerAction}")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует физический пин в соответствующую клавишу клавиатуры.
|
||||
/// </summary>
|
||||
private KeyCode getKeyCode(ButtonPinMapping button)
|
||||
{
|
||||
return button switch
|
||||
{
|
||||
ButtonPinMapping.up1 => KeyCode.W,
|
||||
ButtonPinMapping.right1 => KeyCode.D,
|
||||
ButtonPinMapping.down1 => KeyCode.S,
|
||||
ButtonPinMapping.left1 => KeyCode.A,
|
||||
ButtonPinMapping.action1 => KeyCode.Space,
|
||||
|
||||
ButtonPinMapping.up2 => KeyCode.UpArrow,
|
||||
ButtonPinMapping.right2 => KeyCode.RightArrow,
|
||||
ButtonPinMapping.down2 => KeyCode.DownArrow,
|
||||
ButtonPinMapping.left2 => KeyCode.LeftArrow,
|
||||
ButtonPinMapping.action2 => KeyCode.RightControl,
|
||||
|
||||
ButtonPinMapping.startOnePlayer => KeyCode.Alpha1,
|
||||
ButtonPinMapping.startTwoPlayers => KeyCode.Alpha2,
|
||||
|
||||
ButtonPinMapping.secret => KeyCode.F5,
|
||||
|
||||
ButtonPinMapping.extra => KeyCode.Alpha3,
|
||||
|
||||
ButtonPinMapping.hasPrize => KeyCode.O,
|
||||
ButtonPinMapping.prizeGiven => KeyCode.P,
|
||||
|
||||
_ => KeyCode.None,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует числовой индекс пина в соответствующую клавишу клавиатуры.
|
||||
/// Используется для методов с прямым доступом по индексу.
|
||||
/// </summary>
|
||||
private KeyCode getKeyCode(int pinIndex)
|
||||
{
|
||||
return pinIndex switch
|
||||
{
|
||||
0 => KeyCode.W, // up1
|
||||
1 => KeyCode.D, // right1
|
||||
2 => KeyCode.S, // down1
|
||||
3 => KeyCode.A, // left1
|
||||
4 => KeyCode.Space, // action1
|
||||
|
||||
5 => KeyCode.UpArrow, // up2
|
||||
6 => KeyCode.RightArrow, // right2
|
||||
7 => KeyCode.DownArrow, // down2
|
||||
8 => KeyCode.LeftArrow, // left2
|
||||
9 => KeyCode.RightControl, // action2
|
||||
|
||||
10 => KeyCode.Alpha1, // startOnePlayer
|
||||
11 => KeyCode.Alpha2, // startTwoPlayers
|
||||
|
||||
12 => KeyCode.F5, // secret
|
||||
|
||||
13 => KeyCode.Alpha3, // extra
|
||||
|
||||
14 => KeyCode.O, // hasPrize
|
||||
15 => KeyCode.P, // prizeGiven
|
||||
|
||||
_ => KeyCode.None, // невалидный индекс
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
@ -441,3 +443,4 @@ public class KeyboardInput : InputSource
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@ -1,19 +1,22 @@
|
||||
/// <summary>
|
||||
/// Абстрактные игровые действия для управления игроком.
|
||||
/// Обеспечивает независимость от физического расположения кнопок.
|
||||
/// </summary>
|
||||
public enum PlayerAction
|
||||
namespace RR.Common.Controls
|
||||
{
|
||||
/// <summary>Вверх</summary>
|
||||
up,
|
||||
/// <summary>Вниз</summary>
|
||||
down,
|
||||
/// <summary>Влево</summary>
|
||||
left,
|
||||
/// <summary>Вправо</summary>
|
||||
right,
|
||||
/// <summary>Основное действие</summary>
|
||||
action,
|
||||
/// <summary>Дополнительное действие(ВНИМАНИЕ РАБОТАЕТ У ОБОИХ ИГРОКОВ ОДНОВРЕМЕННО)</summary>
|
||||
extra
|
||||
}
|
||||
/// <summary>
|
||||
/// Абстрактные игровые действия для управления игроком.
|
||||
/// Обеспечивает независимость от физического расположения кнопок.
|
||||
/// </summary>
|
||||
public enum PlayerAction
|
||||
{
|
||||
/// <summary>Вверх</summary>
|
||||
up,
|
||||
/// <summary>Вниз</summary>
|
||||
down,
|
||||
/// <summary>Влево</summary>
|
||||
left,
|
||||
/// <summary>Вправо</summary>
|
||||
right,
|
||||
/// <summary>Основное действие</summary>
|
||||
action,
|
||||
/// <summary>Дополнительное действие(ВНИМАНИЕ РАБОТАЕТ У ОБОИХ ИГРОКОВ ОДНОВРЕМЕННО)</summary>
|
||||
extra
|
||||
}
|
||||
}
|
||||
40
Assets/Scripts/Entities/JumpPad2D.cs
Normal file
40
Assets/Scripts/Entities/JumpPad2D.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
using RR.Game2D.Behaviour;
|
||||
|
||||
namespace RR.Game2D.Entities
|
||||
{
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
public class JumpPad2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float verticalBand = 0.75f;
|
||||
private BoxCollider2D _col;
|
||||
|
||||
public Action<GameObject> PlayerJumped;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_col = GetComponent<BoxCollider2D>();
|
||||
|
||||
if (!_col.isTrigger)
|
||||
_col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (!other.CompareTag("Player")) return;
|
||||
|
||||
PlayerController playerController = other.GetComponent<PlayerController>();
|
||||
Rigidbody2D playerRB = other.GetComponent<Rigidbody2D>();
|
||||
|
||||
if (!(Mathf.Abs(other.transform.position.y - _col.bounds.max.y) <= verticalBand)) return;
|
||||
|
||||
Vector2 newPlayerVelocity = playerRB.linearVelocity;
|
||||
newPlayerVelocity.y = playerController.jumpVelocity;
|
||||
playerRB.linearVelocity = newPlayerVelocity;
|
||||
|
||||
PlayerJumped?.Invoke(other.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Assets/Scripts/Entities/MovingPlatform.cs
Normal file
54
Assets/Scripts/Entities/MovingPlatform.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using RR.Game2D.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Game2D.Entities
|
||||
{
|
||||
public class MovingPlatform : MonoBehaviour
|
||||
{
|
||||
[Header("Настройки движения платформы")]
|
||||
[SerializeField] private float moveSpeed = 1f;
|
||||
[SerializeField] private float pathLength = 5f;
|
||||
[SerializeField] private Direction2D moveDirection = Direction2D.Right;
|
||||
|
||||
[Header("Прикрепление игрока")]
|
||||
[SerializeField] private bool attachPlayer = true;
|
||||
|
||||
private Vector3 _startPosition;
|
||||
private Vector3 _endPosition;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_startPosition = transform.position;
|
||||
|
||||
_endPosition = moveDirection switch
|
||||
{
|
||||
Direction2D.Down => _startPosition + Vector3.down * pathLength,
|
||||
Direction2D.Left => _startPosition + Vector3.left * pathLength,
|
||||
Direction2D.Right => _startPosition + Vector3.right * pathLength,
|
||||
Direction2D.Up => _startPosition + Vector3.up * pathLength,
|
||||
_ => _startPosition
|
||||
};
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
float pingPong = Mathf.PingPong(Time.time * moveSpeed, 1f);
|
||||
transform.position = Vector3.Lerp(_startPosition, _endPosition, pingPong);
|
||||
}
|
||||
|
||||
private void OnCollisionEnter2D(Collision2D other)
|
||||
{
|
||||
if (!attachPlayer) return;
|
||||
if (!other.gameObject.CompareTag("Player")) return;
|
||||
other.transform.SetParent(transform);
|
||||
}
|
||||
|
||||
private void OnCollisionExit2D(Collision2D other)
|
||||
{
|
||||
if (!attachPlayer) return;
|
||||
if (!other.gameObject.CompareTag("Player")) return;
|
||||
other.transform.SetParent(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Entities/MovingPlatform.cs.meta
Normal file
2
Assets/Scripts/Entities/MovingPlatform.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76ff605ebb30e1043bf07de270a4cd73
|
||||
@ -1,16 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
public class CoinCollectible : Collectible
|
||||
namespace RR.Common.Entities.Props
|
||||
{
|
||||
public override bool TryPickup(GameObject newOwner, Transform optionalAttachPoint = null)
|
||||
public class CoinCollectible : Collectible
|
||||
{
|
||||
base.TryPickup(newOwner, optionalAttachPoint);
|
||||
|
||||
GameManager.Instance.AddCoin();
|
||||
Destroy(gameObject);
|
||||
|
||||
return true;
|
||||
public override bool TryPickup(GameObject newOwner, Transform optionalAttachPoint = null)
|
||||
{
|
||||
base.TryPickup(newOwner, optionalAttachPoint);
|
||||
|
||||
GameManager.Instance.AddCoin();
|
||||
Destroy(gameObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,37 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
using RR.Common.Interfaces;
|
||||
using RR.Common.Audio;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class Collectible : MonoBehaviour, IPickable
|
||||
namespace RR.Common.Entities.Props
|
||||
{
|
||||
[SerializeField] private SoundEffect pickupSFX;
|
||||
[SerializeField] private VisualEffect pickupVFX;
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class Collectible : MonoBehaviour, IPickable
|
||||
{
|
||||
if (other.CompareTag("Player")) TryPickup(other.gameObject);
|
||||
}
|
||||
[SerializeField] private SoundEffect pickupSFX;
|
||||
[SerializeField] private VisualEffect pickupVFX;
|
||||
|
||||
public virtual bool TryPickup(GameObject newOwner, Transform optionalAttachPoint = null)
|
||||
{
|
||||
if (pickupSFX.sound != null)
|
||||
GameManager.Instance.Audio.PlaySfxAt(pickupSFX.sound, transform.position,
|
||||
pickupSFX.volume, pickupSFX.pitch);
|
||||
|
||||
if (pickupVFX != null)
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
pickupVFX.transform.position = transform.position;
|
||||
pickupVFX.Play();
|
||||
pickupVFX.Stop();
|
||||
if (other.CompareTag("Player")) TryPickup(other.gameObject);
|
||||
}
|
||||
|
||||
public virtual bool TryPickup(GameObject newOwner, Transform optionalAttachPoint = null)
|
||||
{
|
||||
if (pickupSFX.sound != null)
|
||||
GameManager.Instance.Audio.PlaySfxAt(pickupSFX.sound, transform.position,
|
||||
pickupSFX.volume, pickupSFX.pitch);
|
||||
|
||||
if (pickupVFX != null)
|
||||
{
|
||||
pickupVFX.transform.position = transform.position;
|
||||
pickupVFX.Play();
|
||||
pickupVFX.Stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Assets/Scripts/Entities/ShiftingPlatform.cs
Normal file
76
Assets/Scripts/Entities/ShiftingPlatform.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using RR.Game2D.Core;
|
||||
using UnityEngine;
|
||||
|
||||
public class ShiftingPlatform : MonoBehaviour
|
||||
{
|
||||
[Header("Настройки смещения платформы")]
|
||||
[SerializeField] private Vector3 shiftAmount = new Vector3(0f, 2f, 0f);
|
||||
[SerializeField] private float shiftDuration = 1f;
|
||||
[SerializeField] private Direction2D shiftDirection = Direction2D.Down;
|
||||
|
||||
[Header("Прикрепление игрока")]
|
||||
[SerializeField] private bool attachPlayer = true;
|
||||
|
||||
private Vector3 _startPosition;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_startPosition = transform.position;
|
||||
}
|
||||
|
||||
private void OnCollisionEnter2D(Collision2D other)
|
||||
{
|
||||
if (!other.gameObject.CompareTag("Player")) return;
|
||||
|
||||
if (attachPlayer) other.transform.SetParent(transform);
|
||||
|
||||
Vector3 shiftVector = shiftDirection switch
|
||||
{
|
||||
Direction2D.Down => Vector3.down * shiftAmount.y,
|
||||
Direction2D.Left => Vector3.left * shiftAmount.x,
|
||||
Direction2D.Right => Vector3.right * shiftAmount.x,
|
||||
Direction2D.Up => Vector3.up * shiftAmount.y,
|
||||
_ => Vector3.zero
|
||||
};
|
||||
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(ShiftPlatform(shiftVector));
|
||||
}
|
||||
|
||||
private void OnCollisionExit2D(Collision2D other)
|
||||
{
|
||||
if (!other.gameObject.CompareTag("Player")) return;
|
||||
|
||||
if (attachPlayer) other.transform.SetParent(null);
|
||||
|
||||
Vector3 shiftVector = shiftDirection switch
|
||||
{
|
||||
Direction2D.Down => Vector3.up * shiftAmount.y,
|
||||
Direction2D.Left => Vector3.right * shiftAmount.x,
|
||||
Direction2D.Right => Vector3.left * shiftAmount.x,
|
||||
Direction2D.Up => Vector3.down * shiftAmount.y,
|
||||
_ => Vector3.zero
|
||||
};
|
||||
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(ShiftPlatform(shiftVector));
|
||||
}
|
||||
|
||||
private IEnumerator ShiftPlatform(Vector3 shiftVector)
|
||||
{
|
||||
Vector3 initialPosition = transform.position;
|
||||
Vector3 finalPosition = initialPosition + shiftVector;
|
||||
float elapsedTime = 0f;
|
||||
|
||||
while (elapsedTime < shiftDuration)
|
||||
{
|
||||
transform.position = Vector3.Lerp(initialPosition, finalPosition, (elapsedTime / shiftDuration));
|
||||
elapsedTime += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
transform.position = finalPosition;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Entities/ShiftingPlatform.cs.meta
Normal file
2
Assets/Scripts/Entities/ShiftingPlatform.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c97e8d15633ddd04d9f8582942109ef5
|
||||
202
Assets/Scripts/Entities/VanishingPlatform.cs
Normal file
202
Assets/Scripts/Entities/VanishingPlatform.cs
Normal file
@ -0,0 +1,202 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class VanishingPlatform : MonoBehaviour
|
||||
{
|
||||
[Header("Настройки исчезновения платформы")]
|
||||
[SerializeField] private float vanishDuration = 0.5f;
|
||||
[SerializeField] private float reappearDelay = 2f;
|
||||
[SerializeField] private float reappearDuration = 0.5f;
|
||||
|
||||
private CompositeCollider2D _compositeCollider;
|
||||
private Rigidbody2D _rigidbody;
|
||||
private SpriteRenderer[] _spriteRenderers;
|
||||
private Coroutine _vanishCoroutine;
|
||||
private Coroutine _reappearCoroutine;
|
||||
private Color[] _initialColors;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_compositeCollider = GetComponent<CompositeCollider2D>();
|
||||
_rigidbody = GetComponent<Rigidbody2D>();
|
||||
_spriteRenderers = GetComponentsInChildren<SpriteRenderer>();
|
||||
|
||||
if (_compositeCollider == null)
|
||||
Debug.LogWarning($"[{nameof(VanishingPlatform)}] CompositeCollider2D не найден на {gameObject.name}.");
|
||||
|
||||
if (_rigidbody == null)
|
||||
Debug.LogWarning($"[{nameof(VanishingPlatform)}] Rigidbody2D не найден на {gameObject.name}. CompositeCollider2D требует Rigidbody2D.");
|
||||
|
||||
if (_spriteRenderers == null || _spriteRenderers.Length == 0)
|
||||
Debug.LogWarning($"[{nameof(VanishingPlatform)}] SpriteRenderer'ы не найдены на {gameObject.name} и его дочерних объектах.");
|
||||
}
|
||||
|
||||
private void OnCollisionEnter2D(Collision2D other)
|
||||
{
|
||||
if (!other.gameObject.CompareTag("Player")) return;
|
||||
|
||||
// Если в процессе была корутина Reappear — её отменяем
|
||||
if (_reappearCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_reappearCoroutine);
|
||||
_reappearCoroutine = null;
|
||||
}
|
||||
|
||||
if (_vanishCoroutine != null)
|
||||
StopCoroutine(_vanishCoroutine);
|
||||
|
||||
_vanishCoroutine = StartCoroutine(Vanish());
|
||||
}
|
||||
|
||||
private void OnCollisionExit2D(Collision2D other)
|
||||
{
|
||||
if (!other.gameObject.CompareTag("Player")) return;
|
||||
|
||||
// Если в процессе была корутина Vanish — её отменяем
|
||||
if (_vanishCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_vanishCoroutine);
|
||||
_vanishCoroutine = null;
|
||||
}
|
||||
|
||||
if (_reappearCoroutine != null)
|
||||
StopCoroutine(_reappearCoroutine);
|
||||
|
||||
_reappearCoroutine = StartCoroutine(Reappear());
|
||||
}
|
||||
|
||||
private IEnumerator Vanish()
|
||||
{
|
||||
if (_spriteRenderers == null || _spriteRenderers.Length == 0)
|
||||
yield break; // нечего анимировать
|
||||
|
||||
// Сохраняем начальные цвета для всех рендереров (если ещё не сохранены)
|
||||
if (_initialColors == null || _initialColors.Length != _spriteRenderers.Length)
|
||||
{
|
||||
_initialColors = new Color[_spriteRenderers.Length];
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
_initialColors[i] = _spriteRenderers[i].color;
|
||||
}
|
||||
|
||||
// Запоминаем текущую альфу для каждого рендерера
|
||||
float[] startAlphas = new float[_spriteRenderers.Length];
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
startAlphas[i] = _spriteRenderers[i].color.a;
|
||||
|
||||
float elapsed = 0f;
|
||||
|
||||
while (elapsed < vanishDuration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = vanishDuration > 0f ? Mathf.Clamp01(elapsed / vanishDuration) : 1f;
|
||||
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
{
|
||||
Color c = _initialColors[i];
|
||||
float alpha = Mathf.Lerp(startAlphas[i], 0f, t);
|
||||
_spriteRenderers[i].color = new Color(c.r, c.g, c.b, alpha);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Принудительно установить 0 альфу
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
{
|
||||
Color c = _initialColors[i];
|
||||
_spriteRenderers[i].color = new Color(c.r, c.g, c.b, 0f);
|
||||
}
|
||||
|
||||
// Отключаем физику через Rigidbody2D.simulated, что отключает и CompositeCollider2D
|
||||
if (_rigidbody)
|
||||
_rigidbody.simulated = false;
|
||||
|
||||
_vanishCoroutine = null;
|
||||
}
|
||||
|
||||
private IEnumerator Reappear()
|
||||
{
|
||||
if (_spriteRenderers == null || _spriteRenderers.Length == 0)
|
||||
yield break; // нечего анимировать
|
||||
|
||||
// Убедимся, что у нас есть базовые цвета — если нет, прочитаем текущие
|
||||
if (_initialColors == null || _initialColors.Length != _spriteRenderers.Length)
|
||||
{
|
||||
_initialColors = new Color[_spriteRenderers.Length];
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
_initialColors[i] = _spriteRenderers[i].color;
|
||||
}
|
||||
|
||||
// Задержка перед появлением (как в предыдущей логике)
|
||||
yield return new WaitForSeconds(reappearDelay);
|
||||
|
||||
// Запоминаем текущую альфу для каждого рендерера
|
||||
float[] startAlphas = new float[_spriteRenderers.Length];
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
startAlphas[i] = _spriteRenderers[i].color.a;
|
||||
|
||||
float elapsed = 0f;
|
||||
|
||||
while (elapsed < reappearDuration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = reappearDuration > 0f ? Mathf.Clamp01(elapsed / reappearDuration) : 1f;
|
||||
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
{
|
||||
Color c = _initialColors[i];
|
||||
float alpha = Mathf.Lerp(startAlphas[i], 1f, t);
|
||||
_spriteRenderers[i].color = new Color(c.r, c.g, c.b, alpha);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Принудительно установить полную непрозрачность
|
||||
for (int i = 0; i < _spriteRenderers.Length; i++)
|
||||
{
|
||||
Color c = _initialColors[i];
|
||||
_spriteRenderers[i].color = new Color(c.r, c.g, c.b, 1f);
|
||||
}
|
||||
|
||||
// Включаем физику обратно
|
||||
if (_rigidbody)
|
||||
_rigidbody.simulated = true;
|
||||
|
||||
_reappearCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Внешний метод для немедленного запуска анимации исчезновения (например, вызов из других скриптов).
|
||||
/// </summary>
|
||||
public void TriggerVanish()
|
||||
{
|
||||
if (_reappearCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_reappearCoroutine);
|
||||
_reappearCoroutine = null;
|
||||
}
|
||||
|
||||
if (_vanishCoroutine != null)
|
||||
StopCoroutine(_vanishCoroutine);
|
||||
|
||||
_vanishCoroutine = StartCoroutine(Vanish());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Внешний метод для немедленного запуска анимации появления (например, вызов из других скриптов).
|
||||
/// </summary>
|
||||
public void TriggerReappear()
|
||||
{
|
||||
if (_vanishCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_vanishCoroutine);
|
||||
_vanishCoroutine = null;
|
||||
}
|
||||
|
||||
if (_reappearCoroutine != null)
|
||||
StopCoroutine(_reappearCoroutine);
|
||||
|
||||
_reappearCoroutine = StartCoroutine(Reappear());
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Entities/VanishingPlatform.cs.meta
Normal file
2
Assets/Scripts/Entities/VanishingPlatform.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4edf3d1d9d2a8584ba8c45271c11148d
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using RR.Common.Controls;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using RR.Common.Behaviour.ScriptableObjects;
|
||||
using RR.Common.Controls;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
@ -1,89 +1,92 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using RR.Common.Controls;
|
||||
using RR.Common.Managers;
|
||||
using UnityEngine;
|
||||
using RR.Common.Utility;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
|
||||
public class PrizeSystemManager : MonoBehaviour
|
||||
namespace RR.Common.Managers
|
||||
{
|
||||
public static PrizeSystemManager Instance { get; private set; }
|
||||
|
||||
[Header("Настройки выдачи призов")]
|
||||
[SerializeField] private bool givePrizes = true;
|
||||
[SerializeField] private bool enableHopper = false;
|
||||
|
||||
[Header("Настройки генерации QR-кодов")]
|
||||
[SerializeField] private List<QRCodeInfo> qrCodes = new();
|
||||
private readonly List<Texture2D> generatedQRCodes = new();
|
||||
|
||||
[SerializeField] private string _phoneNumberMask = "+7XXXXXXXXXX";
|
||||
// [SerializeField] private string telegramLinkPrefix = "https://t.me/";
|
||||
// [SerializeField] private string whatsappLinkPrefix = "https://wa.me/";
|
||||
|
||||
private string _phoneNumber = string.Empty;
|
||||
|
||||
public void Initialize()
|
||||
public class PrizeSystemManager : MonoBehaviour
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
public static PrizeSystemManager Instance { get; private set; }
|
||||
|
||||
[Header("Настройки выдачи призов")]
|
||||
[SerializeField] private bool givePrizes = true;
|
||||
[SerializeField] private bool enableHopper = false;
|
||||
|
||||
[Header("Настройки генерации QR-кодов")]
|
||||
[SerializeField] private List<QRCodeInfo> qrCodes = new();
|
||||
private readonly List<Texture2D> generatedQRCodes = new();
|
||||
|
||||
[SerializeField] private string _phoneNumberMask = "+7XXXXXXXXXX";
|
||||
// [SerializeField] private string telegramLinkPrefix = "https://t.me/";
|
||||
// [SerializeField] private string whatsappLinkPrefix = "https://wa.me/";
|
||||
|
||||
private string _phoneNumber = string.Empty;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
Debug.Log(givePrizes
|
||||
? "Prize System Initialized: Prizes will be given."
|
||||
: "Prize System Initialized: Prizes will NOT be given.");
|
||||
|
||||
Debug.Log(enableHopper ? "Hopper is enabled for prize dispensing." : "Hopper is disabled.");
|
||||
|
||||
_phoneNumber = SettingsManager.Instance.GetCurrentGameSettings().phoneNumber;
|
||||
_phoneNumber = PhoneNumberUtils.ValidatePhoneNumber(_phoneNumber);
|
||||
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, _phoneNumberMask);
|
||||
|
||||
GenerateQRCodes();
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
Debug.Log(givePrizes
|
||||
? "Prize System Initialized: Prizes will be given."
|
||||
: "Prize System Initialized: Prizes will NOT be given.");
|
||||
|
||||
Debug.Log(enableHopper ? "Hopper is enabled for prize dispensing." : "Hopper is disabled.");
|
||||
|
||||
_phoneNumber = SettingsManager.Instance.GetCurrentGameSettings().phoneNumber;
|
||||
_phoneNumber = PhoneNumberUtils.ValidatePhoneNumber(_phoneNumber);
|
||||
_phoneNumber = PhoneNumberUtils.ApplyPhoneMask(_phoneNumber, _phoneNumberMask);
|
||||
// Если не инициализируется в GameManager, инициализировать тут
|
||||
// private void Awake()
|
||||
// {
|
||||
// Initialize();
|
||||
// }
|
||||
|
||||
GenerateQRCodes();
|
||||
}
|
||||
|
||||
// Если не инициализируется в GameManager, инициализировать тут
|
||||
// private void Awake()
|
||||
// {
|
||||
// Initialize();
|
||||
// }
|
||||
|
||||
public void GivePrize()
|
||||
{
|
||||
if (enableHopper)
|
||||
PrizeSystem.GivePrize();
|
||||
else
|
||||
Debug.Log("Hopper is disabled. Prize dispensing skipped.");
|
||||
}
|
||||
|
||||
public Texture2D[] GetQRCodes()
|
||||
{
|
||||
return generatedQRCodes.ToArray();
|
||||
}
|
||||
|
||||
private void GenerateQRCodes()
|
||||
{
|
||||
// Очищаем предыдущие QR-коды
|
||||
if (generatedQRCodes.Count > 0) generatedQRCodes.Clear();
|
||||
|
||||
foreach (QRCodeInfo data in qrCodes)
|
||||
public void GivePrize()
|
||||
{
|
||||
if (!data.generateQRCode) continue;
|
||||
string fullLink = data.GetFullLink(_phoneNumber);
|
||||
Texture2D qrTexture = QRMaker.MakeTexture(fullLink,
|
||||
8,
|
||||
true,
|
||||
QRCodeInfo.GetColor(data.darkColor),
|
||||
QRCodeInfo.GetColor(data.lightColor));
|
||||
generatedQRCodes.Add(qrTexture);
|
||||
Debug.Log($"Generated QR Code for link: {fullLink}");
|
||||
if (enableHopper)
|
||||
PrizeSystem.GivePrize();
|
||||
else
|
||||
Debug.Log("Hopper is disabled. Prize dispensing skipped.");
|
||||
}
|
||||
|
||||
public Texture2D[] GetQRCodes()
|
||||
{
|
||||
return generatedQRCodes.ToArray();
|
||||
}
|
||||
|
||||
private void GenerateQRCodes()
|
||||
{
|
||||
// Очищаем предыдущие QR-коды
|
||||
if (generatedQRCodes.Count > 0) generatedQRCodes.Clear();
|
||||
|
||||
foreach (QRCodeInfo data in qrCodes)
|
||||
{
|
||||
if (!data.generateQRCode) continue;
|
||||
string fullLink = data.GetFullLink(_phoneNumber);
|
||||
Texture2D qrTexture = QRMaker.MakeTexture(fullLink,
|
||||
8,
|
||||
true,
|
||||
QRCodeInfo.GetColor(data.darkColor),
|
||||
QRCodeInfo.GetColor(data.lightColor));
|
||||
generatedQRCodes.Add(qrTexture);
|
||||
Debug.Log($"Generated QR Code for link: {fullLink}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using RR.Common.Controls;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
@ -23,7 +24,7 @@ public class SettingsCombination : MonoBehaviour
|
||||
public Image startsTimerImage, prizeTimerImage, numberImage, telegramImage;
|
||||
|
||||
|
||||
void OnEnable()
|
||||
private void OnEnable()
|
||||
{
|
||||
gm = GameManager.Instance;
|
||||
RefreshUI();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using RR.Common.Managers;
|
||||
using UnityEngine;
|
||||
@ -9,115 +9,117 @@ using RR.Common.UI;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
public class LoseBannerController : MonoBehaviour
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
[SerializeField] private GameEndUIController bannersController;
|
||||
|
||||
[Header("Кнопки")]
|
||||
[SerializeField] private TextMeshProUGUI loseText;
|
||||
[SerializeField] private TextMeshProUGUI creditText;
|
||||
[SerializeField] private Button continueButton;
|
||||
[SerializeField] private TextMeshProUGUI continueButtonText;
|
||||
[SerializeField] private float continueButtonCountdown = 30f;
|
||||
|
||||
[Header("Тексты")]
|
||||
[SerializeField] private string creditTextFormat = "У вас осталось: {0}";
|
||||
|
||||
private PaymentManager _paymentManager;
|
||||
private Coroutine _continueCountdownCoroutine;
|
||||
|
||||
public event Action ContinueClicked;
|
||||
|
||||
private void OnEnable()
|
||||
public class LoseBannerController : MonoBehaviour
|
||||
{
|
||||
bannersController.LoseBannerShown += OnLoseBannerShown;
|
||||
|
||||
_paymentManager = GameManager.Instance.Payment;
|
||||
|
||||
if (_paymentManager != null)
|
||||
[SerializeField] private GameEndUIController bannersController;
|
||||
|
||||
[Header("Кнопки")]
|
||||
[SerializeField] private TextMeshProUGUI loseText;
|
||||
[SerializeField] private TextMeshProUGUI creditText;
|
||||
[SerializeField] private Button continueButton;
|
||||
[SerializeField] private TextMeshProUGUI continueButtonText;
|
||||
[SerializeField] private float continueButtonCountdown = 30f;
|
||||
|
||||
[Header("Тексты")]
|
||||
[SerializeField] private string creditTextFormat = "У вас осталось: {0}";
|
||||
|
||||
private PaymentManager _paymentManager;
|
||||
private Coroutine _continueCountdownCoroutine;
|
||||
|
||||
public event Action ContinueClicked;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
int credits = _paymentManager.CreditsCount;
|
||||
creditText.text = string.Format(creditTextFormat, credits);
|
||||
_paymentManager.OnCreditAdded.AddListener(UnlockContinueButton);
|
||||
}
|
||||
}
|
||||
bannersController.LoseBannerShown += OnLoseBannerShown;
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
bannersController.LoseBannerShown -= OnLoseBannerShown;
|
||||
// Остановим корутину, если объект деактивируется
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
if (_paymentManager != null)
|
||||
{
|
||||
_paymentManager.OnCreditAdded.RemoveListener(UnlockContinueButton);
|
||||
}
|
||||
}
|
||||
_paymentManager = GameManager.Instance.Payment;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (continueButton) continueButton.onClick.AddListener(OnContinueClicked);
|
||||
|
||||
// Инициализируем видимость/интерактивность кнопок в безопасное состояние
|
||||
if (continueButton) continueButton.gameObject.SetActive(false);
|
||||
if (continueButton && continueButtonText) continueButtonText.text = string.Empty;
|
||||
}
|
||||
|
||||
private void OnLoseBannerShown(float gameTime)
|
||||
{
|
||||
// EventSystem.current.firstSelectedGameObject = continueButton.gameObject;
|
||||
EventSystem.current.SetSelectedGameObject(continueButton.gameObject);
|
||||
|
||||
loseText.text = $"Время игры: {GameEndUIController.FormatTime(gameTime)}";
|
||||
creditText.text = string.Format(creditTextFormat, _paymentManager.CreditsCount);
|
||||
|
||||
if (continueButtonCountdown <= 0f)
|
||||
{
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
if (_paymentManager != null)
|
||||
{
|
||||
int credits = _paymentManager.CreditsCount;
|
||||
creditText.text = string.Format(creditTextFormat, credits);
|
||||
_paymentManager.OnCreditAdded.AddListener(UnlockContinueButton);
|
||||
}
|
||||
}
|
||||
|
||||
if (continueButton)
|
||||
private void OnDisable()
|
||||
{
|
||||
continueButton.gameObject.SetActive(true);
|
||||
continueButton.interactable = _paymentManager.CreditsCount > 0;
|
||||
bannersController.LoseBannerShown -= OnLoseBannerShown;
|
||||
// Остановим корутину, если объект деактивируется
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
if (_paymentManager != null)
|
||||
{
|
||||
_paymentManager.OnCreditAdded.RemoveListener(UnlockContinueButton);
|
||||
}
|
||||
}
|
||||
|
||||
// Остановим предыдущую корутину, если есть
|
||||
if (_continueCountdownCoroutine != null)
|
||||
private void Awake()
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
if (continueButton) continueButton.onClick.AddListener(OnContinueClicked);
|
||||
|
||||
// Инициализируем видимость/интерактивность кнопок в безопасное состояние
|
||||
if (continueButton) continueButton.gameObject.SetActive(false);
|
||||
if (continueButton && continueButtonText) continueButtonText.text = string.Empty;
|
||||
}
|
||||
|
||||
_continueCountdownCoroutine = StartCoroutine(ContinueCountdownCoroutine(continueButtonCountdown));
|
||||
}
|
||||
private void OnLoseBannerShown(float gameTime)
|
||||
{
|
||||
// EventSystem.current.firstSelectedGameObject = continueButton.gameObject;
|
||||
EventSystem.current.SetSelectedGameObject(continueButton.gameObject);
|
||||
|
||||
private IEnumerator ContinueCountdownCoroutine(float duration)
|
||||
{
|
||||
|
||||
float elapsed = 0f;
|
||||
// Обновляем сразу первоначальный текст
|
||||
int initialSeconds = Mathf.RoundToInt(duration);
|
||||
UpdateContinueText(initialSeconds);
|
||||
loseText.text = $"Время игры: {GameEndUIController.FormatTime(gameTime)}";
|
||||
creditText.text = string.Format(creditTextFormat, _paymentManager.CreditsCount);
|
||||
|
||||
if (continueButtonCountdown <= 0f)
|
||||
{
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
}
|
||||
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.gameObject.SetActive(true);
|
||||
continueButton.interactable = _paymentManager.CreditsCount > 0;
|
||||
}
|
||||
|
||||
// Остановим предыдущую корутину, если есть
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
_continueCountdownCoroutine = StartCoroutine(ContinueCountdownCoroutine(continueButtonCountdown));
|
||||
}
|
||||
|
||||
private IEnumerator ContinueCountdownCoroutine(float duration)
|
||||
{
|
||||
|
||||
float elapsed = 0f;
|
||||
// Обновляем сразу первоначальный текст
|
||||
int initialSeconds = Mathf.RoundToInt(duration);
|
||||
UpdateContinueText(initialSeconds);
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
// Using yield return null and manual timing with unscaled time for more reliable updates
|
||||
float startTime = Time.unscaledTime;
|
||||
yield return null;
|
||||
|
||||
|
||||
// Wait for approximately 1 second using unscaled time
|
||||
while (Time.unscaledTime - startTime < 1.0f)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
|
||||
elapsed += 1f;
|
||||
int remaining = Mathf.Max(0, Mathf.RoundToInt(duration - elapsed));
|
||||
UpdateContinueText(remaining);
|
||||
@ -130,43 +132,44 @@ public class LoseBannerController : MonoBehaviour
|
||||
GameManager.Instance.LoadScene("MainMenu", false);
|
||||
}
|
||||
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
private void UpdateContinueText(int secondsRemaining)
|
||||
{
|
||||
if (continueButtonText)
|
||||
continueButtonText.text = $"Продолжить? ({secondsRemaining})";
|
||||
}
|
||||
|
||||
private void UnlockContinueButton(int newCredits)
|
||||
{
|
||||
if (_paymentManager.IsFreePlayEnabled() || SettingsManager.Instance.GetCurrentGameSettings().isFreeGame)
|
||||
{
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.interactable = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.interactable = _paymentManager.CreditsCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnContinueClicked()
|
||||
{
|
||||
// При клике на "Продолжить" — остановим таймер и продолжим игру
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.ContinueGame();
|
||||
|
||||
private void UpdateContinueText(int secondsRemaining)
|
||||
{
|
||||
if (continueButtonText)
|
||||
continueButtonText.text = $"Продолжить? ({secondsRemaining})";
|
||||
}
|
||||
|
||||
private void UnlockContinueButton(int newCredits)
|
||||
{
|
||||
if (_paymentManager.IsFreePlayEnabled() || SettingsManager.Instance.GetCurrentGameSettings().isFreeGame)
|
||||
{
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.interactable = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (continueButton)
|
||||
{
|
||||
continueButton.interactable = _paymentManager.CreditsCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnContinueClicked()
|
||||
{
|
||||
// При клике на "Продолжить" — остановим таймер и продолжим игру
|
||||
if (_continueCountdownCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continueCountdownCoroutine);
|
||||
_continueCountdownCoroutine = null;
|
||||
}
|
||||
|
||||
ContinueClicked?.Invoke();
|
||||
GameManager.Instance.SetPause(false);
|
||||
GameManager.Instance.ContinueGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,56 +1,60 @@
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class QRCodeUIController : MonoBehaviour
|
||||
namespace RR.Common.UI
|
||||
{
|
||||
[SerializeField] private Transform content;
|
||||
|
||||
private PrizeSystemManager _prizeSystemManager;
|
||||
private GameObject[] _qrCodes;
|
||||
|
||||
private void Start()
|
||||
public class QRCodeUIController : MonoBehaviour
|
||||
{
|
||||
_prizeSystemManager = PrizeSystemManager.Instance;
|
||||
[SerializeField] private Transform content;
|
||||
|
||||
GameManager.Instance.GameWonEvent += AddQRCodes;
|
||||
}
|
||||
private PrizeSystemManager _prizeSystemManager;
|
||||
private GameObject[] _qrCodes;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
GameManager.Instance.GameWonEvent -= AddQRCodes;
|
||||
}
|
||||
|
||||
private void AddQRCodes(float _)
|
||||
{
|
||||
if (_qrCodes != null) ClearQRCodes();
|
||||
|
||||
Texture2D[] qrCodesImages = _prizeSystemManager.GetQRCodes();
|
||||
_qrCodes = new GameObject[qrCodesImages.Length];
|
||||
|
||||
for (int i = 0; i < qrCodesImages.Length; i++)
|
||||
private void Start()
|
||||
{
|
||||
_qrCodes[i] = new GameObject("QRCode_" + i, typeof(RectTransform), typeof(Image));
|
||||
_qrCodes[i].transform.SetParent(content, false);
|
||||
|
||||
Sprite sprite = Sprite.Create(qrCodesImages[i],
|
||||
new Rect(0, 0, qrCodesImages[i].width,
|
||||
qrCodesImages[i].height),
|
||||
new Vector2(0.5f, 0.5f));
|
||||
|
||||
_qrCodes[i].GetComponent<Image>().sprite = sprite;
|
||||
_prizeSystemManager = PrizeSystemManager.Instance;
|
||||
|
||||
GameManager.Instance.GameWonEvent += AddQRCodes;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
GameManager.Instance.GameWonEvent -= AddQRCodes;
|
||||
}
|
||||
|
||||
private void AddQRCodes(float _)
|
||||
{
|
||||
if (_qrCodes != null) ClearQRCodes();
|
||||
|
||||
Texture2D[] qrCodesImages = _prizeSystemManager.GetQRCodes();
|
||||
_qrCodes = new GameObject[qrCodesImages.Length];
|
||||
|
||||
for (int i = 0; i < qrCodesImages.Length; i++)
|
||||
{
|
||||
_qrCodes[i] = new GameObject("QRCode_" + i, typeof(RectTransform), typeof(Image));
|
||||
_qrCodes[i].transform.SetParent(content, false);
|
||||
|
||||
Sprite sprite = Sprite.Create(qrCodesImages[i],
|
||||
new Rect(0, 0, qrCodesImages[i].width,
|
||||
qrCodesImages[i].height),
|
||||
new Vector2(0.5f, 0.5f));
|
||||
|
||||
_qrCodes[i].GetComponent<Image>().sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearQRCodes()
|
||||
{
|
||||
if (_qrCodes == null) return;
|
||||
|
||||
foreach (GameObject qrCode in _qrCodes)
|
||||
{
|
||||
Destroy(qrCode);
|
||||
}
|
||||
|
||||
_qrCodes = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearQRCodes()
|
||||
{
|
||||
if (_qrCodes == null) return;
|
||||
|
||||
foreach (GameObject qrCode in _qrCodes)
|
||||
{
|
||||
Destroy(qrCode);
|
||||
}
|
||||
|
||||
_qrCodes = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,78 +1,81 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
public class AutoCursorHider : MonoBehaviour
|
||||
namespace RR.Common.Utility
|
||||
{
|
||||
[Header("Поведение")]
|
||||
[Tooltip("Сколько секунд после движения мыши держать курсор видимым")]
|
||||
public float mouseVisibleHold = 1.0f;
|
||||
|
||||
[Tooltip("Блокировать ли курсор, когда он скрыт (Locked)") ]
|
||||
public bool lockWhenHidden = false;
|
||||
|
||||
[Tooltip("Начинать со скрытого курсора")]
|
||||
public bool startHidden = true;
|
||||
|
||||
private float _lastMouseMoveTime = -999f;
|
||||
private bool _visible;
|
||||
|
||||
private void Awake()
|
||||
public class AutoCursorHider : MonoBehaviour
|
||||
{
|
||||
SetCursorVisible(!startHidden);
|
||||
[Header("Поведение")]
|
||||
[Tooltip("Сколько секунд после движения мыши держать курсор видимым")]
|
||||
public float mouseVisibleHold = 1.0f;
|
||||
|
||||
[Tooltip("Блокировать ли курсор, когда он скрыт (Locked)")]
|
||||
public bool lockWhenHidden = false;
|
||||
|
||||
[Tooltip("Начинать со скрытого курсора")]
|
||||
public bool startHidden = true;
|
||||
|
||||
private float _lastMouseMoveTime = -999f;
|
||||
private bool _visible;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SetCursorVisible(!startHidden);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
bool mouseMoved = WasMouseMovedOrClickedThisFrame();
|
||||
if (mouseMoved)
|
||||
_lastMouseMoveTime = Time.unscaledTime;
|
||||
|
||||
bool nonPointerInput = WasKeyboardOrGamepadThisFrame();
|
||||
|
||||
// Показать курсор сразу при движении мыши
|
||||
if (mouseMoved && !_visible)
|
||||
SetCursorVisible(true);
|
||||
|
||||
// Скрыть курсор при управлении клавишами/геймпадом
|
||||
if (nonPointerInput && _visible)
|
||||
SetCursorVisible(false);
|
||||
|
||||
// Если мышь не трогали какое-то время — можно снова спрятать (опционально)
|
||||
if (_visible && !mouseMoved && (Time.unscaledTime - _lastMouseMoveTime) > mouseVisibleHold && nonPointerInput)
|
||||
SetCursorVisible(false);
|
||||
}
|
||||
|
||||
private void SetCursorVisible(bool show)
|
||||
{
|
||||
if (_visible == show) return;
|
||||
_visible = show;
|
||||
Cursor.visible = show;
|
||||
Cursor.lockState = show ? CursorLockMode.None
|
||||
: (lockWhenHidden ? CursorLockMode.Locked : CursorLockMode.None);
|
||||
// Совет: для UI-меню лучше Confined вместо Locked, если хочется скрыть не блокируя:
|
||||
// Cursor.lockState = show ? CursorLockMode.None : CursorLockMode.Confined;
|
||||
}
|
||||
|
||||
private static bool WasMouseMovedOrClickedThisFrame()
|
||||
{
|
||||
if (Mouse.current == null) return false;
|
||||
if (Mouse.current.delta.ReadValue().sqrMagnitude > 0) return true;
|
||||
if (Mouse.current.scroll.ReadValue().y != 0) return true;
|
||||
return Mouse.current.leftButton.wasPressedThisFrame ||
|
||||
Mouse.current.rightButton.wasPressedThisFrame ||
|
||||
Mouse.current.middleButton.wasPressedThisFrame;
|
||||
}
|
||||
|
||||
private static bool WasKeyboardOrGamepadThisFrame()
|
||||
{
|
||||
bool kb = Keyboard.current != null && Keyboard.current.anyKey.isPressed;
|
||||
bool gp = Gamepad.current != null && Gamepad.current.allControls.Any(c => c is ButtonControl { wasPressedThisFrame: true });
|
||||
return kb || gp;
|
||||
}
|
||||
|
||||
// Публичные методы, если нужно управлять вручную
|
||||
public void ForceShowCursor() => SetCursorVisible(true);
|
||||
public void ForceHideCursor() => SetCursorVisible(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
bool mouseMoved = WasMouseMovedOrClickedThisFrame();
|
||||
if (mouseMoved)
|
||||
_lastMouseMoveTime = Time.unscaledTime;
|
||||
|
||||
bool nonPointerInput = WasKeyboardOrGamepadThisFrame();
|
||||
|
||||
// Показать курсор сразу при движении мыши
|
||||
if (mouseMoved && !_visible)
|
||||
SetCursorVisible(true);
|
||||
|
||||
// Скрыть курсор при управлении клавишами/геймпадом
|
||||
if (nonPointerInput && _visible)
|
||||
SetCursorVisible(false);
|
||||
|
||||
// Если мышь не трогали какое-то время — можно снова спрятать (опционально)
|
||||
if (_visible && !mouseMoved && (Time.unscaledTime - _lastMouseMoveTime) > mouseVisibleHold && nonPointerInput)
|
||||
SetCursorVisible(false);
|
||||
}
|
||||
|
||||
private void SetCursorVisible(bool show)
|
||||
{
|
||||
if (_visible == show) return;
|
||||
_visible = show;
|
||||
Cursor.visible = show;
|
||||
Cursor.lockState = show ? CursorLockMode.None
|
||||
: (lockWhenHidden ? CursorLockMode.Locked : CursorLockMode.None);
|
||||
// Совет: для UI-меню лучше Confined вместо Locked, если хочется скрыть не блокируя:
|
||||
// Cursor.lockState = show ? CursorLockMode.None : CursorLockMode.Confined;
|
||||
}
|
||||
|
||||
private static bool WasMouseMovedOrClickedThisFrame()
|
||||
{
|
||||
if (Mouse.current == null) return false;
|
||||
if (Mouse.current.delta.ReadValue().sqrMagnitude > 0) return true;
|
||||
if (Mouse.current.scroll.ReadValue().y != 0) return true;
|
||||
return Mouse.current.leftButton.wasPressedThisFrame ||
|
||||
Mouse.current.rightButton.wasPressedThisFrame ||
|
||||
Mouse.current.middleButton.wasPressedThisFrame;
|
||||
}
|
||||
|
||||
private static bool WasKeyboardOrGamepadThisFrame()
|
||||
{
|
||||
bool kb = Keyboard.current != null && Keyboard.current.anyKey.isPressed;
|
||||
bool gp = Gamepad.current != null && Gamepad.current.allControls.Any(c => c is ButtonControl { wasPressedThisFrame: true });
|
||||
return kb || gp;
|
||||
}
|
||||
|
||||
// Публичные методы, если нужно управлять вручную
|
||||
public void ForceShowCursor() => SetCursorVisible(true);
|
||||
public void ForceHideCursor() => SetCursorVisible(false);
|
||||
}
|
||||
}
|
||||
@ -1,354 +1,357 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
public class CompositeLogger : ILogger, IDisposable
|
||||
namespace RR.Common.Utility
|
||||
{
|
||||
public ILogHandler logHandler { get; set; }
|
||||
public bool logEnabled { get; set; }
|
||||
public LogType filterLogType { get; set; }
|
||||
|
||||
private ILogger _oldLogger;
|
||||
private readonly List<ILogger> _additiveLoggers = new();
|
||||
public class CompositeLogger : ILogger, IDisposable
|
||||
{
|
||||
public ILogHandler logHandler { get; set; }
|
||||
public bool logEnabled { get; set; }
|
||||
public LogType filterLogType { get; set; }
|
||||
|
||||
public void Initialize(ILogger additiveLogger = null)
|
||||
{
|
||||
_oldLogger = Debug.unityLogger;
|
||||
|
||||
logHandler = _oldLogger.logHandler;
|
||||
logEnabled = true;
|
||||
filterLogType = _oldLogger.filterLogType;
|
||||
|
||||
if (additiveLogger != null) _additiveLoggers.Add(additiveLogger);
|
||||
}
|
||||
|
||||
public void AddLogger(ILogger additiveLogger)
|
||||
{
|
||||
if (additiveLogger == null)
|
||||
throw new ArgumentNullException(nameof(additiveLogger));
|
||||
|
||||
_additiveLoggers.Add(additiveLogger);
|
||||
}
|
||||
|
||||
public void AddLoggers(params ILogger[] additiveLoggers)
|
||||
{
|
||||
if (additiveLoggers == null)
|
||||
throw new ArgumentNullException(nameof(additiveLoggers));
|
||||
|
||||
foreach (var logger in additiveLoggers)
|
||||
private ILogger _oldLogger;
|
||||
private readonly List<ILogger> _additiveLoggers = new();
|
||||
|
||||
public void Initialize(ILogger additiveLogger = null)
|
||||
{
|
||||
if (logger != null)
|
||||
_additiveLoggers.Add(logger);
|
||||
_oldLogger = Debug.unityLogger;
|
||||
|
||||
logHandler = _oldLogger.logHandler;
|
||||
logEnabled = true;
|
||||
filterLogType = _oldLogger.filterLogType;
|
||||
|
||||
if (additiveLogger != null) _additiveLoggers.Add(additiveLogger);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveLogger(ILogger additiveLogger)
|
||||
{
|
||||
_additiveLoggers.Remove(additiveLogger);
|
||||
}
|
||||
|
||||
public void RemoveLoggers(params ILogger[] additiveLoggers)
|
||||
{
|
||||
_additiveLoggers.RemoveAll(additiveLoggers.Contains);
|
||||
}
|
||||
|
||||
public bool IsLogTypeAllowed(LogType logType)
|
||||
{
|
||||
return _oldLogger.IsLogTypeAllowed(logType) || _additiveLoggers.Any(logger => logger.IsLogTypeAllowed(logType));
|
||||
}
|
||||
|
||||
#region Ilogger Implementation
|
||||
|
||||
public void LogFormat(LogType logType, string format, params object[] args)
|
||||
{
|
||||
_oldLogger.LogFormat(logType, format, args);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
public void AddLogger(ILogger additiveLogger)
|
||||
{
|
||||
try
|
||||
if (additiveLogger == null)
|
||||
throw new ArgumentNullException(nameof(additiveLogger));
|
||||
|
||||
_additiveLoggers.Add(additiveLogger);
|
||||
}
|
||||
|
||||
public void AddLoggers(params ILogger[] additiveLoggers)
|
||||
{
|
||||
if (additiveLoggers == null)
|
||||
throw new ArgumentNullException(nameof(additiveLoggers));
|
||||
|
||||
foreach (var logger in additiveLoggers)
|
||||
{
|
||||
additiveLogger.LogFormat(logType, format, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
if (logger != null)
|
||||
_additiveLoggers.Add(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogFormat(LogType logType, Object context, string format, params object[] args)
|
||||
{
|
||||
_oldLogger.LogFormat(logType, context, format, args);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
public void RemoveLogger(ILogger additiveLogger)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogFormat(logType, context, format, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
_additiveLoggers.Remove(additiveLogger);
|
||||
}
|
||||
}
|
||||
|
||||
public void LogException(Exception exception)
|
||||
{
|
||||
_oldLogger.LogException(exception);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
public void RemoveLoggers(params ILogger[] additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogException(exception);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
_additiveLoggers.RemoveAll(additiveLoggers.Contains);
|
||||
}
|
||||
}
|
||||
|
||||
public void LogException(Exception exception, Object context)
|
||||
{
|
||||
_oldLogger.LogException(exception, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
public bool IsLogTypeAllowed(LogType logType)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogException(exception, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
return _oldLogger.IsLogTypeAllowed(logType) || _additiveLoggers.Any(logger => logger.IsLogTypeAllowed(logType));
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(object message)
|
||||
{
|
||||
_oldLogger.Log(message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
#region Ilogger Implementation
|
||||
|
||||
public void LogFormat(LogType logType, string format, params object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, object message)
|
||||
{
|
||||
_oldLogger.Log(logType, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, object message, GameObject gameObject)
|
||||
{
|
||||
_oldLogger.Log(logType, message, gameObject);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, message, gameObject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, object message, Object context)
|
||||
{
|
||||
_oldLogger.Log(logType, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.Log(logType, tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, string tag, object message)
|
||||
{
|
||||
_oldLogger.Log(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(string tag, object message)
|
||||
{
|
||||
_oldLogger.Log(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.Log(tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWarning(string tag, object message)
|
||||
{
|
||||
_oldLogger.LogWarning(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogWarning(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWarning(string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.LogWarning(tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogWarning(tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogError(string tag, object message)
|
||||
{
|
||||
_oldLogger.LogError(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogError(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogError(string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.LogError(tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogError(tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing) return;
|
||||
|
||||
lock (_additiveLoggers)
|
||||
{
|
||||
foreach (ILogger additiveLogger in _additiveLoggers.ToArray())
|
||||
_oldLogger.LogFormat(logType, format, args);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (additiveLogger is IDisposable disposableLogger)
|
||||
disposableLogger.Dispose();
|
||||
additiveLogger.LogFormat(logType, format, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { _oldLogger?.LogException(ex); } catch { /* ignore */ }
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
_additiveLoggers.Clear();
|
||||
}
|
||||
|
||||
public void LogFormat(LogType logType, Object context, string format, params object[] args)
|
||||
{
|
||||
_oldLogger.LogFormat(logType, context, format, args);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogFormat(logType, context, format, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogException(Exception exception)
|
||||
{
|
||||
_oldLogger.LogException(exception);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogException(exception);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogException(Exception exception, Object context)
|
||||
{
|
||||
_oldLogger.LogException(exception, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogException(exception, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(object message)
|
||||
{
|
||||
_oldLogger.Log(message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, object message)
|
||||
{
|
||||
_oldLogger.Log(logType, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, object message, GameObject gameObject)
|
||||
{
|
||||
_oldLogger.Log(logType, message, gameObject);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, message, gameObject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, object message, Object context)
|
||||
{
|
||||
_oldLogger.Log(logType, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.Log(logType, tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(logType, tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(LogType logType, string tag, object message)
|
||||
{
|
||||
_oldLogger.Log(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(string tag, object message)
|
||||
{
|
||||
_oldLogger.Log(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.Log(tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.Log(tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWarning(string tag, object message)
|
||||
{
|
||||
_oldLogger.LogWarning(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogWarning(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWarning(string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.LogWarning(tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogWarning(tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogError(string tag, object message)
|
||||
{
|
||||
_oldLogger.LogError(tag, message);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogError(tag, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogError(string tag, object message, Object context)
|
||||
{
|
||||
_oldLogger.LogError(tag, message, context);
|
||||
foreach (ILogger additiveLogger in _additiveLoggers)
|
||||
{
|
||||
try
|
||||
{
|
||||
additiveLogger.LogError(tag, message, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_oldLogger.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing) return;
|
||||
|
||||
lock (_additiveLoggers)
|
||||
{
|
||||
foreach (ILogger additiveLogger in _additiveLoggers.ToArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (additiveLogger is IDisposable disposableLogger)
|
||||
disposableLogger.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { _oldLogger?.LogException(ex); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
_additiveLoggers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
~CompositeLogger()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
~CompositeLogger()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@ -6,19 +6,22 @@ using UnityEditor;
|
||||
|
||||
#endif
|
||||
|
||||
public class ReadOnlyInInspectorAttribute : PropertyAttribute {}
|
||||
namespace RR.Common.Utilities
|
||||
{
|
||||
public class ReadOnlyInInspectorAttribute : PropertyAttribute { }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyInInspectorAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
=> EditorGUI.GetPropertyHeight(property, label, true);
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyInInspectorAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
GUI.enabled = false; EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true;
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
=> EditorGUI.GetPropertyHeight(property, label, true);
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
GUI.enabled = false; EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -697,7 +697,7 @@ PlayerSettings:
|
||||
framebufferDepthMemorylessMode: 0
|
||||
qualitySettingsNames: []
|
||||
projectName: Mario-like platformer
|
||||
organizationId:
|
||||
organizationId: dis_bro
|
||||
cloudEnabled: 0
|
||||
legacyClampBlendShapeWeights: 0
|
||||
hmiLoadingImage: {fileID: 0}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user