Working state for minigameobstacles

This commit is contained in:
2025-09-17 16:10:18 +02:00
committed by Michal Pikulski
parent 2ec5c3d855
commit 50070651c5
26 changed files with 4573 additions and 19 deletions

2
.gitignore vendored
View File

@@ -99,6 +99,8 @@ InitTestScene*.unity*
# Auto-generated scenes by play mode tests
/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
.idea/.idea.AppleHillsProduction/.idea/indexLayout.xml
.vscode/extensions.json
.vscode/launch.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")]
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>();
[MenuItem("Tools/Sprite Collider Generator")]
@@ -51,8 +54,29 @@ namespace Editor.Utilities
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()
{
// 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
foreach (var mesh in previewMeshes)
{
@@ -64,6 +88,52 @@ namespace Editor.Utilities
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()
{
EditorGUILayout.BeginVertical();
@@ -146,6 +216,12 @@ namespace Editor.Utilities
new GUIContent("Generate Trigger Colliders", "When enabled, creates trigger colliders instead of solid colliders."),
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
offsetFromCenter = EditorGUILayout.Toggle(
new GUIContent("Offset From Center", "When enabled, allows scaling the collider outward or inward from the sprite center."),
@@ -445,9 +521,25 @@ namespace Editor.Utilities
polygonCollider.SetPath(i, paths[i]);
}
// Set the layer on the GameObject if a specific layer is selected
SetTargetLayer(targetObject);
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)
{
List<Vector2[]> result = new List<Vector2[]>();

View File

@@ -10,8 +10,8 @@ GameObject:
m_Component:
- component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074}
- component: {fileID: 7249681423942450184}
m_Layer: 0
- component: {fileID: 8688779957837852254}
m_Layer: 6
m_Name: Left_Tile2_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -88,7 +88,7 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!60 &7249681423942450184
--- !u!60 &8688779957837852254
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -155,8 +155,8 @@ GameObject:
m_Component:
- component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481}
- component: {fileID: 2843103852598642252}
m_Layer: 0
- component: {fileID: 8615330168962481848}
m_Layer: 6
m_Name: Right_Tile1_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -233,7 +233,7 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!60 &2843103852598642252
--- !u!60 &8615330168962481848
PolygonCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -282,8 +282,6 @@ PolygonCollider2D:
- - {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}

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component:
- component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074}
m_Layer: 0
- component: {fileID: 8698932925238153222}
m_Layer: 6
m_Name: Left_Tile2_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -87,6 +88,63 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0
@@ -97,7 +155,8 @@ GameObject:
m_Component:
- component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481}
m_Layer: 0
- component: {fileID: 1368284191430742655}
m_Layer: 6
m_Name: Right_Tile1_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -174,6 +233,67 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component:
- component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074}
m_Layer: 0
- component: {fileID: 6919687589473331973}
m_Layer: 6
m_Name: Left_Tile2_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component:
- component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481}
m_Layer: 0
- component: {fileID: 1911291775322313535}
m_Layer: 6
m_Name: Right_Tile1_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -174,6 +242,65 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component:
- component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074}
m_Layer: 0
- component: {fileID: 3320970211105392876}
m_Layer: 6
m_Name: Left_Tile2_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component:
- component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481}
m_Layer: 0
- component: {fileID: 3931598674086383704}
m_Layer: 6
m_Name: Right_Tile1_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -174,6 +242,65 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component:
- component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074}
m_Layer: 0
- component: {fileID: 1709932086434338450}
m_Layer: 6
m_Name: Left_Tile2_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component:
- component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481}
m_Layer: 0
- component: {fileID: 9036823987933958098}
m_Layer: 6
m_Name: Right_Tile1_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -174,6 +242,70 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0

View File

