Populate minigame with obstacles and monster spawns (#5)

- Simulated "fake" physics and collisions
- Object pooling for tiles, obstacles and monster spawns
- Base monster scoring with proximity triggers and depth multiplier

Co-authored-by: AlexanderT <alexander@foolhardyhorizons.com>
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: #5
This commit is contained in:
2025-09-21 07:32:56 +00:00
parent 2ec5c3d855
commit 46755fecb3
30 changed files with 3181 additions and 68 deletions

2
.gitignore vendored
View File

@@ -99,6 +99,8 @@ InitTestScene*.unity*
# Auto-generated scenes by play mode tests # Auto-generated scenes by play mode tests
/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity* /[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
.idea/.idea.AppleHillsProduction/.idea/indexLayout.xml
.vscode/extensions.json .vscode/extensions.json
.vscode/launch.json .vscode/launch.json
.vscode/settings.json .vscode/settings.json
.idea/.idea.AppleHillsProduction/.idea/indexLayout.xml

View File

@@ -43,6 +43,9 @@ namespace Editor.Utilities
[Tooltip("Color used for previewing colliders in the scene view")] [Tooltip("Color used for previewing colliders in the scene view")]
private Color previewColor = new Color(0.2f, 1f, 0.3f, 0.5f); private Color previewColor = new Color(0.2f, 1f, 0.3f, 0.5f);
[Tooltip("Layer to assign to GameObjects when colliders are generated")]
private int targetLayer = 0;
private List<Mesh> previewMeshes = new List<Mesh>(); private List<Mesh> previewMeshes = new List<Mesh>();
[MenuItem("Tools/Sprite Collider Generator")] [MenuItem("Tools/Sprite Collider Generator")]
@@ -51,8 +54,29 @@ namespace Editor.Utilities
GetWindow<SpriteColliderGenerator>("Sprite Collider Generator"); GetWindow<SpriteColliderGenerator>("Sprite Collider Generator");
} }
private void OnEnable()
{
// Subscribe to scene change events to clear invalid object references
UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened;
UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed;
// Also subscribe to playmode changes as they can invalidate references
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
// Subscribe to prefab stage changes (Unity 2018.3+)
UnityEditor.SceneManagement.PrefabStage.prefabStageOpened += OnPrefabStageOpened;
UnityEditor.SceneManagement.PrefabStage.prefabStageClosing += OnPrefabStageClosing;
}
private void OnDisable() private void OnDisable()
{ {
// Unsubscribe from events
UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened;
UnityEditor.SceneManagement.EditorSceneManager.sceneClosed -= OnSceneClosed;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
UnityEditor.SceneManagement.PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
UnityEditor.SceneManagement.PrefabStage.prefabStageClosing -= OnPrefabStageClosing;
// Clean up any preview meshes when window is closed // Clean up any preview meshes when window is closed
foreach (var mesh in previewMeshes) foreach (var mesh in previewMeshes)
{ {
@@ -63,7 +87,53 @@ namespace Editor.Utilities
} }
previewMeshes.Clear(); previewMeshes.Clear();
} }
private void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode)
{
// Clear selected objects when a scene is opened
ClearInvalidReferences();
}
private void OnSceneClosed(UnityEngine.SceneManagement.Scene scene)
{
// Clear selected objects when a scene is closed
ClearInvalidReferences();
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
// Clear references when entering/exiting play mode as they become invalid
if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.ExitingPlayMode)
{
ClearInvalidReferences();
}
}
private void OnPrefabStageOpened(UnityEditor.SceneManagement.PrefabStage stage)
{
// Clear selected objects when entering a prefab stage
ClearInvalidReferences();
}
private void OnPrefabStageClosing(UnityEditor.SceneManagement.PrefabStage stage)
{
// Clear selected objects when exiting a prefab stage
ClearInvalidReferences();
}
/// <summary>
/// Clears invalid GameObject references from the selected objects list
/// </summary>
private void ClearInvalidReferences()
{
if (selectedObjects.Count > 0)
{
selectedObjects.Clear();
ClearPreviews(); // Also clear any preview meshes
Repaint(); // Refresh the window UI
}
}
private void OnGUI() private void OnGUI()
{ {
EditorGUILayout.BeginVertical(); EditorGUILayout.BeginVertical();
@@ -146,6 +216,12 @@ namespace Editor.Utilities
new GUIContent("Generate Trigger Colliders", "When enabled, creates trigger colliders instead of solid colliders."), new GUIContent("Generate Trigger Colliders", "When enabled, creates trigger colliders instead of solid colliders."),
generateTriggerColliders); generateTriggerColliders);
// Layer selection
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(new GUIContent("Target Layer:", "Layer to assign to GameObjects when colliders are generated. Leave as 'Nothing' to keep current layer."), GUILayout.Width(180));
targetLayer = EditorGUILayout.LayerField(targetLayer);
EditorGUILayout.EndHorizontal();
// Offset option // Offset option
offsetFromCenter = EditorGUILayout.Toggle( offsetFromCenter = EditorGUILayout.Toggle(
new GUIContent("Offset From Center", "When enabled, allows scaling the collider outward or inward from the sprite center."), new GUIContent("Offset From Center", "When enabled, allows scaling the collider outward or inward from the sprite center."),
@@ -445,8 +521,24 @@ namespace Editor.Utilities
polygonCollider.SetPath(i, paths[i]); polygonCollider.SetPath(i, paths[i]);
} }
// Set the layer on the GameObject if a specific layer is selected
SetTargetLayer(targetObject);
return true; return true;
} }
/// <summary>
/// Sets the target layer on the GameObject if a layer is selected
/// </summary>
/// <param name="targetObject">The GameObject to set the layer on</param>
private void SetTargetLayer(GameObject targetObject)
{
if (targetLayer != 0)
{
Undo.RecordObject(targetObject, "Set GameObject Layer");
targetObject.layer = targetLayer;
}
}
private List<Vector2[]> GetSpritePaths(Sprite sprite, float tolerance) private List<Vector2[]> GetSpritePaths(Sprite sprite, float tolerance)
{ {

View File

@@ -0,0 +1,154 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4743746373562280435
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7889589254526244618}
- component: {fileID: 2565520060777160406}
- component: {fileID: 586678365535466516}
- component: {fileID: 3635110434097976059}
m_Layer: 11
m_Name: FloatingObstacle
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7889589254526244618
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.62358, y: 4.23222, z: 0}
m_LocalScale: {x: 5, y: 5, z: 5}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2565520060777160406
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 32718083aef44be2a4318681fcdf5b2e, type: 3}
m_Name:
m_EditorClassIdentifier:
prefabIndex: 0
damage: 1
moveSpeed: 2
enableMovement: 1
spawner: {fileID: 0}
--- !u!212 &586678365535466516
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
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_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_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0}
m_Color: {r: 0.9150943, g: 0, b: 0, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 0.16, y: 0.16}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!61 &3635110434097976059
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
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: 0
m_UsedByEffector: 0
m_CompositeOperation: 0
m_CompositeOrder: 0
m_Offset: {x: 0, y: 0}
m_SpriteTilingProperty:
border: {x: 0.049999997, y: 0.049999997, z: 0.049999997, w: 0.049999997}
pivot: {x: 0.5, y: 0.5}
oldSize: {x: 0.16, y: 0.16}
newSize: {x: 0.16, y: 0.16}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 0.16, y: 0.16}
m_EdgeRadius: 0

View File

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

View File

@@ -12,7 +12,7 @@ GameObject:
- component: {fileID: 8447572436637192077} - component: {fileID: 8447572436637192077}
- component: {fileID: 4998672042618199381} - component: {fileID: 4998672042618199381}
- component: {fileID: 3714732064953161914} - component: {fileID: 3714732064953161914}
m_Layer: 0 m_Layer: 12
m_Name: QuarryMonster m_Name: QuarryMonster
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}

View File

