Added more music and sounds; started COMInput project import

This commit is contained in:
Данил Ярош 2025-10-28 16:53:24 +03:00
parent 2d1cc90446
commit 0f9c407d70
60 changed files with 12798 additions and 3580 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d4b01536b2516e14b83e7bd7f6758e2d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b223d89225f6c7346be5e14dc720ac2f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d0799ce7bda4b5f418bec56c93a1b5e3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 265471db36c70b54180317c720985aad

View File

@ -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:

View 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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9cee1e552b11c5844aca4d1e755b65f8
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View 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:

Binary file not shown.

View 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:

Binary file not shown.

View 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

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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;
}
}
}

View File

@ -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
{

View File

@ -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);
}
}
}
}

View 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();
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: edb963f0614ea6e4c88c887b3ef08ba6

View File

@ -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();
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f4e85ffb6eef71b46ad9ac320cece3be
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b0d08a1a5fe526e4d826660f76bdc8a1

View 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;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b9c0303bc78dc9d4b83a863b7208fd78

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d9488def0f38e054fa257effe6059149
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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(';');
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 64e7fb466c014ec49891cd56f7744b3f

View 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,
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d793a5f3669a676418d43519feaf3a8d

View 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
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 04cb8758b49014f4caca890b913d86c0

View 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);
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dec428d33ffedb846aa3568b5dbc6d49

View 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);
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fcf9b5b5ddc68fd4ba4eee61a175004d

View File

@ -0,0 +1,5 @@
public enum InputSourceType
{
COM,
Keyboard
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 94078489920deb443a816e107e90afbf

View 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, // невалидный индекс
};
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6f386ce37e6d1664d88158f8bb95e8f6

View 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
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4d4dc3fb86b85574eb4994128deed339

View 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}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a5f478919b32fe34a92c9528c43ed141
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -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

View File

@ -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}