Refactor course maps and travel UI

This commit is contained in:
jongjae0305
2026-07-07 15:57:11 +09:00
parent 74498dcf87
commit a708f18499
37 changed files with 1983 additions and 1633 deletions
+1 -849
View File
@@ -1,281 +1,5 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1166831543635676
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4518572068955746}
- component: {fileID: 212503073192347908}
- component: {fileID: 61881790296472918}
m_Layer: 0
m_Name: Obstacle Right
m_TagString: Dead
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4518572068955746
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1166831543635676}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0.44, y: 1, z: 0}
m_LocalScale: {x: 0.22222222, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4447057583457146}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212503073192347908
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1166831543635676}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: -294611229
m_SortingLayer: 2
m_SortingOrder: 0
m_MaskInteraction: 0
m_Sprite: {fileID: 21300000, guid: c786e87f13804e37be5de982aee4f675, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1.4, y: 1.4}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!61 &61881790296472918
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1166831543635676}
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.5, y: 0.5}
oldSize: {x: 1.6, y: 1}
newSize: {x: 1.4, y: 1.4}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 1.2, y: 1}
m_EdgeRadius: 0
--- !u!1 &1297539188427694
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4605692002121302}
- component: {fileID: 212000956051026164}
- component: {fileID: 61237356497549858}
m_Layer: 0
m_Name: Obstacle Left
m_TagString: Dead
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4605692002121302
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1297539188427694}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -0.44, y: 1, z: 0}
m_LocalScale: {x: 0.22222222, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4447057583457146}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212000956051026164
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1297539188427694}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: -294611229
m_SortingLayer: 2
m_SortingOrder: 0
m_MaskInteraction: 0
m_Sprite: {fileID: 21300000, guid: c786e87f13804e37be5de982aee4f675, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1.4, y: 1.4}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!61 &61237356497549858
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1297539188427694}
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.5, y: 0.5}
oldSize: {x: 1.6, y: 1}
newSize: {x: 1.4, y: 1.4}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 1.2, y: 1}
m_EdgeRadius: 0
--- !u!1 &1671242928466520
GameObject:
m_ObjectHideFlags: 0
@@ -308,11 +32,7 @@ Transform:
m_LocalPosition: {x: 15.2227, y: 0.0055, z: 0}
m_LocalScale: {x: 4.778359, y: 1.011, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4605692002121302}
- {fileID: 4014685509531708}
- {fileID: 4518572068955746}
- {fileID: 4987654321098765}
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212850827289184248
@@ -445,575 +165,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 9f975ef653abb48238f62061ebab1c23, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Platform
obstacles:
- {fileID: 1297539188427694}
- {fileID: 1832961231703714}
- {fileID: 1166831543635676}
- {fileID: 1987654321098765}
emptyPatternWeight: 2
mileageSprite: {fileID: 21300000, guid: 8b2f1d0c3e4a5b6f9d7e0123456789ab, type: 3}
mileagePatternChance: 50
smallMileageValue: 10
bonusMileageValue: 30
--- !u!1 &1832961231703714
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4014685509531708}
- component: {fileID: 212599298415523624}
- component: {fileID: 61928244072500108}
m_Layer: 0
m_Name: Obstacle Mid
m_TagString: Dead
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4014685509531708
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1832961231703714}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: 0}
m_LocalScale: {x: 0.22222222, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4447057583457146}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212599298415523624
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1832961231703714}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: -294611229
m_SortingLayer: 2
m_SortingOrder: 0
m_MaskInteraction: 0
m_Sprite: {fileID: 21300000, guid: c786e87f13804e37be5de982aee4f675, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1.4, y: 1.4}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!61 &61928244072500108
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1832961231703714}
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.5, y: 0.5}
oldSize: {x: 1.6, y: 1}
newSize: {x: 1.4, y: 1.4}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 1.2, y: 1}
m_EdgeRadius: 0
--- !u!1 &1928374650918273
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3876501129384756}
- component: {fileID: 212887766554433221}
- component: {fileID: 61988776655443322}
m_Layer: 0
m_Name: Obstacle High Left
m_TagString: Dead
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3876501129384756
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1928374650918273}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -1.35, y: 2.68, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4987654321098765}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212887766554433221
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1928374650918273}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: -294611229
m_SortingLayer: 2
m_SortingOrder: 0
m_MaskInteraction: 0
m_Sprite: {fileID: 21300000, guid: c7ce697688684c84928ff860570a69b3, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1.4, y: 1.4}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!61 &61988776655443322
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1928374650918273}
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.5, y: 0.5}
oldSize: {x: 1.6, y: 1}
newSize: {x: 1.4, y: 1.4}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 1.55, y: 0.7}
m_EdgeRadius: 0
--- !u!1 &1987654321098765
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4987654321098765}
- component: {fileID: 114987654321098765}
m_Layer: 0
m_Name: Slide Obstacle Pattern
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4987654321098765
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098765}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.22222222, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4987654321098766}
- {fileID: 3876501129384756}
- {fileID: 4987654321098767}
m_Father: {fileID: 4447057583457146}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &114987654321098765
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098765}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9dcd5b5fbb7b40d4b86fa136c439d1b7, type: 3}
m_Name: SlideObstaclePattern
m_EditorClassIdentifier: Assembly-CSharp::SlideObstaclePattern
obstacles:
- {fileID: 3876501129384756}
- {fileID: 4987654321098767}
xPositions:
- -1.65
- -0.55
- 0.55
- 1.65
minSpacing: 1.5
layout: 0
--- !u!1 &1987654321098766
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4987654321098766}
- component: {fileID: 212887766554433222}
m_Layer: 0
m_Name: Ceiling Platform
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4987654321098766
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098766}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -0.09999995, y: 3.5000005, z: 0}
m_LocalScale: {x: 3, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4987654321098765}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212887766554433222
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098766}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: -591980115
m_SortingLayer: 3
m_SortingOrder: 0
m_MaskInteraction: 0
m_Sprite: {fileID: 21300000, guid: c61ae3691bcf45deabea44aed605c29d, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 7.6, y: 1.88}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!1 &1987654321098767
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4987654321098767}
- component: {fileID: 212887766554433223}
- component: {fileID: 61988776655443323}
m_Layer: 0
m_Name: Obstacle High Right
m_TagString: Dead
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4987654321098767
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098767}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 1.1199999, y: 2.6800003, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4987654321098765}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &212887766554433223
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098767}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: -294611229
m_SortingLayer: 2
m_SortingOrder: 0
m_MaskInteraction: 0
m_Sprite: {fileID: 21300000, guid: c7ce697688684c84928ff860570a69b3, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1.4, y: 1.4}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!61 &61988776655443323
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1987654321098767}
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.5, y: 0.5}
oldSize: {x: 1.6, y: 1}
newSize: {x: 1.4, y: 1.4}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 1.55, y: 0.7}
m_EdgeRadius: 0
Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: 93a27e424cd947568993dbf3a6d1e931
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: f455a5c527e246d2a2a51e974d147d2c
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: f7c3aa91d47245efb7e68f53a025cb4d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: abbbda8464dc4b17a7dcc4997a22d398
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9e8469e5a7a7443f9adff42c0c7bf921
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,6 @@
public enum CoursePlatformType {
Basic,
Jump,
Slide,
ForceHit
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02a1bb5c51284fd8ad76d51d8327a14c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+15
View File
@@ -0,0 +1,15 @@
using System;
[Serializable]
public class CourseStep {
public CoursePlatformType coursePlatformType;
public Platform.PlatformPattern platformPattern;
public Platform.MileagePattern mileagePattern;
public SlideObstaclePattern.SlideLayout slideLayout;
public Platform.ItemPattern itemPattern;
public GameManager.TutorialGuideType guideType;
public float yPosition;
public float spawnInterval;
public string title;
public string message;
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21d873850c7043ef8b2854e1a5f16d9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+189
View File
@@ -0,0 +1,189 @@
public static class CourseStepFactory {
public const float GroundY = -2.4f;
public const float AerialY = -0.85f;
public const float BasicSpacing = 0.58f;
public const float ActionSpacing = 0.72f;
public const float LandingSpacing = 0.64f;
public const float AerialSpacing = 2.05f;
public static CourseStep[] CreateEasyLoop() {
return new CourseStep[] {
Basic(),
Jump(),
Basic(GroundY, LandingSpacing),
Aerial(),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
Basic()
};
}
public static CourseStep Basic(
float yPosition = GroundY,
float spawnInterval = BasicSpacing,
Platform.MileagePattern mileagePattern = Platform.MileagePattern.SafeLine,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "",
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
return Step(
Platform.PlatformPattern.Empty,
mileagePattern,
SlideObstaclePattern.SlideLayout.Random,
guideType,
GroundY,
spawnInterval,
title,
message,
itemPattern,
CoursePlatformType.Basic);
}
public static CourseStep Jump(
Platform.PlatformPattern platformPattern = Platform.PlatformPattern.LowMid,
float spawnInterval = ActionSpacing,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Step(
platformPattern,
Platform.MileagePattern.JumpArc,
SlideObstaclePattern.SlideLayout.Random,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
CoursePlatformType.Jump);
}
public static CourseStep Jump(
GameManager.TutorialGuideType guideType,
string title,
string message) {
return Jump(Platform.PlatformPattern.LowMid, ActionSpacing, guideType, title, message);
}
public static CourseStep Aerial(
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Aerial(AerialY, AerialSpacing, guideType, title, message);
}
public static CourseStep Aerial(
float yPosition,
float spawnInterval,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Step(
Platform.PlatformPattern.Empty,
Platform.MileagePattern.SafeLine,
SlideObstaclePattern.SlideLayout.Random,
guideType,
yPosition,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
CoursePlatformType.Basic);
}
public static CourseStep Slide(
SlideObstaclePattern.SlideLayout slideLayout = SlideObstaclePattern.SlideLayout.CenterPair,
float spawnInterval = ActionSpacing,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Step(
Platform.PlatformPattern.Slide,
Platform.MileagePattern.SlideLine,
slideLayout,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
CoursePlatformType.Slide);
}
public static CourseStep Slide(
GameManager.TutorialGuideType guideType,
string title,
string message) {
return Slide(SlideObstaclePattern.SlideLayout.CenterPair, ActionSpacing, guideType, title, message);
}
public static CourseStep ForceHit(
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.Damage,
string title = "",
string message = "",
float spawnInterval = ActionSpacing) {
return Step(
Platform.PlatformPattern.ForceHit,
Platform.MileagePattern.None,
SlideObstaclePattern.SlideLayout.WidePair,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
CoursePlatformType.ForceHit);
}
public static CourseStep Step(
Platform.PlatformPattern platformPattern,
Platform.MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout,
GameManager.TutorialGuideType guideType,
float yPosition,
float spawnInterval,
string title = "",
string message = "",
Platform.ItemPattern itemPattern = Platform.ItemPattern.None,
CoursePlatformType coursePlatformType = CoursePlatformType.Basic) {
return new CourseStep {
coursePlatformType = coursePlatformType,
platformPattern = platformPattern,
mileagePattern = mileagePattern,
slideLayout = slideLayout,
itemPattern = itemPattern,
guideType = guideType,
yPosition = yPosition,
spawnInterval = spawnInterval,
title = title,
message = message
};
}
public static CourseStep[] CloneSteps(CourseStep[] source) {
if(source == null)
{
return new CourseStep[0];
}
CourseStep[] steps = new CourseStep[source.Length];
for(int i = 0; i < source.Length; i++)
{
CourseStep sourceStep = source[i];
steps[i] = Step(
sourceStep.platformPattern,
sourceStep.mileagePattern,
sourceStep.slideLayout,
sourceStep.guideType,
sourceStep.yPosition,
sourceStep.spawnInterval,
sourceStep.title,
sourceStep.message,
sourceStep.itemPattern,
sourceStep.coursePlatformType);
}
return steps;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b8e2ef0ff2346e8ab5e6d6557e41b10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+12
View File
@@ -0,0 +1,12 @@
public enum CourseStepKind {
None,
Jump,
DoubleJumpTakeoff,
Slide,
ForceHit,
HealthItem,
InvincibleItem,
ShieldItem,
SpeedItem,
MileageCardItem
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e543ce69f5964c1195ce10e5fe7e2a7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+620 -33
View File
@@ -15,7 +15,8 @@ public class GameFlowManager : MonoBehaviour {
Game,
Result,
DutyFreeShop,
MapPreview
MapPreview,
DebugMenu
}
public static GameFlowManager Instance { get; private set; }
@@ -39,6 +40,27 @@ public class GameFlowManager : MonoBehaviour {
private const string ResultSceneName = "Result";
private const string DutyFreeShopSceneName = "DutyFreeShop";
private const string MapPreviewSceneName = "MapPreview";
private const string DepartureStampResourcePath = "UI_DepartureStamp_Red";
private const string ResultPassportPanelResourcePath = "UI_LeftHudPassportHorizontalGridPanel";
private const string ResultAirportBackgroundResourcePath = "Map_Tutorial_Incheon_Airport";
private const string ResultButtonResourcePath = "UI_TutorialNextButton";
private const string ResultButtonAssetPath = "Assets/Resources/UI_TutorialNextButton.png";
private const string MileageIconAssetPath = "Assets/Sprites/UIIcons/UI_Mileage.png";
private const string TimerIconAssetPath = "Assets/Sprites/UIIcons/UI_Timer.png";
private const string CheckpointIconAssetPath = "Assets/Sprites/UIIcons/UI_Checkpoint.png";
private const string ClearRankIconAssetPath = "Assets/Sprites/UIIcons/UI_ClearRank.png";
private const string DutyFreeIconAssetPath = "Assets/Sprites/UIIcons/UI_DutyFreeShop.png";
private const string AirplaneTravelIconAssetPath = "Assets/Sprites/UIIcons/UI_AirplaneTravel.png";
private const string DutyFreeBackgroundAssetPath = "Assets/ConceptArt/duty_free_shop_screen_concept_01.png";
private const string DutyFreeLunchboxAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_Lunchbox.png";
private const string DutyFreeShieldAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_ShieldCharm.png";
private const string DutyFreeShoesAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_Shoes.png";
private const string DutyFreeMileageCardAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_MileageCard.png";
private const string SouvenirCollectionIconAssetPath = "Assets/Sprites/UIIcons/UI_SouvenirCollection.png";
private const string AirportSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_AirportMagnet.png";
private const string JapanSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_Japan_Magnet.png";
private const string ChinaSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_TempleCharm.png";
private const string AmericaSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_SkylineMagnet.png";
private const int ShieldCost = 120;
private const int LunchboxCost = 80;
private const int SpeedShoesCost = 140;
@@ -98,6 +120,50 @@ public class GameFlowManager : MonoBehaviour {
ShowIntro();
}
#if UNITY_EDITOR || DEVELOPMENT_BUILD
private void Update() {
if(Input.GetKeyDown(KeyCode.Escape))
{
ShowDebugMenu();
return;
}
HandleDebugMenuShortcutInput();
}
private void HandleDebugMenuShortcutInput() {
if(currentScreen != FlowScreen.DebugMenu)
{
return;
}
if(Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1))
{
ShowDebugIntroPreview();
}
else if(Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2))
{
ShowDebugGameStartPreview();
}
else if(Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3))
{
ShowDebugResultPreview(false);
}
else if(Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4))
{
ShowDebugResultPreview(true);
}
else if(Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5))
{
ShowDebugDutyFreeShopPreview();
}
else if(Input.GetKeyDown(KeyCode.Alpha6) || Input.GetKeyDown(KeyCode.Keypad6))
{
ShowDebugMapPreview();
}
}
#endif
private void OnDestroy() {
if(Instance == this)
{
@@ -173,6 +239,95 @@ public class GameFlowManager : MonoBehaviour {
ShowResult();
}
#if UNITY_EDITOR || DEVELOPMENT_BUILD
private void ShowDebugMenu() {
EnsureDebugPreviewData();
gameplayActive = false;
pendingStartGameplay = false;
currentScreen = FlowScreen.DebugMenu;
EnsureOverlay();
ClearOverlay();
SetOverlayVisible(true);
SetDefaultOverlayBackground();
AddTitle("디버그 메뉴");
AddBody("화면 조정용 바로 이동\n숫자키 1~6도 사용할 수 있습니다.");
AddButton("1. 인트로", ShowDebugIntroPreview);
AddButton("2. 게임 시작", ShowDebugGameStartPreview);
AddButton("3. 출국 실패", delegate { ShowDebugResultPreview(false); });
AddButton("4. 출국 성공", delegate { ShowDebugResultPreview(true); });
AddButton("5. 면세점", ShowDebugDutyFreeShopPreview);
AddButton("6. 지도", ShowDebugMapPreview);
}
private void ShowDebugIntroPreview() {
ShowIntro();
}
private void ShowDebugGameStartPreview() {
EnsureDebugPreviewData();
LoadGameSceneForCurrentMap();
}
private void ShowDebugResultPreview(bool cleared) {
EnsureDebugPreviewData();
gameplayActive = false;
pendingStartGameplay = false;
currentScreen = FlowScreen.Result;
completedMap = currentMap;
lastResultCleared = cleared;
lastResultScore = cleared ? 12800 : 7200;
lastResultMileage = cleared ? 263 : 84;
lastResultTime = cleared ? 23.9f : 38.4f;
lastResultDistance = completedMap != null
? (cleared ? completedMap.targetDistance : completedMap.targetDistance * 0.62f)
: 380f;
if(saveData != null)
{
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
}
ShowResult();
}
private void ShowDebugDutyFreeShopPreview() {
EnsureDebugPreviewData();
if(saveData != null)
{
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
}
ShowDutyFreeShop();
}
private void ShowDebugMapPreview() {
EnsureDebugPreviewData();
ShowMapPreview();
}
private void EnsureDebugPreviewData() {
if(saveData == null)
{
saveData = GameSaveManager.Load();
}
if(currentMap == null)
{
currentMap = MapDatabase.GetMapByIndex(saveData != null ? saveData.currentMapIndex : 0);
}
if(currentMap == null)
{
currentMap = MapDatabase.GetMapByIndex(0);
}
}
#endif
private void StartNewJourney() {
saveData = GameSaveManager.ResetSave();
currentMap = MapDatabase.GetMapByIndex(saveData.currentMapIndex);
@@ -271,33 +426,298 @@ public class GameFlowManager : MonoBehaviour {
EnsureOverlay();
ClearOverlay();
SetOverlayVisible(true);
SetDefaultOverlayBackground();
BuildResultScreen();
}
string title = lastResultCleared ? "맵 클리어" : "일정 실패";
string body = completedMap.displayName
+ "\n점수: " + lastResultScore
+ "\n획득 마일리지: " + lastResultMileage
+ "\n거리: " + Mathf.FloorToInt(lastResultDistance) + "m / " + Mathf.FloorToInt(completedMap.targetDistance) + "m"
+ "\n시간: " + lastResultTime.ToString("0.0") + "s";
private void BuildResultScreen() {
ApplyResultAirportBackground();
MapDefinition resultMap = completedMap != null ? completedMap : currentMap;
int rewardMileage = lastResultCleared && resultMap != null ? resultMap.clearMileageReward : 0;
int gainedMileage = Mathf.Max(0, lastResultMileage) + rewardMileage;
int targetDistance = resultMap != null ? Mathf.FloorToInt(resultMap.targetDistance) : 0;
int shownDistance = targetDistance > 0
? Mathf.Clamp(Mathf.FloorToInt(lastResultDistance), 0, targetDistance)
: Mathf.Max(0, Mathf.FloorToInt(lastResultDistance));
RectTransform passportRect = AddResultPassportPanel();
AddResultHeader(passportRect, resultMap);
AddResultStats(passportRect, gainedMileage, shownDistance, targetDistance);
AddResultTickets(passportRect, resultMap, rewardMileage);
if(lastResultCleared)
{
body += "\n클리어 보상: " + completedMap.clearMileageReward + " 마일리지";
}
AddTitle(title);
AddBody(body);
if(lastResultCleared)
{
AddButton("면세점으로", ShowDutyFreeShop);
AddResultActionButton("면세점으로", ShowDutyFreeShop, new Vector2(-166f, -282f), new Vector2(250f, 48f), true, LoadUiSprite("", DutyFreeIconAssetPath));
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(160f, -282f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
}
else
{
AddButton("다시 도전", RetryCurrentMap);
AddResultActionButton("다시 도전", RetryCurrentMap, new Vector2(-166f, -282f), new Vector2(250f, 48f), true, LoadUiSprite("", CheckpointIconAssetPath));
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(160f, -282f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
}
}
private void ApplyResultAirportBackground() {
if(overlayBackground != null)
{
overlayBackground.sprite = LoadUiSprite(ResultAirportBackgroundResourcePath, "Assets/Resources/Map_Tutorial_Incheon_Airport.png");
overlayBackground.preserveAspect = false;
overlayBackground.color = overlayBackground.sprite != null
? Color.white
: new Color(0.008f, 0.024f, 0.034f, 0.96f);
}
AddButton("인트로", ShowIntro);
if(flowCamera != null)
{
flowCamera.backgroundColor = new Color(0.006f, 0.022f, 0.035f, 1f);
}
GameObject scrimObject = CreateUiObject("Result Airport Background Scrim", overlayRoot, typeof(Image));
RectTransform scrimRect = scrimObject.GetComponent<RectTransform>();
scrimRect.anchorMin = Vector2.zero;
scrimRect.anchorMax = Vector2.one;
scrimRect.offsetMin = Vector2.zero;
scrimRect.offsetMax = Vector2.zero;
Image scrimImage = scrimObject.GetComponent<Image>();
scrimImage.color = new Color(0.005f, 0.015f, 0.020f, 0.30f);
scrimImage.raycastTarget = false;
}
private RectTransform AddResultPassportPanel() {
GameObject panelObject = CreateUiObject("Result Passport Page", overlayRoot, typeof(Image));
RectTransform panelRect = panelObject.GetComponent<RectTransform>();
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
panelRect.pivot = new Vector2(0.5f, 0.5f);
panelRect.anchoredPosition = new Vector2(0f, 38f);
panelRect.sizeDelta = new Vector2(1092f, 330f);
Image panelImage = panelObject.GetComponent<Image>();
panelImage.sprite = LoadUiSprite(ResultPassportPanelResourcePath, "Assets/Resources/UI_LeftHudPassportHorizontalGridPanel.png");
panelImage.color = panelImage.sprite != null
? Color.white
: new Color(0.95f, 0.91f, 0.80f, 0.98f);
panelImage.preserveAspect = true;
panelImage.raycastTarget = false;
return panelRect;
}
private void AddResultHeader(RectTransform parent, MapDefinition resultMap) {
Sprite rankSprite = LoadUiSprite("", ClearRankIconAssetPath);
Sprite stampSprite = LoadUiSprite(DepartureStampResourcePath, "Assets/Resources/UI_DepartureStamp_Red.png");
AddResultImage(parent, "Result Rank Badge", rankSprite, new Vector2(170f, -106f), new Vector2(50f, 50f), true, Color.white);
string title = lastResultCleared ? "출국 성공!" : "일정 실패";
Color titleColor = lastResultCleared
? new Color(0.05f, 0.17f, 0.27f, 1f)
: new Color(0.52f, 0.08f, 0.07f, 1f);
AddResultText(parent, "Result Title", title, new Vector2(194f, -88f), new Vector2(210f, 50f), 28f, FontStyles.Bold, titleColor, TextAlignmentOptions.MidlineLeft);
string destination = GetResultDestinationName(resultMap);
AddResultText(parent, "Result Route", destination, new Vector2(260f, -140f), new Vector2(210f, 22f), 13f, FontStyles.Bold, new Color(0.25f, 0.42f, 0.50f, 0.96f), TextAlignmentOptions.MidlineLeft);
if(lastResultCleared)
{
AddResultImage(parent, "Result Departure Stamp", stampSprite, new Vector2(250f, -232f), new Vector2(108f, 108f), true, new Color(1f, 1f, 1f, 0.92f));
TextMeshProUGUI stampText = AddResultText(parent, "Result Stamp Text", "출국\n성공", new Vector2(228f, -218f), new Vector2(92f, 48f), 21f, FontStyles.Bold, new Color(0.70f, 0.04f, 0.03f, 0.96f), TextAlignmentOptions.Center);
stampText.lineSpacing = -16f;
}
else
{
AddResultText(parent, "Result Retry Note", "다시 준비해서\n출국 게이트까지", new Vector2(178f, -216f), new Vector2(200f, 56f), 17f, FontStyles.Bold, new Color(0.32f, 0.46f, 0.54f, 0.96f), TextAlignmentOptions.Center);
}
}
private void AddResultStats(RectTransform parent, int gainedMileage, int shownDistance, int targetDistance) {
AddResultStat(parent, "Mileage", LoadUiSprite("", MileageIconAssetPath), "마일리지", "+" + gainedMileage, new Vector2(478f, -90f));
AddResultStat(parent, "Time", LoadUiSprite("", TimerIconAssetPath), "도착 시간", lastResultTime.ToString("0.0") + "s", new Vector2(626f, -90f));
string distance = targetDistance > 0
? shownDistance + "/" + targetDistance + "m"
: shownDistance + "m";
AddResultStat(parent, "Distance", LoadUiSprite("", CheckpointIconAssetPath), "이동 거리", distance, new Vector2(770f, -90f));
}
private void AddResultTickets(RectTransform parent, MapDefinition resultMap, int rewardMileage) {
AddResultTicket(
parent,
"Destination",
LoadUiSprite("", CheckpointIconAssetPath),
"목적지",
GetResultDestinationName(resultMap),
new Vector2(410f, -200f));
AddResultTicket(
parent,
"Souvenir",
lastResultCleared ? GetResultSouvenirSprite(resultMap) : LoadUiSprite("", SouvenirCollectionIconAssetPath),
"보상",
lastResultCleared ? GetResultSouvenirName(resultMap) : "다음 도전",
new Vector2(560f, -200f));
AddResultTicket(
parent,
"Total Mileage",
LoadUiSprite("", MileageIconAssetPath),
"보유",
saveData.totalMileage + " M",
new Vector2(710f, -200f));
}
private void AddResultStat(RectTransform parent, string objectName, Sprite iconSprite, string label, string value, Vector2 centerPosition) {
AddResultImage(parent, "Result " + objectName + " Icon", iconSprite, centerPosition + new Vector2(0f, -4f), new Vector2(42f, 42f), true, Color.white);
AddResultText(parent, "Result " + objectName + " Value", value, centerPosition + new Vector2(-70f, -25f), new Vector2(140f, 24f), 15f, FontStyles.Bold, new Color(0.05f, 0.17f, 0.25f, 1f), TextAlignmentOptions.Center);
AddResultText(parent, "Result " + objectName + " Label", label, centerPosition + new Vector2(-70f, -48f), new Vector2(140f, 18f), 8f, FontStyles.Bold, new Color(0.42f, 0.58f, 0.64f, 0.92f), TextAlignmentOptions.Center);
}
private void AddResultTicket(RectTransform parent, string objectName, Sprite iconSprite, string label, string value, Vector2 topLeftPosition) {
AddResultImage(parent, "Result " + objectName + " Icon", iconSprite, topLeftPosition + new Vector2(28f, -40f), new Vector2(40f, 40f), true, Color.white);
AddResultText(parent, "Result " + objectName + " Label", label, topLeftPosition + new Vector2(56f, -19f), new Vector2(94f, 18f), 13f, FontStyles.Bold, new Color(0.43f, 0.58f, 0.64f, 0.96f), TextAlignmentOptions.MidlineLeft);
AddResultText(parent, "Result " + objectName + " Value", value, topLeftPosition + new Vector2(56f, -42f), new Vector2(112f, 26f), 12f, FontStyles.Bold, new Color(0.05f, 0.18f, 0.27f, 1f), TextAlignmentOptions.MidlineLeft);
}
private void AddResultActionButton(string label, UnityEngine.Events.UnityAction action, Vector2 position, Vector2 size, bool primary, Sprite iconSprite) {
GameObject buttonObject = CreateUiObject("Result Button " + label, overlayRoot, typeof(Image), typeof(Button));
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
buttonRect.anchorMin = new Vector2(0.5f, 0.5f);
buttonRect.anchorMax = new Vector2(0.5f, 0.5f);
buttonRect.pivot = new Vector2(0.5f, 0.5f);
buttonRect.anchoredPosition = position;
buttonRect.sizeDelta = size;
Image buttonImage = buttonObject.GetComponent<Image>();
buttonImage.sprite = LoadUiSprite(ResultButtonResourcePath, ResultButtonAssetPath);
buttonImage.color = buttonImage.sprite != null
? (primary ? Color.white : new Color(0.86f, 0.94f, 1f, 0.94f))
: primary
? new Color(1f, 0.82f, 0.28f, 0.98f)
: new Color(0.82f, 0.91f, 0.96f, 0.90f);
buttonImage.preserveAspect = false;
buttonImage.raycastTarget = true;
Button button = buttonObject.GetComponent<Button>();
button.onClick.AddListener(action);
if(iconSprite != null)
{
AddResultImage(buttonRect, "Result Button Icon", iconSprite, new Vector2(34f, -(size.y * 0.5f)), new Vector2(24f, 24f), true, Color.white);
}
TextMeshProUGUI buttonText = AddResultText(buttonRect, "Result Button Label", label, new Vector2(iconSprite != null ? 54f : 0f, -1f), new Vector2(iconSprite != null ? size.x - 70f : size.x, size.y), 16f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
RectTransform textRect = buttonText.rectTransform;
textRect.anchorMin = new Vector2(0f, 0.5f);
textRect.anchorMax = new Vector2(0f, 0.5f);
textRect.pivot = new Vector2(0f, 0.5f);
buttonText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
buttonText.outlineWidth = 0.12f;
}
private Image AddResultImage(RectTransform parent, string objectName, Sprite sprite, Vector2 position, Vector2 size, bool preserveAspect, Color color) {
GameObject imageObject = CreateUiObject(objectName, parent, typeof(Image));
RectTransform imageRect = imageObject.GetComponent<RectTransform>();
imageRect.anchorMin = new Vector2(0f, 1f);
imageRect.anchorMax = new Vector2(0f, 1f);
imageRect.pivot = new Vector2(0.5f, 0.5f);
imageRect.anchoredPosition = position;
imageRect.sizeDelta = size;
Image image = imageObject.GetComponent<Image>();
image.sprite = sprite;
image.color = sprite != null ? color : new Color(0.30f, 0.46f, 0.54f, 0.28f);
image.preserveAspect = preserveAspect;
image.raycastTarget = false;
return image;
}
private TextMeshProUGUI AddResultText(RectTransform parent, string objectName, string text, Vector2 position, Vector2 size, float fontSize, FontStyles fontStyle, Color color, TextAlignmentOptions alignment) {
GameObject textObject = CreateUiObject(objectName, parent);
RectTransform textRect = textObject.GetComponent<RectTransform>();
textRect.anchorMin = new Vector2(0f, 1f);
textRect.anchorMax = new Vector2(0f, 1f);
textRect.pivot = new Vector2(0f, 1f);
textRect.anchoredPosition = position;
textRect.sizeDelta = size;
TextMeshProUGUI textComponent = textObject.AddComponent<TextMeshProUGUI>();
textComponent.text = text;
textComponent.font = runtimeFont != null ? runtimeFont : textComponent.font;
textComponent.fontSize = fontSize;
textComponent.fontStyle = fontStyle;
textComponent.alignment = alignment;
textComponent.color = color;
textComponent.raycastTarget = false;
textComponent.textWrappingMode = TextWrappingModes.Normal;
textComponent.overflowMode = TextOverflowModes.Ellipsis;
textComponent.enableAutoSizing = true;
textComponent.fontSizeMin = Mathf.Max(8f, fontSize * 0.66f);
textComponent.fontSizeMax = fontSize;
return textComponent;
}
private string GetResultDestinationName(MapDefinition resultMap) {
if(resultMap == null)
{
return "여행지";
}
switch(resultMap.mapId)
{
case MapId.TutorialIncheon:
return "인천공항";
case MapId.Japan:
return "일본";
case MapId.China:
return "중국";
case MapId.America:
return "미국";
default:
return resultMap.displayName;
}
}
private string GetResultSouvenirName(MapDefinition resultMap) {
if(resultMap == null)
{
return "여행 도장";
}
switch(resultMap.mapId)
{
case MapId.TutorialIncheon:
return "공항 마그넷";
case MapId.Japan:
return "일본 마그넷";
case MapId.China:
return "여행 부적";
case MapId.America:
return "스카이라인";
default:
return "여행 도장";
}
}
private Sprite GetResultSouvenirSprite(MapDefinition resultMap) {
if(resultMap == null)
{
return LoadUiSprite("", SouvenirCollectionIconAssetPath);
}
switch(resultMap.mapId)
{
case MapId.TutorialIncheon:
return LoadUiSprite("", AirportSouvenirAssetPath);
case MapId.Japan:
return LoadUiSprite("", JapanSouvenirAssetPath);
case MapId.China:
return LoadUiSprite("", ChinaSouvenirAssetPath);
case MapId.America:
return LoadUiSprite("", AmericaSouvenirAssetPath);
default:
return LoadUiSprite("", SouvenirCollectionIconAssetPath);
}
}
private void ShowDutyFreeShop() {
@@ -313,20 +733,166 @@ public class GameFlowManager : MonoBehaviour {
EnsureOverlay();
ClearOverlay();
SetOverlayVisible(true);
SetDefaultOverlayBackground();
AddTitle("면세점");
AddBody("보유 마일리지: " + saveData.totalMileage
+ "\n도시락: " + saveData.lunchboxStock
+ " / 방어막: " + saveData.shieldStock
+ "\n운동화: " + saveData.speedShoesStock
+ " / 마일리지 카드: " + saveData.mileageCardStock);
BuildDutyFreeShopScreen();
}
AddButton("도시락 구매 " + LunchboxCost, BuyLunchbox);
AddButton("방어막 구매 " + ShieldCost, BuyShield);
AddButton("운동화 구매 " + SpeedShoesCost, BuySpeedShoes);
AddButton("마일리지 카드 구매 " + MileageCardCost, BuyMileageCard);
AddButton("다음 맵 보기", ShowMapPreview);
private void BuildDutyFreeShopScreen() {
ApplyDutyFreeShopBackground();
RectTransform boardRect = AddDutyFreeBoard();
AddDutyFreeHeader(boardRect);
AddDutyFreeItemCard(
boardRect,
"도시락",
"HP 회복",
LunchboxCost,
saveData.lunchboxStock,
LoadUiSprite("", DutyFreeLunchboxAssetPath),
BuyLunchbox,
new Vector2(58f, -130f));
AddDutyFreeItemCard(
boardRect,
"방어막",
"1회 방어",
ShieldCost,
saveData.shieldStock,
LoadUiSprite("", DutyFreeShieldAssetPath),
BuyShield,
new Vector2(296f, -130f));
AddDutyFreeItemCard(
boardRect,
"운동화",
"속도 회복",
SpeedShoesCost,
saveData.speedShoesStock,
LoadUiSprite("", DutyFreeShoesAssetPath),
BuySpeedShoes,
new Vector2(534f, -130f));
AddDutyFreeItemCard(
boardRect,
"마일리지 카드",
"획득량 증가",
MileageCardCost,
saveData.mileageCardStock,
LoadUiSprite("", DutyFreeMileageCardAssetPath),
BuyMileageCard,
new Vector2(772f, -130f));
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(0f, -292f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
}
private void ApplyDutyFreeShopBackground() {
if(overlayBackground != null)
{
overlayBackground.sprite = LoadUiSprite("", DutyFreeBackgroundAssetPath);
overlayBackground.preserveAspect = false;
overlayBackground.color = overlayBackground.sprite != null
? Color.white
: new Color(0.03f, 0.035f, 0.038f, 0.96f);
}
if(flowCamera != null)
{
flowCamera.backgroundColor = new Color(0.03f, 0.035f, 0.038f, 1f);
}
GameObject scrimObject = CreateUiObject("Duty Free Background Scrim", overlayRoot, typeof(Image));
RectTransform scrimRect = scrimObject.GetComponent<RectTransform>();
scrimRect.anchorMin = Vector2.zero;
scrimRect.anchorMax = Vector2.one;
scrimRect.offsetMin = Vector2.zero;
scrimRect.offsetMax = Vector2.zero;
Image scrimImage = scrimObject.GetComponent<Image>();
scrimImage.color = new Color(0.01f, 0.012f, 0.014f, 0.34f);
scrimImage.raycastTarget = false;
}
private RectTransform AddDutyFreeBoard() {
GameObject boardObject = CreateUiObject("Duty Free Product Board", overlayRoot, typeof(Image));
RectTransform boardRect = boardObject.GetComponent<RectTransform>();
boardRect.anchorMin = new Vector2(0.5f, 0.5f);
boardRect.anchorMax = new Vector2(0.5f, 0.5f);
boardRect.pivot = new Vector2(0.5f, 0.5f);
boardRect.anchoredPosition = new Vector2(0f, 28f);
boardRect.sizeDelta = new Vector2(1038f, 462f);
Image boardImage = boardObject.GetComponent<Image>();
boardImage.color = new Color(0.015f, 0.055f, 0.085f, 0.84f);
boardImage.raycastTarget = false;
return boardRect;
}
private void AddDutyFreeHeader(RectTransform parent) {
AddResultImage(parent, "Duty Free Header Icon", LoadUiSprite("", DutyFreeIconAssetPath), new Vector2(84f, -60f), new Vector2(48f, 48f), true, Color.white);
AddResultText(parent, "Duty Free Title", "면세점", new Vector2(120f, -38f), new Vector2(180f, 46f), 32f, FontStyles.Bold, new Color(0.96f, 0.99f, 1f, 1f), TextAlignmentOptions.MidlineLeft);
AddResultText(parent, "Duty Free Subtitle", "다음 여행 준비", new Vector2(122f, -82f), new Vector2(220f, 24f), 15f, FontStyles.Bold, new Color(0.64f, 0.82f, 0.92f, 0.96f), TextAlignmentOptions.MidlineLeft);
AddResultImage(parent, "Duty Free Mileage Icon", LoadUiSprite("", MileageIconAssetPath), new Vector2(760f, -61f), new Vector2(42f, 42f), true, Color.white);
AddResultText(parent, "Duty Free Mileage Label", "보유 마일리지", new Vector2(794f, -43f), new Vector2(140f, 22f), 13f, FontStyles.Bold, new Color(0.64f, 0.82f, 0.92f, 0.96f), TextAlignmentOptions.MidlineLeft);
AddResultText(parent, "Duty Free Mileage Value", saveData.totalMileage + " M", new Vector2(794f, -66f), new Vector2(160f, 32f), 24f, FontStyles.Bold, new Color(1f, 0.90f, 0.38f, 1f), TextAlignmentOptions.MidlineLeft);
}
private void AddDutyFreeItemCard(RectTransform parent, string itemName, string effectText, int cost, int stock, Sprite itemSprite, UnityEngine.Events.UnityAction buyAction, Vector2 topLeftPosition) {
bool canBuy = saveData.totalMileage >= cost;
GameObject cardObject = CreateUiObject("Duty Free Item " + itemName, parent, typeof(Image), typeof(Button));
RectTransform cardRect = cardObject.GetComponent<RectTransform>();
cardRect.anchorMin = new Vector2(0f, 1f);
cardRect.anchorMax = new Vector2(0f, 1f);
cardRect.pivot = new Vector2(0f, 1f);
cardRect.anchoredPosition = topLeftPosition;
cardRect.sizeDelta = new Vector2(206f, 242f);
Image cardImage = cardObject.GetComponent<Image>();
cardImage.color = canBuy
? new Color(0.97f, 0.91f, 0.78f, 0.96f)
: new Color(0.40f, 0.45f, 0.48f, 0.88f);
cardImage.raycastTarget = true;
Button button = cardObject.GetComponent<Button>();
button.targetGraphic = cardImage;
button.interactable = canBuy;
button.onClick.AddListener(buyAction);
button.navigation = new Navigation {
mode = Navigation.Mode.None
};
Color textColor = canBuy
? new Color(0.06f, 0.16f, 0.22f, 1f)
: new Color(0.78f, 0.84f, 0.86f, 1f);
Color subColor = canBuy
? new Color(0.34f, 0.49f, 0.54f, 0.98f)
: new Color(0.66f, 0.72f, 0.74f, 0.98f);
AddResultImage(cardRect, "Duty Free " + itemName + " Image", itemSprite, new Vector2(103f, -70f), new Vector2(100f, 88f), true, canBuy ? Color.white : new Color(1f, 1f, 1f, 0.54f));
AddResultText(cardRect, "Duty Free " + itemName + " Name", itemName, new Vector2(16f, -116f), new Vector2(174f, 28f), 20f, FontStyles.Bold, textColor, TextAlignmentOptions.Center);
AddResultText(cardRect, "Duty Free " + itemName + " Effect", effectText, new Vector2(16f, -146f), new Vector2(174f, 22f), 13f, FontStyles.Bold, subColor, TextAlignmentOptions.Center);
AddResultText(cardRect, "Duty Free " + itemName + " Stock", "보유 " + stock, new Vector2(20f, -174f), new Vector2(72f, 22f), 13f, FontStyles.Bold, subColor, TextAlignmentOptions.MidlineLeft);
AddResultText(cardRect, "Duty Free " + itemName + " Cost", cost + " M", new Vector2(102f, -174f), new Vector2(84f, 22f), 15f, FontStyles.Bold, new Color(0.87f, 0.55f, 0.08f, 1f), TextAlignmentOptions.MidlineRight);
GameObject stripObject = CreateUiObject("Duty Free " + itemName + " Buy Strip", cardRect, typeof(Image));
RectTransform stripRect = stripObject.GetComponent<RectTransform>();
stripRect.anchorMin = new Vector2(0.5f, 0f);
stripRect.anchorMax = new Vector2(0.5f, 0f);
stripRect.pivot = new Vector2(0.5f, 0f);
stripRect.anchoredPosition = new Vector2(0f, 14f);
stripRect.sizeDelta = new Vector2(160f, 34f);
Image stripImage = stripObject.GetComponent<Image>();
stripImage.sprite = LoadUiSprite(ResultButtonResourcePath, ResultButtonAssetPath);
stripImage.color = canBuy ? Color.white : new Color(0.60f, 0.65f, 0.68f, 0.88f);
stripImage.preserveAspect = false;
stripImage.raycastTarget = false;
TextMeshProUGUI stripText = AddResultText(cardRect, "Duty Free " + itemName + " Buy Label", canBuy ? "구매" : "마일리지 부족", new Vector2(23f, -200f), new Vector2(160f, 32f), 14f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
stripText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
stripText.outlineWidth = 0.12f;
}
private void ShowMapPreview() {
@@ -384,6 +950,11 @@ public class GameFlowManager : MonoBehaviour {
case FlowScreen.OpeningStory:
ShowOpeningStory();
break;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
case FlowScreen.DebugMenu:
ShowDebugMenu();
break;
#endif
case FlowScreen.Intro:
default:
ShowIntro();
@@ -474,6 +1045,7 @@ public class GameFlowManager : MonoBehaviour {
private void EnsureOverlay() {
EnsureEventSystem();
EnsureFlowCamera();
if(overlayCanvas != null)
{
@@ -487,6 +1059,7 @@ public class GameFlowManager : MonoBehaviour {
overlayCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
overlayCanvas.worldCamera = null;
overlayCanvas.sortingOrder = 100;
overlayCanvas.targetDisplay = 0;
SetUiLayer(canvasObject);
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
@@ -515,6 +1088,8 @@ public class GameFlowManager : MonoBehaviour {
private void SetDefaultOverlayBackground() {
if(overlayBackground != null)
{
overlayBackground.sprite = null;
overlayBackground.preserveAspect = false;
overlayBackground.color = new Color(0.02f, 0.035f, 0.045f, 0.92f);
}
@@ -527,6 +1102,8 @@ public class GameFlowManager : MonoBehaviour {
private void BuildIntroScreen() {
if(overlayBackground != null)
{
overlayBackground.sprite = null;
overlayBackground.preserveAspect = false;
overlayBackground.color = Color.clear;
}
@@ -547,6 +1124,8 @@ public class GameFlowManager : MonoBehaviour {
private void BuildOpeningStoryScreen() {
if(overlayBackground != null)
{
overlayBackground.sprite = null;
overlayBackground.preserveAspect = false;
overlayBackground.color = new Color(0.012f, 0.018f, 0.024f, 0.96f);
}
@@ -976,9 +1555,9 @@ public class GameFlowManager : MonoBehaviour {
stampRect.localRotation = Quaternion.Euler(0f, 0f, -12f);
Image stampImage = stampObject.GetComponent<Image>();
stampImage.sprite = LoadUiSpriteRegion("Assets/ConceptArt/Intro/intro_stamps_01.png", new Rect(54f, 48f, 790f, 790f));
stampImage.sprite = LoadUiSprite(DepartureStampResourcePath, "Assets/Resources/UI_DepartureStamp_Red.png");
stampImage.color = stampImage.sprite != null
? new Color(1f, 1f, 1f, 0.94f)
? new Color(1f, 1f, 1f, 0.96f)
: new Color(0.76f, 0.08f, 0.06f, 0.96f);
stampImage.preserveAspect = true;
stampImage.raycastTarget = false;
@@ -1274,6 +1853,8 @@ public class GameFlowManager : MonoBehaviour {
private void EnsureFlowCamera() {
if(flowCamera != null)
{
flowCamera.enabled = true;
flowCamera.targetDisplay = 0;
return;
}
@@ -1288,6 +1869,7 @@ public class GameFlowManager : MonoBehaviour {
flowCamera.depth = 100f;
flowCamera.orthographic = true;
flowCamera.orthographicSize = 360f;
flowCamera.targetDisplay = 0;
}
private void ClearOverlay() {
@@ -1303,6 +1885,11 @@ public class GameFlowManager : MonoBehaviour {
}
private void SetOverlayVisible(bool visible) {
if(visible)
{
EnsureFlowCamera();
}
if(overlayCanvas != null)
{
overlayCanvas.gameObject.SetActive(visible);
+40 -39
View File
@@ -86,7 +86,7 @@ public class GameManager : MonoBehaviour {
private const string TotalMileageKey = "UniRun.TotalMileage";
private const string InitialMileageGrantedKey = "UniRun.InitialMileageGranted";
private const string CharacterFaceResourcePath = "UI_JJ_PassportPhoto";
private const string PassportPanelResourcePath = "UI_LeftHudPassportHorizontalGridPanel";
private const string PassportPanelResourcePath = "UI_LeftHudPassportCompactPanel";
private const string LifeStampResourcePath = "UI_LifeStamp";
private const string PassportItemSlotResourcePath = "UI_PassportItemSlot";
private const string TutorialGuideFrameResourcePath = "UI_TutorialAirportMonitorFrame";
@@ -95,7 +95,7 @@ public class GameManager : MonoBehaviour {
private const string RightHudLuggageLabelJapanResourcePath = "UI_RightHudLuggageLabel_Japan";
private const string RightHudLuggageLabelChinaResourcePath = "UI_RightHudLuggageLabel_China";
private const string RightHudLuggageLabelAmericaResourcePath = "UI_RightHudLuggageLabel_America";
private const float PassportHudWidth = 320f;
private const float PassportHudWidth = 261f;
private const float PassportHudHeight = 102f;
private const float PassportHudScale = 0.5f;
private const float RightInfoHudWidth = 260f;
@@ -383,7 +383,7 @@ public class GameManager : MonoBehaviour {
for(int i = 0; i < currentMapDefinition.openingSteps.Length; i++)
{
PlatformSpawner.TutorialStep step = currentMapDefinition.openingSteps[i];
CourseStep step = currentMapDefinition.openingSteps[i];
if(step != null
&& (step.guideType != TutorialGuideType.None
|| !string.IsNullOrEmpty(step.title)
@@ -436,7 +436,7 @@ public class GameManager : MonoBehaviour {
SetTutorialGuide(TutorialGuideType.Slide, "슬라이드", "오른쪽 클릭을 누르고 있으면 낮게 지나갑니다.");
break;
case 3:
SetTutorialGuide(TutorialGuideType.Items, "아이템", "초밥 -> 라멘 -> 방어막 -> 운동화 -> 마일리지 카드 순서입니다.");
SetTutorialGuide(TutorialGuideType.Items, "아이템", "초밥 -> 라멘 -> 방어막 -> 마일리지 카드 -> 운동화 순서입니다.");
break;
default:
tutorialGuideSequenceComplete = true;
@@ -795,24 +795,24 @@ public class GameManager : MonoBehaviour {
}
private void UpdateTutorialTextLayout(bool showItemRow, bool showFullText) {
Vector2 textPosition = new Vector2(104f, -38f);
Vector2 textSize = new Vector2(250f, 20f);
Vector2 bodyPosition = new Vector2(104f, -60f);
Vector2 bodySize = new Vector2(250f, 38f);
Vector2 textPosition = new Vector2(118f, -38f);
Vector2 textSize = new Vector2(236f, 20f);
Vector2 bodyPosition = new Vector2(118f, -60f);
Vector2 bodySize = new Vector2(236f, 38f);
if(showItemRow)
{
textPosition = new Vector2(110f, -38f);
textSize = new Vector2(242f, 20f);
bodyPosition = new Vector2(110f, -60f);
bodySize = new Vector2(242f, 38f);
textPosition = new Vector2(124f, -38f);
textSize = new Vector2(228f, 20f);
bodyPosition = new Vector2(124f, -60f);
bodySize = new Vector2(228f, 38f);
}
else if(showFullText)
{
textPosition = new Vector2(54f, -39f);
textSize = new Vector2(284f, 20f);
bodyPosition = new Vector2(54f, -61f);
bodySize = new Vector2(284f, 39f);
textPosition = new Vector2(68f, -39f);
textSize = new Vector2(270f, 20f);
bodyPosition = new Vector2(68f, -61f);
bodySize = new Vector2(270f, 39f);
}
if(tutorialTitleText != null)
@@ -1006,7 +1006,7 @@ public class GameManager : MonoBehaviour {
frameRect.anchorMin = new Vector2(0f, 1f);
frameRect.anchorMax = new Vector2(0f, 1f);
frameRect.pivot = new Vector2(0f, 1f);
frameRect.anchoredPosition = new Vector2(30f, -20f);
frameRect.anchoredPosition = new Vector2(32f, -20f);
frameRect.sizeDelta = new Vector2(72f, 68f);
Image frameImage = faceFrame.GetComponent<Image>();
@@ -1045,8 +1045,8 @@ public class GameManager : MonoBehaviour {
lifeRect.anchorMin = new Vector2(0f, 1f);
lifeRect.anchorMax = new Vector2(0f, 1f);
lifeRect.pivot = new Vector2(0f, 1f);
lifeRect.anchoredPosition = new Vector2(124f + (i * 44f), -20f);
lifeRect.sizeDelta = new Vector2(30f, 30f);
lifeRect.anchoredPosition = new Vector2(125f + (i * 45f), -23f);
lifeRect.sizeDelta = new Vector2(28f, 28f);
Image lifeImage = lifeObject.GetComponent<Image>();
lifeImage.sprite = lifeSprite;
@@ -1071,15 +1071,15 @@ public class GameManager : MonoBehaviour {
slotRect.anchorMin = new Vector2(0f, 1f);
slotRect.anchorMax = new Vector2(0f, 1f);
slotRect.pivot = new Vector2(0f, 1f);
slotRect.anchoredPosition = new Vector2(118f + (i * 46f), -66f);
slotRect.sizeDelta = new Vector2(40f, 24f);
slotRect.anchoredPosition = new Vector2(125f + (i * 45f), -62f);
slotRect.sizeDelta = new Vector2(30f, 30f);
Image slotImage = slotObject.GetComponent<Image>();
slotImage.sprite = slotSprite;
slotImage.color = slotSprite != null
? new Color(1f, 1f, 1f, 0.62f)
: new Color(0.92f, 0.97f, 1f, 0.22f);
slotImage.preserveAspect = false;
slotImage.preserveAspect = true;
slotImage.raycastTarget = false;
}
}
@@ -1140,8 +1140,8 @@ public class GameManager : MonoBehaviour {
CreateTutorialMainImage(tutorialGuideObject.transform);
CreateTutorialItemRow(tutorialGuideObject.transform);
tutorialTitleText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Title", new Vector2(104f, -38f), new Vector2(250f, 20f), 12f, FontStyles.Bold);
tutorialBodyText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Body", new Vector2(104f, -60f), new Vector2(250f, 38f), 9f, FontStyles.Normal);
tutorialTitleText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Title", new Vector2(118f, -38f), new Vector2(236f, 20f), 12f, FontStyles.Bold);
tutorialBodyText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Body", new Vector2(118f, -60f), new Vector2(236f, 38f), 9f, FontStyles.Normal);
tutorialTitleText.color = new Color(0.06f, 0.13f, 0.19f, 0.98f);
tutorialBodyText.color = new Color(0.10f, 0.18f, 0.23f, 0.95f);
CreateTutorialNextButton(tutorialGuideObject.transform);
@@ -1170,16 +1170,18 @@ public class GameManager : MonoBehaviour {
multiplierRect.anchorMin = new Vector2(1f, 0f);
multiplierRect.anchorMax = new Vector2(1f, 0f);
multiplierRect.pivot = new Vector2(1f, 0f);
multiplierRect.anchoredPosition = new Vector2(-2f, 1f);
multiplierRect.sizeDelta = new Vector2(22f, 15f);
multiplierRect.anchoredPosition = new Vector2(8f, -4f);
multiplierRect.sizeDelta = new Vector2(42f, 28f);
tutorialMultiplierText = multiplierObject.AddComponent<TextMeshProUGUI>();
tutorialMultiplierText.font = scoreText.font;
tutorialMultiplierText.text = "x2";
tutorialMultiplierText.text = "X2";
tutorialMultiplierText.alignment = TextAlignmentOptions.Center;
tutorialMultiplierText.fontSize = 9f;
tutorialMultiplierText.fontSize = 17f;
tutorialMultiplierText.fontStyle = FontStyles.Bold;
tutorialMultiplierText.color = new Color(1f, 0.91f, 0.33f, 1f);
tutorialMultiplierText.outlineColor = new Color(0.05f, 0.05f, 0.06f, 0.95f);
tutorialMultiplierText.outlineWidth = 0.18f;
tutorialMultiplierText.raycastTarget = false;
tutorialMultiplierText.textWrappingMode = TextWrappingModes.NoWrap;
tutorialMultiplierText.gameObject.SetActive(false);
@@ -1326,7 +1328,7 @@ public class GameManager : MonoBehaviour {
if(guideType == TutorialGuideType.Slide)
{
return LoadTutorialSprite("Assets/Sprites/JJ_Slide.png", "JJ_Slide_0");
return LoadTutorialSprite("Assets/Sprites/JJ_Slide.png", "JJ_Slide_2");
}
if(guideType == TutorialGuideType.Damage)
@@ -1519,7 +1521,7 @@ public class GameManager : MonoBehaviour {
float cursor = 0f;
AddStepMarkers(mapDefinition.openingSteps, ref cursor, targetDistance);
PlatformSpawner.TutorialStep[] loopSteps = mapDefinition.loopSteps;
CourseStep[] loopSteps = mapDefinition.loopSteps;
int loopGuard = 0;
while(cursor < targetDistance
&& loopSteps != null
@@ -1532,7 +1534,7 @@ public class GameManager : MonoBehaviour {
}
}
private void AddStepMarkers(PlatformSpawner.TutorialStep[] steps, ref float cursor, float targetDistance) {
private void AddStepMarkers(CourseStep[] steps, ref float cursor, float targetDistance) {
if(steps == null)
{
return;
@@ -1540,7 +1542,7 @@ public class GameManager : MonoBehaviour {
for(int i = 0; i < steps.Length; i++)
{
PlatformSpawner.TutorialStep step = steps[i];
CourseStep step = steps[i];
float stepDistance = GetStepDistance(step);
if(markerObjects.Count < MaxCourseObjectCount)
@@ -1564,7 +1566,7 @@ public class GameManager : MonoBehaviour {
}
}
private float GetStepDistance(PlatformSpawner.TutorialStep step) {
private float GetStepDistance(CourseStep step) {
if(step == null)
{
return CourseDistancePerInterval;
@@ -1573,7 +1575,7 @@ public class GameManager : MonoBehaviour {
return Mathf.Max(0.3f, step.spawnInterval) * CourseDistancePerInterval;
}
private bool TryGetMarkerInfo(PlatformSpawner.TutorialStep step, out MarkerInfo markerInfo) {
private bool TryGetMarkerInfo(CourseStep step, out MarkerInfo markerInfo) {
markerInfo = new MarkerInfo();
if(step == null)
@@ -1589,7 +1591,7 @@ public class GameManager : MonoBehaviour {
return true;
}
if(step.coursePlatformType == PlatformSpawner.CoursePlatformType.ForceHit
if(step.coursePlatformType == CoursePlatformType.ForceHit
|| step.platformPattern == Platform.PlatformPattern.ForceHit)
{
markerInfo.label = "!";
@@ -1606,7 +1608,7 @@ public class GameManager : MonoBehaviour {
return true;
}
if(step.coursePlatformType == PlatformSpawner.CoursePlatformType.Slide
if(step.coursePlatformType == CoursePlatformType.Slide
|| step.platformPattern == Platform.PlatformPattern.Slide)
{
markerInfo.label = "S";
@@ -1615,7 +1617,7 @@ public class GameManager : MonoBehaviour {
return true;
}
if(step.coursePlatformType == PlatformSpawner.CoursePlatformType.Jump
if(step.coursePlatformType == CoursePlatformType.Jump
|| step.platformPattern == Platform.PlatformPattern.LowLeft
|| step.platformPattern == Platform.PlatformPattern.LowMid
|| step.platformPattern == Platform.PlatformPattern.LowRight)
@@ -1652,7 +1654,7 @@ public class GameManager : MonoBehaviour {
markerObjects.Add(platformImage.gameObject);
}
private Color GetPlatformColor(PlatformSpawner.TutorialStep step) {
private Color GetPlatformColor(CourseStep step) {
if(step != null && step.spawnInterval >= 1.4f)
{
return new Color(0.28f, 0.50f, 0.57f, 0.96f);
@@ -1671,7 +1673,6 @@ public class GameManager : MonoBehaviour {
if(markerInfo.kind == "ForceHit")
{
CreateJumpFeature(progress, markerInfo);
CreateSlideFeature(progress, markerInfo);
return;
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b33ec3906e4a4b9d90e671da7a8a31a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
using static CourseStepFactory;
public static class AmericaMap {
public static MapDefinition Create() {
return new MapDefinition {
mapId = MapId.America,
displayName = "America",
routeName = "뉴욕 자유의 여신상",
isTutorial = false,
platformSkin = Platform.PlatformSkin.USA,
backgroundResourcePath = "Map_America_NewYork",
finishGateStyle = FinishGateStyle.AmericaLiberty,
targetDistance = 1700f,
timeLimit = 125f,
clearMileageReward = 500,
hasNextMap = false,
nextMapId = MapId.America,
openingSteps = new CourseStep[] {
Basic(),
Basic()
},
loopSteps = new CourseStep[] {
Basic(),
Jump(Platform.PlatformPattern.LowMid),
Basic(GroundY, 0.86f),
Slide(SlideObstaclePattern.SlideLayout.WidePair),
Basic(),
Aerial(AerialY + 0.2f, 1.0f),
Basic(GroundY, 0.86f),
Jump(Platform.PlatformPattern.LowLeft),
Basic(),
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
Basic(),
Jump(Platform.PlatformPattern.LowRight)
}
};
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4aebf219fb334943a89524de85c8977f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,36 @@
using static CourseStepFactory;
public static class ChinaMap {
public static MapDefinition Create() {
return new MapDefinition {
mapId = MapId.China,
displayName = "China",
routeName = "베이징 시장길",
isTutorial = false,
platformSkin = Platform.PlatformSkin.China,
backgroundResourcePath = "",
finishGateStyle = FinishGateStyle.ChinaPaifang,
targetDistance = 1450f,
timeLimit = 115f,
clearMileageReward = 350,
hasNextMap = true,
nextMapId = MapId.America,
openingSteps = new CourseStep[] {
Basic(),
Basic()
},
loopSteps = new CourseStep[] {
Basic(),
Jump(Platform.PlatformPattern.LowRight),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.LeftPair),
Basic(),
Aerial(AerialY + 0.15f, 1.08f),
Basic(GroundY, LandingSpacing),
Jump(),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.RightPair)
}
};
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5fa98f4232774d458c65e430f1bd1f99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
using static CourseStepFactory;
public static class JapanMap {
public static MapDefinition Create() {
return new MapDefinition {
mapId = MapId.Japan,
displayName = "Japan",
routeName = "후쿠오카 라멘 투어",
isTutorial = false,
platformSkin = Platform.PlatformSkin.Japan,
backgroundResourcePath = "",
finishGateStyle = FinishGateStyle.JapanTorii,
targetDistance = 1200f,
timeLimit = 105f,
clearMileageReward = 250,
hasNextMap = true,
nextMapId = MapId.China,
openingSteps = new CourseStep[] {
Basic(),
Basic()
},
loopSteps = new CourseStep[] {
Basic(),
Jump(),
Basic(GroundY, LandingSpacing),
Aerial(),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
Basic(),
Jump(Platform.PlatformPattern.LowLeft),
Basic()
}
};
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8fb126c4369f4ff7a83eaad31e6e581e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,56 @@
using static CourseStepFactory;
public static class TutorialIncheonMap {
public static MapDefinition Create() {
return new MapDefinition {
mapId = MapId.TutorialIncheon,
displayName = "Tutorial / Incheon",
routeName = "인천공항 출발 준비",
isTutorial = true,
platformSkin = Platform.PlatformSkin.Airport,
backgroundResourcePath = "Map_Tutorial_Incheon_Airport",
finishGateStyle = FinishGateStyle.AirportDeparture,
targetDistance = 380f,
timeLimit = 85f,
clearMileageReward = 150,
hasNextMap = true,
nextMapId = MapId.Japan,
openingSteps = new CourseStep[] {
Basic(),
Basic(),
Basic(GroundY, BasicSpacing),
Jump(GameManager.TutorialGuideType.Jump, "점프 구간", "가방 장애물이 오면 왼쪽 클릭으로 뛰어넘습니다."),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
Aerial(GameManager.TutorialGuideType.DoubleJump, "2단 점프 구간", "다음 발판이 멀리 떨어져 있습니다.\n공중에서 왼쪽 클릭을 한 번 더 누릅니다."),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
Slide(GameManager.TutorialGuideType.Slide, "슬라이드 구간", "천장 장애물이 오면 오른쪽 클릭을 누르고 지나갑니다."),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "초밥", "잃은 목숨을 1 회복합니다.\n방금 줄어든 목숨을 채워 봅니다.", Platform.ItemPattern.HealthSushi),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "라멘", "잠깐 무적이 됩니다.\n먹은 뒤 다음 장애물에 일부러 부딪혀 봅니다.", Platform.ItemPattern.InvincibleRamen),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
Basic(GroundY, BasicSpacing),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "방어막", "다음 충돌 1회를 막습니다.\n먹은 뒤 바로 충돌을 막아 봅니다.", Platform.ItemPattern.Shield),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "마일리지 카드", "잠깐 마일리지가 2배가 됩니다.\n먹고 바로 마일리지를 모아 봅니다.", Platform.ItemPattern.MileageCard),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "운동화", "느려진 속도를 회복합니다.\n방금 떨어진 속도를 되돌려 봅니다.", Platform.ItemPattern.SpeedShoes),
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "실전 연습", "시간이 지나면 달리기 속도는 조금씩 빨라지고, 장애물에 맞으면 느려집니다.\n방금 먹은 신발 아이템으로 회복한 뒤 출국 게이트까지 달려보세요."),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
Basic(),
Basic(),
Basic(),
Basic(GroundY, LandingSpacing)
},
loopSteps = CreateEasyLoop()
};
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c16e17831dc94b26a6cfa4c6bbf17130
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+6 -342
View File
@@ -2,12 +2,6 @@ using UnityEngine;
public static class MapDatabase {
private static MapDefinition[] maps;
private const float GroundY = -2.4f;
private const float AerialY = -0.85f;
private const float BasicSpacing = 0.58f;
private const float ActionSpacing = 0.72f;
private const float LandingSpacing = 0.64f;
private const float AerialSpacing = 2.05f;
public static MapDefinition[] AllMaps {
get {
@@ -64,313 +58,10 @@ public static class MapDatabase {
}
maps = new MapDefinition[] {
CreateTutorialIncheon(),
CreateJapan(),
CreateChina(),
CreateAmerica()
};
}
private static MapDefinition CreateTutorialIncheon() {
return new MapDefinition {
mapId = MapId.TutorialIncheon,
displayName = "Tutorial / Incheon",
routeName = "인천공항 출발 준비",
isTutorial = true,
platformSkin = Platform.PlatformSkin.Airport,
backgroundResourcePath = "Map_Tutorial_Incheon_Airport",
finishGateStyle = FinishGateStyle.AirportDeparture,
targetDistance = 320f,
timeLimit = 75f,
clearMileageReward = 150,
hasNextMap = true,
nextMapId = MapId.Japan,
openingSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Basic(),
Basic(GroundY, BasicSpacing),
Jump(GameManager.TutorialGuideType.Jump, "점프 구간", "가방 장애물이 오면 왼쪽 클릭으로 뛰어넘습니다."),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
Aerial(GameManager.TutorialGuideType.DoubleJump, "2단 점프 구간", "다음 발판이 멀리 떨어져 있습니다.\n공중에서 왼쪽 클릭을 한 번 더 누릅니다."),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
Slide(GameManager.TutorialGuideType.Slide, "슬라이드 구간", "천장 장애물이 오면 오른쪽 클릭을 누르고 지나갑니다."),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "초밥", "잃은 목숨을 1 회복합니다.\n방금 줄어든 목숨을 채워 봅니다.", Platform.ItemPattern.HealthSushi),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "라멘", "잠깐 무적이 됩니다.\n먹은 뒤 다음 장애물에 일부러 부딪혀 봅니다.", Platform.ItemPattern.InvincibleRamen),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, LandingSpacing),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "방어막", "다음 충돌 1회를 막습니다.\n먹은 뒤 바로 충돌을 막아 봅니다.", Platform.ItemPattern.Shield),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, LandingSpacing),
ForceHit(GameManager.TutorialGuideType.None, "", ""),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "운동화", "느려진 속도를 회복합니다.\n방금 떨어진 속도를 되돌려 봅니다.", Platform.ItemPattern.SpeedShoes),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "마일리지 카드", "잠깐 마일리지가 2배가 됩니다.\n먹고 바로 마일리지를 모아 봅니다.", Platform.ItemPattern.MileageCard),
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
Basic(),
Basic(),
Basic(),
Basic(GroundY, LandingSpacing)
},
loopSteps = CreateEasyLoop()
};
}
private static MapDefinition CreateJapan() {
return new MapDefinition {
mapId = MapId.Japan,
displayName = "Japan",
routeName = "후쿠오카 라멘 투어",
isTutorial = false,
platformSkin = Platform.PlatformSkin.Japan,
backgroundResourcePath = "",
finishGateStyle = FinishGateStyle.JapanTorii,
targetDistance = 1200f,
timeLimit = 105f,
clearMileageReward = 250,
hasNextMap = true,
nextMapId = MapId.China,
openingSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Basic()
},
loopSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Jump(),
Basic(GroundY, LandingSpacing),
Aerial(),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
Basic(),
Jump(Platform.PlatformPattern.LowLeft),
Basic()
}
};
}
private static MapDefinition CreateChina() {
return new MapDefinition {
mapId = MapId.China,
displayName = "China",
routeName = "베이징 시장길",
isTutorial = false,
platformSkin = Platform.PlatformSkin.China,
backgroundResourcePath = "",
finishGateStyle = FinishGateStyle.ChinaPaifang,
targetDistance = 1450f,
timeLimit = 115f,
clearMileageReward = 350,
hasNextMap = true,
nextMapId = MapId.America,
openingSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Basic()
},
loopSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Jump(Platform.PlatformPattern.LowRight),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.LeftPair),
Basic(),
Aerial(AerialY + 0.15f, 1.08f),
Basic(GroundY, LandingSpacing),
Jump(),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.RightPair)
}
};
}
private static MapDefinition CreateAmerica() {
return new MapDefinition {
mapId = MapId.America,
displayName = "America",
routeName = "뉴욕 자유의 여신상",
isTutorial = false,
platformSkin = Platform.PlatformSkin.USA,
backgroundResourcePath = "Map_America_NewYork",
finishGateStyle = FinishGateStyle.AmericaLiberty,
targetDistance = 1700f,
timeLimit = 125f,
clearMileageReward = 500,
hasNextMap = false,
nextMapId = MapId.America,
openingSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Basic()
},
loopSteps = new PlatformSpawner.TutorialStep[] {
Basic(),
Jump(Platform.PlatformPattern.LowMid),
Basic(GroundY, 0.86f),
Slide(SlideObstaclePattern.SlideLayout.WidePair),
Basic(),
Aerial(AerialY + 0.2f, 1.0f),
Basic(GroundY, 0.86f),
Jump(Platform.PlatformPattern.LowLeft),
Basic(),
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
Basic(),
Jump(Platform.PlatformPattern.LowRight)
}
};
}
private static PlatformSpawner.TutorialStep[] CreateEasyLoop() {
return new PlatformSpawner.TutorialStep[] {
Basic(),
Jump(),
Basic(GroundY, LandingSpacing),
Aerial(),
Basic(GroundY, LandingSpacing),
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
Basic()
};
}
private static PlatformSpawner.TutorialStep Basic(
float yPosition = GroundY,
float spawnInterval = BasicSpacing,
Platform.MileagePattern mileagePattern = Platform.MileagePattern.SafeLine,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "",
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
return Step(
Platform.PlatformPattern.Empty,
mileagePattern,
SlideObstaclePattern.SlideLayout.Random,
guideType,
yPosition,
spawnInterval,
title,
message,
itemPattern,
PlatformSpawner.CoursePlatformType.Basic);
}
private static PlatformSpawner.TutorialStep Jump(
Platform.PlatformPattern platformPattern = Platform.PlatformPattern.LowMid,
float spawnInterval = ActionSpacing,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Step(
platformPattern,
Platform.MileagePattern.JumpArc,
SlideObstaclePattern.SlideLayout.Random,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
PlatformSpawner.CoursePlatformType.Jump);
}
private static PlatformSpawner.TutorialStep Jump(
GameManager.TutorialGuideType guideType,
string title,
string message) {
return Jump(Platform.PlatformPattern.LowMid, ActionSpacing, guideType, title, message);
}
private static PlatformSpawner.TutorialStep Aerial(
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Aerial(AerialY, AerialSpacing, guideType, title, message);
}
private static PlatformSpawner.TutorialStep Aerial(
float yPosition,
float spawnInterval,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Step(
Platform.PlatformPattern.Empty,
Platform.MileagePattern.SafeLine,
SlideObstaclePattern.SlideLayout.Random,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
PlatformSpawner.CoursePlatformType.Basic);
}
private static PlatformSpawner.TutorialStep Slide(
SlideObstaclePattern.SlideLayout slideLayout = SlideObstaclePattern.SlideLayout.CenterPair,
float spawnInterval = ActionSpacing,
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
string title = "",
string message = "") {
return Step(
Platform.PlatformPattern.Slide,
Platform.MileagePattern.SlideLine,
slideLayout,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
PlatformSpawner.CoursePlatformType.Slide);
}
private static PlatformSpawner.TutorialStep Slide(
GameManager.TutorialGuideType guideType,
string title,
string message) {
return Slide(SlideObstaclePattern.SlideLayout.CenterPair, ActionSpacing, guideType, title, message);
}
private static PlatformSpawner.TutorialStep ForceHit(
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.Damage,
string title = "",
string message = "",
float spawnInterval = ActionSpacing) {
return Step(
Platform.PlatformPattern.ForceHit,
Platform.MileagePattern.None,
SlideObstaclePattern.SlideLayout.WidePair,
guideType,
GroundY,
spawnInterval,
title,
message,
Platform.ItemPattern.None,
PlatformSpawner.CoursePlatformType.ForceHit);
}
private static PlatformSpawner.TutorialStep Step(
Platform.PlatformPattern platformPattern,
Platform.MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout,
GameManager.TutorialGuideType guideType,
float yPosition,
float spawnInterval,
string title = "",
string message = "",
Platform.ItemPattern itemPattern = Platform.ItemPattern.None,
PlatformSpawner.CoursePlatformType coursePlatformType = PlatformSpawner.CoursePlatformType.Basic) {
return new PlatformSpawner.TutorialStep {
coursePlatformType = coursePlatformType,
platformPattern = platformPattern,
mileagePattern = mileagePattern,
slideLayout = slideLayout,
itemPattern = itemPattern,
guideType = guideType,
yPosition = yPosition,
spawnInterval = spawnInterval,
title = title,
message = message
TutorialIncheonMap.Create(),
JapanMap.Create(),
ChinaMap.Create(),
AmericaMap.Create()
};
}
@@ -388,35 +79,8 @@ public static class MapDatabase {
clearMileageReward = source.clearMileageReward,
hasNextMap = source.hasNextMap,
nextMapId = source.nextMapId,
openingSteps = CloneSteps(source.openingSteps),
loopSteps = CloneSteps(source.loopSteps)
openingSteps = CourseStepFactory.CloneSteps(source.openingSteps),
loopSteps = CourseStepFactory.CloneSteps(source.loopSteps)
};
}
public static PlatformSpawner.TutorialStep[] CloneSteps(PlatformSpawner.TutorialStep[] source) {
if(source == null)
{
return new PlatformSpawner.TutorialStep[0];
}
PlatformSpawner.TutorialStep[] steps = new PlatformSpawner.TutorialStep[source.Length];
for(int i = 0; i < source.Length; i++)
{
PlatformSpawner.TutorialStep sourceStep = source[i];
steps[i] = Step(
sourceStep.platformPattern,
sourceStep.mileagePattern,
sourceStep.slideLayout,
sourceStep.guideType,
sourceStep.yPosition,
sourceStep.spawnInterval,
sourceStep.title,
sourceStep.message,
sourceStep.itemPattern,
sourceStep.coursePlatformType);
}
return steps;
}
}
+2 -2
View File
@@ -14,6 +14,6 @@ public class MapDefinition {
public int clearMileageReward;
public bool hasNextMap;
public MapId nextMapId;
public PlatformSpawner.TutorialStep[] openingSteps;
public PlatformSpawner.TutorialStep[] loopSteps;
public CourseStep[] openingSteps;
public CourseStep[] loopSteps;
}
+8 -260
View File
@@ -39,8 +39,6 @@ public class Platform : MonoBehaviour {
USA
}
public GameObject[] obstacles; // 장애물 오브젝트들
public int emptyPatternWeight = 2; // 장애물이 없는 패턴이 선택될 가중치
public Sprite mileageSprite; // 이 발판에서 사용할 마일리지 토큰 스프라이트
public int mileagePatternChance = 50; // 마일리지 패턴 등장 확률
public int smallMileageValue = 10; // 일반 토큰 가치
@@ -49,9 +47,7 @@ public class Platform : MonoBehaviour {
private bool stepped = false; // 플레이어 캐릭터가 밟았었는가
private MileagePickup[] mileagePickups; // 런타임에 생성하는 마일리지 토큰 풀
private ItemPickup[] itemPickups; // 런타임에 생성하는 튜토리얼 아이템 풀
private PlatformPattern reservedPattern = PlatformPattern.Random; // 다음 활성화 때 사용할 수동 패턴
private MileagePattern reservedMileagePattern = MileagePattern.Random; // 다음 활성화 때 사용할 마일리지 패턴
private SlideObstaclePattern.SlideLayout reservedSlideLayout = SlideObstaclePattern.SlideLayout.Random; // 다음 슬라이딩 장애물 배치
private ItemPattern reservedItemPattern = ItemPattern.None; // 다음 활성화 때 사용할 아이템 배치
private PlatformSkin reservedSkin = PlatformSkin.Airport; // 현재 맵에서 사용할 발판/장애물 스킨
private const int MileagePoolSize = 5;
@@ -63,23 +59,14 @@ public class Platform : MonoBehaviour {
private const float JumpMileageMinWidthScale = 1.8f;
private const float JumpMileageMaxWidthScale = 2.5f;
public void ConfigureForTutorial(
PlatformPattern pattern,
MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout = SlideObstaclePattern.SlideLayout.Random,
ItemPattern itemPattern = ItemPattern.None,
PlatformSkin platformSkin = PlatformSkin.Airport) {
reservedPattern = pattern;
public void ConfigureForTutorial(MileagePattern mileagePattern, ItemPattern itemPattern = ItemPattern.None, PlatformSkin platformSkin = PlatformSkin.Airport) {
reservedMileagePattern = mileagePattern;
reservedSlideLayout = slideLayout;
reservedItemPattern = itemPattern;
reservedSkin = platformSkin;
}
public void ConfigureForRandom(PlatformSkin platformSkin = PlatformSkin.Airport) {
reservedPattern = PlatformPattern.Random;
reservedMileagePattern = MileagePattern.Random;
reservedSlideLayout = SlideObstaclePattern.SlideLayout.Random;
reservedItemPattern = ItemPattern.None;
reservedSkin = platformSkin;
}
@@ -93,38 +80,10 @@ public class Platform : MonoBehaviour {
EnsureMileagePickups();
EnsureItemPickups();
for(int i = 0; i < obstacles.Length; i++)
{
obstacles[i].SetActive(false);
}
HideMileagePickups();
HideItemPickups();
if(reservedPattern != PlatformPattern.Random)
{
ApplyReservedPattern();
return;
}
if(obstacles.Length == 0)
{
TryPlaceMileagePattern(null);
return;
}
// 낮은 장애물과 높은 장애물이 동시에 켜져 불가능한 배치가 되지 않도록 하나만 선택한다.
int patternIndex = Random.Range(0, obstacles.Length + emptyPatternWeight);
GameObject activeObstacle = null;
if(patternIndex < obstacles.Length)
{
activeObstacle = obstacles[patternIndex];
ConfigureSlideObstacle(activeObstacle);
activeObstacle.SetActive(true);
}
TryPlaceMileagePattern(activeObstacle);
PlaceReservedMileagePattern();
PlaceReservedItemPattern();
}
void OnCollisionEnter2D(Collision2D collision) {
@@ -223,100 +182,16 @@ public class Platform : MonoBehaviour {
}
}
private void TryPlaceMileagePattern(GameObject activeObstacle) {
private void TryPlaceMileagePattern() {
if(mileageSprite == null || mileagePickups == null || Random.Range(0, 100) >= mileagePatternChance)
{
return;
}
if(activeObstacle == null)
{
PlaceSafeMileageLine();
return;
}
if(activeObstacle.name.Contains("Slide"))
{
PlaceSlideMileageLine();
}
else
{
PlaceJumpMileageArc(GetWorldRelativeX(activeObstacle.transform));
}
PlaceSafeMileageLine();
}
private void ApplyReservedPattern() {
GameObject activeObstacle = null;
switch(reservedPattern)
{
case PlatformPattern.LowLeft:
activeObstacle = FindObstacle("Obstacle Left");
break;
case PlatformPattern.LowMid:
activeObstacle = FindObstacle("Obstacle Mid");
break;
case PlatformPattern.LowRight:
activeObstacle = FindObstacle("Obstacle Right");
break;
case PlatformPattern.Slide:
activeObstacle = FindObstacle("Slide Obstacle Pattern");
break;
case PlatformPattern.ForceHit:
activeObstacle = FindObstacle("Obstacle Mid");
ActivateObstacle(activeObstacle);
GameObject slideObstacle = FindObstacle("Slide Obstacle Pattern");
reservedSlideLayout = SlideObstaclePattern.SlideLayout.WidePair;
ActivateObstacle(slideObstacle);
break;
}
if(reservedPattern != PlatformPattern.ForceHit && activeObstacle != null)
{
ActivateObstacle(activeObstacle);
}
PlaceReservedMileagePattern(activeObstacle);
PlaceReservedItemPattern();
}
private void ActivateObstacle(GameObject obstacle) {
if(obstacle == null)
{
return;
}
ConfigureSlideObstacle(obstacle);
obstacle.SetActive(true);
}
private void ConfigureSlideObstacle(GameObject activeObstacle) {
if(activeObstacle == null || !activeObstacle.name.Contains("Slide"))
{
return;
}
SlideObstaclePattern slideObstaclePattern = activeObstacle.GetComponent<SlideObstaclePattern>();
if(slideObstaclePattern != null)
{
slideObstaclePattern.ConfigureLayout(reservedSlideLayout);
}
}
private GameObject FindObstacle(string obstacleName) {
for(int i = 0; i < obstacles.Length; i++)
{
if(obstacles[i] != null && obstacles[i].name == obstacleName)
{
return obstacles[i];
}
}
return null;
}
private void PlaceReservedMileagePattern(GameObject activeObstacle) {
private void PlaceReservedMileagePattern() {
if(mileageSprite == null || mileagePickups == null || reservedMileagePattern == MileagePattern.None)
{
return;
@@ -330,8 +205,7 @@ public class Platform : MonoBehaviour {
if(reservedMileagePattern == MileagePattern.JumpArc)
{
float obstacleX = activeObstacle != null ? GetWorldRelativeX(activeObstacle.transform) : 0f;
PlaceJumpMileageArc(obstacleX);
PlaceJumpMileageArc(0f);
return;
}
@@ -341,7 +215,7 @@ public class Platform : MonoBehaviour {
return;
}
TryPlaceMileagePattern(activeObstacle);
TryPlaceMileagePattern();
}
private void PlaceReservedItemPattern() {
@@ -397,22 +271,10 @@ public class Platform : MonoBehaviour {
private void ApplyVisualSkin() {
#if UNITY_EDITOR
Sprite platformSprite = LoadSpriteAtPath(GetPlatformSkinPath(reservedSkin));
Sprite ceilingSprite = LoadSpriteAtPath(GetCeilingSkinPath(reservedSkin));
Sprite lowObstacleSprite = LoadSpriteAtPath(GetLowObstacleSkinPath(reservedSkin));
Sprite hangingObstacleSprite = LoadSpriteAtPath(GetHangingObstacleSkinPath(reservedSkin));
Sprite mileageSkinSprite = LoadSpriteAtPath(GetMileageSkinPath(reservedSkin));
ApplySprite(GetComponent<SpriteRenderer>(), platformSprite);
Transform ceilingPlatform = FindChildRecursive(transform, "Ceiling Platform");
if(ceilingPlatform != null)
{
ApplySprite(ceilingPlatform.GetComponent<SpriteRenderer>(), ceilingSprite != null ? ceilingSprite : platformSprite);
}
ApplyLowObstacleSkin(lowObstacleSprite);
ApplyHangingObstacleSkin(hangingObstacleSprite);
if(mileageSkinSprite != null)
{
mileageSprite = mileageSkinSprite;
@@ -421,52 +283,6 @@ public class Platform : MonoBehaviour {
}
#if UNITY_EDITOR
private void ApplyLowObstacleSkin(Sprite obstacleSprite) {
if(obstacleSprite == null || obstacles == null)
{
return;
}
for(int i = 0; i < obstacles.Length; i++)
{
GameObject obstacle = obstacles[i];
if(obstacle == null || obstacle.name.Contains("Slide"))
{
continue;
}
ApplySprite(obstacle.GetComponent<SpriteRenderer>(), obstacleSprite);
}
}
private void ApplyHangingObstacleSkin(Sprite obstacleSprite) {
if(obstacleSprite == null)
{
return;
}
GameObject slideObstacle = FindObstacle("Slide Obstacle Pattern");
if(slideObstacle == null)
{
return;
}
SlideObstaclePattern slideObstaclePattern = slideObstacle.GetComponent<SlideObstaclePattern>();
if(slideObstaclePattern == null || slideObstaclePattern.obstacles == null)
{
return;
}
for(int i = 0; i < slideObstaclePattern.obstacles.Length; i++)
{
Transform hangingObstacle = slideObstaclePattern.obstacles[i];
if(hangingObstacle != null)
{
ApplySprite(hangingObstacle.GetComponent<SpriteRenderer>(), obstacleSprite);
}
}
}
private void ApplySprite(SpriteRenderer spriteRenderer, Sprite sprite) {
if(spriteRenderer != null && sprite != null)
{
@@ -483,31 +299,6 @@ public class Platform : MonoBehaviour {
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
}
private Transform FindChildRecursive(Transform parent, string childName) {
if(parent == null)
{
return null;
}
for(int i = 0; i < parent.childCount; i++)
{
Transform child = parent.GetChild(i);
if(child.name == childName)
{
return child;
}
Transform match = FindChildRecursive(child, childName);
if(match != null)
{
return match;
}
}
return null;
}
private string GetPlatformSkinPath(PlatformSkin platformSkin) {
switch(platformSkin)
{
@@ -523,45 +314,6 @@ public class Platform : MonoBehaviour {
}
}
private string GetCeilingSkinPath(PlatformSkin platformSkin) {
if(platformSkin == PlatformSkin.Airport)
{
return "Assets/Sprites/ObstacleSkins/Skin_Platform_AirportCeiling.png";
}
return GetPlatformSkinPath(platformSkin);
}
private string GetLowObstacleSkinPath(PlatformSkin platformSkin) {
switch(platformSkin)
{
case PlatformSkin.Japan:
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_StoneLantern.png";
case PlatformSkin.China:
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_MarketCrate.png";
case PlatformSkin.USA:
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_Cone.png";
case PlatformSkin.Airport:
default:
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_Suitcases.png";
}
}
private string GetHangingObstacleSkinPath(PlatformSkin platformSkin) {
switch(platformSkin)
{
case PlatformSkin.Japan:
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_RoadSign.png";
case PlatformSkin.China:
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_RedLanterns.png";
case PlatformSkin.USA:
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_Banner.png";
case PlatformSkin.Airport:
default:
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_AirportSign.png";
}
}
private string GetMileageSkinPath(PlatformSkin platformSkin) {
switch(platformSkin)
{
@@ -681,10 +433,6 @@ public class Platform : MonoBehaviour {
return new Vector3(position.x / parentScaleX, position.y / parentScaleY, 0f);
}
private float GetWorldRelativeX(Transform child) {
return child.localPosition.x * transform.localScale.x;
}
private void SetPickupValue(int index, int value) {
if(index < 0 || index >= mileagePickups.Length || !mileagePickups[index].gameObject.activeSelf)
{
+53 -84
View File
@@ -1,44 +1,9 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
// 발판을 생성하고 주기적으로 재배치하는 스크립트
public class PlatformSpawner : MonoBehaviour {
public enum CoursePlatformType {
Basic,
Jump,
Slide,
ForceHit
}
private enum TutorialStepKind {
None,
Jump,
DoubleJumpTakeoff,
Slide,
ForceHit,
HealthItem,
InvincibleItem,
ShieldItem,
SpeedItem,
MileageCardItem
}
[Serializable]
public class TutorialStep {
public CoursePlatformType coursePlatformType;
public Platform.PlatformPattern platformPattern;
public Platform.MileagePattern mileagePattern;
public SlideObstaclePattern.SlideLayout slideLayout;
public Platform.ItemPattern itemPattern;
public GameManager.TutorialGuideType guideType;
public float yPosition;
public float spawnInterval;
public string title;
public string message;
}
private class PendingTutorialGuide {
public Transform target;
public GameManager.TutorialGuideType guideType;
@@ -53,8 +18,8 @@ public class PlatformSpawner : MonoBehaviour {
public int count = 12; // 기본 발판 풀 개수
public bool usePatternCourse = true; // 검증된 발판 패턴 코스를 사용
public bool tutorialMode = true; // 처음에는 튜토리얼 패턴을 먼저 배치
public TutorialStep[] tutorialSteps; // 튜토리얼 발판 순서
public TutorialStep[] stagePatternSteps; // 튜토리얼 이후 반복할 검증된 발판 순서
public CourseStep[] tutorialSteps; // 튜토리얼 발판 순서
public CourseStep[] stagePatternSteps; // 튜토리얼 이후 반복할 검증된 발판 순서
public bool keepWorldSpacingBySpeed = true; // 게임 속도가 빨라져도 발판 사이 실제 거리를 유지
public int continuousCoursePoolCount = 12; // 기본 발판을 촘촘히 깔 때 필요한 최소 풀 크기
@@ -99,8 +64,8 @@ public class PlatformSpawner : MonoBehaviour {
usePatternCourse = true;
tutorialMode = mapDefinition.isTutorial;
mapPlatformSkin = mapDefinition.platformSkin;
tutorialSteps = MapDatabase.CloneSteps(mapDefinition.openingSteps);
stagePatternSteps = MapDatabase.CloneSteps(mapDefinition.loopSteps);
tutorialSteps = CourseStepFactory.CloneSteps(mapDefinition.openingSteps);
stagePatternSteps = CourseStepFactory.CloneSteps(mapDefinition.loopSteps);
courseStepIndex = 0;
courseSpawningStopped = false;
pendingTutorialGuides.Clear();
@@ -123,7 +88,7 @@ public class PlatformSpawner : MonoBehaviour {
activeFinishLandingPlatform = Instantiate(platformPrefab, poolPosition, Quaternion.identity);
activeFinishLandingPlatform.SetActive(false);
TutorialStep landingStep = CreateStep(
CourseStep landingStep = CreateStep(
Platform.PlatformPattern.Empty,
Platform.MileagePattern.None,
SlideObstaclePattern.SlideLayout.Random,
@@ -266,7 +231,7 @@ public class PlatformSpawner : MonoBehaviour {
{
lastSpawnTime = Time.time;
TutorialStep step = usePatternCourse ? GetCurrentCourseStep() : null;
CourseStep step = usePatternCourse ? GetCurrentCourseStep() : null;
timeBetSpawn = step != null ? GetStepSpawnInterval(step) : Random.Range(timeBetSpawnMin, timeBetSpawnMax);
float yPos = step != null
@@ -319,7 +284,7 @@ public class PlatformSpawner : MonoBehaviour {
}
}
private void RegisterTutorialGuide(Transform target, TutorialStep step) {
private void RegisterTutorialGuide(Transform target, CourseStep step) {
if(target == null
|| step == null
|| step.guideType == GameManager.TutorialGuideType.None
@@ -337,7 +302,7 @@ public class PlatformSpawner : MonoBehaviour {
});
}
private float GetTutorialGuideTriggerX(TutorialStep step) {
private float GetTutorialGuideTriggerX(CourseStep step) {
if(step != null && step.guideType == GameManager.TutorialGuideType.DoubleJump)
{
return -2f;
@@ -346,7 +311,7 @@ public class PlatformSpawner : MonoBehaviour {
return tutorialGuideTriggerX;
}
private void SpawnCourseObstacles(TutorialStep step, float yPos) {
private void SpawnCourseObstacles(CourseStep step, float yPos) {
if(step == null)
{
return;
@@ -355,7 +320,6 @@ public class PlatformSpawner : MonoBehaviour {
if(step.coursePlatformType == CoursePlatformType.ForceHit || step.platformPattern == Platform.PlatformPattern.ForceHit)
{
SpawnJumpObstacle(Platform.PlatformPattern.LowMid, yPos);
SpawnSlideObstacle(SlideObstaclePattern.SlideLayout.WidePair, yPos);
return;
}
@@ -440,7 +404,7 @@ public class PlatformSpawner : MonoBehaviour {
return platformObject;
}
private void ConfigurePlatform(GameObject platformObject, TutorialStep step) {
private void ConfigurePlatform(GameObject platformObject, CourseStep step) {
Platform platform = platformObject.GetComponent<Platform>();
if(platform == null)
@@ -454,12 +418,12 @@ public class PlatformSpawner : MonoBehaviour {
return;
}
platform.ConfigureForTutorial(Platform.PlatformPattern.Empty, step.mileagePattern, step.slideLayout, step.itemPattern, mapPlatformSkin);
platform.ConfigureForTutorial(step.mileagePattern, step.itemPattern, mapPlatformSkin);
// 설명은 발판 생성 시점이 아니라 실제 체험 직전에 RegisterTutorialGuide가 띄운다.
}
private float GetStepSpawnInterval(TutorialStep step) {
private float GetStepSpawnInterval(CourseStep step) {
float spawnInterval;
if(step != null && step.spawnInterval > 0f)
@@ -479,7 +443,7 @@ public class PlatformSpawner : MonoBehaviour {
return Mathf.Max(0.05f, spawnInterval);
}
private TutorialStep GetCurrentCourseStep() {
private CourseStep GetCurrentCourseStep() {
EnsurePatternSteps();
if(tutorialMode && courseStepIndex < tutorialSteps.Length)
@@ -525,23 +489,28 @@ public class PlatformSpawner : MonoBehaviour {
return;
}
tutorialSteps = new TutorialStep[] {
CreateTutorialStep(TutorialStepKind.Jump, Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(TutorialStepKind.DoubleJumpTakeoff, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackAerialSpacing),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(TutorialStepKind.Slide, Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(TutorialStepKind.ForceHit, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(TutorialStepKind.HealthItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing, Platform.ItemPattern.HealthSushi),
CreateTutorialStep(TutorialStepKind.InvincibleItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.InvincibleRamen),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(TutorialStepKind.ShieldItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.Shield),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(TutorialStepKind.SpeedItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.SpeedShoes),
CreateTutorialStep(TutorialStepKind.MileageCardItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.MileageCard),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
tutorialSteps = new CourseStep[] {
CreateTutorialStep(CourseStepKind.Jump, Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(CourseStepKind.DoubleJumpTakeoff, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackAerialSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(CourseStepKind.Slide, Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(CourseStepKind.ForceHit, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(CourseStepKind.HealthItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing, Platform.ItemPattern.HealthSushi),
CreateTutorialStep(CourseStepKind.InvincibleItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.InvincibleRamen),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
CreateTutorialStep(CourseStepKind.ShieldItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.Shield),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(CourseStepKind.MileageCardItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.MileageCard),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
CreateTutorialStep(CourseStepKind.SpeedItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.SpeedShoes),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
};
}
@@ -551,7 +520,7 @@ public class PlatformSpawner : MonoBehaviour {
return;
}
stagePatternSteps = new TutorialStep[] {
stagePatternSteps = new CourseStep[] {
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackBasicSpacing, "", ""),
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Jump),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackLandingSpacing, "", ""),
@@ -569,7 +538,7 @@ public class PlatformSpawner : MonoBehaviour {
};
}
private TutorialStep CreateStep(
private CourseStep CreateStep(
Platform.PlatformPattern platformPattern,
Platform.MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout,
@@ -580,7 +549,7 @@ public class PlatformSpawner : MonoBehaviour {
string message,
Platform.ItemPattern itemPattern = Platform.ItemPattern.None,
CoursePlatformType coursePlatformType = CoursePlatformType.Basic) {
TutorialStep step = new TutorialStep();
CourseStep step = new CourseStep();
step.coursePlatformType = coursePlatformType;
step.platformPattern = platformPattern;
step.mileagePattern = mileagePattern;
@@ -594,8 +563,8 @@ public class PlatformSpawner : MonoBehaviour {
return step;
}
private TutorialStep CreateTutorialStep(
TutorialStepKind stepKind,
private CourseStep CreateTutorialStep(
CourseStepKind stepKind,
Platform.PlatformPattern platformPattern,
Platform.MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout,
@@ -608,47 +577,47 @@ public class PlatformSpawner : MonoBehaviour {
switch(stepKind)
{
case TutorialStepKind.Jump:
case CourseStepKind.Jump:
guideType = GameManager.TutorialGuideType.Jump;
title = "점프";
message = "가방 장애물이 보이면 왼쪽 클릭으로 뛰어넘습니다.";
break;
case TutorialStepKind.DoubleJumpTakeoff:
case CourseStepKind.DoubleJumpTakeoff:
guideType = GameManager.TutorialGuideType.DoubleJump;
title = "2단 점프";
message = "다음 발판이 멀리 있으면 공중에서 한 번 더 왼쪽 클릭합니다.";
break;
case TutorialStepKind.Slide:
case CourseStepKind.Slide:
guideType = GameManager.TutorialGuideType.Slide;
title = "슬라이드";
message = "천장 표지판이 보이면 오른쪽 클릭을 누르고 지나갑니다.";
break;
case TutorialStepKind.ForceHit:
case CourseStepKind.ForceHit:
guideType = GameManager.TutorialGuideType.Damage;
title = "피격";
message = "이번 구간은 일부러 맞아 목숨과 속도 감소를 확인합니다.";
break;
case TutorialStepKind.HealthItem:
case CourseStepKind.HealthItem:
guideType = GameManager.TutorialGuideType.Items;
title = "초밥";
message = "초밥을 먹으면 잃은 목숨을 1 회복합니다.";
break;
case TutorialStepKind.InvincibleItem:
case CourseStepKind.InvincibleItem:
guideType = GameManager.TutorialGuideType.Items;
title = "라멘";
message = "라멘을 먹고 다음 충돌을 피해 없이 지나가 봅니다.";
break;
case TutorialStepKind.ShieldItem:
case CourseStepKind.ShieldItem:
guideType = GameManager.TutorialGuideType.Items;
title = "방어막";
message = "방어막을 먹고 다음 충돌 1회를 막아 봅니다.";
break;
case TutorialStepKind.SpeedItem:
case CourseStepKind.SpeedItem:
guideType = GameManager.TutorialGuideType.Items;
title = "운동화";
message = "운동화는 속도를 회복해 일정이 늦어지는 것을 줄입니다.";
break;
case TutorialStepKind.MileageCardItem:
case CourseStepKind.MileageCardItem:
guideType = GameManager.TutorialGuideType.Items;
title = "마일리지 카드";
message = "카드를 먹은 뒤 마일리지를 모으면 획득량이 늘어납니다.";
@@ -668,18 +637,18 @@ public class PlatformSpawner : MonoBehaviour {
GetCoursePlatformType(stepKind, platformPattern, yPosition));
}
private CoursePlatformType GetCoursePlatformType(TutorialStepKind stepKind, Platform.PlatformPattern platformPattern, float yPosition) {
if(stepKind == TutorialStepKind.ForceHit || platformPattern == Platform.PlatformPattern.ForceHit)
private CoursePlatformType GetCoursePlatformType(CourseStepKind stepKind, Platform.PlatformPattern platformPattern, float yPosition) {
if(stepKind == CourseStepKind.ForceHit || platformPattern == Platform.PlatformPattern.ForceHit)
{
return CoursePlatformType.ForceHit;
}
if(stepKind == TutorialStepKind.Slide || platformPattern == Platform.PlatformPattern.Slide)
if(stepKind == CourseStepKind.Slide || platformPattern == Platform.PlatformPattern.Slide)
{
return CoursePlatformType.Slide;
}
if(stepKind == TutorialStepKind.Jump
if(stepKind == CourseStepKind.Jump
|| platformPattern == Platform.PlatformPattern.LowLeft
|| platformPattern == Platform.PlatformPattern.LowMid
|| platformPattern == Platform.PlatformPattern.LowRight)
+22 -1
View File
@@ -9,11 +9,13 @@
| 파일 | 용도 |
| --- | --- |
| `Assets/Resources/UI_LeftHudPassportHorizontalGridPanel.png` | 현재 적용 중인 가로형 왼쪽 여권 HUD 배경 |
| `Assets/Resources/UI_LeftHudPassportCompactPanel.png` | 오른쪽 빈 장식 영역을 줄인 왼쪽 HUD 전용 컴팩트 여권 배경 |
| `Assets/Resources/UI_LeftHudPassportPanel.png` | 이전 큰 세로형 왼쪽 여권 HUD 배경 |
| `Assets/Resources/UI_PassportPanel.png` | 기존 작은 왼쪽 여권 HUD 배경 |
| `Assets/Resources/UI_JJ_PassportPhoto.png` | 현재 적용 중인 JJ 여권사진 UI |
| `Assets/Resources/UI_JJ_Face.png` | 플레이어 얼굴 UI |
| `Assets/Resources/UI_LifeStamp.png` | 목숨 도장 |
| `Assets/Resources/UI_DepartureStamp_Red.png` | 배경 없는 출국 도장 |
| `Assets/Resources/UI_PassportItemSlot.png` | 아이템 슬롯 |
| `Assets/Resources/Map_Tutorial_Incheon_Airport.png` | 튜토리얼 인천공항 배경 |
| `Assets/Resources/Map_America_NewYork.png` | 미국 뉴욕 배경 |
@@ -57,7 +59,8 @@
| 파일 | 용도 |
| --- | --- |
| `Assets/Resources/UI_LeftHudPassportHorizontalGridPanel.png` | 실제 적용하는 가로형 여권 2줄 그리드 HUD 런타임 PNG |
| `Assets/Resources/UI_LeftHudPassportCompactPanel.png` | 실제 적용하는 컴팩트 가로형 여권 2줄 그리드 HUD 런타임 PNG |
| `Assets/Resources/UI_LeftHudPassportHorizontalGridPanel.png` | 결과 화면과 원본 보관용 가로형 여권 2줄 그리드 HUD PNG |
| `Assets/ConceptArt/left_hud_passport_horizontal_grid_panel_concept_01.png` | 가로형 여권 2줄 그리드 HUD 컨셉 원본 |
| `Assets/Resources/UI_JJ_PassportPhoto.png` | 오프닝 만화의 JJ를 바탕으로 만든 현재 여권사진 런타임 PNG |
| `Assets/ConceptArt/jj_passport_photo_concept_01.png` | JJ 여권사진 컨셉 원본 |
@@ -96,6 +99,24 @@
| `Assets/ConceptArt/right_hud_luggage_label_china_concept_01.png` | 가로형 중국 버전 컨셉 원본 |
| `Assets/ConceptArt/right_hud_luggage_label_america_concept_01.png` | 가로형 미국 버전 컨셉 원본 |
## Result UI Assets
결과 화면은 여권 정산표처럼 필요한 정보만 간추려 보여줍니다.
| 파일 | 용도 |
| --- | --- |
| `Assets/Resources/UI_LeftHudPassportHorizontalGridPanel.png` | 결과 화면 여권 정산표 배경 |
| `Assets/Resources/UI_DepartureStamp_Red.png` | `출국 성공!` 클리어 도장 |
| `Assets/Sprites/UIIcons/UI_ClearRank.png` | 클리어 배지 |
| `Assets/Sprites/UIIcons/UI_Mileage.png` | 마일리지 스탯 |
| `Assets/Sprites/UIIcons/UI_Timer.png` | 도착 시간 스탯 |
| `Assets/Sprites/UIIcons/UI_Checkpoint.png` | 이동 거리/목적지 스탯 |
| `Assets/Sprites/UIIcons/UI_DutyFreeShop.png` | `면세점으로` 버튼 아이콘 |
| `Assets/Sprites/UIIcons/UI_AirplaneTravel.png` | `다음 여행지 가기` 버튼 아이콘 |
| `Assets/Resources/UI_TutorialNextButton.png` | 결과 화면 버튼 배경 |
| `Assets/Resources/Map_Tutorial_Incheon_Airport.png` | 결과 화면 뒤 인천공항 배경 |
| `Assets/Sprites/Souvenirs/` | 맵별 보상 기념품 표시 |
## Opening Story Assets
| 파일/폴더 | 용도 |
+23 -3
View File
@@ -19,6 +19,7 @@ README.md
| [gameplay.md](gameplay.md) | 조작, 장애물, 아이템, 클리어 조건 |
| [maps.md](maps.md) | 맵별 배경, 도착 지점, 패턴 방향 |
| [systems.md](systems.md) | 씬 흐름, 저장/로드, 면세점, 맵 해금 |
| [refactor-plan.md](refactor-plan.md) | 스크립트 책임 분리와 리팩터링 순서 |
| [feedback.md](feedback.md) | 날짜별 수정 기록과 다음 할 일 |
## 게임 핵심
@@ -73,6 +74,9 @@ Intro
랜덤 배치가 아니라 검증된 패턴을 이어붙이는 방식으로 갑니다.
튜토리얼 코스와 일반 여행 맵 코스는 분리합니다.
튜토리얼은 조작 학습과 아이템 체험을 담당하고, 일본/중국/미국은 일반 반복 패턴과 맵별 난이도 차이를 담당합니다.
```text
기본 발판
-> 점프 장애물
@@ -85,6 +89,21 @@ Intro
이 방식은 플레이어가 왜 그 조작을 해야 하는지 실제로 체험하게 만듭니다.
## 현재 구조 정리 방향
현재 `GameFlowManager`, `GameManager`, `MapDatabase`, `PlatformSpawner`에 책임이 크게 모여 있습니다.
동작을 바꾸기보다 책임을 분리하는 방향으로 정리합니다.
우선순위:
1. 완료: `PlatformSpawner.TutorialStep``CourseStep`으로 독립
2. 완료: 맵별 정의를 `Maps/Definitions`로 분리
3. 튜토리얼/일본/중국/미국 코스를 `CourseProfile`로 분리
4. 결과 화면, 면세점, 디버그 메뉴를 화면 View로 분리
5. 게임 HUD와 튜토리얼 안내창을 `GameManager`에서 분리
자세한 기준은 [refactor-plan.md](refactor-plan.md)를 봅니다.
## 문서 운영 규칙
- `README.md`는 짧은 입구로 유지합니다.
@@ -96,6 +115,7 @@ Intro
## 다음 작업 우선순위
1. 면세점 구매 아이템을 장착 슬롯과 연결
2. 일본, 중국, 미국 맵의 실제 반복 패턴과 난이도 차별화
3. 도착 지점 이미지의 실제 화면 크기를 플레이하면서 세부 조정
1. 튜토리얼 코스와 일반 맵 코스를 `CourseProfile`로 분리
2. `PlatformSpawner`의 fallback 코스 생성 책임 이동
3. `GameFlowManager`의 화면 UI를 View 클래스로 분리
4. 면세점 구매 아이템을 장착 슬롯과 연결
+13 -4
View File
@@ -97,10 +97,17 @@ HUD:
방향:
- 여행 정산표 느낌
- 클리어/실패
- 획득 마일리지
- 보상 기념품
- 면세점/다음 맵/재도전 버튼
- 클리어 문구는 `출국 성공!`
- 필요한 정보만 표시
- 목적지
- 획득 마일리지
- 도착 시간
- 이동 거리
- 보상 기념품
- 버튼은 클리어 시 `면세점으로`, 실패 시 `다시 도전`
- 보조 버튼은 `인트로` 대신 `다음 여행지 가기`
- 배경은 인천공항 배경을 사용
- 여권 페이지, 출국 도장, 마일리지/시간/체크포인트/기념품 아이콘, 공항 안내 패널 버튼을 사용
## DutyFreeShop
@@ -110,6 +117,8 @@ HUD:
- 상품 진열 중심
- 보유 마일리지 표시
- 다음 맵 이동은 오른쪽 방향성으로 안내
- 면세점 내부 컨셉 배경 위에 도시락, 방어막, 운동화, 마일리지 카드 상품 카드를 배치
- 상품 카드는 아이템 이미지, 효과, 보유 수량, 가격, 구매 상태를 함께 표시
- 구매 아이템은 실제 장착 슬롯과 연결 예정
## MapPreview
+31 -3
View File
@@ -19,18 +19,46 @@
- 오른쪽 HUD에는 거리와 이번 판 적립 마일리지만 표시
- 오른쪽 HUD는 맵에 따라 한국/일본/중국/미국 라벨을 자동으로 바꾸고, 원본 기준 약 60% 크기로 표시
- 오른쪽 HUD 글자는 부모 스케일 축소 대신 실제 크기로 배치해 회색 번짐을 줄임
- 튜토리얼 설명창 글자 위치를 오른쪽으로 조정
- 슬라이드 안내 이미지를 더 낮게 숙인 프레임으로 교체
- 라멘 무적 체험 뒤 방어막이 바로 나오지 않도록 안전 발판 간격 추가
- 튜토리얼 아이템 순서를 `초밥 -> 라멘 -> 방어막 -> 마일리지 카드 -> 운동화`로 조정
- 운동화 뒤에 속도 변화 설명과 출국 게이트까지 실전 연습 안내를 추가
- `Platform.prefab`에서 내장 장애물을 제거하고 발판 전용 프리팹으로 정리
- 출국 버튼 도장은 종이 배경이 같이 찍히지 않도록 투명 배경의 빨간 잉크 리소스로 교체
- 2단 점프 설명 이미지의 `X2` 표시를 더 크게 조정하고 외곽선을 추가
- 강제 피격 구간에서 점프 장애물과 슬라이드 장애물이 같은 위치에 겹치지 않도록 슬라이드 장애물 생성을 제거
- 결과 화면을 `출국 성공!` 여권 정산표 형태로 변경하고, 마일리지/시간/거리/보상만 간추려 표시
- 결과 화면 배경을 인천공항으로 바꾸고, 아이콘/글자/도장 위치를 여권 슬롯에 맞춰 재배치
- 결과 화면의 `인트로` 버튼을 `다음 여행지 가기`로 바꾸고 기존 공항 안내 버튼 에셋을 적용
- 결과 화면에서 `No cameras rendering` 경고가 뜨지 않도록 플로우 카메라를 보장하고, 버튼 글자를 밝은 색/외곽선으로 보정
- 왼쪽 HUD는 오른쪽 빈 장식 영역을 줄인 컴팩트 여권 배경으로 교체하고, HP/아이템 슬롯 비율과 간격을 재조정
- 면세점 화면을 내부 배경과 상품 카드형 UI로 교체하고, 도시락/방어막/운동화/마일리지 카드 구매 상태를 표시
- `Esc` 디버그 메뉴로 인트로, 게임 시작, 출국 실패, 출국 성공, 면세점, 지도 화면을 바로 확인할 수 있게 수정
- 스크립트 책임 분리 방향을 `Docs/refactor-plan.md`에 정리
- 맵별 정의와 코스 패턴을 `CourseStep` / `CourseProfile` 중심으로 나누는 리팩터링 방향을 문서화
- `PlatformSpawner.TutorialStep`을 독립 `CourseStep`, `CourseStepKind`, `CoursePlatformType` 파일로 분리
- `MapDefinition`, `MapDatabase`, `GameManager`가 새 `CourseStep` 타입을 참조하도록 정리
- `MapDatabase`의 맵별 생성 코드를 `Maps/Definitions/*Map.cs` 파일로 분리
- 공통 코스 조각 생성과 복제 로직을 `CourseStepFactory`로 이동
### 현재 문제
- `GameFlowManager`에 화면 흐름과 UI 생성 코드가 함께 몰려 있음
- `GameManager`에 플레이 진행, HUD, 튜토리얼, 도착 처리 코드가 함께 몰려 있음
- 코스 배열은 아직 `CourseProfile`로 묶이지 않고 `openingSteps`, `loopSteps`로 직접 연결됨
- `PlatformSpawner`에 fallback 코스 생성 코드가 아직 남아 있음
- 면세점 구매 아이템이 아직 장착 슬롯과 연결되지 않음
- 일본, 중국, 미국 맵의 실제 패턴과 난이도 차별화가 더 필요함
- 새 HUD 이미지들은 실제 플레이 화면에서 위치와 글자 크기를 한 번 더 미세 조정할 수 있음
### 다음 할 일
1. 면세점 구매 아이템을 장착 슬롯과 연결
2. 일본, 중국, 미국 맵의 실제 패턴과 난이도 차별화
3. 새 HUD와 튜토리얼 안내창의 실제 플레이 화면 가독성 최종 확인
1. 튜토리얼 코스와 일본/중국/미국 일반 코스를 `CourseProfile`로 분리
2. `PlatformSpawner`의 fallback 코스 생성 책임을 `Course` 쪽으로 이동
3. `GameFlowManager`의 결과 화면, 면세점, 디버그 메뉴 UI를 View 클래스로 분리
4. `GameManager`의 HUD와 튜토리얼 안내창을 별도 스크립트로 분리
5. 면세점 구매 아이템을 장착 슬롯과 연결
## 2026-07-06
+35 -4
View File
@@ -40,8 +40,9 @@
1. 점프: 낮은 장애물 1개
2. 2단 점프: 멀리 떨어진 발판
3. 슬라이드: 천장 장애물
4. 강제 피격: 일부러 맞아 목숨/속도 감소 체험
5. 아이템: 각 아이템을 먹고 효과 체험
4. 강제 피격: 낮은 장애물에 일부러 맞아 목숨/속도 감소 체험
5. 아이템: 초밥, 라멘, 방어막, 마일리지 카드, 운동화 순서로 효과 체험
6. 실전 연습: 속도 변화 설명 후 출국 게이트까지 주행
## 아이템
@@ -50,11 +51,11 @@
| 초밥 | 목숨 1 회복 | 강제 피격 후 회복 |
| 라멘 | 잠깐 무적 | 먹은 뒤 장애물 통과 |
| 방어막 | 충돌 1회 방어 | 먹은 뒤 다음 충돌 방어 |
| 운동화 | 속도 회복 | 피격 후 느려진 속도 회복 |
| 마일리지 카드 | 마일리지 획득량 증가 | 먹은 뒤 마일리지 수집 |
| 운동화 | 속도 회복 | 피격 후 느려진 속도 회복 |
아이템을 먹으면 짧은 링, 반짝임, 문구 이펙트가 떠서 획득 여부를 확인할 수 있습니다.
라멘 무적 아이템의 현재 지속 시간은 `2.2초`니다.
라멘 무적 아이템의 현재 지속 시간은 `2.2초`이며, 튜토리얼에서는 방어막이 바로 이어지지 않도록 안전 발판을 사이에 둡니다.
점프 구간의 마일리지 코인은 플레이어가 뛰는 높이에 맞춰 넓고 높은 포물선으로 배치합니다.
게임 속도가 빨라지면 코인 포물선의 가로 폭도 함께 넓혀 점프 타이밍과 맞춥니다.
@@ -63,6 +64,8 @@
## 코스 생성
현재 방향은 개별 랜덤 생성이 아니라 검증된 코스 조각을 순서대로 배치하는 방식입니다.
코스 조각은 `CourseStep`으로 분리했습니다.
다음 리팩터링에서는 여러 조각의 묶음을 `CourseProfile`로 분리합니다.
패턴 예시:
@@ -75,3 +78,31 @@
- Finish
이 방식은 쿠키런식으로 계속 이어지는 기본 발판을 유지하면서도, 장애물과 아이템은 상황에 맞게 배치할 수 있습니다.
## CourseProfile 방향
튜토리얼과 일반 맵의 코스 목적을 분리합니다.
| 프로필 | 목적 |
| --- | --- |
| `TutorialCourseProfile` | 조작 학습, 강제 피격, 아이템 체험, 실전 연습 |
| `JapanCourseProfile` | 쉬운 일반 반복 패턴 |
| `ChinaCourseProfile` | 간격 변화와 점프/슬라이드 전환 |
| `AmericaCourseProfile` | 빠른 속도와 후반 난이도 |
현재 `PlatformSpawner``CourseStep` 배열을 순서대로 실행합니다.
다음 목표는 코스 내용을 `CourseProfile`로 묶어, 스포너가 맵별 코스 정의를 직접 만들지 않게 하는 것입니다.
튜토리얼 전용 요소:
- 조작 안내 문구
- 강제 피격 체험
- 아이템 설명 트리거
- 출국 게이트 전 실전 연습 안내
일반 맵 전용 요소:
- 반복 가능한 점프/슬라이드 조합
- 맵별 장애물 스킨
- 맵별 속도와 간격 차이
- 보상 마일리지와 도착 지점 연출
+64 -4
View File
@@ -20,6 +20,42 @@
- `openingSteps`
- `loopSteps`
리팩터링 목표:
- `openingSteps`, `loopSteps``CourseProfile` 기반 이름으로 정리합니다.
- `PlatformSpawner.TutorialStep``CourseStep`으로 독립시켰습니다.
- `MapDatabase`는 맵 목록 관리만 담당하고, 실제 맵 정의는 맵별 파일로 나눴습니다.
현재 구조:
```text
MapDatabase
-> Definitions/TutorialIncheonMap.cs
-> Definitions/JapanMap.cs
-> Definitions/ChinaMap.cs
-> Definitions/AmericaMap.cs
Course
-> TutorialCourseProfile.cs
-> JapanCourseProfile.cs
-> ChinaCourseProfile.cs
-> AmericaCourseProfile.cs
```
## 코스 분리 기준
튜토리얼과 일반 맵은 같은 구조를 복사하지 않습니다.
| 코스 | 목적 |
| --- | --- |
| Tutorial / Incheon | 조작 학습, 강제 피격, 아이템 효과 체험, 출국 게이트 실전 연습 |
| Japan | 첫 일반 맵, 쉬운 점프/슬라이드 반복 패턴 |
| China | 간격 변화, 연속 슬라이드/점프 확장 |
| America | 빠른 속도, 후반 난이도, 긴 주행 압박 |
현재 `PlatformSpawner`는 맵이 가진 `CourseStep` 배열을 실행합니다.
다음 목표는 맵별 코스를 `CourseProfile`로 묶어, 스포너가 현재 맵의 프로필만 실행하게 만드는 것입니다.
## Tutorial / Incheon
역할:
@@ -45,8 +81,9 @@
5. 초밥
6. 라멘
7. 방어막
8. 운동화
9. 마일리지 카드
8. 마일리지 카드
9. 운동화
10. 실전 연습 후 출국 게이트
## Japan
@@ -54,6 +91,7 @@
- 첫 일반 여행 맵
- 튜토리얼에서 배운 조작을 반복 패턴으로 사용
- 튜토리얼 설명, 강제 피격 체험, 아이템 설명 트리거는 사용하지 않음
도착 지점:
@@ -66,12 +104,20 @@
- 석등 낮은 장애물
- 배너/표지판 슬라이드 장애물
코스 방향:
- 안전 발판으로 시작
- 낮은 점프 장애물과 짧은 슬라이드를 번갈아 배치
- 마일리지 코인은 점프 아크와 안전 라인 중심
- 튜토리얼보다 긴 반복 패턴을 사용하되 난이도는 낮게 유지
## China
역할:
- 붉은색/금색 지역감
- 슬라이드와 점프 패턴 확장
- 일본보다 장애물 간격 변화가 큼
도착 지점:
@@ -84,12 +130,19 @@
- 시장 상자 낮은 장애물
- 홍등 슬라이드 장애물
코스 방향:
- 점프 뒤 슬라이드, 슬라이드 뒤 점프처럼 전환 패턴을 늘림
- 마일리지 라인은 안전 구간과 위험 보상 구간을 구분
- 튜토리얼용 강제 피격 패턴은 사용하지 않음
## America
역할:
- 뉴욕 도착 맵
- 자유의 여신상에 도착하면 클리어되는 느낌
- 후반 맵답게 속도와 반응 시간을 더 강하게 사용
배경:
@@ -107,13 +160,20 @@
- 콘/도로 장애물
- 배너/표지판 슬라이드 장애물
코스 방향:
- 넓은 점프와 짧은 슬라이드가 빠르게 이어짐
- 안전 발판 비중은 줄이고 패턴 반복 속도를 올림
- 도착 지점 전 마지막 구간은 시야가 복잡하지 않게 정리
## 맵 추가 규칙
새 맵을 추가할 때:
1. `MapId` 추가
2. `MapDatabase``CreateMap()` 추가
2. `Maps/Definitions`에 맵별 정의 파일 추가
3. 배경 이미지를 `Assets/Resources/Map_*.png`로 저장
4. 도착 지점 이미지를 `Assets/Resources/FinishGate_*.png`로 저장
5. `backgroundResourcePath`, `finishGateStyle` 연결
6. `openingSteps`, `loopSteps` 구성
6. `Course` 폴더에 해당 맵의 `CourseProfile` 추가
7. `MapDatabase` 목록에 새 맵 연결
+219
View File
@@ -0,0 +1,219 @@
# Refactor Plan
스크립트가 커진 이유와 앞으로 나눌 구조를 정리합니다.
이 문서는 실제 리팩터링 기준과 진행 상태를 함께 정리합니다.
## 목표
현재 목표는 기능을 바꾸는 것이 아니라 책임을 분리하는 것입니다.
- `GameFlowManager`는 화면 흐름만 관리
- `GameManager`는 플레이 세션의 큰 흐름만 관리
- 화면 UI는 화면별 View 클래스로 분리
- 맵 데이터는 맵별 파일로 분리
- 코스 패턴은 튜토리얼 전용과 일반 맵 전용으로 분리
- `PlatformSpawner`는 코스를 직접 정의하지 않고 받은 코스만 실행
## 현재 문제
### GameFlowManager
현재 `GameFlowManager`가 담당하는 일이 너무 많습니다.
- 씬 흐름
- 저장 데이터 처리
- 결과 화면 UI 생성
- 면세점 UI 생성
- 인트로 UI 생성
- 맵 프리뷰 UI 생성
- 디버그 메뉴
- 공통 UI 오브젝트 생성
- 이미지/스프라이트 로딩
최종적으로는 흐름 제어만 남기고 화면 생성 코드는 `Flow/Screens`로 옮깁니다.
### GameManager
현재 `GameManager`도 플레이와 UI가 섞여 있습니다.
- 게임 시작/종료
- 거리/시간/점수 계산
- 목숨/피격/무적
- 아이템 효과
- 튜토리얼 설명창
- 왼쪽/오른쪽 HUD
- 도착 게이트 생성
- 코스 미니맵 내부 클래스
최종적으로는 플레이 세션 관리만 남기고 HUD, 튜토리얼, 도착 게이트, 아이템 효과를 분리합니다.
### Map / Course
`PlatformSpawner.TutorialStep`은 독립 `CourseStep` 타입으로 분리했습니다.
맵별 생성 코드는 `Maps/Definitions/*Map.cs`로 분리했습니다.
아직 코스 배열은 `CourseProfile`로 묶이지 않고 `openingSteps`, `loopSteps`로 직접 연결되어 있습니다.
최종적으로는 `CourseProfile`을 독립 타입으로 만들고, 맵은 코스 프로필만 참조하게 합니다.
## 목표 폴더 구조
```text
Assets/Scripts
├─ Flow
│ ├─ GameFlowManager.cs
│ ├─ FlowScreen.cs
│ ├─ FlowScreenContext.cs
│ ├─ Screens
│ │ ├─ IntroScreenView.cs
│ │ ├─ OpeningStoryScreenView.cs
│ │ ├─ ResultScreenView.cs
│ │ ├─ DutyFreeShopView.cs
│ │ ├─ MapPreviewScreenView.cs
│ │ └─ DebugMenuView.cs
│ └─ UI
│ ├─ FlowUiFactory.cs
│ └─ FlowAssetLoader.cs
├─ Gameplay
│ ├─ GameManager.cs
│ ├─ GameplaySession.cs
│ ├─ StageProgressTracker.cs
│ ├─ FinishGateController.cs
│ ├─ ItemEffectController.cs
│ └─ DamageController.cs
├─ Gameplay/UI
│ ├─ GameplayHudView.cs
│ ├─ TutorialGuideController.cs
│ ├─ TutorialGuideView.cs
│ └─ CourseMinimapView.cs
├─ Maps
│ ├─ MapId.cs
│ ├─ MapDefinition.cs
│ ├─ MapDatabase.cs
│ └─ Definitions
│ ├─ TutorialIncheonMap.cs
│ ├─ JapanMap.cs
│ ├─ ChinaMap.cs
│ └─ AmericaMap.cs
└─ Course
├─ CourseStep.cs
├─ CourseStepKind.cs
├─ CoursePlatformType.cs
├─ CourseStepFactory.cs
├─ CourseProfile.cs
├─ TutorialCourseProfile.cs
├─ JapanCourseProfile.cs
├─ ChinaCourseProfile.cs
└─ AmericaCourseProfile.cs
```
## 책임 분리 기준
### Flow
`GameFlowManager`는 다음만 담당합니다.
- 현재 화면 상태
- 씬 이동
- 저장 데이터 로드/저장
- 게임 시작 요청
- 결과 화면으로 이동
- 다음 맵 선택
화면 구성은 각 View가 담당합니다.
| 화면 | 분리 대상 |
| --- | --- |
| Intro | `IntroScreenView` |
| Opening Story | `OpeningStoryScreenView` |
| Result | `ResultScreenView` |
| DutyFreeShop | `DutyFreeShopView` |
| MapPreview | `MapPreviewScreenView` |
| Debug Menu | `DebugMenuView` |
공통 UI 생성은 `FlowUiFactory`, 에셋 로딩은 `FlowAssetLoader`로 옮깁니다.
### Gameplay
`GameManager`는 플레이 세션의 큰 흐름만 담당합니다.
| 책임 | 분리 대상 |
| --- | --- |
| 거리/시간/클리어 판정 | `StageProgressTracker` |
| 도착 게이트 생성/정리 | `FinishGateController` |
| 목숨/피격/무적/방어막 | `DamageController` |
| 아이템 효과 | `ItemEffectController` |
| 왼쪽/오른쪽 HUD | `GameplayHudView` |
| 튜토리얼 안내창 표시 | `TutorialGuideView` |
| 튜토리얼 단계 제어 | `TutorialGuideController` |
| 코스 미니맵 | `CourseMinimapView` |
### Maps / Course
맵 데이터와 코스 실행을 분리합니다.
현재:
```text
MapDefinition
-> CourseStep[]
-> Maps/Definitions/*Map.cs가 맵별 코스 배열을 직접 생성
-> PlatformSpawner가 fallback 패턴도 직접 생성
```
목표:
```text
MapDefinition
-> CourseProfile
-> CourseStep[]
-> PlatformSpawner는 CourseStep을 실행만 함
```
튜토리얼과 일반 맵은 역할이 다릅니다.
| 코스 | 역할 |
| --- | --- |
| `TutorialCourseProfile` | 조작 학습, 강제 피격, 아이템 체험, 실전 연습 |
| `JapanCourseProfile` | 첫 일반 맵, 쉬운 반복 패턴 |
| `ChinaCourseProfile` | 간격 변화와 슬라이드/점프 확장 |
| `AmericaCourseProfile` | 빠른 속도와 후반 난이도 |
## 진행 순서
1. 완료: `CourseStep` 분리
- `PlatformSpawner.TutorialStep`을 독립 파일로 이동
- `TutorialStep` 이름을 `CourseStep`으로 변경
- `CourseStepKind`, `CoursePlatformType`도 독립 파일로 이동
- 남은 이름 정리는 `CourseProfile` 분리 때 함께 진행
2. 완료: 맵 정의 파일 분리
- `MapDatabase.cs`는 목록 관리만 담당
- `TutorialIncheonMap`, `JapanMap`, `ChinaMap`, `AmericaMap`으로 나눔
3. 다음: 코스 프로필 분리
- 튜토리얼 코스와 일반 맵 코스를 각각 `CourseProfile`로 분리
- 일본, 중국, 미국은 튜토리얼 패턴을 복붙하지 않음
4. `GameFlowManager` 화면 View 분리
- 결과 화면, 면세점, 디버그 메뉴부터 분리
- 위치 조정 중인 UI 값은 유지한 채 파일만 나눔
5. `GameManager` UI/진행 분리
- HUD와 튜토리얼 안내창을 먼저 분리
- 이후 도착 게이트, 아이템, 피격 처리 분리
6. 검증
- 각 단계마다 `dotnet build Assembly-CSharp.csproj`
- Unity Play 모드에서 `Esc` 디버그 메뉴로 화면 확인
- 튜토리얼, 일본, 중국, 미국 맵 진입 확인
## 주의 사항
- 기능 변경과 파일 분리를 동시에 많이 하지 않습니다.
- UI 위치 조정이 끝난 화면은 좌표를 유지한 채 View로 옮깁니다.
- 맵별 난이도 조정은 코스 구조 분리 후 진행합니다.
- 삭제보다 이동을 우선하고, 빌드가 깨지지 않는 작은 단위로 진행합니다.
+68 -4
View File
@@ -5,6 +5,7 @@
## Scene Flow
`GameFlowManager`가 전체 흐름을 관리합니다.
다음 리팩터링 이후에는 `GameFlowManager`가 화면을 직접 그리지 않고, 화면별 View를 호출하는 구조로 정리합니다.
```text
Intro
@@ -37,6 +38,8 @@ Intro
- `Assets/Scripts/Maps/MapId.cs`
- `Assets/Scripts/Maps/MapDefinition.cs`
- `Assets/Scripts/Maps/MapDatabase.cs`
- `Assets/Scripts/Maps/Definitions/*.cs`
- `Assets/Scripts/Course/*.cs`
역할:
@@ -50,6 +53,27 @@ Intro
- 다음 맵
- 코스 패턴
현재는 `MapDefinition`이 독립 타입인 `CourseStep[]` 형태의 `openingSteps`, `loopSteps`를 들고 있습니다.
맵 생성 코드는 `Maps/Definitions/*Map.cs`로 분리했습니다.
다음 리팩터링에서는 `CourseProfile`을 만들고 맵별 파일에서 코스 프로필을 연결합니다.
목표 구조:
```text
MapDatabase
-> TutorialIncheonMap.Create()
-> JapanMap.Create()
-> ChinaMap.Create()
-> AmericaMap.Create()
MapDefinition
-> CourseProfile openingCourse
-> CourseProfile loopCourse
PlatformSpawner
-> CourseProfile 실행
```
## Gameplay Runtime
관련 파일:
@@ -73,20 +97,60 @@ Intro
- 아이템 획득/방어 성공 이펙트
- 도착 지점 통과 시 클리어
현재 `GameManager`는 플레이 진행과 UI를 함께 담당합니다.
리팩터링 후에는 아래처럼 책임을 나눕니다.
| 책임 | 목표 스크립트 |
| --- | --- |
| 플레이 세션 총괄 | `GameManager` |
| 거리/시간/클리어 판정 | `StageProgressTracker` |
| 도착 게이트 생성/정리 | `FinishGateController` |
| 목숨/피격/무적/방어막 | `DamageController` |
| 아이템 효과 | `ItemEffectController` |
| 왼쪽/오른쪽 HUD | `GameplayHudView` |
| 튜토리얼 안내창 | `TutorialGuideView` |
| 튜토리얼 단계 제어 | `TutorialGuideController` |
## Flow UI
현재 상태:
- `GameFlowManager` 안에 결과 화면, 면세점, 인트로, 맵 프리뷰, 디버그 메뉴 UI 생성 코드가 들어 있음
- 공통 UI 오브젝트 생성과 에셋 로딩도 같은 파일에 있음
목표 구조:
| 책임 | 목표 스크립트 |
| --- | --- |
| 흐름 제어 | `GameFlowManager` |
| 현재 화면 상태와 공통 데이터 | `FlowScreenContext` |
| 인트로 화면 | `IntroScreenView` |
| 오프닝 스토리 | `OpeningStoryScreenView` |
| 결과 화면 | `ResultScreenView` |
| 면세점 화면 | `DutyFreeShopView` |
| 맵 프리뷰 | `MapPreviewScreenView` |
| 디버그 메뉴 | `DebugMenuView` |
| 공통 UI 생성 | `FlowUiFactory` |
| UI 에셋 로딩 | `FlowAssetLoader` |
디버그 메뉴는 현재 `Esc`로 열며, 인트로/게임 시작/출국 실패/출국 성공/면세점/지도 화면으로 바로 이동합니다.
리팩터링 후에도 개발 빌드 전용 기능으로 유지합니다.
## DutyFreeShop
현재 상태:
- 화면 흐름은 있음
- 코드 오버레이 화면은 면세점 내부 배경과 상품 카드형 UI로 표시함
- 도시락, 방어막, 운동화, 마일리지 카드의 보유 수량과 가격을 표시함
- 구매 데이터 저장은 있음
- 실제 장착 슬롯과 플레이 시작 전 준비 구조는 아직 미완성
다음 방향:
1. 면세점 구매 아이템을 보유 수량으로 표시
2. 스테이지 시작 전 장착 슬롯에 넣기
3. 가방 업그레이드로 슬롯 수 증가
4. 스테이지 중 장착 아이템 사용/소모
1. 스테이지 시작 전 장착 슬롯에 넣기
2. 가방 업그레이드로 슬롯 수 증가
3. 스테이지 중 장착 아이템 사용/소모
## Finish Gate