@@ -10,8 +10,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 7111145574660306503} - component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074} - component: {fileID: 3889795708575321074}
- component: {fileID: 7249681423942450184} - component: {fileID: 8688779957837852254}
m_Layer: 0 m_Layer: 6
m_Name: Left_Tile2_0 m_Name: Left_Tile2_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -88,7 +88,7 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &7249681423942450184 --- !u!60 &8688779957837852254
PolygonCollider2D: PolygonCollider2D:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0} m_CorrespondingSourceObject: {fileID: 0}
@@ -155,8 +155,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 1003080013996268193} - component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481} - component: {fileID: 4856205316150460481}
- component: {fileID: 2843103852598642252} - component: {fileID: 8615330168962481848}
m_Layer: 0 m_Layer: 6
m_Name: Right_Tile1_0 m_Name: Right_Tile1_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -233,7 +233,7 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &2843103852598642252 --- !u!60 &8615330168962481848
PolygonCollider2D: PolygonCollider2D:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0} m_CorrespondingSourceObject: {fileID: 0}
@@ -282,8 +282,6 @@ PolygonCollider2D:
- - {x: -0.48499998, y: 2.5} - - {x: -0.48499998, y: 2.5}
- {x: -0.49499997, y: 2.19} - {x: -0.49499997, y: 2.19}
- {x: 0.035, y: 1.66} - {x: 0.035, y: 1.66}
- {x: 0.035, y: 1.42}
- {x: -0.24499999, y: 1.15}
- {x: -0.285, y: 0.90999997} - {x: -0.285, y: 0.90999997}
- {x: -0.13499999, y: 0.45999998} - {x: -0.13499999, y: 0.45999998}
- {x: -1.115, y: -0.03} - {x: -1.115, y: -0.03}

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 7111145574660306503} - component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074} - component: {fileID: 3889795708575321074}
m_Layer: 0 - component: {fileID: 8698932925238153222}
m_Layer: 6
m_Name: Left_Tile2_0 m_Name: Left_Tile2_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -87,6 +88,63 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &8698932925238153222
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 864595161669782950}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.37, y: 5}
newSize: {x: 2.37, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: 0.575, y: -1.5}
- {x: 0.185, y: -0.63}
- {x: 1.145, y: 0.85999995}
- {x: 1.185, y: 1.41}
- {x: 0.835, y: 1.8499999}
- {x: 0.635, y: 2.5}
- {x: -1.185, y: 2.5}
- {x: -1.185, y: -2.5}
- {x: 0.635, y: -2.5}
- {x: 0.625, y: -1.64}
m_UseDelaunayMesh: 0
--- !u!1 &2171518497100337372 --- !u!1 &2171518497100337372
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -97,7 +155,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 1003080013996268193} - component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481} - component: {fileID: 4856205316150460481}
m_Layer: 0 - component: {fileID: 1368284191430742655}
m_Layer: 6
m_Name: Right_Tile1_0 m_Name: Right_Tile1_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -174,6 +233,67 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &1368284191430742655
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2171518497100337372}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.65, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: -0.48499998, y: 2.5}
- {x: -0.49499997, y: 2.19}
- {x: 0.035, y: 1.66}
- {x: -0.285, y: 0.90999997}
- {x: -0.13499999, y: 0.45999998}
- {x: -1.115, y: -0.03}
- {x: -1.3249999, y: -0.35}
- {x: -1.2049999, y: -0.84999996}
- {x: -0.36499998, y: -1.0699999}
- {x: -0.585, y: -1.27}
- {x: -0.625, y: -1.65}
- {x: -0.48499998, y: -2.5}
- {x: 1.3249999, y: -2.5}
- {x: 1.3249999, y: 2.5}
m_UseDelaunayMesh: 0
--- !u!1 &2956826569642009690 --- !u!1 &2956826569642009690
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 7111145574660306503} - component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074} - component: {fileID: 3889795708575321074}
m_Layer: 0 - component: {fileID: 6919687589473331973}
m_Layer: 6
m_Name: Left_Tile2_0 m_Name: Left_Tile2_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &6919687589473331973
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 864595161669782950}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.37, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: 0.635, y: -1.78}
- {x: 0.585, y: -1.27}
- {x: 0.36499998, y: -1.14}
- {x: 0.415, y: -1.05}
- {x: 1.2149999, y: -0.85999995}
- {x: 1.3249999, y: -0.7}
- {x: 1.3249999, y: -0.35}
- {x: 1.145, y: -0.049999997}
- {x: 0.13499999, y: 0.45999998}
- {x: 0.275, y: 0.87}
- {x: 0.265, y: 1.0699999}
- {x: -0.044999998, y: 1.41}
- {x: -0.035, y: 1.66}
- {x: 0.48499998, y: 2.2}
- {x: 0.49499997, y: 2.48}
- {x: -1.3249999, y: 2.5}
- {x: -1.3249999, y: -2.5}
- {x: 0.505, y: -2.5}
- {x: 0.555, y: -2.12}
m_UseDelaunayMesh: 0
--- !u!1 &2171518497100337372 --- !u!1 &2171518497100337372
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 1003080013996268193} - component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481} - component: {fileID: 4856205316150460481}
m_Layer: 0 - component: {fileID: 1911291775322313535}
m_Layer: 6
m_Name: Right_Tile1_0 m_Name: Right_Tile1_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -174,6 +242,65 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &1911291775322313535
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2171518497100337372}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.37, y: 5}
newSize: {x: 2.65, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: -0.635, y: 2.5}
- {x: -0.835, y: 1.8499999}
- {x: -1.185, y: 1.42}
- {x: -1.185, y: 1.06}
- {x: -1.055, y: 0.69}
- {x: -0.185, y: -0.65999997}
- {x: -0.175, y: -0.78999996}
- {x: -0.585, y: -1.49}
- {x: -0.675, y: -2.08}
- {x: -0.635, y: -2.5}
- {x: 1.185, y: -2.5}
- {x: 1.185, y: 2.5}
m_UseDelaunayMesh: 0
--- !u!1 &2956826569642009690 --- !u!1 &2956826569642009690
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 7111145574660306503} - component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074} - component: {fileID: 3889795708575321074}
m_Layer: 0 - component: {fileID: 3320970211105392876}
m_Layer: 6
m_Name: Left_Tile2_0 m_Name: Left_Tile2_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &3320970211105392876
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 864595161669782950}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.37, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: 0.635, y: -1.78}
- {x: 0.585, y: -1.27}
- {x: 0.36499998, y: -1.14}
- {x: 0.415, y: -1.05}
- {x: 1.2149999, y: -0.85999995}
- {x: 1.3249999, y: -0.7}
- {x: 1.3249999, y: -0.35}
- {x: 1.145, y: -0.049999997}
- {x: 0.13499999, y: 0.45999998}
- {x: 0.275, y: 0.87}
- {x: 0.265, y: 1.0699999}
- {x: -0.044999998, y: 1.41}
- {x: -0.035, y: 1.66}
- {x: 0.48499998, y: 2.2}
- {x: 0.49499997, y: 2.48}
- {x: -1.3249999, y: 2.5}
- {x: -1.3249999, y: -2.5}
- {x: 0.505, y: -2.5}
- {x: 0.555, y: -2.12}
m_UseDelaunayMesh: 0
--- !u!1 &2171518497100337372 --- !u!1 &2171518497100337372
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 1003080013996268193} - component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481} - component: {fileID: 4856205316150460481}
m_Layer: 0 - component: {fileID: 3931598674086383704}
m_Layer: 6
m_Name: Right_Tile1_0 m_Name: Right_Tile1_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -174,6 +242,65 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &3931598674086383704
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2171518497100337372}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.37, y: 5}
newSize: {x: 2.65, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: -0.635, y: 2.5}
- {x: -0.835, y: 1.8499999}
- {x: -1.185, y: 1.42}
- {x: -1.185, y: 1.06}
- {x: -1.055, y: 0.69}
- {x: -0.185, y: -0.65999997}
- {x: -0.175, y: -0.78999996}
- {x: -0.585, y: -1.49}
- {x: -0.675, y: -2.08}
- {x: -0.635, y: -2.5}
- {x: 1.185, y: -2.5}
- {x: 1.185, y: 2.5}
m_UseDelaunayMesh: 0
--- !u!1 &2956826569642009690 --- !u!1 &2956826569642009690
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 7111145574660306503} - component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074} - component: {fileID: 3889795708575321074}
m_Layer: 0 - component: {fileID: 1709932086434338450}
m_Layer: 6
m_Name: Left_Tile2_0 m_Name: Left_Tile2_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &1709932086434338450
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 864595161669782950}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.37, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: 0.635, y: -1.78}
- {x: 0.585, y: -1.27}
- {x: 0.36499998, y: -1.14}
- {x: 0.415, y: -1.05}
- {x: 1.2149999, y: -0.85999995}
- {x: 1.3249999, y: -0.7}
- {x: 1.3249999, y: -0.35}
- {x: 1.145, y: -0.049999997}
- {x: 0.13499999, y: 0.45999998}
- {x: 0.275, y: 0.87}
- {x: 0.265, y: 1.0699999}
- {x: -0.044999998, y: 1.41}
- {x: -0.035, y: 1.66}
- {x: 0.48499998, y: 2.2}
- {x: 0.49499997, y: 2.48}
- {x: -1.3249999, y: 2.5}
- {x: -1.3249999, y: -2.5}
- {x: 0.505, y: -2.5}
- {x: 0.555, y: -2.12}
m_UseDelaunayMesh: 0
--- !u!1 &2171518497100337372 --- !u!1 &2171518497100337372
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 1003080013996268193} - component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481} - component: {fileID: 4856205316150460481}
m_Layer: 0 - component: {fileID: 9036823987933958098}
m_Layer: 6
m_Name: Right_Tile1_0 m_Name: Right_Tile1_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -174,6 +242,70 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &9036823987933958098
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2171518497100337372}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.65, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: -0.48499998, y: 2.5}
- {x: -0.49499997, y: 2.19}
- {x: 0.035, y: 1.66}
- {x: 0.035, y: 1.42}
- {x: -0.24499999, y: 1.15}
- {x: -0.285, y: 0.90999997}
- {x: -0.13499999, y: 0.45999998}
- {x: -1.115, y: -0.03}
- {x: -1.3249999, y: -0.35}
- {x: -1.3249999, y: -0.71}
- {x: -1.2049999, y: -0.84999996}
- {x: -0.36499998, y: -1.0699999}
- {x: -0.585, y: -1.27}
- {x: -0.625, y: -1.65}
- {x: -0.48499998, y: -2.5}
- {x: 1.3249999, y: -2.5}
- {x: 1.3249999, y: 2.5}
m_UseDelaunayMesh: 0
--- !u!1 &2956826569642009690 --- !u!1 &2956826569642009690
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 7111145574660306503} - component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074} - component: {fileID: 3889795708575321074}
m_Layer: 0 - component: {fileID: 6967660003360049346}
m_Layer: 6
m_Name: Left_Tile2_0 m_Name: Left_Tile2_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &6967660003360049346
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 864595161669782950}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.37, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: 0.635, y: -1.78}
- {x: 0.585, y: -1.27}
- {x: 0.36499998, y: -1.14}
- {x: 0.415, y: -1.05}
- {x: 1.2149999, y: -0.85999995}
- {x: 1.3249999, y: -0.7}
- {x: 1.3249999, y: -0.35}
- {x: 1.145, y: -0.049999997}
- {x: 0.13499999, y: 0.45999998}
- {x: 0.275, y: 0.87}
- {x: 0.265, y: 1.0699999}
- {x: -0.044999998, y: 1.41}
- {x: -0.035, y: 1.66}
- {x: 0.48499998, y: 2.2}
- {x: 0.49499997, y: 2.48}
- {x: -1.3249999, y: 2.5}
- {x: -1.3249999, y: -2.5}
- {x: 0.505, y: -2.5}
- {x: 0.555, y: -2.12}
m_UseDelaunayMesh: 0
--- !u!1 &2171518497100337372 --- !u!1 &2171518497100337372
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 1003080013996268193} - component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481} - component: {fileID: 4856205316150460481}
m_Layer: 0 - component: {fileID: 5503039050943823342}
m_Layer: 6
m_Name: Right_Tile1_0 m_Name: Right_Tile1_0
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@@ -174,6 +242,70 @@ SpriteRenderer:
m_WasSpriteAssigned: 1 m_WasSpriteAssigned: 1
m_MaskInteraction: 0 m_MaskInteraction: 0
m_SpriteSortPoint: 0 m_SpriteSortPoint: 0
--- !u!60 &5503039050943823342
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2171518497100337372}
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: 0
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.5, y: 0.5}
oldSize: {x: 2.65, y: 5}
newSize: {x: 2.65, y: 5}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Points:
m_Paths:
- - {x: -0.48499998, y: 2.5}
- {x: -0.49499997, y: 2.19}
- {x: 0.035, y: 1.66}
- {x: 0.035, y: 1.42}
- {x: -0.24499999, y: 1.15}
- {x: -0.285, y: 0.90999997}
- {x: -0.13499999, y: 0.45999998}
- {x: -1.115, y: -0.03}
- {x: -1.3249999, y: -0.35}
- {x: -1.3249999, y: -0.71}
- {x: -1.2049999, y: -0.84999996}
- {x: -0.36499998, y: -1.0699999}
- {x: -0.585, y: -1.27}
- {x: -0.625, y: -1.65}
- {x: -0.48499998, y: -2.5}
- {x: 1.3249999, y: -2.5}
- {x: 1.3249999, y: 2.5}
m_UseDelaunayMesh: 0
--- !u!1 &2956826569642009690 --- !u!1 &2956826569642009690
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -492,6 +492,73 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &323864663
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 323864665}
- component: {fileID: 323864664}
m_Layer: 0
m_Name: ObstacleSpawner
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &323864664
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 323864663}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 49ec62157fd945fab730193e9ea0bff7, type: 3}
m_Name:
m_EditorClassIdentifier:
obstaclePrefabs:
- {fileID: 4743746373562280435, guid: 315a624eb99600444a51bb1d37c51742, type: 3}
spawnInterval: 3
spawnIntervalVariation: 1
maxSpawnAttempts: 10
spawnCollisionRadius: 1
spawnDistanceBelowScreen: 2
spawnRangeX: 8
minMoveSpeed: 1
maxMoveSpeed: 4
useObjectPooling: 1
maxPerPrefabPoolSize: 15
totalMaxPoolSize: 30
tileLayerMask:
serializedVersion: 2
m_Bits: 576
obstacleLayer: 11
onObstacleSpawned:
m_PersistentCalls:
m_Calls: []
onObstacleDestroyed:
m_PersistentCalls:
m_Calls: []
--- !u!4 &323864665
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 323864663}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.66745, y: 0.68592, 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 &424805724 --- !u!1 &424805724
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -531,8 +598,8 @@ MonoBehaviour:
probabilityIncreaseRate: 0.01 probabilityIncreaseRate: 0.01
guaranteedSpawnTime: 10 guaranteedSpawnTime: 10
spawnCooldown: 5 spawnCooldown: 5
basePoints: 100 basePoints: 10
depthMultiplier: 10 depthMultiplier: 2
--- !u!4 &424805726 --- !u!4 &424805726
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -646,9 +713,14 @@ GameObject:
- component: {fileID: 747976397} - component: {fileID: 747976397}
- component: {fileID: 747976398} - component: {fileID: 747976398}
- component: {fileID: 747976399} - component: {fileID: 747976399}
- component: {fileID: 747976400}
- component: {fileID: 747976401}
- component: {fileID: 747976402}
- component: {fileID: 747976403}
- component: {fileID: 747976404}
m_Layer: 0 m_Layer: 0
m_Name: BottleMarine m_Name: BottleMarine
m_TagString: Untagged m_TagString: Player
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
@@ -701,6 +773,181 @@ MonoBehaviour:
verticalAmplitude: 0.2 verticalAmplitude: 0.2
velocitySmoothing: 10 velocitySmoothing: 10
rotationSmoothing: 10 rotationSmoothing: 10
--- !u!114 &747976400
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 747976396}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8222f0e3aeeb4fc4975aaead6cf7afbe, type: 3}
m_Name:
m_EditorClassIdentifier:
damageImmunityDuration: 1
obstacleLayerMask:
serializedVersion: 2
m_Bits: 64
blockInputDuringImmunity: 0
playerCharacter: {fileID: 747976396}
playerController: {fileID: 747976398}
bumpMode: 0
bumpForce: 5
smoothMoveSpeed: 8
bumpCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 2
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
blockInputDuringBump: 1
--- !u!114 &747976401
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 747976396}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c9c18dbd013d42ae8c221e6205e4d49c, type: 3}
m_Name:
m_EditorClassIdentifier:
damageImmunityDuration: 1
obstacleLayerMask:
serializedVersion: 2
m_Bits: 2048
blockInputDuringImmunity: 0
playerCharacter: {fileID: 747976396}
playerController: {fileID: 747976398}
--- !u!60 &747976402
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 747976396}
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_Points:
m_Paths:
- - {x: -0.7990411, y: 2.6669068}
- {x: -0.9294033, y: 2.502293}
- {x: -1.005722, y: 2.1038642}
- {x: -0.9038467, y: 1.652195}
- {x: -0.7700032, y: 1.5415951}
- {x: 0.28712562, y: 1.5507066}
- {x: 0.48471105, y: 1.6954134}
- {x: 0.5636723, y: 1.8504347}
- {x: 0.63941646, y: 1.8455681}
- {x: 0.69998366, y: 1.761524}
- {x: 0.95461226, y: 1.7980554}
- {x: 1.0003967, y: 2.1063333}
- {x: 0.94308287, y: 2.433347}
- {x: 0.8147154, y: 2.464337}
- {x: 0.6922814, y: 2.465095}
- {x: 0.6549096, y: 2.3498883}
- {x: 0.5677627, y: 2.3522122}
- {x: 0.38462818, y: 2.6102822}
- {x: 0.20396256, y: 2.6596293}
m_UseDelaunayMesh: 0
--- !u!50 &747976403
Rigidbody2D:
serializedVersion: 5
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 747976396}
m_BodyType: 1
m_Simulated: 1
m_UseFullKinematicContacts: 0
m_UseAutoMass: 0
m_Mass: 1
m_LinearDamping: 0
m_AngularDamping: 0.05
m_GravityScale: 1
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_Interpolate: 0
m_SleepingMode: 1
m_CollisionDetection: 0
m_Constraints: 0
--- !u!114 &747976404
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 747976396}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d8ea29cc80524de8affe17b930cd75c1, type: 3}
m_Name:
m_EditorClassIdentifier:
damageBlinkColor: {r: 1, g: 0, b: 0, a: 1}
blinkRate: 0.15
damageColorAlpha: 0.7
targetSpriteRenderer: {fileID: 730962734}
--- !u!1 &824396214 --- !u!1 &824396214
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -1847,3 +2094,4 @@ SceneRoots:
- {fileID: 424805726} - {fileID: 424805726}
- {fileID: 116234201} - {fileID: 116234201}
- {fileID: 824396217} - {fileID: 824396217}
- {fileID: 323864665}

