Added more music and sounds; started COMInput project import
This commit is contained in:
parent
2d1cc90446
commit
0f9c407d70
1939
Assets/Animations/Crab/Death.anim
Normal file
1939
Assets/Animations/Crab/Death.anim
Normal file
File diff suppressed because it is too large
Load Diff
8
Assets/Animations/Crab/Death.anim.meta
Normal file
8
Assets/Animations/Crab/Death.anim.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4b01536b2516e14b83e7bd7f6758e2d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -67,6 +67,9 @@ AnimatorStateMachine:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -3373467894423064931}
|
||||
m_Position: {x: 260, y: 190, z: 0}
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -492021373417904795}
|
||||
m_Position: {x: 490, y: 190, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
@ -104,6 +107,32 @@ AnimatorState:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1102 &-492021373417904795
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Death
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: d4b01536b2516e14b83e7bd7f6758e2d, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
Assets/Plugins.meta
Normal file
8
Assets/Plugins.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b223d89225f6c7346be5e14dc720ac2f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/x86_64.meta
Normal file
8
Assets/Plugins/x86_64.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0799ce7bda4b5f418bec56c93a1b5e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/x86_64/ComNative.dll
Normal file
BIN
Assets/Plugins/x86_64/ComNative.dll
Normal file
Binary file not shown.
2
Assets/Plugins/x86_64/ComNative.dll.meta
Normal file
2
Assets/Plugins/x86_64/ComNative.dll.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 265471db36c70b54180317c720985aad
|
||||
@ -390,6 +390,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 16ac12688a958d34983f6f0307a045ad, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
sfxAsset: {fileID: 11400000, guid: a5f478919b32fe34a92c9528c43ed141, type: 2}
|
||||
config: {fileID: 11400000, guid: d8fdc5ccf56a1ca4389e7927d0b86526, type: 2}
|
||||
--- !u!114 &6533472671113616431
|
||||
MonoBehaviour:
|
||||
|
||||
52
Assets/Prefabs/Utilities/Music.prefab
Normal file
52
Assets/Prefabs/Utilities/Music.prefab
Normal file
@ -0,0 +1,52 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &4019462856715984920
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7680842389916411230}
|
||||
- component: {fileID: 361486735828258013}
|
||||
m_Layer: 0
|
||||
m_Name: Music
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7680842389916411230
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4019462856715984920}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 220.89835, y: 44.50611, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &361486735828258013
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4019462856715984920}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 66bc203e34559bc48a35861084992cf5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: '::'
|
||||
audioManager: {fileID: 0}
|
||||
musicClip: {fileID: 8300000, guid: 3a30c29718ace1c468575da20ba204f1, type: 3}
|
||||
musicVolume: 0.2
|
||||
loop: 1
|
||||
useCrossfade: 1
|
||||
fadeDuration: 3
|
||||
7
Assets/Prefabs/Utilities/Music.prefab.meta
Normal file
7
Assets/Prefabs/Utilities/Music.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cee1e552b11c5844aca4d1e755b65f8
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/SFX/Enemies/Crab_Click.wav
Normal file
BIN
Assets/SFX/Enemies/Crab_Click.wav
Normal file
Binary file not shown.
23
Assets/SFX/Enemies/Crab_Click.wav.meta
Normal file
23
Assets/SFX/Enemies/Crab_Click.wav.meta
Normal file
@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aeba847740e70ba46b7d3df9513316e1
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/SFX/Music/music_Level_2.mp3
Normal file
BIN
Assets/SFX/Music/music_Level_2.mp3
Normal file
Binary file not shown.
23
Assets/SFX/Music/music_Level_2.mp3.meta
Normal file
23
Assets/SFX/Music/music_Level_2.mp3.meta
Normal file
@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff6aa1560276fae488e89d642928a57a
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/SFX/Props/Checkpoint.wav
Normal file
BIN
Assets/SFX/Props/Checkpoint.wav
Normal file
Binary file not shown.
23
Assets/SFX/Props/Checkpoint.wav.meta
Normal file
23
Assets/SFX/Props/Checkpoint.wav.meta
Normal file
@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfbbacd3ef3f01743aa42aea3cb29270
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@ -119,56 +119,6 @@ NavMeshSettings:
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &223226
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 223228}
|
||||
- component: {fileID: 223227}
|
||||
m_Layer: 0
|
||||
m_Name: Music
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &223227
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 223226}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 66bc203e34559bc48a35861084992cf5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: '::'
|
||||
audioManager: {fileID: 0}
|
||||
musicClip: {fileID: 8300000, guid: 3a30c29718ace1c468575da20ba204f1, type: 3}
|
||||
musicVolume: 0.2
|
||||
loop: 1
|
||||
useCrossfade: 1
|
||||
fadeDuration: 3
|
||||
--- !u!4 &223228
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 223226}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 220.89835, y: 44.50611, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!60 &2920184 stripped
|
||||
PolygonCollider2D:
|
||||
m_CorrespondingSourceObject: {fileID: 8872464155886732166, guid: 67e6c76bc4c4797499f5b2a55fc7ab4a, type: 3}
|
||||
@ -118031,6 +117981,7 @@ Transform:
|
||||
- {fileID: 1226189837}
|
||||
- {fileID: 94085880}
|
||||
- {fileID: 105165846}
|
||||
- {fileID: 1528420254}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &1054736783
|
||||
@ -186239,6 +186190,141 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
|
||||
--- !u!1 &1528420253
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1528420254}
|
||||
- component: {fileID: 1528420257}
|
||||
- component: {fileID: 1528420256}
|
||||
- component: {fileID: 1528420255}
|
||||
m_Layer: 0
|
||||
m_Name: CheckpointFX
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1528420254
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1528420253}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1053302775}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &1528420255
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1528420253}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: edb963f0614ea6e4c88c887b3ef08ba6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.FX.CheckPointFX
|
||||
checkPointVFX: {fileID: 1528420257}
|
||||
checkpointSFX:
|
||||
sound: {fileID: 8300000, guid: cfbbacd3ef3f01743aa42aea3cb29270, type: 3}
|
||||
volume: 0.25
|
||||
pitch: 1.2
|
||||
--- !u!73398921 &1528420256
|
||||
VFXRenderer:
|
||||
serializedVersion: 1
|
||||
m_ObjectHideFlags: 2
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1528420253}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 0
|
||||
m_LightProbeUsage: 0
|
||||
m_ReflectionProbeUsage: 0
|
||||
m_RayTracingMode: 0
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
--- !u!2083052967 &1528420257
|
||||
VisualEffect:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1528420253}
|
||||
m_Enabled: 1
|
||||
m_Asset: {fileID: 8926484042661614526, guid: d3210933b376d454996980c907be3169, type: 3}
|
||||
m_InitialEventName: OnPlay
|
||||
m_InitialEventNameOverriden: 0
|
||||
m_StartSeed: 0
|
||||
m_ResetSeedOnPlay: 1
|
||||
m_AllowInstancing: 1
|
||||
m_ResourceVersion: 1
|
||||
m_PropertySheet:
|
||||
m_Float:
|
||||
m_Array: []
|
||||
m_Vector2f:
|
||||
m_Array: []
|
||||
m_Vector3f:
|
||||
m_Array: []
|
||||
m_Vector4f:
|
||||
m_Array: []
|
||||
m_Uint:
|
||||
m_Array: []
|
||||
m_Int:
|
||||
m_Array: []
|
||||
m_Matrix4x4f:
|
||||
m_Array: []
|
||||
m_AnimationCurve:
|
||||
m_Array: []
|
||||
m_Gradient:
|
||||
m_Array: []
|
||||
m_NamedObject:
|
||||
m_Array: []
|
||||
m_Bool:
|
||||
m_Array: []
|
||||
--- !u!1 &1529754408
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -345178,8 +345264,8 @@ SpriteRenderer:
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 981438795
|
||||
m_SortingLayer: 2
|
||||
m_SortingLayerID: 1712314021
|
||||
m_SortingLayer: -2
|
||||
m_SortingOrder: 3
|
||||
m_Sprite: {fileID: 6342536824791490674, guid: 0919aa77dd2e3ac44b66d8373b3efded, type: 3}
|
||||
m_Color: {r: 0.6698113, g: 0.6698113, b: 0.6698113, a: 1}
|
||||
@ -383092,6 +383178,21 @@ SpriteRenderer:
|
||||
m_WasSpriteAssigned: 1
|
||||
m_MaskInteraction: 0
|
||||
m_SpriteSortPoint: 0
|
||||
--- !u!4 &2208544354061546943
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4898093653917724921}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 220.89835, y: 44.50611, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &2698812485863897060
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -383327,6 +383428,23 @@ GameObject:
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!1 &4898093653917724921
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2208544354061546943}
|
||||
- component: {fileID: 8157642162628828220}
|
||||
m_Layer: 0
|
||||
m_Name: Music
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!212 &4980034301103874201
|
||||
SpriteRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -384312,6 +384430,24 @@ SpriteRenderer:
|
||||
m_WasSpriteAssigned: 1
|
||||
m_MaskInteraction: 0
|
||||
m_SpriteSortPoint: 0
|
||||
--- !u!114 &8157642162628828220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4898093653917724921}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 66bc203e34559bc48a35861084992cf5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: '::'
|
||||
audioManager: {fileID: 0}
|
||||
musicClip: {fileID: 8300000, guid: 3a30c29718ace1c468575da20ba204f1, type: 3}
|
||||
musicVolume: 0.2
|
||||
loop: 1
|
||||
useCrossfade: 1
|
||||
fadeDuration: 3
|
||||
--- !u!1 &8278861871892717312
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -384584,4 +384720,4 @@ SceneRoots:
|
||||
- {fileID: 5189102261111869253}
|
||||
- {fileID: 523566710}
|
||||
- {fileID: 283858835}
|
||||
- {fileID: 223228}
|
||||
- {fileID: 2208544354061546943}
|
||||
|
||||
@ -5560,6 +5560,67 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 4535171522523579101, guid: 6ab87d9d9d9f20e4c86df42ae805f6f4, type: 3}
|
||||
m_PrefabInstance: {fileID: 27567628}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &593214697
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 361486735828258013, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: musicClip
|
||||
value:
|
||||
objectReference: {fileID: 8300000, guid: ff6aa1560276fae488e89d642928a57a, type: 3}
|
||||
- target: {fileID: 4019462856715984920, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: Music
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 9.4576
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 1.61247
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7680842389916411230, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 9cee1e552b11c5844aca4d1e755b65f8, type: 3}
|
||||
--- !u!1001 &593988813
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -203513,3 +203574,4 @@ SceneRoots:
|
||||
- {fileID: 88089423}
|
||||
- {fileID: 2086062556}
|
||||
- {fileID: 1685954871}
|
||||
- {fileID: 593214697}
|
||||
|
||||
@ -908,11 +908,11 @@ MonoBehaviour:
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.7764706, g: 0.7764706, b: 0.7764706, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_NormalColor: {r: 0.83137256, g: 0.83137256, b: 0.83137256, a: 1}
|
||||
m_HighlightedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_PressedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_SelectedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_DisabledColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
@ -1034,7 +1034,7 @@ MonoBehaviour:
|
||||
m_HandleRect: {fileID: 434895585}
|
||||
m_Direction: 2
|
||||
m_Value: 0
|
||||
m_Size: 0.9999941
|
||||
m_Size: 0.9999981
|
||||
m_NumberOfSteps: 0
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
@ -1481,11 +1481,11 @@ MonoBehaviour:
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.7764706, g: 0.7764706, b: 0.7764706, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_NormalColor: {r: 0.83137256, g: 0.83137256, b: 0.83137256, a: 1}
|
||||
m_HighlightedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_PressedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_SelectedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_DisabledColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
@ -4306,11 +4306,11 @@ MonoBehaviour:
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.8301887, g: 0.8301887, b: 0.8301887, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.7764706, g: 0.7764706, b: 0.7764706, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_NormalColor: {r: 0.83137256, g: 0.83137256, b: 0.83137256, a: 1}
|
||||
m_HighlightedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_PressedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_SelectedColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_DisabledColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
@ -4540,7 +4540,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.000704448}
|
||||
m_AnchoredPosition: {x: 0, y: 0.0006653946}
|
||||
m_SizeDelta: {x: 0, y: 300}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &1792218764
|
||||
|
||||
@ -1,50 +1,53 @@
|
||||
using System;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Behaviour.ScriptableObjects;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
public class EnemyAnimationEvents : MonoBehaviour
|
||||
namespace RR.Game2D.Animations
|
||||
{
|
||||
private SFXAsset _stepSounds;
|
||||
private SFXAsset _hurtSounds;
|
||||
public class EnemyAnimationEvents : MonoBehaviour
|
||||
{
|
||||
private SFXAsset _stepSounds;
|
||||
private SFXAsset _hurtSounds;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnemyController controller = GetComponent<EnemyController>();
|
||||
_stepSounds = controller.sfxAsset.stepSounds;
|
||||
_hurtSounds = controller.sfxAsset.hurtSounds;
|
||||
}
|
||||
private void Awake()
|
||||
{
|
||||
EnemyController controller = GetComponent<EnemyController>();
|
||||
_stepSounds = controller.sfxAsset.stepSounds;
|
||||
_hurtSounds = controller.sfxAsset.hurtSounds;
|
||||
}
|
||||
|
||||
public void OnDeathBegin()
|
||||
{
|
||||
// Проигрывание SFX для получения урона
|
||||
if (_hurtSounds && _hurtSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_hurtSounds.soundEffects, transform.position);
|
||||
|
||||
gameObject.layer = LayerMask.NameToLayer("No Collision");
|
||||
Rigidbody2D rb = GetComponent<Rigidbody2D>();
|
||||
Collider2D col = GetComponent<Collider2D>();
|
||||
EnemyController controller = GetComponent<EnemyController>();
|
||||
EnemyFireScript fireScript = GetComponent<EnemyFireScript>();
|
||||
if (controller) controller.enabled = false;
|
||||
if (fireScript) fireScript.enabled = false;
|
||||
if (col) col.enabled = false;
|
||||
if (!rb) return;
|
||||
rb.linearVelocity = Vector2.zero;
|
||||
rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
}
|
||||
|
||||
public void OnDeathEnd()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
public void OnDeathBegin()
|
||||
{
|
||||
// Проигрывание SFX для получения урона
|
||||
if (_hurtSounds && _hurtSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_hurtSounds.soundEffects, transform.position);
|
||||
|
||||
public void Step()
|
||||
{
|
||||
// Проигрывание SFX для шага
|
||||
if (_stepSounds && _stepSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_stepSounds.soundEffects, transform.position);
|
||||
gameObject.layer = LayerMask.NameToLayer("No Collision");
|
||||
Rigidbody2D rb = GetComponent<Rigidbody2D>();
|
||||
Collider2D col = GetComponent<Collider2D>();
|
||||
EnemyController controller = GetComponent<EnemyController>();
|
||||
EnemyFireScript fireScript = GetComponent<EnemyFireScript>();
|
||||
if (controller) controller.enabled = false;
|
||||
if (fireScript) fireScript.enabled = false;
|
||||
if (col) col.enabled = false;
|
||||
if (!rb) return;
|
||||
rb.linearVelocity = Vector2.zero;
|
||||
rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
}
|
||||
|
||||
public void OnDeathEnd()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
// Проигрывание SFX для шага
|
||||
if (_stepSounds && _stepSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_stepSounds.soundEffects, transform.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,50 +1,53 @@
|
||||
using RR.Common.Managers;
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Behaviour.ScriptableObjects;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerAnimationEvents : MonoBehaviour
|
||||
namespace RR.Game2D.Animations
|
||||
{
|
||||
private SFXAsset _stepSounds;
|
||||
private SFXAsset _hurtSounds;
|
||||
private SFXAsset _jumpSounds;
|
||||
private Rigidbody2D _rb;
|
||||
public class PlayerAnimationEvents : MonoBehaviour
|
||||
{
|
||||
private SFXAsset _stepSounds;
|
||||
private SFXAsset _hurtSounds;
|
||||
private SFXAsset _jumpSounds;
|
||||
private Rigidbody2D _rb;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
PlayerController controller = GetComponent<PlayerController>();
|
||||
|
||||
_stepSounds = controller.stepSounds;
|
||||
_hurtSounds = controller.hurtSounds;
|
||||
_jumpSounds = controller.jumpSounds;
|
||||
_rb = GetComponent<Rigidbody2D>();
|
||||
}
|
||||
private void Awake()
|
||||
{
|
||||
PlayerController controller = GetComponent<PlayerController>();
|
||||
|
||||
public void OnDeathBegin()
|
||||
{
|
||||
if (_hurtSounds && _hurtSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_hurtSounds.soundEffects, transform.position);
|
||||
}
|
||||
|
||||
public void OnDeathEnd()
|
||||
{
|
||||
Animator animator = GetComponent<Animator>();
|
||||
animator.ResetTrigger("Death");
|
||||
gameObject.layer = LayerMask.NameToLayer("Default");
|
||||
_rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
GameManager.Instance.RespawnPlayer();
|
||||
}
|
||||
_stepSounds = controller.stepSounds;
|
||||
_hurtSounds = controller.hurtSounds;
|
||||
_jumpSounds = controller.jumpSounds;
|
||||
_rb = GetComponent<Rigidbody2D>();
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
if (_stepSounds && _stepSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_stepSounds.soundEffects, transform.position);
|
||||
}
|
||||
public void OnDeathBegin()
|
||||
{
|
||||
if (_hurtSounds && _hurtSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_hurtSounds.soundEffects, transform.position);
|
||||
}
|
||||
|
||||
public void Jump()
|
||||
{
|
||||
if (_jumpSounds && _jumpSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_jumpSounds.soundEffects, transform.position);
|
||||
public void OnDeathEnd()
|
||||
{
|
||||
Animator animator = GetComponent<Animator>();
|
||||
animator.ResetTrigger("Death");
|
||||
gameObject.layer = LayerMask.NameToLayer("Default");
|
||||
_rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
GameManager.Instance.RespawnPlayer();
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
if (_stepSounds && _stepSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_stepSounds.soundEffects, transform.position);
|
||||
}
|
||||
|
||||
public void Jump()
|
||||
{
|
||||
if (_jumpSounds && _jumpSounds.soundEffects.Length > 0)
|
||||
GameManager.Instance.Audio.PlayRandomSfxAt(_jumpSounds.soundEffects, transform.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,109 +5,113 @@ using Unity.Cinemachine;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public class CameraControlTrigger : MonoBehaviour
|
||||
namespace RR.Game2D.Behaviour.Camera
|
||||
{
|
||||
public CustomInspectorObjects customInspectorObjects;
|
||||
|
||||
private Collider2D _col;
|
||||
private bool _cameraPanned = false; // Флаг для отслеживания состояния
|
||||
|
||||
private void Start()
|
||||
public class CameraControlTrigger : MonoBehaviour
|
||||
{
|
||||
_col = GetComponent<Collider2D>();
|
||||
}
|
||||
public CustomInspectorObjects customInspectorObjects;
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (collision.CompareTag("Player"))
|
||||
private Collider2D _col;
|
||||
private bool _cameraPanned = false; // Флаг для отслеживания состояния
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (customInspectorObjects.panCameraOnContact && !_cameraPanned)
|
||||
{
|
||||
if (CameraManager.instance != null)
|
||||
{
|
||||
CameraManager.instance.PanCameraOnContact(customInspectorObjects.panDistance, customInspectorObjects.panTime, customInspectorObjects.panDirection, false);
|
||||
_cameraPanned = true;
|
||||
}
|
||||
}
|
||||
_col = GetComponent<Collider2D>();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D collision)
|
||||
{
|
||||
if (collision.CompareTag("Player"))
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (customInspectorObjects.panCameraOnContact && _cameraPanned)
|
||||
if (collision.CompareTag("Player"))
|
||||
{
|
||||
if (CameraManager.instance != null)
|
||||
if (customInspectorObjects.panCameraOnContact && !_cameraPanned)
|
||||
{
|
||||
CameraManager.instance.PanCameraOnContact(customInspectorObjects.panDistance, customInspectorObjects.panTime, customInspectorObjects.panDirection, true);
|
||||
_cameraPanned = false;
|
||||
if (CameraManager.instance != null)
|
||||
{
|
||||
CameraManager.instance.PanCameraOnContact(customInspectorObjects.panDistance, customInspectorObjects.panTime, customInspectorObjects.panDirection, false);
|
||||
_cameraPanned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D collision)
|
||||
{
|
||||
if (collision.CompareTag("Player"))
|
||||
{
|
||||
if (customInspectorObjects.panCameraOnContact && _cameraPanned)
|
||||
{
|
||||
if (CameraManager.instance != null)
|
||||
{
|
||||
CameraManager.instance.PanCameraOnContact(customInspectorObjects.panDistance, customInspectorObjects.panTime, customInspectorObjects.panDirection, true);
|
||||
_cameraPanned = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class CustomInspectorObjects
|
||||
{
|
||||
public bool swapCameras = false;
|
||||
public bool panCameraOnContact = false;
|
||||
[System.Serializable]
|
||||
public class CustomInspectorObjects
|
||||
{
|
||||
public bool swapCameras = false;
|
||||
public bool panCameraOnContact = false;
|
||||
|
||||
[HideInInspector] public CinemachineCamera cameraOnLeft;
|
||||
[HideInInspector] public CinemachineCamera cameraOnRight;
|
||||
[HideInInspector] public CinemachineCamera cameraOnLeft;
|
||||
[HideInInspector] public CinemachineCamera cameraOnRight;
|
||||
|
||||
[HideInInspector] public PanDirection panDirection;
|
||||
[HideInInspector] public float panDistance = 3f;
|
||||
[HideInInspector] public float panTime = 0.35f;
|
||||
}
|
||||
[HideInInspector] public PanDirection panDirection;
|
||||
[HideInInspector] public float panDistance = 3f;
|
||||
[HideInInspector] public float panTime = 0.35f;
|
||||
}
|
||||
|
||||
public enum PanDirection
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
public enum PanDirection
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CameraControlTrigger))]
|
||||
public class CustomScriptEditor : Editor
|
||||
{
|
||||
CameraControlTrigger cameraControlTrigger;
|
||||
|
||||
private void OnEnable()
|
||||
[CustomEditor(typeof(CameraControlTrigger))]
|
||||
public class CustomScriptEditor : Editor
|
||||
{
|
||||
cameraControlTrigger = (CameraControlTrigger)target;
|
||||
}
|
||||
CameraControlTrigger cameraControlTrigger;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (cameraControlTrigger.customInspectorObjects.swapCameras)
|
||||
private void OnEnable()
|
||||
{
|
||||
cameraControlTrigger.customInspectorObjects.cameraOnLeft = EditorGUILayout.ObjectField("Camera on Left", cameraControlTrigger.customInspectorObjects.cameraOnLeft,
|
||||
typeof(CinemachineCamera), true) as CinemachineCamera;
|
||||
|
||||
cameraControlTrigger.customInspectorObjects.cameraOnRight = EditorGUILayout.ObjectField("Camera on Right", cameraControlTrigger.customInspectorObjects.cameraOnRight,
|
||||
typeof(CinemachineCamera), true) as CinemachineCamera;
|
||||
cameraControlTrigger = (CameraControlTrigger)target;
|
||||
}
|
||||
|
||||
if (cameraControlTrigger.customInspectorObjects.panCameraOnContact)
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
cameraControlTrigger.customInspectorObjects.panDirection = (PanDirection)EditorGUILayout.EnumPopup("Camera Pan Direction",
|
||||
cameraControlTrigger.customInspectorObjects.panDirection);
|
||||
DrawDefaultInspector();
|
||||
|
||||
cameraControlTrigger.customInspectorObjects.panDistance = EditorGUILayout.FloatField("Pan Distance", cameraControlTrigger.customInspectorObjects.panDistance);
|
||||
cameraControlTrigger.customInspectorObjects.panTime = EditorGUILayout.FloatField("Pan Time", cameraControlTrigger.customInspectorObjects.panTime);
|
||||
}
|
||||
if (cameraControlTrigger.customInspectorObjects.swapCameras)
|
||||
{
|
||||
cameraControlTrigger.customInspectorObjects.cameraOnLeft = EditorGUILayout.ObjectField("Camera on Left", cameraControlTrigger.customInspectorObjects.cameraOnLeft,
|
||||
typeof(CinemachineCamera), true) as CinemachineCamera;
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
EditorUtility.SetDirty(cameraControlTrigger);
|
||||
cameraControlTrigger.customInspectorObjects.cameraOnRight = EditorGUILayout.ObjectField("Camera on Right", cameraControlTrigger.customInspectorObjects.cameraOnRight,
|
||||
typeof(CinemachineCamera), true) as CinemachineCamera;
|
||||
}
|
||||
|
||||
if (cameraControlTrigger.customInspectorObjects.panCameraOnContact)
|
||||
{
|
||||
cameraControlTrigger.customInspectorObjects.panDirection = (PanDirection)EditorGUILayout.EnumPopup("Camera Pan Direction",
|
||||
cameraControlTrigger.customInspectorObjects.panDirection);
|
||||
|
||||
cameraControlTrigger.customInspectorObjects.panDistance = EditorGUILayout.FloatField("Pan Distance", cameraControlTrigger.customInspectorObjects.panDistance);
|
||||
cameraControlTrigger.customInspectorObjects.panTime = EditorGUILayout.FloatField("Pan Time", cameraControlTrigger.customInspectorObjects.panTime);
|
||||
}
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
EditorUtility.SetDirty(cameraControlTrigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -3,145 +3,148 @@ using System.Collections;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
public class CameraManager : MonoBehaviour
|
||||
namespace RR.Game2D.Behaviour.Camera
|
||||
{
|
||||
public static CameraManager instance;
|
||||
public class CameraManager : MonoBehaviour
|
||||
{
|
||||
public static CameraManager instance;
|
||||
|
||||
[SerializeField] private CinemachineCamera[] _allVirtualCameras;
|
||||
[SerializeField] private CinemachineCamera[] _allVirtualCameras;
|
||||
|
||||
[SerializeField] private float _fallPanAmount = 0.25f;
|
||||
[SerializeField] private float _fallYPanTime = 0.35f;
|
||||
public float _fallSpeedYDampingChangeThreshold = -15f;
|
||||
[SerializeField] private float _fallPanAmount = 0.25f;
|
||||
[SerializeField] private float _fallYPanTime = 0.35f;
|
||||
public float _fallSpeedYDampingChangeThreshold = -15f;
|
||||
|
||||
public bool IsLerpingYDamping { get; private set; }
|
||||
public bool LerpedFromPlayerFalling { get; set; }
|
||||
public bool IsLerpingYDamping { get; private set; }
|
||||
public bool LerpedFromPlayerFalling { get; set; }
|
||||
|
||||
private Coroutine _lerpYPanCoroutine;
|
||||
private Coroutine _panCameraCoroutine;
|
||||
private Coroutine _lerpYPanCoroutine;
|
||||
private Coroutine _panCameraCoroutine;
|
||||
|
||||
private CinemachineCamera _currentCamera;
|
||||
private CinemachinePositionComposer _positionComposer;
|
||||
private CinemachineCamera _currentCamera;
|
||||
private CinemachinePositionComposer _positionComposer;
|
||||
|
||||
private float _normalYPanAmount;
|
||||
private float _normalYPanAmount;
|
||||
|
||||
private Vector2 _startingTrackedObjectOffset;
|
||||
private Vector2 _startingTrackedObjectOffset;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance == null)
|
||||
private void Awake()
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _allVirtualCameras.Length; i++)
|
||||
{
|
||||
if (_allVirtualCameras[i].enabled)
|
||||
if (instance == null)
|
||||
{
|
||||
_currentCamera = _allVirtualCameras[i];
|
||||
|
||||
_positionComposer = _currentCamera.GetComponent<CinemachinePositionComposer>();
|
||||
}
|
||||
}
|
||||
|
||||
_normalYPanAmount = _positionComposer.Damping.y;
|
||||
|
||||
_startingTrackedObjectOffset = _positionComposer.TargetOffset;
|
||||
}
|
||||
|
||||
#region Lerp Y Damping
|
||||
|
||||
public void LerpYDamping(bool isPlayerFalling)
|
||||
{
|
||||
_lerpYPanCoroutine = StartCoroutine(LerpYAction(isPlayerFalling));
|
||||
}
|
||||
|
||||
private IEnumerator LerpYAction(bool isPlayerFalling)
|
||||
{
|
||||
IsLerpingYDamping = true;
|
||||
|
||||
float startDampAmount = _positionComposer.Damping.y;
|
||||
float endDampAmount = 0f;
|
||||
|
||||
if (isPlayerFalling)
|
||||
{
|
||||
endDampAmount = _fallPanAmount;
|
||||
LerpedFromPlayerFalling = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
endDampAmount = _normalYPanAmount;
|
||||
}
|
||||
|
||||
float elapsedTime = 0f;
|
||||
while (elapsedTime < _fallYPanTime)
|
||||
{
|
||||
elapsedTime += Time.deltaTime;
|
||||
|
||||
float lerpedPanAmount = Mathf.Lerp(startDampAmount, endDampAmount, (elapsedTime / _fallYPanTime));
|
||||
_positionComposer.Damping.y = lerpedPanAmount;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
IsLerpingYDamping = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pan camera
|
||||
|
||||
public void PanCameraOnContact(float panDistance, float panTime, PanDirection panDirection, bool panToStartingPos)
|
||||
{
|
||||
_panCameraCoroutine = StartCoroutine(PanCamera(panDistance, panTime, panDirection, panToStartingPos));
|
||||
}
|
||||
|
||||
private IEnumerator PanCamera(float panDistance, float panTime, PanDirection panDirection, bool panToStartingPos)
|
||||
{
|
||||
Vector2 endPos = Vector2.zero;
|
||||
Vector2 startPos = Vector2.zero;
|
||||
|
||||
if (!panToStartingPos)
|
||||
{
|
||||
switch (panDirection)
|
||||
{
|
||||
case PanDirection.Up:
|
||||
endPos = Vector2.up;
|
||||
break;
|
||||
case PanDirection.Down:
|
||||
endPos = Vector2.down;
|
||||
break;
|
||||
case PanDirection.Left:
|
||||
endPos = Vector2.left;
|
||||
break;
|
||||
case PanDirection.Right:
|
||||
endPos = Vector2.right;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
instance = this;
|
||||
}
|
||||
|
||||
endPos *= panDistance;
|
||||
startPos = _startingTrackedObjectOffset;
|
||||
endPos += startPos;
|
||||
for (int i = 0; i < _allVirtualCameras.Length; i++)
|
||||
{
|
||||
if (_allVirtualCameras[i].enabled)
|
||||
{
|
||||
_currentCamera = _allVirtualCameras[i];
|
||||
|
||||
_positionComposer = _currentCamera.GetComponent<CinemachinePositionComposer>();
|
||||
}
|
||||
}
|
||||
|
||||
_normalYPanAmount = _positionComposer.Damping.y;
|
||||
|
||||
_startingTrackedObjectOffset = _positionComposer.TargetOffset;
|
||||
}
|
||||
else
|
||||
|
||||
#region Lerp Y Damping
|
||||
|
||||
public void LerpYDamping(bool isPlayerFalling)
|
||||
{
|
||||
startPos = _positionComposer.TargetOffset;
|
||||
endPos = _startingTrackedObjectOffset;
|
||||
_lerpYPanCoroutine = StartCoroutine(LerpYAction(isPlayerFalling));
|
||||
}
|
||||
|
||||
float elapsedTime = 0f;
|
||||
while (elapsedTime < panTime)
|
||||
private IEnumerator LerpYAction(bool isPlayerFalling)
|
||||
{
|
||||
elapsedTime += Time.deltaTime;
|
||||
IsLerpingYDamping = true;
|
||||
|
||||
Vector3 panLerp = Vector3.Lerp(startPos, endPos, (elapsedTime / panTime));
|
||||
_positionComposer.TargetOffset = panLerp;
|
||||
float startDampAmount = _positionComposer.Damping.y;
|
||||
float endDampAmount = 0f;
|
||||
|
||||
yield return null;
|
||||
if (isPlayerFalling)
|
||||
{
|
||||
endDampAmount = _fallPanAmount;
|
||||
LerpedFromPlayerFalling = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
endDampAmount = _normalYPanAmount;
|
||||
}
|
||||
|
||||
float elapsedTime = 0f;
|
||||
while (elapsedTime < _fallYPanTime)
|
||||
{
|
||||
elapsedTime += Time.deltaTime;
|
||||
|
||||
float lerpedPanAmount = Mathf.Lerp(startDampAmount, endDampAmount, (elapsedTime / _fallYPanTime));
|
||||
_positionComposer.Damping.y = lerpedPanAmount;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
IsLerpingYDamping = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pan camera
|
||||
|
||||
public void PanCameraOnContact(float panDistance, float panTime, PanDirection panDirection, bool panToStartingPos)
|
||||
{
|
||||
_panCameraCoroutine = StartCoroutine(PanCamera(panDistance, panTime, panDirection, panToStartingPos));
|
||||
}
|
||||
|
||||
private IEnumerator PanCamera(float panDistance, float panTime, PanDirection panDirection, bool panToStartingPos)
|
||||
{
|
||||
Vector2 endPos = Vector2.zero;
|
||||
Vector2 startPos = Vector2.zero;
|
||||
|
||||
if (!panToStartingPos)
|
||||
{
|
||||
switch (panDirection)
|
||||
{
|
||||
case PanDirection.Up:
|
||||
endPos = Vector2.up;
|
||||
break;
|
||||
case PanDirection.Down:
|
||||
endPos = Vector2.down;
|
||||
break;
|
||||
case PanDirection.Left:
|
||||
endPos = Vector2.left;
|
||||
break;
|
||||
case PanDirection.Right:
|
||||
endPos = Vector2.right;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
endPos *= panDistance;
|
||||
startPos = _startingTrackedObjectOffset;
|
||||
endPos += startPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
startPos = _positionComposer.TargetOffset;
|
||||
endPos = _startingTrackedObjectOffset;
|
||||
}
|
||||
|
||||
float elapsedTime = 0f;
|
||||
while (elapsedTime < panTime)
|
||||
{
|
||||
elapsedTime += Time.deltaTime;
|
||||
|
||||
Vector3 panLerp = Vector3.Lerp(startPos, endPos, (elapsedTime / panTime));
|
||||
_positionComposer.TargetOffset = panLerp;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace RR.Game2D.Entities
|
||||
{
|
||||
// Явный делегат для события смены чекпоинта
|
||||
public delegate void CheckpointChangedHandler(Checkpoint previous, Checkpoint current);
|
||||
|
||||
public class CheckpointSystem : MonoBehaviour
|
||||
{
|
||||
public static CheckpointSystem Instance { get; private set; }
|
||||
@ -17,7 +15,11 @@ namespace RR.Game2D.Entities
|
||||
private Checkpoint[] _all;
|
||||
|
||||
// Событие для внешних подписчиков: (старый, новый)
|
||||
public event CheckpointChangedHandler ActiveChanged;
|
||||
public event Action<Checkpoint, Checkpoint> ActiveChanged;
|
||||
|
||||
// Дополнительные события с современным синтаксисом
|
||||
public event Action<Checkpoint> OnCheckpointActivated;
|
||||
public event Action<Checkpoint> OnCheckpointDeactivated;
|
||||
|
||||
// Текущий активный чекпоинт (read-only)
|
||||
public Checkpoint Active => _active;
|
||||
@ -58,34 +60,40 @@ namespace RR.Game2D.Entities
|
||||
|
||||
if (persistBetweenSessions)
|
||||
{
|
||||
if (cp != null)
|
||||
PlayerPrefs.SetString(PrefKey, cp.checkpointId);
|
||||
else
|
||||
PlayerPrefs.DeleteKey(PrefKey);
|
||||
if (cp != null)
|
||||
PlayerPrefs.SetString(PrefKey, cp.checkpointId);
|
||||
else
|
||||
PlayerPrefs.DeleteKey(PrefKey);
|
||||
|
||||
PlayerPrefs.Save(); // гарантируем запись на диск
|
||||
PlayerPrefs.Save(); // гарантируем запись на диск
|
||||
}
|
||||
|
||||
// Оповестим подписчиков о смене
|
||||
if (ActiveChanged != null)
|
||||
ActiveChanged(previous, _active);
|
||||
// здесь можно задизейблить «подсветку» у старого и включить у нового
|
||||
ActiveChanged?.Invoke(previous, _active);
|
||||
|
||||
// Оповещаем о деактивации предыдущего
|
||||
if (previous != null)
|
||||
OnCheckpointDeactivated?.Invoke(previous);
|
||||
|
||||
// Оповещаем об активации нового
|
||||
if (_active != null)
|
||||
OnCheckpointActivated?.Invoke(_active);
|
||||
}
|
||||
|
||||
// Новый вспомогательный метод: активировать чекпоинт по его идентификатору
|
||||
// Новый вспомогательный метод: активировать чекпоинт по его идентификатору
|
||||
public void SetActiveById(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id)) return;
|
||||
|
||||
// Обновим кэш, если он по какой-то причине пуст
|
||||
if (_all == null || _all.Length == 0)
|
||||
_all = FindObjectsByType<Checkpoint>(FindObjectsSortMode.None);
|
||||
_all = FindObjectsByType<Checkpoint>(FindObjectsSortMode.None);
|
||||
|
||||
for (int i = 0; i < _all.Length; i++)
|
||||
{
|
||||
var cp = _all[i];
|
||||
if (cp != null && cp.checkpointId == id)
|
||||
{
|
||||
{
|
||||
SetActive(cp);
|
||||
return;
|
||||
}
|
||||
@ -93,21 +101,21 @@ namespace RR.Game2D.Entities
|
||||
// Если не нашли — ничего не делаем (можно добавить Debug.LogWarning при необходимости)
|
||||
}
|
||||
|
||||
public Vector3 GetSpawnPosition(Vector3 defaultPos)
|
||||
=> _active ? _active.GetSpawnPosition() : defaultPos;
|
||||
public Vector3 GetSpawnPosition(Vector3 defaultPos)
|
||||
=> _active ? _active.GetSpawnPosition() : defaultPos;
|
||||
|
||||
public Vector2 GetSpawnForward(Vector2 defaultForward)
|
||||
=> _active ? _active.GetForward() : defaultForward;
|
||||
public Vector2 GetSpawnForward(Vector2 defaultForward)
|
||||
=> _active ? _active.GetForward() : defaultForward;
|
||||
|
||||
// Если нужно сбросить прогресс
|
||||
public void ClearSaved()
|
||||
{
|
||||
if (persistBetweenSessions)
|
||||
{
|
||||
PlayerPrefs.DeleteKey(PrefKey);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
_active = null;
|
||||
}
|
||||
// Если нужно сбросить прогресс
|
||||
public void ClearSaved()
|
||||
{
|
||||
if (persistBetweenSessions)
|
||||
{
|
||||
PlayerPrefs.DeleteKey(PrefKey);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
_active = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,9 @@
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
using RR.Common.Interfaces;
|
||||
using RR.Game2D.Behaviour.ScriptableObjects;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using RR.Game2D.Behaviour.Camera;
|
||||
using RR.Game2D.Behaviour.ScriptableObjects;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
{
|
||||
|
||||
@ -1,267 +1,270 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class LightDimmer : MonoBehaviour
|
||||
namespace RR.Game2D.Behaviour.Lighting
|
||||
{
|
||||
[Tooltip("Если не указан, будет использован Collider2D на этом же объекте.")]
|
||||
[SerializeField] private Collider2D targetCollider;
|
||||
|
||||
[Tooltip("Тег игрока (используется для поиска Transform игрока)")]
|
||||
[SerializeField] private string playerTag;
|
||||
|
||||
[Tooltip("Массив со всеми источниками света, которые нужно изменять")]
|
||||
[SerializeField] private Light2D[] lights;
|
||||
|
||||
[Tooltip("Использовать прогресс по оси Y вместо оси X")]
|
||||
[SerializeField] private bool useYProgress = false;
|
||||
|
||||
[Header("Настройки света")]
|
||||
[SerializeField] [Min(0)] private float startIntensity;
|
||||
[SerializeField] [Min(0)] private float endIntensity;
|
||||
[SerializeField] [Min(0)] private float smoothing;
|
||||
|
||||
[Header("Настройки свечения")]
|
||||
[SerializeField] private bool applyBloom = true;
|
||||
[SerializeField] [Min(0)] private float startBloomIntensity = 0f;
|
||||
[SerializeField] [Min(0)] private float targetBloomIntensity = 1f;
|
||||
|
||||
private Transform _playerTransform;
|
||||
|
||||
// Храним все коллайдеры игрока, которые в данный момент пересекают триггер
|
||||
private readonly HashSet<Collider2D> _playerColliders = new HashSet<Collider2D>();
|
||||
|
||||
private float _boundsMin;
|
||||
private float _boundsMax;
|
||||
private Volume[] _volumes;
|
||||
|
||||
/// <summary>
|
||||
/// Валидация в редакторе — обновляем границы, если указан collider, и подставляем тег игрока по умолчанию.
|
||||
/// Вызвается в редакторе при изменении полей в инспекторе.
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class LightDimmer : MonoBehaviour
|
||||
{
|
||||
if (targetCollider != null)
|
||||
[Tooltip("Если не указан, будет использован Collider2D на этом же объекте.")]
|
||||
[SerializeField] private Collider2D targetCollider;
|
||||
|
||||
[Tooltip("Тег игрока (используется для поиска Transform игрока)")]
|
||||
[SerializeField] private string playerTag;
|
||||
|
||||
[Tooltip("Массив со всеми источниками света, которые нужно изменять")]
|
||||
[SerializeField] private Light2D[] lights;
|
||||
|
||||
[Tooltip("Использовать прогресс по оси Y вместо оси X")]
|
||||
[SerializeField] private bool useYProgress = false;
|
||||
|
||||
[Header("Настройки света")]
|
||||
[SerializeField][Min(0)] private float startIntensity;
|
||||
[SerializeField][Min(0)] private float endIntensity;
|
||||
[SerializeField][Min(0)] private float smoothing;
|
||||
|
||||
[Header("Настройки свечения")]
|
||||
[SerializeField] private bool applyBloom = true;
|
||||
[SerializeField][Min(0)] private float startBloomIntensity = 0f;
|
||||
[SerializeField][Min(0)] private float targetBloomIntensity = 1f;
|
||||
|
||||
private Transform _playerTransform;
|
||||
|
||||
// Храним все коллайдеры игрока, которые в данный момент пересекают триггер
|
||||
private readonly HashSet<Collider2D> _playerColliders = new HashSet<Collider2D>();
|
||||
|
||||
private float _boundsMin;
|
||||
private float _boundsMax;
|
||||
private Volume[] _volumes;
|
||||
|
||||
/// <summary>
|
||||
/// Валидация в редакторе — обновляем границы, если указан collider, и подставляем тег игрока по умолчанию.
|
||||
/// Вызвается в редакторе при изменении полей в инспекторе.
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
{
|
||||
if (targetCollider != null)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Инициализация при включении объекта: подставляем collider если не указан, находим игрока и кэшируем Volume для каждого света.
|
||||
/// Важно: здесь мы не создаём новых Volume, а только берём существующие компоненты в родительских объектах света.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (targetCollider == null)
|
||||
targetCollider = GetComponent<Collider2D>();
|
||||
|
||||
if (targetCollider != null)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
// Подготавливаем массив volumes той же длины, что и массив lights.
|
||||
// Это кэш для ускорения доступа к Bloom в Update, чтобы не делать GetComponent каждый кадр.
|
||||
_volumes = new Volume[lights.Length];
|
||||
|
||||
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
|
||||
|
||||
// Пытаемся найти игрока на сцене для удобства — это позволит скрипту работать, даже если playerTransform не задан вручную.
|
||||
var playerObj = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (playerObj != null)
|
||||
_playerTransform = playerObj.transform;
|
||||
|
||||
// Кэшируем Volume (если есть) для каждого источника света. Volume может находиться на родителе или выше.
|
||||
for (int i = 0; i < lights.Length; i++)
|
||||
{
|
||||
Light2D light2D = lights[i];
|
||||
// Возможна ситуация, когда элемент массива пустой (null) — защищаемся от этого.
|
||||
if (light2D == null)
|
||||
{
|
||||
_volumes[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ищем ближайший родительский Volume (он может быть на том же GameObject или выше в иерархии).
|
||||
Volume volume = light2D.GetComponentInParent<Volume>();
|
||||
if (volume != null)
|
||||
{
|
||||
// Включаем volume, чтобы настроенные эффекты (Bloom и т.п.) применялись во время работы.
|
||||
volume.enabled = true;
|
||||
}
|
||||
|
||||
_volumes[i] = volume;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start — дополнительная страховка: подставляем collider и обновляем границы на старте сцены.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (targetCollider == null)
|
||||
{
|
||||
targetCollider = GetComponent<Collider2D>();
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
|
||||
if (_playerTransform == null) _playerTransform = GameManager.Instance.PlayerObj.transform;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Инициализация при включении объекта: подставляем collider если не указан, находим игрока и кэшируем Volume для каждого света.
|
||||
/// Важно: здесь мы не создаём новых Volume, а только берём существующие компоненты в родительских объектах света.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (targetCollider == null)
|
||||
targetCollider = GetComponent<Collider2D>();
|
||||
|
||||
if (targetCollider != null)
|
||||
/// <summary>
|
||||
/// Основной цикл: если игрок находится внутри триггера, вычисляем прогресс по выбранной оси и плавно интерполируем интенсивность света
|
||||
/// и, при необходимости, значение bloom в соответствующем Volume.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
// Требуем наличие: playerTransform, массив lights и targetCollider
|
||||
if (!_playerTransform || lights == null || lights.Length == 0 || targetCollider == null)
|
||||
return;
|
||||
// Debug.Log($"{name}: Player Transform Detected");
|
||||
|
||||
// Подготавливаем массив volumes той же длины, что и массив lights.
|
||||
// Это кэш для ускорения доступа к Bloom в Update, чтобы не делать GetComponent каждый кадр.
|
||||
_volumes = new Volume[lights.Length];
|
||||
// Проверяем, что игрок действительно внутри триггера, учитываем все коллайдеры (на случай CompositeCollider/мультиколлайдеров)
|
||||
bool playerInside = _playerColliders.Count > 0;
|
||||
if (!playerInside)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
|
||||
// Debug.Log($"{name}: Player Inside Trigger");
|
||||
|
||||
// Пытаемся найти игрока на сцене для удобства — это позволит скрипту работать, даже если playerTransform не задан вручную.
|
||||
var playerObj = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (playerObj != null)
|
||||
_playerTransform = playerObj.transform;
|
||||
Vector3 playerPos = _playerTransform.position;
|
||||
|
||||
// Кэшируем Volume (если есть) для каждого источника света. Volume может находиться на родителе или выше.
|
||||
for (int i = 0; i < lights.Length; i++)
|
||||
{
|
||||
Light2D light2D = lights[i];
|
||||
// Возможна ситуация, когда элемент массива пустой (null) — защищаемся от этого.
|
||||
if (light2D == null)
|
||||
// Дополнительная проверка: AABB bounds коллайдера (быстрая фильтрация), чтобы избежать лишних вычислений
|
||||
// if (!targetCollider.bounds.Contains(playerPos))
|
||||
// return;
|
||||
|
||||
// Debug.Log($"{name}: Player Position: {playerPos}");
|
||||
|
||||
// Выбираем координату для прогресса: X или Y
|
||||
float playerCurrent = useYProgress ? playerPos.y : playerPos.x;
|
||||
|
||||
// Debug.Log($"{name}: Player Current Coordinate: {playerCurrent}");
|
||||
|
||||
float t = 0f;
|
||||
// Если boundsMin==boundsMax — делить нельзя, поэтому проверяем приблизительное равенство
|
||||
if (!Mathf.Approximately(_boundsMax, _boundsMin))
|
||||
{
|
||||
_volumes[i] = null;
|
||||
continue;
|
||||
// Нормализуем позицию игрока по отрезку [boundsMin, boundsMax]
|
||||
t = Mathf.InverseLerp(_boundsMin, _boundsMax, playerCurrent);
|
||||
t = Mathf.Clamp01(t);
|
||||
}
|
||||
|
||||
// Ищем ближайший родительский Volume (он может быть на том же GameObject или выше в иерархии).
|
||||
Volume volume = light2D.GetComponentInParent<Volume>();
|
||||
if (volume != null)
|
||||
|
||||
|
||||
// Интерполируем целевые значения интенсивности и bloom в зависимости от нормализованной позиции t
|
||||
float desiredForPosition = Mathf.Lerp(startIntensity, endIntensity, t);
|
||||
float desiredForBloom = Mathf.Lerp(startBloomIntensity, targetBloomIntensity, t);
|
||||
|
||||
// Проходим по всем светам и плавно (или мгновенно, если smoothing<=0) приводим интенсивность к целевой
|
||||
for (int i = 0; i < lights.Length; i++)
|
||||
{
|
||||
// Включаем volume, чтобы настроенные эффекты (Bloom и т.п.) применялись во время работы.
|
||||
volume.enabled = true;
|
||||
var light2D = lights[i];
|
||||
|
||||
// Безопасность: если света нет, пропускаем
|
||||
if (!light2D)
|
||||
continue;
|
||||
|
||||
// Получаем заранее закэшированный Volume (может быть null)
|
||||
var vol = _volumes != null && i < _volumes.Length ? _volumes[i] : null;
|
||||
|
||||
// Пытаемся извлечь компонент Bloom из профиля, если volume и профиль присутствуют
|
||||
Bloom bloom = null;
|
||||
if (applyBloom && vol != null && vol.profile != null)
|
||||
{
|
||||
// TryGet возвращает true если компонент есть, и мы получаем ссылку на него
|
||||
vol.profile.TryGet(out bloom);
|
||||
}
|
||||
|
||||
if (smoothing <= 0f)
|
||||
{
|
||||
// Мгновенное изменение — полезно для теста или резких переключателей
|
||||
light2D.intensity = desiredForPosition;
|
||||
if (applyBloom && bloom != null) bloom.intensity.value = desiredForBloom;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Плавное изменение — используем MoveTowards для стабильного изменения без overshoot
|
||||
light2D.intensity = Mathf.MoveTowards(light2D.intensity, desiredForPosition, smoothing * Time.deltaTime);
|
||||
if (applyBloom && bloom != null)
|
||||
bloom.intensity.value = Mathf.MoveTowards(bloom.intensity.value, desiredForBloom, smoothing * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
_volumes[i] = volume;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start — дополнительная страховка: подставляем collider и обновляем границы на старте сцены.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (targetCollider == null)
|
||||
{
|
||||
targetCollider = GetComponent<Collider2D>();
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
|
||||
if (_playerTransform == null) _playerTransform = GameManager.Instance.PlayerObj.transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Основной цикл: если игрок находится внутри триггера, вычисляем прогресс по выбранной оси и плавно интерполируем интенсивность света
|
||||
/// и, при необходимости, значение bloom в соответствующем Volume.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
// Требуем наличие: playerTransform, массив lights и targetCollider
|
||||
if (!_playerTransform || lights == null || lights.Length == 0 || targetCollider == null)
|
||||
return;
|
||||
// Debug.Log($"{name}: Player Transform Detected");
|
||||
|
||||
// Проверяем, что игрок действительно внутри триггера, учитываем все коллайдеры (на случай CompositeCollider/мультиколлайдеров)
|
||||
bool playerInside = _playerColliders.Count > 0;
|
||||
if (!playerInside)
|
||||
return;
|
||||
|
||||
// Debug.Log($"{name}: Player Inside Trigger");
|
||||
|
||||
Vector3 playerPos = _playerTransform.position;
|
||||
|
||||
// Дополнительная проверка: AABB bounds коллайдера (быстрая фильтрация), чтобы избежать лишних вычислений
|
||||
// if (!targetCollider.bounds.Contains(playerPos))
|
||||
// return;
|
||||
|
||||
// Debug.Log($"{name}: Player Position: {playerPos}");
|
||||
|
||||
// Выбираем координату для прогресса: X или Y
|
||||
float playerCurrent = useYProgress ? playerPos.y : playerPos.x;
|
||||
|
||||
// Debug.Log($"{name}: Player Current Coordinate: {playerCurrent}");
|
||||
|
||||
float t = 0f;
|
||||
// Если boundsMin==boundsMax — делить нельзя, поэтому проверяем приблизительное равенство
|
||||
if (!Mathf.Approximately(_boundsMax, _boundsMin))
|
||||
/// <summary>
|
||||
/// Триггер: запоминаем коллайдер игрока, когда любой из его коллайдеров входит в область.
|
||||
/// Используем HashSet, чтобы корректно работать с объектом, у которого несколько Collider2D (включая CompositeCollider2D).
|
||||
/// </summary>
|
||||
/// <param name="other">Коллайдер, вошедший в триггер</param>
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Нормализуем позицию игрока по отрезку [boundsMin, boundsMax]
|
||||
t = Mathf.InverseLerp(_boundsMin, _boundsMax, playerCurrent);
|
||||
t = Mathf.Clamp01(t);
|
||||
if (!other.CompareTag(playerTag)) return;
|
||||
|
||||
// Запоминаем коллайдер игрока
|
||||
_playerColliders.Add(other);
|
||||
|
||||
// Сохраняем Transform игрока (на случай, если раньше не найден)
|
||||
if (_playerTransform == null)
|
||||
_playerTransform = other.transform;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Интерполируем целевые значения интенсивности и bloom в зависимости от нормализованной позиции t
|
||||
float desiredForPosition = Mathf.Lerp(startIntensity, endIntensity, t);
|
||||
float desiredForBloom = Mathf.Lerp(startBloomIntensity, targetBloomIntensity, t);
|
||||
|
||||
// Проходим по всем светам и плавно (или мгновенно, если smoothing<=0) приводим интенсивность к целевой
|
||||
for (int i = 0; i < lights.Length; i++)
|
||||
/// <summary>
|
||||
/// Триггер выхода: убираем соответствующий коллайдер из набора; если набор пуст — игрок полностью вышел.
|
||||
/// </summary>
|
||||
/// <param name="other">Коллайдер, вышедший из триггера</param>
|
||||
private void OnTriggerExit2D(Collider2D other)
|
||||
{
|
||||
var light2D = lights[i];
|
||||
if (!other.CompareTag(playerTag)) return;
|
||||
|
||||
// Безопасность: если света нет, пропускаем
|
||||
if (!light2D)
|
||||
continue;
|
||||
// Убираем коллайдер из множества; если множество пустое — игрок полностью вышел
|
||||
_playerColliders.Remove(other);
|
||||
}
|
||||
|
||||
// Получаем заранее закэшированный Volume (может быть null)
|
||||
var vol = _volumes != null && i < _volumes.Length ? _volumes[i] : null;
|
||||
/// <summary>
|
||||
/// Обновляет внутренние границы отслеживаемого Collider2D.
|
||||
/// При useYProgress==false возвращает min/max по X, иначе по Y.
|
||||
/// </summary>
|
||||
private void UpdateBounds()
|
||||
{
|
||||
var b = targetCollider.bounds;
|
||||
_boundsMin = b.min.x;
|
||||
_boundsMax = b.max.x;
|
||||
|
||||
// Пытаемся извлечь компонент Bloom из профиля, если volume и профиль присутствуют
|
||||
Bloom bloom = null;
|
||||
if (applyBloom && vol != null && vol.profile != null)
|
||||
if (useYProgress)
|
||||
{
|
||||
// TryGet возвращает true если компонент есть, и мы получаем ссылку на него
|
||||
vol.profile.TryGet(out bloom);
|
||||
_boundsMin = b.min.y;
|
||||
_boundsMax = b.max.y;
|
||||
}
|
||||
}
|
||||
|
||||
if (smoothing <= 0f)
|
||||
/// <summary>
|
||||
/// Рисует в редакторе границу collider'а и позицию игрока для удобства отладки.
|
||||
/// </summary>
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (targetCollider == null) return;
|
||||
|
||||
var b = targetCollider.bounds;
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(new Vector3(b.min.x, b.min.y, 0f), new Vector3(b.max.x, b.min.y, 0f));
|
||||
Gizmos.DrawLine(new Vector3(b.max.x, b.min.y, 0f), new Vector3(b.max.x, b.max.y, 0f));
|
||||
Gizmos.DrawLine(new Vector3(b.max.x, b.max.y, 0f), new Vector3(b.min.x, b.max.y, 0f));
|
||||
Gizmos.DrawLine(new Vector3(b.min.x, b.max.y, 0f), new Vector3(b.min.x, b.min.y, 0f));
|
||||
|
||||
if (_playerTransform != null)
|
||||
{
|
||||
// Мгновенное изменение — полезно для теста или резких переключателей
|
||||
light2D.intensity = desiredForPosition;
|
||||
if (applyBloom && bloom != null) bloom.intensity.value = desiredForBloom;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Плавное изменение — используем MoveTowards для стабильного изменения без overshoot
|
||||
light2D.intensity = Mathf.MoveTowards(light2D.intensity, desiredForPosition, smoothing * Time.deltaTime);
|
||||
if (applyBloom && bloom != null)
|
||||
bloom.intensity.value = Mathf.MoveTowards(bloom.intensity.value, desiredForBloom, smoothing * Time.deltaTime);
|
||||
Gizmos.color = Color.cyan;
|
||||
Vector3 p = _playerTransform.position;
|
||||
Gizmos.DrawSphere(new Vector3(p.x, p.y, 0f), 0.05f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Триггер: запоминаем коллайдер игрока, когда любой из его коллайдеров входит в область.
|
||||
/// Используем HashSet, чтобы корректно работать с объектом, у которого несколько Collider2D (включая CompositeCollider2D).
|
||||
/// </summary>
|
||||
/// <param name="other">Коллайдер, вошедший в триггер</param>
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (!other.CompareTag(playerTag)) return;
|
||||
|
||||
// Запоминаем коллайдер игрока
|
||||
_playerColliders.Add(other);
|
||||
|
||||
// Сохраняем Transform игрока (на случай, если раньше не найден)
|
||||
if (_playerTransform == null)
|
||||
_playerTransform = other.transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Триггер выхода: убираем соответствующий коллайдер из набора; если набор пуст — игрок полностью вышел.
|
||||
/// </summary>
|
||||
/// <param name="other">Коллайдер, вышедший из триггера</param>
|
||||
private void OnTriggerExit2D(Collider2D other)
|
||||
{
|
||||
if (!other.CompareTag(playerTag)) return;
|
||||
|
||||
// Убираем коллайдер из множества; если множество пустое — игрок полностью вышел
|
||||
_playerColliders.Remove(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет внутренние границы отслеживаемого Collider2D.
|
||||
/// При useYProgress==false возвращает min/max по X, иначе по Y.
|
||||
/// </summary>
|
||||
private void UpdateBounds()
|
||||
{
|
||||
var b = targetCollider.bounds;
|
||||
_boundsMin = b.min.x;
|
||||
_boundsMax = b.max.x;
|
||||
|
||||
if (useYProgress)
|
||||
{
|
||||
_boundsMin = b.min.y;
|
||||
_boundsMax = b.max.y;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Рисует в редакторе границу collider'а и позицию игрока для удобства отладки.
|
||||
/// </summary>
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (targetCollider == null) return;
|
||||
|
||||
var b = targetCollider.bounds;
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(new Vector3(b.min.x, b.min.y, 0f), new Vector3(b.max.x, b.min.y, 0f));
|
||||
Gizmos.DrawLine(new Vector3(b.max.x, b.min.y, 0f), new Vector3(b.max.x, b.max.y, 0f));
|
||||
Gizmos.DrawLine(new Vector3(b.max.x, b.max.y, 0f), new Vector3(b.min.x, b.max.y, 0f));
|
||||
Gizmos.DrawLine(new Vector3(b.min.x, b.max.y, 0f), new Vector3(b.min.x, b.min.y, 0f));
|
||||
|
||||
if (_playerTransform != null)
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Vector3 p = _playerTransform.position;
|
||||
Gizmos.DrawSphere(new Vector3(p.x, p.y, 0f), 0.05f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Assets/Scripts/FX/CheckPointVFX.cs
Normal file
52
Assets/Scripts/FX/CheckPointVFX.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
using RR.Common.Managers;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Entities;
|
||||
using RR.Game2D.Core.Managers;
|
||||
|
||||
namespace RR.Game2D.FX
|
||||
{
|
||||
[RequireComponent(typeof(VisualEffect))]
|
||||
public class CheckPointFX : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private VisualEffect checkPointVFX;
|
||||
[SerializeField] private SoundEffect checkpointSFX;
|
||||
// [SerializeField] private Vector2 offset = Vector2.zero;
|
||||
//[SerializeField] private Color color = Color.white;
|
||||
|
||||
private GameObject _player;
|
||||
private Rigidbody2D _playerRB;
|
||||
private PlayerController _playerController;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CheckpointSystem.Instance.ActiveChanged -= PlayFX;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_player = GameManager.Instance.PlayerObj;
|
||||
|
||||
if (_player == null) _player = GameObject.FindGameObjectWithTag("Player");
|
||||
|
||||
_playerRB = _player.GetComponent<Rigidbody2D>();
|
||||
_playerController = _player.GetComponent<PlayerController>();
|
||||
|
||||
checkPointVFX.Stop();
|
||||
|
||||
CheckpointSystem.Instance.ActiveChanged += PlayFX;
|
||||
}
|
||||
|
||||
private void PlayFX(Checkpoint previous,Checkpoint current)
|
||||
{
|
||||
transform.position = current.transform.position;
|
||||
transform.rotation = current.transform.rotation;
|
||||
checkPointVFX.Play();
|
||||
GameManager.Instance.Audio.PlaySfxAt(checkpointSFX.sound, transform.position,
|
||||
checkpointSFX.volume, checkpointSFX.pitch);
|
||||
checkPointVFX.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/FX/CheckPointVFX.cs.meta
Normal file
2
Assets/Scripts/FX/CheckPointVFX.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edb963f0614ea6e4c88c887b3ef08ba6
|
||||
@ -1,68 +1,71 @@
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Behaviour;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
[RequireComponent(typeof(VisualEffect))]
|
||||
public class PlayerRunVFX : MonoBehaviour
|
||||
namespace RR.Game2D.FX
|
||||
{
|
||||
[SerializeField] private VisualEffect runVFX;
|
||||
[SerializeField] private Vector2 offset = Vector2.zero;
|
||||
[SerializeField] private float requiredPlayerRunTime;
|
||||
|
||||
private GameObject _player;
|
||||
private Rigidbody2D _playerRB;
|
||||
private PlayerController _playerController;
|
||||
private float _startRunTime = 0;
|
||||
|
||||
private Vector3 particlesVelocityDirection;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
[RequireComponent(typeof(VisualEffect))]
|
||||
public class PlayerRunVFX : MonoBehaviour
|
||||
{
|
||||
_player = GameManager.Instance.PlayerObj;
|
||||
_playerRB = _player.GetComponent<Rigidbody2D>();
|
||||
_playerController = _player.GetComponent<PlayerController>();
|
||||
|
||||
if (_player == null) _player = GameObject.FindGameObjectWithTag("Player");
|
||||
[SerializeField] private VisualEffect runVFX;
|
||||
[SerializeField] private Vector2 offset = Vector2.zero;
|
||||
[SerializeField] private float requiredPlayerRunTime;
|
||||
|
||||
particlesVelocityDirection = runVFX.GetVector3("ParticlesVelocityDirection");
|
||||
runVFX.Stop();
|
||||
// runVFX.enabled = false;
|
||||
}
|
||||
private GameObject _player;
|
||||
private Rigidbody2D _playerRB;
|
||||
private PlayerController _playerController;
|
||||
private float _startRunTime = 0;
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
if (_playerController.IsGrounded) transform.position = _player.transform.position
|
||||
+ new Vector3(offset.x * Mathf.Sign(_playerRB.linearVelocityX), offset.y, 0f);
|
||||
else transform.position = new Vector3(_player.transform.position.x, transform.position.y, transform.position.z);
|
||||
private Vector3 particlesVelocityDirection;
|
||||
|
||||
if (!runVFX.enabled) runVFX.enabled = true;
|
||||
|
||||
if (Mathf.Abs(_playerRB.linearVelocityX) >= 0.01f && _playerController.IsGrounded)
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
if (_startRunTime == 0) _startRunTime = Time.time;
|
||||
|
||||
if (Time.time - _startRunTime < requiredPlayerRunTime) return;
|
||||
runVFX.Play();
|
||||
runVFX.SetVector3("ParticlesVelocityDirection", new Vector3(particlesVelocityDirection.x * Mathf.Sign(_playerRB.linearVelocityX),
|
||||
particlesVelocityDirection.y,
|
||||
particlesVelocityDirection.z));
|
||||
return;
|
||||
}
|
||||
if (_playerRB.linearVelocityY >= 0.25f * _playerController.jumpVelocity)
|
||||
{
|
||||
runVFX.SetVector3("ParticlesVelocityDirection", new Vector3(particlesVelocityDirection.x * Mathf.Sign(_playerRB.linearVelocityX),
|
||||
_playerRB.linearVelocityY,
|
||||
particlesVelocityDirection.z));
|
||||
runVFX.Play();
|
||||
if (_playerRB.linearVelocityX == 0) _startRunTime = 0;
|
||||
return;
|
||||
_player = GameManager.Instance.PlayerObj;
|
||||
_playerRB = _player.GetComponent<Rigidbody2D>();
|
||||
_playerController = _player.GetComponent<PlayerController>();
|
||||
|
||||
if (_player == null) _player = GameObject.FindGameObjectWithTag("Player");
|
||||
|
||||
particlesVelocityDirection = runVFX.GetVector3("ParticlesVelocityDirection");
|
||||
runVFX.Stop();
|
||||
// runVFX.enabled = false;
|
||||
}
|
||||
|
||||
_startRunTime = 0;
|
||||
runVFX.Stop();
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
if (_playerController.IsGrounded) transform.position = _player.transform.position
|
||||
+ new Vector3(offset.x * Mathf.Sign(_playerRB.linearVelocityX), offset.y, 0f);
|
||||
else transform.position = new Vector3(_player.transform.position.x, transform.position.y, transform.position.z);
|
||||
|
||||
if (!runVFX.enabled) runVFX.enabled = true;
|
||||
|
||||
if (Mathf.Abs(_playerRB.linearVelocityX) >= 0.01f && _playerController.IsGrounded)
|
||||
{
|
||||
if (_startRunTime == 0) _startRunTime = Time.time;
|
||||
|
||||
if (Time.time - _startRunTime < requiredPlayerRunTime) return;
|
||||
runVFX.Play();
|
||||
runVFX.SetVector3("ParticlesVelocityDirection", new Vector3(particlesVelocityDirection.x * Mathf.Sign(_playerRB.linearVelocityX),
|
||||
particlesVelocityDirection.y,
|
||||
particlesVelocityDirection.z));
|
||||
return;
|
||||
}
|
||||
if (_playerRB.linearVelocityY >= 0.25f * _playerController.jumpVelocity)
|
||||
{
|
||||
runVFX.SetVector3("ParticlesVelocityDirection", new Vector3(particlesVelocityDirection.x * Mathf.Sign(_playerRB.linearVelocityX),
|
||||
_playerRB.linearVelocityY,
|
||||
particlesVelocityDirection.z));
|
||||
runVFX.Play();
|
||||
if (_playerRB.linearVelocityX == 0) _startRunTime = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_startRunTime = 0;
|
||||
runVFX.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Assets/Scripts/Inputs.meta
Normal file
8
Assets/Scripts/Inputs.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4e85ffb6eef71b46ad9ac320cece3be
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Assets/Scripts/Inputs/ButtonPinMapping.cs
Normal file
43
Assets/Scripts/Inputs/ButtonPinMapping.cs
Normal file
@ -0,0 +1,43 @@
|
||||
/// <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>
|
||||
startOnePlayer = 10,
|
||||
/// <summary>Запуск игры для двух игроков</summary>
|
||||
startTwoPlayers = 11,
|
||||
/// <summary>Секретная/служебная кнопка для захода в настройки</summary>
|
||||
secret = 12,
|
||||
|
||||
/// <summary>Дополнительная/резервная кнопка</summary>
|
||||
extra = 13,
|
||||
|
||||
/// <summary>Датчик наличия приза в хопере</summary>
|
||||
hasPrize = 14,
|
||||
/// <summary>Датчик выдачи приза из хопера</summary>
|
||||
prizeGiven = 15
|
||||
}
|
||||
2
Assets/Scripts/Inputs/ButtonPinMapping.cs.meta
Normal file
2
Assets/Scripts/Inputs/ButtonPinMapping.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0d08a1a5fe526e4d826660f76bdc8a1
|
||||
254
Assets/Scripts/Inputs/COMinput.cs
Normal file
254
Assets/Scripts/Inputs/COMinput.cs
Normal file
@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
public class COMinput : InputSource
|
||||
{
|
||||
//Количество = цифровые + 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()
|
||||
{
|
||||
try
|
||||
{
|
||||
gm = GameManager.Instance;
|
||||
|
||||
//ОБЯЗАТЕЛЬНЫЙ БЛОК
|
||||
ComWrapper.onInputRecieved.AddListener(UpdateInputState);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log($"Что то в 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])
|
||||
{
|
||||
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); //пишем в буфер чтобы между кадрами и порядком вызова не проебать
|
||||
|
||||
Debug.Log("Инпуты: " + 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
Assets/Scripts/Inputs/COMinput.cs.meta
Normal file
2
Assets/Scripts/Inputs/COMinput.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9c0303bc78dc9d4b83a863b7208fd78
|
||||
8
Assets/Scripts/Inputs/ComSystem.meta
Normal file
8
Assets/Scripts/Inputs/ComSystem.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9488def0f38e054fa257effe6059149
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
57
Assets/Scripts/Inputs/ComSystem/ComNativeWrapper.cs
Normal file
57
Assets/Scripts/Inputs/ComSystem/ComNativeWrapper.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
public static class ComNativeWrapper
|
||||
{
|
||||
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()
|
||||
{
|
||||
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(';');
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Inputs/ComSystem/ComNativeWrapper.cs.meta
Normal file
2
Assets/Scripts/Inputs/ComSystem/ComNativeWrapper.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64e7fb466c014ec49891cd56f7744b3f
|
||||
262
Assets/Scripts/Inputs/ComSystem/ComWrapper.cs
Normal file
262
Assets/Scripts/Inputs/ComSystem/ComWrapper.cs
Normal file
@ -0,0 +1,262 @@
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Основной класс-обертка для работы с COM-портом и периферийной платой.
|
||||
/// Управляет всей коммуникацией с аппаратным обеспечением: прием монет, чтение кнопок, управление подсветкой, отправка сигналов.
|
||||
/// </summary>
|
||||
public class ComWrapper : MonoBehaviour
|
||||
{
|
||||
//Настраиваем из инспектора
|
||||
[SerializeField] bool saveMoneyBetweenSessions;
|
||||
[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||||
|
||||
|
||||
public static UnityEvent<int> onCoinAdded;
|
||||
public static UnityEvent<int> onCoinRemoved;
|
||||
public static UnityEvent<string> onInputRecieved;
|
||||
|
||||
#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 string inputString = DEFAULT_INPUT;
|
||||
#endregion
|
||||
|
||||
//===========================start: PUBLIC API===========================//
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему работы с COM-портом.
|
||||
/// Открывает соединение, настраивает события и запускает периодические запросы состояния.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Обязателен к вызову при старте приложения. Выполняет:
|
||||
/// - Создание UnityEvent'ов для уведомлений
|
||||
/// - Загрузку количества кредитов из PlayerPrefs
|
||||
/// - Открытие COM-порта
|
||||
/// - Запуск периодического опроса пинов (если включена проверка призов)
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
{
|
||||
//Создаем ивенты
|
||||
onCoinAdded = new UnityEvent<int>();
|
||||
onCoinRemoved = new UnityEvent<int>();
|
||||
|
||||
//Начальная настройка
|
||||
if (!saveMoneyBetweenSessions) PlayerPrefs.SetInt("CoinsCount", 0);
|
||||
CreditsCount = PlayerPrefs.GetInt("CoinsCount", 0);
|
||||
initPort(); //вот это важный метод который собственно порт открывает.
|
||||
|
||||
//Каждые 5 сек запрашиваем инпуты у платы. Надо потому что при заходе в игру с пинов еще не пришла инфа
|
||||
if (enablePrizeChecking) InvokeRepeating("requestInputs", 0, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет один кредит в систему и сохраняет обновленное количество.
|
||||
/// Вызывается автоматически событием при получении сигнала "COIN" из COM-порта.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Увеличивает счетчик кредитов и вызывает событие onCoinAdded для уведомления подписчиков.
|
||||
/// Автоматически сохраняет состояние в PlayerPrefs.
|
||||
/// </remarks>
|
||||
public static void AddCredit()
|
||||
{
|
||||
CreditsCount++;
|
||||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||||
onCoinAdded?.Invoke(CreditsCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет произвольную строковую команду на периферийную плату через COM-порт.
|
||||
/// </summary>
|
||||
/// <param name="message">Команда для отправки</param>
|
||||
/// <remarks>
|
||||
/// Используется для отправки пользовательских команд на плату.
|
||||
/// Команда должна быть в формате, ожидаемом прошивкой платы.
|
||||
/// В случае ошибки отправки выводит предупреждение в DebugWindow.
|
||||
/// </remarks>
|
||||
public static void Send(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
|
||||
//Debug.Log("Wrote: " + w);
|
||||
|
||||
}
|
||||
catch { Debug.Log("Ошибка отправки строкового сигнала."); }
|
||||
}
|
||||
|
||||
/// <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 lightMode) =>
|
||||
|
||||
lightMode switch
|
||||
{
|
||||
LightMode.Idle => "106",
|
||||
LightMode.Orange => "107",
|
||||
LightMode.Blue => "108",
|
||||
LightMode.Win => "109",
|
||||
LightMode.Lose => "110",
|
||||
LightMode.Prize => "111",
|
||||
_ => "106"
|
||||
};
|
||||
|
||||
}
|
||||
catch { Debug.Log("Ошибка отправки светового сигнала."); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет списание платы за запуск игры.
|
||||
/// Проверяет наличие доступных кредитов или режим бесплатной игры.
|
||||
/// При успешном списании увеличивает счетчики запусков.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true - если списание прошло успешно (бесплатный режим или есть кредиты),
|
||||
/// false - если кредитов недостаточно и платный режим
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// В случае успеха:
|
||||
/// - Увеличивает общие счетчики запусков (key_starts и key_startsNul)
|
||||
/// - При платном режиме уменьшает количество кредитов на 1
|
||||
/// - Вызывает событие onCoinRemoved при списании кредита
|
||||
/// </remarks>
|
||||
public static bool Pay()
|
||||
{
|
||||
//блок проверки оплаты
|
||||
// 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 > 0)
|
||||
{
|
||||
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);
|
||||
|
||||
onCoinRemoved?.Invoke(CreditsCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
//===========================end: PUBLIC API===========================//
|
||||
|
||||
|
||||
|
||||
|
||||
private void Update() => pollPort();
|
||||
private void OnDisable() => ClosePort();
|
||||
private void OnApplicationQuit() => ClosePort();
|
||||
|
||||
|
||||
#region Приватные методы
|
||||
//Ну тут по дебаг логам все должно быть понятно
|
||||
//Если интересно, то h - это хэндл. Что то на плюсовом, типа ID соединения или чтото. Если 0 то нету соед. если меньше то ошибка, больше 0 - норм.
|
||||
private void initPort()
|
||||
{
|
||||
Debug.Log("Старт программы");
|
||||
ComNativeWrapper.Serial_CloseAll();
|
||||
var ports = ComNativeWrapper.ListPorts();
|
||||
Debug.Log("Ports: " + string.Join(", ", ports));
|
||||
if (ports.Length == 0) { Debug.Log("Нет портов, завершаю"); return; }
|
||||
|
||||
string port = ports[^1];
|
||||
Debug.Log("Открываю первый с конца порт: " + port);
|
||||
|
||||
|
||||
//вот эти настройки лучше не ковырять, но если очень хочется можно поменять readTimeoutMs и writeTimeoutMs
|
||||
//Если read поставить слишком маленьким оно проебует кредиты когда они идут большой пачкой например при оплате через вендисту.
|
||||
//Там в порт спамит "COIN" в больших количествах и процентов 10 из этих строк не успевают прочитаться при таймауте 1мс, соотв. монеты проебуются.
|
||||
//Слишком большой таймаут будет тормозить игру потому что оно все выполняется синхронно и каждый кадр забирает столько мс сколько в переменной указано.
|
||||
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 5, 10);
|
||||
|
||||
|
||||
Debug.Log($"handle: {h}");
|
||||
if (h <= 0)
|
||||
{
|
||||
Debug.Log("Open failed: " + ComNativeWrapper.GetLastError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClosePort()
|
||||
{
|
||||
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)
|
||||
{
|
||||
var s = Encoding.UTF8.GetString(buf, 0, read);
|
||||
//Debug.Log($"ReadLine len={read} data=[{s}]");
|
||||
return s;
|
||||
}
|
||||
else if (read < 0)
|
||||
{
|
||||
Debug.Log($"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
//просим прислать состояние цифровых входов. Приходит что то вроде "INPUT111111101"
|
||||
private void requestInputs() => Send(REQUEST_INPUT);
|
||||
|
||||
private void pollPort()
|
||||
{
|
||||
//Тут все просто, читаем что нам приходит с порта и сравниваем.
|
||||
string comInput = ReadBufferLine();
|
||||
if (comInput != "")
|
||||
{
|
||||
Debug.Log("COM: " + comInput);
|
||||
|
||||
if (comInput == "COIN") AddCredit();
|
||||
if (comInput.StartsWith("INPUT")) onInputRecieved?.Invoke(comInput.Substring(5)); //инвокаю сразу без префикса "INPUT"
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
public enum LightMode
|
||||
{
|
||||
Idle,
|
||||
Orange,
|
||||
Blue,
|
||||
Win,
|
||||
Lose,
|
||||
Prize,
|
||||
}
|
||||
2
Assets/Scripts/Inputs/ComSystem/ComWrapper.cs.meta
Normal file
2
Assets/Scripts/Inputs/ComSystem/ComWrapper.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d793a5f3669a676418d43519feaf3a8d
|
||||
116
Assets/Scripts/Inputs/ComSystem/PrizeSystem.cs
Normal file
116
Assets/Scripts/Inputs/ComSystem/PrizeSystem.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System.Collections;
|
||||
using RR.Common.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Система управления выдачей призов и ведения статистики игры.
|
||||
/// Отвечает за процесс выдачи призов, подсчет запусков и наград.
|
||||
/// </summary>
|
||||
public class PrizeSystem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] AudioClip prizeErrorSound;
|
||||
|
||||
public static string key_starts = "STARTS";
|
||||
public static string key_startsNul = "STARTSNUL";
|
||||
public static string key_prizesgiven = "PRIZESGIVEN";
|
||||
public static string key_prizesgivenNul = "PRIZESGIVENNUL";
|
||||
|
||||
private static PrizeSystem instance;
|
||||
private static Coroutine prizeCoroutine;
|
||||
private static bool prizing = false;
|
||||
|
||||
|
||||
//===========================start: PUBLIC API===========================//
|
||||
/// <summary>
|
||||
/// Инициализирует систему призов и счетчики статистики.
|
||||
/// Должен быть вызван один раз при запуске приложения.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Выполняет:
|
||||
/// - Установку статического экземпляра (singleton)
|
||||
/// - Инициализацию счетчиков в PlayerPrefs если они отсутствуют
|
||||
/// - Подготовку системы к работе
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
{
|
||||
instance = this;
|
||||
InitCounters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запускает процесс выдачи физического приза игроку.
|
||||
/// Проверяет настройки и текущее состояние перед началом выдачи.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Условия для выдачи приза:
|
||||
/// - В настройках включена выдача призов при игре с ботом
|
||||
/// - В данный момент не выполняется другая выдача приза
|
||||
///
|
||||
/// Процесс выдачи:
|
||||
/// - Включает мотор хопера
|
||||
/// - Ожидает подтверждения выдачи от датчиков (10 сек таймаут)
|
||||
/// - Выключает мотор по завершению
|
||||
/// - Обновляет счетчики выданных призов
|
||||
/// - Воспроизводит звук ошибки при неудаче
|
||||
/// </remarks>
|
||||
public static void GivePrize()
|
||||
{
|
||||
// bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать";
|
||||
bool settingEnabled = true;
|
||||
|
||||
if (!settingEnabled) { Debug.Log("Выдача приза отключена в настройках"); return; }
|
||||
if (prizing) { Debug.Log("Уже идет выдача приза. Не спамь кнопку."); return; }
|
||||
|
||||
Debug.Log("Выдаю приз");
|
||||
if (prizeCoroutine != null) instance.StopCoroutine(prizeCoroutine);
|
||||
prizeCoroutine = instance.StartCoroutine(instance.Prize());
|
||||
}
|
||||
//===========================end: PUBLIC API===========================//
|
||||
|
||||
#region Приватные методы
|
||||
private IEnumerator Prize()
|
||||
{
|
||||
prizing = true;
|
||||
Debug.Log("Начинаю выдачу");
|
||||
|
||||
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)
|
||||
{
|
||||
success = true;
|
||||
IncreasePrizeCounters();
|
||||
break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
|
||||
//Выключаем мотор
|
||||
ComWrapper.Send("300");
|
||||
Debug.Log("Закончил. Успех: " + success);
|
||||
prizing = false;
|
||||
|
||||
if (!success) AudioManager.Instance.PlaySfx(prizeErrorSound);
|
||||
}
|
||||
|
||||
void IncreasePrizeCounters()
|
||||
{
|
||||
PlayerPrefs.SetInt(key_prizesgiven, PlayerPrefs.GetInt(key_prizesgiven) + 1);
|
||||
PlayerPrefs.SetInt(key_prizesgivenNul, PlayerPrefs.GetInt(key_prizesgivenNul) + 1);
|
||||
}
|
||||
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
|
||||
}
|
||||
2
Assets/Scripts/Inputs/ComSystem/PrizeSystem.cs.meta
Normal file
2
Assets/Scripts/Inputs/ComSystem/PrizeSystem.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04cb8758b49014f4caca890b913d86c0
|
||||
89
Assets/Scripts/Inputs/In.cs
Normal file
89
Assets/Scripts/Inputs/In.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Статический фасад для унифицированного доступа к системе ввода.
|
||||
/// Предоставляет глобальный интерфейс для работы с управлением независимо от выбранного источника.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Этот класс является точкой входа для всей игровой логики, работающей с вводом.
|
||||
/// Автоматически инициализирует выбранный источник управления (клавиатура или COM-порт)
|
||||
/// и предоставляет статические методы для удобного доступа из любого места кода.
|
||||
///
|
||||
/// Использование:
|
||||
/// 1. Настройте тип ввода в инспекторе
|
||||
/// 2. Вызовите Initialize() при старте игры
|
||||
/// 3. Используйте статические методы для проверки ввода
|
||||
/// </remarks>
|
||||
public class In : MonoBehaviour
|
||||
{
|
||||
[Header("Выбор источника управления")]
|
||||
[SerializeField] private InputSourceType inputType;
|
||||
|
||||
static InputSource source;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему ввода на основе выбранного типа.
|
||||
/// Должен быть вызван один раз при запуске приложения.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Создает соответствующий экземпляр InputSource:
|
||||
/// - KeyboardInput для тестирования на клавиатуре
|
||||
/// - COMinput для работы с периферийной платой
|
||||
/// </remarks>
|
||||
public void Initialize()
|
||||
{
|
||||
source = inputType switch
|
||||
{
|
||||
InputSourceType.Keyboard => new KeyboardInput(),
|
||||
InputSourceType.COM => new COMinput(),
|
||||
_ => throw new System.NotImplementedException()
|
||||
};
|
||||
|
||||
var success = source.Initialize();
|
||||
if (!success) Debug.LogWarning("Не удалось проинициалзировать источник управления.");
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// СТАТИЧЕСКИЕ МЕТОДЫ ДЛЯ РАБОТЫ С ФИЗИЧЕСКИМИ ПИНАМИ
|
||||
// ================================================================================
|
||||
|
||||
/// <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);
|
||||
}
|
||||
2
Assets/Scripts/Inputs/In.cs.meta
Normal file
2
Assets/Scripts/Inputs/In.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dec428d33ffedb846aa3568b5dbc6d49
|
||||
117
Assets/Scripts/Inputs/InputSource.cs
Normal file
117
Assets/Scripts/Inputs/InputSource.cs
Normal file
@ -0,0 +1,117 @@
|
||||
/// <summary>
|
||||
/// Абстрактный базовый класс для всех источников ввода в системе.
|
||||
/// Определяет унифицированный интерфейс для работы с аппаратными кнопками и виртуальными действиями игроков.
|
||||
/// </summary>
|
||||
/// <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 GetButtonDown(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 GetButtonDown(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 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 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 GetButtonUpCurrentPlayer(PlayerAction button);
|
||||
}
|
||||
2
Assets/Scripts/Inputs/InputSource.cs.meta
Normal file
2
Assets/Scripts/Inputs/InputSource.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcf9b5b5ddc68fd4ba4eee61a175004d
|
||||
5
Assets/Scripts/Inputs/InputSourceType.cs
Normal file
5
Assets/Scripts/Inputs/InputSourceType.cs
Normal file
@ -0,0 +1,5 @@
|
||||
public enum InputSourceType
|
||||
{
|
||||
COM,
|
||||
Keyboard
|
||||
}
|
||||
2
Assets/Scripts/Inputs/InputSourceType.cs.meta
Normal file
2
Assets/Scripts/Inputs/InputSourceType.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94078489920deb443a816e107e90afbf
|
||||
176
Assets/Scripts/Inputs/KeyboardInput.cs
Normal file
176
Assets/Scripts/Inputs/KeyboardInput.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация системы ввода с клавиатуры для тестирования и разработки.
|
||||
/// Эмулирует работу периферийной платы через стандартные клавиши клавиатуры.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Соответствие клавиш и пинов:
|
||||
/// - Игрок 1: WASD + Space
|
||||
/// - Игрок 2: Стрелки + RightControl
|
||||
/// - Служебные: 1, 2, 3, F5
|
||||
/// - Датчики хопера: O, P
|
||||
/// </remarks>
|
||||
public class KeyboardInput : InputSource
|
||||
{
|
||||
private int currentPlayer = 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
|
||||
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
|
||||
|
||||
|
||||
|
||||
/// <summary>Проверяет состояние кнопки по её физическому пину через клавиатуру</summary>
|
||||
public override bool GetButton(ButtonPinMapping button) => Input.GetKey(getKeyCode(button));
|
||||
|
||||
/// <summary>Проверяет нажатие кнопки по пину в текущем кадре через клавиатуру</summary>
|
||||
public override bool GetButtonDown(ButtonPinMapping button) => Input.GetKeyDown(getKeyCode(button));
|
||||
|
||||
/// <summary>Проверяет отпускание кнопки по пину в текущем кадре через клавиатуру</summary>
|
||||
public override bool GetButtonUp(ButtonPinMapping button) => Input.GetKeyUp(getKeyCode(button));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Проверяет состояние кнопки по числовому индексу пина через клавиатуру</summary>
|
||||
public override bool GetButton(int pinIndex) => Input.GetKey(getKeyCode(pinIndex));
|
||||
|
||||
/// <summary>Проверяет нажатие кнопки по индексу пина в текущем кадре через клавиатуру</summary>
|
||||
public override bool GetButtonDown(int pinIndex) => Input.GetKeyDown(getKeyCode(pinIndex));
|
||||
|
||||
/// <summary>Проверяет отпускание кнопки по индексу пина в текущем кадре через клавиатуру</summary>
|
||||
public override bool GetButtonUp(int pinIndex) => Input.GetKeyUp(getKeyCode(pinIndex));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Проверяет состояние действия для текущего игрока (всегда игрок 1 для клавиатуры)</summary>
|
||||
public override bool GetButtonCurrentPlayer(PlayerAction button) => GetButton(GetButtonForCurrentPlayer(button));
|
||||
|
||||
/// <summary>Проверяет начало действия текущим игроком в текущем кадре</summary>
|
||||
public override bool GetButtonDownCurrentPlayer(PlayerAction button) => GetButtonDown(GetButtonForCurrentPlayer(button));
|
||||
|
||||
/// <summary>Проверяет завершение действия текущим игроком в текущем кадре</summary>
|
||||
public override bool GetButtonUpCurrentPlayer(PlayerAction button) => GetButtonUp(GetButtonForCurrentPlayer(button));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Проверяет состояние действия для любого из игроков (игрок 1 ИЛИ игрок 2)</summary>
|
||||
public override bool GetButtonAnyPlayer(PlayerAction button) => CheckBothPlayers(button, GetButton);
|
||||
|
||||
/// <summary>Проверяет начало действия любым игроком в текущем кадре</summary>
|
||||
public override bool GetButtonDownAnyPlayer(PlayerAction button) => CheckBothPlayers(button, GetButtonDown);
|
||||
|
||||
/// <summary>Проверяет завершение действия любым игроком в текущем кадре</summary>
|
||||
public override bool GetButtonUpAnyPlayer(PlayerAction button) => CheckBothPlayers(button, GetButtonUp);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует клавиатурный ввод. Всегда возвращает true так как клавиатура всегда доступна.
|
||||
/// </summary>
|
||||
/// <returns>Всегда true</returns>
|
||||
public override bool Initialize() => gm = GameManager.Instance;
|
||||
|
||||
// ================================================================================
|
||||
// ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ
|
||||
// ================================================================================
|
||||
|
||||
/// <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, // невалидный индекс
|
||||
};
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Inputs/KeyboardInput.cs.meta
Normal file
2
Assets/Scripts/Inputs/KeyboardInput.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f386ce37e6d1664d88158f8bb95e8f6
|
||||
19
Assets/Scripts/Inputs/PlayerAction.cs
Normal file
19
Assets/Scripts/Inputs/PlayerAction.cs
Normal file
@ -0,0 +1,19 @@
|
||||
/// <summary>
|
||||
/// Абстрактные игровые действия для управления игроком.
|
||||
/// Обеспечивает независимость от физического расположения кнопок.
|
||||
/// </summary>
|
||||
public enum PlayerAction
|
||||
{
|
||||
/// <summary>Вверх</summary>
|
||||
up,
|
||||
/// <summary>Вниз</summary>
|
||||
down,
|
||||
/// <summary>Влево</summary>
|
||||
left,
|
||||
/// <summary>Вправо</summary>
|
||||
right,
|
||||
/// <summary>Основное действие</summary>
|
||||
action,
|
||||
/// <summary>Дополнительное действие(ВНИМАНИЕ РАБОТАЕТ У ОБОИХ ИГРОКОВ ОДНОВРЕМЕННО)</summary>
|
||||
extra
|
||||
}
|
||||
2
Assets/Scripts/Inputs/PlayerAction.cs.meta
Normal file
2
Assets/Scripts/Inputs/PlayerAction.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d4dc3fb86b85574eb4994128deed339
|
||||
16
Assets/Scripts/ScriptableObjects/Enemies/CrabSFXAsset.asset
Normal file
16
Assets/Scripts/ScriptableObjects/Enemies/CrabSFXAsset.asset
Normal file
@ -0,0 +1,16 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7d0ab526e27a165469927c990b5cfaba, type: 3}
|
||||
m_Name: CrabSFXAsset
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.ScriptableObjects.EnemySFXAsset
|
||||
stepSounds: {fileID: 11400000, guid: 37fe07ddcac109a46a86ba44df16d551, type: 2}
|
||||
hurtSounds: {fileID: 11400000, guid: cb8b624f554b6f94f9027cdf3cf6331b, type: 2}
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5f478919b32fe34a92c9528c43ed141
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,30 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8b7a812250cb02a439b8c382ae42dd27, type: 3}
|
||||
m_Name: CrabHurtSounds
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.ScriptableObjects.SFXAsset
|
||||
soundEffects:
|
||||
- sound: {fileID: 8300000, guid: aeba847740e70ba46b7d3df9513316e1, type: 3}
|
||||
volume: 1
|
||||
pitch: 1
|
||||
- sound: {fileID: 8300000, guid: aeba847740e70ba46b7d3df9513316e1, type: 3}
|
||||
volume: 1
|
||||
pitch: 2
|
||||
- sound: {fileID: 8300000, guid: aeba847740e70ba46b7d3df9513316e1, type: 3}
|
||||
volume: 1
|
||||
pitch: 0.5
|
||||
- sound: {fileID: 8300000, guid: aeba847740e70ba46b7d3df9513316e1, type: 3}
|
||||
volume: 1
|
||||
pitch: 0.8
|
||||
- sound: {fileID: 8300000, guid: aeba847740e70ba46b7d3df9513316e1, type: 3}
|
||||
volume: 1
|
||||
pitch: 1.2
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb8b624f554b6f94f9027cdf3cf6331b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@ EditorBuildSettings:
|
||||
path: Assets/Scenes/Levels/Old/1-5.unity
|
||||
guid: b63c94138b89518459c618e66e063850
|
||||
- enabled: 1
|
||||
path: Assets/Scenes/Levels/2-1.unity
|
||||
path: Assets/Scenes/Levels/1-2.unity
|
||||
guid: 86bb899abf617614ea731dfb70ed822e
|
||||
m_configObjects:
|
||||
com.unity.addressableassets: {fileID: 11400000, guid: 40a4d2dfb24d5af4d8277c2c4cdffcc3, type: 2}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user