@@ -10,7 +10,8 @@ GameObject:
m_Component:
- component: {fileID: 7111145574660306503}
- component: {fileID: 3889795708575321074}
m_Layer: 0
- component: {fileID: 6967660003360049346}
m_Layer: 6
m_Name: Left_Tile2_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -87,6 +88,72 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0
@@ -97,7 +164,8 @@ GameObject:
m_Component:
- component: {fileID: 1003080013996268193}
- component: {fileID: 4856205316150460481}
m_Layer: 0
- component: {fileID: 5503039050943823342}
m_Layer: 6
m_Name: Right_Tile1_0
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -174,6 +242,70 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 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
GameObject:
m_ObjectHideFlags: 0

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
using UnityEngine;
using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Complete floating obstacle component that handles data, movement, collision detection, and pooling.
/// Obstacles move upward toward the surface and detect collisions with the player.
/// </summary>
public class FloatingObstacle : MonoBehaviour, IPoolable
{
[Header("Obstacle Properties")]
[Tooltip("Index of the prefab this obstacle was created from")]
[SerializeField] private int prefabIndex;
[Tooltip("Damage this obstacle deals to the player")]
[SerializeField] private float damage = 1f;
[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("Collision Detection")]
[Tooltip("Layer mask for player detection - should match Player layer")]
[SerializeField] private LayerMask playerLayerMask = 1 << 7; // Player layer
[Tooltip("How often to check for collisions (in seconds)")]
[SerializeField] private float collisionCheckInterval = 0.1f;
[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 Damage
{
get => damage;
set => damage = value;
}
public float MoveSpeed
{
get => moveSpeed;
set => moveSpeed = value;
}
public bool HasDealtDamage => _hasDealtDamage;
// Private fields
private Collider2D _collider;
private float _collisionCheckTimer;
private bool _hasDealtDamage;
private Camera _mainCamera;
private float _screenTop;
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 Update()
{
if (enableMovement)
{
HandleMovement();
}
HandleCollisionDetection();
CheckIfOffScreen();
}
/// <summary>
/// Moves the obstacle upward based on its speed
/// </summary>
private void HandleMovement()
{
transform.position += Vector3.up * (moveSpeed * Time.deltaTime);
}
/// <summary>
/// Checks for collisions with the player at regular intervals
/// </summary>
private void HandleCollisionDetection()
{
if (_hasDealtDamage || _collider == null) return;
_collisionCheckTimer -= Time.deltaTime;
if (_collisionCheckTimer <= 0f)
{
_collisionCheckTimer = collisionCheckInterval;
CheckForPlayerCollision();
}
}
/// <summary>
/// Checks if this obstacle is colliding with the player
/// </summary>
private void CheckForPlayerCollision()
{
Collider2D[] overlapping = new Collider2D[5];
ContactFilter2D filter = new ContactFilter2D();
filter.SetLayerMask(playerLayerMask);
filter.useTriggers = true;
int count = _collider.Overlap(filter, overlapping);
if (count > 0 && !_hasDealtDamage)
{
// Found collision with player
OnPlayerCollision(overlapping[0]);
}
}
/// <summary>
/// Called when this obstacle collides with the player
/// </summary>
/// <param name="playerCollider">The player's collider</param>
private void OnPlayerCollision(Collider2D playerCollider)
{
_hasDealtDamage = true;
// Trigger damage through events (following the existing pattern)
Debug.Log($"[FloatingObstacle] Obstacle dealt {damage} damage to player");
// Broadcast damage event using the static method
PlayerCollisionBehavior.TriggerDamageStart();
// Continue moving upward - don't destroy or stop the obstacle
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} hit player and continues moving");
}
/// <summary>
/// Checks if the obstacle has moved off-screen and should be despawned
/// </summary>
private void CheckIfOffScreen()
{
if (_mainCamera == null) return;
// Calculate screen top if not cached
if (_screenTop == 0f)
{
Vector3 topWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.nearClipPlane));
_screenTop = topWorldPoint.y;
}
// Check if obstacle is above screen
if (transform.position.y > _screenTop + 2f) // Extra buffer for safety
{
ReturnToPool();
}
}
/// <summary>
/// Returns this obstacle to the spawner's pool
/// </summary>
private void ReturnToPool()
{
if (spawner != null)
{
spawner.ReturnObstacleToPool(gameObject, prefabIndex);
}
else
{
Debug.LogWarning($"[FloatingObstacle] Cannot return {gameObject.name} to pool - missing spawner reference");
Destroy(gameObject);
}
}
/// <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()
{
_hasDealtDamage = false;
_collisionCheckTimer = 0f;
_screenTop = 0f; // Reset cached screen bounds
// Ensure the obstacle is active and visible
gameObject.SetActive(true);
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} spawned");
}
/// <summary>
/// Called when the obstacle is returned to the pool
/// </summary>
public void OnDespawn()
{
_hasDealtDamage = false;
_collisionCheckTimer = 0f;
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} despawned");
}
/// <summary>
/// Public method to manually trigger return to pool (for external systems)
/// </summary>
public void ForceReturnToPool()
{
ReturnToPool();
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
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)
{
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)
{
FloatingObstacle obstacleComponent = Get(prefabIndex);
return obstacleComponent.gameObject;
}
}
}