View File

@@ -1,30 +1,38 @@
using UnityEngine; using UnityEngine;
using System.Collections;
using Pooling; using Pooling;
namespace Minigames.DivingForPictures namespace Minigames.DivingForPictures
{ {
/// <summary> /// <summary>
/// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment. /// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment.
/// Uses coroutines for better performance instead of Update() calls.
/// </summary> /// </summary>
public class Bubble : MonoBehaviour, IPoolableWithReference<BubblePool> public class Bubble : MonoBehaviour, IPoolableWithReference<BubblePool>
{ {
public float speed = 1f; public float speed = 1f;
public float wobbleSpeed = 1f; public float wobbleSpeed = 1f;
private SpriteRenderer spriteRenderer; private SpriteRenderer spriteRenderer;
private SpriteRenderer bubbleSpriteRenderer; // Renamed from bottleSpriteRenderer private SpriteRenderer bubbleSpriteRenderer;
private float timeOffset; private float timeOffset;
private float minScale = 0.2f; private float minScale = 0.2f;
private float maxScale = 1.2f; private float maxScale = 1.2f;
private float baseScale = 1f; // Added to store the initial scale private float baseScale = 1f;
private Camera mainCamera; // Cache camera reference private Camera mainCamera;
private BubblePool parentPool; // Reference to the pool this bubble came from private BubblePool parentPool;
// Coroutine references
private Coroutine _movementCoroutine;
private Coroutine _wobbleCoroutine;
private Coroutine _offScreenCheckCoroutine;
void Awake() void Awake()
{ {
// Cache references and randomize time offset for wobble // Cache references and randomize time offset for wobble
spriteRenderer = GetComponent<SpriteRenderer>(); spriteRenderer = GetComponent<SpriteRenderer>();
timeOffset = Random.value * 100f; timeOffset = Random.value * 100f;
// Find the child named "BubbleSprite" and get its SpriteRenderer // Find the child named "BubbleSprite" and get its SpriteRenderer
Transform bubbleSpriteTransform = transform.Find("BubbleSprite"); Transform bubbleSpriteTransform = transform.Find("BubbleSprite");
if (bubbleSpriteTransform != null) if (bubbleSpriteTransform != null)
@@ -36,20 +44,101 @@ namespace Minigames.DivingForPictures
mainCamera = Camera.main; mainCamera = Camera.main;
} }
void Update() private void OnEnable()
{ {
// Move bubble upward StartBubbleBehavior();
transform.position += Vector3.up * (speed * Time.deltaTime); }
// Wobble effect (smooth oscillation between min and max scale) private void OnDisable()
float t = (Mathf.Sin((Time.time + timeOffset) * wobbleSpeed) + 1f) * 0.5f; // t in [0,1] {
float wobbleFactor = Mathf.Lerp(minScale, maxScale, t); StopBubbleBehavior();
transform.localScale = Vector3.one * (baseScale * wobbleFactor); }
// Destroy when off screen - using cached camera reference /// <summary>
if (mainCamera != null && transform.position.y > mainCamera.orthographicSize + 2f) /// Starts all bubble behavior coroutines
/// </summary>
private void StartBubbleBehavior()
{
_movementCoroutine = StartCoroutine(MovementCoroutine());
_wobbleCoroutine = StartCoroutine(WobbleCoroutine());
_offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine());
}
/// <summary>
/// Stops all bubble behavior coroutines
/// </summary>
private void StopBubbleBehavior()
{
if (_movementCoroutine != null)
{ {
OnBubbleDestroy(); StopCoroutine(_movementCoroutine);
_movementCoroutine = null;
}
if (_wobbleCoroutine != null)
{
StopCoroutine(_wobbleCoroutine);
_wobbleCoroutine = null;
}
if (_offScreenCheckCoroutine != null)
{
StopCoroutine(_offScreenCheckCoroutine);
_offScreenCheckCoroutine = null;
}
}
/// <summary>
/// Coroutine that handles bubble upward movement
/// </summary>
private IEnumerator MovementCoroutine()
{
while (enabled && gameObject.activeInHierarchy)
{
// Move bubble upward
transform.position += Vector3.up * (speed * Time.deltaTime);
// Wait for next frame
yield return null;
}
}
/// <summary>
/// Coroutine that handles the wobble scaling effect
/// </summary>
private IEnumerator WobbleCoroutine()
{
while (enabled && gameObject.activeInHierarchy)
{
// Wobble effect (smooth oscillation between min and max scale)
float t = (Mathf.Sin((Time.time + timeOffset) * wobbleSpeed) + 1f) * 0.5f; // t in [0,1]
float wobbleFactor = Mathf.Lerp(minScale, maxScale, t);
transform.localScale = Vector3.one * (baseScale * wobbleFactor);
// Wait for next frame
yield return null;
}
}
/// <summary>
/// Coroutine that checks if bubble has moved off-screen
/// Runs at a lower frequency for better performance
/// </summary>
private IEnumerator OffScreenCheckCoroutine()
{
const float checkInterval = 0.1f; // Check every 100ms instead of every frame
while (enabled && gameObject.activeInHierarchy)
{
// Check if bubble is off screen
if (mainCamera != null && transform.position.y > mainCamera.orthographicSize + 2f)
{
OnBubbleDestroy();
yield break; // Exit coroutine since bubble is being destroyed
}
// Wait for the check interval
yield return new WaitForSeconds(checkInterval);
} }
} }
@@ -143,6 +232,24 @@ namespace Minigames.DivingForPictures
minScale = min; minScale = min;
maxScale = max; maxScale = max;
} }
/// <summary>
/// Sets the movement speed at runtime
/// </summary>
/// <param name="newSpeed">New movement speed</param>
public void SetSpeed(float newSpeed)
{
speed = newSpeed;
}
/// <summary>
/// Sets the wobble speed at runtime
/// </summary>
/// <param name="newWobbleSpeed">New wobble speed</param>
public void SetWobbleSpeed(float newWobbleSpeed)
{
wobbleSpeed = newWobbleSpeed;
}
/// <summary> /// <summary>
/// Resets the bubble state for reuse from object pool /// Resets the bubble state for reuse from object pool

