draggableList/Tab 개발 중
@@ -73,9 +73,9 @@ Material:
|
|||||||
- _OutlineWidth: 0
|
- _OutlineWidth: 0
|
||||||
- _PerspectiveFilter: 0.875
|
- _PerspectiveFilter: 0.875
|
||||||
- _Reflectivity: 10
|
- _Reflectivity: 10
|
||||||
- _ScaleRatioA: 1
|
- _ScaleRatioA: 0.8333333
|
||||||
- _ScaleRatioB: 1
|
- _ScaleRatioB: 0.6770833
|
||||||
- _ScaleRatioC: 1
|
- _ScaleRatioC: 0.6770833
|
||||||
- _ScaleX: 1
|
- _ScaleX: 1
|
||||||
- _ScaleY: 1
|
- _ScaleY: 1
|
||||||
- _ShaderFlags: 0
|
- _ShaderFlags: 0
|
||||||
@@ -120,7 +120,7 @@ Texture2D:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Hash: 00000000000000000000000000000000
|
Hash: 00000000000000000000000000000000
|
||||||
m_IsAlphaChannelOptional: 0
|
m_IsAlphaChannelOptional: 0
|
||||||
serializedVersion: 4
|
serializedVersion: 3
|
||||||
m_Width: 1
|
m_Width: 1
|
||||||
m_Height: 1
|
m_Height: 1
|
||||||
m_CompleteImageSize: 1
|
m_CompleteImageSize: 1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 32fc15a19aea4e944a5ebe70369dc9f1
|
guid: 5baaa5a2fc5d561429616c5a45329202
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
8
Assets/Resources/Prefabs/UI/List.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 789fbaba80497234b826caaf22001c7a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
1039
Assets/Resources/Prefabs/UI/List/DraggableListItem.prefab
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f20039b3cd21554096c9ac3adfbd7a5
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
99
Assets/Resources/Prefabs/UI/List/DraggableScrollList.prefab
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &8136911777273008353
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 5208007342563872741}
|
||||||
|
- component: {fileID: 2965738868788178389}
|
||||||
|
- component: {fileID: 5076158976701961981}
|
||||||
|
- component: {fileID: 4550704492290639124}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: DraggableScrollList
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &5208007342563872741
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8136911777273008353}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 1}
|
||||||
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
|
m_Pivot: {x: 0, y: 1}
|
||||||
|
--- !u!114 &2965738868788178389
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8136911777273008353}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 71e6121c6103b0a4c9aeadc24c891b86, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
scrollRect: {fileID: 0}
|
||||||
|
contentParent: {fileID: 5208007342563872741}
|
||||||
|
layoutGroup: {fileID: 5076158976701961981}
|
||||||
|
itemPrefabPath: Prefabs/UI/List/DraggableListItem
|
||||||
|
dropZoneThreshold: 50
|
||||||
|
scrollSensitivity: 100
|
||||||
|
enableAutoScroll: 1
|
||||||
|
--- !u!114 &5076158976701961981
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8136911777273008353}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Padding:
|
||||||
|
m_Left: 8
|
||||||
|
m_Right: 8
|
||||||
|
m_Top: 8
|
||||||
|
m_Bottom: 8
|
||||||
|
m_ChildAlignment: 0
|
||||||
|
m_Spacing: 4
|
||||||
|
m_ChildForceExpandWidth: 1
|
||||||
|
m_ChildForceExpandHeight: 0
|
||||||
|
m_ChildControlWidth: 1
|
||||||
|
m_ChildControlHeight: 0
|
||||||
|
m_ChildScaleWidth: 0
|
||||||
|
m_ChildScaleHeight: 0
|
||||||
|
m_ReverseArrangement: 0
|
||||||
|
--- !u!114 &4550704492290639124
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8136911777273008353}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_HorizontalFit: 0
|
||||||
|
m_VerticalFit: 2
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 11968bbba596a584e92e29ce26e3897d
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -54,6 +54,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
canvasGroup: {fileID: 4097232251975178814}
|
canvasGroup: {fileID: 4097232251975178814}
|
||||||
loadinImage: {fileID: 5537735754607583444}
|
loadinImage: {fileID: 5537735754607583444}
|
||||||
|
loadingImageTransform: {fileID: 2492902462340881910}
|
||||||
--- !u!223 &5836275117983516284
|
--- !u!223 &5836275117983516284
|
||||||
Canvas:
|
Canvas:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -114,7 +115,7 @@ RectTransform:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 3720191927695001841}
|
m_GameObject: {fileID: 3720191927695001841}
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ GameObject:
|
|||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 3316965954832882549}
|
- component: {fileID: 3316965954832882549}
|
||||||
- component: {fileID: 923263126373587623}
|
- component: {fileID: 923263126373587623}
|
||||||
- component: {fileID: 3314964221659757925}
|
|
||||||
- component: {fileID: 4097232251975178814}
|
- component: {fileID: 4097232251975178814}
|
||||||
- component: {fileID: 5959041612264278031}
|
- component: {fileID: 5959041612264278031}
|
||||||
m_Layer: 5
|
m_Layer: 5
|
||||||
@@ -130,14 +129,9 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 4fb7ebdfa824a214caad9f55f3398cc0, type: 3}
|
m_Script: {fileID: 11500000, guid: 4fb7ebdfa824a214caad9f55f3398cc0, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
--- !u!222 &3314964221659757925
|
canvasGroup: {fileID: 4097232251975178814}
|
||||||
CanvasRenderer:
|
loadinImage: {fileID: 5537735754607583444}
|
||||||
m_ObjectHideFlags: 0
|
text: {fileID: 5965769881319077586}
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 3247177050376678973}
|
|
||||||
m_CullTransparentMesh: 0
|
|
||||||
--- !u!225 &4097232251975178814
|
--- !u!225 &4097232251975178814
|
||||||
CanvasGroup:
|
CanvasGroup:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -171,7 +165,7 @@ Canvas:
|
|||||||
m_AdditionalShaderChannelsFlag: 0
|
m_AdditionalShaderChannelsFlag: 0
|
||||||
m_UpdateRectTransformForStandalone: 0
|
m_UpdateRectTransformForStandalone: 0
|
||||||
m_SortingLayerID: 0
|
m_SortingLayerID: 0
|
||||||
m_SortingOrder: 0
|
m_SortingOrder: 101
|
||||||
m_TargetDisplay: 0
|
m_TargetDisplay: 0
|
||||||
--- !u!1 &3720191927695001841
|
--- !u!1 &3720191927695001841
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -411,8 +405,8 @@ MonoBehaviour:
|
|||||||
m_Calls: []
|
m_Calls: []
|
||||||
m_text:
|
m_text:
|
||||||
m_isRightToLeft: 0
|
m_isRightToLeft: 0
|
||||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
m_fontAsset: {fileID: 11400000, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
||||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
m_sharedMaterial: {fileID: 4860575619018115804, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
||||||
m_fontSharedMaterials: []
|
m_fontSharedMaterials: []
|
||||||
m_fontMaterial: {fileID: 0}
|
m_fontMaterial: {fileID: 0}
|
||||||
m_fontMaterials: []
|
m_fontMaterials: []
|
||||||
|
|||||||
@@ -1,80 +1,5 @@
|
|||||||
%YAML 1.1
|
%YAML 1.1
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
--- !u!1 &142473307186880493
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 2212794593377927555}
|
|
||||||
- component: {fileID: 2543815536604706675}
|
|
||||||
- component: {fileID: 907042583023779876}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: Fill
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &2212794593377927555
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 142473307186880493}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children: []
|
|
||||||
m_Father: {fileID: 2052035080147039476}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
|
||||||
m_AnchorMax: {x: 0, y: 0}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 8, y: 0}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!222 &2543815536604706675
|
|
||||||
CanvasRenderer:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 142473307186880493}
|
|
||||||
m_CullTransparentMesh: 1
|
|
||||||
--- !u!114 &907042583023779876
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 142473307186880493}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Material: {fileID: 0}
|
|
||||||
m_Color: {r: 0.14117648, g: 0.54509807, b: 1, a: 1}
|
|
||||||
m_RaycastTarget: 1
|
|
||||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_Maskable: 1
|
|
||||||
m_OnCullStateChanged:
|
|
||||||
m_PersistentCalls:
|
|
||||||
m_Calls: []
|
|
||||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
|
||||||
m_Type: 1
|
|
||||||
m_PreserveAspect: 0
|
|
||||||
m_FillCenter: 1
|
|
||||||
m_FillMethod: 4
|
|
||||||
m_FillAmount: 1
|
|
||||||
m_FillClockwise: 1
|
|
||||||
m_FillOrigin: 0
|
|
||||||
m_UseSpriteMesh: 0
|
|
||||||
m_PixelsPerUnitMultiplier: 1
|
|
||||||
--- !u!1 &524629217329646727
|
--- !u!1 &524629217329646727
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -671,178 +596,6 @@ MonoBehaviour:
|
|||||||
m_FillOrigin: 0
|
m_FillOrigin: 0
|
||||||
m_UseSpriteMesh: 0
|
m_UseSpriteMesh: 0
|
||||||
m_PixelsPerUnitMultiplier: 1
|
m_PixelsPerUnitMultiplier: 1
|
||||||
--- !u!1 &2596344147536603055
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 4603262739189244112}
|
|
||||||
- component: {fileID: 2148154503416279850}
|
|
||||||
- component: {fileID: 2098005496725083057}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: PlayTimeTxt
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &4603262739189244112
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 2596344147536603055}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children: []
|
|
||||||
m_Father: {fileID: 1355571794369558167}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
|
||||||
m_AnchorMax: {x: 0, y: 0}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 80, y: 20}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!222 &2148154503416279850
|
|
||||||
CanvasRenderer:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 2596344147536603055}
|
|
||||||
m_CullTransparentMesh: 1
|
|
||||||
--- !u!114 &2098005496725083057
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 2596344147536603055}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Material: {fileID: 0}
|
|
||||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_RaycastTarget: 1
|
|
||||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_Maskable: 1
|
|
||||||
m_OnCullStateChanged:
|
|
||||||
m_PersistentCalls:
|
|
||||||
m_Calls: []
|
|
||||||
m_text: 00:00:00
|
|
||||||
m_isRightToLeft: 0
|
|
||||||
m_fontAsset: {fileID: 11400000, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
|
||||||
m_sharedMaterial: {fileID: 4860575619018115804, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
|
||||||
m_fontSharedMaterials: []
|
|
||||||
m_fontMaterial: {fileID: 0}
|
|
||||||
m_fontMaterials: []
|
|
||||||
m_fontColor32:
|
|
||||||
serializedVersion: 2
|
|
||||||
rgba: 4294967295
|
|
||||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_enableVertexGradient: 0
|
|
||||||
m_colorMode: 3
|
|
||||||
m_fontColorGradient:
|
|
||||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_fontColorGradientPreset: {fileID: 0}
|
|
||||||
m_spriteAsset: {fileID: 0}
|
|
||||||
m_tintAllSprites: 0
|
|
||||||
m_StyleSheet: {fileID: 0}
|
|
||||||
m_TextStyleHashCode: -1183493901
|
|
||||||
m_overrideHtmlColors: 0
|
|
||||||
m_faceColor:
|
|
||||||
serializedVersion: 2
|
|
||||||
rgba: 4294967295
|
|
||||||
m_fontSize: 12
|
|
||||||
m_fontSizeBase: 12
|
|
||||||
m_fontWeight: 400
|
|
||||||
m_enableAutoSizing: 0
|
|
||||||
m_fontSizeMin: 18
|
|
||||||
m_fontSizeMax: 72
|
|
||||||
m_fontStyle: 0
|
|
||||||
m_HorizontalAlignment: 1
|
|
||||||
m_VerticalAlignment: 512
|
|
||||||
m_textAlignment: 65535
|
|
||||||
m_characterSpacing: -1
|
|
||||||
m_wordSpacing: 0
|
|
||||||
m_lineSpacing: 0
|
|
||||||
m_lineSpacingMax: 0
|
|
||||||
m_paragraphSpacing: 0
|
|
||||||
m_charWidthMaxAdj: 0
|
|
||||||
m_TextWrappingMode: 1
|
|
||||||
m_wordWrappingRatios: 0.4
|
|
||||||
m_overflowMode: 0
|
|
||||||
m_linkedTextComponent: {fileID: 0}
|
|
||||||
parentLinkedComponent: {fileID: 0}
|
|
||||||
m_enableKerning: 1
|
|
||||||
m_ActiveFontFeatures: 6e72656b
|
|
||||||
m_enableExtraPadding: 0
|
|
||||||
checkPaddingRequired: 0
|
|
||||||
m_isRichText: 1
|
|
||||||
m_EmojiFallbackSupport: 1
|
|
||||||
m_parseCtrlCharacters: 1
|
|
||||||
m_isOrthographic: 1
|
|
||||||
m_isCullingEnabled: 0
|
|
||||||
m_horizontalMapping: 0
|
|
||||||
m_verticalMapping: 0
|
|
||||||
m_uvLineOffset: 0
|
|
||||||
m_geometrySortingOrder: 0
|
|
||||||
m_IsTextObjectScaleStatic: 0
|
|
||||||
m_VertexBufferAutoSizeReduction: 0
|
|
||||||
m_useMaxVisibleDescender: 1
|
|
||||||
m_pageToDisplay: 1
|
|
||||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_isUsingLegacyAnimationComponent: 0
|
|
||||||
m_isVolumetricText: 0
|
|
||||||
m_hasFontAssetChanged: 0
|
|
||||||
m_baseMaterial: {fileID: 0}
|
|
||||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
--- !u!1 &2691644320341202184
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 2934293647230093717}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: Handle Slide Area
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &2934293647230093717
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 2691644320341202184}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 1379976025267262066}
|
|
||||||
m_Father: {fileID: 475259887956418939}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 0, y: -24}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!1 &3243958776734417420
|
--- !u!1 &3243958776734417420
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1339,42 +1092,6 @@ MonoBehaviour:
|
|||||||
m_FillOrigin: 0
|
m_FillOrigin: 0
|
||||||
m_UseSpriteMesh: 0
|
m_UseSpriteMesh: 0
|
||||||
m_PixelsPerUnitMultiplier: 1
|
m_PixelsPerUnitMultiplier: 1
|
||||||
--- !u!1 &4064351744636590050
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 2052035080147039476}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: Fill Area
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &2052035080147039476
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 4064351744636590050}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 2212794593377927555}
|
|
||||||
m_Father: {fileID: 475259887956418939}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0.25}
|
|
||||||
m_AnchorMax: {x: 1, y: 0.75}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 0, y: -8}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!1 &4548019106164848943
|
--- !u!1 &4548019106164848943
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1972,97 +1689,6 @@ CanvasRenderer:
|
|||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 5842636379417192733}
|
m_GameObject: {fileID: 5842636379417192733}
|
||||||
m_CullTransparentMesh: 1
|
m_CullTransparentMesh: 1
|
||||||
--- !u!1 &5959356688591116280
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 1355571794369558167}
|
|
||||||
- component: {fileID: 1875730371384184921}
|
|
||||||
- component: {fileID: 3137698686019385162}
|
|
||||||
- component: {fileID: 3213640032001464174}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: UIPlaybackProgressBar
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &1355571794369558167
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 5959356688591116280}
|
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 4603262739189244112}
|
|
||||||
- {fileID: 475259887956418939}
|
|
||||||
- {fileID: 2037023258457412994}
|
|
||||||
m_Father: {fileID: 2985284779945075815}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
|
||||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
|
||||||
m_AnchoredPosition: {x: 5, y: 0}
|
|
||||||
m_SizeDelta: {x: 428, y: 20}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!114 &1875730371384184921
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 5959356688591116280}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: bce52072cdea6144b98e9297b89d6558, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
--- !u!114 &3137698686019385162
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 5959356688591116280}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Padding:
|
|
||||||
m_Left: 0
|
|
||||||
m_Right: 0
|
|
||||||
m_Top: 0
|
|
||||||
m_Bottom: 0
|
|
||||||
m_ChildAlignment: 3
|
|
||||||
m_Spacing: 0
|
|
||||||
m_ChildForceExpandWidth: 1
|
|
||||||
m_ChildForceExpandHeight: 1
|
|
||||||
m_ChildControlWidth: 0
|
|
||||||
m_ChildControlHeight: 0
|
|
||||||
m_ChildScaleWidth: 0
|
|
||||||
m_ChildScaleHeight: 0
|
|
||||||
m_ReverseArrangement: 0
|
|
||||||
--- !u!225 &3213640032001464174
|
|
||||||
CanvasGroup:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 5959356688591116280}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_Alpha: 1
|
|
||||||
m_Interactable: 0
|
|
||||||
m_BlocksRaycasts: 1
|
|
||||||
m_IgnoreParentGroups: 0
|
|
||||||
--- !u!1 &6095878340803605202
|
--- !u!1 &6095878340803605202
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -2424,232 +2050,6 @@ RectTransform:
|
|||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 0, y: -28}
|
m_SizeDelta: {x: 0, y: -28}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!1 &7180270421573006431
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 2037023258457412994}
|
|
||||||
- component: {fileID: 3418930163742956515}
|
|
||||||
- component: {fileID: 1879602012963642071}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: TotalTimeTxt
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &2037023258457412994
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 7180270421573006431}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children: []
|
|
||||||
m_Father: {fileID: 1355571794369558167}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
|
||||||
m_AnchorMax: {x: 0, y: 0}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 130, y: 20}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!222 &3418930163742956515
|
|
||||||
CanvasRenderer:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 7180270421573006431}
|
|
||||||
m_CullTransparentMesh: 1
|
|
||||||
--- !u!114 &1879602012963642071
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 7180270421573006431}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Material: {fileID: 0}
|
|
||||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_RaycastTarget: 1
|
|
||||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_Maskable: 1
|
|
||||||
m_OnCullStateChanged:
|
|
||||||
m_PersistentCalls:
|
|
||||||
m_Calls: []
|
|
||||||
m_text: 00:60:00
|
|
||||||
m_isRightToLeft: 0
|
|
||||||
m_fontAsset: {fileID: 11400000, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
|
||||||
m_sharedMaterial: {fileID: 4860575619018115804, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
|
||||||
m_fontSharedMaterials: []
|
|
||||||
m_fontMaterial: {fileID: 0}
|
|
||||||
m_fontMaterials: []
|
|
||||||
m_fontColor32:
|
|
||||||
serializedVersion: 2
|
|
||||||
rgba: 4294967295
|
|
||||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_enableVertexGradient: 0
|
|
||||||
m_colorMode: 3
|
|
||||||
m_fontColorGradient:
|
|
||||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_fontColorGradientPreset: {fileID: 0}
|
|
||||||
m_spriteAsset: {fileID: 0}
|
|
||||||
m_tintAllSprites: 0
|
|
||||||
m_StyleSheet: {fileID: 0}
|
|
||||||
m_TextStyleHashCode: -1183493901
|
|
||||||
m_overrideHtmlColors: 0
|
|
||||||
m_faceColor:
|
|
||||||
serializedVersion: 2
|
|
||||||
rgba: 4294967295
|
|
||||||
m_fontSize: 12
|
|
||||||
m_fontSizeBase: 12
|
|
||||||
m_fontWeight: 400
|
|
||||||
m_enableAutoSizing: 0
|
|
||||||
m_fontSizeMin: 18
|
|
||||||
m_fontSizeMax: 72
|
|
||||||
m_fontStyle: 0
|
|
||||||
m_HorizontalAlignment: 4
|
|
||||||
m_VerticalAlignment: 512
|
|
||||||
m_textAlignment: 65535
|
|
||||||
m_characterSpacing: -1
|
|
||||||
m_wordSpacing: 0
|
|
||||||
m_lineSpacing: 0
|
|
||||||
m_lineSpacingMax: 0
|
|
||||||
m_paragraphSpacing: 0
|
|
||||||
m_charWidthMaxAdj: 0
|
|
||||||
m_TextWrappingMode: 1
|
|
||||||
m_wordWrappingRatios: 0.4
|
|
||||||
m_overflowMode: 0
|
|
||||||
m_linkedTextComponent: {fileID: 0}
|
|
||||||
parentLinkedComponent: {fileID: 0}
|
|
||||||
m_enableKerning: 1
|
|
||||||
m_ActiveFontFeatures: 6e72656b
|
|
||||||
m_enableExtraPadding: 0
|
|
||||||
checkPaddingRequired: 0
|
|
||||||
m_isRichText: 1
|
|
||||||
m_EmojiFallbackSupport: 1
|
|
||||||
m_parseCtrlCharacters: 1
|
|
||||||
m_isOrthographic: 1
|
|
||||||
m_isCullingEnabled: 0
|
|
||||||
m_horizontalMapping: 0
|
|
||||||
m_verticalMapping: 0
|
|
||||||
m_uvLineOffset: 0
|
|
||||||
m_geometrySortingOrder: 0
|
|
||||||
m_IsTextObjectScaleStatic: 0
|
|
||||||
m_VertexBufferAutoSizeReduction: 0
|
|
||||||
m_useMaxVisibleDescender: 1
|
|
||||||
m_pageToDisplay: 1
|
|
||||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_isUsingLegacyAnimationComponent: 0
|
|
||||||
m_isVolumetricText: 0
|
|
||||||
m_hasFontAssetChanged: 0
|
|
||||||
m_baseMaterial: {fileID: 0}
|
|
||||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
--- !u!1 &7255621567353008705
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 475259887956418939}
|
|
||||||
- component: {fileID: 838636949093095911}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: ProgressBar
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &475259887956418939
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 7255621567353008705}
|
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 2404807485042726370}
|
|
||||||
- {fileID: 2052035080147039476}
|
|
||||||
- {fileID: 2934293647230093717}
|
|
||||||
m_Father: {fileID: 1355571794369558167}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
|
||||||
m_AnchorMax: {x: 0, y: 0}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 268, y: 40}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!114 &838636949093095911
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 7255621567353008705}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: c91122bcd4466654a8edf4e66c23448a, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Navigation:
|
|
||||||
m_Mode: 3
|
|
||||||
m_WrapAround: 0
|
|
||||||
m_SelectOnUp: {fileID: 0}
|
|
||||||
m_SelectOnDown: {fileID: 0}
|
|
||||||
m_SelectOnLeft: {fileID: 0}
|
|
||||||
m_SelectOnRight: {fileID: 0}
|
|
||||||
m_Transition: 1
|
|
||||||
m_Colors:
|
|
||||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
|
||||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
|
||||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
|
||||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
|
||||||
m_ColorMultiplier: 1
|
|
||||||
m_FadeDuration: 0.1
|
|
||||||
m_SpriteState:
|
|
||||||
m_HighlightedSprite: {fileID: 0}
|
|
||||||
m_PressedSprite: {fileID: 0}
|
|
||||||
m_SelectedSprite: {fileID: 0}
|
|
||||||
m_DisabledSprite: {fileID: 0}
|
|
||||||
m_AnimationTriggers:
|
|
||||||
m_NormalTrigger: Normal
|
|
||||||
m_HighlightedTrigger: Highlighted
|
|
||||||
m_PressedTrigger: Pressed
|
|
||||||
m_SelectedTrigger: Selected
|
|
||||||
m_DisabledTrigger: Disabled
|
|
||||||
m_Interactable: 1
|
|
||||||
m_TargetGraphic: {fileID: 6601414212130519427}
|
|
||||||
m_FillRect: {fileID: 2212794593377927555}
|
|
||||||
m_HandleRect: {fileID: 1379976025267262066}
|
|
||||||
m_Direction: 0
|
|
||||||
m_MinValue: 0
|
|
||||||
m_MaxValue: 3600
|
|
||||||
m_WholeNumbers: 0
|
|
||||||
m_Value: 0
|
|
||||||
m_OnValueChanged:
|
|
||||||
m_PersistentCalls:
|
|
||||||
m_Calls: []
|
|
||||||
--- !u!1 &8010062772174439758
|
--- !u!1 &8010062772174439758
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -2982,81 +2382,6 @@ MonoBehaviour:
|
|||||||
m_hasFontAssetChanged: 0
|
m_hasFontAssetChanged: 0
|
||||||
m_baseMaterial: {fileID: 0}
|
m_baseMaterial: {fileID: 0}
|
||||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||||
--- !u!1 &9069967403573002026
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 1379976025267262066}
|
|
||||||
- component: {fileID: 4229706552096532897}
|
|
||||||
- component: {fileID: 6601414212130519427}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: Handle
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &1379976025267262066
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 9069967403573002026}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children: []
|
|
||||||
m_Father: {fileID: 2934293647230093717}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
|
||||||
m_AnchorMax: {x: 0, y: 0}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 6, y: 0}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!222 &4229706552096532897
|
|
||||||
CanvasRenderer:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 9069967403573002026}
|
|
||||||
m_CullTransparentMesh: 1
|
|
||||||
--- !u!114 &6601414212130519427
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 9069967403573002026}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Material: {fileID: 0}
|
|
||||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
|
||||||
m_RaycastTarget: 1
|
|
||||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_Maskable: 1
|
|
||||||
m_OnCullStateChanged:
|
|
||||||
m_PersistentCalls:
|
|
||||||
m_Calls: []
|
|
||||||
m_Sprite: {fileID: 21300000, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3}
|
|
||||||
m_Type: 1
|
|
||||||
m_PreserveAspect: 0
|
|
||||||
m_FillCenter: 1
|
|
||||||
m_FillMethod: 4
|
|
||||||
m_FillAmount: 1
|
|
||||||
m_FillClockwise: 1
|
|
||||||
m_FillOrigin: 0
|
|
||||||
m_UseSpriteMesh: 0
|
|
||||||
m_PixelsPerUnitMultiplier: 1
|
|
||||||
--- !u!1 &9120583007845979077
|
--- !u!1 &9120583007845979077
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -3094,81 +2419,6 @@ RectTransform:
|
|||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 528, y: 32}
|
m_SizeDelta: {x: 528, y: 32}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!1 &9142233292563063716
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 2404807485042726370}
|
|
||||||
- component: {fileID: 1771436850855349595}
|
|
||||||
- component: {fileID: 8104840636219438454}
|
|
||||||
m_Layer: 5
|
|
||||||
m_Name: Background
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!224 &2404807485042726370
|
|
||||||
RectTransform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 9142233292563063716}
|
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children: []
|
|
||||||
m_Father: {fileID: 475259887956418939}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
m_AnchorMin: {x: 0, y: 0.25}
|
|
||||||
m_AnchorMax: {x: 1, y: 0.75}
|
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
|
||||||
m_SizeDelta: {x: 0, y: -10}
|
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
|
||||||
--- !u!222 &1771436850855349595
|
|
||||||
CanvasRenderer:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 9142233292563063716}
|
|
||||||
m_CullTransparentMesh: 1
|
|
||||||
--- !u!114 &8104840636219438454
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 9142233292563063716}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Material: {fileID: 0}
|
|
||||||
m_Color: {r: 1, g: 1, b: 1, a: 0.2}
|
|
||||||
m_RaycastTarget: 1
|
|
||||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_Maskable: 1
|
|
||||||
m_OnCullStateChanged:
|
|
||||||
m_PersistentCalls:
|
|
||||||
m_Calls: []
|
|
||||||
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
|
|
||||||
m_Type: 1
|
|
||||||
m_PreserveAspect: 0
|
|
||||||
m_FillCenter: 1
|
|
||||||
m_FillMethod: 4
|
|
||||||
m_FillAmount: 1
|
|
||||||
m_FillClockwise: 1
|
|
||||||
m_FillOrigin: 0
|
|
||||||
m_UseSpriteMesh: 0
|
|
||||||
m_PixelsPerUnitMultiplier: 1
|
|
||||||
--- !u!1001 &1741713488949895712
|
--- !u!1001 &1741713488949895712
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -3185,6 +2435,10 @@ PrefabInstance:
|
|||||||
propertyPath: dragObject
|
propertyPath: dragObject
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 1772582241787108090}
|
objectReference: {fileID: 1772582241787108090}
|
||||||
|
- target: {fileID: 4336813538161482815, guid: 530e5bfd9a80cce4a99696a1442657e7, type: 3}
|
||||||
|
propertyPath: yMinHeight
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 7039059871646261441, guid: 530e5bfd9a80cce4a99696a1442657e7, type: 3}
|
- target: {fileID: 7039059871646261441, guid: 530e5bfd9a80cce4a99696a1442657e7, type: 3}
|
||||||
propertyPath: m_Pivot.x
|
propertyPath: m_Pivot.x
|
||||||
value: 0
|
value: 0
|
||||||
@@ -3286,3 +2540,124 @@ RectTransform:
|
|||||||
m_CorrespondingSourceObject: {fileID: 7039059871646261441, guid: 530e5bfd9a80cce4a99696a1442657e7, type: 3}
|
m_CorrespondingSourceObject: {fileID: 7039059871646261441, guid: 530e5bfd9a80cce4a99696a1442657e7, type: 3}
|
||||||
m_PrefabInstance: {fileID: 1741713488949895712}
|
m_PrefabInstance: {fileID: 1741713488949895712}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
--- !u!1001 &2818395949540507463
|
||||||
|
PrefabInstance:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Modification:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransformParent: {fileID: 2985284779945075815}
|
||||||
|
m_Modifications:
|
||||||
|
- target: {fileID: 3763422474971032885, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchorMax.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_Pivot.x
|
||||||
|
value: 0.5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_Pivot.y
|
||||||
|
value: 0.5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchorMax.x
|
||||||
|
value: 0.5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchorMax.y
|
||||||
|
value: 0.5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchorMin.x
|
||||||
|
value: 0.5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchorMin.y
|
||||||
|
value: 0.5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_SizeDelta.x
|
||||||
|
value: 428
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_SizeDelta.y
|
||||||
|
value: 20
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.w
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchoredPosition.x
|
||||||
|
value: 5
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchoredPosition.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 4155020195241443012, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_AnchorMax.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 8480022438557558975, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
propertyPath: m_Name
|
||||||
|
value: UIPlaybackProgressBar
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
m_RemovedComponents: []
|
||||||
|
m_RemovedGameObjects: []
|
||||||
|
m_AddedGameObjects: []
|
||||||
|
m_AddedComponents: []
|
||||||
|
m_SourcePrefab: {fileID: 100100000, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
--- !u!224 &1355571794369558167 stripped
|
||||||
|
RectTransform:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 3878445294948272592, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 2818395949540507463}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
--- !u!114 &1875730371384184921 stripped
|
||||||
|
MonoBehaviour:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 4403142724682182430, guid: ededfe70f265a2f409ca4fdfffb735fc, type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 2818395949540507463}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: bce52072cdea6144b98e9297b89d6558, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
@@ -0,0 +1,756 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &163906468634978895
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1127096903710226130}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Handle Slide Area
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &1127096903710226130
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 163906468634978895}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 3763422474971032885}
|
||||||
|
m_Father: {fileID: 2415198746135535164}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 0, y: -24}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!1 &222054138603084008
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1801131152219070359}
|
||||||
|
- component: {fileID: 4238787803905428589}
|
||||||
|
- component: {fileID: 4179737159690841334}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: PlayTimeTxt
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &1801131152219070359
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 222054138603084008}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 3878445294948272592}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 80, y: 20}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &4238787803905428589
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 222054138603084008}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &4179737159690841334
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 222054138603084008}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_text: 00:00:00
|
||||||
|
m_isRightToLeft: 0
|
||||||
|
m_fontAsset: {fileID: 11400000, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
||||||
|
m_sharedMaterial: {fileID: 4860575619018115804, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
||||||
|
m_fontSharedMaterials: []
|
||||||
|
m_fontMaterial: {fileID: 0}
|
||||||
|
m_fontMaterials: []
|
||||||
|
m_fontColor32:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 4294967295
|
||||||
|
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_enableVertexGradient: 0
|
||||||
|
m_colorMode: 3
|
||||||
|
m_fontColorGradient:
|
||||||
|
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_fontColorGradientPreset: {fileID: 0}
|
||||||
|
m_spriteAsset: {fileID: 0}
|
||||||
|
m_tintAllSprites: 0
|
||||||
|
m_StyleSheet: {fileID: 0}
|
||||||
|
m_TextStyleHashCode: -1183493901
|
||||||
|
m_overrideHtmlColors: 0
|
||||||
|
m_faceColor:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 4294967295
|
||||||
|
m_fontSize: 12
|
||||||
|
m_fontSizeBase: 12
|
||||||
|
m_fontWeight: 400
|
||||||
|
m_enableAutoSizing: 0
|
||||||
|
m_fontSizeMin: 18
|
||||||
|
m_fontSizeMax: 72
|
||||||
|
m_fontStyle: 0
|
||||||
|
m_HorizontalAlignment: 1
|
||||||
|
m_VerticalAlignment: 512
|
||||||
|
m_textAlignment: 65535
|
||||||
|
m_characterSpacing: -1
|
||||||
|
m_wordSpacing: 0
|
||||||
|
m_lineSpacing: 0
|
||||||
|
m_lineSpacingMax: 0
|
||||||
|
m_paragraphSpacing: 0
|
||||||
|
m_charWidthMaxAdj: 0
|
||||||
|
m_TextWrappingMode: 1
|
||||||
|
m_wordWrappingRatios: 0.4
|
||||||
|
m_overflowMode: 0
|
||||||
|
m_linkedTextComponent: {fileID: 0}
|
||||||
|
parentLinkedComponent: {fileID: 0}
|
||||||
|
m_enableKerning: 1
|
||||||
|
m_ActiveFontFeatures: 6e72656b
|
||||||
|
m_enableExtraPadding: 0
|
||||||
|
checkPaddingRequired: 0
|
||||||
|
m_isRichText: 1
|
||||||
|
m_EmojiFallbackSupport: 1
|
||||||
|
m_parseCtrlCharacters: 1
|
||||||
|
m_isOrthographic: 1
|
||||||
|
m_isCullingEnabled: 0
|
||||||
|
m_horizontalMapping: 0
|
||||||
|
m_verticalMapping: 0
|
||||||
|
m_uvLineOffset: 0
|
||||||
|
m_geometrySortingOrder: 0
|
||||||
|
m_IsTextObjectScaleStatic: 0
|
||||||
|
m_VertexBufferAutoSizeReduction: 0
|
||||||
|
m_useMaxVisibleDescender: 1
|
||||||
|
m_pageToDisplay: 1
|
||||||
|
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_isUsingLegacyAnimationComponent: 0
|
||||||
|
m_isVolumetricText: 0
|
||||||
|
m_hasFontAssetChanged: 0
|
||||||
|
m_baseMaterial: {fileID: 0}
|
||||||
|
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
--- !u!1 &2268563310445029541
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 4280318672268889011}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Fill Area
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &4280318672268889011
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2268563310445029541}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 4155020195241443012}
|
||||||
|
m_Father: {fileID: 2415198746135535164}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0.25}
|
||||||
|
m_AnchorMax: {x: 1, y: 0.75}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 0, y: -8}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!1 &2803172565992579242
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 4155020195241443012}
|
||||||
|
- component: {fileID: 311178213436131380}
|
||||||
|
- component: {fileID: 3137463316529282915}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Fill
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &4155020195241443012
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2803172565992579242}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 4280318672268889011}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 8, y: 0}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &311178213436131380
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2803172565992579242}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &3137463316529282915
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2803172565992579242}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 0.14117648, g: 0.54509807, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_Type: 1
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 4
|
||||||
|
m_FillAmount: 1
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
m_PixelsPerUnitMultiplier: 1
|
||||||
|
--- !u!1 &4876784253204942086
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 2415198746135535164}
|
||||||
|
- component: {fileID: 3224467329000990368}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: ProgressBar
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &2415198746135535164
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4876784253204942086}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 451313813165107877}
|
||||||
|
- {fileID: 4280318672268889011}
|
||||||
|
- {fileID: 1127096903710226130}
|
||||||
|
m_Father: {fileID: 3878445294948272592}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 268, y: 40}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!114 &3224467329000990368
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4876784253204942086}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: c91122bcd4466654a8edf4e66c23448a, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Navigation:
|
||||||
|
m_Mode: 3
|
||||||
|
m_WrapAround: 0
|
||||||
|
m_SelectOnUp: {fileID: 0}
|
||||||
|
m_SelectOnDown: {fileID: 0}
|
||||||
|
m_SelectOnLeft: {fileID: 0}
|
||||||
|
m_SelectOnRight: {fileID: 0}
|
||||||
|
m_Transition: 1
|
||||||
|
m_Colors:
|
||||||
|
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||||
|
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||||
|
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||||
|
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||||
|
m_ColorMultiplier: 1
|
||||||
|
m_FadeDuration: 0.1
|
||||||
|
m_SpriteState:
|
||||||
|
m_HighlightedSprite: {fileID: 0}
|
||||||
|
m_PressedSprite: {fileID: 0}
|
||||||
|
m_SelectedSprite: {fileID: 0}
|
||||||
|
m_DisabledSprite: {fileID: 0}
|
||||||
|
m_AnimationTriggers:
|
||||||
|
m_NormalTrigger: Normal
|
||||||
|
m_HighlightedTrigger: Highlighted
|
||||||
|
m_PressedTrigger: Pressed
|
||||||
|
m_SelectedTrigger: Selected
|
||||||
|
m_DisabledTrigger: Disabled
|
||||||
|
m_Interactable: 1
|
||||||
|
m_TargetGraphic: {fileID: 8971200621436978884}
|
||||||
|
m_FillRect: {fileID: 4155020195241443012}
|
||||||
|
m_HandleRect: {fileID: 3763422474971032885}
|
||||||
|
m_Direction: 0
|
||||||
|
m_MinValue: 0
|
||||||
|
m_MaxValue: 3600
|
||||||
|
m_WholeNumbers: 0
|
||||||
|
m_Value: 0
|
||||||
|
m_OnValueChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
--- !u!1 &4952136363032480536
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 4276169991110453957}
|
||||||
|
- component: {fileID: 607580168181425316}
|
||||||
|
- component: {fileID: 4398121541985658256}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: TotalTimeTxt
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &4276169991110453957
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4952136363032480536}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 3878445294948272592}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 130, y: 20}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &607580168181425316
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4952136363032480536}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &4398121541985658256
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4952136363032480536}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_text: 00:60:00
|
||||||
|
m_isRightToLeft: 0
|
||||||
|
m_fontAsset: {fileID: 11400000, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
||||||
|
m_sharedMaterial: {fileID: 4860575619018115804, guid: 08cebd004d97ca742ac80400f37f4eed, type: 2}
|
||||||
|
m_fontSharedMaterials: []
|
||||||
|
m_fontMaterial: {fileID: 0}
|
||||||
|
m_fontMaterials: []
|
||||||
|
m_fontColor32:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 4294967295
|
||||||
|
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_enableVertexGradient: 0
|
||||||
|
m_colorMode: 3
|
||||||
|
m_fontColorGradient:
|
||||||
|
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_fontColorGradientPreset: {fileID: 0}
|
||||||
|
m_spriteAsset: {fileID: 0}
|
||||||
|
m_tintAllSprites: 0
|
||||||
|
m_StyleSheet: {fileID: 0}
|
||||||
|
m_TextStyleHashCode: -1183493901
|
||||||
|
m_overrideHtmlColors: 0
|
||||||
|
m_faceColor:
|
||||||
|
serializedVersion: 2
|
||||||
|
rgba: 4294967295
|
||||||
|
m_fontSize: 12
|
||||||
|
m_fontSizeBase: 12
|
||||||
|
m_fontWeight: 400
|
||||||
|
m_enableAutoSizing: 0
|
||||||
|
m_fontSizeMin: 18
|
||||||
|
m_fontSizeMax: 72
|
||||||
|
m_fontStyle: 0
|
||||||
|
m_HorizontalAlignment: 4
|
||||||
|
m_VerticalAlignment: 512
|
||||||
|
m_textAlignment: 65535
|
||||||
|
m_characterSpacing: -1
|
||||||
|
m_wordSpacing: 0
|
||||||
|
m_lineSpacing: 0
|
||||||
|
m_lineSpacingMax: 0
|
||||||
|
m_paragraphSpacing: 0
|
||||||
|
m_charWidthMaxAdj: 0
|
||||||
|
m_TextWrappingMode: 1
|
||||||
|
m_wordWrappingRatios: 0.4
|
||||||
|
m_overflowMode: 0
|
||||||
|
m_linkedTextComponent: {fileID: 0}
|
||||||
|
parentLinkedComponent: {fileID: 0}
|
||||||
|
m_enableKerning: 1
|
||||||
|
m_ActiveFontFeatures: 6e72656b
|
||||||
|
m_enableExtraPadding: 0
|
||||||
|
checkPaddingRequired: 0
|
||||||
|
m_isRichText: 1
|
||||||
|
m_EmojiFallbackSupport: 1
|
||||||
|
m_parseCtrlCharacters: 1
|
||||||
|
m_isOrthographic: 1
|
||||||
|
m_isCullingEnabled: 0
|
||||||
|
m_horizontalMapping: 0
|
||||||
|
m_verticalMapping: 0
|
||||||
|
m_uvLineOffset: 0
|
||||||
|
m_geometrySortingOrder: 0
|
||||||
|
m_IsTextObjectScaleStatic: 0
|
||||||
|
m_VertexBufferAutoSizeReduction: 0
|
||||||
|
m_useMaxVisibleDescender: 1
|
||||||
|
m_pageToDisplay: 1
|
||||||
|
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_isUsingLegacyAnimationComponent: 0
|
||||||
|
m_isVolumetricText: 0
|
||||||
|
m_hasFontAssetChanged: 0
|
||||||
|
m_baseMaterial: {fileID: 0}
|
||||||
|
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
--- !u!1 &6468093285766946019
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 451313813165107877}
|
||||||
|
- component: {fileID: 4578362299949511196}
|
||||||
|
- component: {fileID: 6297960483353807409}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Background
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &451313813165107877
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6468093285766946019}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 2415198746135535164}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0.25}
|
||||||
|
m_AnchorMax: {x: 1, y: 0.75}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 0, y: -10}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &4578362299949511196
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6468093285766946019}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &6297960483353807409
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6468093285766946019}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 0.2}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_Type: 1
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 4
|
||||||
|
m_FillAmount: 1
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
m_PixelsPerUnitMultiplier: 1
|
||||||
|
--- !u!1 &6539802130848792685
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 3763422474971032885}
|
||||||
|
- component: {fileID: 2138677238417708774}
|
||||||
|
- component: {fileID: 8971200621436978884}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Handle
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &3763422474971032885
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6539802130848792685}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 1127096903710226130}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: 6, y: 0}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &2138677238417708774
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6539802130848792685}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &8971200621436978884
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6539802130848792685}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_Sprite: {fileID: 21300000, guid: e5829cbc100001646956a9c3ed4e33c5, type: 3}
|
||||||
|
m_Type: 1
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 4
|
||||||
|
m_FillAmount: 1
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
m_PixelsPerUnitMultiplier: 1
|
||||||
|
--- !u!1 &8480022438557558975
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 3878445294948272592}
|
||||||
|
- component: {fileID: 4403142724682182430}
|
||||||
|
- component: {fileID: 907383497048967181}
|
||||||
|
- component: {fileID: 830299204133775401}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: UIPlaybackProgressBar
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &3878445294948272592
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8480022438557558975}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 1801131152219070359}
|
||||||
|
- {fileID: 2415198746135535164}
|
||||||
|
- {fileID: 4276169991110453957}
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||||
|
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||||
|
m_AnchoredPosition: {x: 5, y: 0}
|
||||||
|
m_SizeDelta: {x: 428, y: 20}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!114 &4403142724682182430
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8480022438557558975}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: bce52072cdea6144b98e9297b89d6558, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
canvasGroup: {fileID: 830299204133775401}
|
||||||
|
playTimeTxt: {fileID: 4179737159690841334}
|
||||||
|
totalTimeTxt: {fileID: 4398121541985658256}
|
||||||
|
progressBar: {fileID: 3224467329000990368}
|
||||||
|
--- !u!114 &907383497048967181
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8480022438557558975}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Padding:
|
||||||
|
m_Left: 0
|
||||||
|
m_Right: 0
|
||||||
|
m_Top: 0
|
||||||
|
m_Bottom: 0
|
||||||
|
m_ChildAlignment: 3
|
||||||
|
m_Spacing: 0
|
||||||
|
m_ChildForceExpandWidth: 1
|
||||||
|
m_ChildForceExpandHeight: 1
|
||||||
|
m_ChildControlWidth: 0
|
||||||
|
m_ChildControlHeight: 0
|
||||||
|
m_ChildScaleWidth: 0
|
||||||
|
m_ChildScaleHeight: 0
|
||||||
|
m_ReverseArrangement: 0
|
||||||
|
--- !u!225 &830299204133775401
|
||||||
|
CanvasGroup:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8480022438557558975}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_Alpha: 1
|
||||||
|
m_Interactable: 0
|
||||||
|
m_BlocksRaycasts: 1
|
||||||
|
m_IgnoreParentGroups: 0
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ededfe70f265a2f409ca4fdfffb735fc
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 340 B |
|
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 261 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 249 B After Width: | Height: | Size: 249 B |
|
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 226 B |
|
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
|
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
8
Assets/Resources/Prefabs/UI/Tab.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a47c62d73f7117344bc36b27a7cce32c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -47,9 +47,6 @@ namespace SampleProject
|
|||||||
{
|
{
|
||||||
Initialized.Invoke();
|
Initialized.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
//MqttReceiver 시작
|
|
||||||
DataRepository.Instance.MqttReceiver.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -70,7 +67,8 @@ namespace SampleProject
|
|||||||
|
|
||||||
Debug.Log("requestDataAsync");
|
Debug.Log("requestDataAsync");
|
||||||
|
|
||||||
UILoading.Show();
|
//UILoading.Show();
|
||||||
|
|
||||||
//Debug.Log("Requesting BaseInfo data...");
|
//Debug.Log("Requesting BaseInfo data...");
|
||||||
//var httpFetcher = DataRepository.Instance.HttpFetcher;
|
//var httpFetcher = DataRepository.Instance.HttpFetcher;
|
||||||
//var splitRequest = new HttpRequestConfig(URLList.Get("baseinfo"))
|
//var splitRequest = new HttpRequestConfig(URLList.Get("baseinfo"))
|
||||||
@@ -83,12 +81,12 @@ namespace SampleProject
|
|||||||
//UILoading.Hide();
|
//UILoading.Hide();
|
||||||
|
|
||||||
//MqttReceiver 시작
|
//MqttReceiver 시작
|
||||||
DataRepository.Instance.MqttReceiver.Start();
|
//DataRepository.Instance.MqttReceiver.Start();
|
||||||
if(AGVManager.Instance.Created)
|
//if(AGVManager.Instance.Created)
|
||||||
{
|
//{
|
||||||
await UniTask.Delay(1000);
|
// await UniTask.Delay(1000);
|
||||||
UILoading.Hide();
|
// UILoading.Hide();
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnExitPlayback()
|
private async void OnExitPlayback()
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace UVC.Data.Core
|
namespace UVC.Data.Core
|
||||||
{
|
{
|
||||||
@@ -17,7 +16,9 @@ namespace UVC.Data.Core
|
|||||||
/// 이 객체가 객체 풀에 있는지 여부를 나타냅니다.
|
/// 이 객체가 객체 풀에 있는지 여부를 나타냅니다.
|
||||||
/// 중복 반환을 방지하기 위해 DataArrayPool에서 내부적으로 사용됩니다.
|
/// 중복 반환을 방지하기 위해 DataArrayPool에서 내부적으로 사용됩니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool IsInPool { get => isInPool;
|
internal bool IsInPool
|
||||||
|
{
|
||||||
|
get => isInPool;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
isInPool = value;
|
isInPool = value;
|
||||||
@@ -268,7 +269,7 @@ namespace UVC.Data.Core
|
|||||||
|
|
||||||
// 기존 변경 추적 목록을 초기화합니다.
|
// 기존 변경 추적 목록을 초기화합니다.
|
||||||
ClearTrackedChanges();
|
ClearTrackedChanges();
|
||||||
|
|
||||||
var thisDict = this.ToDictionary(item => item.Id);
|
var thisDict = this.ToDictionary(item => item.Id);
|
||||||
var otherDict = otherArray.ToDictionary(item => item.Id);
|
var otherDict = otherArray.ToDictionary(item => item.Id);
|
||||||
|
|
||||||
@@ -453,7 +454,7 @@ namespace UVC.Data.Core
|
|||||||
{
|
{
|
||||||
// 풀에서 새 DataArray 인스턴스를 가져옵니다.
|
// 풀에서 새 DataArray 인스턴스를 가져옵니다.
|
||||||
DataArray clone;
|
DataArray clone;
|
||||||
if(fromPool) clone = DataArrayPool.Get();
|
if (fromPool) clone = DataArrayPool.Get();
|
||||||
else clone = new DataArray();
|
else clone = new DataArray();
|
||||||
|
|
||||||
// 배열의 모든 DataObject를 순회하며 각각을 복제합니다.
|
// 배열의 모든 DataObject를 순회하며 각각을 복제합니다.
|
||||||
@@ -475,7 +476,7 @@ namespace UVC.Data.Core
|
|||||||
{
|
{
|
||||||
clone.modifiedList.Add(clonedItem);
|
clone.modifiedList.Add(clonedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Newtonsoft.Json;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
|
||||||
using UVC.Data.Core;
|
using UVC.Data.Core;
|
||||||
using UVC.Data.Http;
|
using UVC.Data.Http;
|
||||||
using UVC.Data.Mqtt;
|
using UVC.Data.Mqtt;
|
||||||
@@ -241,7 +240,8 @@ namespace UVC.Data
|
|||||||
{
|
{
|
||||||
if (dataUpdateHandlers.ContainsKey(key))
|
if (dataUpdateHandlers.ContainsKey(key))
|
||||||
{
|
{
|
||||||
UniTask.Post(() => dataUpdateHandlers[key]!.Invoke(dataObject));
|
var handler = dataUpdateHandlers[key];
|
||||||
|
UniTask.Post(() => handler.Invoke(dataObject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
Assets/Scripts/UVC/Factory/Modal/ConfigDataOrderModal.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#nullable enable
|
||||||
|
using UnityEngine;
|
||||||
|
using UVC.UI.List;
|
||||||
|
|
||||||
|
namespace UVC.Factory.Modal
|
||||||
|
{
|
||||||
|
public class ConfigDataOrderModal : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private DraggableScrollList? draggableList;
|
||||||
|
|
||||||
|
protected virtual void Awake()
|
||||||
|
{
|
||||||
|
if (draggableList == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("draggableList 참조가 설정되지 않았습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이벤트 구독
|
||||||
|
draggableList.OnItemReordered += OnItemReordered;
|
||||||
|
draggableList.OnItemSelected += OnItemSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
// 1. DraggableItemData 설정
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
draggableList?.AddItem(new DraggableItemData($"Item {i + 1}", i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템 순서 변경 이벤트 처리
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "sender" > 이벤트 발생자</param>
|
||||||
|
/// <param name = "e" > 이벤트 인자</param>
|
||||||
|
private void OnItemReordered(object? sender, DraggableItemReorderEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.Log($"아이템 순서 변경됨: ID={e.ItemId}, {e.OldIndex} -> {e.NewIndex}");
|
||||||
|
|
||||||
|
// 여기에 순서 변경에 대한 비즈니스 로직 구현
|
||||||
|
// 예: 서버에 변경사항 전송, 설정 저장 등
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템 선택 이벤트 처리
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">이벤트 발생자</param>
|
||||||
|
/// <param name="item">선택된 아이템</param>
|
||||||
|
private void OnItemSelected(object? sender, DraggableListItem item)
|
||||||
|
{
|
||||||
|
if (item?.Data != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"아이템 선택됨: {item.Data.Id}");
|
||||||
|
|
||||||
|
// 선택된 아이템에 대한 처리
|
||||||
|
// 예: 상세 정보 표시, 편집 모드 진입 등
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 컴포넌트 정리
|
||||||
|
/// </summary>
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (draggableList != null)
|
||||||
|
{
|
||||||
|
draggableList.OnItemReordered -= OnItemReordered;
|
||||||
|
draggableList.OnItemSelected -= OnItemSelected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dc92cc933c1ce844c955ffc32982d3af
|
||||||
@@ -8,33 +8,75 @@ using UVC.UI.Modal;
|
|||||||
|
|
||||||
namespace UVC.Factory.Playback
|
namespace UVC.Factory.Playback
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 재생(Playback) 명령을 실행하는 클래스입니다.
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// 이 명령은 팩토리 카메라를 비활성화하고, 재생 목록 모달을 띄운 뒤,
|
||||||
|
/// 사용자가 재생 항목을 선택하면 재생을 시작합니다.
|
||||||
|
/// 사용자가 취소하면 재생을 종료합니다.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // ICommand 인터페이스를 구현하므로 다음과 같이 사용할 수 있습니다.
|
||||||
|
/// ICommand playbackCommand = new PlaybackCommand();
|
||||||
|
/// playbackCommand.Execute();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// </summary>
|
||||||
public class PlaybackCommand : ICommand
|
public class PlaybackCommand : ICommand
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 명령을 실행합니다.
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// 1. 카메라를 비활성화합니다.<br/>
|
||||||
|
/// 2. 재생 목록 모달을 띄웁니다.<br/>
|
||||||
|
/// 3. 사용자가 항목을 선택하면 재생을 시작합니다.<br/>
|
||||||
|
/// 4. 사용자가 취소하면 재생을 종료합니다.<br/>
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 명령 실행 예시
|
||||||
|
/// var command = new PlaybackCommand();
|
||||||
|
/// command.Execute();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">사용하지 않음</param>
|
||||||
public async void Execute(object? parameter = null)
|
public async void Execute(object? parameter = null)
|
||||||
{
|
{
|
||||||
|
// 1. 카메라 비활성화
|
||||||
FactoryCameraController.Instance.Enable = false;
|
FactoryCameraController.Instance.Enable = false;
|
||||||
|
// 2. 재생 목록 모달 생성 및 표시
|
||||||
var modalContent = new ModalContent(UIPlaybackListModal.PrefabPath)
|
var modalContent = new ModalContent(UIPlaybackListModal.PrefabPath)
|
||||||
{
|
{
|
||||||
Title = "Playback List",
|
Title = "Playback List",
|
||||||
ConfirmButtonText = "Play",
|
ConfirmButtonText = "Play",
|
||||||
ShowCancelButton = false
|
ShowCancelButton = false
|
||||||
};
|
};
|
||||||
|
// 3. 사용자가 항목을 선택할 때까지 대기
|
||||||
UIPlaybackListItemData? result = await UVC.UI.Modal.Modal.Open<UIPlaybackListItemData>(modalContent);
|
UIPlaybackListItemData? result = await UVC.UI.Modal.Modal.Open<UIPlaybackListItemData>(modalContent);
|
||||||
|
|
||||||
Debug.Log($"PlaybackCommand result==null:{result==null}");
|
Debug.Log($"PlaybackCommand result==null:{result==null}");
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
|
// 4. 항목 선택 시: 로딩 표시, 재생 시작
|
||||||
UILoading.Show();
|
UILoading.Show();
|
||||||
UIPlaybackListItemData data = result;
|
UIPlaybackListItemData data = result;
|
||||||
Debug.Log($"PlaybackCommand data:{data}");
|
Debug.Log($"PlaybackCommand data:{data}");
|
||||||
DataRepository.Instance.MqttReceiver.Stop();
|
DataRepository.Instance.MqttReceiver.Stop();
|
||||||
|
|
||||||
await PlaybackService.Instance.StartAsync(data);
|
await PlaybackService.Instance.StartAsync(data);
|
||||||
|
|
||||||
FactoryCameraController.Instance.Enable = true;
|
FactoryCameraController.Instance.Enable = true;
|
||||||
UILoading.Hide();
|
UILoading.Hide();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// 5. 취소 시: 재생 종료
|
||||||
UILoading.Show();
|
UILoading.Show();
|
||||||
PlaybackService.Instance.Exit();
|
PlaybackService.Instance.Exit();
|
||||||
FactoryCameraController.Instance.Enable = true;
|
FactoryCameraController.Instance.Enable = true;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Best.HTTP;
|
using Best.HTTP;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using SampleProject.Config;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -15,6 +14,23 @@ namespace UVC.Factory.Playback
|
|||||||
|
|
||||||
private PlaybackSQLiteService? sqliteService = null;
|
private PlaybackSQLiteService? sqliteService = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 서버에서 재생 목록 날짜 리스트를 요청합니다.
|
||||||
|
///
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var repo = new PlaybackRepository();
|
||||||
|
/// var dateList = await repo.RequestPlaybackDateList();
|
||||||
|
/// if (dateList != null)
|
||||||
|
/// {
|
||||||
|
/// foreach (var date in dateList.Keys)
|
||||||
|
/// {
|
||||||
|
/// Debug.Log($"날짜: {date}");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>성공 시 날짜별 재생 목록 딕셔너리, 실패 시 null</returns>
|
||||||
public async UniTask<Dictionary<string, Dictionary<string, string>>?> RequestPlaybackDateList()
|
public async UniTask<Dictionary<string, Dictionary<string, string>>?> RequestPlaybackDateList()
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -45,6 +61,29 @@ namespace UVC.Factory.Playback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 서버에서 재생 데이터 파일을 다운로드합니다.
|
||||||
|
///
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var repo = new PlaybackRepository();
|
||||||
|
/// string fileName = "sample.db";
|
||||||
|
/// string savePath = Application.persistentDataPath + "/sample.db";
|
||||||
|
/// repo.DownloadPlaybackData(
|
||||||
|
/// fileName,
|
||||||
|
/// savePath,
|
||||||
|
/// (current, total) => Debug.Log($"{current}/{total} bytes 다운로드 중"),
|
||||||
|
/// () => Debug.Log("다운로드 완료"),
|
||||||
|
/// (error) => Debug.LogError($"다운로드 실패: {error}")
|
||||||
|
/// );
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">다운로드할 파일명</param>
|
||||||
|
/// <param name="savePath">저장 경로</param>
|
||||||
|
/// <param name="OnProgress">다운로드 진행 콜백 (현재, 전체 바이트)</param>
|
||||||
|
/// <param name="OnComplete">다운로드 완료 콜백</param>
|
||||||
|
/// <param name="OnError">다운로드 실패 콜백 (에러 메시지)</param>
|
||||||
|
/// <returns>다운로드 요청 객체(필요시 Abort 등 제어 가능), 실패 시 null</returns>
|
||||||
public HTTPRequest? DownloadPlaybackData(string fileName, string savePath, Action<long, long> OnProgress, Action OnComplete, Action<string> OnError)
|
public HTTPRequest? DownloadPlaybackData(string fileName, string savePath, Action<long, long> OnProgress, Action OnComplete, Action<string> OnError)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -59,15 +98,29 @@ namespace UVC.Factory.Playback
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
|
/// selectTime보다 ±second 사이의 데이터를 조회합니다. selectTime, second 포함.
|
||||||
|
///
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var repo = new PlaybackRepository();
|
||||||
|
/// string date = "2024-07-29";
|
||||||
|
/// string sqlFileName = "sample.db";
|
||||||
|
/// string selectTime = "2024-07-29T12:00:00.000Z";
|
||||||
|
/// int second = 10;
|
||||||
|
/// var list = await repo.SelectBySecondAsync(date, sqlFileName, selectTime, second, true, 5);
|
||||||
|
/// foreach (var entity in list)
|
||||||
|
/// {
|
||||||
|
/// Debug.Log($"데이터: {entity.data}, 시간: {entity.timestamp}");
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="date"></param>
|
/// <param name="date">폴더명(날짜 등)</param>
|
||||||
/// <param name="sqlFileName"></param>
|
/// <param name="sqlFileName">SQLite 파일명</param>
|
||||||
/// <param name="selectTime">yyyy-MM-ddTHH:mm:ss.fffZ format string</param>
|
/// <param name="selectTime">yyyy-MM-ddTHH:mm:ss.fffZ 형식의 기준 시간</param>
|
||||||
/// <param name="second"></param>
|
/// <param name="second">±초(양수: 미래, 음수: 과거)</param>
|
||||||
/// <param name="orderAsc">true: 오래된 시간이 먼저, false: 최근 시간이 먼저</param>
|
/// <param name="orderAsc">true: 오래된 시간부터, false: 최근 시간부터</param>
|
||||||
/// <param name="limit"></param>
|
/// <param name="limit">최대 조회 개수(0이면 제한 없음)</param>
|
||||||
/// <returns></returns>
|
/// <returns>조회된 데이터 리스트</returns>
|
||||||
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondAsync(string date, string sqlFileName, string selectTime, int second, bool orderAsc = true, int limit = 0)
|
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondAsync(string date, string sqlFileName, string selectTime, int second, bool orderAsc = true, int limit = 0)
|
||||||
{
|
{
|
||||||
validationSqliteService(date, sqlFileName);
|
validationSqliteService(date, sqlFileName);
|
||||||
@@ -75,22 +128,46 @@ namespace UVC.Factory.Playback
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
|
/// baseInfo 테이블에서 selectTime보다 ±second 사이의 데이터를 조회합니다. selectTime, second 포함.
|
||||||
|
///
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var repo = new PlaybackRepository();
|
||||||
|
/// string date = "2024-07-29";
|
||||||
|
/// string sqlFileName = "sample.db";
|
||||||
|
/// string selectTime = "2024-07-29T12:00:00.000Z";
|
||||||
|
/// int second = -5;
|
||||||
|
/// var list = await repo.SelectBySecondBaseInfo(date, sqlFileName, selectTime, second, false, 1);
|
||||||
|
/// foreach (var entity in list)
|
||||||
|
/// {
|
||||||
|
/// Debug.Log($"데이터: {entity.data}, 시간: {entity.timestamp}");
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="date"></param>
|
/// <param name="date">폴더명(날짜 등)</param>
|
||||||
/// <param name="sqlFileName"></param>
|
/// <param name="sqlFileName">SQLite 파일명</param>
|
||||||
/// <param name="selectTime">yyyy-MM-ddTHH:mm:ss.fffZ format string</param>
|
/// <param name="selectTime">yyyy-MM-ddTHH:mm:ss.fffZ 형식의 기준 시간</param>
|
||||||
/// <param name="second"></param>
|
/// <param name="second">±초(양수: 미래, 음수: 과거)</param>
|
||||||
/// <param name="orderAsc">true: 오래된 시간이 먼저, false: 최근 시간이 먼저</param>
|
/// <param name="orderAsc">true: 오래된 시간부터, false: 최근 시간부터</param>
|
||||||
/// <param name="limit"></param>
|
/// <param name="limit">최대 조회 개수</param>
|
||||||
/// <returns></returns>
|
/// <returns>조회된 데이터 리스트</returns>
|
||||||
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string date, string sqlFileName, string selectTime, int second = 59, bool orderAsc = true, int limit = 1)
|
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string date, string sqlFileName, string selectTime, int second = 59, bool orderAsc = true, int limit = 1)
|
||||||
{
|
{
|
||||||
validationSqliteService(date, sqlFileName);
|
validationSqliteService(date, sqlFileName);
|
||||||
return await sqliteService!.SelectBySecondBaseInfo(selectTime, second, orderAsc, limit);
|
return await sqliteService!.SelectBySecondBaseInfo(selectTime, second, orderAsc, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 내부적으로 SQLite 서비스가 올바른 파일에 연결되어 있는지 확인하고, 필요시 재연결합니다.
|
||||||
|
///
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// // 일반적으로 직접 호출할 필요 없음(내부에서 자동 호출)
|
||||||
|
/// validationSqliteService("2024-07-29", "sample.db");
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">폴더명(날짜 등)</param>
|
||||||
|
/// <param name="sqlFileName">SQLite 파일명</param>
|
||||||
private void validationSqliteService(string date, string sqlFileName)
|
private void validationSqliteService(string date, string sqlFileName)
|
||||||
{
|
{
|
||||||
if (sqliteService == null) sqliteService = new PlaybackSQLiteService();
|
if (sqliteService == null) sqliteService = new PlaybackSQLiteService();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
|
||||||
using UVC.Factory.Playback;
|
using UVC.Factory.Playback;
|
||||||
using UVC.Util;
|
using UVC.Util;
|
||||||
|
|
||||||
@@ -12,16 +11,10 @@ namespace UVC.Factory
|
|||||||
{
|
{
|
||||||
public class PlaybackSQLiteService
|
public class PlaybackSQLiteService
|
||||||
{
|
{
|
||||||
|
// SQLite 데이터베이스 연결 객체
|
||||||
//#region Singleton
|
|
||||||
//private static readonly SQLiteService instance = new SQLiteService();
|
|
||||||
//public static SQLiteService Instance => instance;
|
|
||||||
//static SQLiteService() { }
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
|
|
||||||
private SQLiteConnection dbConnection;
|
private SQLiteConnection dbConnection;
|
||||||
|
|
||||||
|
// 데이터베이스 연결 여부 확인
|
||||||
public bool Connected { get => dbConnection != null; }
|
public bool Connected { get => dbConnection != null; }
|
||||||
|
|
||||||
private string date;
|
private string date;
|
||||||
@@ -29,6 +22,16 @@ namespace UVC.Factory
|
|||||||
private string sqliteFileName;
|
private string sqliteFileName;
|
||||||
public string SqliteFileName { get => sqliteFileName; }
|
public string SqliteFileName { get => sqliteFileName; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 데이터베이스 파일에 연결합니다.
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var service = new PlaybackSQLiteService();
|
||||||
|
/// service.Connect("2024-07-29", "sample.db");
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">폴더명(날짜 등)</param>
|
||||||
|
/// <param name="sqliteFileName">SQLite 파일명</param>
|
||||||
public void Connect(string date, string sqliteFileName)
|
public void Connect(string date, string sqliteFileName)
|
||||||
{
|
{
|
||||||
this.date = date;
|
this.date = date;
|
||||||
@@ -36,6 +39,13 @@ namespace UVC.Factory
|
|||||||
dbConnection = new SQLiteConnection(Path.Combine(PlaybackService.PlaybackFolderPath, date, sqliteFileName));
|
dbConnection = new SQLiteConnection(Path.Combine(PlaybackService.PlaybackFolderPath, date, sqliteFileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 데이터베이스 연결을 닫습니다.
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// service.CloseDB();
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
public void CloseDB()
|
public void CloseDB()
|
||||||
{
|
{
|
||||||
dbConnection.Close();
|
dbConnection.Close();
|
||||||
@@ -43,30 +53,23 @@ namespace UVC.Factory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 추가하기
|
/// realTime 테이블에 데이터를 추가합니다.
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// int rows = service.Insert("센서값", "2024-07-29T12:00:00.000Z", "온도값");
|
||||||
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="data">저장할 데이터(문자열)</param>
|
||||||
/// <param name="timeStamp">yyyy-MM-ddTHH:mm:ss.fffZ format string</param>
|
/// <param name="timeStamp">yyyy-MM-ddTHH:mm:ss.fffZ 형식의 시간</param>
|
||||||
/// <param name="temp"></param>
|
/// <param name="temp">임시 데이터(옵션)</param>
|
||||||
/// <returns>데이터베이스에서 추가된 행 수</returns>
|
/// <returns>추가된 행 수</returns>
|
||||||
public int Insert(string data, string timeStamp, string temp = null)
|
public int Insert(string data, string timeStamp, string temp = null)
|
||||||
{
|
{
|
||||||
var query = $"INSERT INTO realTime (data, timestamp, temp) VALUES ('{data}', '{timeStamp}', " + (temp == null ? "null" : "'" + temp + "'") + ");";
|
var query = $"INSERT INTO realTime (data, timestamp, temp) VALUES ('{data}', '{timeStamp}', " + (temp == null ? "null" : "'" + temp + "'") + ");";
|
||||||
int changedRowLen = dbConnection.Execute(query);
|
int changedRowLen = dbConnection.Execute(query);
|
||||||
return changedRowLen;
|
return changedRowLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
|
|
||||||
/// second > 0 : selectTime <= data < selectTime + second
|
|
||||||
/// second < 0 : selectTime + second < data <= selectTime
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="selectTime">yyyy-MM-ddTHH:mm:ss.fffZ format string</param>
|
|
||||||
/// <param name="second"></param>
|
|
||||||
/// <param name="orderAsc">true: 오래된 시간이 먼저, false: 최근 시간이 먼저</param>
|
|
||||||
/// <param name="limit"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
///
|
|
||||||
readonly string[] queryParts =
|
readonly string[] queryParts =
|
||||||
{
|
{
|
||||||
"SELECT * FROM realTime WHERE ",
|
"SELECT * FROM realTime WHERE ",
|
||||||
@@ -77,6 +80,20 @@ namespace UVC.Factory
|
|||||||
" ORDER BY timestamp ",
|
" ORDER BY timestamp ",
|
||||||
" LIMIT ",
|
" LIMIT ",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 시간(selectTime) 기준으로 ±second 범위의 데이터를 조회합니다.
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// // 10초 뒤까지의 데이터 5개를 조회(오름차순)
|
||||||
|
/// var list = await service.SelectBySecond("2024-07-29T12:00:00.000Z", 10, true, 5);
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selectTime">기준 시간(yyyy-MM-ddTHH:mm:ss.fffZ)</param>
|
||||||
|
/// <param name="second">±초(양수: 미래, 음수: 과거)</param>
|
||||||
|
/// <param name="orderAsc">true: 오래된 시간부터, false: 최근 시간부터</param>
|
||||||
|
/// <param name="limit">최대 조회 개수(0이면 제한 없음)</param>
|
||||||
|
/// <returns>조회된 데이터 리스트</returns>
|
||||||
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0)
|
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0)
|
||||||
{
|
{
|
||||||
bool isMainThread = PlayerLoopHelper.IsMainThread;
|
bool isMainThread = PlayerLoopHelper.IsMainThread;
|
||||||
@@ -104,25 +121,26 @@ namespace UVC.Factory
|
|||||||
//Debug.Log($"SelectBySecond {query}");
|
//Debug.Log($"SelectBySecond {query}");
|
||||||
var query = queryBuilder.ToString();
|
var query = queryBuilder.ToString();
|
||||||
queryBuilder.Clear();
|
queryBuilder.Clear();
|
||||||
|
// 쿼리 실행 및 결과 반환
|
||||||
return dbConnection.Query<PlaybackSQLiteDataEntity>(query);
|
return dbConnection.Query<PlaybackSQLiteDataEntity>(query);
|
||||||
});
|
});
|
||||||
if (!isMainThread) await UniTask.SwitchToThreadPool();
|
if (!isMainThread) await UniTask.SwitchToThreadPool();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
|
|
||||||
/// second > 0 : selectTime <= data < selectTime + second
|
|
||||||
/// second < 0 : selectTime + second < data <= selectTime
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="selectTime">yyyy-MM-ddTHH:mm:ss.fffZ format string</param>
|
|
||||||
/// <param name="second"></param>
|
|
||||||
/// <param name="orderAsc">true: 오래된 시간이 먼저, false: 최근 시간이 먼저</param>
|
|
||||||
/// <param name="limit"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
///
|
|
||||||
|
|
||||||
StringBuilder queryBuilder = new();
|
StringBuilder queryBuilder = new();
|
||||||
|
/// baseInfo 테이블에서 특정 시간(selectTime) 기준으로 ±second 범위의 데이터를 조회합니다.
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// // 5초 전까지의 데이터 1개를 조회(내림차순)
|
||||||
|
/// var list = await service.SelectBySecondBaseInfo("2024-07-29T12:00:00.000Z", -5);
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selectTime">기준 시간(yyyy-MM-ddTHH:mm:ss.fffZ)</param>
|
||||||
|
/// <param name="second">±초(양수: 미래, 음수: 과거)</param>
|
||||||
|
/// <param name="orderAsc">true: 오래된 시간부터, false: 최근 시간부터</param>
|
||||||
|
/// <param name="limit">최대 조회 개수</param>
|
||||||
|
/// <returns>조회된 데이터 리스트</returns>
|
||||||
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string selectTime, int second, bool orderAsc = false, int limit = 1)
|
public async UniTask<List<PlaybackSQLiteDataEntity>> SelectBySecondBaseInfo(string selectTime, int second, bool orderAsc = false, int limit = 1)
|
||||||
{
|
{
|
||||||
bool isMainThread = PlayerLoopHelper.IsMainThread;
|
bool isMainThread = PlayerLoopHelper.IsMainThread;
|
||||||
@@ -148,6 +166,7 @@ namespace UVC.Factory
|
|||||||
//Debug.Log($"SelectBySecondBaseInfo {query}");
|
//Debug.Log($"SelectBySecondBaseInfo {query}");
|
||||||
var query = queryBuilder.ToString();
|
var query = queryBuilder.ToString();
|
||||||
queryBuilder.Clear();
|
queryBuilder.Clear();
|
||||||
|
// 쿼리 실행 및 결과 반환
|
||||||
return dbConnection.Query<PlaybackSQLiteDataEntity>(query);
|
return dbConnection.Query<PlaybackSQLiteDataEntity>(query);
|
||||||
});
|
});
|
||||||
if (!isMainThread) await UniTask.SwitchToThreadPool();
|
if (!isMainThread) await UniTask.SwitchToThreadPool();
|
||||||
@@ -155,14 +174,25 @@ namespace UVC.Factory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 데이터베이스에서 사용하는 데이터 구조체입니다.
|
||||||
|
/// 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var entity = new PlaybackSQLiteDataEntity {
|
||||||
|
/// data = "센서값",
|
||||||
|
/// timestamp = "2024-07-29T12:00:00.000Z",
|
||||||
|
/// temp = "임시값"
|
||||||
|
/// };
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
public class PlaybackSQLiteDataEntity
|
public class PlaybackSQLiteDataEntity
|
||||||
{
|
{
|
||||||
public string data { get; set; }
|
public string data { get; set; }
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string timestamp { get; set; }
|
public string timestamp { get; set; }
|
||||||
public DateTime timestampHungary { get => DateTimeUtil.UtcStringToKoreaDateTime(timestamp); }
|
// timestampHungary는 timestamp를 DateTime으로 변환한 값입니다.
|
||||||
|
public DateTime timestampHungary { get => DateTimeUtil.UtcStringToHungaryDateTime(timestamp); }
|
||||||
public string temp { get; set; }
|
public string temp { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,34 @@ using UVC.Factory.Playback.UI;
|
|||||||
using UVC.Util;
|
using UVC.Util;
|
||||||
namespace UVC.Factory.Playback
|
namespace UVC.Factory.Playback
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Playback 관련 서비스 클래스입니다.
|
||||||
|
/// - 싱글턴 패턴으로 사용합니다.
|
||||||
|
/// - 재생 데이터 요청, 다운로드, 시간 스케일 조정 등 주요 기능을 제공합니다.
|
||||||
|
///
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 재생 시작 예시
|
||||||
|
/// var itemData = new UIPlaybackListItemData { date = "2024-07-29", time = "13", sqlFileName = "2024-07-29_13.sqlite" };
|
||||||
|
/// await PlaybackService.Instance.StartAsync(itemData);
|
||||||
|
///
|
||||||
|
/// // 재생 목록 데이터 요청 예시
|
||||||
|
/// var data = await PlaybackService.Instance.RequestDataAsync();
|
||||||
|
///
|
||||||
|
/// // 재생 종료 예시
|
||||||
|
/// PlaybackService.Instance.Exit();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// </summary>
|
||||||
public class PlaybackService
|
public class PlaybackService
|
||||||
{
|
{
|
||||||
#region Singleton
|
#region Singleton
|
||||||
|
// 싱글턴 인스턴스. PlaybackService.Instance로 접근합니다.
|
||||||
private static readonly PlaybackService instance = new PlaybackService(new PlaybackRepository());
|
private static readonly PlaybackService instance = new PlaybackService(new PlaybackRepository());
|
||||||
public static PlaybackService Instance => instance;
|
public static PlaybackService Instance => instance;
|
||||||
static PlaybackService() { }
|
static PlaybackService() { }
|
||||||
#endregion
|
#endregion
|
||||||
|
// 재생 데이터가 저장되는 폴더 경로입니다.
|
||||||
public static readonly string PlaybackFolderPath = Path.Combine(Application.persistentDataPath, "playback");//streamingAssetsPath, "playback"); appData 폴더로 변경
|
public static readonly string PlaybackFolderPath = Path.Combine(Application.persistentDataPath, "playback");//streamingAssetsPath, "playback"); appData 폴더로 변경
|
||||||
|
|
||||||
private readonly PlaybackRepository repository;
|
private readonly PlaybackRepository repository;
|
||||||
@@ -31,6 +51,15 @@ namespace UVC.Factory.Playback
|
|||||||
public Action OnExitPlayback;
|
public Action OnExitPlayback;
|
||||||
|
|
||||||
private float timeScale = 1.0f;
|
private float timeScale = 1.0f;
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 시간 스케일(배속)입니다. 1.0f가 기본입니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 재생 속도를 2배로 변경
|
||||||
|
/// PlaybackService.Instance.TimeScale = 2.0f;
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public float TimeScale
|
public float TimeScale
|
||||||
{
|
{
|
||||||
get => timeScale;
|
get => timeScale;
|
||||||
@@ -48,17 +77,49 @@ namespace UVC.Factory.Playback
|
|||||||
|
|
||||||
public Action<float> OnChangeTimeScale;
|
public Action<float> OnChangeTimeScale;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 생성자. 일반적으로 직접 호출하지 않고 싱글턴 인스턴스를 사용합니다.
|
||||||
|
/// </summary>
|
||||||
public PlaybackService(PlaybackRepository repository)
|
public PlaybackService(PlaybackRepository repository)
|
||||||
{
|
{
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 서버에서 재생 가능한 날짜별 데이터 목록을 비동기로 요청합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>성공 시 날짜별 재생 목록 딕셔너리, 실패 시 null</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// var data = await PlaybackService.Instance.RequestDataAsync();
|
||||||
|
/// if (data != null)
|
||||||
|
/// {
|
||||||
|
/// foreach (var date in data.Keys)
|
||||||
|
/// {
|
||||||
|
/// Debug.Log($"날짜: {date}");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public async UniTask<Dictionary<string, Dictionary<string, string>>?> RequestDataAsync()
|
public async UniTask<Dictionary<string, Dictionary<string, string>>?> RequestDataAsync()
|
||||||
{
|
{
|
||||||
Dictionary<string, Dictionary<string, string>>? data = await repository.RequestPlaybackDateList();
|
Dictionary<string, Dictionary<string, string>>? data = await repository.RequestPlaybackDateList();
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생을 위한 기본 정보 데이터를 비동기로 처리합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">날짜(예: "2024-07-29")</param>
|
||||||
|
/// <param name="time">시간(예: "13")</param>
|
||||||
|
/// <param name="fileName">파일명(예: "2024-07-29_13.sqlite")</param>
|
||||||
|
/// <param name="minute">분(기본값: "00")</param>
|
||||||
|
/// <param name="second">초(기본값: "00")</param>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// await PlaybackService.Instance.DispatchBaseInfoData("2024-07-29", "13", "2024-07-29_13.sqlite");
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public async UniTask DispatchBaseInfoData(string date, string time, string fileName, string minute = "00", string second = "00")
|
public async UniTask DispatchBaseInfoData(string date, string time, string fileName, string minute = "00", string second = "00")
|
||||||
{
|
{
|
||||||
await UniTask.RunOnThreadPool(async () =>
|
await UniTask.RunOnThreadPool(async () =>
|
||||||
@@ -87,9 +148,15 @@ namespace UVC.Factory.Playback
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// 실시간 재생 데이터(특정 초 단위)를 비동기로 처리합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="second">0 ~ 3600</param>
|
/// <param name="second">0 ~ 3600 (초 단위)</param>
|
||||||
|
/// <param name="speed">재생 속도</param>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// await PlaybackService.Instance.DispatchRealTimeData(120, 1); // 120초(2분) 위치 데이터 처리
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public async UniTask DispatchRealTimeData(int second, int speed)
|
public async UniTask DispatchRealTimeData(int second, int speed)
|
||||||
{
|
{
|
||||||
await UniTask.RunOnThreadPool(async () =>
|
await UniTask.RunOnThreadPool(async () =>
|
||||||
@@ -117,6 +184,16 @@ namespace UVC.Factory.Playback
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생을 시작합니다. UI를 표시하고 데이터를 세팅합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">재생 목록 아이템 데이터</param>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// var itemData = new UIPlaybackListItemData { date = "2024-07-29", time = "13", sqlFileName = "2024-07-29_13.sqlite" };
|
||||||
|
/// await PlaybackService.Instance.StartAsync(itemData);
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public async Task StartAsync(UIPlaybackListItemData data)
|
public async Task StartAsync(UIPlaybackListItemData data)
|
||||||
{
|
{
|
||||||
timeScale = 1.0f; //기본 시간 스케일 설정
|
timeScale = 1.0f; //기본 시간 스케일 설정
|
||||||
@@ -124,11 +201,44 @@ namespace UVC.Factory.Playback
|
|||||||
await UIPlayback.Instance.SetData(data.date, data.time, data.sqlFileName);
|
await UIPlayback.Instance.SetData(data.date, data.time, data.sqlFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생을 종료합니다. (이벤트 발생)
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// PlaybackService.Instance.Exit();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
OnExitPlayback?.Invoke();
|
OnExitPlayback?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 데이터 파일을 준비(다운로드 및 압축 해제)합니다.
|
||||||
|
/// 이미 파일이 있으면 바로 콜백을 호출합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">날짜(예: "2024-12-05")</param>
|
||||||
|
/// <param name="time">시간(예: "13")</param>
|
||||||
|
/// <param name="fileName">파일명(예: "2024-12-05_0.sqlite.7z")</param>
|
||||||
|
/// <param name="OnProgress">진행 상황 콜백 (진행 바이트, 전체 바이트, 퍼센트)</param>
|
||||||
|
/// <param name="OnComplete">완료 콜백 (에러 메시지, 성공 시 null)</param>
|
||||||
|
/// <returns>다운로드 요청 객체(필요시 Abort 등 제어 가능), 이미 파일이 있으면 null</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 파일 준비 및 다운로드 예시
|
||||||
|
/// PlaybackService.Instance.ReadyData(
|
||||||
|
/// "2024-12-05", "13", "2024-12-05_0.sqlite.7z",
|
||||||
|
/// (progress, total, percent) => Debug.Log($"{progress}/{total} ({percent * 100:F1}%)"),
|
||||||
|
/// (error) => {
|
||||||
|
/// if (string.IsNullOrEmpty(error))
|
||||||
|
/// Debug.Log("파일 준비 완료");
|
||||||
|
/// else
|
||||||
|
/// Debug.LogError($"오류: {error}");
|
||||||
|
/// }
|
||||||
|
/// );
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public HTTPRequest? ReadyData(string date, string time, string fileName, Action<long, long, float> OnProgress, Action<string> OnComplete)
|
public HTTPRequest? ReadyData(string date, string time, string fileName, Action<long, long, float> OnProgress, Action<string> OnComplete)
|
||||||
{
|
{
|
||||||
//date : "2024-12-05"
|
//date : "2024-12-05"
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ using System;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using UVC.Extension;
|
|
||||||
using UVC.UI;
|
using UVC.UI;
|
||||||
using UVC.UI.Loading;
|
|
||||||
|
|
||||||
namespace UVC.Factory.Playback.UI
|
namespace UVC.Factory.Playback.UI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 UI를 관리하는 클래스입니다.
|
||||||
|
/// - UI 요소의 표시/숨김, 버튼/슬라이더 등 UI 이벤트를 처리합니다.
|
||||||
|
/// - 실제 재생 로직은 UIPlaybackController에서 처리합니다.
|
||||||
|
/// </summary>
|
||||||
public class UIPlayback : MonoBehaviour
|
public class UIPlayback : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
// 싱글톤 패턴: 어디서든 UIPlayback.Instance로 접근할 수 있습니다.
|
||||||
private static UIPlayback instance;
|
private static UIPlayback instance;
|
||||||
public static UIPlayback Instance
|
public static UIPlayback Instance
|
||||||
{
|
{
|
||||||
@@ -22,98 +25,129 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UIPlayback 프리팹을 동적으로 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>생성된 UIPlayback 인스턴스</returns>
|
||||||
private static UIPlayback CreateUIPlayBack()
|
private static UIPlayback CreateUIPlayBack()
|
||||||
{
|
{
|
||||||
GameObject prefab = Resources.Load<GameObject>("Prefabs/Factory/Playback/UIPlayback");
|
// Resources 폴더에서 프리팹을 불러와 인스턴스화합니다.
|
||||||
|
GameObject prefab = Resources.Load<GameObject>("Prefabs/UI/Playback/UIPlayback");
|
||||||
GameObject go = GameObject.Instantiate(prefab);
|
GameObject go = GameObject.Instantiate(prefab);
|
||||||
return go.GetComponent<UIPlayback>();
|
return go.GetComponent<UIPlayback>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inspector에서 연결할 UI 컴포넌트들입니다.
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("종료 버튼")]
|
[Tooltip("종료 버튼")]
|
||||||
private Button exitButton;
|
private Button exitButton;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("종료 버튼")]
|
[Tooltip("종료 버튼")]
|
||||||
private TextMeshProUGUI dateTimeTxt0;
|
private TextMeshProUGUI dateTimeTxt0;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("종료 버튼")]
|
[Tooltip("종료 버튼")]
|
||||||
private TextMeshProUGUI dateTimeTxt1;
|
private TextMeshProUGUI dateTimeTxt1;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("play 버튼")]
|
[Tooltip("play 버튼")]
|
||||||
private Button playButton;
|
private Button playButton;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("play 버튼 이미지")]
|
[Tooltip("play 버튼 이미지")]
|
||||||
private Image playButtonImage;
|
private Image playButtonImage;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("play 버튼 이미지 Sprite")]
|
[Tooltip("play 버튼 이미지 Sprite")]
|
||||||
private Sprite playButtonImagePlay;
|
private Sprite playButtonImagePlay;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("play 버튼 Puase 이미지 Sprite")]
|
[Tooltip("play 버튼 Puase 이미지 Sprite")]
|
||||||
private Sprite playButtonImagePause;
|
private Sprite playButtonImagePause;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("Speed Slider")]
|
[Tooltip("Speed Slider")]
|
||||||
private UISliderWithLabel sliderSpeed;
|
private UISliderWithLabel sliderSpeed;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("투명 조절 Slider")]
|
[Tooltip("투명 조절 Slider")]
|
||||||
private SliderWithEvent opacitySlider;
|
private SliderWithEvent opacitySlider;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("Progress Bar")]
|
[Tooltip("Progress Bar")]
|
||||||
private UIPlaybackProgressBar progressBar;
|
private UIPlaybackProgressBar progressBar;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private CanvasGroup canvasGroup;
|
private CanvasGroup canvasGroup;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private UIDragger uiDragger;
|
private UIDragger uiDragger;
|
||||||
|
|
||||||
private bool isPlaying = false;
|
// 컨트롤러: 실제 재생 로직을 담당합니다.
|
||||||
private bool preparingData = false;
|
private UIPlaybackController controller;
|
||||||
|
|
||||||
private string date;
|
// UI 이벤트를 외부(Controller)로 전달하기 위한 이벤트입니다.
|
||||||
private string time;
|
public event Action OnClickExitButton;
|
||||||
private string fileName;
|
public event Action OnClickPlayButton;
|
||||||
|
public event Action<int> OnChangeProgressValue;
|
||||||
|
public event Action<int> OnChangeSpeedValue;
|
||||||
|
public event Action<float> OnChangeOpacityValue;
|
||||||
|
|
||||||
private bool isTick = false;
|
/// <summary>
|
||||||
private bool IsTick
|
/// 오브젝트가 생성될 때 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
private void Awake()
|
||||||
{
|
{
|
||||||
get => isTick;
|
controller = new UIPlaybackController(this);
|
||||||
set
|
Init();
|
||||||
{
|
|
||||||
if (isTick != value)
|
|
||||||
{
|
|
||||||
var temp = isTick;
|
|
||||||
isTick = value;
|
|
||||||
if (!temp && value) OnTimer().Forget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI 이벤트 리스너를 등록합니다.
|
||||||
|
/// </summary>
|
||||||
private void Init()
|
private void Init()
|
||||||
{
|
{
|
||||||
exitButton.onClick.AddListener(OnClickExit);
|
// 버튼 클릭 시 이벤트 발생
|
||||||
playButton.onClick.AddListener(OnClickPlay);
|
exitButton.onClick.AddListener(() => OnClickExitButton?.Invoke());
|
||||||
|
playButton.onClick.AddListener(() => OnClickPlayButton?.Invoke());
|
||||||
|
|
||||||
progressBar.OnChangeValue += OnChangeProgress;
|
// 슬라이더/프로그레스바 값 변경 시 이벤트 발생
|
||||||
sliderSpeed.OnChangeValue += OnChangeSpeed;
|
progressBar.OnChangeValue += (value) => OnChangeProgressValue?.Invoke(value);
|
||||||
opacitySlider.onValueChanged.AddListener(OnValueChangedOpcity);
|
sliderSpeed.OnChangeValue += (value) => OnChangeSpeedValue?.Invoke(value);
|
||||||
|
opacitySlider.onValueChanged.AddListener((value) => OnChangeOpacityValue?.Invoke(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 오브젝트가 파괴될 때 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
exitButton.onClick.RemoveListener(OnClickExit);
|
// 모든 이벤트 리스너 해제
|
||||||
playButton.onClick.RemoveListener(OnClickPlay);
|
exitButton.onClick.RemoveAllListeners();
|
||||||
|
playButton.onClick.RemoveAllListeners();
|
||||||
progressBar.OnChangeValue = null;
|
progressBar.OnChangeValue = null;
|
||||||
sliderSpeed.OnChangeValue = null;
|
sliderSpeed.OnChangeValue = null;
|
||||||
opacitySlider.onValueChanged.RemoveListener(OnValueChangedOpcity);
|
opacitySlider.onValueChanged.RemoveAllListeners();
|
||||||
if (isPlaying) IsTick = false;
|
controller.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI를 화면에 표시합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // UIPlayback을 화면에 띄우는 예시
|
||||||
|
/// UIPlayback.Instance.Show();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public void Show()
|
public void Show()
|
||||||
{
|
{
|
||||||
if (playButton == null) Init();
|
if (playButton == null) Init();
|
||||||
gameObject.SetActive(true);
|
gameObject.SetActive(true);
|
||||||
if (transform.parent == null)
|
if (transform.parent == null)
|
||||||
{
|
{
|
||||||
|
// ModalCanvas에 붙여서 항상 위에 보이도록 설정
|
||||||
var canvases = GameObject.FindObjectsByType<Canvas>(FindObjectsSortMode.None);
|
var canvases = GameObject.FindObjectsByType<Canvas>(FindObjectsSortMode.None);
|
||||||
foreach (var canvas in canvases)
|
foreach (var canvas in canvases)
|
||||||
{
|
{
|
||||||
@@ -127,146 +161,118 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI를 화면에서 숨깁니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // UIPlayback을 숨기는 예시
|
||||||
|
/// UIPlayback.Instance.Hide();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public void Hide()
|
public void Hide()
|
||||||
{
|
{
|
||||||
UpdateTimeScale(1);
|
controller.UpdateTimeScale(1);
|
||||||
IsTick = false;
|
controller.IsTick = false;
|
||||||
gameObject.SetActive(false);
|
gameObject.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClickExit()
|
/// <summary>
|
||||||
|
/// 재생에 필요한 데이터를 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">날짜(예: "2024-07-29")</param>
|
||||||
|
/// <param name="time">시간(초 단위 문자열, 예: "3600")</param>
|
||||||
|
/// <param name="fileName">파일명</param>
|
||||||
|
/// <returns>비동기 작업(UniTask)</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// await UIPlayback.Instance.SetData("2024-07-29", "3600", "sample.sqlite");
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public UniTask SetData(string date, string time, string fileName)
|
||||||
{
|
{
|
||||||
UILoading.Show();
|
return controller.SetData(date, time, fileName);
|
||||||
isPlaying = false;
|
|
||||||
UpdatePlayState();
|
|
||||||
Hide();
|
|
||||||
PlaybackService.Instance.Exit();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClickPlay()
|
#region View Update Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 날짜/시간 텍스트를 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">표시할 날짜 문자열</param>
|
||||||
|
public void UpdateDateTime(string date)
|
||||||
{
|
{
|
||||||
isPlaying = !isPlaying;
|
dateTimeTxt0.text = dateTimeTxt1.text = date;
|
||||||
UpdatePlayState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChangeProgress(int newValue)
|
/// <summary>
|
||||||
|
/// 재생 진행 바를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">전체 재생 시간(초)</param>
|
||||||
|
public void InitProgressBar(int time) => progressBar.Init(time);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 속도 슬라이더를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void InitSpeedSlider() => sliderSpeed.Init();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생/일시정지 버튼 이미지를 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isPlaying">재생 중이면 true, 아니면 false</param>
|
||||||
|
public void UpdatePlayButtonState(bool isPlaying)
|
||||||
{
|
{
|
||||||
ChangePlayTime().Forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChangeSpeed(int newValue)
|
|
||||||
{
|
|
||||||
if (isPlaying)
|
|
||||||
{
|
|
||||||
//if (Time.timeScale != sliderSpeed.Value) UpdateTimeScale(sliderSpeed.Value);
|
|
||||||
UpdateTimeScale(sliderSpeed.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateTimeScale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnValueChangedOpcity(float newValue)
|
|
||||||
{
|
|
||||||
canvasGroup.alpha = opacitySlider.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private async UniTaskVoid ChangePlayTime()
|
|
||||||
{
|
|
||||||
|
|
||||||
bool tempIsPlaing = isPlaying;
|
|
||||||
isPlaying = false;
|
|
||||||
int newSecond = (int)progressBar.Value;
|
|
||||||
if (newSecond == progressBar.MaxValue)
|
|
||||||
{
|
|
||||||
newSecond -= 60;
|
|
||||||
progressBar.Value = newSecond;
|
|
||||||
}
|
|
||||||
preparingData = true;
|
|
||||||
progressBar.Interactable = !preparingData;
|
|
||||||
IsTick = false;
|
|
||||||
UILoading.Show();
|
|
||||||
UpdatePlayState();
|
|
||||||
await UniTask.WaitForSeconds(0.5f);
|
|
||||||
int minute = (int)newSecond / 60;
|
|
||||||
int seconds = (int)newSecond % 60;
|
|
||||||
await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName, minute.ToString("00"), seconds.ToString("00"));
|
|
||||||
preparingData = false;
|
|
||||||
progressBar.Interactable = !preparingData;
|
|
||||||
if (isPlaying != tempIsPlaing)
|
|
||||||
{
|
|
||||||
isPlaying = tempIsPlaing;
|
|
||||||
UpdatePlayState();
|
|
||||||
}
|
|
||||||
UILoading.Hide();
|
|
||||||
await UniTask.WaitForSeconds(0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask SetData(string date, string time, string fileName)
|
|
||||||
{
|
|
||||||
Init();
|
|
||||||
this.date = date;
|
|
||||||
this.time = time;
|
|
||||||
this.fileName = fileName;
|
|
||||||
Debug.Log($"UIPlayback SetData {date} {time}");
|
|
||||||
|
|
||||||
int timeInt = int.Parse(time);
|
|
||||||
dateTimeTxt0.text = dateTimeTxt1.text = date.Substring(2).Replace("-", ".");
|
|
||||||
progressBar.Init(timeInt);
|
|
||||||
sliderSpeed.Init();
|
|
||||||
|
|
||||||
UpdateTimeScale(1);
|
|
||||||
canvasGroup.alpha = opacitySlider.value = 1;
|
|
||||||
preparingData = true;
|
|
||||||
progressBar.Interactable = !preparingData;
|
|
||||||
isPlaying = false;
|
|
||||||
|
|
||||||
UpdatePlayState();
|
|
||||||
|
|
||||||
await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName);
|
|
||||||
preparingData = false;
|
|
||||||
progressBar.Interactable = !preparingData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePlayState()
|
|
||||||
{
|
|
||||||
playButton.enabled = false;
|
|
||||||
progressBar.enabled = false;
|
|
||||||
playButtonImage.sprite = isPlaying ? playButtonImagePause : playButtonImagePlay;
|
playButtonImage.sprite = isPlaying ? playButtonImagePause : playButtonImagePlay;
|
||||||
IsTick = isPlaying;
|
|
||||||
progressBar.enabled = true;
|
|
||||||
playButton.enabled = true;
|
|
||||||
OnChangeSpeed(sliderSpeed.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
private async UniTaskVoid OnTimer()
|
/// 재생 진행 바의 값을 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">설정할 값(초)</param>
|
||||||
|
public void SetProgressValue(int value)
|
||||||
{
|
{
|
||||||
if (progressBar.Value == progressBar.MaxValue)
|
if (progressBar.Value != value)
|
||||||
{
|
{
|
||||||
if (isPlaying) OnClickPlay();
|
progressBar.Value = value;
|
||||||
return;
|
|
||||||
}
|
|
||||||
progressBar.Value += 1;
|
|
||||||
//PlaybackService.Instance.DispatchingTimelineEvent = false;
|
|
||||||
PlaybackService.Instance.DispatchRealTimeData(progressBar.Value, sliderSpeed.Value).Forget();
|
|
||||||
|
|
||||||
if (isTick)
|
|
||||||
{
|
|
||||||
//PlaybackService.Instance.DispatchingTimelineEvent = true;
|
|
||||||
await UniTask.Delay(TimeSpan.FromMilliseconds(1000 / sliderSpeed.Value));
|
|
||||||
OnTimer().Forget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTimeScale(float timeScale)
|
/// <summary>
|
||||||
|
/// 현재 재생 위치(초)를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
public int GetProgressValue() => progressBar.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 전체 재생 시간(초)를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
public int GetProgressMaxValue() => progressBar.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 선택된 재생 속도를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
public int GetSpeedValue() => sliderSpeed.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI의 투명도를 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">0(완전 투명) ~ 1(완전 불투명)</param>
|
||||||
|
public void SetOpacity(float value)
|
||||||
{
|
{
|
||||||
PlaybackService.Instance.TimeScale = timeScale;
|
canvasGroup.alpha = value;
|
||||||
|
opacitySlider.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI의 상호작용 가능 여부를 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isInteractable">true면 버튼/슬라이더 사용 가능</param>
|
||||||
|
public void SetUIInteractable(bool isInteractable)
|
||||||
|
{
|
||||||
|
playButton.enabled = isInteractable;
|
||||||
|
progressBar.Interactable = isInteractable;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
283
Assets/Scripts/UVC/Factory/Playback/UI/UIPlaybackController.cs
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using UVC.UI.Loading;
|
||||||
|
|
||||||
|
namespace UVC.Factory.Playback.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UIPlaybackController는 UIPlayback(View)에서 발생하는 이벤트를 받아
|
||||||
|
/// 실제 재생, 일시정지, 데이터 준비 등 비즈니스 로직을 처리하는 컨트롤러입니다.
|
||||||
|
///
|
||||||
|
/// <b>예시: UIPlayback과의 연결</b>
|
||||||
|
/// <code>
|
||||||
|
/// // UIPlayback에서 컨트롤러를 생성할 때 View를 넘겨줍니다.
|
||||||
|
/// var controller = new UIPlaybackController(this);
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
public class UIPlaybackController : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// View 역할을 하는 UIPlayback 참조입니다.
|
||||||
|
/// </summary>
|
||||||
|
private readonly UIPlayback view;
|
||||||
|
|
||||||
|
// 재생 중 여부
|
||||||
|
private bool isPlaying = false;
|
||||||
|
// 데이터 준비 중 여부
|
||||||
|
private bool preparingData = false;
|
||||||
|
// 재생에 필요한 정보
|
||||||
|
private string date;
|
||||||
|
private string time;
|
||||||
|
private string fileName;
|
||||||
|
// 타이머 동작 여부
|
||||||
|
private bool isTick = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 타이머 동작 여부를 외부에서 제어할 수 있습니다.
|
||||||
|
/// true로 설정하면 내부적으로 OnTimer()가 실행됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 타이머를 시작하려면
|
||||||
|
/// controller.IsTick = true;
|
||||||
|
/// // 타이머를 멈추려면
|
||||||
|
/// controller.IsTick = false;
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public bool IsTick
|
||||||
|
{
|
||||||
|
get => isTick;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isTick != value)
|
||||||
|
{
|
||||||
|
isTick = value;
|
||||||
|
if (isTick) OnTimer().Forget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 생성자에서 View와 이벤트를 연결합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="view">UIPlayback 인스턴스</param>
|
||||||
|
public UIPlaybackController(UIPlayback view)
|
||||||
|
{
|
||||||
|
this.view = view;
|
||||||
|
SubscribeToViewEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// View에서 발생하는 이벤트를 컨트롤러의 메서드와 연결합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void SubscribeToViewEvents()
|
||||||
|
{
|
||||||
|
view.OnClickExitButton += OnClickExit;
|
||||||
|
view.OnClickPlayButton += OnClickPlay;
|
||||||
|
view.OnChangeProgressValue += OnChangeProgress;
|
||||||
|
view.OnChangeSpeedValue += OnChangeSpeed;
|
||||||
|
view.OnChangeOpacityValue += OnValueChangedOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 컨트롤러가 더 이상 필요 없을 때 이벤트 연결을 해제합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (isPlaying) IsTick = false;
|
||||||
|
view.OnClickExitButton -= OnClickExit;
|
||||||
|
view.OnClickPlayButton -= OnClickPlay;
|
||||||
|
view.OnChangeProgressValue -= OnChangeProgress;
|
||||||
|
view.OnChangeSpeedValue -= OnChangeSpeed;
|
||||||
|
view.OnChangeOpacityValue -= OnValueChangedOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생에 필요한 데이터를 설정하고 UI를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">날짜(예: "2024-07-29")</param>
|
||||||
|
/// <param name="time">시간(초 단위 문자열, 예: "3600")</param>
|
||||||
|
/// <param name="fileName">파일명</param>
|
||||||
|
/// <returns>비동기 작업(UniTask)</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 재생 데이터를 설정하는 예시
|
||||||
|
/// await controller.SetData("2024-07-29", "3600", "sample.sqlite");
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public async UniTask SetData(string date, string time, string fileName)
|
||||||
|
{
|
||||||
|
this.date = date;
|
||||||
|
this.time = time;
|
||||||
|
this.fileName = fileName;
|
||||||
|
|
||||||
|
int timeInt = int.Parse(time);
|
||||||
|
view.UpdateDateTime(date.Substring(2).Replace("-", "."));
|
||||||
|
view.InitProgressBar(timeInt);
|
||||||
|
view.InitSpeedSlider();
|
||||||
|
view.SetOpacity(1);
|
||||||
|
|
||||||
|
UpdateTimeScale(1);
|
||||||
|
preparingData = true;
|
||||||
|
view.SetUIInteractable(!preparingData);
|
||||||
|
isPlaying = false;
|
||||||
|
UpdatePlayState();
|
||||||
|
|
||||||
|
// 실제 데이터 로딩 (비동기)
|
||||||
|
await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName);
|
||||||
|
preparingData = false;
|
||||||
|
view.SetUIInteractable(!preparingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 종료 버튼 클릭 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// UI를 숨기고, 재생 상태를 초기화하며, PlaybackService에 종료를 알립니다.
|
||||||
|
/// </remarks>
|
||||||
|
private void OnClickExit()
|
||||||
|
{
|
||||||
|
UILoading.Show();
|
||||||
|
isPlaying = false;
|
||||||
|
UpdatePlayState();
|
||||||
|
view.Hide();
|
||||||
|
PlaybackService.Instance.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생/일시정지 버튼 클릭 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 데이터 준비 중에는 동작하지 않습니다.
|
||||||
|
/// </remarks>
|
||||||
|
private void OnClickPlay()
|
||||||
|
{
|
||||||
|
if (preparingData) return;
|
||||||
|
isPlaying = !isPlaying;
|
||||||
|
UpdatePlayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// <summary>
|
||||||
|
/// 재생 위치(프로그레스바) 변경 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newValue">변경된 위치(초)</param>
|
||||||
|
private void OnChangeProgress(int newValue)
|
||||||
|
{
|
||||||
|
ChangePlayTime().Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 속도 변경 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newValue">변경된 속도 값</param>
|
||||||
|
private void OnChangeSpeed(int newValue)
|
||||||
|
{
|
||||||
|
UpdateTimeScale(isPlaying ? view.GetSpeedValue() : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 투명도 슬라이더 변경 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newValue">0~1 사이의 투명도 값</param>
|
||||||
|
private void OnValueChangedOpacity(float newValue)
|
||||||
|
{
|
||||||
|
view.SetOpacity(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생/일시정지 상태에 따라 UI와 타이머를 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdatePlayState()
|
||||||
|
{
|
||||||
|
view.UpdatePlayButtonState(isPlaying);
|
||||||
|
IsTick = isPlaying;
|
||||||
|
OnChangeSpeed(view.GetSpeedValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 위치를 변경할 때 호출됩니다.
|
||||||
|
/// 데이터 준비, UI 비활성화, 로딩 표시 등 처리 후 재생 위치를 이동합니다.
|
||||||
|
/// </summary>
|
||||||
|
private async UniTaskVoid ChangePlayTime()
|
||||||
|
{
|
||||||
|
bool tempIsPlaying = isPlaying;
|
||||||
|
isPlaying = false;
|
||||||
|
UpdatePlayState();
|
||||||
|
|
||||||
|
int newSecond = view.GetProgressValue();
|
||||||
|
if (newSecond == view.GetProgressMaxValue())
|
||||||
|
{
|
||||||
|
newSecond -= 60;
|
||||||
|
view.SetProgressValue(newSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
preparingData = true;
|
||||||
|
view.SetUIInteractable(!preparingData);
|
||||||
|
IsTick = false;
|
||||||
|
UILoading.Show();
|
||||||
|
|
||||||
|
|
||||||
|
int minute = newSecond / 60;
|
||||||
|
int seconds = newSecond % 60;
|
||||||
|
// 새로운 위치로 데이터 요청
|
||||||
|
await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName, minute.ToString("00"), seconds.ToString("00"));
|
||||||
|
|
||||||
|
preparingData = false;
|
||||||
|
view.SetUIInteractable(!preparingData);
|
||||||
|
|
||||||
|
if (tempIsPlaying)
|
||||||
|
{
|
||||||
|
isPlaying = true;
|
||||||
|
UpdatePlayState();
|
||||||
|
}
|
||||||
|
UILoading.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 중일 때 일정 간격으로 호출되어 진행 바를 업데이트합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// IsTick이 true일 때만 동작합니다.
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 타이머를 시작하려면
|
||||||
|
/// controller.IsTick = true;
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
private async UniTaskVoid OnTimer()
|
||||||
|
{
|
||||||
|
if (view.GetProgressValue() >= view.GetProgressMaxValue())
|
||||||
|
{
|
||||||
|
if (isPlaying) OnClickPlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.SetProgressValue(view.GetProgressValue() + 1);
|
||||||
|
// 실시간 데이터 요청
|
||||||
|
PlaybackService.Instance.DispatchRealTimeData(view.GetProgressValue(), view.GetSpeedValue()).Forget();
|
||||||
|
|
||||||
|
if (IsTick)
|
||||||
|
{
|
||||||
|
// 재생 속도에 따라 대기 시간 조절
|
||||||
|
await UniTask.Delay(TimeSpan.FromMilliseconds(1000 / view.GetSpeedValue()));
|
||||||
|
if (IsTick) OnTimer().Forget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 속도를 PlaybackService에 반영합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeScale">적용할 재생 속도</param>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 재생 속도를 2배로 변경
|
||||||
|
/// controller.UpdateTimeScale(2f);
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
internal void UpdateTimeScale(float timeScale)
|
||||||
|
{
|
||||||
|
PlaybackService.Instance.TimeScale = timeScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eedd46434a3229b479a141df871ae2ae
|
||||||
@@ -14,20 +14,55 @@ using UVC.Util;
|
|||||||
|
|
||||||
namespace UVC.Factory.Playback.UI
|
namespace UVC.Factory.Playback.UI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 목록의 각 아이템(1시간 단위 파일)을 나타내는 UI 컴포넌트입니다.
|
||||||
|
/// 다운로드, 삭제, 선택 등 다양한 상태를 관리합니다.
|
||||||
|
///
|
||||||
|
/// <para>샘플 사용법:</para>
|
||||||
|
/// <code>
|
||||||
|
/// // 1. 풀에서 아이템 생성 및 초기화
|
||||||
|
/// var itemData = new UIPlaybackListItemData {
|
||||||
|
/// date = "2024-07-29",
|
||||||
|
/// time = "13",
|
||||||
|
/// zipFileName = "2024-07-29_13.zip",
|
||||||
|
/// sqlFileName = "2024-07-29_13.sql"
|
||||||
|
/// };
|
||||||
|
/// var item = UIPlaybackListItem.CreateFromPool(parentTransform);
|
||||||
|
/// item.Init(itemData);
|
||||||
|
///
|
||||||
|
/// // 2. 선택 이벤트 등록
|
||||||
|
/// item.OnSelect = (data, selected) => {
|
||||||
|
/// Debug.Log($"{data.time}시 아이템이 {(selected ? "선택됨" : "선택 해제됨")}");
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // 3. 상태 변경 이벤트 등록
|
||||||
|
/// item.OnChangeStatus = (data, status) => {
|
||||||
|
/// Debug.Log($"{data.time}시 아이템 상태: {status}");
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // 4. 다운로드 버튼 클릭(직접 호출 예시)
|
||||||
|
/// // item.SendMessage("onClickDownload");
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
public enum UIPlaybackListItemStatus
|
public enum UIPlaybackListItemStatus
|
||||||
{
|
{
|
||||||
|
/// <summary>기본 상태(다운로드 전)</summary>
|
||||||
Default,
|
Default,
|
||||||
|
/// <summary>다운로드 중</summary>
|
||||||
Downloading,
|
Downloading,
|
||||||
|
/// <summary>다운로드 완료</summary>
|
||||||
Downloaded
|
Downloaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 목록 아이템의 데이터 구조체입니다.
|
||||||
|
/// </summary>
|
||||||
public class UIPlaybackListItemData
|
public class UIPlaybackListItemData
|
||||||
{
|
{
|
||||||
public string date = "";
|
public string date = ""; // 날짜 (예: "2024-07-29")
|
||||||
public string time = "";
|
public string time = ""; // 시간 (예: "13")
|
||||||
public string zipFileName = "";
|
public string zipFileName = ""; // ZIP 파일명
|
||||||
public string sqlFileName = "";
|
public string sqlFileName = ""; // SQL 파일명
|
||||||
public UIPlaybackListItemStatus status = UIPlaybackListItemStatus.Default;
|
public UIPlaybackListItemStatus status = UIPlaybackListItemStatus.Default;
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -36,13 +71,28 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 목록의 각 아이템을 관리하는 UI 컴포넌트입니다.
|
||||||
|
/// - 풀링, 상태 관리, 다운로드/삭제/중지 버튼, 선택 이벤트 등을 제공합니다.
|
||||||
|
/// </summary>
|
||||||
public class UIPlaybackListItem : UnityEngine.MonoBehaviour, IPointerClickHandler
|
public class UIPlaybackListItem : UnityEngine.MonoBehaviour, IPointerClickHandler
|
||||||
{
|
{
|
||||||
|
// 오브젝트 풀: 아이템을 효율적으로 재사용하기 위한 풀입니다.
|
||||||
protected static ItemPool<UIPlaybackListItem> pool;
|
protected static ItemPool<UIPlaybackListItem> pool;
|
||||||
|
|
||||||
public static readonly string PrefabPath = "Prefabs/Factory/Playback/UIPlaybackListItem";
|
/// <summary>
|
||||||
|
/// 프리팹 경로(Resources 폴더 기준)
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string PrefabPath = "Prefabs/UI/Playback/UIPlaybackListItem";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 풀에서 아이템을 생성(또는 재사용)합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">부모 Transform</param>
|
||||||
|
/// <returns>UIPlaybackListItem 인스턴스</returns>
|
||||||
|
/// <example>
|
||||||
|
/// var item = UIPlaybackListItem.CreateFromPool(parentTransform);
|
||||||
|
/// </example>
|
||||||
public static UIPlaybackListItem CreateFromPool(Transform parent)
|
public static UIPlaybackListItem CreateFromPool(Transform parent)
|
||||||
{
|
{
|
||||||
if (pool == null)
|
if (pool == null)
|
||||||
@@ -53,6 +103,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
return pool.GetItem(true, parent);
|
return pool.GetItem(true, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된(화면에 보이는) 아이템 목록
|
||||||
|
/// </summary>
|
||||||
public static List<UIPlaybackListItem> ActiveItems
|
public static List<UIPlaybackListItem> ActiveItems
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -62,6 +115,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 다운로드 중인 아이템 목록
|
||||||
|
/// </summary>
|
||||||
public static List<UIPlaybackListItem> DownloadingItems
|
public static List<UIPlaybackListItem> DownloadingItems
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -70,6 +126,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 아이템을 풀에 반환(비활성화)합니다.
|
||||||
|
/// </summary>
|
||||||
public static void ReleaseAll()
|
public static void ReleaseAll()
|
||||||
{
|
{
|
||||||
if (pool != null)
|
if (pool != null)
|
||||||
@@ -79,6 +138,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 아이템을 풀에 반환하고, 재활용 목록도 비웁니다.
|
||||||
|
/// </summary>
|
||||||
public static void ClearAll()
|
public static void ClearAll()
|
||||||
{
|
{
|
||||||
if (pool != null)
|
if (pool != null)
|
||||||
@@ -89,6 +151,7 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- UI 요소들 (Unity 에디터에서 할당) ---
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private Image loadingImage;
|
private Image loadingImage;
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
@@ -104,13 +167,26 @@ namespace UVC.Factory.Playback.UI
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
private TextMeshProUGUI text;
|
private TextMeshProUGUI text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템이 선택될 때 호출되는 콜백 (data, 선택여부)
|
||||||
|
/// </summary>
|
||||||
public Action<UIPlaybackListItemData, bool> OnSelect { get; set; }
|
public Action<UIPlaybackListItemData, bool> OnSelect { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템 상태가 변경될 때 호출되는 콜백 (data, 변경된 상태)
|
||||||
|
/// </summary>
|
||||||
public Action<UIPlaybackListItemData, UIPlaybackListItemStatus> OnChangeStatus { get; set; }
|
public Action<UIPlaybackListItemData, UIPlaybackListItemStatus> OnChangeStatus { get; set; }
|
||||||
|
|
||||||
private UIPlaybackListItemData data;
|
private UIPlaybackListItemData data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 이 아이템의 데이터
|
||||||
|
/// </summary>
|
||||||
public UIPlaybackListItemData Data { get => data; }
|
public UIPlaybackListItemData Data { get => data; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템의 현재 상태 (Default/Downloading/Downloaded)
|
||||||
|
/// </summary>
|
||||||
public UIPlaybackListItemStatus Status
|
public UIPlaybackListItemStatus Status
|
||||||
{
|
{
|
||||||
get => data.status;
|
get => data.status;
|
||||||
@@ -125,8 +201,12 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private bool selected = false;
|
private bool selected = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템이 선택되었는지 여부
|
||||||
|
/// </summary>
|
||||||
public bool Selected
|
public bool Selected
|
||||||
{
|
{
|
||||||
get => selected;
|
get => selected;
|
||||||
@@ -140,10 +220,17 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 다운로드 요청 객체 (다운로드 중일 때만 사용)
|
||||||
private HTTPRequest? downloadRequest;
|
private HTTPRequest? downloadRequest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템을 초기화합니다. (데이터 바인딩 및 버튼 이벤트 등록)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">초기화할 데이터</param>
|
||||||
|
/// <example>
|
||||||
|
/// var item = UIPlaybackListItem.CreateFromPool(parent);
|
||||||
|
/// item.Init(data);
|
||||||
|
/// </example>
|
||||||
public void Init(UIPlaybackListItemData data)
|
public void Init(UIPlaybackListItemData data)
|
||||||
{
|
{
|
||||||
transform.localScale = Vector3.one;
|
transform.localScale = Vector3.one;
|
||||||
@@ -158,6 +245,10 @@ namespace UVC.Factory.Playback.UI
|
|||||||
ChangeStatus();
|
ChangeStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운로드 버튼 클릭 시 호출됩니다.
|
||||||
|
/// - 용량 체크, 동시 다운로드 제한, 다운로드 시작 등 처리
|
||||||
|
/// </summary>
|
||||||
private void onClickDownload()
|
private void onClickDownload()
|
||||||
{
|
{
|
||||||
if (Status == UIPlaybackListItemStatus.Downloading) return;
|
if (Status == UIPlaybackListItemStatus.Downloading) return;
|
||||||
@@ -179,6 +270,8 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
Status = UIPlaybackListItemStatus.Downloading;
|
Status = UIPlaybackListItemStatus.Downloading;
|
||||||
|
|
||||||
|
// 실제 다운로드 요청 (진행률/완료/에러 콜백 처리)
|
||||||
downloadRequest = PlaybackService.Instance.ReadyData(data.date, data.time, data.zipFileName,
|
downloadRequest = PlaybackService.Instance.ReadyData(data.date, data.time, data.zipFileName,
|
||||||
(long read, long total, float percent) =>
|
(long read, long total, float percent) =>
|
||||||
{
|
{
|
||||||
@@ -206,6 +299,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운로드/삭제 시 파일을 실제로 삭제합니다.
|
||||||
|
/// </summary>
|
||||||
private void deleteFile()
|
private void deleteFile()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -231,6 +327,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다운로드 중지 버튼 클릭 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
private void onClickStop()
|
private void onClickStop()
|
||||||
{
|
{
|
||||||
if (downloadRequest != null && Status == UIPlaybackListItemStatus.Downloading)
|
if (downloadRequest != null && Status == UIPlaybackListItemStatus.Downloading)
|
||||||
@@ -243,23 +342,36 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 삭제 버튼 클릭 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
private void onClickDelete()
|
private void onClickDelete()
|
||||||
{
|
{
|
||||||
deleteFile();
|
deleteFile();
|
||||||
Status = UIPlaybackListItemStatus.Default;
|
Status = UIPlaybackListItemStatus.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템을 클릭하면 선택/해제 상태가 토글됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventData">이벤트 데이터</param>
|
||||||
public void OnPointerClick(PointerEventData eventData)
|
public void OnPointerClick(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
Selected = !selected;
|
Selected = !selected;
|
||||||
if (OnSelect != null) OnSelect.Invoke(data, Selected);
|
if (OnSelect != null) OnSelect.Invoke(data, Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 풀에 반환될 때 호출 (이벤트 해제 등)
|
||||||
|
/// </summary>
|
||||||
public void Release()
|
public void Release()
|
||||||
{
|
{
|
||||||
OnDestroy();
|
OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 오브젝트가 파괴될 때 호출 (이벤트 해제)
|
||||||
|
/// </summary>
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
downloadButton.onClick.RemoveListener(onClickDownload);
|
downloadButton.onClick.RemoveListener(onClickDownload);
|
||||||
@@ -270,6 +382,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
transform.SetParent(null);
|
transform.SetParent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 상태에 따라 UI를 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
private void ChangeStatus()
|
private void ChangeStatus()
|
||||||
{
|
{
|
||||||
downloadText.text = "";
|
downloadText.text = "";
|
||||||
|
|||||||
@@ -13,11 +13,43 @@ using UVC.Util;
|
|||||||
|
|
||||||
namespace UVC.Factory.Playback.UI
|
namespace UVC.Factory.Playback.UI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 📅 재생 목록(Playback List)을 날짜별로 보여주고, 원하는 시간대의 파일을 선택할 수 있는 모달 창입니다.
|
||||||
|
/// - 날짜 드롭다운, 시간별 리스트, 다운로드 상태, 선택/확인 버튼 등을 제공합니다.
|
||||||
|
///
|
||||||
|
/// <para>샘플 사용법:</para>
|
||||||
|
/// <code>
|
||||||
|
/// // 1. 모달을 띄우는 코드 예시
|
||||||
|
/// var content = new ModalContent(UIPlaybackListModal.PrefabPath)
|
||||||
|
/// {
|
||||||
|
/// Title = "Playback List",
|
||||||
|
/// ConfirmButtonText = "Play",
|
||||||
|
/// ShowCancelButton = false
|
||||||
|
/// };
|
||||||
|
/// // 모달을 열고, 사용자가 선택한 결과를 기다립니다.
|
||||||
|
/// var result = await Modal.Open<UIPlaybackListItemData>(content);
|
||||||
|
/// if (result != null)
|
||||||
|
/// {
|
||||||
|
/// Debug.Log($"선택된 파일: {result.date} {result.time}시");
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
[RequireComponent(typeof(CanvasGroup))]
|
[RequireComponent(typeof(CanvasGroup))]
|
||||||
public class UIPlaybackListModal : ModalView
|
public class UIPlaybackListModal : ModalView
|
||||||
{
|
{
|
||||||
public static new readonly string PrefabPath = "Prefabs/Factory/Playback/UIPlaybackListModal";
|
/// <summary>
|
||||||
|
/// 이 모달의 프리팹 경로(Resources 폴더 기준)
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string PrefabPath = "Prefabs/UI/Playback/UIPlaybackListModal";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 프리팹에서 모달을 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">부모 Transform (생략 가능)</param>
|
||||||
|
/// <returns>UIPlaybackListModal 인스턴스</returns>
|
||||||
|
/// <example>
|
||||||
|
/// var modal = UIPlaybackListModal.CreateFromPrefab(parentTransform);
|
||||||
|
/// </example>
|
||||||
public static UIPlaybackListModal CreateFromPrefab(Transform parent = null)
|
public static UIPlaybackListModal CreateFromPrefab(Transform parent = null)
|
||||||
{
|
{
|
||||||
GameObject prefab = Resources.Load<GameObject>(PrefabPath);
|
GameObject prefab = Resources.Load<GameObject>(PrefabPath);
|
||||||
@@ -26,26 +58,40 @@ namespace UVC.Factory.Playback.UI
|
|||||||
return modal;
|
return modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 날짜별로 시간대 파일 목록을 저장하는 딕셔너리
|
||||||
private Dictionary<string, List<UIPlaybackListItemData>>? data;
|
private Dictionary<string, List<UIPlaybackListItemData>>? data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '확인' 버튼을 누를 수 있는지 여부
|
||||||
|
/// (다운로드 완료된 항목이 선택되어 있고, 현재 다운로드 중인 항목이 없을 때만 true)
|
||||||
|
/// </summary>
|
||||||
public bool IsOkable => (selectedItem != null && selectedItem.status == UIPlaybackListItemStatus.Downloaded && UIPlaybackListItem.DownloadingItems.Count == 0);
|
public bool IsOkable => (selectedItem != null && selectedItem.status == UIPlaybackListItemStatus.Downloaded && UIPlaybackListItem.DownloadingItems.Count == 0);
|
||||||
|
|
||||||
|
// 현재 선택된 아이템 데이터
|
||||||
private UIPlaybackListItemData? selectedItem = null;
|
private UIPlaybackListItemData? selectedItem = null;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private TMP_Dropdown dropdownDate;
|
private TMP_Dropdown dropdownDate; // 날짜 선택 드롭다운
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private ScrollRect scrollRectTime;
|
private ScrollRect scrollRectTime; // 시간대 리스트 스크롤 영역
|
||||||
|
|
||||||
|
// 모달이 닫힐 때 반환할 결과 데이터
|
||||||
private UIPlaybackListItemData? resultData = null;
|
private UIPlaybackListItemData? resultData = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모달이 열릴 때 호출됩니다. (비동기)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">모달에 표시할 내용/설정</param>
|
||||||
public override async UniTask OnOpen(ModalContent content)
|
public override async UniTask OnOpen(ModalContent content)
|
||||||
{
|
{
|
||||||
await base.OnOpen(content); // 부모의 OnOpen을 먼저 호출해서 기본 UI를 설정해요.
|
await base.OnOpen(content); // 부모의 OnOpen을 먼저 호출해서 기본 UI를 설정해요.
|
||||||
|
|
||||||
initContent();
|
initContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모달이 닫힐 때 결과로 반환할 데이터를 돌려줍니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>선택된 UIPlaybackListItemData 또는 null</returns>
|
||||||
public override object? GetResult()
|
public override object? GetResult()
|
||||||
{
|
{
|
||||||
if (data != null) data.Clear();
|
if (data != null) data.Clear();
|
||||||
@@ -53,29 +99,40 @@ namespace UVC.Factory.Playback.UI
|
|||||||
return resultData;
|
return resultData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모달이 닫힐 때 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
public override async UniTask OnClose(ModalContent content)
|
public override async UniTask OnClose(ModalContent content)
|
||||||
{
|
{
|
||||||
await base.OnClose(content);
|
await base.OnClose(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모달의 내용을 초기화합니다. (날짜/시간대 데이터 불러오기)
|
||||||
|
/// </summary>
|
||||||
private async void initContent()
|
private async void initContent()
|
||||||
{
|
{
|
||||||
confirmButton.interactable = false;
|
confirmButton.interactable = false;
|
||||||
|
|
||||||
|
// 서버에서 날짜/시간대 목록을 받아옵니다.
|
||||||
Dictionary<string, Dictionary<string, string>>? data = await PlaybackService.Instance.RequestDataAsync();
|
Dictionary<string, Dictionary<string, string>>? data = await PlaybackService.Instance.RequestDataAsync();
|
||||||
dropdownDate.onValueChanged.AddListener(DropDownDateChanged);
|
dropdownDate.onValueChanged.AddListener(DropDownDateChanged);
|
||||||
LocalSetData();
|
LocalSetData(); // 로컬 파일 기준으로 먼저 리스트를 만듭니다.
|
||||||
if (data != null) SetData(data);
|
if (data != null) SetData(data); // 서버 데이터가 있으면 반영합니다.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 오브젝트가 파괴될 때 이벤트 해제 및 정리
|
||||||
|
/// </summary>
|
||||||
protected void OnDestroy()
|
protected void OnDestroy()
|
||||||
{
|
{
|
||||||
dropdownDate.onValueChanged.RemoveListener(DropDownDateChanged);
|
dropdownDate.onValueChanged.RemoveListener(DropDownDateChanged);
|
||||||
UIPlaybackListItem.ClearAll();
|
UIPlaybackListItem.ClearAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로컬 저장소에서 날짜/시간대 파일 목록을 읽어와 리스트를 만듭니다.
|
||||||
|
/// </summary>
|
||||||
public void LocalSetData()
|
public void LocalSetData()
|
||||||
{
|
{
|
||||||
Dictionary<string, List<UIPlaybackListItemData>> newData = new Dictionary<string, List<UIPlaybackListItemData>>();
|
Dictionary<string, List<UIPlaybackListItemData>> newData = new Dictionary<string, List<UIPlaybackListItemData>>();
|
||||||
@@ -131,6 +188,10 @@ namespace UVC.Factory.Playback.UI
|
|||||||
dropdownDate.value = -1;
|
dropdownDate.value = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 서버에서 받은 날짜/시간대 데이터를 리스트에 반영합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">서버에서 받은 데이터</param>
|
||||||
public void SetData(Dictionary<string, Dictionary<string, string>> data)
|
public void SetData(Dictionary<string, Dictionary<string, string>> data)
|
||||||
{
|
{
|
||||||
//{
|
//{
|
||||||
@@ -142,7 +203,7 @@ namespace UVC.Factory.Playback.UI
|
|||||||
//}
|
//}
|
||||||
Dictionary<string, Dictionary<string, string>> dataList = new Dictionary<string, Dictionary<string, string>>();
|
Dictionary<string, Dictionary<string, string>> dataList = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
|
||||||
//헝가리 시간으로 변경
|
// 날짜/시간대 정규화
|
||||||
foreach (var keyPair in data)
|
foreach (var keyPair in data)
|
||||||
{
|
{
|
||||||
if (!dataList.ContainsKey(keyPair.Key)) dataList[keyPair.Key] = new Dictionary<string, string>();
|
if (!dataList.ContainsKey(keyPair.Key)) dataList[keyPair.Key] = new Dictionary<string, string>();
|
||||||
@@ -215,8 +276,7 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 서버 데이터 기준으로 추가
|
||||||
|
|
||||||
foreach (var keyPair in dataList)
|
foreach (var keyPair in dataList)
|
||||||
{
|
{
|
||||||
string date = keyPair.Key;
|
string date = keyPair.Key;
|
||||||
@@ -238,7 +298,8 @@ namespace UVC.Factory.Playback.UI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//내림차순 정리
|
|
||||||
|
// 시간 오름차순 정렬
|
||||||
if (newData.ContainsKey(date)) newData[date].Sort((a, b) => int.Parse(a.time) - int.Parse(b.time));
|
if (newData.ContainsKey(date)) newData[date].Sort((a, b) => int.Parse(a.time) - int.Parse(b.time));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,14 +310,17 @@ namespace UVC.Factory.Playback.UI
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 날짜 드롭다운이 변경될 때 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">선택된 인덱스</param>
|
||||||
private void DropDownDateChanged(int value)
|
private void DropDownDateChanged(int value)
|
||||||
{
|
{
|
||||||
|
// 선택된 날짜의 시간대 리스트를 갱신
|
||||||
//dropdownDatePlaceHolder.gameObject.SetActive(value == -1);
|
//dropdownDatePlaceHolder.gameObject.SetActive(value == -1);
|
||||||
string key = dropdownDate.options[dropdownDate.value].text;
|
string key = dropdownDate.options[dropdownDate.value].text;
|
||||||
Debug.Log($"DropDownDateChanged dropdownDate.value:{dropdownDate.value} value:{value} key:{key}");
|
Debug.Log($"DropDownDateChanged dropdownDate.value:{dropdownDate.value} value:{value} key:{key}");
|
||||||
if (data.ContainsKey(key))
|
if (data != null && data.ContainsKey(key))
|
||||||
{
|
{
|
||||||
if (UIPlaybackListItem.ActiveItems.Count > 0) UIPlaybackListItem.ReleaseAll();
|
if (UIPlaybackListItem.ActiveItems.Count > 0) UIPlaybackListItem.ReleaseAll();
|
||||||
List<UIPlaybackListItemData> itemList = data[key];
|
List<UIPlaybackListItemData> itemList = data[key];
|
||||||
@@ -270,11 +334,16 @@ namespace UVC.Factory.Playback.UI
|
|||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
updateButtonStatus();
|
updateButtonStatus();
|
||||||
}
|
}
|
||||||
//scroll move to top
|
|
||||||
scrollRectTime.normalizedPosition = new Vector2(0, 1);
|
|
||||||
|
|
||||||
|
// 스크롤을 맨 위로 이동
|
||||||
|
scrollRectTime.normalizedPosition = new Vector2(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 시간대 아이템이 선택될 때 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">선택된 아이템 데이터</param>
|
||||||
|
/// <param name="selected">선택 여부</param>
|
||||||
private void OnItemSelect(UIPlaybackListItemData data, bool selected)
|
private void OnItemSelect(UIPlaybackListItemData data, bool selected)
|
||||||
{
|
{
|
||||||
if (selected)
|
if (selected)
|
||||||
@@ -296,17 +365,26 @@ namespace UVC.Factory.Playback.UI
|
|||||||
updateButtonStatus();
|
updateButtonStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템의 다운로드 상태가 변경될 때 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
private void OnItemChangeStatus(UIPlaybackListItemData data, UIPlaybackListItemStatus status)
|
private void OnItemChangeStatus(UIPlaybackListItemData data, UIPlaybackListItemStatus status)
|
||||||
{
|
{
|
||||||
bool enable = UIPlaybackListItem.DownloadingItems.Count == 0;
|
bool enable = UIPlaybackListItem.DownloadingItems.Count == 0;
|
||||||
dropdownDate.interactable = enable;
|
dropdownDate.interactable = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '확인' 버튼의 활성화 상태를 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
private void updateButtonStatus()
|
private void updateButtonStatus()
|
||||||
{
|
{
|
||||||
confirmButton.interactable = IsOkable;
|
confirmButton.interactable = IsOkable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '확인' 버튼 클릭 시 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
public override void OnConfirmButtonClicked()
|
public override void OnConfirmButtonClicked()
|
||||||
{
|
{
|
||||||
resultData = selectedItem;
|
resultData = selectedItem;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UVC.Extension;
|
using UVC.Extension;
|
||||||
@@ -6,18 +6,59 @@ using UVC.UI;
|
|||||||
|
|
||||||
namespace UVC.Factory.Playback.UI
|
namespace UVC.Factory.Playback.UI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 재생 위치를 표시하고 조작할 수 있는 UI 프로그레스바 컴포넌트입니다.
|
||||||
|
/// - 현재 재생 시간, 전체 시간, 슬라이더(Seek Bar)로 구성됩니다.
|
||||||
|
/// - 슬라이더를 드래그하거나 클릭하여 재생 위치를 변경할 수 있습니다.
|
||||||
|
///
|
||||||
|
/// <para>샘플 사용법:</para>
|
||||||
|
/// <code>
|
||||||
|
/// // UIPlaybackProgressBar를 가진 오브젝트를 찾고 초기화
|
||||||
|
/// var progressBar = FindObjectOfType<UIPlaybackProgressBar>();
|
||||||
|
/// progressBar.Init(1); // 1시간짜리 재생바로 초기화
|
||||||
|
///
|
||||||
|
/// // 값이 변경될 때마다 호출되는 콜백 등록
|
||||||
|
/// progressBar.OnChangeValue = (value) => {
|
||||||
|
/// Debug.Log($"재생 위치가 {value}초로 변경됨");
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // 재생 위치를 코드로 변경
|
||||||
|
/// progressBar.Value = 120; // 2분(120초) 위치로 이동
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
public class UIPlaybackProgressBar : MonoBehaviour
|
public class UIPlaybackProgressBar : MonoBehaviour
|
||||||
{
|
{
|
||||||
private TextMeshProUGUI playTimeTxt;
|
[Header("UI Playback Progress Bar")]
|
||||||
private TextMeshProUGUI totalTimeTxt;
|
[Tooltip("UI 활성/비활성 및 상호작용 제어용")]
|
||||||
private SliderWithEvent progressBar;
|
[SerializeField]
|
||||||
private float progressBarPrevValue = 0;
|
private CanvasGroup canvasGroup;// UI 활성/비활성 및 상호작용 제어용
|
||||||
|
|
||||||
private CanvasGroup canvasGroup;
|
[Tooltip("현재 재생 시간 텍스트")]
|
||||||
|
[SerializeField]
|
||||||
|
private TextMeshProUGUI playTimeTxt;// 현재 재생 시간 텍스트
|
||||||
|
|
||||||
|
[Tooltip("전체 재생 시간 텍스트")]
|
||||||
|
[SerializeField]
|
||||||
|
private TextMeshProUGUI totalTimeTxt;// 전체 재생 시간 텍스트
|
||||||
|
|
||||||
|
[Tooltip("재생 위치를 조작하는 슬라이더")]
|
||||||
|
[SerializeField]
|
||||||
|
private SliderWithEvent progressBar;// 재생 위치를 조작하는 슬라이더
|
||||||
|
|
||||||
|
private float progressBarPrevValue = 0;// 이전 슬라이더 값(변경 감지용)
|
||||||
|
// 전체 시간(시간 단위, 예: 1이면 1시간)
|
||||||
private int time;
|
private int time;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더 값이 변경될 때 호출되는 이벤트입니다.
|
||||||
|
/// <para>예시: progressBar.OnChangeValue = (value) => { ... };</para>
|
||||||
|
/// </summary>
|
||||||
public Action<int> OnChangeValue { get; set; }
|
public Action<int> OnChangeValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 슬라이더(재생 위치) 값(초 단위)입니다.
|
||||||
|
/// 값을 설정하면 UI와 내부 상태가 함께 갱신됩니다.
|
||||||
|
/// </summary>
|
||||||
public int Value
|
public int Value
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -36,17 +77,26 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더의 최대값(전체 재생 시간, 초 단위)입니다.
|
||||||
|
/// </summary>
|
||||||
public int MaxValue
|
public int MaxValue
|
||||||
{
|
{
|
||||||
get => (int)progressBar.maxValue;
|
get => (int)progressBar.maxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 프로그레스바의 상호작용 가능 여부를 설정/조회합니다.
|
||||||
|
/// </summary>
|
||||||
public bool Interactable
|
public bool Interactable
|
||||||
{
|
{
|
||||||
get => canvasGroup.interactable;
|
get => canvasGroup.interactable;
|
||||||
set => canvasGroup.interactable = value;
|
set => canvasGroup.interactable = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 오브젝트가 파괴될 때 이벤트 리스너를 해제합니다.
|
||||||
|
/// </summary>
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
progressBar.onValueChanged.RemoveListener(OnChangeSlider);
|
progressBar.onValueChanged.RemoveListener(OnChangeSlider);
|
||||||
@@ -55,27 +105,35 @@ namespace UVC.Factory.Playback.UI
|
|||||||
progressBar.OnEndDragAction = null;
|
progressBar.OnEndDragAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 프로그레스바를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">전체 시간(시간 단위, 예: 1이면 1시간)</param>
|
||||||
|
/// <example>
|
||||||
|
/// progressBar.Init(1); // 1시간짜리 재생바로 초기화
|
||||||
|
/// </example>
|
||||||
public void Init(int time)
|
public void Init(int time)
|
||||||
{
|
{
|
||||||
this.time = time;
|
this.time = time;
|
||||||
canvasGroup = GetComponent<CanvasGroup>();
|
|
||||||
|
|
||||||
playTimeTxt = transform.FindChildren("PlayTimeTxt").GetComponent<TextMeshProUGUI>();
|
|
||||||
totalTimeTxt = transform.FindChildren("TotalTimeTxt").GetComponent<TextMeshProUGUI>();
|
|
||||||
progressBar = GetComponentInChildren<SliderWithEvent>();
|
|
||||||
|
|
||||||
|
// 슬라이더 이벤트 등록
|
||||||
progressBar.onValueChanged.AddListener(OnChangeSlider);
|
progressBar.onValueChanged.AddListener(OnChangeSlider);
|
||||||
progressBar.OnClickAction += OnClickProgressBar;
|
progressBar.OnClickAction += OnClickProgressBar;
|
||||||
progressBar.OnDragAction += OnDragProgressBar;
|
progressBar.OnDragAction += OnDragProgressBar;
|
||||||
progressBar.OnEndDragAction += OnEndDragProgressBar;
|
progressBar.OnEndDragAction += OnEndDragProgressBar;
|
||||||
|
|
||||||
|
// 초기 텍스트 및 값 설정
|
||||||
playTimeTxt.text = $"{time.ToString("00")}:00:00";
|
playTimeTxt.text = $"{time.ToString("00")}:00:00";
|
||||||
totalTimeTxt.text = $"{(time + 1).ToString("00")}:00:00";
|
totalTimeTxt.text = $"{(time + 1).ToString("00")}:00:00";
|
||||||
progressBar.value = 0;
|
progressBar.value = 0;
|
||||||
Interactable = true;
|
Interactable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더 값이 변경될 때 호출됩니다.
|
||||||
|
/// 상호작용 불가 상태면 값을 되돌립니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newValue">변경된 값</param>
|
||||||
private void OnChangeSlider(float newValue)
|
private void OnChangeSlider(float newValue)
|
||||||
{
|
{
|
||||||
if (!Interactable)
|
if (!Interactable)
|
||||||
@@ -86,6 +144,10 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더를 클릭했을 때 호출됩니다.
|
||||||
|
/// 값이 변경되면 OnChangeValue 이벤트가 발생합니다.
|
||||||
|
/// </summary>
|
||||||
private void OnClickProgressBar()
|
private void OnClickProgressBar()
|
||||||
{
|
{
|
||||||
float snapedValue = SnapProgressBarValue();
|
float snapedValue = SnapProgressBarValue();
|
||||||
@@ -98,6 +160,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더를 드래그하는 동안 계속 호출됩니다.
|
||||||
|
/// </summary>
|
||||||
private void OnDragProgressBar()
|
private void OnDragProgressBar()
|
||||||
{
|
{
|
||||||
float snapedValue = SnapProgressBarValue();
|
float snapedValue = SnapProgressBarValue();
|
||||||
@@ -105,6 +170,10 @@ namespace UVC.Factory.Playback.UI
|
|||||||
UpdateTimeText();
|
UpdateTimeText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더 드래그가 끝났을 때 호출됩니다.
|
||||||
|
/// 값이 변경되면 OnChangeValue 이벤트가 발생합니다.
|
||||||
|
/// </summary>
|
||||||
private void OnEndDragProgressBar()
|
private void OnEndDragProgressBar()
|
||||||
{
|
{
|
||||||
float snapedValue = SnapProgressBarValue();
|
float snapedValue = SnapProgressBarValue();
|
||||||
@@ -117,6 +186,10 @@ namespace UVC.Factory.Playback.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 슬라이더 값을 60초(1분) 단위로 스냅합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>스냅된 값</returns>
|
||||||
private float SnapProgressBarValue()
|
private float SnapProgressBarValue()
|
||||||
{
|
{
|
||||||
float value = progressBar.value;
|
float value = progressBar.value;
|
||||||
@@ -125,6 +198,9 @@ namespace UVC.Factory.Playback.UI
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 재생 시간 텍스트를 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
private void UpdateTimeText()
|
private void UpdateTimeText()
|
||||||
{
|
{
|
||||||
int minute = (int)progressBar.value / 60;
|
int minute = (int)progressBar.value / 60;
|
||||||
|
|||||||
8
Assets/Scripts/UVC/UI/List.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 01351942c80cc7042b2ec18f41173854
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
53
Assets/Scripts/UVC/UI/List/DraggableItem.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace UVC.UI.List
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 개별 드래그 가능한 아이템의 UI 컴포넌트
|
||||||
|
/// 드래그 동작과 시각적 피드백을 담당
|
||||||
|
/// </summary>
|
||||||
|
public class DraggableItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||||
|
{
|
||||||
|
// 이벤트
|
||||||
|
public Action<PointerEventData>? OnBeginDragEvent;
|
||||||
|
public Action<PointerEventData>? OnDragEvent;
|
||||||
|
public Action<PointerEventData>? OnEndDragEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 시작 시 호출
|
||||||
|
/// </summary>
|
||||||
|
public void OnBeginDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
OnBeginDragEvent?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 중 호출
|
||||||
|
/// </summary>
|
||||||
|
public void OnDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
// 이벤트 발생
|
||||||
|
OnDragEvent?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 종료 시 호출
|
||||||
|
/// </summary>
|
||||||
|
public void OnEndDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
// 이벤트 발생
|
||||||
|
OnEndDragEvent?.Invoke(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
// 이벤트 구독 해제
|
||||||
|
OnBeginDragEvent = null;
|
||||||
|
OnDragEvent = null;
|
||||||
|
OnEndDragEvent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/List/DraggableItem.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2d93b757e3738184492e84c051530130
|
||||||
42
Assets/Scripts/UVC/UI/List/DraggableItemData.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UVC.UI.List
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 가능한 목록 아이템의 데이터 모델
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class DraggableItemData
|
||||||
|
{
|
||||||
|
[SerializeField] protected string id;
|
||||||
|
[SerializeField] protected int sortOrder;
|
||||||
|
|
||||||
|
public string Id => id;
|
||||||
|
public int SortOrder { get => sortOrder; set => sortOrder = value; }
|
||||||
|
|
||||||
|
public DraggableItemData(string id, int sortOrder = 0)
|
||||||
|
{
|
||||||
|
this.id = id ?? throw new ArgumentNullException(nameof(id));
|
||||||
|
this.sortOrder = sortOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 작업의 결과를 나타내는 이벤트 인자
|
||||||
|
/// </summary>
|
||||||
|
public class DraggableItemReorderEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string ItemId { get; }
|
||||||
|
public int OldIndex { get; }
|
||||||
|
public int NewIndex { get; }
|
||||||
|
|
||||||
|
public DraggableItemReorderEventArgs(string itemId, int oldIndex, int newIndex)
|
||||||
|
{
|
||||||
|
ItemId = itemId;
|
||||||
|
OldIndex = oldIndex;
|
||||||
|
NewIndex = newIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/List/DraggableItemData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2a576b71abcd5ff41a1c3d0adf21a45c
|
||||||
191
Assets/Scripts/UVC/UI/List/DraggableListItem.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace UVC.UI.List
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 개별 드래그 가능한 아이템의 UI 컴포넌트
|
||||||
|
/// 드래그 동작과 시각적 피드백을 담당
|
||||||
|
/// </summary>
|
||||||
|
public class DraggableListItem : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("UI 컴포넌트")]
|
||||||
|
[SerializeField] protected CanvasGroup? canvasGroup;
|
||||||
|
[SerializeField] protected RectTransform? rectTransform;
|
||||||
|
[SerializeField] protected DraggableItem? dragAnchor;
|
||||||
|
[SerializeField] protected TMP_InputField? inputField;
|
||||||
|
|
||||||
|
[Header("드래그 설정")]
|
||||||
|
[SerializeField] protected float dragAlpha = 0.6f;
|
||||||
|
[SerializeField] protected bool blockRaycastsWhileDragging = false;
|
||||||
|
|
||||||
|
// 프로퍼티
|
||||||
|
public DraggableItemData? Data { get; private set; }
|
||||||
|
public RectTransform? RectTransform => rectTransform;
|
||||||
|
public bool IsDragging { get; private set; }
|
||||||
|
|
||||||
|
public event Action<DraggableListItem>? OnBeginDragEvent;
|
||||||
|
public event Action<DraggableListItem, Vector2>? OnDragEvent;
|
||||||
|
public event Action<DraggableListItem>? OnEndDragEvent;
|
||||||
|
|
||||||
|
private Vector2 originalPosition;
|
||||||
|
private Transform? originalParent;
|
||||||
|
private int originalSiblingIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 컴포넌트 초기화
|
||||||
|
/// </summary>
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
// null 체크 및 자동 할당
|
||||||
|
if (rectTransform == null)
|
||||||
|
rectTransform = GetComponent<RectTransform>();
|
||||||
|
|
||||||
|
if (canvasGroup == null)
|
||||||
|
canvasGroup = GetComponent<CanvasGroup>();
|
||||||
|
|
||||||
|
// CanvasGroup이 없으면 추가
|
||||||
|
if (canvasGroup == null)
|
||||||
|
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||||
|
|
||||||
|
if (dragAnchor == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("Drag Anchor is not assigned. Please assign it in the inspector.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragAnchor.OnBeginDragEvent += OnBeginDrag;
|
||||||
|
dragAnchor.OnDragEvent += OnDrag;
|
||||||
|
dragAnchor.OnEndDragEvent += OnEndDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아이템 데이터로 UI 업데이트
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">표시할 데이터</param>
|
||||||
|
public void SetData(DraggableItemData? data)
|
||||||
|
{
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
Data = data;
|
||||||
|
UpdateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI 요소들을 데이터에 맞게 업데이트
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void UpdateUI()
|
||||||
|
{
|
||||||
|
if (Data == null) return;
|
||||||
|
if(inputField != null)
|
||||||
|
{
|
||||||
|
inputField.text = Data.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 시작 시 호출
|
||||||
|
/// </summary>
|
||||||
|
public void OnBeginDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (rectTransform == null) return;
|
||||||
|
|
||||||
|
IsDragging = true;
|
||||||
|
|
||||||
|
// 원래 위치와 부모 저장
|
||||||
|
originalPosition = rectTransform.anchoredPosition;
|
||||||
|
originalParent = transform.parent;
|
||||||
|
originalSiblingIndex = transform.GetSiblingIndex();
|
||||||
|
|
||||||
|
// 시각적 피드백 적용
|
||||||
|
ApplyDragVisuals(true);
|
||||||
|
|
||||||
|
// 이벤트 발생
|
||||||
|
OnBeginDragEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 중 호출
|
||||||
|
/// </summary>
|
||||||
|
public void OnDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (rectTransform == null) return;
|
||||||
|
|
||||||
|
// 마우스 위치로 아이템 이동
|
||||||
|
rectTransform.anchoredPosition += new Vector2(0, eventData.delta.y);//eventData.delta
|
||||||
|
|
||||||
|
// 이벤트 발생
|
||||||
|
OnDragEvent?.Invoke(this, rectTransform.anchoredPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 종료 시 호출
|
||||||
|
/// </summary>
|
||||||
|
public void OnEndDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
IsDragging = false;
|
||||||
|
|
||||||
|
// 시각적 피드백 복원
|
||||||
|
ApplyDragVisuals(false);
|
||||||
|
|
||||||
|
// 이벤트 발생
|
||||||
|
OnEndDragEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 시각적 효과 적용/해제
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isDragging">드래그 중인지 여부</param>
|
||||||
|
private void ApplyDragVisuals(bool isDragging)
|
||||||
|
{
|
||||||
|
if (canvasGroup == null) return;
|
||||||
|
|
||||||
|
if (isDragging)
|
||||||
|
{
|
||||||
|
canvasGroup.alpha = dragAlpha;
|
||||||
|
canvasGroup.blocksRaycasts = blockRaycastsWhileDragging;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
canvasGroup.alpha = 1f;
|
||||||
|
canvasGroup.blocksRaycasts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 원래 위치로 되돌리기 (드래그 취소 시 사용)
|
||||||
|
/// </summary>
|
||||||
|
public void ResetToOriginalPosition()
|
||||||
|
{
|
||||||
|
if (rectTransform == null || originalParent == null) return;
|
||||||
|
|
||||||
|
transform.SetParent(originalParent);
|
||||||
|
transform.SetSiblingIndex(originalSiblingIndex);
|
||||||
|
rectTransform.anchoredPosition = originalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDestroy()
|
||||||
|
{
|
||||||
|
// 이벤트 구독 해제
|
||||||
|
if (dragAnchor != null)
|
||||||
|
{
|
||||||
|
dragAnchor.OnBeginDragEvent -= OnBeginDrag;
|
||||||
|
dragAnchor.OnDragEvent -= OnDrag;
|
||||||
|
dragAnchor.OnEndDragEvent -= OnEndDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnBeginDragEvent = null;
|
||||||
|
OnDragEvent = null;
|
||||||
|
OnEndDragEvent = null;
|
||||||
|
|
||||||
|
// 리소스 정리
|
||||||
|
canvasGroup = null;
|
||||||
|
rectTransform = null;
|
||||||
|
dragAnchor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/List/DraggableListItem.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7cdfe032ad5874e4cbc571f344516b93
|
||||||
1075
Assets/Scripts/UVC/UI/List/DraggableScrollList.cs
Normal file
2
Assets/Scripts/UVC/UI/List/DraggableScrollList.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 71e6121c6103b0a4c9aeadc24c891b86
|
||||||
@@ -2,101 +2,214 @@ using System.Collections;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UILoading 스크립트 사용법 예시
|
||||||
|
*
|
||||||
|
* 다른 스크립트에서 아래와 같이 간단하게 호출하여 로딩 화면을 표시하거나 숨길 수 있습니다.
|
||||||
|
*
|
||||||
|
* public class MyGameManager : MonoBehaviour
|
||||||
|
* {
|
||||||
|
* public void LoadNextScene()
|
||||||
|
* {
|
||||||
|
* // 로딩 화면 표시
|
||||||
|
* UVC.UI.Loading.UILoading.Show();
|
||||||
|
*
|
||||||
|
* // 코루틴을 사용하여 비동기 씬 로딩 시작
|
||||||
|
* StartCoroutine(LoadSceneAsync());
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* private IEnumerator LoadSceneAsync()
|
||||||
|
* {
|
||||||
|
* // 씬을 비동기로 로드합니다.
|
||||||
|
* AsyncOperation asyncLoad = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("NextSceneName");
|
||||||
|
*
|
||||||
|
* // 씬 로딩이 완료될 때까지 대기합니다.
|
||||||
|
* while (!asyncLoad.isDone)
|
||||||
|
* {
|
||||||
|
* yield return null;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 씬 로딩이 완료되면 로딩 화면을 숨깁니다.
|
||||||
|
* UVC.UI.Loading.UILoading.Hide();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
namespace UVC.UI.Loading
|
namespace UVC.UI.Loading
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 게임 전체에서 사용되는 로딩 UI를 제어하는 클래스입니다.
|
||||||
|
/// 싱글톤(Singleton)과 유사한 방식으로 구현되어 어디서든 쉽게 접근하고 사용할 수 있습니다.
|
||||||
|
/// </summary>
|
||||||
[RequireComponent(typeof(CanvasGroup))]
|
[RequireComponent(typeof(CanvasGroup))]
|
||||||
public class UILoading : UnityEngine.MonoBehaviour
|
public class UILoading : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
// 로딩 UI 프리팹이 Resources 폴더 내에 위치하는 경로입니다.
|
||||||
|
// Resources.Load를 통해 동적으로 프리팹을 불러올 때 사용됩니다.
|
||||||
public static string PrefabPath = "Prefabs/UI/Loading/UILoading";
|
public static string PrefabPath = "Prefabs/UI/Loading/UILoading";
|
||||||
|
|
||||||
|
// UILoading 클래스의 유일한 인스턴스를 저장하는 정적(static) 변수입니다.
|
||||||
|
// 이 변수를 통해 다른 모든 스크립트에서 동일한 로딩 화면 인스턴스에 접근할 수 있습니다.
|
||||||
private static UILoading instance;
|
private static UILoading instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 화면을 표시합니다.
|
||||||
|
/// 만약 로딩 화면이 아직 생성되지 않았다면, 프리팹을 이용해 새로 생성합니다.
|
||||||
|
/// </summary>
|
||||||
public static void Show()
|
public static void Show()
|
||||||
{
|
{
|
||||||
if (instance == null) {
|
// instance가 null일 경우, 아직 로딩 화면이 만들어지지 않았다는 의미입니다.
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
// Resources 폴더에서 프리팹을 불러옵니다.
|
||||||
GameObject prefab = Resources.Load<GameObject>(PrefabPath);
|
GameObject prefab = Resources.Load<GameObject>(PrefabPath);
|
||||||
|
// 불러온 프리팹을 씬에 인스턴스(복제)하여 생성합니다.
|
||||||
GameObject go = Instantiate(prefab);
|
GameObject go = Instantiate(prefab);
|
||||||
|
// 생성된 GameObject의 이름을 "UILoading"으로 설정하여 씬에서 쉽게 식별할 수 있도록 합니다.
|
||||||
go.name = "UILoading";
|
go.name = "UILoading";
|
||||||
|
// 부모를 null로 설정하여 씬의 최상위 계층에 위치시킵니다.
|
||||||
|
// 이렇게 하면 다른 씬으로 전환될 때 함께 파괴되지 않도록 관리하기 용이합니다. (DontDestroyOnLoad와 함께 사용 가능)
|
||||||
go.transform.SetParent(null, false);
|
go.transform.SetParent(null, false);
|
||||||
|
// 생성된 GameObject에서 UILoading 컴포넌트를 찾아 instance 변수에 할당합니다.
|
||||||
instance = go.GetComponent<UILoading>();
|
instance = go.GetComponent<UILoading>();
|
||||||
}
|
}
|
||||||
|
// 인스턴스의 ShowLoading 메서드를 호출하여 페이드인 애니메이션을 시작합니다.
|
||||||
instance.ShowLoading();
|
instance.ShowLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 표시되고 있는 로딩 화면을 숨깁니다.
|
||||||
|
/// </summary>
|
||||||
public static void Hide()
|
public static void Hide()
|
||||||
{
|
{
|
||||||
|
// instance가 null이 아닐 경우에만 (즉, 로딩 화면이 존재할 때만) 실행합니다.
|
||||||
if (instance != null)
|
if (instance != null)
|
||||||
{
|
{
|
||||||
|
// 인스턴스의 HideLoading 메서드를 호출하여 페이드아웃 애니메이션을 시작합니다.
|
||||||
instance.HideLoading();
|
instance.HideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 인스펙터(Inspector)에서 설정할 변수들 ---
|
||||||
|
[Header("Component References")]
|
||||||
|
[Tooltip("페이드 효과를 제어할 CanvasGroup 컴포넌트")]
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private CanvasGroup canvasGroup;
|
private CanvasGroup canvasGroup;
|
||||||
|
|
||||||
|
[Tooltip("채우기(Fill) 및 회전 효과를 적용할 Image 컴포넌트")]
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private Image loadinImage;
|
private Image loadinImage;
|
||||||
|
|
||||||
private float duration = 0.25f;
|
|
||||||
private float target = 0;
|
|
||||||
private float alpha = 1;
|
|
||||||
private bool animatting = false;
|
|
||||||
private Transform loadingImageTransform;
|
|
||||||
|
|
||||||
|
// --- 내부 동작을 위한 변수들 ---
|
||||||
|
[Header("Animation Settings")]
|
||||||
|
[Tooltip("페이드인/아웃 애니메이션의 지속 시간 (초)")]
|
||||||
|
[SerializeField]
|
||||||
|
private float duration = 0.25f;
|
||||||
|
|
||||||
|
[Tooltip("로딩 아이콘의 회전 속도")]
|
||||||
|
[SerializeField]
|
||||||
private float loadingSpeed = -1.5f;
|
private float loadingSpeed = -1.5f;
|
||||||
|
|
||||||
|
[Tooltip("이미지 채우기(Fill) 효과의 속도")]
|
||||||
|
[SerializeField]
|
||||||
private float rotationSpeed = -1.0f;
|
private float rotationSpeed = -1.0f;
|
||||||
|
|
||||||
|
private float target = 0; // 애니메이션의 목표 알파 값 (0: 투명, 1: 불투명)
|
||||||
|
private bool animatting = false; // 현재 애니메이션이 진행 중인지 여부
|
||||||
|
private Transform loadingImageTransform; // 회전 애니메이션을 적용할 이미지의 Transform
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 스크립트 인스턴스가 처음 로드될 때 호출됩니다.
|
||||||
|
/// 변수 초기화에 사용됩니다.
|
||||||
|
/// </summary>
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
|
// Image 컴포넌트의 Transform을 미리 찾아 변수에 저장해두어,
|
||||||
|
// LateUpdate에서 매번 찾는 비용을 절약합니다.
|
||||||
loadingImageTransform = loadinImage.transform;
|
loadingImageTransform = loadinImage.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 화면을 나타나게 하는 애니메이션을 시작합니다.
|
||||||
|
/// </summary>
|
||||||
public void ShowLoading()
|
public void ShowLoading()
|
||||||
{
|
{
|
||||||
|
// 이미 애니메이션이 진행 중이고, 목표가 '나타나기(target=1)'라면 중복 실행을 방지합니다.
|
||||||
if (animatting && target == 1) return;
|
if (animatting && target == 1) return;
|
||||||
|
|
||||||
target = 1;
|
target = 1; // 목표 알파 값을 1(불투명)로 설정
|
||||||
animatting = true;
|
animatting = true; // 애니메이션 시작 상태로 변경
|
||||||
StopCoroutine("Animate");
|
StopCoroutine("Animate"); // 이전에 실행 중이던 Animate 코루틴이 있다면 중지
|
||||||
StartCoroutine(Animate());
|
StartCoroutine(Animate()); // Animate 코루틴을 새로 시작
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 화면을 사라지게 하는 애니메이션을 시작합니다.
|
||||||
|
/// </summary>
|
||||||
public void HideLoading()
|
public void HideLoading()
|
||||||
{
|
{
|
||||||
|
// 이미 애니메이션이 진행 중이고, 목표가 '사라지기(target=0)'라면 중복 실행을 방지합니다.
|
||||||
if (animatting && target == 0) return;
|
if (animatting && target == 0) return;
|
||||||
|
|
||||||
target = 0;
|
target = 0; // 목표 알파 값을 0(투명)으로 설정
|
||||||
animatting = true;
|
animatting = true; // 애니메이션 시작 상태로 변경
|
||||||
StopCoroutine("Animate");
|
StopCoroutine("Animate"); // 이전에 실행 중이던 Animate 코루틴이 있다면 중지
|
||||||
StartCoroutine(Animate());
|
StartCoroutine(Animate()); // Animate 코루틴을 새로 시작
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CanvasGroup의 알파 값을 부드럽게 변경하여 페이드인/아웃 효과를 주는 코루틴입니다.
|
||||||
|
/// </summary>
|
||||||
private IEnumerator Animate()
|
private IEnumerator Animate()
|
||||||
{
|
{
|
||||||
float start = canvasGroup.alpha;
|
float start = canvasGroup.alpha; // 현재 알파 값을 시작 값으로 설정
|
||||||
float time = 0;
|
float time = 0;
|
||||||
|
|
||||||
|
// 경과 시간이 duration에 도달할 때까지 반복합니다.
|
||||||
while (time < duration)
|
while (time < duration)
|
||||||
{
|
{
|
||||||
time += Time.deltaTime;
|
// Time.unscaledDeltaTime: Time.timeScale 값에 영향을 받지 않는 시간 간격입니다.
|
||||||
|
// 로딩 중 Time.timeScale이 0이 되어도 UI 애니메이션은 계속 실행되어야 하므로 사용합니다.
|
||||||
|
time += Time.unscaledDeltaTime;
|
||||||
|
// Mathf.Lerp(시작값, 목표값, 진행률): 두 값 사이를 선형 보간합니다.
|
||||||
|
// 진행률(time / duration)에 따라 부드럽게 알파 값이 변합니다.
|
||||||
canvasGroup.alpha = Mathf.Lerp(start, target, time / duration);
|
canvasGroup.alpha = Mathf.Lerp(start, target, time / duration);
|
||||||
|
// 다음 프레임까지 대기합니다.
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 애니메이션이 끝난 후 목표 알파 값으로 정확히 맞춰줍니다.
|
||||||
canvasGroup.alpha = target;
|
canvasGroup.alpha = target;
|
||||||
animatting = false;
|
animatting = false; // 애니메이션 종료 상태로 변경
|
||||||
if(target == 0)
|
|
||||||
|
// 만약 목표가 '사라지기(target=0)'였다면, 애니메이션 종료 후 GameObject를 파괴합니다.
|
||||||
|
if (target == 0)
|
||||||
{
|
{
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
/// <summary>
|
||||||
|
/// 모든 Update 함수가 호출된 후에 프레임마다 호출됩니다.
|
||||||
|
/// 주로 카메라 이동이나 다른 애니메이션이 끝난 후 최종적으로 UI를 업데이트할 때 사용되어,
|
||||||
|
/// 떨림(Jitter) 현상을 방지하는 데 도움이 됩니다.
|
||||||
|
/// </summary>
|
||||||
|
private void LateUpdate()
|
||||||
{
|
{
|
||||||
|
// 로딩 화면이 완전히 불투명할 때(애니메이션이 진행 중일 때)만 실행합니다.
|
||||||
if (canvasGroup.alpha == 1)
|
if (canvasGroup.alpha == 1)
|
||||||
{
|
{
|
||||||
loadingImageTransform.Rotate(Vector3.forward, loadingSpeed * Time.deltaTime * 360);
|
// 1. 이미지 회전 애니메이션
|
||||||
loadinImage.fillAmount = Mathf.PingPong(Time.time * rotationSpeed, 1);
|
// Time.unscaledTime: Time.timeScale에 영향을 받지 않는 전체 게임 시간입니다.
|
||||||
|
// 시간에 비례하는 절대적인 회전 각도를 계산하여 매 프레임 설정합니다.
|
||||||
|
// 이렇게 하면 프레임 속도 변화에 관계없이 항상 일관되고 부드러운 회전을 보장할 수 있습니다.
|
||||||
|
float zRotation = Time.unscaledTime * loadingSpeed * 360;
|
||||||
|
loadingImageTransform.rotation = Quaternion.Euler(0, 0, zRotation);
|
||||||
|
|
||||||
|
// 2. 이미지 채우기(Fill) 애니메이션 (필요 시 주석 해제하여 사용)
|
||||||
|
// Mathf.PingPong(시간, 길이): 0과 '길이' 사이를 왕복하는 값을 반환합니다.
|
||||||
|
// 여기서는 fillAmount가 0과 1 사이를 계속 오가도록 하여 채워졌다 비워지는 효과를 만듭니다.
|
||||||
|
loadinImage.fillAmount = Mathf.PingPong(Time.unscaledTime * rotationSpeed, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,36 @@ using UnityEngine.UI;
|
|||||||
|
|
||||||
namespace UVC.UI.Loading
|
namespace UVC.UI.Loading
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 UI의 표시와 숨김, 진행 상태를 관리하는 클래스입니다.
|
||||||
|
/// CanvasGroup 컴포넌트를 사용하여 페이드 인/아웃 효과를 구현합니다.
|
||||||
|
/// 이 클래스가 게임 오브젝트에 추가되면 CanvasGroup 컴포넌트도 자동으로 추가됩니다.
|
||||||
|
/// </summary>
|
||||||
[RequireComponent(typeof(CanvasGroup))]
|
[RequireComponent(typeof(CanvasGroup))]
|
||||||
public class UILoadingBar : UnityEngine.MonoBehaviour
|
public class UILoadingBar : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 바 UI 프리팹이 있는 Resources 폴더 내의 경로입니다.
|
||||||
|
/// Show() 메서드가 처음 호출될 때 이 경로를 사용하여 프리팹을 동적으로 생성합니다.
|
||||||
|
/// </summary>
|
||||||
public static string PrefabPath = "Prefabs/UI/Loading/UILoadingBar";
|
public static string PrefabPath = "Prefabs/UI/Loading/UILoadingBar";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UILoadingBar의 유일한 인스턴스(Singleton)입니다.
|
||||||
|
/// static으로 선언되어 어디서든 UILoadingBar.instance 형태로 접근할 수 있습니다.
|
||||||
|
/// </summary>
|
||||||
private static UILoadingBar instance;
|
private static UILoadingBar instance;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 0.0~1.0
|
/// 로딩 진행률을 0.0에서 1.0 사이의 값으로 설정하거나 가져옵니다.
|
||||||
|
/// 이 값은 로딩 이미지의 채워지는 양(fillAmount)에 직접 반영됩니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 로딩 진행률을 50%로 설정
|
||||||
|
/// UILoadingBar.Percent = 0.5f;
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public static float Percent
|
public static float Percent
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -29,6 +49,15 @@ namespace UVC.UI.Loading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 화면에 표시될 메시지를 설정하거나 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 로딩 메시지 설정
|
||||||
|
/// UILoadingBar.Message = "데이터를 불러오는 중입니다...";
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public static string Message
|
public static string Message
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -43,6 +72,20 @@ namespace UVC.UI.Loading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 바를 화면에 표시합니다.
|
||||||
|
/// 만약 로딩 바 인스턴스가 없다면 PrefabPath에서 프리팹을 불러와 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">로딩 화면에 표시할 메시지입니다.</param>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 기본 메시지로 로딩 바 표시
|
||||||
|
/// UILoadingBar.Show();
|
||||||
|
///
|
||||||
|
/// // 특정 메시지와 함께 로딩 바 표시
|
||||||
|
/// UILoadingBar.Show("플레이어 정보를 로딩 중입니다...");
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public static void Show(string message = "")
|
public static void Show(string message = "")
|
||||||
{
|
{
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@@ -56,6 +99,16 @@ namespace UVC.UI.Loading
|
|||||||
instance.ShowLoading();
|
instance.ShowLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 바를 화면에서 숨깁니다.
|
||||||
|
/// 숨겨진 후에는 자동으로 파괴됩니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // 로딩 완료 후 로딩 바 숨기기
|
||||||
|
/// UILoadingBar.Hide();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public static void Hide()
|
public static void Hide()
|
||||||
{
|
{
|
||||||
if (instance != null)
|
if (instance != null)
|
||||||
@@ -64,64 +117,74 @@ namespace UVC.UI.Loading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Tooltip("페이드 인/아웃 효과를 제어하는 CanvasGroup 컴포넌트입니다. 알파 값을 조정하여 UI의 투명도를 변경합니다.")]
|
||||||
|
[SerializeField]
|
||||||
|
private CanvasGroup canvasGroup; // 페이드 효과를 제어하기 위한 CanvasGroup 컴포넌트
|
||||||
|
[Tooltip("로딩 진행 상태를 표시하는 이미지 컴포넌트입니다. 로딩 중 진행률을 시각적으로 나타냅니다.")]
|
||||||
|
[SerializeField]
|
||||||
|
private Image loadinImage; // 로딩 진행 상태를 표시하는 이미지
|
||||||
|
[Tooltip("로딩 메시지를 표시하는 텍스트 컴포넌트입니다. 로딩 중 사용자에게 정보를 제공합니다.")]
|
||||||
|
[SerializeField]
|
||||||
|
private TextMeshProUGUI text; // 로딩 메시지를 표시하는 텍스트
|
||||||
|
private float target = 0; // 애니메이션의 목표 알파 값 (0: 투명, 1: 불투명)
|
||||||
|
private float duration = 0.25f; // 페이드 인/아웃 애니메이션 지속 시간
|
||||||
|
private bool animatting = false; // 현재 애니메이션이 진행 중인지 여부
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
private CanvasGroup canvasGroup;
|
/// 로딩 바를 부드럽게 나타나게 하는 애니메이션을 시작합니다.
|
||||||
private float target = 0;
|
/// 이미 나타나는 중이면 다시 호출되지 않습니다.
|
||||||
private float duration = 0.25f;
|
/// </summary>
|
||||||
private float alpha = 1;
|
|
||||||
private bool animatting = false;
|
|
||||||
private Image loadinImage;
|
|
||||||
private Transform loadingImageTransform;
|
|
||||||
private TextMeshProUGUI text;
|
|
||||||
|
|
||||||
private float loadingSpeed = 1.5f;
|
|
||||||
private float rotationSpeed = 1.0f;
|
|
||||||
|
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
canvasGroup = GetComponent<CanvasGroup>();
|
|
||||||
loadinImage = transform.Find("loadingImage").GetComponent<Image>();
|
|
||||||
text = transform.Find("message").GetComponent<TextMeshProUGUI>();
|
|
||||||
loadingImageTransform = loadinImage.transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void ShowLoading()
|
public void ShowLoading()
|
||||||
{
|
{
|
||||||
|
// 이미 애니메이션 중이고 목표 알파 값이 1(불투명)이면 중복 실행 방지
|
||||||
if (animatting && target == 1) return;
|
if (animatting && target == 1) return;
|
||||||
|
|
||||||
target = 1;
|
target = 1;
|
||||||
animatting = true;
|
animatting = true;
|
||||||
StopCoroutine("Animate");
|
StopCoroutine("Animate"); // 이전에 실행 중이던 Animate 코루틴이 있다면 중지
|
||||||
StartCoroutine(Animate());
|
StartCoroutine(Animate()); // Animate 코루틴 시작
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 바를 부드럽게 사라지게 하는 애니메이션을 시작합니다.
|
||||||
|
/// 이미 사라지는 중이면 다시 호출되지 않습니다.
|
||||||
|
/// </summary>
|
||||||
public void HideLoading()
|
public void HideLoading()
|
||||||
{
|
{
|
||||||
|
// 이미 애니메이션 중이고 목표 알파 값이 0(투명)이면 중복 실행 방지
|
||||||
if (animatting && target == 0) return;
|
if (animatting && target == 0) return;
|
||||||
|
|
||||||
target = 0;
|
target = 0;
|
||||||
animatting = true;
|
animatting = true;
|
||||||
StopCoroutine("Animate");
|
StopCoroutine("Animate"); // 이전에 실행 중이던 Animate 코루틴이 있다면 중지
|
||||||
StartCoroutine(Animate());
|
StartCoroutine(Animate()); // Animate 코루틴 시작
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CanvasGroup의 알파 값을 조절하여 페이드 인/아웃 효과를 주는 코루틴입니다.
|
||||||
|
/// 코루틴은 특정 시간 동안 또는 특정 조건이 만족될 때까지 함수의 실행을 잠시 멈출 수 있는 특별한 함수입니다.
|
||||||
|
/// </summary>
|
||||||
private IEnumerator Animate()
|
private IEnumerator Animate()
|
||||||
{
|
{
|
||||||
float start = canvasGroup.alpha;
|
float start = canvasGroup.alpha; // 애니메이션 시작 시점의 알파 값
|
||||||
float time = 0;
|
float time = 0; // 애니메이션 경과 시간
|
||||||
|
|
||||||
|
// 경과 시간이 duration에 도달할 때까지 반복
|
||||||
while (time < duration)
|
while (time < duration)
|
||||||
{
|
{
|
||||||
time += Time.deltaTime;
|
time += Time.deltaTime; // 한 프레임 동안의 시간을 더해줌
|
||||||
|
// Mathf.Lerp를 사용하여 시작 알파 값과 목표 알파 값 사이를 부드럽게 보간
|
||||||
canvasGroup.alpha = Mathf.Lerp(start, target, time / duration);
|
canvasGroup.alpha = Mathf.Lerp(start, target, time / duration);
|
||||||
|
// 다음 프레임까지 대기
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 애니메이션이 끝난 후 목표 알파 값으로 정확히 설정
|
||||||
canvasGroup.alpha = target;
|
canvasGroup.alpha = target;
|
||||||
animatting = false;
|
animatting = false;
|
||||||
if(target == 0)
|
|
||||||
|
// 만약 목표 알파 값이 0(사라지는 애니메이션)이었다면, 게임 오브젝트를 파괴
|
||||||
|
if (target == 0)
|
||||||
{
|
{
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
|
|||||||
8
Assets/Scripts/UVC/UI/Tab.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 87d7308ca55b2a34e818090493d0b897
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
16
Assets/Scripts/UVC/UI/Tab/ITabContent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#nullable enable
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 탭 콘텐츠 클래스가 구현해야 하는 인터페이스입니다.
|
||||||
|
/// 데이터를 받을 수 있는 공통 메서드를 정의합니다.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITabContent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 콘텐츠에 데이터를 전달합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">전달할 데이터 객체</param>
|
||||||
|
void SetContentData(object? data);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/ITabContent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2226fb1b6c4b11a4a9f899ab5e456a21
|
||||||
113
Assets/Scripts/UVC/UI/Tab/TabButtonView.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 단일 탭 버튼의 UI를 관리하는 컴포넌트입니다.
|
||||||
|
/// </summary>
|
||||||
|
public class TabButtonView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("탭 버튼 컴포넌트")]
|
||||||
|
[SerializeField] private Button? button;
|
||||||
|
|
||||||
|
[Tooltip("탭 이름을 표시할 Text 컴포넌트")]
|
||||||
|
[SerializeField] private TextMeshProUGUI? tabText;
|
||||||
|
|
||||||
|
[Tooltip("탭 아이콘을 표시할 Image 컴포넌트")]
|
||||||
|
[SerializeField] private Image? tabIcon;
|
||||||
|
|
||||||
|
[Tooltip("탭 배경 이미지 컴포넌트")]
|
||||||
|
[SerializeField] private Image? background;
|
||||||
|
|
||||||
|
[Tooltip("탭이 활성화되었을 때의 색상")]
|
||||||
|
[SerializeField] private Color? activeColor;// = Color.white;
|
||||||
|
|
||||||
|
[Tooltip("탭이 비활성화되었을 때의 색상")]
|
||||||
|
[SerializeField] private Color? inactiveColor;// = new Color(0.8f, 0.8f, 0.8f);
|
||||||
|
|
||||||
|
private int _tabIndex;
|
||||||
|
private Action<int>? _onTabSelected;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
// Button 컴포넌트가 할당되지 않은 경우 자동으로 찾기
|
||||||
|
if (button == null)
|
||||||
|
button = GetComponent<Button>();
|
||||||
|
|
||||||
|
// 버튼 클릭 이벤트 연결
|
||||||
|
if (button != null)
|
||||||
|
button.onClick.AddListener(OnButtonClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 버튼을 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">탭 인덱스</param>
|
||||||
|
/// <param name="tabName">탭 이름</param>
|
||||||
|
/// <param name="icon">탭 아이콘</param>
|
||||||
|
/// <param name="onSelectedCallback">탭 선택 시 호출될 콜백</param>
|
||||||
|
public void Setup(int index, string tabName, Sprite? icon, Action<int> onSelectedCallback)
|
||||||
|
{
|
||||||
|
_tabIndex = index;
|
||||||
|
_onTabSelected = onSelectedCallback;
|
||||||
|
|
||||||
|
// 탭 이름 설정
|
||||||
|
if (tabText != null)
|
||||||
|
tabText.text = tabName;
|
||||||
|
|
||||||
|
// 탭 아이콘 설정
|
||||||
|
if (tabIcon != null)
|
||||||
|
{
|
||||||
|
if (icon != null)
|
||||||
|
{
|
||||||
|
tabIcon.sprite = icon;
|
||||||
|
tabIcon.gameObject.SetActive(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tabIcon.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 버튼의 활성화 상태를 변경합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isActive">활성화 여부</param>
|
||||||
|
public void SetActive(bool isActive)
|
||||||
|
{
|
||||||
|
// 배경 색상 변경
|
||||||
|
if (background != null && activeColor.HasValue && inactiveColor.HasValue)
|
||||||
|
{
|
||||||
|
background.color = isActive ? activeColor.Value : inactiveColor.Value;
|
||||||
|
}
|
||||||
|
else if(button != null)
|
||||||
|
{
|
||||||
|
button.image.color = isActive ? button.colors.pressedColor : button.colors.normalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 텍스트 스타일 변경
|
||||||
|
if (tabText != null) {
|
||||||
|
tabText.fontStyle = isActive ? FontStyles.Bold : FontStyles.Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 버튼 클릭 이벤트 처리
|
||||||
|
private void OnButtonClick()
|
||||||
|
{
|
||||||
|
_onTabSelected?.Invoke(_tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 게임 오브젝트가 제거될 때 이벤트 리스너 제거
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (button != null)
|
||||||
|
button.onClick.RemoveListener(OnButtonClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabButtonView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 58af61b23f40877429f8889337d2006a
|
||||||
29
Assets/Scripts/UVC/UI/Tab/TabContentConfig.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#nullable enable
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 컨텐츠 설정 정보를 정의하는 클래스
|
||||||
|
/// </summary>
|
||||||
|
[System.Serializable]
|
||||||
|
public class TabContentConfig
|
||||||
|
{
|
||||||
|
public string tabID = "";
|
||||||
|
public string tabName = "";
|
||||||
|
public string contentPath = "";
|
||||||
|
public Sprite? tabIcon = null;
|
||||||
|
public bool useLazyLoading = false;
|
||||||
|
public object? initialData = null;
|
||||||
|
|
||||||
|
public TabContentConfig(string id, string name, string path, Sprite? icon = null, bool lazy = false, object? data = null)
|
||||||
|
{
|
||||||
|
tabID = id;
|
||||||
|
tabName = name;
|
||||||
|
contentPath = path;
|
||||||
|
tabIcon = icon;
|
||||||
|
useLazyLoading = lazy;
|
||||||
|
initialData = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabContentConfig.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d7ed77ea0eae0094b92dd0507d68f76d
|
||||||
285
Assets/Scripts/UVC/UI/Tab/TabContentLoader.cs
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 컨텐츠의 동적 로딩을 관리하는 클래스입니다.
|
||||||
|
/// TabView에 소속되어 지연 로딩 기능을 제공합니다.
|
||||||
|
/// </summary>
|
||||||
|
public class TabContentLoader : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("지연 로딩 사용 여부")]
|
||||||
|
[SerializeField] private bool lazyLoadTabs = true;
|
||||||
|
|
||||||
|
[Tooltip("최대 동시 로드 탭 수")]
|
||||||
|
[SerializeField] private int maxLoadedTabs = 3;
|
||||||
|
|
||||||
|
[Tooltip("탭 전환 시 자동 로드/언로드 여부")]
|
||||||
|
[SerializeField] private bool autoManageTabs = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지연 로딩할 탭 컨텐츠 정보를 정의하는 클래스입니다.
|
||||||
|
/// </summary>
|
||||||
|
[System.Serializable]
|
||||||
|
public class LazyTabContent
|
||||||
|
{
|
||||||
|
public string tabID = "";
|
||||||
|
public string contentPrefabPath = "";
|
||||||
|
public bool isLoaded = false;
|
||||||
|
|
||||||
|
[HideInInspector] public GameObject? instance = null;
|
||||||
|
[HideInInspector] public object? contentData = null;
|
||||||
|
|
||||||
|
public LazyTabContent(string id, string path, object? data = null)
|
||||||
|
{
|
||||||
|
tabID = id;
|
||||||
|
contentPrefabPath = path;
|
||||||
|
contentData = data;
|
||||||
|
isLoaded = false;
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 지연 로드 컨텐츠 목록
|
||||||
|
private List<LazyTabContent> _lazyContents = new List<LazyTabContent>();
|
||||||
|
|
||||||
|
// 초기화 여부
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
|
// 현재 활성화된 탭 ID
|
||||||
|
private string? _currentTabID = null;
|
||||||
|
|
||||||
|
// 부모 TabView 참조
|
||||||
|
private TabView? _parentTabView;
|
||||||
|
|
||||||
|
// 이벤트
|
||||||
|
public UnityEvent<string> OnTabContentLoaded = new UnityEvent<string>();
|
||||||
|
public UnityEvent<string> OnTabContentUnloaded = new UnityEvent<string>();
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_parentTabView = GetComponentInParent<TabView>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 로더를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void InitializeTabLoader()
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
|
if (!lazyLoadTabs)
|
||||||
|
{
|
||||||
|
PreloadAllTabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지연 로드 탭 설정을 추가합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool AddLazyTabContent(string tabID, string prefabPath, object? initialData = null)
|
||||||
|
{
|
||||||
|
LazyTabContent content = new LazyTabContent(tabID, prefabPath, initialData);
|
||||||
|
_lazyContents.Add(content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 지연 로드 설정을 제거합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearLazyContents()
|
||||||
|
{
|
||||||
|
foreach (var content in _lazyContents)
|
||||||
|
{
|
||||||
|
if (content.isLoaded && content.instance != null)
|
||||||
|
{
|
||||||
|
Destroy(content.instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lazyContents.Clear();
|
||||||
|
_isInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠를 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? LoadTabContent(string tabID, object? data = null)
|
||||||
|
{
|
||||||
|
LazyTabContent? contentToLoad = _lazyContents.Find(c => c.tabID == tabID);
|
||||||
|
if (contentToLoad == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"지연 로드할 탭 컨텐츠를 찾을 수 없음: {tabID}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이미 로드된 경우
|
||||||
|
if (contentToLoad.isLoaded && contentToLoad.instance != null)
|
||||||
|
{
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
contentToLoad.contentData = data;
|
||||||
|
UpdateContentData(contentToLoad);
|
||||||
|
}
|
||||||
|
return contentToLoad.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 프리팹 로드
|
||||||
|
GameObject prefab = Resources.Load<GameObject>(contentToLoad.contentPrefabPath);
|
||||||
|
if (prefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"탭 컨텐츠 프리팹을 찾을 수 없음: {contentToLoad.contentPrefabPath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TabView의 ContentContainer를 부모로 사용
|
||||||
|
Transform parentTransform = _parentTabView?.ContentContainer ?? transform;
|
||||||
|
|
||||||
|
// 인스턴스 생성
|
||||||
|
contentToLoad.instance = Instantiate(prefab, parentTransform);
|
||||||
|
contentToLoad.instance.name = $"LazyTabContent_{tabID}";
|
||||||
|
contentToLoad.instance.SetActive(false);
|
||||||
|
contentToLoad.isLoaded = true;
|
||||||
|
|
||||||
|
// 데이터 설정
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
contentToLoad.contentData = data;
|
||||||
|
}
|
||||||
|
UpdateContentData(contentToLoad);
|
||||||
|
|
||||||
|
// 이벤트 발생
|
||||||
|
OnTabContentLoaded.Invoke(tabID);
|
||||||
|
|
||||||
|
Debug.Log($"지연 로드 탭 컨텐츠 로드됨: {tabID}");
|
||||||
|
return contentToLoad.instance;
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"탭 컨텐츠 로드 중 오류 발생: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭 컨텐츠의 데이터를 업데이트합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateContentData(LazyTabContent content)
|
||||||
|
{
|
||||||
|
if (content.instance != null && content.contentData != null)
|
||||||
|
{
|
||||||
|
ITabContent? tabContent = content.instance.GetComponent<ITabContent>();
|
||||||
|
tabContent?.SetContentData(content.contentData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠를 언로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool UnloadTabContent(string tabID)
|
||||||
|
{
|
||||||
|
LazyTabContent? contentToUnload = _lazyContents.Find(c => c.tabID == tabID);
|
||||||
|
if (contentToUnload == null || !contentToUnload.isLoaded || contentToUnload.instance == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Destroy(contentToUnload.instance);
|
||||||
|
contentToUnload.instance = null;
|
||||||
|
contentToUnload.isLoaded = false;
|
||||||
|
|
||||||
|
OnTabContentUnloaded.Invoke(tabID);
|
||||||
|
Debug.Log($"지연 로드 탭 컨텐츠 언로드됨: {tabID}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"탭 컨텐츠 언로드 중 오류 발생: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된 탭을 제외한 모든 탭을 언로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void UnloadAllExceptCurrent(string currentTabID)
|
||||||
|
{
|
||||||
|
foreach (var content in _lazyContents)
|
||||||
|
{
|
||||||
|
if (content.tabID != currentTabID && content.isLoaded)
|
||||||
|
{
|
||||||
|
UnloadTabContent(content.tabID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 탭 컨텐츠를 미리 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void PreloadAllTabs()
|
||||||
|
{
|
||||||
|
foreach (var content in _lazyContents)
|
||||||
|
{
|
||||||
|
if (!content.isLoaded)
|
||||||
|
{
|
||||||
|
LoadTabContent(content.tabID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 데이터를 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetTabContentData(string tabID, object? data)
|
||||||
|
{
|
||||||
|
LazyTabContent? content = _lazyContents.Find(c => c.tabID == tabID);
|
||||||
|
if (content != null)
|
||||||
|
{
|
||||||
|
content.contentData = data;
|
||||||
|
|
||||||
|
if (content.isLoaded && content.instance != null)
|
||||||
|
{
|
||||||
|
UpdateContentData(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭이 로드되었는지 확인합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTabLoaded(string tabID)
|
||||||
|
{
|
||||||
|
LazyTabContent? content = _lazyContents.Find(c => c.tabID == tabID);
|
||||||
|
return content?.isLoaded == true && content.instance != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠 인스턴스를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? GetTabInstance(string tabID)
|
||||||
|
{
|
||||||
|
LazyTabContent? content = _lazyContents.Find(c => c.tabID == tabID);
|
||||||
|
return content?.isLoaded == true ? content.instance : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
foreach (var content in _lazyContents)
|
||||||
|
{
|
||||||
|
if (content.isLoaded && content.instance != null)
|
||||||
|
{
|
||||||
|
Destroy(content.instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabContentLoader.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 19466bc7c0ad9c940820d52eb7e676a6
|
||||||
352
Assets/Scripts/UVC/UI/Tab/TabController.cs
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 시스템을 제어하는 컨트롤러 클래스입니다.
|
||||||
|
/// 설정 관리와 조정 역할만 담당하고, 실제 UI와 로딩은 TabView에서 처리합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <b>사용 예제:</b>
|
||||||
|
/// 1. TabContentConfig 설정
|
||||||
|
/// 2. 컨트롤러 초기화
|
||||||
|
/// <code>
|
||||||
|
/// public class TabSetup : MonoBehaviour
|
||||||
|
/// {
|
||||||
|
/// [SerializeField]
|
||||||
|
/// protected TabController? tabController;
|
||||||
|
///
|
||||||
|
/// protected virtual void Awake()
|
||||||
|
/// {
|
||||||
|
/// if (tabController == null)
|
||||||
|
/// {
|
||||||
|
/// Debug.LogError("TabController가 설정되지 않았습니다.");
|
||||||
|
/// return;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // 코드로 탭 설정하기
|
||||||
|
/// SetupTabs();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void SetupTabs()
|
||||||
|
/// {
|
||||||
|
/// // 1. TabConfig 설정
|
||||||
|
/// tabController?.AddTabConfig("AGV", "AGV", "Prefabs/Factory/UI/Tab/DraggableListContent", null, CreateAGVData(), true);
|
||||||
|
/// tabController?.AddTabConfig("ALARM", "ALARM", "Prefabs/Factory/UI/Tab/DraggableListContent", null, CreateAlarmData(), true);
|
||||||
|
///
|
||||||
|
/// // 2. 컨트롤러 초기화
|
||||||
|
/// tabController?.Initialize();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // 샘플 데이터 생성 메서드들
|
||||||
|
///
|
||||||
|
/// private object CreateAGVData()
|
||||||
|
/// {
|
||||||
|
/// Dictionary<string, string> data = new Dictionary<string, string>();
|
||||||
|
/// return data;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private object CreateAlarmData()
|
||||||
|
/// {
|
||||||
|
/// Dictionary<string, string> data = new Dictionary<string, string>();
|
||||||
|
/// return data;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class TabController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("탭 설정")]
|
||||||
|
[Tooltip("탭 뷰 컴포넌트")]
|
||||||
|
[SerializeField] private TabView? tabView;
|
||||||
|
|
||||||
|
[Header("탭 컨텐츠")]
|
||||||
|
[Tooltip("시작 시 자동 초기화 여부")]
|
||||||
|
[SerializeField] private bool initializeOnStart = false;
|
||||||
|
|
||||||
|
[Tooltip("탭 설정 목록")]
|
||||||
|
[SerializeField] private TabContentConfig[] tabConfigs = new TabContentConfig[0];
|
||||||
|
|
||||||
|
// 탭 모델
|
||||||
|
private TabModel? _tabModel;
|
||||||
|
|
||||||
|
// 초기화 여부
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
|
// 코드로 추가된 탭 설정 저장
|
||||||
|
private List<TabContentConfig> _additionalTabConfigs = new List<TabContentConfig>();
|
||||||
|
|
||||||
|
// 탭 변경 이벤트
|
||||||
|
public event Action<int>? OnTabChanged;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_tabModel = new TabModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (initializeOnStart)
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 코드 기반 탭 설정 관리
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 코드에서 탭 설정을 추가합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool AddTabConfig(string id, string name, string path, Sprite? icon = null, object? data = null, bool useLazyLoading = false)
|
||||||
|
{
|
||||||
|
if (_isInitialized)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("탭 시스템이 이미 초기화되었습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = new TabContentConfig(id, name, path, icon, useLazyLoading, data);
|
||||||
|
_additionalTabConfigs.Add(config);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 탭 설정을 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public List<TabContentConfig> GetAllTabConfigs()
|
||||||
|
{
|
||||||
|
List<TabContentConfig> allConfigs = new List<TabContentConfig>();
|
||||||
|
|
||||||
|
// Inspector 설정 추가
|
||||||
|
foreach (var config in tabConfigs)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(config.tabID) && !string.IsNullOrEmpty(config.contentPath))
|
||||||
|
{
|
||||||
|
allConfigs.Add(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 코드로 추가된 설정 추가
|
||||||
|
allConfigs.AddRange(_additionalTabConfigs);
|
||||||
|
|
||||||
|
return allConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 탭 시스템 초기화 및 관리
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 시스템을 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
if (_isInitialized || _tabModel == null || tabView == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 탭 모델 초기화
|
||||||
|
InitializeModel();
|
||||||
|
|
||||||
|
// TabView에 설정 전달하여 초기화
|
||||||
|
List<TabContentConfig> allConfigs = GetAllTabConfigs();
|
||||||
|
tabView.InitializeTabs(_tabModel.Tabs, allConfigs, OnTabButtonSelected);
|
||||||
|
|
||||||
|
// 모델 이벤트 구독
|
||||||
|
_tabModel.OnTabChanged += HandleTabChanged;
|
||||||
|
|
||||||
|
// 초기 탭 활성화
|
||||||
|
if (_tabModel.Tabs.Count > 0)
|
||||||
|
{
|
||||||
|
HandleTabChanged(_tabModel.ActiveTabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 모델을 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeModel()
|
||||||
|
{
|
||||||
|
if (_tabModel == null) return;
|
||||||
|
|
||||||
|
List<TabContentConfig> allConfigs = GetAllTabConfigs();
|
||||||
|
|
||||||
|
foreach (var config in allConfigs)
|
||||||
|
{
|
||||||
|
_tabModel.AddTab(new TabData(
|
||||||
|
config.tabID,
|
||||||
|
config.tabName,
|
||||||
|
config.contentPath,
|
||||||
|
config.tabIcon,
|
||||||
|
config.initialData
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 버튼 선택 이벤트 처리
|
||||||
|
/// </summary>
|
||||||
|
private void OnTabButtonSelected(int tabIndex)
|
||||||
|
{
|
||||||
|
_tabModel?.SwitchToTab(tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 변경 이벤트 처리
|
||||||
|
/// </summary>
|
||||||
|
private void HandleTabChanged(int newTabIndex)
|
||||||
|
{
|
||||||
|
if (_tabModel == null || tabView == null) return;
|
||||||
|
|
||||||
|
TabData? activeTabData = _tabModel.GetActiveTab();
|
||||||
|
if (activeTabData == null) return;
|
||||||
|
|
||||||
|
// TabView에 탭 변경 전달
|
||||||
|
tabView.UpdateActiveTab(newTabIndex, activeTabData);
|
||||||
|
|
||||||
|
// 외부 이벤트 발생
|
||||||
|
OnTabChanged?.Invoke(newTabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 탭 활성화 및 데이터 관리
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID로 특정 탭을 활성화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ActivateTab(string tabID)
|
||||||
|
{
|
||||||
|
_tabModel?.SwitchToTab(tabID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 인덱스로 특정 탭을 활성화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ActivateTab(int tabIndex)
|
||||||
|
{
|
||||||
|
_tabModel?.SwitchToTab(tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된 탭 인덱스를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
public int GetActiveTabIndex()
|
||||||
|
{
|
||||||
|
return _tabModel?.ActiveTabIndex ?? -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된 탭의 ID를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
public string? GetActiveTabID()
|
||||||
|
{
|
||||||
|
TabData? activeTab = _tabModel?.GetActiveTab();
|
||||||
|
return activeTab?.tabID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 데이터를 설정하고 갱신합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetTabContentData(string tabID, object? data)
|
||||||
|
{
|
||||||
|
if (_tabModel == null || tabView == null) return;
|
||||||
|
|
||||||
|
// 모델 업데이트
|
||||||
|
_tabModel.UpdateTabContentData(tabID, data);
|
||||||
|
|
||||||
|
// TabView에 업데이트 전달
|
||||||
|
int tabIndex = GetTabIndexByID(tabID);
|
||||||
|
if (tabIndex >= 0)
|
||||||
|
{
|
||||||
|
tabView.UpdateTabContentData(tabIndex, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 ID로 인덱스를 찾습니다.
|
||||||
|
/// </summary>
|
||||||
|
private int GetTabIndexByID(string tabID)
|
||||||
|
{
|
||||||
|
if (_tabModel != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _tabModel.Tabs.Count; i++)
|
||||||
|
{
|
||||||
|
if (_tabModel.Tabs[i].tabID == tabID)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TabView 위임 메서드
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠 인스턴스를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? GetTabInstance(string tabID, bool autoLoad = false)
|
||||||
|
{
|
||||||
|
return tabView?.GetTabInstance(tabID, autoLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컴포넌트를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public T? GetTabComponent<T>(string tabID, bool autoLoad = false) where T : Component
|
||||||
|
{
|
||||||
|
return tabView?.GetTabComponent<T>(tabID, autoLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭이 로드되었는지 확인합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTabLoaded(string tabID)
|
||||||
|
{
|
||||||
|
return tabView?.IsTabLoaded(tabID) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된 탭을 제외한 모든 지연 로드 탭을 언로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void UnloadAllExceptCurrent()
|
||||||
|
{
|
||||||
|
int currentIndex = GetActiveTabIndex();
|
||||||
|
tabView?.UnloadAllExceptCurrent(currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠를 언로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool UnloadTabContent(string tabID)
|
||||||
|
{
|
||||||
|
return tabView?.UnloadTabContent(tabID) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 탭 컨텐츠를 미리 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void PreloadAllTabs()
|
||||||
|
{
|
||||||
|
tabView?.PreloadAllTabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (_tabModel != null)
|
||||||
|
{
|
||||||
|
_tabModel.OnTabChanged -= HandleTabChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2d811cbf1956561469a1474f3949ea60
|
||||||
37
Assets/Scripts/UVC/UI/Tab/TabData.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 단일 탭에 대한 데이터를 보관하는 클래스입니다.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class TabData
|
||||||
|
{
|
||||||
|
public string tabID; // 탭 고유 식별자
|
||||||
|
public string tabName; // 탭 표시 이름
|
||||||
|
public string contentPath; // 탭 내용을 담고 있는 Prefab 경로 (Resources 폴더 기준)
|
||||||
|
public Sprite? tabIcon; // 탭 아이콘 (선택사항, null 가능)
|
||||||
|
public object? contentData; // 탭 콘텐츠에 전달할 데이터 객체 (null 가능)
|
||||||
|
|
||||||
|
// 프리팹 경로로 초기화하는 생성자
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 데이터를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">탭의 고유 식별자 (예: "inventory", "settings")</param>
|
||||||
|
/// <param name="name">탭의 표시 이름 (예: "인벤토리", "설정")</param>
|
||||||
|
/// <param name="path">탭 컨텐츠 프리팹의 리소스 경로 (예: "Prefabs/UI/InventoryTab")</param>
|
||||||
|
/// <param name="icon">탭 아이콘 이미지 (선택사항)</param>
|
||||||
|
/// <param name="data">탭 컨텐츠에 전달할 초기 데이터 (선택사항)</param>
|
||||||
|
public TabData(string id, string name, string path, Sprite? icon = null, object? data = null)
|
||||||
|
{
|
||||||
|
tabID = id;
|
||||||
|
tabName = name;
|
||||||
|
contentPath = path;
|
||||||
|
tabIcon = icon;
|
||||||
|
contentData = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 11ef99aba8ccd6245ac008e719d0f81a
|
||||||
183
Assets/Scripts/UVC/UI/Tab/TabModel.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 전체 탭 시스템의 데이터를 관리하는 모델 클래스입니다.
|
||||||
|
/// </summary>
|
||||||
|
public class TabModel
|
||||||
|
{
|
||||||
|
// 모든 탭 목록
|
||||||
|
private List<TabData> _tabs = new List<TabData>();
|
||||||
|
// 현재 활성화된 탭의 인덱스 (-1은 활성화된 탭이 없음을 의미)
|
||||||
|
private int _activeTabIndex = -1;
|
||||||
|
|
||||||
|
// 탭 컨텐츠 인스턴스를 저장하는 Dictionary (키: tabID)
|
||||||
|
private Dictionary<string, GameObject> _contentInstances = new Dictionary<string, GameObject>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된 탭 인덱스를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public int ActiveTabIndex => _activeTabIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 등록된 모든 탭의 목록을 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<TabData> Tabs => _tabs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭이 변경될 때 발생하는 이벤트입니다.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<int>? OnTabChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 새로운 탭을 모델에 추가합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tab">추가할 탭 데이터</param>
|
||||||
|
public void AddTab(TabData tab)
|
||||||
|
{
|
||||||
|
_tabs.Add(tab);
|
||||||
|
|
||||||
|
// 첫 번째 추가된 탭을 기본 활성화 탭으로 설정
|
||||||
|
if (_activeTabIndex == -1 && _tabs.Count == 1)
|
||||||
|
{
|
||||||
|
_activeTabIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 인덱스로 탭을 전환합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabIndex">활성화할 탭의 인덱스</param>
|
||||||
|
public void SwitchToTab(int tabIndex)
|
||||||
|
{
|
||||||
|
// 인덱스 범위 확인
|
||||||
|
if (tabIndex < 0 || tabIndex >= _tabs.Count)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"잘못된 탭 인덱스: {tabIndex}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 같은 탭을 다시 선택한 경우 무시
|
||||||
|
if (_activeTabIndex == tabIndex)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 활성화 탭 인덱스 업데이트
|
||||||
|
_activeTabIndex = tabIndex;
|
||||||
|
|
||||||
|
// 이벤트 발생
|
||||||
|
OnTabChanged?.Invoke(_activeTabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 ID로 탭을 전환합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabID">활성화할 탭의 ID</param>
|
||||||
|
public void SwitchToTab(string tabID)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _tabs.Count; i++)
|
||||||
|
{
|
||||||
|
if (_tabs[i].tabID == tabID)
|
||||||
|
{
|
||||||
|
SwitchToTab(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogWarning($"해당 ID의 탭을 찾을 수 없음: {tabID}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 활성화된 탭 데이터를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>활성화된 탭 데이터 또는 활성화된 탭이 없는 경우 null</returns>
|
||||||
|
public TabData? GetActiveTab()
|
||||||
|
{
|
||||||
|
if (_activeTabIndex >= 0 && _activeTabIndex < _tabs.Count)
|
||||||
|
{
|
||||||
|
return _tabs[_activeTabIndex];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 contentData를 업데이트합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabID">업데이트할 탭의 ID</param>
|
||||||
|
/// <param name="newData">새로운 데이터</param>
|
||||||
|
public void UpdateTabContentData(string tabID, object? newData)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _tabs.Count; i++)
|
||||||
|
{
|
||||||
|
if (_tabs[i].tabID == tabID)
|
||||||
|
{
|
||||||
|
_tabs[i].contentData = newData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 contentData를 업데이트합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabIndex">업데이트할 탭의 인덱스</param>
|
||||||
|
/// <param name="newData">새로운 데이터</param>
|
||||||
|
public void UpdateTabContentData(int tabIndex, object? newData)
|
||||||
|
{
|
||||||
|
if (tabIndex >= 0 && tabIndex < _tabs.Count)
|
||||||
|
{
|
||||||
|
_tabs[tabIndex].contentData = newData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠 인스턴스를 저장합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabID">탭 ID</param>
|
||||||
|
/// <param name="instance">인스턴스화된 컨텐츠 GameObject</param>
|
||||||
|
public void SetContentInstance(string tabID, GameObject instance)
|
||||||
|
{
|
||||||
|
if (_contentInstances.ContainsKey(tabID))
|
||||||
|
{
|
||||||
|
_contentInstances[tabID] = instance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_contentInstances.Add(tabID, instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠 인스턴스를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabID">탭 ID</param>
|
||||||
|
/// <returns>컨텐츠 인스턴스 또는 인스턴스가 없는 경우 null</returns>
|
||||||
|
public GameObject? GetContentInstance(string tabID)
|
||||||
|
{
|
||||||
|
if (_contentInstances.TryGetValue(tabID, out GameObject instance))
|
||||||
|
{
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 인덱스로 특정 탭의 컨텐츠 인스턴스를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabIndex">탭 인덱스</param>
|
||||||
|
/// <returns>컨텐츠 인스턴스 또는 인스턴스가 없는 경우 null</returns>
|
||||||
|
public GameObject? GetContentInstance(int tabIndex)
|
||||||
|
{
|
||||||
|
if (tabIndex >= 0 && tabIndex < _tabs.Count)
|
||||||
|
{
|
||||||
|
return GetContentInstance(_tabs[tabIndex].tabID);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabModel.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 35d6d440b8fa06549b7df3c17961456d
|
||||||
481
Assets/Scripts/UVC/UI/Tab/TabView.cs
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UVC.UI.Tab
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 전체 탭 시스템의 UI를 관리하는 View 클래스입니다.
|
||||||
|
/// TabContentLoader와 협력하여 컨텐츠 로딩도 담당합니다.
|
||||||
|
/// </summary>
|
||||||
|
public class TabView : MonoBehaviour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 전체 탭 시스템의 UI를 관리하는 View 클래스입니다.
|
||||||
|
/// TabContentLoader와 협력하여 컨텐츠 로딩도 담당합니다.
|
||||||
|
/// </summary>
|
||||||
|
[Header("UI 설정")]
|
||||||
|
[Tooltip("탭 버튼들이 생성될 부모 Transform")]
|
||||||
|
[SerializeField] private Transform? tabButtonContainer;
|
||||||
|
|
||||||
|
[Tooltip("탭 컨텐츠들이 표시될 부모 Transform")]
|
||||||
|
[SerializeField] private Transform? contentContainer;
|
||||||
|
|
||||||
|
[Tooltip("탭 버튼 프리팹")]
|
||||||
|
[SerializeField] private GameObject? tabButtonPrefab;
|
||||||
|
|
||||||
|
[Header("컨텐츠 로딩")]
|
||||||
|
[Tooltip("컨텐츠 로더 (지연 로딩 기능, 선택사항)")]
|
||||||
|
[SerializeField] private TabContentLoader? contentLoader;
|
||||||
|
|
||||||
|
// 생성된 탭 버튼 컴포넌트들
|
||||||
|
private List<TabButtonView> _tabButtons = new List<TabButtonView>();
|
||||||
|
|
||||||
|
// 모든 컨텐츠 인스턴스를 저장
|
||||||
|
private List<GameObject?> _allContentInstances = new List<GameObject?>();
|
||||||
|
|
||||||
|
// 탭 설정 정보 (TabController에서 전달받음)
|
||||||
|
private List<TabContentConfig> _tabConfigs = new List<TabContentConfig>();
|
||||||
|
|
||||||
|
// 탭 버튼 클릭 시 호출될 콜백
|
||||||
|
private Action<int>? _onTabButtonClicked;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 컨텐츠 컨테이너를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
public Transform? ContentContainer => contentContainer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 시스템을 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabs">탭 데이터 목록</param>
|
||||||
|
/// <param name="configs">탭 설정 목록 (TabController에서 전달)</param>
|
||||||
|
/// <param name="onTabSelected">탭 선택 시 호출될 콜백</param>
|
||||||
|
public void InitializeTabs(IReadOnlyList<TabData> tabs, List<TabContentConfig> configs, Action<int> onTabSelected)
|
||||||
|
{
|
||||||
|
// 기존 탭 정리
|
||||||
|
ClearTabs();
|
||||||
|
|
||||||
|
_onTabButtonClicked = onTabSelected;
|
||||||
|
_tabConfigs = new List<TabContentConfig>(configs);
|
||||||
|
|
||||||
|
// 컨텐츠 인스턴스 리스트 초기화
|
||||||
|
_allContentInstances.Clear();
|
||||||
|
for (int i = 0; i < tabs.Count; i++)
|
||||||
|
{
|
||||||
|
_allContentInstances.Add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TabContentLoader 초기화 (있는 경우)
|
||||||
|
if (contentLoader != null)
|
||||||
|
{
|
||||||
|
InitializeContentLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 탭 버튼 생성
|
||||||
|
for (int i = 0; i < tabs.Count; i++)
|
||||||
|
{
|
||||||
|
CreateTabButton(i, tabs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 지연 로딩을 사용하지 않는 탭들 미리 로드
|
||||||
|
PreloadNonLazyTabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TabContentLoader를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeContentLoader()
|
||||||
|
{
|
||||||
|
if (contentLoader == null) return;
|
||||||
|
|
||||||
|
// 기존 설정 클리어
|
||||||
|
contentLoader.ClearLazyContents();
|
||||||
|
|
||||||
|
// 지연 로딩 탭들을 ContentLoader에 등록
|
||||||
|
foreach (var config in _tabConfigs)
|
||||||
|
{
|
||||||
|
if (config.useLazyLoading)
|
||||||
|
{
|
||||||
|
contentLoader.AddLazyTabContent(config.tabID, config.contentPath, config.initialData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentLoader 초기화
|
||||||
|
contentLoader.InitializeTabLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지연 로딩을 사용하지 않는 탭들을 미리 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void PreloadNonLazyTabs()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _tabConfigs.Count; i++)
|
||||||
|
{
|
||||||
|
var config = _tabConfigs[i];
|
||||||
|
if (!config.useLazyLoading)
|
||||||
|
{
|
||||||
|
GameObject? instance = LoadTabContentDirectly(config);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
_allContentInstances[i] = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("지연 로딩을 사용하지 않는 탭들이 미리 로드되었습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 컨텐츠를 직접 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
private GameObject? LoadTabContentDirectly(TabContentConfig config)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 프리팹 로드
|
||||||
|
GameObject prefab = Resources.Load<GameObject>(config.contentPath);
|
||||||
|
if (prefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"탭 컨텐츠 프리팹을 찾을 수 없음: {config.contentPath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 인스턴스 생성
|
||||||
|
GameObject instance = Instantiate(prefab, contentContainer);
|
||||||
|
instance.name = $"TabContent_{config.tabID}";
|
||||||
|
instance.SetActive(false);
|
||||||
|
|
||||||
|
// 초기 데이터 전달
|
||||||
|
if (config.initialData != null)
|
||||||
|
{
|
||||||
|
ITabContent? tabContent = instance.GetComponent<ITabContent>();
|
||||||
|
tabContent?.SetContentData(config.initialData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"탭 컨텐츠 직접 로드됨: {config.tabID}");
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"탭 컨텐츠 직접 로드 중 오류 발생: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 버튼을 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void CreateTabButton(int index, TabData tabData)
|
||||||
|
{
|
||||||
|
if (tabButtonPrefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("탭 버튼 프리팹이 설정되지 않았습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabButtonContainer == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("탭 버튼 컨테이너가 설정되지 않았습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 탭 버튼 인스턴스 생성
|
||||||
|
GameObject tabButtonGO = Instantiate(tabButtonPrefab, tabButtonContainer);
|
||||||
|
TabButtonView? tabButton = tabButtonGO.GetComponent<TabButtonView>();
|
||||||
|
|
||||||
|
if (tabButton == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("탭 버튼 프리팹에 TabButtonView 컴포넌트가 없습니다.");
|
||||||
|
Destroy(tabButtonGO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 탭 버튼 설정
|
||||||
|
tabButton.Setup(index, tabData.tabName, tabData.tabIcon, OnTabButtonClicked);
|
||||||
|
_tabButtons.Add(tabButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 버튼 클릭 이벤트 처리
|
||||||
|
/// </summary>
|
||||||
|
private void OnTabButtonClicked(int tabIndex)
|
||||||
|
{
|
||||||
|
_onTabButtonClicked?.Invoke(tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 활성화된 탭을 업데이트합니다.
|
||||||
|
/// 필요한 경우 컨텐츠를 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabIndex">활성화할 탭 인덱스</param>
|
||||||
|
/// <param name="tabData">탭 데이터</param>
|
||||||
|
public void UpdateActiveTab(int tabIndex, TabData tabData)
|
||||||
|
{
|
||||||
|
if (tabIndex < 0 || tabIndex >= _tabConfigs.Count) return;
|
||||||
|
|
||||||
|
var config = _tabConfigs[tabIndex];
|
||||||
|
|
||||||
|
// 컨텐츠가 아직 로드되지 않은 경우 로드
|
||||||
|
if (_allContentInstances[tabIndex] == null)
|
||||||
|
{
|
||||||
|
if (config.useLazyLoading && contentLoader != null)
|
||||||
|
{
|
||||||
|
// 지연 로딩을 통해 로드
|
||||||
|
GameObject? lazyInstance = contentLoader.LoadTabContent(config.tabID, tabData.contentData);
|
||||||
|
if (lazyInstance != null)
|
||||||
|
{
|
||||||
|
// ContentContainer로 이동 (일관성 확보)
|
||||||
|
if (lazyInstance.transform.parent != contentContainer)
|
||||||
|
{
|
||||||
|
lazyInstance.transform.SetParent(contentContainer, false);
|
||||||
|
}
|
||||||
|
_allContentInstances[tabIndex] = lazyInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 직접 로드
|
||||||
|
_allContentInstances[tabIndex] = LoadTabContentDirectly(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 컨텐츠 비활성화
|
||||||
|
foreach (var content in _allContentInstances)
|
||||||
|
{
|
||||||
|
if (content != null)
|
||||||
|
content.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 활성화된 컨텐츠만 활성화
|
||||||
|
GameObject? activeContent = _allContentInstances[tabIndex];
|
||||||
|
if (activeContent != null)
|
||||||
|
{
|
||||||
|
activeContent.SetActive(true);
|
||||||
|
|
||||||
|
// 데이터 업데이트
|
||||||
|
if (tabData.contentData != null)
|
||||||
|
{
|
||||||
|
ITabContent? tabContent = activeContent.GetComponent<ITabContent>();
|
||||||
|
tabContent?.SetContentData(tabData.contentData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 탭 버튼 상태 업데이트
|
||||||
|
for (int i = 0; i < _tabButtons.Count; i++)
|
||||||
|
{
|
||||||
|
if (_tabButtons[i] != null)
|
||||||
|
_tabButtons[i].SetActive(i == tabIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠 데이터를 업데이트합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabIndex">업데이트할 탭 인덱스</param>
|
||||||
|
/// <param name="data">새로운 데이터</param>
|
||||||
|
public void UpdateTabContentData(int tabIndex, object? data)
|
||||||
|
{
|
||||||
|
if (tabIndex < 0 || tabIndex >= _allContentInstances.Count) return;
|
||||||
|
|
||||||
|
GameObject? contentInstance = _allContentInstances[tabIndex];
|
||||||
|
if (contentInstance != null && data != null)
|
||||||
|
{
|
||||||
|
ITabContent? tabContent = contentInstance.GetComponent<ITabContent>();
|
||||||
|
tabContent?.SetContentData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 지연 로딩 탭인 경우 ContentLoader에도 업데이트
|
||||||
|
if (tabIndex < _tabConfigs.Count)
|
||||||
|
{
|
||||||
|
var config = _tabConfigs[tabIndex];
|
||||||
|
if (config.useLazyLoading && contentLoader != null)
|
||||||
|
{
|
||||||
|
contentLoader.SetTabContentData(config.tabID, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컨텐츠 인스턴스를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabID">탭 ID</param>
|
||||||
|
/// <param name="autoLoad">자동 로드 여부</param>
|
||||||
|
/// <returns>컨텐츠 인스턴스</returns>
|
||||||
|
public GameObject? GetTabInstance(string tabID, bool autoLoad = false)
|
||||||
|
{
|
||||||
|
int tabIndex = GetTabIndexByID(tabID);
|
||||||
|
if (tabIndex < 0) return null;
|
||||||
|
|
||||||
|
// 이미 로드된 인스턴스가 있으면 반환
|
||||||
|
if (_allContentInstances[tabIndex] != null)
|
||||||
|
{
|
||||||
|
return _allContentInstances[tabIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 자동 로드가 활성화된 경우
|
||||||
|
if (autoLoad && tabIndex < _tabConfigs.Count)
|
||||||
|
{
|
||||||
|
var config = _tabConfigs[tabIndex];
|
||||||
|
|
||||||
|
if (config.useLazyLoading && contentLoader != null)
|
||||||
|
{
|
||||||
|
// 지연 로딩을 통해 로드
|
||||||
|
GameObject? instance = contentLoader.LoadTabContent(tabID);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
// ContentContainer로 이동
|
||||||
|
if (instance.transform.parent != contentContainer)
|
||||||
|
{
|
||||||
|
instance.transform.SetParent(contentContainer, false);
|
||||||
|
}
|
||||||
|
_allContentInstances[tabIndex] = instance;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 직접 로드
|
||||||
|
GameObject? instance = LoadTabContentDirectly(config);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
_allContentInstances[tabIndex] = instance;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭의 컴포넌트를 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
public T? GetTabComponent<T>(string tabID, bool autoLoad = false) where T : Component
|
||||||
|
{
|
||||||
|
GameObject? instance = GetTabInstance(tabID, autoLoad);
|
||||||
|
return instance?.GetComponent<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 탭 ID로 인덱스를 찾습니다.
|
||||||
|
/// </summary>
|
||||||
|
private int GetTabIndexByID(string tabID)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _tabConfigs.Count; i++)
|
||||||
|
{
|
||||||
|
if (_tabConfigs[i].tabID == tabID)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 탭이 로드되었는지 확인합니다.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTabLoaded(string tabID)
|
||||||
|
{
|
||||||
|
int tabIndex = GetTabIndexByID(tabID);
|
||||||
|
if (tabIndex < 0) return false;
|
||||||
|
|
||||||
|
return _allContentInstances[tabIndex] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ContentLoader 관련 메서드들 (있는 경우에만 동작)
|
||||||
|
/// </summary>
|
||||||
|
public void UnloadAllExceptCurrent(int currentTabIndex)
|
||||||
|
{
|
||||||
|
if (contentLoader != null && currentTabIndex < _tabConfigs.Count)
|
||||||
|
{
|
||||||
|
string currentTabID = _tabConfigs[currentTabIndex].tabID;
|
||||||
|
contentLoader.UnloadAllExceptCurrent(currentTabID);
|
||||||
|
|
||||||
|
// 언로드된 탭들의 인스턴스 참조도 제거
|
||||||
|
for (int i = 0; i < _allContentInstances.Count; i++)
|
||||||
|
{
|
||||||
|
if (i != currentTabIndex && _tabConfigs[i].useLazyLoading)
|
||||||
|
{
|
||||||
|
_allContentInstances[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnloadTabContent(string tabID)
|
||||||
|
{
|
||||||
|
if (contentLoader != null)
|
||||||
|
{
|
||||||
|
bool result = contentLoader.UnloadTabContent(tabID);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
// 인스턴스 참조도 제거
|
||||||
|
int tabIndex = GetTabIndexByID(tabID);
|
||||||
|
if (tabIndex >= 0)
|
||||||
|
{
|
||||||
|
_allContentInstances[tabIndex] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PreloadAllTabs()
|
||||||
|
{
|
||||||
|
if (contentLoader != null)
|
||||||
|
{
|
||||||
|
contentLoader.PreloadAllTabs();
|
||||||
|
|
||||||
|
// 지연 로딩 탭들의 인스턴스를 _allContentInstances에 등록
|
||||||
|
for (int i = 0; i < _tabConfigs.Count; i++)
|
||||||
|
{
|
||||||
|
var config = _tabConfigs[i];
|
||||||
|
if (config.useLazyLoading)
|
||||||
|
{
|
||||||
|
GameObject? instance = contentLoader.GetTabInstance(config.tabID);
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
// ContentContainer로 이동
|
||||||
|
if (instance.transform.parent != contentContainer)
|
||||||
|
{
|
||||||
|
instance.transform.SetParent(contentContainer, false);
|
||||||
|
}
|
||||||
|
_allContentInstances[i] = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 탭과 관련 게임오브젝트를 정리합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void ClearTabs()
|
||||||
|
{
|
||||||
|
// 탭 버튼 제거
|
||||||
|
foreach (var button in _tabButtons)
|
||||||
|
{
|
||||||
|
if (button != null)
|
||||||
|
Destroy(button.gameObject);
|
||||||
|
}
|
||||||
|
_tabButtons.Clear();
|
||||||
|
|
||||||
|
// 컨텐츠 인스턴스 제거
|
||||||
|
foreach (var content in _allContentInstances)
|
||||||
|
{
|
||||||
|
if (content != null)
|
||||||
|
Destroy(content);
|
||||||
|
}
|
||||||
|
_allContentInstances.Clear();
|
||||||
|
|
||||||
|
// 설정 클리어
|
||||||
|
_tabConfigs.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/UVC/UI/Tab/TabView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d5346e2ecd480134fa0aac7853ba41bb
|
||||||
@@ -19,88 +19,151 @@ namespace UVC.UI
|
|||||||
/// using UnityEngine;
|
/// using UnityEngine;
|
||||||
/// using UVC.UI;
|
/// using UVC.UI;
|
||||||
///
|
///
|
||||||
/// public class DraggableWindowController : MonoBehaviour
|
///public class DraggableWindowController : MonoBehaviour
|
||||||
/// {
|
///{
|
||||||
/// // UIDragger 컴포넌트가 부착된 드래그 핸들 오브젝트
|
/// [SerializeField] private UIDragger draggerHandle;
|
||||||
/// public UIDragger draggerHandle;
|
/// [SerializeField] private RectTransform customDragArea;
|
||||||
///
|
///
|
||||||
/// void Start()
|
/// private void Start()
|
||||||
/// {
|
/// {
|
||||||
/// // 드래그 시작 이벤트 구독
|
/// // 커스텀 드래그 영역 설정
|
||||||
/// draggerHandle.onBeginDragHandler += () =>
|
/// if (customDragArea != null)
|
||||||
/// {
|
/// {
|
||||||
/// Debug.Log("창 드래그가 시작되었습니다!");
|
/// draggerHandle.SetDragArea(customDragArea);
|
||||||
/// };
|
/// }
|
||||||
///
|
///
|
||||||
/// // 드래그 종료 이벤트 구독
|
/// // 이벤트 구독
|
||||||
/// draggerHandle.OnEndDragHandler += pos =>
|
/// draggerHandle.OnBeginDragHandler += OnDragStart;
|
||||||
/// {
|
/// draggerHandle.OnDragHandler += OnDragging;
|
||||||
/// Debug.Log($"창 드래그가 종료되었습니다! 최종 위치: {pos}");
|
/// draggerHandle.OnEndDragHandler += OnDragEnd;
|
||||||
/// };
|
///
|
||||||
/// }
|
/// // 창을 중앙으로 이동
|
||||||
/// }
|
/// draggerHandle.CenterInDragArea();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void OnDragStart()
|
||||||
|
/// {
|
||||||
|
/// Debug.Log("창 드래그 시작!");
|
||||||
|
/// // 드래그 시작 시 추가 로직
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void OnDragging(Vector2 position)
|
||||||
|
/// {
|
||||||
|
/// // 드래그 중 실시간 처리
|
||||||
|
/// Debug.Log($"드래그 중: {position}");
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void OnDragEnd(Vector2 finalPosition)
|
||||||
|
/// {
|
||||||
|
/// Debug.Log($"드래그 완료! 최종 위치: {finalPosition}");
|
||||||
|
/// // 위치 저장 등의 후처리
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // 런타임에서 드래그 기능 제어
|
||||||
|
/// public void ToggleDragging()
|
||||||
|
/// {
|
||||||
|
/// draggerHandle.SetDraggingEnabled(!draggerHandle.enabled);
|
||||||
|
/// }
|
||||||
|
///}
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public class UIDragger : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
public class UIDragger : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||||
{
|
{
|
||||||
[Header("드래그 설정")]
|
[Header("드래그 설정")]
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("드래그 가능한 영역을 지정합니다. 이 영역 내에서 드래그가 가능합니다.")]
|
[Tooltip("드래그 가능한 영역을 지정합니다. null인 경우 Canvas를 자동으로 찾습니다.")]
|
||||||
private RectTransform dragArea; // 드래그가 가능한 영역
|
private RectTransform dragArea; // 드래그가 가능한 영역
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("드래그할 UI 요소를 지정합니다. 이 요소가 실제로 드래그됩니다.")]
|
[Tooltip("드래그할 UI 요소를 지정합니다. null인 경우 부모를 자동으로 설정합니다.")]
|
||||||
private RectTransform dragObject; // 실제로 드래그될 UI 요소 (예: 창 전체)
|
private RectTransform dragObject; // 실제로 드래그될 UI 요소 (예: 창 전체)
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("드래그 시작 시 해당 UI 요소를 맨 앞으로 가져올지 여부를 설정합니다.")]
|
[Tooltip("드래그 시작 시 해당 UI 요소를 맨 앞으로 가져올지 여부를 설정합니다.")]
|
||||||
private bool topOnDrag = true; // 드래그 시작 시 맨 앞으로 가져올지 여부
|
private bool bringToFrontOnDrag = true; // 드래그 시작 시 맨 앞으로 가져올지 여부
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
[Tooltip("드래그 영역 제한 시 사용할 최소 높이값")]
|
[Tooltip("드래그 영역 제한 시 사용할 최소 높이값")]
|
||||||
private float yMinHeight = 30;
|
private float yMinHeight = 0;
|
||||||
|
|
||||||
private Vector2 originalLocalPointerPosition; // 드래그 시작 시 마우스 포인터의 로컬 위치
|
[SerializeField]
|
||||||
private Vector3 originalPanelLocalPosition; // 드래그 시작 시 패널의 로컬 위치
|
[Tooltip("드래그 중 실시간으로 영역 제한을 적용할지 여부")]
|
||||||
|
private bool constrainDuringDrag = true;
|
||||||
|
|
||||||
/// <summary>
|
// 이벤트
|
||||||
/// 드래그가 끝났을 때 발생하는 이벤트입니다. 최종 위치 정보를 전달합니다.
|
public Action OnBeginDragHandler { get; set; }
|
||||||
/// </summary>
|
public Action<Vector2> OnDragHandler { get; set; }
|
||||||
public Action<Vector3> OnEndDragHandler { get; set; }
|
public Action<Vector2> OnEndDragHandler { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
// 캐시된 변수들
|
||||||
/// 드래그가 시작될 때 발생하는 액션입니다.
|
private Vector2 originalLocalPointerPosition;
|
||||||
/// </summary>
|
private Vector2 originalAnchoredPosition;
|
||||||
public Action onBeginDragHandler;
|
private int originalSiblingIndex;
|
||||||
|
private Canvas parentCanvas;
|
||||||
|
private Camera canvasCamera;
|
||||||
|
|
||||||
|
// 프로퍼티
|
||||||
// 원래의 형제 순서(UI 렌더링 순서)
|
public RectTransform DragObject => dragObject;
|
||||||
private int baseSibling;
|
public RectTransform DragArea => dragArea;
|
||||||
|
public bool IsDragging { private set; get; }
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
|
InitializeComponents();
|
||||||
|
ValidateSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 컴포넌트들을 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponents()
|
||||||
|
{
|
||||||
|
|
||||||
// dragObject가 설정되지 않았다면, 부모를 드래그 대상으로 설정
|
// dragObject가 설정되지 않았다면, 부모를 드래그 대상으로 설정
|
||||||
if (dragObject == null)
|
if (dragObject == null)
|
||||||
{
|
{
|
||||||
dragObject = transform.parent as RectTransform;
|
dragObject = transform.parent as RectTransform;
|
||||||
if (dragObject == null)
|
|
||||||
{
|
|
||||||
Debug.LogError("<b>[UIDragger]</b> 드래그할 객체(dragObject)를 찾을 수 없습니다. 부모가 RectTransform이 아닙니다.", this);
|
|
||||||
enabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dragArea가 설정되지 않았다면, 최상위 Canvas를 드래그 영역으로 설정
|
// Canvas와 Camera 캐싱
|
||||||
if (dragArea == null)
|
parentCanvas = GetComponentInParent<Canvas>();
|
||||||
|
if (parentCanvas != null)
|
||||||
{
|
{
|
||||||
dragArea = GetComponentInParent<Canvas>()?.transform as RectTransform;
|
canvasCamera = parentCanvas.worldCamera;
|
||||||
|
|
||||||
|
// dragArea가 설정되지 않았다면, Canvas를 드래그 영역으로 설정
|
||||||
if (dragArea == null)
|
if (dragArea == null)
|
||||||
{
|
{
|
||||||
Debug.Log("<b>[UIDragger]</b> 드래그 영역(dragArea)으로 사용할 Canvas를 찾을 수 없습니다.", this);
|
dragArea = parentCanvas.transform as RectTransform;
|
||||||
enabled = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 설정이 올바른지 검증합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void ValidateSetup()
|
||||||
|
{
|
||||||
|
if (dragObject == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[UIDragger] dragObject를 찾을 수 없습니다. 부모가 RectTransform이 아닙니다.", this);
|
||||||
|
enabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dragArea == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[UIDragger] dragArea를 찾을 수 없습니다. Canvas를 찾을 수 없습니다.", this);
|
||||||
|
enabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentCanvas == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[UIDragger] Canvas를 찾을 수 없습니다. 드래그 기능이 제한될 수 있습니다.", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 드래그가 허용되는 영역을 설정합니다.
|
/// 드래그가 허용되는 영역을 설정합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -109,43 +172,84 @@ namespace UVC.UI
|
|||||||
/// <param name="area">드래그 영역의 경계를 정의하는 <see cref="RectTransform"/>입니다. null일 수 없습니다.</param>
|
/// <param name="area">드래그 영역의 경계를 정의하는 <see cref="RectTransform"/>입니다. null일 수 없습니다.</param>
|
||||||
public void SetDragArea(RectTransform area)
|
public void SetDragArea(RectTransform area)
|
||||||
{
|
{
|
||||||
|
if (area == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[UIDragger] null인 dragArea를 설정하려고 했습니다.", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dragArea = area;
|
dragArea = area;
|
||||||
enabled = true;
|
enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 대상을 동적으로 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetDragObject(RectTransform target)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[UIDragger] null인 dragObject를 설정하려고 했습니다.", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragObject = target;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 드래그가 시작될 때 호출됩니다. (IBeginDragHandler)
|
/// 드래그가 시작될 때 호출됩니다. (IBeginDragHandler)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnBeginDrag(PointerEventData data)
|
public void OnBeginDrag(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
if (data.button != PointerEventData.InputButton.Left) return;
|
if (eventData.button != PointerEventData.InputButton.Left) return;
|
||||||
|
if (!IsValidForDrag()) return;
|
||||||
|
|
||||||
originalPanelLocalPosition = dragObject.localPosition;
|
IsDragging = true;
|
||||||
baseSibling = dragObject.GetSiblingIndex();
|
originalAnchoredPosition = dragObject.anchoredPosition;
|
||||||
|
originalSiblingIndex = dragObject.GetSiblingIndex();
|
||||||
|
|
||||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(dragArea, data.position, data.pressEventCamera, out originalLocalPointerPosition);
|
// 마우스 포인터의 로컬 위치 계산
|
||||||
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||||
|
dragArea,
|
||||||
|
eventData.position,
|
||||||
|
canvasCamera,
|
||||||
|
out originalLocalPointerPosition);
|
||||||
|
|
||||||
if (topOnDrag)
|
// 맨 앞으로 가져오기
|
||||||
|
if (bringToFrontOnDrag)
|
||||||
{
|
{
|
||||||
dragObject.SetAsLastSibling();
|
dragObject.SetAsLastSibling();
|
||||||
}
|
}
|
||||||
onBeginDragHandler?.Invoke();
|
|
||||||
|
OnBeginDragHandler?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 드래그 중일 때 매 프레임 호출됩니다. (IDragHandler)
|
/// 드래그 중일 때 매 프레임 호출됩니다. (IDragHandler)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnDrag(PointerEventData data)
|
public void OnDrag(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
if (data.button != PointerEventData.InputButton.Left) return;
|
if (eventData.button != PointerEventData.InputButton.Left) return;
|
||||||
|
if (!IsDragging || !IsValidForDrag()) return;
|
||||||
|
|
||||||
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(dragArea, data.position, data.pressEventCamera, out Vector2 localPointerPosition))
|
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||||
|
dragArea,
|
||||||
|
eventData.position,
|
||||||
|
canvasCamera,
|
||||||
|
out Vector2 localPointerPosition))
|
||||||
{
|
{
|
||||||
Vector3 offsetToOriginal = localPointerPosition - originalLocalPointerPosition;
|
Vector2 offsetToOriginal = localPointerPosition - originalLocalPointerPosition;
|
||||||
dragObject.localPosition = originalPanelLocalPosition + offsetToOriginal;
|
Vector2 newPosition = originalAnchoredPosition + offsetToOriginal;
|
||||||
}
|
|
||||||
|
|
||||||
ClampToArea();
|
// 실시간 제약 적용
|
||||||
|
if (constrainDuringDrag)
|
||||||
|
{
|
||||||
|
newPosition = ClampToArea(newPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragObject.anchoredPosition = newPosition;
|
||||||
|
OnDragHandler?.Invoke(newPosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -154,34 +258,100 @@ namespace UVC.UI
|
|||||||
public void OnEndDrag(PointerEventData eventData)
|
public void OnEndDrag(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
if (eventData.button != PointerEventData.InputButton.Left) return;
|
if (eventData.button != PointerEventData.InputButton.Left) return;
|
||||||
|
if (!IsDragging) return;
|
||||||
|
|
||||||
if (topOnDrag)
|
IsDragging = false;
|
||||||
|
|
||||||
|
// 원래 형제 순서로 복원
|
||||||
|
if (bringToFrontOnDrag)
|
||||||
{
|
{
|
||||||
dragObject.SetSiblingIndex(baseSibling);
|
dragObject.SetSiblingIndex(originalSiblingIndex);
|
||||||
}
|
}
|
||||||
OnEndDragHandler?.Invoke(dragObject.anchoredPosition);
|
|
||||||
|
// 최종 위치 제약 적용
|
||||||
|
Vector2 finalPosition = ClampToArea(dragObject.anchoredPosition);
|
||||||
|
dragObject.anchoredPosition = finalPosition;
|
||||||
|
|
||||||
|
OnEndDragHandler?.Invoke(finalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그가 가능한 상태인지 확인합니다.
|
||||||
|
/// </summary>
|
||||||
|
private bool IsValidForDrag()
|
||||||
|
{
|
||||||
|
return dragObject != null && dragArea != null && enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UI 요소가 드래그 영역 내에 있도록 위치를 제한합니다.
|
/// UI 요소가 드래그 영역 내에 있도록 위치를 제한합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ClampToArea()
|
private Vector2 ClampToArea(Vector2 position)
|
||||||
{
|
{
|
||||||
Vector3 pos = dragObject.localPosition;
|
if (dragArea == null || dragObject == null)
|
||||||
|
return position;
|
||||||
|
|
||||||
Rect dragObjectRect = dragObject.rect;
|
Rect dragObjectRect = dragObject.rect;
|
||||||
Rect dragAreaRect = dragArea.rect;
|
Rect dragAreaRect = dragArea.rect;
|
||||||
|
|
||||||
// Pivot을 기준으로 최소/최대 위치를 계산합니다.
|
// Pivot과 앵커를 고려한 경계 계산
|
||||||
float minX = dragAreaRect.xMin - dragObjectRect.xMin;
|
Vector2 pivot = dragObject.pivot;
|
||||||
float maxX = dragAreaRect.xMax - dragObjectRect.xMax;
|
Vector2 size = dragObjectRect.size;
|
||||||
float minY = dragAreaRect.yMin - dragObjectRect.yMin;
|
|
||||||
float maxY = dragAreaRect.yMax - dragObjectRect.yMax;
|
|
||||||
|
|
||||||
pos.x = Mathf.Clamp(pos.x, minX, maxX);
|
float leftBoundary = dragAreaRect.xMin + (size.x * pivot.x);
|
||||||
pos.y = Mathf.Clamp(pos.y, minY, maxY - yMinHeight);
|
float rightBoundary = dragAreaRect.xMax - (size.x * (1f - pivot.x));
|
||||||
|
float bottomBoundary = dragAreaRect.yMin + (size.y * pivot.y) + yMinHeight;
|
||||||
|
float topBoundary = dragAreaRect.yMax - (size.y * (1f - pivot.y));
|
||||||
|
|
||||||
dragObject.localPosition = pos;
|
position.x = Mathf.Clamp(position.x, leftBoundary, rightBoundary);
|
||||||
|
position.y = Mathf.Clamp(position.y, bottomBoundary, topBoundary);
|
||||||
|
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 객체를 특정 위치로 이동시킵니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetPosition(Vector2 position, bool clampToArea = true)
|
||||||
|
{
|
||||||
|
if (dragObject == null) return;
|
||||||
|
|
||||||
|
if (clampToArea)
|
||||||
|
{
|
||||||
|
position = ClampToArea(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragObject.anchoredPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 객체를 중앙으로 이동시킵니다.
|
||||||
|
/// </summary>
|
||||||
|
public void CenterInDragArea()
|
||||||
|
{
|
||||||
|
if (dragArea == null || dragObject == null) return;
|
||||||
|
|
||||||
|
Vector2 centerPosition = dragArea.rect.center;
|
||||||
|
SetPosition(centerPosition, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 기능을 활성화/비활성화합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetDraggingEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
// 에디터에서 값 변경 시 유효성 검사
|
||||||
|
if (Application.isPlaying)
|
||||||
|
{
|
||||||
|
ValidateSetup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||