View File

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

View File

@@ -0,0 +1,452 @@
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;
[Tooltip("Minimum damage dealt by obstacles")]
[SerializeField] private float minDamage = 0.5f;
[Tooltip("Maximum damage dealt by obstacles")]
[SerializeField] private float maxDamage = 2f;
[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 - should match WorldObstacle layer")]
[SerializeField] private LayerMask tileLayerMask = 1 << 6; // WorldObstacle layer
[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 Coroutine _spawnCoroutine;
private readonly List<GameObject> _activeObstacles = new List<GameObject>();
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
if (obstaclePrefabs[i].layer != 11) // QuarryObstacle layer
{
Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on QuarryObstacle layer (11). Setting layer automatically.");
SetLayerRecursively(obstaclePrefabs[i], 11);
}
}
}
/// <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);
// Periodically trim the pool
InvokeRepeating(nameof(TrimExcessPooledObstacles), 15f, 30f);
}
/// <summary>
/// Calculate screen bounds in world space
/// </summary>
private void CalculateScreenBounds()
{
if (_mainCamera == null)
{
_mainCamera = Camera.main;
if (_mainCamera == null)
{
Debug.LogError("[ObstacleSpawner] No main camera found!");
return;
}
}
Vector3 bottomWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 0f, _mainCamera.nearClipPlane));
_screenBottom = bottomWorldPoint.y;
}
/// <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()
{
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))
{
SpawnObstacleAt(spawnPosition);
foundValidPosition = true;
break;
}
}
if (!foundValidPosition)
{
Debug.Log($"[ObstacleSpawner] Could not find valid spawn position after {maxSpawnAttempts} attempts");
}
}
/// <summary>
/// Gets a random spawn position below the screen
/// </summary>
private Vector3 GetRandomSpawnPosition()
{
float randomX = Random.Range(-spawnRangeX, spawnRangeX);
float spawnY = _screenBottom - spawnDistanceBelowScreen;
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)
{
// Select random prefab
int prefabIndex = Random.Range(0, obstaclePrefabs.Count);
GameObject prefab = obstaclePrefabs[prefabIndex];
if (prefab == null)
{
Debug.LogError($"[ObstacleSpawner] Obstacle prefab at index {prefabIndex} is null!");
return;
}
GameObject obstacle;
// Spawn using pool or instantiate directly
if (useObjectPooling && _obstaclePool != null)
{
obstacle = _obstaclePool.GetObstacle(prefabIndex);
if (obstacle == null)
{
Debug.LogError("[ObstacleSpawner] Failed to get obstacle from pool!");
return;
}
obstacle.transform.position = position;
obstacle.transform.rotation = prefab.transform.rotation;
obstacle.transform.SetParent(transform);
}
else
{
obstacle = Instantiate(prefab, position, prefab.transform.rotation, transform);
}
// Configure the obstacle
ConfigureObstacle(obstacle, prefabIndex);
// Track active obstacles
_activeObstacles.Add(obstacle);
// Invoke events
onObstacleSpawned?.Invoke(obstacle);
Debug.Log($"[ObstacleSpawner] Spawned obstacle {obstacle.name} at {position}");
}
/// <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);
obstacleComponent.Damage = Random.Range(minDamage, maxDamage);
// 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;
// Remove from active list
_activeObstacles.Remove(obstacle);
// Invoke events
onObstacleDestroyed?.Invoke(obstacle);
// Return to pool or destroy
if (useObjectPooling && _obstaclePool != null)
{
_obstaclePool.ReturnObstacle(obstacle, prefabIndex);
}
else
{
Destroy(obstacle);
}
}
/// <summary>
/// Called periodically to trim excess pooled obstacles
/// </summary>
private void TrimExcessPooledObstacles()
{
if (_obstaclePool != null)
{
_obstaclePool.TrimExcess();
}
}
/// <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 set damage range at runtime
/// </summary>
public void SetDamageRange(float min, float max)
{
minDamage = min;
maxDamage = max;
}
/// <summary>
/// Gets the count of currently active obstacles
/// </summary>
public int ActiveObstacleCount => _activeObstacles.Count;
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
// Draw spawn area
Gizmos.color = Color.yellow;
Vector3 center = new Vector3(0f, _screenBottom - spawnDistanceBelowScreen, 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 damage events
PlayerCollisionBehavior.OnDamageStart += StartBlinking;
PlayerCollisionBehavior.OnDamageEnd += StopBlinking;
}
private void OnDisable()
{
// Unsubscribe from damage events
PlayerCollisionBehavior.OnDamageStart -= StartBlinking;
PlayerCollisionBehavior.OnDamageEnd -= 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,234 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that bumps the player toward the center of the trench.
/// Provides two modes: impulse (force-based push) or smooth movement to center.
/// </summary>
public class PlayerBumpCollisionBehavior : 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 float _bumpTimer;
private float _bumpStartX;
private float _bumpTargetX;
private float _bumpDuration;
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
protected override void Update()
{
base.Update();
// Handle bump movement
if (_isBumping)
{
_bumpTimer -= Time.deltaTime;
if (_bumpTimer <= 0f)
{
// Bump finished
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
// Ensure we end exactly at target
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(_bumpTargetX, currentPos.y, currentPos.z);
}
}
else
{
// Apply bump movement
float progress = 1f - (_bumpTimer / _bumpDuration);
float curveValue = bumpCurve.Evaluate(progress);
float currentX = Mathf.Lerp(_bumpStartX, _bumpTargetX, 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);
}
}
}
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
switch (bumpMode)
{
case BumpMode.Impulse:
StartImpulseBump();
break;
case BumpMode.SmoothToCenter:
StartSmoothMoveToCenter();
break;
}
Debug.Log($"[PlayerBumpCollisionBehavior] 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;
}
// Set bump parameters
_bumpStartX = currentX;
_bumpTargetX = targetX;
_bumpDuration = 0.5f; // Fixed duration for impulse
StartBump();
Debug.Log($"[PlayerBumpCollisionBehavior] Starting impulse bump from X={_bumpStartX} to X={_bumpTargetX} (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);
// Set bump parameters
_bumpStartX = currentX;
_bumpTargetX = 0f; // Always move to center
_bumpDuration = distanceToCenter / smoothMoveSpeed; // Duration based on distance and speed
StartBump();
Debug.Log($"[PlayerBumpCollisionBehavior] Starting smooth move to center from X={_bumpStartX} (speed={smoothMoveSpeed}, duration={_bumpDuration:F2}s)");
}
/// <summary>
/// Common bump initialization
/// </summary>
private void StartBump()
{
_isBumping = true;
_bumpTimer = _bumpDuration;
// Block player input if enabled (use bump-specific blocking)
if (blockInputDuringBump && playerController != null && playerController.enabled)
{
playerController.enabled = false;
_bumpInputBlocked = true;
Debug.Log("[PlayerBumpCollisionBehavior] Player input blocked during bump");
}
}
/// <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("[PlayerBumpCollisionBehavior] Player input restored after bump");
}
}
protected override void OnImmunityEnd()
{
base.OnImmunityEnd();
// Stop any ongoing bump if immunity ends
if (_isBumping)
{
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
}
}
/// <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;
}
}