View File

@@ -0,0 +1,283 @@
using UnityEngine;
using System.Collections;
using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Complete floating obstacle component that handles movement and pooling.
/// Obstacles move upward toward the surface. Collision detection is handled by the player.
/// Once an obstacle hits the player, its collider is disabled to prevent further collisions.
/// Uses coroutines for better performance instead of Update() calls.
/// </summary>
public class FloatingObstacle : MonoBehaviour, IPoolable
{
[Header("Obstacle Properties")]
[Tooltip("Index of the prefab this obstacle was created from")]
[SerializeField] private int prefabIndex;
[Tooltip("Movement speed of this obstacle")]
[SerializeField] private float moveSpeed = 2f;
[Header("Movement")]
[Tooltip("Whether this obstacle moves (can be disabled for static obstacles)")]
[SerializeField] private bool enableMovement = true;
[Header("References")]
[Tooltip("Reference to the spawner that created this obstacle")]
[SerializeField] private ObstacleSpawner spawner;
// Public properties
public int PrefabIndex
{
get => prefabIndex;
set => prefabIndex = value;
}
public float MoveSpeed
{
get => moveSpeed;
set => moveSpeed = value;
}
// Private fields
private Collider2D _collider;
private Camera _mainCamera;
private float _screenTop;
private Coroutine _movementCoroutine;
private Coroutine _offScreenCheckCoroutine;
private void Awake()
{
_collider = GetComponent<Collider2D>();
if (_collider == null)
{
_collider = GetComponentInChildren<Collider2D>();
}
if (_collider == null)
{
Debug.LogError($"[FloatingObstacle] No Collider2D found on {gameObject.name}!");
}
_mainCamera = Camera.main;
}
private void OnEnable()
{
StartObstacleBehavior();
}
private void OnDisable()
{
StopObstacleBehavior();
}
/// <summary>
/// Starts the obstacle behavior coroutines
/// </summary>
private void StartObstacleBehavior()
{
if (enableMovement)
{
_movementCoroutine = StartCoroutine(MovementCoroutine());
}
_offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine());
}
/// <summary>
/// Stops all obstacle behavior coroutines
/// </summary>
private void StopObstacleBehavior()
{
if (_movementCoroutine != null)
{
StopCoroutine(_movementCoroutine);
_movementCoroutine = null;
}
if (_offScreenCheckCoroutine != null)
{
StopCoroutine(_offScreenCheckCoroutine);
_offScreenCheckCoroutine = null;
}
}
/// <summary>
/// Coroutine that handles obstacle movement
/// </summary>
private IEnumerator MovementCoroutine()
{
while (enabled && gameObject.activeInHierarchy)
{
// Move the obstacle upward
transform.position += Vector3.up * (moveSpeed * Time.deltaTime);
// Wait for next frame
yield return null;
}
}
/// <summary>
/// Coroutine that checks if obstacle has moved off-screen
/// Runs at a lower frequency than movement for better performance
/// </summary>
private IEnumerator OffScreenCheckCoroutine()
{
const float checkInterval = 0.2f; // Check every 200ms instead of every frame
while (enabled && gameObject.activeInHierarchy)
{
CheckIfOffScreen();
// Wait for the check interval
yield return new WaitForSeconds(checkInterval);
}
}
/// <summary>
/// Disables the collider after hitting the player to prevent further collisions
/// This is more performant than tracking hit state
/// </summary>
public void MarkDamageDealt()
{
if (_collider != null && _collider.enabled)
{
_collider.enabled = false;
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} hit player - collider disabled");
}
}
/// <summary>
/// Checks if the obstacle has moved off-screen and should be despawned
/// </summary>
private void CheckIfOffScreen()
{
if (_mainCamera == null)
{
_mainCamera = Camera.main;
if (_mainCamera == null) return;
}
// Always recalculate screen bounds to ensure accuracy
Vector3 topWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.transform.position.z));
_screenTop = topWorldPoint.y;
// Check if obstacle is significantly above screen top (obstacles move upward)
// Use a larger buffer to ensure obstacles are truly off-screen before returning to pool
if (transform.position.y > _screenTop + 5f)
{
Debug.Log($"[FloatingObstacle] {gameObject.name} off-screen at Y:{transform.position.y:F2}, screen top:{_screenTop:F2}");
ReturnToPool();
}
}
/// <summary>
/// Returns this obstacle to the spawner's pool
/// </summary>
private void ReturnToPool()
{
// CRITICAL: Stop all behavior first to prevent race conditions
// This ensures no more off-screen checks or movement happen during pool return
StopObstacleBehavior();
if (spawner != null)
{
spawner.ReturnObstacleToPool(gameObject, prefabIndex);
}
else
{
// Try to find the spawner instead of destroying the object
ObstacleSpawner foundSpawner = FindFirstObjectByType<ObstacleSpawner>();
if (foundSpawner != null)
{
Debug.LogWarning($"[FloatingObstacle] Obstacle {gameObject.name} lost spawner reference, found replacement spawner");
spawner = foundSpawner;
spawner.ReturnObstacleToPool(gameObject, prefabIndex);
}
else
{
// No spawner found - just deactivate the object instead of destroying it
Debug.LogWarning($"[FloatingObstacle] No spawner found for {gameObject.name}, deactivating safely");
gameObject.SetActive(false);
// Move to a safe location to avoid interference
transform.position = new Vector3(1000f, 1000f, 0f);
}
}
}
/// <summary>
/// Sets the spawner reference for this obstacle
/// </summary>
/// <param name="obstacleSpawner">The spawner that created this obstacle</param>
public void SetSpawner(ObstacleSpawner obstacleSpawner)
{
spawner = obstacleSpawner;
}
/// <summary>
/// Called when the obstacle is retrieved from the pool
/// </summary>
public void OnSpawn()
{
// Reset all state first
_screenTop = 0f; // Reset cached screen bounds
_mainCamera = Camera.main; // Refresh camera reference
// Re-enable the collider for reuse
if (_collider != null)
{
_collider.enabled = true;
}
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} spawned from pool");
// Note: Don't start coroutines here - OnEnable() will handle that when SetActive(true) is called
}
/// <summary>
/// Called when the obstacle is returned to the pool
/// </summary>
public void OnDespawn()
{
// Stop all coroutines before returning to pool
StopObstacleBehavior();
// Re-enable collider for next use (in case it was disabled)
if (_collider != null)
{
_collider.enabled = true;
}
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} despawned to pool");
}
/// <summary>
/// Public method to manually trigger return to pool (for external systems)
/// </summary>
public void ForceReturnToPool()
{
ReturnToPool();
}
/// <summary>
/// Public method to enable/disable movement at runtime
/// </summary>
public void SetMovementEnabled(bool enabled)
{
if (enableMovement == enabled) return;
enableMovement = enabled;
// Restart coroutines to apply movement change
if (gameObject.activeInHierarchy)
{
StopObstacleBehavior();
StartObstacleBehavior();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 32718083aef44be2a4318681fcdf5b2e
timeCreated: 1758117709

View File

@@ -0,0 +1,44 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that handles damage from mobile obstacles.
/// Uses trigger-based collision detection with shared immunity state.
/// </summary>
public class ObstacleCollision : PlayerCollisionBehavior
{
protected override void HandleCollisionResponse(Collider2D obstacle)
{
// Mark the obstacle as having dealt damage to prevent multiple hits
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
obstacleComponent.MarkDamageDealt();
}
Debug.Log($"[ObstacleCollision] Player hit by obstacle {obstacle.gameObject.name}");
}
/// <summary>
/// Override to prevent input blocking during damage immunity
/// Since obstacles pass through the player, we don't want to block input
/// </summary>
protected override void OnImmunityStart()
{
Debug.Log($"[ObstacleCollision] Damage immunity started for {damageImmunityDuration} seconds");
// Don't block input for obstacle damage - let player keep moving
// The shared immunity system will handle the collision prevention
}
/// <summary>
/// Override to handle immunity end
/// </summary>
protected override void OnImmunityEnd()
{
Debug.Log($"[ObstacleCollision] Damage immunity ended");
// No special handling needed - shared immunity system handles collider re-enabling
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c9c18dbd013d42ae8c221e6205e4d49c
timeCreated: 1758116850

View File

@@ -0,0 +1,54 @@
using UnityEngine;
using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Manages a pool of floating obstacle objects to reduce garbage collection overhead.
/// Optimized for handling a large number of different obstacle prefab types.
/// </summary>
public class ObstaclePool : MultiPrefabPool<FloatingObstacle>
{
/// <summary>
/// Returns an obstacle to the pool
/// </summary>
/// <param name="obstacle">The obstacle to return to the pool</param>
/// <param name="prefabIndex">The index of the prefab this obstacle was created from</param>
public void ReturnObstacle(GameObject obstacle, int prefabIndex)
{
if (obstacle != null)
{
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
Debug.Log($"[ObstaclePool] Returning obstacle {obstacle.name} to pool");
Return(obstacleComponent, prefabIndex);
}
else
{
Debug.LogWarning($"Attempted to return a GameObject without a FloatingObstacle component: {obstacle.name}");
Destroy(obstacle);
}
}
}
/// <summary>
/// Gets an obstacle from the pool, or creates a new one if the pool is empty
/// </summary>
/// <returns>An obstacle instance ready to use</returns>
public GameObject GetObstacle(int prefabIndex)
{
Debug.Log($"[ObstaclePool] GetObstacle called for prefab index {prefabIndex}");
FloatingObstacle obstacleComponent = Get(prefabIndex);
if (obstacleComponent == null)
{
Debug.LogError($"[ObstaclePool] Get() returned null for prefab index {prefabIndex}");
return null;
}
Debug.Log($"[ObstaclePool] Get() returned obstacle {obstacleComponent.name}, active state: {obstacleComponent.gameObject.activeInHierarchy}");
return obstacleComponent.gameObject;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a53ba79246a94dc4a71d2fb0d7214cfb
timeCreated: 1758116804

View File

@@ -0,0 +1,486 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Spawns and manages mobile obstacles for the diving minigame.
/// Uses object pooling and validates spawn positions to avoid colliding with tiles.
/// </summary>
public class ObstacleSpawner : MonoBehaviour
{
[Header("Obstacle Prefabs")]
[Tooltip("List of possible obstacle prefabs to spawn")]
[SerializeField] private List<GameObject> obstaclePrefabs;
[Header("Spawn Settings")]
[Tooltip("Time interval between spawn attempts (in seconds)")]
[SerializeField] private float spawnInterval = 2f;
[Tooltip("Random variation in spawn timing (+/- seconds)")]
[SerializeField] private float spawnIntervalVariation = 0.5f;
[Tooltip("Maximum number of spawn position attempts before skipping")]
[SerializeField] private int maxSpawnAttempts = 10;
[Tooltip("Radius around spawn point to check for tile collisions")]
[SerializeField] private float spawnCollisionRadius = 1f;
[Header("Spawn Position")]
[Tooltip("How far below screen to spawn obstacles")]
[SerializeField] private float spawnDistanceBelowScreen = 2f;
[Tooltip("Horizontal spawn range (distance from center)")]
[SerializeField] private float spawnRangeX = 8f;
[Header("Obstacle Properties Randomization")]
[Tooltip("Minimum movement speed for spawned obstacles")]
[SerializeField] private float minMoveSpeed = 1f;
[Tooltip("Maximum movement speed for spawned obstacles")]
[SerializeField] private float maxMoveSpeed = 4f;
[Header("Object Pooling")]
[Tooltip("Whether to use object pooling for obstacles")]
[SerializeField] private bool useObjectPooling = true;
[Tooltip("Maximum objects per prefab type in pool")]
[SerializeField] private int maxPerPrefabPoolSize = 3;
[Tooltip("Total maximum pool size across all prefab types")]
[SerializeField] private int totalMaxPoolSize = 15;
[Header("Layer Settings")]
[Tooltip("Layer mask for tile collision detection during spawn position validation")]
[SerializeField] private LayerMask tileLayerMask = -1; // Let user configure which layers to avoid
[Tooltip("Target layer for spawned obstacles - obstacles will be placed on this layer")]
[SerializeField] private int obstacleLayer = 11; // Default to layer 11, but configurable
[Header("Events")]
[Tooltip("Called when an obstacle is spawned")]
public UnityEvent<GameObject> onObstacleSpawned;
[Tooltip("Called when an obstacle is returned to pool")]
public UnityEvent<GameObject> onObstacleDestroyed;
// Private fields
private ObstaclePool _obstaclePool;
private Camera _mainCamera;
private float _screenBottom;
private float _spawnRangeX;
private Coroutine _spawnCoroutine;
private readonly List<GameObject> _activeObstacles = new List<GameObject>();
private int _obstacleCounter = 0; // Counter for unique obstacle naming
private void Awake()
{
_mainCamera = Camera.main;
// Validate obstacle prefabs
ValidateObstaclePrefabs();
if (useObjectPooling)
{
InitializeObjectPool();
}
}
private void Start()
{
CalculateScreenBounds();
StartSpawning();
}
private void OnDestroy()
{
StopSpawning();
}
/// <summary>
/// Validates that all prefabs have required components
/// </summary>
private void ValidateObstaclePrefabs()
{
for (int i = 0; i < obstaclePrefabs.Count; i++)
{
if (obstaclePrefabs[i] == null) continue;
// Check if the prefab has a FloatingObstacle component
if (obstaclePrefabs[i].GetComponent<FloatingObstacle>() == null)
{
Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} does not have a FloatingObstacle component. Adding one automatically.");
obstaclePrefabs[i].AddComponent<FloatingObstacle>();
}
// Ensure the prefab is on the correct layer (using configurable obstacleLayer)
if (obstaclePrefabs[i].layer != obstacleLayer)
{
Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({obstacleLayer}). Setting layer automatically.");
SetLayerRecursively(obstaclePrefabs[i], obstacleLayer);
}
}
}
/// <summary>
/// Sets the layer of a GameObject and all its children
/// </summary>
private void SetLayerRecursively(GameObject obj, int layer)
{
obj.layer = layer;
foreach (Transform child in obj.transform)
{
SetLayerRecursively(child.gameObject, layer);
}
}
/// <summary>
/// Initialize the object pool system
/// </summary>
private void InitializeObjectPool()
{
GameObject poolGO = new GameObject("ObstaclePool");
poolGO.transform.SetParent(transform);
_obstaclePool = poolGO.AddComponent<ObstaclePool>();
// Set up pool configuration
_obstaclePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize;
_obstaclePool.totalMaxPoolSize = totalMaxPoolSize;
// Convert GameObject list to FloatingObstacle list
List<FloatingObstacle> prefabObstacles = new List<FloatingObstacle>(obstaclePrefabs.Count);
foreach (var prefab in obstaclePrefabs)
{
if (prefab != null)
{
FloatingObstacle obstacleComponent = prefab.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
prefabObstacles.Add(obstacleComponent);
}
else
{
Debug.LogError($"Obstacle prefab {prefab.name} is missing a FloatingObstacle component!");
}
}
}
// Initialize the pool
_obstaclePool.Initialize(prefabObstacles);
}
/// <summary>
/// Calculate screen bounds in world space dynamically
/// </summary>
private void CalculateScreenBounds()
{
if (_mainCamera == null)
{
_mainCamera = Camera.main;
if (_mainCamera == null)
{
Debug.LogError("[ObstacleSpawner] No main camera found!");
return;
}
}
// Calculate screen bottom (Y spawn position will be 2 units below this)
Vector3 bottomWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 0f, _mainCamera.nearClipPlane));
_screenBottom = bottomWorldPoint.y;
// Calculate screen width in world units
Vector3 leftWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0f, 0.5f, _mainCamera.nearClipPlane));
Vector3 rightWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(1f, 0.5f, _mainCamera.nearClipPlane));
float screenWidth = rightWorldPoint.x - leftWorldPoint.x;
// Calculate spawn range based on 80% of screen width (40% on each side from center)
_spawnRangeX = (screenWidth * 0.8f) / 2f;
Debug.Log($"[ObstacleSpawner] Screen calculated - Width: {screenWidth:F2}, Bottom: {_screenBottom:F2}, Spawn Range X: ±{_spawnRangeX:F2}");
}
/// <summary>
/// Starts the obstacle spawning coroutine
/// </summary>
public void StartSpawning()
{
if (_spawnCoroutine == null)
{
_spawnCoroutine = StartCoroutine(SpawnObstaclesCoroutine());
Debug.Log("[ObstacleSpawner] Started spawning obstacles");
}
}
/// <summary>
/// Stops the obstacle spawning coroutine
/// </summary>
public void StopSpawning()
{
if (_spawnCoroutine != null)
{
StopCoroutine(_spawnCoroutine);
_spawnCoroutine = null;
Debug.Log("[ObstacleSpawner] Stopped spawning obstacles");
}
}
/// <summary>
/// Main spawning coroutine that runs continuously
/// </summary>
private IEnumerator SpawnObstaclesCoroutine()
{
while (true)
{
// Calculate next spawn time with variation
float nextSpawnTime = spawnInterval + Random.Range(-spawnIntervalVariation, spawnIntervalVariation);
nextSpawnTime = Mathf.Max(0.1f, nextSpawnTime); // Ensure minimum interval
yield return new WaitForSeconds(nextSpawnTime);
// Attempt to spawn an obstacle
TrySpawnObstacle();
}
}
/// <summary>
/// Attempts to spawn an obstacle at a valid position
/// </summary>
private void TrySpawnObstacle()
{
Debug.Log($"[ObstacleSpawner] TrySpawnObstacle called at {Time.time:F2}");
if (obstaclePrefabs == null || obstaclePrefabs.Count == 0)
{
Debug.LogWarning("[ObstacleSpawner] No obstacle prefabs available for spawning!");
return;
}
Vector3 spawnPosition;
bool foundValidPosition = false;
// Try to find a valid spawn position
for (int attempts = 0; attempts < maxSpawnAttempts; attempts++)
{
spawnPosition = GetRandomSpawnPosition();
if (IsValidSpawnPosition(spawnPosition))
{
Debug.Log($"[ObstacleSpawner] Found valid position at {spawnPosition} after {attempts + 1} attempts");
SpawnObstacleAt(spawnPosition);
foundValidPosition = true;
break;
}
else
{
Debug.Log($"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{maxSpawnAttempts})");
}
}
if (!foundValidPosition)
{
Debug.LogWarning($"[ObstacleSpawner] SPAWN MISSED: Could not find valid spawn position after {maxSpawnAttempts} attempts at {Time.time:F2}");
}
}
/// <summary>
/// Gets a random spawn position below the screen
/// </summary>
private Vector3 GetRandomSpawnPosition()
{
// Use dynamically calculated spawn range (80% of screen width)
float randomX = Random.Range(-_spawnRangeX, _spawnRangeX);
// Spawn 2 units below screen bottom
float spawnY = _screenBottom - 2f;
return new Vector3(randomX, spawnY, 0f);
}
/// <summary>
/// Checks if a spawn position is valid (not colliding with tiles)
/// </summary>
private bool IsValidSpawnPosition(Vector3 position)
{
// Use OverlapCircle to check for collisions with tiles
Collider2D collision = Physics2D.OverlapCircle(position, spawnCollisionRadius, tileLayerMask);
return collision == null;
}
/// <summary>
/// Spawns an obstacle at the specified position
/// </summary>
private void SpawnObstacleAt(Vector3 position)
{
Debug.Log($"[ObstacleSpawner] SpawnObstacleAt called for position {position}");
// Select random prefab
int prefabIndex = Random.Range(0, obstaclePrefabs.Count);
GameObject prefab = obstaclePrefabs[prefabIndex];
if (prefab == null)
{
Debug.LogError($"[ObstacleSpawner] SPAWN FAILED: Obstacle prefab at index {prefabIndex} is null!");
return;
}
GameObject obstacle;
// Spawn using pool or instantiate directly
if (useObjectPooling && _obstaclePool != null)
{
Debug.Log($"[ObstacleSpawner] Requesting obstacle from pool (prefab index {prefabIndex})");
obstacle = _obstaclePool.GetObstacle(prefabIndex);
if (obstacle == null)
{
Debug.LogError($"[ObstacleSpawner] SPAWN FAILED: Failed to get obstacle from pool for prefab index {prefabIndex}!");
return;
}
Debug.Log($"[ObstacleSpawner] Got obstacle {obstacle.name} from pool, active state: {obstacle.activeInHierarchy}");
// FORCE ACTIVATION - bypass pool issues
if (!obstacle.activeInHierarchy)
{
Debug.LogWarning($"[ObstacleSpawner] Pool returned inactive object {obstacle.name}, force activating!");
obstacle.SetActive(true);
Debug.Log($"[ObstacleSpawner] After force activation, {obstacle.name} active state: {obstacle.activeInHierarchy}");
}
obstacle.transform.position = position;
obstacle.transform.rotation = prefab.transform.rotation;
obstacle.transform.SetParent(transform);
Debug.Log($"[ObstacleSpawner] After positioning, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}");
}
else
{
Debug.Log($"[ObstacleSpawner] Instantiating new obstacle (pooling disabled)");
obstacle = Instantiate(prefab, position, prefab.transform.rotation, transform);
}
// Assign unique name with counter
_obstacleCounter++;
string oldName = obstacle.name;
obstacle.name = $"Obstacle{_obstacleCounter:D3}";
Debug.Log($"[ObstacleSpawner] Renamed obstacle from '{oldName}' to '{obstacle.name}', active state: {obstacle.activeInHierarchy}");
// Configure the obstacle
ConfigureObstacle(obstacle, prefabIndex);
Debug.Log($"[ObstacleSpawner] After configuration, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}");
// Track active obstacles
_activeObstacles.Add(obstacle);
// Invoke events
onObstacleSpawned?.Invoke(obstacle);
Debug.Log($"[ObstacleSpawner] After events, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}");
Debug.Log($"[ObstacleSpawner] Successfully spawned obstacle {obstacle.name} at {position}. Active count: {_activeObstacles.Count}, Final active state: {obstacle.activeInHierarchy}");
}
/// <summary>
/// Configures an obstacle with randomized properties
/// </summary>
private void ConfigureObstacle(GameObject obstacle, int prefabIndex)
{
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
// Set prefab index
obstacleComponent.PrefabIndex = prefabIndex;
// Randomize properties
obstacleComponent.MoveSpeed = Random.Range(minMoveSpeed, maxMoveSpeed);
// Set spawner reference (since FloatingObstacle has this built-in now)
obstacleComponent.SetSpawner(this);
}
}
/// <summary>
/// Returns an obstacle to the pool (called by FloatingObstacle)
/// </summary>
public void ReturnObstacleToPool(GameObject obstacle, int prefabIndex)
{
if (obstacle == null) return;
Debug.Log($"[ObstacleSpawner] ReturnObstacleToPool called for {obstacle.name}, active state: {obstacle.activeInHierarchy}");
// Remove from active list
_activeObstacles.Remove(obstacle);
// Invoke events
onObstacleDestroyed?.Invoke(obstacle);
// Return to pool or destroy
if (useObjectPooling && _obstaclePool != null)
{
Debug.Log($"[ObstacleSpawner] Returning {obstacle.name} to pool");
_obstaclePool.ReturnObstacle(obstacle, prefabIndex);
}
else
{
Debug.Log($"[ObstacleSpawner] Destroying {obstacle.name} (pooling disabled)");
Destroy(obstacle);
}
}
/// <summary>
/// Public method to change spawn interval at runtime
/// </summary>
public void SetSpawnInterval(float interval)
{
spawnInterval = interval;
}
/// <summary>
/// Public method to change spawn range at runtime
/// </summary>
public void SetSpawnRange(float range)
{
spawnRangeX = range;
}
/// <summary>
/// Public method to set speed range at runtime
/// </summary>
public void SetSpeedRange(float min, float max)
{
minMoveSpeed = min;
maxMoveSpeed = max;
}
/// <summary>
/// Public method to recalculate screen bounds (useful if camera changes)
/// </summary>
public void RecalculateScreenBounds()
{
CalculateScreenBounds();
}
/// <summary>
/// Gets the count of currently active obstacles
/// </summary>
public int ActiveObstacleCount => _activeObstacles.Count;
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
// Only draw if screen bounds have been calculated
if (_spawnRangeX > 0f)
{
// Draw spawn area using dynamic calculations
Gizmos.color = Color.yellow;
Vector3 center = new Vector3(0f, _screenBottom - 2f, 0f);
Vector3 size = new Vector3(_spawnRangeX * 2f, 1f, 1f);
Gizmos.DrawWireCube(center, size);
// Draw collision radius at spawn point
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(center, spawnCollisionRadius);
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 49ec62157fd945fab730193e9ea0bff7
timeCreated: 1758116903

View File

@@ -0,0 +1,242 @@
using UnityEngine;
using System.Collections;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Handles visual feedback for player damage by making the sprite renderer blink red.
/// Automatically subscribes to damage events from PlayerCollisionBehavior components.
/// </summary>
public class PlayerBlinkBehavior : MonoBehaviour
{
[Header("Blink Settings")]
[Tooltip("Color to blink to when taking damage (typically red for damage indication)")]
[SerializeField] private Color damageBlinkColor = Color.red;
[Tooltip("How fast to blink between normal and damage colors (seconds between color changes)")]
[SerializeField] private float blinkRate = 0.15f;
[Tooltip("Alpha value for the damage color (0 = transparent, 1 = opaque)")]
[Range(0f, 1f)]
[SerializeField] private float damageColorAlpha = 0.7f;
[Header("References")]
[Tooltip("The SpriteRenderer to apply blink effects to (auto-assigned if empty)")]
[SerializeField] private SpriteRenderer targetSpriteRenderer;
private bool _isBlinking;
private bool _isShowingDamageColor;
private Coroutine _blinkCoroutine;
private Color _originalColor; // Missing field declaration
private void Awake()
{
// Auto-assign sprite renderer if not set
if (targetSpriteRenderer == null)
{
targetSpriteRenderer = GetComponent<SpriteRenderer>();
if (targetSpriteRenderer == null)
{
targetSpriteRenderer = GetComponentInChildren<SpriteRenderer>();
if (targetSpriteRenderer != null)
{
Debug.Log($"[PlayerBlinkBehavior] Found SpriteRenderer on child object: {targetSpriteRenderer.gameObject.name}");
}
}
}
if (targetSpriteRenderer == null)
{
Debug.LogError("[PlayerBlinkBehavior] No SpriteRenderer found on this GameObject or its children!");
return;
}
// Store original color
_originalColor = targetSpriteRenderer.color;
// Ensure damage color has the correct alpha
damageBlinkColor.a = damageColorAlpha;
}
private void OnEnable()
{
// Subscribe to immunity events (renamed from damage events)
PlayerCollisionBehavior.OnImmunityStarted += StartBlinking;
PlayerCollisionBehavior.OnImmunityEnded += StopBlinking;
}
private void OnDisable()
{
// Unsubscribe from immunity events
PlayerCollisionBehavior.OnImmunityStarted -= StartBlinking;
PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking;
// Stop any ongoing blink effect
if (_blinkCoroutine != null)
{
StopCoroutine(_blinkCoroutine);
_blinkCoroutine = null;
}
// Restore original color
RestoreOriginalColor();
}
/// <summary>
/// Starts the red blinking effect when damage begins
/// </summary>
private void StartBlinking()
{
if (targetSpriteRenderer == null) return;
Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect");
// Stop any existing blink coroutine
if (_blinkCoroutine != null)
{
StopCoroutine(_blinkCoroutine);
}
_isBlinking = true;
_isShowingDamageColor = false;
// Start the blink coroutine
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
}
/// <summary>
/// Stops the blinking effect when damage ends
/// </summary>
private void StopBlinking()
{
Debug.Log("[PlayerBlinkBehavior] Stopping damage blink effect");
_isBlinking = false;
// Stop the blink coroutine
if (_blinkCoroutine != null)
{
StopCoroutine(_blinkCoroutine);
_blinkCoroutine = null;
}
// Restore original color
RestoreOriginalColor();
}
/// <summary>
/// Coroutine that handles the blinking animation
/// </summary>
private IEnumerator BlinkCoroutine()
{
while (_isBlinking && targetSpriteRenderer != null)
{
// Toggle between original and damage colors
if (_isShowingDamageColor)
{
targetSpriteRenderer.color = _originalColor;
_isShowingDamageColor = false;
}
else
{
targetSpriteRenderer.color = damageBlinkColor;
_isShowingDamageColor = true;
}
// Wait for blink interval
yield return new WaitForSeconds(blinkRate);
}
}
/// <summary>
/// Restores the sprite renderer to its original color
/// </summary>
private void RestoreOriginalColor()
{
if (targetSpriteRenderer != null)
{
targetSpriteRenderer.color = _originalColor;
_isShowingDamageColor = false;
}
}
/// <summary>
/// Updates the original color reference (useful if sprite color changes during gameplay)
/// </summary>
public void UpdateOriginalColor()
{
if (targetSpriteRenderer != null && !_isBlinking)
{
_originalColor = targetSpriteRenderer.color;
}
}
/// <summary>
/// Public method to change blink color at runtime
/// </summary>
public void SetDamageBlinkColor(Color newColor)
{
damageBlinkColor = newColor;
damageBlinkColor.a = damageColorAlpha;
}
/// <summary>
/// Public method to change blink rate at runtime
/// </summary>
public void SetBlinkRate(float rate)
{
blinkRate = rate;
}
/// <summary>
/// Check if currently blinking
/// </summary>
public bool IsBlinking => _isBlinking;
/// <summary>
/// Manually trigger blink effect (useful for testing or other damage sources)
/// </summary>
public void TriggerBlink(float duration)
{
if (_blinkCoroutine != null)
{
StopCoroutine(_blinkCoroutine);
}
StartCoroutine(ManualBlinkCoroutine(duration));
}
/// <summary>
/// Coroutine for manually triggered blink effects
/// </summary>
private IEnumerator ManualBlinkCoroutine(float duration)
{
_isBlinking = true;
_isShowingDamageColor = false;
float elapsed = 0f;
while (elapsed < duration && targetSpriteRenderer != null)
{
// Toggle between original and damage colors
if (_isShowingDamageColor)
{
targetSpriteRenderer.color = _originalColor;
_isShowingDamageColor = false;
}
else
{
targetSpriteRenderer.color = damageBlinkColor;
_isShowingDamageColor = true;
}
yield return new WaitForSeconds(blinkRate);
elapsed += blinkRate;
}
// Ensure we end with original color
RestoreOriginalColor();
_isBlinking = false;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d8ea29cc80524de8affe17b930cd75c1
timeCreated: 1758114229

View File

@@ -0,0 +1,371 @@
using UnityEngine;
using System;
using System.Collections;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Base class for handling player collisions with world obstacles.
/// Uses trigger-based collision detection with shared immunity state across all collision behaviors.
/// </summary>
public abstract class PlayerCollisionBehavior : MonoBehaviour
{
[Header("Collision Settings")]
[Tooltip("Duration in seconds of damage immunity after being hit")]
[SerializeField] protected float damageImmunityDuration = 1.0f;
[Tooltip("Layer mask for obstacle detection - configure which layers contain obstacles")]
[SerializeField] protected LayerMask obstacleLayerMask = -1;
[Header("Input Blocking")]
[Tooltip("Whether to block player input during damage immunity period")]
[SerializeField] protected bool blockInputDuringImmunity;
[Header("References")]
[Tooltip("The player character GameObject (auto-assigned if empty)")]
[SerializeField] protected GameObject playerCharacter;
[Tooltip("Reference to the PlayerController component (auto-assigned if empty)")]
[SerializeField] protected PlayerController playerController;
// Static shared immunity state across all collision behaviors
private static bool _isGloballyImmune;
private static Coroutine _globalImmunityCoroutine;
private static MonoBehaviour _coroutineRunner;
private static Collider2D _sharedPlayerCollider;
// Events for immunity and damage state changes
public static event Action OnImmunityStarted;
public static event Action OnImmunityEnded;
public static event Action OnDamageTaken;
// Instance tracking for shared state management
private static readonly System.Collections.Generic.HashSet<PlayerCollisionBehavior> _allInstances =
new System.Collections.Generic.HashSet<PlayerCollisionBehavior>();
/// <summary>
/// Public static method to trigger immunity start event from external classes
/// </summary>
public static void TriggerImmunityStarted()
{
OnImmunityStarted?.Invoke();
}
/// <summary>
/// Public static method to trigger immunity end event from external classes
/// </summary>
public static void TriggerImmunityEnded()
{
OnImmunityEnded?.Invoke();
}
/// <summary>
/// Public static method to trigger damage taken event from external classes
/// </summary>
public static void TriggerDamageTaken()
{
OnDamageTaken?.Invoke();
}
protected bool wasInputBlocked;
protected virtual void Awake()
{
// Auto-assign if not set in inspector
if (playerCharacter == null)
playerCharacter = gameObject;
if (playerController == null)
playerController = GetComponent<PlayerController>();
// Set up shared collider reference (only once)
if (_sharedPlayerCollider == null)
{
_sharedPlayerCollider = GetComponent<Collider2D>();
if (_sharedPlayerCollider == null)
{
_sharedPlayerCollider = GetComponentInChildren<Collider2D>();
if (_sharedPlayerCollider != null)
{
Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}");
}
}
if (_sharedPlayerCollider == null)
{
Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!");
}
}
// Set up coroutine runner (use first instance)
if (_coroutineRunner == null)
{
_coroutineRunner = this;
}
// Register this instance
_allInstances.Add(this);
}
private void OnDestroy()
{
// Unregister this instance
_allInstances.Remove(this);
// Clean up static references if this was the coroutine runner
if (_coroutineRunner == this)
{
if (_globalImmunityCoroutine != null)
{
StopCoroutine(_globalImmunityCoroutine);
_globalImmunityCoroutine = null;
}
_coroutineRunner = null;
// Find a new coroutine runner if there are other instances
foreach (var instance in _allInstances)
{
if (instance != null)
{
_coroutineRunner = instance;
break;
}
}
}
}
/// <summary>
/// Called when another collider enters this trigger collider
/// </summary>
/// <param name="other">The other collider that entered the trigger</param>
private void OnTriggerEnter2D(Collider2D other)
{
Debug.Log($"[{GetType().Name}] OnTriggerEnter2D called with collider: {other.gameObject.name} on layer: {other.gameObject.layer}");
// Check if the other collider is on one of our obstacle layers and we're not immune
if (IsObstacleLayer(other.gameObject.layer) && !_isGloballyImmune)
{
OnCollisionDetected(other);
}
}
/// <summary>
/// Called when a collision with an obstacle is detected
/// </summary>
/// <param name="obstacle">The obstacle collider that was hit</param>
protected virtual void OnCollisionDetected(Collider2D obstacle)
{
if (_isGloballyImmune) return;
// Trigger damage taken event first
OnDamageTaken?.Invoke();
// Start shared immunity period
StartGlobalImmunity();
// Call the specific collision response
HandleCollisionResponse(obstacle);
}
/// <summary>
/// Starts the shared immunity period across all collision behaviors
/// </summary>
private void StartGlobalImmunity()
{
if (_isGloballyImmune) return; // Already immune
_isGloballyImmune = true;
// Disable the shared collider to prevent further collisions
if (_sharedPlayerCollider != null)
{
_sharedPlayerCollider.enabled = false;
}
// Stop any existing immunity coroutine
if (_globalImmunityCoroutine != null && _coroutineRunner != null)
{
_coroutineRunner.StopCoroutine(_globalImmunityCoroutine);
}
// Start new immunity coroutine
if (_coroutineRunner != null)
{
_globalImmunityCoroutine = _coroutineRunner.StartCoroutine(ImmunityCoroutine());
}
// Notify all instances about immunity start
foreach (var instance in _allInstances)
{
if (instance != null)
{
instance.OnImmunityStart();
}
}
// Broadcast immunity start event
OnImmunityStarted?.Invoke();
}
/// <summary>
/// Coroutine that handles the immunity timer
/// </summary>
private IEnumerator ImmunityCoroutine()
{
Debug.Log($"[PlayerCollisionBehavior] Starting immunity coroutine for {damageImmunityDuration} seconds");
yield return new WaitForSeconds(damageImmunityDuration);
Debug.Log($"[PlayerCollisionBehavior] Immunity period ended");
// End immunity
_isGloballyImmune = false;
_globalImmunityCoroutine = null;
// Re-enable the shared collider
if (_sharedPlayerCollider != null)
{
_sharedPlayerCollider.enabled = true;
}
// Notify all instances about immunity end
foreach (var instance in _allInstances)
{
if (instance != null)
{
instance.OnImmunityEnd();
}
}
// Broadcast immunity end event
OnImmunityEnded?.Invoke();
}
/// <summary>
/// Override this method to implement specific collision response behavior
/// </summary>
/// <param name="obstacle">The obstacle that was collided with</param>
protected abstract void HandleCollisionResponse(Collider2D obstacle);
/// <summary>
/// Called when damage immunity starts (called on all instances)
/// </summary>
protected virtual void OnImmunityStart()
{
Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds");
// Block input if specified
if (blockInputDuringImmunity)
{
BlockPlayerInput();
}
}
/// <summary>
/// Called when damage immunity ends (called on all instances)
/// </summary>
protected virtual void OnImmunityEnd()
{
Debug.Log($"[{GetType().Name}] Damage immunity ended");
// Restore input if it was blocked
if (wasInputBlocked)
{
RestorePlayerInput();
}
}
/// <summary>
/// Restores player input after immunity
/// </summary>
protected virtual void RestorePlayerInput()
{
if (playerController != null && wasInputBlocked)
{
playerController.enabled = true;
wasInputBlocked = false;
// Update the controller's target position to current position to prevent snapping
UpdateControllerTarget();
Debug.Log($"[{GetType().Name}] Player input restored after immunity");
}
}
/// <summary>
/// Blocks player input during immunity
/// </summary>
protected virtual void BlockPlayerInput()
{
if (playerController != null && playerController.enabled)
{
playerController.enabled = false;
wasInputBlocked = true;
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
}
}
/// <summary>
/// Updates the PlayerController's internal target to match current position
/// </summary>
protected virtual void UpdateControllerTarget()
{
if (playerController != null && playerCharacter != null)
{
// Use reflection to update the private _targetFingerX field
var targetField = typeof(PlayerController)
.GetField("_targetFingerX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (targetField != null)
{
targetField.SetValue(playerController, playerCharacter.transform.position.x);
}
}
}
/// <summary>
/// Checks if the given layer is included in our obstacle layer mask
/// </summary>
/// <param name="layer">The layer to check</param>
/// <returns>True if the layer is included in the obstacle layer mask</returns>
private bool IsObstacleLayer(int layer)
{
return (obstacleLayerMask.value & (1 << layer)) != 0;
}
/// <summary>
/// Public property to check if player is currently immune (shared across all instances)
/// </summary>
public static bool IsImmune => _isGloballyImmune;
/// <summary>
/// Public method to manually end immunity (affects all collision behaviors)
/// </summary>
public static void EndImmunity()
{
if (_isGloballyImmune && _globalImmunityCoroutine != null && _coroutineRunner != null)
{
_coroutineRunner.StopCoroutine(_globalImmunityCoroutine);
_globalImmunityCoroutine = null;
_isGloballyImmune = false;
// Re-enable the shared collider
if (_sharedPlayerCollider != null)
{
_sharedPlayerCollider.enabled = true;
}
// Notify all instances
foreach (var instance in _allInstances)
{
if (instance != null)
{
instance.OnImmunityEnd();
}
}
OnImmunityEnded?.Invoke();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6c959bca2e24e72bf22e92439580d79
timeCreated: 1758109598

View File

@@ -31,7 +31,7 @@ namespace Minigames.DivingForPictures
/// </summary> /// </summary>
public void OnTap(Vector2 worldPosition) public void OnTap(Vector2 worldPosition)
{ {
Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}"); // Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
_isTouchActive = true; _isTouchActive = true;
} }
@@ -41,7 +41,7 @@ namespace Minigames.DivingForPictures
/// </summary> /// </summary>
public void OnHoldStart(Vector2 worldPosition) public void OnHoldStart(Vector2 worldPosition)
{ {
Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}"); // Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
_isTouchActive = true; _isTouchActive = true;
} }
@@ -51,7 +51,7 @@ namespace Minigames.DivingForPictures
/// </summary> /// </summary>
public void OnHoldMove(Vector2 worldPosition) public void OnHoldMove(Vector2 worldPosition)
{ {
Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}"); // Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
} }
@@ -60,7 +60,7 @@ namespace Minigames.DivingForPictures
/// </summary> /// </summary>
public void OnHoldEnd(Vector2 worldPosition) public void OnHoldEnd(Vector2 worldPosition)
{ {
Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}"); // Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}");
_isTouchActive = false; _isTouchActive = false;
} }

View File

@@ -0,0 +1,294 @@
using UnityEngine;
using System.Collections;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that bumps the player toward the center of the trench.
/// Uses trigger-based collision detection with coroutine-based bump timing.
/// </summary>
public class TileBumpCollision : PlayerCollisionBehavior
{
[Header("Bump Settings")]
[Tooltip("Type of bump response: Impulse pushes with force, SmoothToCenter moves directly to center")]
[SerializeField] private BumpMode bumpMode = BumpMode.Impulse;
[Tooltip("Force strength for impulse bumps - higher values push further toward center")]
[SerializeField] private float bumpForce = 5.0f;
[Tooltip("Speed for smooth movement to center (units per second)")]
[SerializeField] private float smoothMoveSpeed = 8.0f;
[Tooltip("Animation curve controlling bump movement over time")]
[SerializeField] private AnimationCurve bumpCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 2f), new Keyframe(1f, 1f, 0f, 0f));
[Tooltip("Whether to block player input during bump movement")]
[SerializeField] private bool blockInputDuringBump = true;
public enum BumpMode
{
Impulse, // Force-based push toward center (distance depends on force)
SmoothToCenter // Smooth movement to center with configurable speed
}
private bool _isBumping;
private Coroutine _bumpCoroutine;
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
protected override void HandleCollisionResponse(Collider2D obstacle)
{
switch (bumpMode)
{
case BumpMode.Impulse:
StartImpulseBump();
break;
case BumpMode.SmoothToCenter:
StartSmoothMoveToCenter();
break;
}
Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode");
}
/// <summary>
/// Starts an impulse bump toward the center with force-based distance
/// </summary>
private void StartImpulseBump()
{
if (playerCharacter == null) return;
float currentX = playerCharacter.transform.position.x;
// Calculate bump distance based on force and current position
float directionToCenter = currentX > 0 ? -1f : 1f; // Direction toward center
// Calculate target position - closer to center based on bump force
float bumpDistance = bumpForce * 0.2f; // Scale factor for distance
float targetX = currentX + (directionToCenter * bumpDistance);
// Clamp to center if we would overshoot
if ((currentX > 0 && targetX < 0) || (currentX < 0 && targetX > 0))
{
targetX = 0f;
}
float bumpDuration = 0.5f; // Fixed duration for impulse
StartBump(currentX, targetX, bumpDuration);
Debug.Log($"[TileBumpCollision] Starting impulse bump from X={currentX} to X={targetX} (force={bumpForce})");
}
/// <summary>
/// Starts smooth movement to the center
/// </summary>
private void StartSmoothMoveToCenter()
{
if (playerCharacter == null) return;
float currentX = playerCharacter.transform.position.x;
float distanceToCenter = Mathf.Abs(currentX);
float targetX = 0f; // Always move to center
float bumpDuration = distanceToCenter / smoothMoveSpeed; // Duration based on distance and speed
StartBump(currentX, targetX, bumpDuration);
Debug.Log($"[TileBumpCollision] Starting smooth move to center from X={currentX} (speed={smoothMoveSpeed}, duration={bumpDuration:F2}s)");
}
/// <summary>
/// Common bump initialization using coroutines
/// </summary>
private void StartBump(float startX, float targetX, float duration)
{
// Stop any existing bump
if (_bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
}
_isBumping = true;
// Block player input if enabled (use bump-specific blocking)
if (blockInputDuringBump && playerController != null && playerController.enabled)
{
playerController.enabled = false;
_bumpInputBlocked = true;
Debug.Log("[TileBumpCollision] Player input blocked during bump");
}
// Start bump coroutine
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
}
/// <summary>
/// Coroutine that handles the bump movement over time
/// </summary>
private IEnumerator BumpCoroutine(float startX, float targetX, float duration)
{
float elapsedTime = 0f;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
// Calculate progress and apply curve
float progress = elapsedTime / duration;
float curveValue = bumpCurve.Evaluate(progress);
// Interpolate position
float currentX = Mathf.Lerp(startX, targetX, curveValue);
// Apply the position to the player character
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(currentX, currentPos.y, currentPos.z);
}
yield return null;
}
// Ensure we end exactly at target
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(targetX, currentPos.y, currentPos.z);
}
// Bump finished
_isBumping = false;
_bumpCoroutine = null;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
Debug.Log("[TileBumpCollision] Bump movement completed");
}
/// <summary>
/// Restores player input after bump movement
/// </summary>
private void RestoreBumpInput()
{
if (_bumpInputBlocked && playerController != null)
{
playerController.enabled = true;
_bumpInputBlocked = false;
// Update the controller's target position to current position to prevent snapping
UpdateControllerTarget();
Debug.Log("[TileBumpCollision] Player input restored after bump");
}
}
/// <summary>
/// Override to handle bump-specific input blocking during immunity
/// </summary>
protected override void OnImmunityStart()
{
Debug.Log($"[TileBumpCollision] Damage immunity started for {damageImmunityDuration} seconds");
// Block input if specified (in addition to any bump input blocking)
if (blockInputDuringImmunity && !_bumpInputBlocked)
{
BlockPlayerInput();
}
}
/// <summary>
/// Override to handle immunity end and bump cleanup
/// </summary>
protected override void OnImmunityEnd()
{
base.OnImmunityEnd();
// Stop any ongoing bump if immunity ends
if (_isBumping && _bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
Debug.Log("[TileBumpCollision] Bump interrupted by immunity end");
}
}
/// <summary>
/// Called when component is destroyed - cleanup coroutines
/// </summary>
private void OnDestroy()
{
if (_bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
}
}
/// <summary>
/// Public method to change bump mode at runtime
/// </summary>
public void SetBumpMode(BumpMode mode)
{
bumpMode = mode;
}
/// <summary>
/// Public method to change bump force at runtime
/// </summary>
public void SetBumpForce(float force)
{
bumpForce = force;
}
/// <summary>
/// Public method to change smooth move speed at runtime
/// </summary>
public void SetSmoothMoveSpeed(float speed)
{
smoothMoveSpeed = speed;
}
/// <summary>
/// Check if player is currently being bumped
/// </summary>
public bool IsBumping => _isBumping;
/// <summary>
/// Check if input is currently blocked by bump
/// </summary>
public bool IsBumpInputBlocked => _bumpInputBlocked;
/// <summary>
/// Public method to manually stop bump movement
/// </summary>
public void StopBump()
{
if (_isBumping && _bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
Debug.Log("[TileBumpCollision] Bump manually stopped");
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8222f0e3aeeb4fc4975aaead6cf7afbe
timeCreated: 1758109619

View File

@@ -100,7 +100,7 @@ namespace Pooling
/// <returns>An object ready to use</returns> /// <returns>An object ready to use</returns>
public virtual T Get(int prefabIndex) public virtual T Get(int prefabIndex)
{ {
T obj; T obj = null;
// Track usage frequency // Track usage frequency
if (prefabUsageCount.ContainsKey(prefabIndex)) if (prefabUsageCount.ContainsKey(prefabIndex))
@@ -112,27 +112,71 @@ namespace Pooling
prefabUsageCount[prefabIndex] = 1; prefabUsageCount[prefabIndex] = 1;
} }
// Try to get a valid object from the pool, cleaning up any destroyed objects
if (pooledObjects.ContainsKey(prefabIndex) && pooledObjects[prefabIndex].Count > 0) if (pooledObjects.ContainsKey(prefabIndex) && pooledObjects[prefabIndex].Count > 0)
{ {
obj = pooledObjects[prefabIndex].Pop(); Debug.Log($"[{GetType().Name}] Found {pooledObjects[prefabIndex].Count} objects in pool for prefab index {prefabIndex}");
totalPooledCount--;
// Keep trying until we find a valid object or the pool is empty
while (pooledObjects[prefabIndex].Count > 0)
{
obj = pooledObjects[prefabIndex].Pop();
totalPooledCount--;
// Check if the object is still valid (not destroyed)
if (obj != null && obj.gameObject != null)
{
Debug.Log($"[{GetType().Name}] Retrieved valid object {obj.name} from pool, current active state: {obj.gameObject.activeInHierarchy}");
break; // Found a valid object
}
else
{
// Object was destroyed, continue looking
Debug.LogWarning($"[{GetType().Name}] Found destroyed object in pool, removing it");
obj = null;
}
}
} }
else else
{ {
// Create new object without adding to pool Debug.Log($"[{GetType().Name}] No objects in pool for prefab index {prefabIndex}, creating new one");
T prefab = prefabs[prefabIndex];
obj = Instantiate(prefab, transform);
} }
// If we couldn't find a valid object in the pool, create a new one
if (obj == null)
{
T prefab = prefabs[prefabIndex];
obj = Instantiate(prefab, transform);
Debug.Log($"[{GetType().Name}] Created new object {obj.name} from prefab, active state: {obj.gameObject.activeInHierarchy}");
}
// Ensure the object is valid before proceeding
if (obj == null || obj.gameObject == null)
{
Debug.LogError($"[{GetType().Name}] Failed to create valid object for prefab index {prefabIndex}");
return null;
}
// CRITICAL FIX: Reset position to safe location BEFORE activation
// This prevents off-screen checks from triggering during spawn process
Vector3 originalPosition = obj.transform.position;
obj.transform.position = new Vector3(0f, -1000f, 0f);
Debug.Log($"[{GetType().Name}] Moved object {obj.name} from {originalPosition} to safe position before activation");
Debug.Log($"[{GetType().Name}] About to activate object {obj.name}, current state: {obj.gameObject.activeInHierarchy}");
obj.gameObject.SetActive(true); obj.gameObject.SetActive(true);
Debug.Log($"[{GetType().Name}] After SetActive(true), object {obj.name} state: {obj.gameObject.activeInHierarchy}");
// Call OnSpawn for IPoolable components // Call OnSpawn for IPoolable components
IPoolable poolable = obj.GetComponent<IPoolable>(); IPoolable poolable = obj.GetComponent<IPoolable>();
if (poolable != null) if (poolable != null)
{ {
Debug.Log($"[{GetType().Name}] Calling OnSpawn for object {obj.name}");
poolable.OnSpawn(); poolable.OnSpawn();
Debug.Log($"[{GetType().Name}] After OnSpawn, object {obj.name} state: {obj.gameObject.activeInHierarchy}");
} }
Debug.Log($"[{GetType().Name}] Returning object {obj.name} with final state: {obj.gameObject.activeInHierarchy}");
return obj; return obj;
} }
@@ -152,33 +196,61 @@ namespace Pooling
poolable.OnDespawn(); poolable.OnDespawn();
} }
// Check if we're under the maximum pool size for this prefab type // Always deactivate and parent the object
bool keepObject = totalPooledCount < totalMaxPoolSize; obj.gameObject.SetActive(false);
obj.transform.SetParent(transform);
// Additional constraint: don't keep too many of any single prefab type // Initialize stack if it doesn't exist
if (pooledObjects.ContainsKey(prefabIndex) && if (!pooledObjects.ContainsKey(prefabIndex))
pooledObjects[prefabIndex].Count >= maxPerPrefabPoolSize)
{ {
keepObject = false; pooledObjects[prefabIndex] = new Stack<T>();
} }
if (keepObject) // Check if we need to trim this specific prefab type's pool
if (pooledObjects[prefabIndex].Count >= maxPerPrefabPoolSize)
{ {
obj.gameObject.SetActive(false); // Remove the oldest object from this prefab's pool to make room
obj.transform.SetParent(transform); if (pooledObjects[prefabIndex].Count > 0)
if (!pooledObjects.ContainsKey(prefabIndex))
{ {
pooledObjects[prefabIndex] = new Stack<T>(); T oldestObj = pooledObjects[prefabIndex].Pop();
if (oldestObj != null && oldestObj.gameObject != null)
{
Destroy(oldestObj.gameObject);
totalPooledCount--;
}
}
}
// Check global pool limit
if (totalPooledCount >= totalMaxPoolSize)
{
// Find the prefab type with the most pooled objects and remove one
int maxCount = 0;
int prefabToTrim = -1;
foreach (var kvp in pooledObjects)
{
if (kvp.Value.Count > maxCount)
{
maxCount = kvp.Value.Count;
prefabToTrim = kvp.Key;
}
} }
pooledObjects[prefabIndex].Push(obj); if (prefabToTrim >= 0 && pooledObjects[prefabToTrim].Count > 0)
totalPooledCount++; {
} T oldestObj = pooledObjects[prefabToTrim].Pop();
else if (oldestObj != null && oldestObj.gameObject != null)
{ {
Destroy(obj.gameObject); Destroy(oldestObj.gameObject);
totalPooledCount--;
}
}
} }
// Now add the current object to the pool
pooledObjects[prefabIndex].Push(obj);
totalPooledCount++;
} }
/// <summary> /// <summary>

View File

@@ -18,8 +18,8 @@ TagManager:
- Pulver - Pulver
- WorldBoundary - WorldBoundary
- Interactable - Interactable
- - QuarryObstacle
- - QuarryMonster
- -
- -
- -