refactor: rework environmental spawner, remove pooling, add trigger-based particle switching
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Убран пулинг из `EnvironmentalCollectibleBase` и `EnvironmentalCollectibleSpawner`: при подборе и деспауне частицы теперь уничтожаются через `Destroy()`, что устраняет проблему переиспользования объектов старого типа при смене частиц. - Удалены поля `usePooling`, `_ownerPool`, `_originPrefab` из `EnvironmentalCollectibleBase`; упрощены методы `Collect()` и `Despawn()`. - Добавлено время жизни частиц: поле `defaultLifetime` в `EnvironmentalCollectibleBase`, таймер `_age` с автоматическим деспауном по истечении; виртуальный `OnDespawned()` для наследников. - В `SpawnEntry` добавлены `overrideLifetime` и `lifetimeOverride` для переопределения времени жизни. - Добавлена структура `NamedSpawnEntry` — именованные записи с ключом (`string key`), полным `SpawnEntry` и флагом `hardReplace`, настраиваемые в инспекторе. - Спаунер получает игрока из `GameManager.Instance.PlayerObj` вместо `FindGameObjectWithTag`; подписка на `GameManager.PlayerSpawned` для автообновления ссылки. - Добавлен постепенный спаун: `MaintainFixedCount()` запускает корутину `SpawnRoutine()` с рандомной задержкой `spawnDelay` между объектами. - Добавлены диапазоны случайного масштаба (`scaleRange`) и угла поворота (`rotationRange`) при спауне. - Добавлены публичные методы переключения типа частиц для `UnityEvent`: `SetForcedPrefab`, `SetForcedPrefabHard`, `SetForcedPrefabByObject`, `SetForcedPrefabByObjectHard`, `SetForcedPrefabByIndex`, `SetForcedPrefabHardByIndex`, `SetForcedEntry`, `SetForcedEntryByName`, `SetEntryByName` (мягкая постепенная замена через `GradualReplaceRoutine`), `ClearForcedPrefab`, `SetOverrideArea`. - Исправлено сравнение prefab по ссылкам на сравнение по `name`, устраняя проблему когда один и тот же ассет из разных массивов (`spawnEntries` / `namedEntries`) не совпадал. - Удалён `EnvironmentalZoneTrigger.cs` (триггер-зона не нужна как отдельный скрипт). - Удалены `EnvironmentalPool` из использования в спаунере и базовом классе. - Префабы `Coin.prefab` и `Coin_Robotail.prefab` перемещены в `Prefabs/Props/Collectibles/`; добавлены `LeafCollectible.prefab` и `BatCollectible.prefab`.
This commit is contained in:
parent
de87b318b7
commit
4374c6f63f
File diff suppressed because one or more lines are too long
8
Assets/Prefabs/Props/Collectibles.meta
Normal file
8
Assets/Prefabs/Props/Collectibles.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a197c719b8349a4ab403591cf98c834
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
197
Assets/Prefabs/Props/Collectibles/BatCollectible.prefab
Normal file
197
Assets/Prefabs/Props/Collectibles/BatCollectible.prefab
Normal file
@ -0,0 +1,197 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &3316243588096306883
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3722284284489069898}
|
||||
- component: {fileID: -349059794222274119}
|
||||
- component: {fileID: 7133210491211008283}
|
||||
m_Layer: 0
|
||||
m_Name: BatCollectible
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3722284284489069898
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3316243588096306883}
|
||||
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: 1
|
||||
m_Children:
|
||||
- {fileID: 932494216999854972}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!61 &-349059794222274119
|
||||
BoxCollider2D:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3316243588096306883}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Density: 1
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_ForceSendLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ForceReceiveLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ContactCaptureLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_CallbackLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_IsTrigger: 1
|
||||
m_UsedByEffector: 0
|
||||
m_CompositeOperation: 0
|
||||
m_CompositeOrder: 0
|
||||
m_Offset: {x: 0, y: -0.002134174}
|
||||
m_SpriteTilingProperty:
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
pivot: {x: 0, y: 0}
|
||||
oldSize: {x: 0, y: 0}
|
||||
newSize: {x: 0, y: 0}
|
||||
adaptiveTilingThreshold: 0
|
||||
drawMode: 0
|
||||
adaptiveTiling: 0
|
||||
m_AutoTiling: 0
|
||||
m_Size: {x: 1, y: 0.7225633}
|
||||
m_EdgeRadius: 0
|
||||
--- !u!114 &7133210491211008283
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3316243588096306883}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0a07d694adfb80d4594401abc8aa8509, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Entities.Props.BatCollectible
|
||||
points: 1
|
||||
playerTag: Player
|
||||
usePooling: 1
|
||||
defaultLifetime: 10
|
||||
moveSpeed: 1.75
|
||||
verticalAmplitude: 0.35
|
||||
verticalFrequency: 2.4
|
||||
horizontalWaveAmplitude: 0.2
|
||||
horizontalWaveFrequency: 1.35
|
||||
maxRange: 3.5
|
||||
directionChangeIntervalMin: 1.5
|
||||
directionChangeIntervalMax: 3.5
|
||||
--- !u!1 &3870501198317419823
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 932494216999854972}
|
||||
- component: {fileID: 4329754353811481997}
|
||||
m_Layer: 0
|
||||
m_Name: bat_0
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &932494216999854972
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3870501198317419823}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.05, y: 0.05, z: 0.05}
|
||||
m_ConstrainProportionsScale: 1
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3722284284489069898}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!212 &4329754353811481997
|
||||
SpriteRenderer:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3870501198317419823}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
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_Materials:
|
||||
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
|
||||
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: 0
|
||||
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
|
||||
m_MaskInteraction: 0
|
||||
m_Sprite: {fileID: 21300000, guid: 580cb7a0781814a49a32652f9a97e49d, type: 3}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_FlipX: 0
|
||||
m_FlipY: 0
|
||||
m_DrawMode: 0
|
||||
m_Size: {x: 19.73, y: 14.64}
|
||||
m_AdaptiveModeThreshold: 0.5
|
||||
m_SpriteTileMode: 0
|
||||
m_WasSpriteAssigned: 1
|
||||
m_SpriteSortPoint: 0
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76ff5cdfacce8e04ca29a050fd1b41db
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
202
Assets/Prefabs/Props/Collectibles/LeafCollectible.prefab
Normal file
202
Assets/Prefabs/Props/Collectibles/LeafCollectible.prefab
Normal file
@ -0,0 +1,202 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &7680590638374317877
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4550815583012328490}
|
||||
- component: {fileID: 759722639321813396}
|
||||
m_Layer: 0
|
||||
m_Name: Square
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4550815583012328490
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7680590638374317877}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.4, y: 0.4, z: 0.4}
|
||||
m_ConstrainProportionsScale: 1
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7358477386062440731}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!212 &759722639321813396
|
||||
SpriteRenderer:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7680590638374317877}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
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_Materials:
|
||||
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
|
||||
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: 0
|
||||
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
|
||||
m_MaskInteraction: 0
|
||||
m_Sprite: {fileID: 5781445747943558089, guid: a191b676b9627ca44aceb3a028607100, type: 3}
|
||||
m_Color: {r: 0.2140805, g: 0.5754717, b: 0.13843894, a: 1}
|
||||
m_FlipX: 0
|
||||
m_FlipY: 0
|
||||
m_DrawMode: 0
|
||||
m_Size: {x: 1, y: 1}
|
||||
m_AdaptiveModeThreshold: 0.5
|
||||
m_SpriteTileMode: 0
|
||||
m_WasSpriteAssigned: 1
|
||||
m_SpriteSortPoint: 0
|
||||
--- !u!1 &8928884138224752087
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7358477386062440731}
|
||||
- component: {fileID: -205316951964799471}
|
||||
- component: {fileID: -6130303908877127613}
|
||||
m_Layer: 0
|
||||
m_Name: LeafCollectible
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7358477386062440731
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8928884138224752087}
|
||||
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: 1
|
||||
m_Children:
|
||||
- {fileID: 4550815583012328490}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!61 &-205316951964799471
|
||||
BoxCollider2D:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8928884138224752087}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Density: 1
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_ForceSendLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ForceReceiveLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ContactCaptureLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_CallbackLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_IsTrigger: 1
|
||||
m_UsedByEffector: 0
|
||||
m_CompositeOperation: 0
|
||||
m_CompositeOrder: 0
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_SpriteTilingProperty:
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
pivot: {x: 0, y: 0}
|
||||
oldSize: {x: 0, y: 0}
|
||||
newSize: {x: 0, y: 0}
|
||||
adaptiveTilingThreshold: 0
|
||||
drawMode: 0
|
||||
adaptiveTiling: 0
|
||||
m_AutoTiling: 0
|
||||
m_Size: {x: 1, y: 1}
|
||||
m_EdgeRadius: 0
|
||||
--- !u!114 &-6130303908877127613
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8928884138224752087}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d5b7e46ee9c8087468860b8f11049bdc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.Entities.Props.LeafCollectible
|
||||
points: 1
|
||||
playerTag: Player
|
||||
usePooling: 1
|
||||
defaultLifetime: 10
|
||||
fallSpeed: 1.25
|
||||
horizontalAmplitude: 0.4
|
||||
horizontalFrequency: 1.1
|
||||
rotationAmplitude: 22
|
||||
rotationSpeed: 2.2
|
||||
stopAtGround: 0
|
||||
groundMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 64
|
||||
groundCheckDistance: 0.2
|
||||
minY: -10
|
||||
groundedSwayAmplitude: 0.08
|
||||
groundedSwaySpeed: 1.5
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1002877f2c88eb47911b3d3b30db95c
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -46,7 +46,7 @@ TextureImporter:
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
@ -151,7 +151,7 @@ TextureImporter:
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
|
||||
@ -1581,6 +1581,19 @@ MonoBehaviour:
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 1476321865}
|
||||
m_TargetAssemblyTypeName: RR.Game2D.Behaviour.EnvironmentalCollectibleSpawner,
|
||||
Assembly-CSharp
|
||||
m_MethodName: SetEntryByName
|
||||
m_Mode: 5
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument: bat
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
OnEventTriggerExit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
@ -2839,7 +2852,7 @@ Transform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1329653979}
|
||||
- {fileID: 1476321862}
|
||||
m_Father: {fileID: 1053302775}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &95746896
|
||||
@ -96243,71 +96256,6 @@ SpriteRenderer:
|
||||
m_SpriteTileMode: 0
|
||||
m_WasSpriteAssigned: 1
|
||||
m_SpriteSortPoint: 0
|
||||
--- !u!1001 &568219572
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 94085880}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1754008902089564202, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: Checkpoint_Particles
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1754008902089564202, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_IsActive
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6931528425163354462, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_Enabled
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
--- !u!1 &575351087
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -129463,6 +129411,19 @@ MonoBehaviour:
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 1476321865}
|
||||
m_TargetAssemblyTypeName: RR.Game2D.Behaviour.EnvironmentalCollectibleSpawner,
|
||||
Assembly-CSharp
|
||||
m_MethodName: SetEntryByName
|
||||
m_Mode: 5
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument: leaf
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
OnEventTriggerExit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
@ -198897,6 +198858,19 @@ MonoBehaviour:
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 1476321865}
|
||||
m_TargetAssemblyTypeName: RR.Game2D.Behaviour.EnvironmentalCollectibleSpawner,
|
||||
Assembly-CSharp
|
||||
m_MethodName: SetEntryByName
|
||||
m_Mode: 5
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument: leaf
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
OnEventTriggerExit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
@ -203128,11 +203102,6 @@ BoxCollider2D:
|
||||
m_CorrespondingSourceObject: {fileID: -9173128868689176959, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
|
||||
m_PrefabInstance: {fileID: 1731914843}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!4 &1329653979 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 8213225719790051476, guid: c95e72e011a10ca43a3cb1bd524cdc19, type: 3}
|
||||
m_PrefabInstance: {fileID: 568219572}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &1331373545
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -208304,7 +208273,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!4 &1465160040
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -208341,7 +208310,7 @@ VFXRenderer:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1465160039}
|
||||
m_Enabled: 1
|
||||
m_Enabled: 0
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
m_DynamicOccludee: 1
|
||||
@ -208388,7 +208357,7 @@ VisualEffect:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1465160039}
|
||||
m_Enabled: 1
|
||||
m_Asset: {fileID: 8926484042661614526, guid: 484c0e7574383dd4b9e863848199e369, type: 3}
|
||||
m_Asset: {fileID: 8926484042661614526, guid: f81f06c7755accd42bf8e15cf6687eee, type: 3}
|
||||
m_InitialEventName: OnPlay
|
||||
m_InitialEventNameOverriden: 0
|
||||
m_StartSeed: 0
|
||||
@ -208618,6 +208587,165 @@ BoxCollider2D:
|
||||
m_AutoTiling: 0
|
||||
m_Size: {x: 3, y: 7}
|
||||
m_EdgeRadius: 0
|
||||
--- !u!1 &1476321861
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1476321862}
|
||||
- component: {fileID: 1476321863}
|
||||
- component: {fileID: 1476321866}
|
||||
- component: {fileID: 1476321865}
|
||||
- component: {fileID: 1476321864}
|
||||
m_Layer: 0
|
||||
m_Name: CollectibleSpawner
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1476321862
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1476321861}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 13, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 94085880}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!61 &1476321863
|
||||
BoxCollider2D:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1476321861}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Density: 1
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_ForceSendLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ForceReceiveLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ContactCaptureLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_CallbackLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_IsTrigger: 1
|
||||
m_UsedByEffector: 0
|
||||
m_CompositeOperation: 0
|
||||
m_CompositeOrder: 0
|
||||
m_Offset: {x: -0.5, y: 0.5}
|
||||
m_SpriteTilingProperty:
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
pivot: {x: 0, y: 0}
|
||||
oldSize: {x: 0, y: 0}
|
||||
newSize: {x: 0, y: 0}
|
||||
adaptiveTilingThreshold: 0
|
||||
drawMode: 0
|
||||
adaptiveTiling: 0
|
||||
m_AutoTiling: 0
|
||||
m_Size: {x: 32, y: 4}
|
||||
m_EdgeRadius: 0
|
||||
--- !u!114 &1476321864
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1476321861}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a6b98ac2c91fab545952a401bce4e156, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.EnvironmentalPool
|
||||
--- !u!114 &1476321865
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1476321861}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c3bdb5d5e24e42b43aece26257a11ede, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Behaviour.EnvironmentalCollectibleSpawner
|
||||
fixedCount: 8
|
||||
maxSpawnAttempts: 12
|
||||
spawnDelay: {x: 0.15, y: 0.4}
|
||||
spawnArea: {fileID: 1476321863}
|
||||
areaCenter: {x: 0, y: 0}
|
||||
areaSize: {x: 8, y: 4}
|
||||
scaleRange: {x: 0.6, y: 1}
|
||||
rotationRange: {x: -15, y: 15}
|
||||
minDistanceToPlayer: 1.5
|
||||
maxDistanceToPlayer: 25
|
||||
spawnEntries:
|
||||
- prefab: {fileID: -6130303908877127613, guid: b1002877f2c88eb47911b3d3b30db95c, type: 3}
|
||||
weight: 100
|
||||
overridePoints: 0
|
||||
pointsOverride: 0
|
||||
overrideLifetime: 0
|
||||
lifetimeOverride: 0
|
||||
limit: 0
|
||||
namedEntries:
|
||||
- key: leaf
|
||||
entry:
|
||||
prefab: {fileID: -6130303908877127613, guid: b1002877f2c88eb47911b3d3b30db95c, type: 3}
|
||||
weight: 100
|
||||
overridePoints: 0
|
||||
pointsOverride: 0
|
||||
overrideLifetime: 0
|
||||
lifetimeOverride: 0
|
||||
limit: 0
|
||||
hardReplace: 0
|
||||
- key: bat
|
||||
entry:
|
||||
prefab: {fileID: 7133210491211008283, guid: 76ff5cdfacce8e04ca29a050fd1b41db, type: 3}
|
||||
weight: 100
|
||||
overridePoints: 0
|
||||
pointsOverride: 0
|
||||
overrideLifetime: 0
|
||||
lifetimeOverride: 0
|
||||
limit: 0
|
||||
hardReplace: 0
|
||||
--- !u!114 &1476321866
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1476321861}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a0e43ee58f3b28e459ebefd25720dcfe, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::RR.Game2D.Core.Managers.Parallax
|
||||
parallaxFactorX: 1
|
||||
parallaxFactorY: 1
|
||||
lockYAxis: 0
|
||||
--- !u!1 &1476793369
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -210554,7 +210682,7 @@ Transform:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1528420253}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
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
|
||||
@ -212141,6 +212269,19 @@ MonoBehaviour:
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
- m_Target: {fileID: 1476321865}
|
||||
m_TargetAssemblyTypeName: RR.Game2D.Behaviour.EnvironmentalCollectibleSpawner,
|
||||
Assembly-CSharp
|
||||
m_MethodName: SetEntryByName
|
||||
m_Mode: 5
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument: leaf
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
OnEventTriggerExit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using RR.Common.Entities.Props;
|
||||
using RR.Game2D.Core.Managers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
@ -14,50 +16,68 @@ namespace RR.Game2D.Behaviour
|
||||
public int weight;
|
||||
public bool overridePoints;
|
||||
public int pointsOverride;
|
||||
public bool overrideLifetime;
|
||||
[Tooltip("Время жизни в секундах. 0 или меньше — бесконечно.")]
|
||||
public float lifetimeOverride;
|
||||
public int limit;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct NamedSpawnEntry
|
||||
{
|
||||
[Tooltip("Имя, по которому триггер выбирает этот entry (например \"leaves\", \"bats\").")]
|
||||
public string key;
|
||||
public SpawnEntry entry;
|
||||
[Tooltip("Мгновенно убрать все чужие объекты при активации.")]
|
||||
public bool hardReplace;
|
||||
}
|
||||
|
||||
[Header("Spawn Count")]
|
||||
[SerializeField] private int fixedCount = 8;
|
||||
[SerializeField] private int maxSpawnAttempts = 12;
|
||||
[Tooltip("Задержка между спауном каждого объекта (секунды). 0 — все сразу.")]
|
||||
[SerializeField] private Vector2 spawnDelay = new(0.15f, 0.4f);
|
||||
|
||||
[Header("Spawn Area")]
|
||||
[SerializeField] private BoxCollider2D spawnArea;
|
||||
[SerializeField] private Vector2 areaCenter;
|
||||
[SerializeField] private Vector2 areaSize = new(8f, 4f);
|
||||
|
||||
[Header("Random Scale & Rotation")]
|
||||
[Tooltip("Минимальный и максимальный uniform-масштаб при спауне.")]
|
||||
[SerializeField] private Vector2 scaleRange = new(0.8f, 1.2f);
|
||||
[Tooltip("Минимальный и максимальный угол поворота по Z (градусы) при спауне.")]
|
||||
[SerializeField] private Vector2 rotationRange = new(0f, 0f);
|
||||
|
||||
[Header("Player")]
|
||||
[SerializeField] private Transform player;
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
[SerializeField] private float minDistanceToPlayer = 1.5f;
|
||||
[SerializeField] private float maxDistanceToPlayer = 25f;
|
||||
|
||||
private Transform _player;
|
||||
|
||||
[Header("Prefabs")]
|
||||
[SerializeField] private bool usePooling = true;
|
||||
[SerializeField] private SpawnEntry[] spawnEntries;
|
||||
|
||||
[Header("Trigger Entries")]
|
||||
[Tooltip("Именованные SpawnEntry для вызова из триггеров. Используйте SetForcedEntryByName(string) в UnityEvent.")]
|
||||
[SerializeField] private NamedSpawnEntry[] namedEntries;
|
||||
|
||||
private readonly HashSet<EnvironmentalCollectibleBase> _activeCollectibles = new();
|
||||
private readonly Dictionary<EnvironmentalCollectibleBase, GameObject> _prefabByCollectible = new();
|
||||
private readonly Dictionary<GameObject, int> _activeCountByPrefab = new();
|
||||
private readonly List<EnvironmentalCollectibleBase> _refreshBuffer = new();
|
||||
|
||||
private EnvironmentalPool _pool;
|
||||
private GameObject _forcedPrefab;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (usePooling)
|
||||
{
|
||||
_pool = GetComponent<EnvironmentalPool>();
|
||||
if (_pool == null)
|
||||
_pool = gameObject.AddComponent<EnvironmentalPool>();
|
||||
}
|
||||
}
|
||||
private SpawnEntry? _forcedEntry;
|
||||
private BoxCollider2D _overrideArea;
|
||||
private Coroutine _spawnCoroutine;
|
||||
private Coroutine _gradualReplaceCoroutine;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ResolvePlayer();
|
||||
WarmupPool();
|
||||
if (GameManager.Instance != null)
|
||||
GameManager.Instance.PlayerSpawned += OnPlayerSpawned;
|
||||
MaintainFixedCount();
|
||||
}
|
||||
|
||||
@ -68,40 +88,239 @@ namespace RR.Game2D.Behaviour
|
||||
if (collectible != null)
|
||||
collectible.Collected -= HandleCollected;
|
||||
}
|
||||
|
||||
if (GameManager.Instance != null)
|
||||
GameManager.Instance.PlayerSpawned -= OnPlayerSpawned;
|
||||
}
|
||||
|
||||
public void SetSingleSpawnPrefab(EnvironmentalCollectibleBase prefab)
|
||||
#region Public API — смена типа спауна
|
||||
|
||||
/// <summary>
|
||||
/// Мягко переключает спаунер на prefab (компонент). Старые объекты доживают.
|
||||
/// </summary>
|
||||
public void SetForcedPrefab(EnvironmentalCollectibleBase prefab)
|
||||
{
|
||||
_forcedPrefab = prefab != null ? prefab.gameObject : null;
|
||||
ApplyForced(prefab != null ? prefab.gameObject : null, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Жёстко переключает на prefab (компонент). Все «чужие» объекты убираются сразу.
|
||||
/// </summary>
|
||||
public void SetForcedPrefabHard(EnvironmentalCollectibleBase prefab)
|
||||
{
|
||||
ApplyForced(prefab != null ? prefab.gameObject : null, null, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Мягко переключает спаунер на prefab (GameObject).
|
||||
/// Удобно вызывать из UnityEvent — просто перетащить prefab в инспекторе.
|
||||
/// </summary>
|
||||
public void SetForcedPrefabByObject(GameObject prefabObject)
|
||||
{
|
||||
ApplyForced(prefabObject, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Жёстко переключает на prefab (GameObject). Все «чужие» объекты убираются сразу.
|
||||
/// </summary>
|
||||
public void SetForcedPrefabByObjectHard(GameObject prefabObject)
|
||||
{
|
||||
ApplyForced(prefabObject, null, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Мягко переключает на prefab по индексу в массиве spawnEntries.
|
||||
/// </summary>
|
||||
public void SetForcedPrefabByIndex(int index)
|
||||
{
|
||||
if (!TryGetEntry(index, out SpawnEntry entry)) return;
|
||||
ApplyForced(entry.prefab.gameObject, entry, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Жёстко переключает на prefab по индексу — все «чужие» убираются сразу.
|
||||
/// </summary>
|
||||
public void SetForcedPrefabHardByIndex(int index)
|
||||
{
|
||||
if (!TryGetEntry(index, out SpawnEntry entry)) return;
|
||||
ApplyForced(entry.prefab.gameObject, entry, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переключает спаунер на полностью кастомный SpawnEntry (из кода).
|
||||
/// </summary>
|
||||
public void SetForcedEntry(SpawnEntry entry, bool hardReplace = false)
|
||||
{
|
||||
if (entry.prefab == null) return;
|
||||
ApplyForced(entry.prefab.gameObject, entry, hardReplace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переключает спаунер по строковому ключу из массива namedEntries.
|
||||
/// Вызывайте из UnityEvent (EventTrigger) — Unity покажет поле ввода строки.
|
||||
/// </summary>
|
||||
public void SetForcedEntryByName(string key)
|
||||
{
|
||||
if (namedEntries == null || string.IsNullOrEmpty(key)) return;
|
||||
|
||||
for (int i = 0; i < namedEntries.Length; i++)
|
||||
{
|
||||
if (string.Equals(namedEntries[i].key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NamedSpawnEntry named = namedEntries[i];
|
||||
if (named.entry.prefab == null) return;
|
||||
ApplyForced(named.entry.prefab.gameObject, named.entry, named.hardReplace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[{nameof(EnvironmentalCollectibleSpawner)}] Named entry '{key}' not found.", this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Мягко переключает тип спауна по ключу из namedEntries.
|
||||
/// Существующие объекты другого типа убираются постепенно (по одному с задержкой)
|
||||
/// и заменяются новыми.
|
||||
/// </summary>
|
||||
public void SetEntryByName(string key)
|
||||
{
|
||||
if (namedEntries == null || string.IsNullOrEmpty(key)) return;
|
||||
|
||||
for (int i = 0; i < namedEntries.Length; i++)
|
||||
{
|
||||
if (string.Equals(namedEntries[i].key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NamedSpawnEntry named = namedEntries[i];
|
||||
if (named.entry.prefab == null) return;
|
||||
|
||||
_forcedPrefab = named.entry.prefab.gameObject;
|
||||
_forcedEntry = named.entry;
|
||||
|
||||
StartGradualReplace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[{nameof(EnvironmentalCollectibleSpawner)}] Named entry '{key}' not found.", this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает спаунер в смешанный режим (все записи из spawnEntries).
|
||||
/// </summary>
|
||||
public void ClearForcedPrefab()
|
||||
{
|
||||
_forcedPrefab = null;
|
||||
_forcedEntry = null;
|
||||
MaintainFixedCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Временно подменяет область спауна (например, из триггер-зоны).
|
||||
/// Передайте null чтобы вернуть оригинальную область.
|
||||
/// </summary>
|
||||
public void SetOverrideArea(BoxCollider2D area)
|
||||
{
|
||||
_overrideArea = area;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void ApplyForced(GameObject prefabObj, SpawnEntry? entry, bool hardReplace)
|
||||
{
|
||||
_forcedPrefab = prefabObj;
|
||||
_forcedEntry = entry;
|
||||
|
||||
RefreshActiveCollectibles();
|
||||
MaintainFixedCount();
|
||||
}
|
||||
|
||||
public void SetSingleSpawnPrefabByIndex(int index)
|
||||
private bool TryGetEntry(int index, out SpawnEntry entry)
|
||||
{
|
||||
entry = default;
|
||||
if (spawnEntries == null || index < 0 || index >= spawnEntries.Length)
|
||||
return;
|
||||
return false;
|
||||
|
||||
SpawnEntry entry = spawnEntries[index];
|
||||
SetSingleSpawnPrefab(entry.prefab);
|
||||
}
|
||||
|
||||
public void ClearSingleSpawnPrefab()
|
||||
{
|
||||
_forcedPrefab = null;
|
||||
MaintainFixedCount();
|
||||
entry = spawnEntries[index];
|
||||
return entry.prefab != null;
|
||||
}
|
||||
|
||||
private void MaintainFixedCount()
|
||||
{
|
||||
if (_spawnCoroutine != null)
|
||||
StopCoroutine(_spawnCoroutine);
|
||||
|
||||
if (!gameObject.activeInHierarchy) return;
|
||||
|
||||
_spawnCoroutine = StartCoroutine(SpawnRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator SpawnRoutine()
|
||||
{
|
||||
int safety = fixedCount * Mathf.Max(1, maxSpawnAttempts);
|
||||
bool isFirst = true;
|
||||
|
||||
while (_activeCollectibles.Count < fixedCount && safety > 0)
|
||||
{
|
||||
// Задержка перед каждым спауном (кроме первого — чтобы был хоть один сразу)
|
||||
if (!isFirst && spawnDelay.y > 0f)
|
||||
yield return new WaitForSeconds(UnityEngine.Random.Range(spawnDelay.x, spawnDelay.y));
|
||||
|
||||
isFirst = false;
|
||||
|
||||
if (!TrySpawnOne())
|
||||
break;
|
||||
|
||||
safety--;
|
||||
}
|
||||
|
||||
_spawnCoroutine = null;
|
||||
}
|
||||
|
||||
private void StartGradualReplace()
|
||||
{
|
||||
if (_gradualReplaceCoroutine != null)
|
||||
StopCoroutine(_gradualReplaceCoroutine);
|
||||
|
||||
if (!gameObject.activeInHierarchy) return;
|
||||
|
||||
_gradualReplaceCoroutine = StartCoroutine(GradualReplaceRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator GradualReplaceRoutine()
|
||||
{
|
||||
if (_forcedPrefab == null)
|
||||
{
|
||||
_gradualReplaceCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
string forcedName = _forcedPrefab.name;
|
||||
|
||||
while (true)
|
||||
{
|
||||
EnvironmentalCollectibleBase toRemove = null;
|
||||
foreach (EnvironmentalCollectibleBase c in _activeCollectibles)
|
||||
{
|
||||
if (c == null) continue;
|
||||
if (_prefabByCollectible.TryGetValue(c, out GameObject p) && p.name != forcedName)
|
||||
{
|
||||
toRemove = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove == null) break;
|
||||
|
||||
DespawnCollectible(toRemove);
|
||||
MaintainFixedCount();
|
||||
|
||||
if (spawnDelay.y > 0f)
|
||||
yield return new WaitForSeconds(UnityEngine.Random.Range(spawnDelay.x, spawnDelay.y));
|
||||
else
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_gradualReplaceCoroutine = null;
|
||||
}
|
||||
|
||||
private bool TrySpawnOne()
|
||||
@ -115,25 +334,31 @@ namespace RR.Game2D.Behaviour
|
||||
return false;
|
||||
|
||||
GameObject prefab = entry.prefab.gameObject;
|
||||
GameObject instance = usePooling && _pool != null
|
||||
? _pool.Get(prefab, spawnPosition, Quaternion.identity)
|
||||
: Instantiate(prefab, spawnPosition, Quaternion.identity, transform);
|
||||
GameObject instance = Instantiate(prefab, spawnPosition, Quaternion.identity, transform);
|
||||
|
||||
if (instance == null) return false;
|
||||
|
||||
// Случайный масштаб и поворот
|
||||
float scale = UnityEngine.Random.Range(scaleRange.x, scaleRange.y);
|
||||
instance.transform.localScale = Vector3.one * scale;
|
||||
|
||||
float angle = UnityEngine.Random.Range(rotationRange.x, rotationRange.y);
|
||||
instance.transform.rotation = Quaternion.Euler(0f, 0f, angle);
|
||||
|
||||
EnvironmentalCollectibleBase collectible = instance.GetComponent<EnvironmentalCollectibleBase>();
|
||||
if (collectible == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(EnvironmentalCollectibleSpawner)}] Prefab {prefab.name} is missing EnvironmentalCollectibleBase.", prefab);
|
||||
if (!usePooling)
|
||||
Destroy(instance);
|
||||
Destroy(instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
collectible.transform.SetParent(transform, true);
|
||||
collectible.Collected -= HandleCollected;
|
||||
collectible.Collected += HandleCollected;
|
||||
collectible.InitializeForSpawn(_pool, prefab, spawnPosition, entry.overridePoints ? entry.pointsOverride : null);
|
||||
collectible.InitializeForSpawn(
|
||||
spawnPosition,
|
||||
entry.overridePoints ? entry.pointsOverride : null,
|
||||
entry.overrideLifetime ? entry.lifetimeOverride : null);
|
||||
|
||||
_activeCollectibles.Add(collectible);
|
||||
_prefabByCollectible[collectible] = prefab;
|
||||
@ -166,10 +391,35 @@ namespace RR.Game2D.Behaviour
|
||||
|
||||
private bool TryPickSpawnEntry(out SpawnEntry entry)
|
||||
{
|
||||
// Если задан полный SpawnEntry — используем его напрямую
|
||||
if (_forcedEntry.HasValue)
|
||||
{
|
||||
SpawnEntry forced = _forcedEntry.Value;
|
||||
if (forced.prefab == null)
|
||||
{
|
||||
entry = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем лимит
|
||||
if (forced.limit > 0 &&
|
||||
_activeCountByPrefab.TryGetValue(forced.prefab.gameObject, out int fc) &&
|
||||
fc >= forced.limit)
|
||||
{
|
||||
entry = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = forced;
|
||||
return true;
|
||||
}
|
||||
|
||||
entry = default;
|
||||
if (spawnEntries == null || spawnEntries.Length == 0)
|
||||
return false;
|
||||
|
||||
string forcedName = _forcedPrefab != null ? _forcedPrefab.name : null;
|
||||
|
||||
int totalWeight = 0;
|
||||
for (int i = 0; i < spawnEntries.Length; i++)
|
||||
{
|
||||
@ -177,7 +427,7 @@ namespace RR.Game2D.Behaviour
|
||||
if (current.prefab == null || current.weight <= 0) continue;
|
||||
|
||||
GameObject prefab = current.prefab.gameObject;
|
||||
if (_forcedPrefab != null && prefab != _forcedPrefab) continue;
|
||||
if (forcedName != null && prefab.name != forcedName) continue;
|
||||
|
||||
if (current.limit > 0 &&
|
||||
_activeCountByPrefab.TryGetValue(prefab, out int activeCount) &&
|
||||
@ -189,6 +439,17 @@ namespace RR.Game2D.Behaviour
|
||||
totalWeight += current.weight;
|
||||
}
|
||||
|
||||
// Фильтрация по _forcedPrefab — если prefab не найден в spawnEntries, создаём временный entry
|
||||
if (totalWeight <= 0 && _forcedPrefab != null)
|
||||
{
|
||||
EnvironmentalCollectibleBase comp = _forcedPrefab.GetComponent<EnvironmentalCollectibleBase>();
|
||||
if (comp != null)
|
||||
{
|
||||
entry = new SpawnEntry { prefab = comp, weight = 1 };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalWeight <= 0)
|
||||
return false;
|
||||
|
||||
@ -199,7 +460,7 @@ namespace RR.Game2D.Behaviour
|
||||
if (current.prefab == null || current.weight <= 0) continue;
|
||||
|
||||
GameObject prefab = current.prefab.gameObject;
|
||||
if (_forcedPrefab != null && prefab != _forcedPrefab) continue;
|
||||
if (forcedName != null && prefab.name != forcedName) continue;
|
||||
|
||||
if (current.limit > 0 &&
|
||||
_activeCountByPrefab.TryGetValue(prefab, out int activeCount) &&
|
||||
@ -229,10 +490,10 @@ namespace RR.Game2D.Behaviour
|
||||
float y = UnityEngine.Random.Range(bounds.min.y, bounds.max.y);
|
||||
spawnPosition = new Vector3(x, y, 0f);
|
||||
|
||||
if (player == null)
|
||||
if (_player == null)
|
||||
return true;
|
||||
|
||||
float distance = Vector2.Distance(player.position, spawnPosition);
|
||||
float distance = Vector2.Distance(_player.position, spawnPosition);
|
||||
if (distance < minDistanceToPlayer) continue;
|
||||
if (maxDistanceToPlayer > 0f && distance > maxDistanceToPlayer) continue;
|
||||
|
||||
@ -245,6 +506,9 @@ namespace RR.Game2D.Behaviour
|
||||
|
||||
private Bounds GetSpawnBounds()
|
||||
{
|
||||
if (_overrideArea != null)
|
||||
return _overrideArea.bounds;
|
||||
|
||||
if (spawnArea != null)
|
||||
return spawnArea.bounds;
|
||||
|
||||
@ -255,36 +519,37 @@ namespace RR.Game2D.Behaviour
|
||||
|
||||
private void ResolvePlayer()
|
||||
{
|
||||
if (player != null) return;
|
||||
|
||||
GameObject playerObject = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (playerObject != null)
|
||||
player = playerObject.transform;
|
||||
}
|
||||
|
||||
private void WarmupPool()
|
||||
{
|
||||
if (!usePooling || _pool == null || spawnEntries == null) return;
|
||||
|
||||
for (int i = 0; i < spawnEntries.Length; i++)
|
||||
if (GameManager.Instance?.PlayerObj != null)
|
||||
{
|
||||
SpawnEntry entry = spawnEntries[i];
|
||||
if (entry.prefab == null) continue;
|
||||
_pool.Warmup(entry.prefab.gameObject, Mathf.Max(1, fixedCount));
|
||||
_player = GameManager.Instance.PlayerObj.transform;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback на случай, если GameManager ещё не проинициализирован
|
||||
GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
|
||||
if (playerObject != null)
|
||||
_player = playerObject.transform;
|
||||
}
|
||||
|
||||
private void OnPlayerSpawned(GameObject playerObj)
|
||||
{
|
||||
_player = playerObj != null ? playerObj.transform : null;
|
||||
}
|
||||
|
||||
|
||||
private void RefreshActiveCollectibles()
|
||||
{
|
||||
if (_forcedPrefab == null)
|
||||
return;
|
||||
|
||||
string forcedName = _forcedPrefab.name;
|
||||
|
||||
_refreshBuffer.Clear();
|
||||
foreach (EnvironmentalCollectibleBase collectible in _activeCollectibles)
|
||||
{
|
||||
if (collectible == null) continue;
|
||||
|
||||
if (_prefabByCollectible.TryGetValue(collectible, out GameObject prefab) && prefab != _forcedPrefab)
|
||||
if (_prefabByCollectible.TryGetValue(collectible, out GameObject prefab) && prefab.name != forcedName)
|
||||
_refreshBuffer.Add(collectible);
|
||||
}
|
||||
|
||||
@ -306,15 +571,12 @@ namespace RR.Game2D.Behaviour
|
||||
{
|
||||
if (_activeCountByPrefab.TryGetValue(prefab, out int currentCount))
|
||||
_activeCountByPrefab[prefab] = Mathf.Max(0, currentCount - 1);
|
||||
|
||||
if (usePooling && _pool != null)
|
||||
_pool.Return(prefab, collectible.gameObject);
|
||||
else
|
||||
Destroy(collectible.gameObject);
|
||||
}
|
||||
|
||||
_activeCollectibles.Remove(collectible);
|
||||
_prefabByCollectible.Remove(collectible);
|
||||
|
||||
Destroy(collectible.gameObject);
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
|
||||
@ -9,37 +9,52 @@ namespace RR.Common.Entities.Props
|
||||
{
|
||||
[SerializeField] private int points = 1;
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
[SerializeField] private bool usePooling = true;
|
||||
[Tooltip("Время жизни в секундах. 0 или меньше — бесконечно.")]
|
||||
[SerializeField] private float defaultLifetime;
|
||||
|
||||
private bool _collected;
|
||||
private int _defaultPoints;
|
||||
private float _defaultLifetimeBackup;
|
||||
private float _lifetime;
|
||||
private float _age;
|
||||
private Collider2D _trigger;
|
||||
private EnvironmentalPool _ownerPool;
|
||||
private GameObject _originPrefab;
|
||||
private Vector3 _spawnPosition;
|
||||
|
||||
public event Action<EnvironmentalCollectibleBase> Collected;
|
||||
|
||||
public int Points => points;
|
||||
public Vector3 SpawnPosition => _spawnPosition;
|
||||
public bool UsesPooling => usePooling;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_defaultPoints = Mathf.Max(0, points);
|
||||
_defaultLifetimeBackup = defaultLifetime;
|
||||
_trigger = GetComponent<Collider2D>();
|
||||
ValidateTrigger();
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
TickBehaviour(Time.deltaTime);
|
||||
float dt = Time.deltaTime;
|
||||
|
||||
if (_lifetime > 0f && !_collected)
|
||||
{
|
||||
_age += dt;
|
||||
if (_age >= _lifetime)
|
||||
{
|
||||
Despawn();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TickBehaviour(dt);
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
if (points < 0) points = 0;
|
||||
_defaultPoints = points;
|
||||
_defaultLifetimeBackup = defaultLifetime;
|
||||
ValidateTrigger();
|
||||
}
|
||||
|
||||
@ -53,15 +68,14 @@ namespace RR.Common.Entities.Props
|
||||
}
|
||||
|
||||
public void InitializeForSpawn(
|
||||
EnvironmentalPool ownerPool,
|
||||
GameObject originPrefab,
|
||||
Vector3 spawnPosition,
|
||||
int? pointsOverride)
|
||||
int? pointsOverride,
|
||||
float? lifetimeOverride = null)
|
||||
{
|
||||
_ownerPool = ownerPool;
|
||||
_originPrefab = originPrefab;
|
||||
_spawnPosition = spawnPosition;
|
||||
points = pointsOverride.HasValue ? Mathf.Max(0, pointsOverride.Value) : _defaultPoints;
|
||||
_lifetime = lifetimeOverride.HasValue ? lifetimeOverride.Value : _defaultLifetimeBackup;
|
||||
_age = 0f;
|
||||
|
||||
ResetStateForReuse();
|
||||
}
|
||||
@ -69,6 +83,7 @@ namespace RR.Common.Entities.Props
|
||||
public void ResetStateForReuse()
|
||||
{
|
||||
_collected = false;
|
||||
_age = 0f;
|
||||
_spawnPosition = transform.position;
|
||||
OnSpawned();
|
||||
}
|
||||
@ -84,13 +99,21 @@ namespace RR.Common.Entities.Props
|
||||
ScoreSystem.Instance?.RegisterEnvironmentalPoints(points);
|
||||
|
||||
Collected?.Invoke(this);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
if (usePooling && _ownerPool != null && _originPrefab != null)
|
||||
{
|
||||
_ownerPool.Return(_originPrefab, gameObject);
|
||||
return;
|
||||
}
|
||||
/// <summary>
|
||||
/// Деспаун по истечении времени жизни — без начисления очков.
|
||||
/// </summary>
|
||||
private void Despawn()
|
||||
{
|
||||
if (_collected) return;
|
||||
_collected = true;
|
||||
|
||||
OnDespawned();
|
||||
|
||||
|
||||
Collected?.Invoke(this);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
@ -102,6 +125,10 @@ namespace RR.Common.Entities.Props
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDespawned()
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract void TickBehaviour(float dt);
|
||||
|
||||
private void ValidateTrigger()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user