View File

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

View File

@@ -0,0 +1,238 @@
using UnityEngine;
using System;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Base class for handling player collisions with world obstacles.
/// Detects collisions between Player layer (7) and WorldObstacle layer (6).
/// </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 - should match WorldObstacle layer")]
[SerializeField] protected LayerMask obstacleLayerMask = 1 << 6; // WorldObstacle layer
[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;
// Events for damage state changes
public static event Action OnDamageStart;
public static event Action OnDamageEnd;
/// <summary>
/// Public static method to trigger damage start event from external classes
/// </summary>
public static void TriggerDamageStart()
{
OnDamageStart?.Invoke();
}
/// <summary>
/// Public static method to trigger damage end event from external classes
/// </summary>
public static void TriggerDamageEnd()
{
OnDamageEnd?.Invoke();
}
protected bool isImmune;
protected float immunityTimer;
protected Collider2D playerCollider;
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>();
// Look for collider on this GameObject first, then in children
playerCollider = GetComponent<Collider2D>();
if (playerCollider == null)
{
playerCollider = GetComponentInChildren<Collider2D>();
if (playerCollider != null)
{
Debug.Log($"[{GetType().Name}] Found collider on child object: {playerCollider.gameObject.name}");
}
}
if (playerCollider == null)
{
Debug.LogError($"[{GetType().Name}] No Collider2D found on this GameObject or its children!");
}
}
protected virtual void Update()
{
// Handle immunity timer
if (isImmune)
{
immunityTimer -= Time.deltaTime;
if (immunityTimer <= 0f)
{
isImmune = false;
OnImmunityEnd();
}
}
// Check for collisions if not immune
if (!isImmune && playerCollider != null)
{
CheckForCollisions();
}
}
/// <summary>
/// Checks for collisions with obstacle layer objects
/// </summary>
protected virtual void CheckForCollisions()
{
// Get all colliders overlapping with the player
Collider2D[] overlapping = new Collider2D[10];
ContactFilter2D filter = new ContactFilter2D();
filter.SetLayerMask(obstacleLayerMask);
filter.useTriggers = true;
int count = playerCollider.Overlap(filter, overlapping);
if (count > 0)
{
// Found collision, trigger response
OnCollisionDetected(overlapping[0]);
}
}
/// <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 (isImmune) return;
// Start immunity period
isImmune = true;
immunityTimer = damageImmunityDuration;
// Call the specific collision response
HandleCollisionResponse(obstacle);
// Notify about immunity start
OnImmunityStart();
}
/// <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
/// </summary>
protected virtual void OnImmunityStart()
{
Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds");
// Block input if specified
if (blockInputDuringImmunity)
{
BlockPlayerInput();
}
// Broadcast damage start event
OnDamageStart?.Invoke();
}
/// <summary>
/// Called when damage immunity ends
/// </summary>
protected virtual void OnImmunityEnd()
{
Debug.Log($"[{GetType().Name}] Damage immunity ended");
// Restore input if it was blocked
if (wasInputBlocked)
{
RestorePlayerInput();
}
// Broadcast damage end event
OnDamageEnd?.Invoke();
}
/// <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>
/// Public property to check if player is currently immune
/// </summary>
public bool IsImmune => isImmune;
/// <summary>
/// Remaining immunity time
/// </summary>
public float RemainingImmunityTime => immunityTimer;
}
}

View File

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

View File

@@ -0,0 +1,102 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that handles damage from mobile obstacles.
/// Unlike bump collisions, this only deals damage without physical response.
/// Detects collisions between Player layer (7) and QuarryObstacle layer (11).
/// </summary>
public class PlayerDamageCollisionBehavior : PlayerCollisionBehavior
{
[Header("Damage Settings")]
[Tooltip("Base damage amount dealt by obstacles")]
[SerializeField] private float baseDamage = 1f;
[Tooltip("Whether to use the obstacle's individual damage value or the base damage")]
[SerializeField] private bool useObstacleDamageValue = true;
protected override void Awake()
{
base.Awake();
// Override the obstacle layer mask to target QuarryObstacle layer (11)
obstacleLayerMask = 1 << 11; // QuarryObstacle layer
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
float damageAmount = baseDamage;
// Try to get damage from the obstacle component if enabled
if (useObstacleDamageValue)
{
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
damageAmount = obstacleComponent.Damage;
}
}
// Apply damage (this could be extended to integrate with a health system)
ApplyDamage(damageAmount);
Debug.Log($"[PlayerDamageCollisionBehavior] Player took {damageAmount} damage from obstacle {obstacle.gameObject.name}");
}
/// <summary>
/// Applies damage to the player
/// Override this method to integrate with your health/damage system
/// </summary>
/// <param name="damage">Amount of damage to apply</param>
protected virtual void ApplyDamage(float damage)
{
// For now, just log the damage
// In a full implementation, this would reduce player health, trigger UI updates, etc.
Debug.Log($"[PlayerDamageCollisionBehavior] Applied {damage} damage to player");
// TODO: Integrate with health system when available
// Example: playerHealth.TakeDamage(damage);
}
/// <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($"[PlayerDamageCollisionBehavior] Damage immunity started for {damageImmunityDuration} seconds");
// Don't block input for obstacle damage - let player keep moving
// Only broadcast the damage event
TriggerDamageStart();
}
/// <summary>
/// Override to handle immunity end without input restoration
/// </summary>
protected override void OnImmunityEnd()
{
Debug.Log($"[PlayerDamageCollisionBehavior] Damage immunity ended");
// Broadcast damage end event
TriggerDamageEnd();
}
/// <summary>
/// Public method to set base damage at runtime
/// </summary>
public void SetBaseDamage(float damage)
{
baseDamage = damage;
}
/// <summary>
/// Public method to toggle between base damage and obstacle-specific damage
/// </summary>
public void SetUseObstacleDamage(bool useObstacleDamage)
{
useObstacleDamageValue = useObstacleDamage;
}
}
}

View File

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

View File

@@ -0,0 +1,180 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that grants temporary immunity and allows the player to phase through obstacles.
/// During immunity, the player can pass through all obstacles without further collision detection.
/// </summary>
public class PlayerPhaseCollisionBehavior : PlayerCollisionBehavior
{
[Header("Phase Settings")]
[Tooltip("Whether to disable the player's collider during immunity to allow phasing through obstacles")]
[SerializeField] private bool disableColliderDuringImmunity = true;
[Tooltip("How fast the player sprite blinks during phase mode (seconds between blinks)")]
[SerializeField] private float visualFeedbackBlinkRate = 0.1f;
private SpriteRenderer _spriteRenderer;
private bool _originalColliderState;
private bool _isBlinking;
private float _blinkTimer;
private Color _originalColor;
private float _originalAlpha;
protected override void Awake()
{
base.Awake();
// Get sprite renderer from player character
if (playerCharacter != null)
{
_spriteRenderer = playerCharacter.GetComponent<SpriteRenderer>();
}
if (_spriteRenderer != null)
{
_originalColor = _spriteRenderer.color;
_originalAlpha = _originalColor.a;
}
if (playerCollider != null)
{
_originalColliderState = playerCollider.enabled;
}
}
protected override void Update()
{
base.Update();
// Handle visual feedback blinking during immunity
if (_isBlinking && _spriteRenderer != null)
{
_blinkTimer -= Time.deltaTime;
if (_blinkTimer <= 0f)
{
// Toggle visibility
Color currentColor = _spriteRenderer.color;
currentColor.a = currentColor.a > 0.5f ? 0.3f : _originalAlpha;
_spriteRenderer.color = currentColor;
_blinkTimer = visualFeedbackBlinkRate;
}
}
}
protected override void CheckForCollisions()
{
// Override to skip collision detection entirely during immunity if collider is disabled
if (isImmune && disableColliderDuringImmunity)
{
return; // Skip collision detection completely
}
base.CheckForCollisions();
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
Debug.Log("[PlayerPhaseCollisionBehavior] Collision detected - entering phase mode");
// No immediate physical response - just start immunity period
// The immunity will be handled by the base class
}
protected override void OnImmunityStart()
{
base.OnImmunityStart();
// Disable collider to allow phasing through obstacles
if (disableColliderDuringImmunity && playerCollider != null)
{
playerCollider.enabled = false;
Debug.Log("[PlayerPhaseCollisionBehavior] Collider disabled - entering phase mode");
}
// Start visual feedback
StartVisualFeedback();
}
protected override void OnImmunityEnd()
{
base.OnImmunityEnd();
// Re-enable collider
if (playerCollider != null)
{
playerCollider.enabled = _originalColliderState;
Debug.Log("[PlayerPhaseCollisionBehavior] Collider re-enabled - exiting phase mode");
}
// Stop visual feedback
StopVisualFeedback();
}
/// <summary>
/// Starts the visual feedback to indicate immunity/phase mode
/// </summary>
private void StartVisualFeedback()
{
if (_spriteRenderer != null)
{
_isBlinking = true;
_blinkTimer = 0f;
// Start with reduced opacity
Color immunityColor = _originalColor;
immunityColor.a = 0.3f;
_spriteRenderer.color = immunityColor;
}
}
/// <summary>
/// Stops the visual feedback and restores original appearance
/// </summary>
private void StopVisualFeedback()
{
_isBlinking = false;
if (_spriteRenderer != null)
{
// Restore original color and alpha
_spriteRenderer.color = _originalColor;
}
}
/// <summary>
/// Public method to toggle collider behavior during immunity
/// </summary>
public void SetColliderDisabling(bool disable)
{
disableColliderDuringImmunity = disable;
}
/// <summary>
/// Check if player is currently in phase mode
/// </summary>
public bool IsPhasing => isImmune && disableColliderDuringImmunity;
/// <summary>
/// Manually trigger phase mode (useful for testing or special abilities)
/// </summary>
public void TriggerPhaseMode(float duration = -1f)
{
if (duration > 0f)
{
isImmune = true;
immunityTimer = duration;
OnImmunityStart();
}
else
{
isImmune = true;
immunityTimer = damageImmunityDuration;
OnImmunityStart();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d849a517ce3a41249ae9f37d2722cefa
timeCreated: 1758109641

View File

@@ -18,7 +18,7 @@ TagManager:
- Pulver
- WorldBoundary
- Interactable
-
- QuarryObstacle
-
-
-