From f1c8b49d52bc7b9131569c69ed912fec7b0de4e3 Mon Sep 17 00:00:00 2001 From: NTG_Lenovo Date: Tue, 17 Jun 2025 20:47:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=90=EB=8F=99=ED=99=94=20=ED=88=B4=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/ES3/ES3Defaults.asset | 4 + .../Scripts/PlanarReflectionsRenderer.cs | 3 +- .../_Datas/02.Scripts/AssetPostprocessor.meta | 8 + .../AssetPostprocessor/AssetPostProcessor.cs | 77 ++ .../AssetPostProcessor.cs.meta | 2 + .../AssetPostprocessorSprite.cs | 290 ++++ .../AssetPostprocessorSprite.cs.meta | 2 + Assets/_Datas/02.Scripts/Utils.cs | 45 + Assets/_Datas/02.Scripts/Utils.cs.meta | 2 + Assets/_Datas/Addressables.meta | 8 + Assets/_Datas/Addressables/Barrel.prefab | 121 ++ Assets/_Datas/Addressables/Barrel.prefab.meta | 7 + .../_Datas/Addressables/Sprites.spriteatlasv2 | 16 + .../Addressables/Sprites.spriteatlasv2.meta | 30 + Assets/_Datas/Raw.meta | 8 + Assets/_Datas/Raw/Sprites.meta | 8 + Assets/_Datas/Raw/Sprites/Barrel.png | Bin 0 -> 145825 bytes Assets/_Datas/Raw/Sprites/Barrel.png.meta | 143 ++ Assets/_Datas/SLShared.meta | 8 + Assets/_Datas/SLShared/Directory.Build.props | 9 + .../SLShared/Directory.Build.props.meta | 7 + Assets/_Datas/SLShared/SLLogger.meta | 8 + Assets/_Datas/SLShared/SLLogger/ISLLogger.cs | 11 + .../SLShared/SLLogger/ISLLogger.cs.meta | 11 + Assets/_Datas/SLShared/SLLogger/SLLog.cs | 49 + Assets/_Datas/SLShared/SLLogger/SLLog.cs.meta | 11 + .../_Datas/SLShared/SLLogger/SLLogger.asmdef | 3 + .../SLShared/SLLogger/SLLogger.asmdef.meta | 7 + .../SLShared/SLLogger/SLLogger.csproj.meta | 7 + Assets/_Datas/SLShared/SLLogger/package.json | 9 + .../SLShared/SLLogger/package.json.meta | 7 + Assets/_Datas/SLShared/SLSystem.meta | 8 + .../SLSystem/ComponentOrderAttribute.cs | 19 + .../SLSystem/ComponentOrderAttribute.cs.meta | 11 + Assets/_Datas/SLShared/SLSystem/Evaluator.cs | 135 ++ .../SLShared/SLSystem/Evaluator.cs.meta | 2 + .../SLShared/SLSystem/SLDateTimeUtil.cs | 209 +++ .../SLShared/SLSystem/SLDateTimeUtil.cs.meta | 11 + Assets/_Datas/SLShared/SLSystem/SLEntity.meta | 8 + .../SLShared/SLSystem/SLEntity/Container.cs | 670 ++++++++++ .../SLSystem/SLEntity/Container.cs.meta | 11 + .../SLShared/SLSystem/SLEntity/SLEntity.cs | 941 +++++++++++++ .../SLSystem/SLEntity/SLEntity.cs.meta | 11 + .../SLShared/SLSystem/SLEntity/Utility.cs | 174 +++ .../SLSystem/SLEntity/Utility.cs.meta | 11 + .../SLShared/SLSystem/SLEntity/Values.cs | 214 +++ .../SLShared/SLSystem/SLEntity/Values.cs.meta | 11 + .../SLShared/SLSystem/SLEntitySpace.meta | 8 + .../SLSystem/SLEntitySpace/Loader.meta | 8 + .../SLEntitySpace/Loader/JsonLoader.cs | 476 +++++++ .../SLEntitySpace/Loader/JsonLoader.cs.meta | 11 + .../SLEntitySpace/Loader/XMLLoader.cs | 31 + .../SLEntitySpace/Loader/XMLLoader.cs.meta | 11 + .../SLEntitySpace/Loader/YamlLoader.cs | 207 +++ .../SLEntitySpace/Loader/YamlLoader.cs.meta | 11 + .../SLSystem/SLEntitySpace/SLDataLoader.cs | 21 + .../SLEntitySpace/SLDataLoader.cs.meta | 11 + .../SLSystem/SLEntitySpace/SLWindowsLoader.cs | 83 ++ .../SLEntitySpace/SLWindowsLoader.cs.meta | 11 + Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs | 74 + .../SLShared/SLSystem/SLLinqUtil.cs.meta | 11 + Assets/_Datas/SLShared/SLSystem/SLRandom.cs | 38 + .../_Datas/SLShared/SLSystem/SLRandom.cs.meta | 11 + .../_Datas/SLShared/SLSystem/SLSystem.asmdef | 16 + .../SLShared/SLSystem/SLSystem.asmdef.meta | 7 + Assets/_Datas/SLShared/SLSystem/SLSystem.cs | 41 + .../_Datas/SLShared/SLSystem/SLSystem.cs.meta | 11 + .../SLShared/SLSystem/SLSystem.csproj.meta | 7 + .../SLShared/SLSystem/SLSystemUtility.cs | 197 +++ .../SLShared/SLSystem/SLSystemUtility.cs.meta | 11 + .../SLShared/SLSystem/UnitComponent.meta | 8 + Assets/_Datas/SLShared/SLSystem/package.json | 11 + .../SLShared/SLSystem/package.json.meta | 7 + Assets/_Datas/SLShared/SLUnity.meta | 8 + .../SLShared/SLUnity/CustomSceneCamera.cs | 20 + .../SLUnity/CustomSceneCamera.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/Editor.meta | 8 + .../Editor/FixTexturePlatformSettings.cs | 39 + .../Editor/FixTexturePlatformSettings.cs.meta | 2 + .../SLUnity/Editor/SLAssetPostprocessor.cs | 124 ++ .../Editor/SLAssetPostprocessor.cs.meta | 11 + .../Editor/SLAssetPostprocessorAnim.cs | 79 ++ .../Editor/SLAssetPostprocessorAnim.cs.meta | 11 + .../Editor/SLAssetPostprocessorEffect.cs | 79 ++ .../Editor/SLAssetPostprocessorEffect.cs.meta | 11 + .../Editor/SLAssetPostprocessorModel.cs | 200 +++ .../Editor/SLAssetPostprocessorModel.cs.meta | 11 + .../Editor/SLAssetPostprocessorScene.cs | 36 + .../Editor/SLAssetPostprocessorScene.cs.meta | 11 + .../Editor/SLAssetPostprocessorSprite.cs | 180 +++ .../Editor/SLAssetPostprocessorSprite.cs.meta | 11 + .../SLShared/SLUnity/Editor/SLBuild.meta | 8 + .../SLUnity/Editor/SLBuild/SLAppBuild.cs | 345 +++++ .../SLUnity/Editor/SLBuild/SLAppBuild.cs.meta | 11 + .../SLUnity/Editor/SLBuild/SLBuildMenu.cs | 34 + .../Editor/SLBuild/SLBuildMenu.cs.meta | 11 + .../SLShared/SLUnity/Editor/SLFileUtility.cs | 206 +++ .../SLUnity/Editor/SLFileUtility.cs.meta | 11 + .../SLUnity/Editor/SLResourceObjectEditor.cs | 24 + .../Editor/SLResourceObjectEditor.cs.meta | 11 + .../_Datas/SLShared/SLUnity/Editor/SLUI.meta | 8 + .../Editor/SLUI/SLBindingEntityView.cs | 123 ++ .../Editor/SLUI/SLBindingEntityView.cs.meta | 11 + .../SLUnity/Editor/SLUI/SLScreenStick.cs | 79 ++ .../SLUnity/Editor/SLUI/SLScreenStick.cs.meta | 11 + .../SLUnity/Editor/SLUI/SLUIButtonEditor.cs | 95 ++ .../Editor/SLUI/SLUIButtonEditor.cs.meta | 11 + .../Editor/SLUI/SLUIComponentEditor.cs | 22 + .../Editor/SLUI/SLUIComponentEditor.cs.meta | 11 + .../Editor/SLUI/SLUIDragButtonEditor.cs | 111 ++ .../Editor/SLUI/SLUIDragButtonEditor.cs.meta | 2 + .../SLUnity/Editor/SLUI/SLUIEntityEditor.cs | 124 ++ .../Editor/SLUI/SLUIEntityEditor.cs.meta | 11 + .../SLUnity/Editor/SLUI/SLUIFadeEditor.cs | 32 + .../Editor/SLUI/SLUIFadeEditor.cs.meta | 11 + .../Editor/SLUI/SLUIInputFieldEditor.cs | 82 ++ .../Editor/SLUI/SLUIInputFieldEditor.cs.meta | 11 + .../SLUnity/Editor/SLUI/SLUIListEditor.cs | 99 ++ .../Editor/SLUI/SLUIListEditor.cs.meta | 11 + .../SLUnity/Editor/SLUI/SLUIResourceEditor.cs | 46 + .../Editor/SLUI/SLUIResourceEditor.cs.meta | 11 + .../SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs | 27 + .../SLUnity/Editor/SLUI/SLUIUtil.cs.meta | 11 + .../SLUnity/Editor/SLUI/SLUIValueEditor.cs | 135 ++ .../Editor/SLUI/SLUIValueEditor.cs.meta | 11 + .../Editor/SLUI/SLValueComparisonInspector.cs | 54 + .../SLUI/SLValueComparisonInspector.cs.meta | 11 + .../SLUnity/Editor/SLUnityEditor.asmdef | 27 + .../SLUnity/Editor/SLUnityEditor.asmdef.meta | 7 + .../SLUnity/Editor/SLUnityEditor.csproj.meta | 7 + .../SLUnity/Editor/SLUnityInspector.cs | 392 ++++++ .../SLUnity/Editor/SLUnityInspector.cs.meta | 11 + .../SLShared/SLUnity/Editor/SLUnityLogger.cs | 127 ++ .../SLUnity/Editor/SLUnityLogger.cs.meta | 11 + .../_Datas/SLShared/SLUnity/Editor/Tools.meta | 8 + .../SLUnity/Editor/Tools/AnimatorUtil.cs | 58 + .../SLUnity/Editor/Tools/AnimatorUtil.cs.meta | 11 + .../SLShared/SLUnity/SLContextController.cs | 13 + .../SLUnity/SLContextController.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLGame.cs | 483 +++++++ Assets/_Datas/SLShared/SLUnity/SLGame.cs.meta | 11 + .../SLShared/SLUnity/SLGameComponent.cs | 13 + .../SLShared/SLUnity/SLGameComponent.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs | 206 +++ .../SLShared/SLUnity/SLGameSaver.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLOption.cs | 64 + .../_Datas/SLShared/SLUnity/SLOption.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLResource.cs | 200 +++ .../SLShared/SLUnity/SLResource.cs.meta | 11 + .../SLShared/SLUnity/SLResourceObject.cs | 188 +++ .../SLShared/SLUnity/SLResourceObject.cs.meta | 11 + .../SLShared/SLUnity/SLSceneController.cs | 160 +++ .../SLUnity/SLSceneController.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLSound.cs | 295 ++++ .../_Datas/SLShared/SLUnity/SLSound.cs.meta | 11 + .../SLShared/SLUnity/SLStateController.cs | 13 + .../SLUnity/SLStateController.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLString.cs | 394 ++++++ .../_Datas/SLShared/SLUnity/SLString.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLTag.cs | 345 +++++ Assets/_Datas/SLShared/SLUnity/SLTag.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLUI.meta | 8 + .../SLShared/SLUnity/SLUI/SLUI2DPosition.cs | 55 + .../SLUnity/SLUI/SLUI2DPosition.cs.meta | 2 + .../SLShared/SLUnity/SLUI/SLUIButton.cs | 351 +++++ .../SLShared/SLUnity/SLUI/SLUIButton.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUICanvas.cs | 86 ++ .../SLShared/SLUnity/SLUI/SLUICanvas.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIColorSelect.cs | 63 + .../SLUnity/SLUI/SLUIColorSelect.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIComponent.cs | 72 + .../SLUnity/SLUI/SLUIComponent.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIDrag.cs | 162 +++ .../SLShared/SLUnity/SLUI/SLUIDrag.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIDragButton.cs | 550 ++++++++ .../SLUnity/SLUI/SLUIDragButton.cs.meta | 2 + .../SLShared/SLUnity/SLUI/SLUIDragClick.cs | 139 ++ .../SLUnity/SLUI/SLUIDragClick.cs.meta | 2 + .../SLUnity/SLUI/SLUIDragClickTarget.cs | 41 + .../SLUnity/SLUI/SLUIDragClickTarget.cs.meta | 2 + .../SLShared/SLUnity/SLUI/SLUIEntity.cs | 221 +++ .../SLShared/SLUnity/SLUI/SLUIEntity.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIEventList.cs | 102 ++ .../SLUnity/SLUI/SLUIEventList.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIHover.cs | 173 +++ .../SLShared/SLUnity/SLUI/SLUIHover.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIImageSelect.cs | 61 + .../SLUnity/SLUI/SLUIImageSelect.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIInputControl.cs | 110 ++ .../SLUnity/SLUI/SLUIInputControl.cs.meta | 2 + .../SLShared/SLUnity/SLUI/SLUIInputField.cs | 178 +++ .../SLUnity/SLUI/SLUIInputField.cs.meta | 11 + .../SLUnity/SLUI/SLUILayoutElement.cs | 40 + .../SLUnity/SLUI/SLUILayoutElement.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUILayoutScale.cs | 98 ++ .../SLUnity/SLUI/SLUILayoutScale.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIList.cs | 356 +++++ .../SLShared/SLUnity/SLUI/SLUIList.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUILongPressed.cs | 159 +++ .../SLUnity/SLUI/SLUILongPressed.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIMove.cs | 91 ++ .../SLShared/SLUnity/SLUI/SLUIMove.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIMoveTo.cs | 135 ++ .../SLShared/SLUnity/SLUI/SLUIMoveTo.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIObjectOnOff.cs | 55 + .../SLUnity/SLUI/SLUIObjectOnOff.cs.meta | 11 + .../SLUnity/SLUI/SLUIOnOffTransition.cs | 90 ++ .../SLUnity/SLUI/SLUIOnOffTransition.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIPosition.cs | 88 ++ .../SLUnity/SLUI/SLUIPosition.cs.meta | 11 + .../SLUnity/SLUI/SLUIPositionToScreen.cs | 47 + .../SLUnity/SLUI/SLUIPositionToScreen.cs.meta | 2 + .../SLShared/SLUnity/SLUI/SLUIRepeat.cs | 111 ++ .../SLShared/SLUnity/SLUI/SLUIRepeat.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIResource.cs | 17 + .../SLUnity/SLUI/SLUIResource.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIRoot.cs | 43 + .../SLShared/SLUnity/SLUI/SLUIRoot.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUISafeArea.cs | 234 ++++ .../SLUnity/SLUI/SLUISafeArea.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIScale.cs | 86 ++ .../SLShared/SLUnity/SLUI/SLUIScale.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIScaleBind.cs | 78 ++ .../SLUnity/SLUI/SLUIScaleBind.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIScrollRect.cs | 193 +++ .../SLUnity/SLUI/SLUIScrollRect.cs.meta | 11 + .../SLUnity/SLUI/SLUIScrollRectRefresh.cs | 111 ++ .../SLUI/SLUIScrollRectRefresh.cs.meta | 2 + .../SLShared/SLUnity/SLUI/SLUISmoothValue.cs | 122 ++ .../SLUnity/SLUI/SLUISmoothValue.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUISound.cs | 43 + .../SLShared/SLUnity/SLUI/SLUISound.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIUtil.cs | 79 ++ .../SLShared/SLUnity/SLUI/SLUIUtil.cs.meta | 11 + .../_Datas/SLShared/SLUnity/SLUI/SLUIValue.cs | 514 +++++++ .../SLShared/SLUnity/SLUI/SLUIValue.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIValuePrefab.cs | 49 + .../SLUnity/SLUI/SLUIValuePrefab.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIValueScale.cs | 46 + .../SLUnity/SLUI/SLUIValueScale.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLUIWindowsList.cs | 53 + .../SLUnity/SLUI/SLUIWindowsList.cs.meta | 11 + .../SLUnity/SLUI/SLValueComparison.cs | 258 ++++ .../SLUnity/SLUI/SLValueComparison.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SLWorldTransform.cs | 36 + .../SLUnity/SLUI/SLWorldTransform.cs.meta | 11 + .../SLShared/SLUnity/SLUI/SpriteText.cs | 99 ++ .../SLShared/SLUnity/SLUI/SpriteText.cs.meta | 2 + .../_Datas/SLShared/SLUnity/SLUI/UIOnOff.meta | 8 + .../SLUnity/SLUI/UIOnOff/SLUIAnimator.cs | 95 ++ .../SLUnity/SLUI/UIOnOff/SLUIAnimator.cs.meta | 11 + .../SLShared/SLUnity/SLUI/UIOnOff/SLUIFade.cs | 77 ++ .../SLUnity/SLUI/UIOnOff/SLUIFade.cs.meta | 11 + .../SLUI/UIOnOff/SLUIFadeCanvasGroup.cs | 61 + .../SLUI/UIOnOff/SLUIFadeCanvasGroup.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLUITrigger.cs | 102 ++ .../SLShared/SLUnity/SLUITrigger.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLUnity.asmdef | 23 + .../SLShared/SLUnity/SLUnity.asmdef.meta | 7 + Assets/_Datas/SLShared/SLUnity/SLUnity.cs | 70 + .../_Datas/SLShared/SLUnity/SLUnity.cs.meta | 11 + .../SLShared/SLUnity/SLUnity.csproj.meta | 7 + .../SLShared/SLUnity/SLUnityDataLoader.cs | 27 + .../SLUnity/SLUnityDataLoader.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/SLUnityUtil.cs | 32 + .../SLShared/SLUnity/SLUnityUtil.cs.meta | 11 + .../SLShared/SLUnity/SLWindowsDataReloader.cs | 96 ++ .../SLUnity/SLWindowsDataReloader.cs.meta | 11 + .../SLUnity/UnitAnimatorController.cs | 226 ++++ .../SLUnity/UnitAnimatorController.cs.meta | 11 + .../SLShared/SLUnity/UnitSpriteController.cs | 42 + .../SLUnity/UnitSpriteController.cs.meta | 11 + Assets/_Datas/SLShared/SLUnity/UnitSytem.meta | 8 + .../SLUnity/UnitSytem/Components.meta | 8 + .../UnitSytem/Components/ActionController.cs | 251 ++++ .../Components/ActionController.cs.meta | 2 + .../UnitSytem/Components/MoveController.cs | 146 ++ .../Components/MoveController.cs.meta | 2 + .../_Datas/SLShared/SLUnity/UnitSytem/UI.meta | 8 + .../SLShared/SLUnity/UnitSytem/UI/UIUnit.cs | 404 ++++++ .../SLUnity/UnitSytem/UI/UIUnit.cs.meta | 2 + .../_Datas/SLShared/SLUnity/UnitSytem/Unit.cs | 89 ++ .../SLShared/SLUnity/UnitSytem/Unit.cs.meta | 2 + .../SLUnity/UnitSytem/UnitComponent.cs | 17 + .../SLUnity/UnitSytem/UnitComponent.cs.meta | 2 + .../SLShared/SLUnity/UnitSytem/UnitHandler.cs | 354 +++++ .../SLUnity/UnitSytem/UnitHandler.cs.meta | 2 + .../SLUnity/UnitSytem/UnitSystem.asmdef | 21 + .../SLUnity/UnitSytem/UnitSystem.asmdef.meta | 7 + .../SLShared/SLUnity/UnitSytem/UnitUtil.cs | 568 ++++++++ .../SLUnity/UnitSytem/UnitUtil.cs.meta | 2 + .../SLShared/SLUnity/UnitSytem/View.meta | 8 + .../SLUnity/UnitSytem/View/EffectView.cs | 338 +++++ .../SLUnity/UnitSytem/View/EffectView.cs.meta | 2 + .../SLUnity/UnitSytem/View/SLCamera.cs | 471 +++++++ .../SLUnity/UnitSytem/View/SLCamera.cs.meta | 2 + .../SLUnity/UnitSytem/View/SpawnEffect.cs | 27 + .../UnitSytem/View/SpawnEffect.cs.meta | 2 + .../SLUnity/UnitSytem/View/SpeedUtil.cs | 141 ++ .../SLUnity/UnitSytem/View/SpeedUtil.cs.meta | 2 + .../SLUnity/UnitSytem/View/UnitView.cs | 1188 +++++++++++++++++ .../SLUnity/UnitSytem/View/UnitView.cs.meta | 2 + .../SLUnity/UnitSytem/View/UnitViewHP.cs | 72 + .../SLUnity/UnitSytem/View/UnitViewHP.cs.meta | 2 + .../SLUnity/UnitSytem/View/ZoneView.cs | 163 +++ .../SLUnity/UnitSytem/View/ZoneView.cs.meta | 2 + .../_Datas/SLShared/SLUnity/UnitSytem/Zone.cs | 244 ++++ .../SLShared/SLUnity/UnitSytem/Zone.cs.meta | 2 + .../SLUnity/UnitSytem/ZoneComponent.cs | 11 + .../SLUnity/UnitSytem/ZoneComponent.cs.meta | 2 + .../SLUnity/UnitSytem/ZoneController.cs | 233 ++++ .../SLUnity/UnitSytem/ZoneController.cs.meta | 2 + .../SLShared/SLUnity/UnitSytem/ZoneHandler.cs | 255 ++++ .../SLUnity/UnitSytem/ZoneHandler.cs.meta | 2 + Assets/_Datas/SLShared/SLUnity/package.json | 14 + .../_Datas/SLShared/SLUnity/package.json.meta | 7 + ProjectSettings/EditorSettings.asset | 5 +- ProjectSettings/ProjectSettings.asset | 2 +- 318 files changed, 22734 insertions(+), 4 deletions(-) create mode 100644 Assets/_Datas/02.Scripts/AssetPostprocessor.meta create mode 100644 Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs create mode 100644 Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs.meta create mode 100644 Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs create mode 100644 Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs.meta create mode 100644 Assets/_Datas/02.Scripts/Utils.cs create mode 100644 Assets/_Datas/02.Scripts/Utils.cs.meta create mode 100644 Assets/_Datas/Addressables.meta create mode 100644 Assets/_Datas/Addressables/Barrel.prefab create mode 100644 Assets/_Datas/Addressables/Barrel.prefab.meta create mode 100644 Assets/_Datas/Addressables/Sprites.spriteatlasv2 create mode 100644 Assets/_Datas/Addressables/Sprites.spriteatlasv2.meta create mode 100644 Assets/_Datas/Raw.meta create mode 100644 Assets/_Datas/Raw/Sprites.meta create mode 100644 Assets/_Datas/Raw/Sprites/Barrel.png create mode 100644 Assets/_Datas/Raw/Sprites/Barrel.png.meta create mode 100644 Assets/_Datas/SLShared.meta create mode 100644 Assets/_Datas/SLShared/Directory.Build.props create mode 100644 Assets/_Datas/SLShared/Directory.Build.props.meta create mode 100644 Assets/_Datas/SLShared/SLLogger.meta create mode 100644 Assets/_Datas/SLShared/SLLogger/ISLLogger.cs create mode 100644 Assets/_Datas/SLShared/SLLogger/ISLLogger.cs.meta create mode 100644 Assets/_Datas/SLShared/SLLogger/SLLog.cs create mode 100644 Assets/_Datas/SLShared/SLLogger/SLLog.cs.meta create mode 100644 Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef create mode 100644 Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef.meta create mode 100644 Assets/_Datas/SLShared/SLLogger/SLLogger.csproj.meta create mode 100644 Assets/_Datas/SLShared/SLLogger/package.json create mode 100644 Assets/_Datas/SLShared/SLLogger/package.json.meta create mode 100644 Assets/_Datas/SLShared/SLSystem.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/Evaluator.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/Evaluator.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLRandom.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLRandom.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystem.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystem.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystem.csproj.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs create mode 100644 Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/UnitComponent.meta create mode 100644 Assets/_Datas/SLShared/SLSystem/package.json create mode 100644 Assets/_Datas/SLShared/SLSystem/package.json.meta create mode 100644 Assets/_Datas/SLShared/SLUnity.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLBuild.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.csproj.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/Tools.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLContextController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLContextController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLGame.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLGame.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLOption.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLOption.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLResource.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLResource.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLSceneController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLSceneController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLSound.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLSound.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLStateController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLStateController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLString.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLString.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLTag.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLTag.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIButton.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIButton.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUICanvas.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUICanvas.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIColorSelect.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIColorSelect.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIComponent.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIComponent.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDrag.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDrag.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDragButton.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDragButton.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDragClick.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDragClick.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDragClickTarget.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIDragClickTarget.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIEntity.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIEntity.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIEventList.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIEventList.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIHover.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIHover.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIImageSelect.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIImageSelect.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIInputControl.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIInputControl.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIInputField.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIInputField.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUILayoutElement.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUILayoutElement.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUILayoutScale.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUILayoutScale.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIList.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIList.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUILongPressed.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUILongPressed.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIMove.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIMove.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIMoveTo.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIMoveTo.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIObjectOnOff.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIObjectOnOff.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIOnOffTransition.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIOnOffTransition.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIPosition.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIPosition.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIPositionToScreen.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIPositionToScreen.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIRepeat.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIRepeat.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIResource.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIResource.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIRoot.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIRoot.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUISafeArea.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUISafeArea.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScale.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScale.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScaleBind.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScaleBind.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScrollRect.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScrollRect.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScrollRectRefresh.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIScrollRectRefresh.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUISmoothValue.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUISmoothValue.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUISound.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUISound.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIUtil.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIValue.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIValue.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIValuePrefab.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIValuePrefab.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIValueScale.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIValueScale.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIWindowsList.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLUIWindowsList.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLValueComparison.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLValueComparison.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLWorldTransform.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SLWorldTransform.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SpriteText.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/SpriteText.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff/SLUIAnimator.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff/SLUIAnimator.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff/SLUIFade.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff/SLUIFade.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff/SLUIFadeCanvasGroup.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUI/UIOnOff/SLUIFadeCanvasGroup.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUITrigger.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUITrigger.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnity.asmdef create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnity.asmdef.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnity.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnity.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnity.csproj.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnityDataLoader.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnityDataLoader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnityUtil.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLUnityUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/SLWindowsDataReloader.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/SLWindowsDataReloader.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitAnimatorController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitAnimatorController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSpriteController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSpriteController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Components.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Components/ActionController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Components/ActionController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Components/MoveController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Components/MoveController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UI.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UI/UIUnit.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UI/UIUnit.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Unit.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Unit.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitComponent.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitComponent.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitHandler.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitHandler.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitSystem.asmdef create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitSystem.asmdef.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitUtil.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/UnitUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/EffectView.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/EffectView.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/SLCamera.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/SLCamera.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/SpawnEffect.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/SpawnEffect.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/SpeedUtil.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/SpeedUtil.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/UnitView.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/UnitView.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/UnitViewHP.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/UnitViewHP.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/ZoneView.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/View/ZoneView.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Zone.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/Zone.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/ZoneComponent.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/ZoneComponent.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/ZoneController.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/ZoneController.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/ZoneHandler.cs create mode 100644 Assets/_Datas/SLShared/SLUnity/UnitSytem/ZoneHandler.cs.meta create mode 100644 Assets/_Datas/SLShared/SLUnity/package.json create mode 100644 Assets/_Datas/SLShared/SLUnity/package.json.meta diff --git a/Assets/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset b/Assets/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset index ad44b3b50..cf31368c6 100644 --- a/Assets/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset +++ b/Assets/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset @@ -58,9 +58,13 @@ MonoBehaviour: - SingularityGroup.HotReload.Runtime.Public - Sirenix.OdinInspector.Modules.Unity.Addressables - Sirenix.OdinInspector.Modules.UnityLocalization + - SLLogger + - SLSystem + - SLUnity - spine-csharp - spine-unity - spine-unity-examples + - UnitSystem showAdvancedSettings: 0 addMgrToSceneAutomatically: 0 autoUpdateReferences: 1 diff --git a/Assets/Toon Water URP/Scripts/PlanarReflectionsRenderer.cs b/Assets/Toon Water URP/Scripts/PlanarReflectionsRenderer.cs index 86f3b2347..eb437f3ad 100644 --- a/Assets/Toon Water URP/Scripts/PlanarReflectionsRenderer.cs +++ b/Assets/Toon Water URP/Scripts/PlanarReflectionsRenderer.cs @@ -231,7 +231,8 @@ private void RenderReflections(ScriptableRenderContext context, Camera camera) QualitySettings.lodBias = lodBiasBeforeReflections * 0.5f; //render - UniversalRenderPipeline.RenderSingleCamera(context, reflectionCamera); + //UniversalRenderPipeline.RenderSingleCamera(context, reflectionCamera); + RenderPipeline.SubmitRenderRequest(reflectionCamera, context); //restore quality settings GL.invertCulling = false; diff --git a/Assets/_Datas/02.Scripts/AssetPostprocessor.meta b/Assets/_Datas/02.Scripts/AssetPostprocessor.meta new file mode 100644 index 000000000..c948e9ed5 --- /dev/null +++ b/Assets/_Datas/02.Scripts/AssetPostprocessor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9907ae9e89d078f42b31833612011df5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs new file mode 100644 index 000000000..161317d79 --- /dev/null +++ b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs @@ -0,0 +1,77 @@ +using UnityEditor; +using UnityEngine; + +namespace DDD +{ + public class AssetPostProcessor : AssetPostprocessor + { + private void OnPreprocessTexture() + { + var importer = assetImporter as TextureImporter; + + var upperPath = importer.assetPath.ToUpper(); + + // if (upperPath.Contains("ASSETS/RAW/Units/")) + // { + // AssetPostprocessorModel.OnPreprocessTexture(importer); + // } + + if (upperPath.Contains("ASSETS/_DATAS/RAW/SPRITES/")) + { + AssetPostprocessorSprite.OnPreprocessTexture(importer); + } + } + + public static void OnPostprocessAllAssets(string[] importedAssets, string[] deleteAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + foreach (var path in deleteAssets) + { + PostRemove(path); + } + + var index = 0; + foreach (var path in movedFromAssetPaths) + { + PostRemove(path, movedAssets[index]); + ++index; + } + + foreach (var path in movedAssets) + { + PostAdd(path); + } + + foreach (var path in importedAssets) + { + PostAdd(path); + } + + AssetPostprocessorSprite.BuildTarget(); + AssetPostprocessorSprite.BuildTarget(); + } + + private static void PostRemove(string path, string movePath = "") + { + try + { + AssetPostprocessorSprite.OnRemove(path, movePath); + } + catch (System.Exception e) + { + Debug.LogError("Can't remove " + path + "\n" + e); + } + } + + private static void PostAdd(string path) + { + try + { + AssetPostprocessorSprite.OnAdd(path); + } + catch (System.Exception e) + { + Debug.LogError("Can't import " + path + "\n" + e); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs.meta b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs.meta new file mode 100644 index 000000000..64b750428 --- /dev/null +++ b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostProcessor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0027aa4e810904745aab9cda1a5e11be \ No newline at end of file diff --git a/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs new file mode 100644 index 000000000..e0992d1a2 --- /dev/null +++ b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs @@ -0,0 +1,290 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Superlazy; +using UnityEditor; +using UnityEditor.U2D; +using UnityEngine; +using UnityEngine.U2D; + +namespace DDD +{ + public static class AssetPostprocessorSprite + { + private static readonly HashSet TargetPaths = new HashSet(); + + public static void OnPreprocessTexture(TextureImporter importer) + { + importer.textureType = TextureImporterType.Sprite; + importer.spriteImportMode = SpriteImportMode.Single; + + importer.GetSourceTextureWidthAndHeight(out var width, out var height); + importer.spritePixelsPerUnit = width <= height ? width : height; + + importer.sRGBTexture = true; + importer.isReadable = false; + importer.mipmapEnabled = false; + importer.streamingMipmaps = false; + importer.wrapMode = TextureWrapMode.Clamp; + importer.filterMode = FilterMode.Bilinear; + + importer.textureCompression = TextureImporterCompression.Uncompressed; + importer.crunchedCompression = false; + + var textureSettings = new TextureImporterSettings(); + importer.ReadTextureSettings(textureSettings); + textureSettings.spriteMeshType = SpriteMeshType.FullRect; + textureSettings.spriteExtrude = 2; + importer.SetTextureSettings(textureSettings); + } + + public static void OnRemove(string path, string movePath) + { + var upperPath = path.ToUpper(); + if (upperPath.Contains("ASSETS/_DATAS/RAW/SPRITES/") == false || + upperPath.Contains(".PNG") == false) return; + + if (TargetPaths.Contains(path) == false) + { + TargetPaths.Add(path); + } + } + + public static void OnAdd(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.Contains("ASSETS/_DATAS/RAW/SPRITES/") == false || + upperPath.Contains(".PNG") == false) return; + + if (TargetPaths.Contains(path) == false) + { + TargetPaths.Add(path); + } + } + + public static void CreateAtlas(string path, string destPath) + { + var oldAtlas = AssetDatabase.LoadAssetAtPath(destPath); + + if (oldAtlas != null) + { + AssetDatabase.DeleteAsset(destPath); + } + + var di = new DirectoryInfo(path); + if (di.Exists == false) return; + + var objects = new List(); + foreach (var file in di.GetFiles()) + { + if (file.Name.ToUpper().IsRight(".PNG") == false) continue; + var filePath = path + "/" + file.Name; + var fileName = file.Name.Substring(0, file.Name.Length - ".png".Length); + var sprite = AssetDatabase.LoadAssetAtPath(filePath); + + var maxSize = sprite.rect.size.x > sprite.rect.size.y ? sprite.rect.size.x : sprite.rect.size.y; + if (maxSize > 1024) + { + CreateSingleAtlas(filePath, path.Replace("/Raw/", "/Addressables/") + $"_{fileName}.spriteatlasv2"); + continue; + } + + objects.Add(sprite); + } + + if (objects.Count == 0) return; + + Utils.MakeFolderFromFilePath(destPath); + var atlas = new SpriteAtlasAsset(); + + var spriteAtlasComponents = new List(); + spriteAtlasComponents.CreateInstanceList(); + foreach (var component in spriteAtlasComponents) + { + component.OnAddSprite(atlas); + } + + atlas.Add(objects.ToArray()); + + SpriteAtlasAsset.Save(atlas, destPath); + AssetDatabase.Refresh(); + + var sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(destPath); + sai.packingSettings = new SpriteAtlasPackingSettings + { + enableRotation = false, + enableTightPacking = false, + enableAlphaDilation = false, + padding = 4, + blockOffset = 0 + }; + + sai.textureSettings = new SpriteAtlasTextureSettings + { + filterMode = FilterMode.Bilinear, + sRGB = true, + generateMipMaps = false + }; + } + + public static void CreateSingleAtlas(string path, string destPath) + { + var oldAtlas = AssetDatabase.LoadAssetAtPath(destPath); + + if (oldAtlas != null) + { + AssetDatabase.DeleteAsset(destPath); + } + + Utils.MakeFolderFromFilePath(destPath); + var atlas = new SpriteAtlasAsset(); + + var sprite = AssetDatabase.LoadAssetAtPath(path); + atlas.Add(new Object[] { sprite }); + + var spriteAtlasComponents = new List(); + spriteAtlasComponents.CreateInstanceList(); + foreach (var component in spriteAtlasComponents) + { + component.OnAddSprite(atlas); + } + + SpriteAtlasAsset.Save(atlas, destPath); + AssetDatabase.Refresh(); + + var sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(destPath); + sai.packingSettings = new SpriteAtlasPackingSettings + { + enableRotation = false, + enableTightPacking = false, + enableAlphaDilation = false, + padding = 4, + blockOffset = 0 + }; + + sai.textureSettings = new SpriteAtlasTextureSettings + { + filterMode = FilterMode.Bilinear, + sRGB = true, + generateMipMaps = false + }; + } + + public static void CreatePrefab(string path, string destPath) + { + try + { + var spriteCreateComponents = new List(); + spriteCreateComponents.CreateInstanceList(); + var sprite = AssetDatabase.LoadAssetAtPath(path); + + if (sprite == null) return; + + GameObject prefab = new GameObject(sprite.name); + prefab.transform.localPosition = Vector3.zero; + prefab.transform.localRotation = Quaternion.identity; + prefab.name = sprite.name; + + GameObject visualLook = new GameObject("VisualLook"); + + visualLook.transform.SetParent(prefab.transform); + visualLook.transform.localPosition = Vector3.zero; + visualLook.transform.localRotation = Quaternion.Euler(new Vector3(40f, 0f, 0f)); + + SpriteRenderer spriteRenderer = visualLook.AddComponent(); + spriteRenderer.sprite = sprite; + spriteRenderer.sortingOrder = 5; + + // var guids = AssetDatabase.FindAssets("t:Material", new string[] { Utils.FolderPath(path) }); + // var materials = guids + // .Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))) + // .ToDictionary(m => m.name); + // + // foreach (var renderer in model.GetComponentsInChildren()) + // { + // var sharedMaterials = + // new Material[renderer.sharedMaterials.Length]; // Material을 직접 엘리먼트로 대입하면 안되고 배열을 통으로 넣어야함 + // for (var i = 0; i < renderer.sharedMaterials.Length; ++i) + // { + // if (materials.TryGetValue($"{renderer.name}_{i + 1}", out var numMat)) + // { + // sharedMaterials[i] = numMat; + // } + // else if (materials.TryGetValue(renderer.name, out var mat)) + // { + // sharedMaterials[i] = mat; + // } + // else if (materials.TryGetValue(go.name, out var defaultMat)) + // { + // sharedMaterials[i] = defaultMat; + // } + // else + // { + // sharedMaterials[i] = renderer.sharedMaterials[i]; + // // 에러인가? 잠깐 파일 안갖다놨을뿐인가? + // } + // } + // + // renderer.sharedMaterials = sharedMaterials; + // } + // + // var res = prefab.AddComponent(); + + //res.Init(); + + Utils.MakeFolderFromFilePath(destPath); + + // { + // // Make Duumy(for test) + // var dummyObj = Object.Instantiate(model); + // var anim = dummyObj.GetComponentInChildren(); + // var controller = + // AnimatorController.CreateAnimatorControllerAtPath(GetPrefabPath(path) + // .Replace(".prefab", ".controller")); + // + // anim.runtimeAnimatorController = controller; + // var rootStateMachine = controller.layers[0].stateMachine; + // + // var clipids = AssetDatabase.FindAssets("t:AnimationClip", + // new string[] { SLFileUtility.FolderPath(path) }); + // foreach (var guid in clipids) + // { + // var clip = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); + // if (clip != null) + // { + // var state = rootStateMachine.AddState(clip.name); + // state.motion = clip; + // } + // } + // + // var dummy = PrefabUtility.SaveAsPrefabAssetAndConnect(dummyObj, GetPrefabPath(path), + // InteractionMode.AutomatedAction); + // + // Object.DestroyImmediate(dummyObj, false); + // } + + AssetDatabase.DeleteAsset(destPath); + var newPrefab = + PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, destPath, InteractionMode.AutomatedAction); + + Object.DestroyImmediate(prefab, false); + Debug.Log("Build : " + destPath, newPrefab); + } + catch (System.Exception e) + { + Debug.LogError("Cant build " + destPath + "\n" + e); + } + } + + public static void BuildTarget() + { + foreach (var path in TargetPaths) + { + CreateAtlas(Utils.FolderPath(path), Utils.FolderPath(path).Replace("/Raw/", "/Addressables/") + ".spriteatlasv2"); + CreatePrefab(path, (path.Replace("/Raw/Sprites/", "/Addressables/") + ".prefab").Replace(".png", "")); + } + + TargetPaths.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs.meta b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs.meta new file mode 100644 index 000000000..5ef81f564 --- /dev/null +++ b/Assets/_Datas/02.Scripts/AssetPostprocessor/AssetPostprocessorSprite.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 076707aa8e0f8464b86a7927d71b98bf \ No newline at end of file diff --git a/Assets/_Datas/02.Scripts/Utils.cs b/Assets/_Datas/02.Scripts/Utils.cs new file mode 100644 index 000000000..174b91c58 --- /dev/null +++ b/Assets/_Datas/02.Scripts/Utils.cs @@ -0,0 +1,45 @@ +using System.IO; +using UnityEditor; + +namespace DDD +{ + public static class Utils + { + public static string FixPath(string path) + { + path = path.Replace('\\', '/'); + path = path.Replace("//", "/"); + while (path.Length > 0 && path[0] == '/') + { + path = path.Remove(0, 1); + } + return path; + } + + public static string FileName(string path) + { + return Path.GetFileNameWithoutExtension(path); + } + + public static string FolderPath(string path) + { + return FixPath(Path.GetDirectoryName(path)); + } + + public static void MakeFolderFromFilePath(string filePath) + { + var paths = FixPath(filePath).Split('/'); + var path = ""; + + for (var i = 0; i < paths.Length - 1; ++i) + { + path += paths[i] + "/"; + if (Directory.Exists(path) == false) + { + Directory.CreateDirectory(path); + AssetDatabase.Refresh(); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/02.Scripts/Utils.cs.meta b/Assets/_Datas/02.Scripts/Utils.cs.meta new file mode 100644 index 000000000..08829c062 --- /dev/null +++ b/Assets/_Datas/02.Scripts/Utils.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 854af13e3da02544a8778a7e8bebf2c0 \ No newline at end of file diff --git a/Assets/_Datas/Addressables.meta b/Assets/_Datas/Addressables.meta new file mode 100644 index 000000000..39d02959a --- /dev/null +++ b/Assets/_Datas/Addressables.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a4e564e0ed0fc547963a94ea07f86e6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/Addressables/Barrel.prefab b/Assets/_Datas/Addressables/Barrel.prefab new file mode 100644 index 000000000..b5b8bf080 --- /dev/null +++ b/Assets/_Datas/Addressables/Barrel.prefab @@ -0,0 +1,121 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &5969173919338450677 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8764769398795247692} + m_Layer: 0 + m_Name: Barrel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8764769398795247692 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5969173919338450677} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2842124097711829421} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &9076417642579187436 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2842124097711829421} + - component: {fileID: 5861905487478007050} + m_Layer: 0 + m_Name: VisualLook + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2842124097711829421 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9076417642579187436} + serializedVersion: 2 + m_LocalRotation: {x: 0.34202015, y: 0, z: 0, w: 0.9396927} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8764769398795247692} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &5861905487478007050 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9076417642579187436} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 5 + m_Sprite: {fileID: 21300000, guid: eb70d7085c7b27d4a939289414a09cef, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 diff --git a/Assets/_Datas/Addressables/Barrel.prefab.meta b/Assets/_Datas/Addressables/Barrel.prefab.meta new file mode 100644 index 000000000..9b6e3c8c1 --- /dev/null +++ b/Assets/_Datas/Addressables/Barrel.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6ade463886ca5294685e2c4997a976b7 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/Addressables/Sprites.spriteatlasv2 b/Assets/_Datas/Addressables/Sprites.spriteatlasv2 new file mode 100644 index 000000000..038cec99d --- /dev/null +++ b/Assets/_Datas/Addressables/Sprites.spriteatlasv2 @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!612988286 &1 +SpriteAtlasAsset: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + serializedVersion: 2 + m_MasterAtlas: {fileID: 0} + m_ImporterData: + packables: + - {fileID: 21300000, guid: eb70d7085c7b27d4a939289414a09cef, type: 3} + m_IsVariant: 0 + m_ScriptablePacker: {fileID: 0} diff --git a/Assets/_Datas/Addressables/Sprites.spriteatlasv2.meta b/Assets/_Datas/Addressables/Sprites.spriteatlasv2.meta new file mode 100644 index 000000000..094ab7344 --- /dev/null +++ b/Assets/_Datas/Addressables/Sprites.spriteatlasv2.meta @@ -0,0 +1,30 @@ +fileFormatVersion: 2 +guid: 1f850df0adb104f4ba2a6c097152f6fa +SpriteAtlasImporter: + externalObjects: {} + textureSettings: + serializedVersion: 2 + anisoLevel: 1 + compressionQuality: 50 + maxTextureSize: 2048 + textureCompression: 0 + filterMode: 1 + generateMipMaps: 0 + readable: 0 + crunchedCompression: 0 + sRGB: 1 + platformSettings: [] + packingSettings: + serializedVersion: 2 + padding: 4 + blockOffset: 1 + allowAlphaSplitting: 0 + enableRotation: 1 + enableTightPacking: 1 + enableAlphaDilation: 0 + secondaryTextureSettings: {} + variantMultiplier: 1 + bindAsDefault: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/Raw.meta b/Assets/_Datas/Raw.meta new file mode 100644 index 000000000..bc6835714 --- /dev/null +++ b/Assets/_Datas/Raw.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e332838ddcbf84743875b13dd197a48e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/Raw/Sprites.meta b/Assets/_Datas/Raw/Sprites.meta new file mode 100644 index 000000000..929ee412d --- /dev/null +++ b/Assets/_Datas/Raw/Sprites.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7bc2bc9148f83834780431aee504cf69 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/Raw/Sprites/Barrel.png b/Assets/_Datas/Raw/Sprites/Barrel.png new file mode 100644 index 0000000000000000000000000000000000000000..188956ba5b235a4c176db0c4544786f4459703d8 GIT binary patch literal 145825 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

O9gGYC;z(9&Noqi;AxBh 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 ymTqX?5)U4Bw7d#y9lr5B7u1M8qwRC{zer+ literal 0 HcmV?d00001 diff --git a/Assets/_Datas/Raw/Sprites/Barrel.png.meta b/Assets/_Datas/Raw/Sprites/Barrel.png.meta new file mode 100644 index 000000000..c46076c5f --- /dev/null +++ b/Assets/_Datas/Raw/Sprites/Barrel.png.meta @@ -0,0 +1,143 @@ +fileFormatVersion: 2 +guid: eb70d7085c7b27d4a939289414a09cef +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 2 + spriteMeshType: 0 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 512 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared.meta b/Assets/_Datas/SLShared.meta new file mode 100644 index 000000000..0514df998 --- /dev/null +++ b/Assets/_Datas/SLShared.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 711a98d8afbbc1a4d8698262c2463ad2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/Directory.Build.props b/Assets/_Datas/SLShared/Directory.Build.props new file mode 100644 index 000000000..99d0a91c9 --- /dev/null +++ b/Assets/_Datas/SLShared/Directory.Build.props @@ -0,0 +1,9 @@ + + + ../obj/ + + + 6000.1.5f1 + ../../ProjectIM/IMUnity/ + + \ No newline at end of file diff --git a/Assets/_Datas/SLShared/Directory.Build.props.meta b/Assets/_Datas/SLShared/Directory.Build.props.meta new file mode 100644 index 000000000..7bb69874a --- /dev/null +++ b/Assets/_Datas/SLShared/Directory.Build.props.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: dcd0ead37234ae04e8e8a382d49789c3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLLogger.meta b/Assets/_Datas/SLShared/SLLogger.meta new file mode 100644 index 000000000..0b58897d4 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ef9524ee33df2ff4b96d774d118222a9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLLogger/ISLLogger.cs b/Assets/_Datas/SLShared/SLLogger/ISLLogger.cs new file mode 100644 index 000000000..c6f1cdad8 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/ISLLogger.cs @@ -0,0 +1,11 @@ +namespace Superlazy +{ + public interface ISLLogger + { + void Info(string format, params object[] args); + + void Warn(string format, params object[] args); + + void Error(string format, params object[] args); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLLogger/ISLLogger.cs.meta b/Assets/_Datas/SLShared/SLLogger/ISLLogger.cs.meta new file mode 100644 index 000000000..f42bffe04 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/ISLLogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f93bf8f3ffea29c4db3a7eabd0119ae6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLLogger/SLLog.cs b/Assets/_Datas/SLShared/SLLogger/SLLog.cs new file mode 100644 index 000000000..e8a59ac75 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/SLLog.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Superlazy +{ + public static class SLLog + { + public static ISLLogger Logger + { + set + { + Loggers ??= new List(); + Loggers.Add(value); + } + } + + private static List Loggers { get; set; } + + [Conditional("SLLOG"), Conditional("UNITY_EDITOR")] + public static void Error(string format, params object[] args) + { + if (Loggers == null) return; + foreach (var logger in Loggers) + { + logger.Error(format, args); + } + } + + [Conditional("SLLOG"), Conditional("UNITY_EDITOR")] + public static void Info(string format, params object[] args) + { + if (Loggers == null) return; + foreach (var logger in Loggers) + { + logger.Info(format, args); + } + } + + [Conditional("SLLOG"), Conditional("UNITY_EDITOR")] + public static void Warn(string format, params object[] args) + { + if (Loggers == null) return; + foreach (var logger in Loggers) + { + logger.Warn(format, args); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLLogger/SLLog.cs.meta b/Assets/_Datas/SLShared/SLLogger/SLLog.cs.meta new file mode 100644 index 000000000..9ad29fe62 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/SLLog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bea77d7b448102e49bfeebe6a3d23eaa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef b/Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef new file mode 100644 index 000000000..727e8f7f8 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef @@ -0,0 +1,3 @@ +{ + "name": "SLLogger" +} diff --git a/Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef.meta b/Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef.meta new file mode 100644 index 000000000..be5d73735 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/SLLogger.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f0e91edf4495c2045b34f001325cddb3 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLLogger/SLLogger.csproj.meta b/Assets/_Datas/SLShared/SLLogger/SLLogger.csproj.meta new file mode 100644 index 000000000..910531798 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/SLLogger.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a18b99084fc30604b9de617a181246f0 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLLogger/package.json b/Assets/_Datas/SLShared/SLLogger/package.json new file mode 100644 index 000000000..702c79fb7 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/package.json @@ -0,0 +1,9 @@ +{ + "name": "com.superlazy.sllogger", + "displayName": "SL Logger", + "description": "SL Logger", + "version": "1.0.0", + "unity": "2018.2", + "license": "MIT", + "dependencies": {} +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLLogger/package.json.meta b/Assets/_Datas/SLShared/SLLogger/package.json.meta new file mode 100644 index 000000000..60db92e17 --- /dev/null +++ b/Assets/_Datas/SLShared/SLLogger/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2a5b4383d891a734aa2cbfaf1f4c1d4c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem.meta b/Assets/_Datas/SLShared/SLSystem.meta new file mode 100644 index 000000000..e93b4fcba --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 76a47edf5a3c36a4fb5d877cd7d024e3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs b/Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs new file mode 100644 index 000000000..af6a8afd2 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs @@ -0,0 +1,19 @@ +using System; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class ComponentOrderAttribute : Attribute +{ + public int order; + public string methodOverride; + + public ComponentOrderAttribute(int order) + { + this.order = order; + } + + public ComponentOrderAttribute(string methodOverride, int order) + { + this.order = order; + this.methodOverride = methodOverride; + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs.meta b/Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs.meta new file mode 100644 index 000000000..e5a872095 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/ComponentOrderAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 730f3af4df651064da4cd6a28965353e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/Evaluator.cs b/Assets/_Datas/SLShared/SLSystem/Evaluator.cs new file mode 100644 index 000000000..aae22f009 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/Evaluator.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Superlazy; + +public class Evaluator +{ + public static string Evaluate(SLEntity player, string desc) + { + if (desc.StartsWith('$') == false) return desc; + + // 변수 대체: {name} 패턴을 찾아 Global.Get으로 변환 + var replacedDesc = Regex.Replace(desc.Substring(1), @"\{(\w+)\}", match => + { + var varName = match.Groups[1].Value; + var ret = player["EventValues"].Get(varName); // 그냥 글로벌로 하면 변수 길어질듯해서 + if (ret.IsValue && ret.IsNumeric == false) return $"\"{ret}\""; + + return ret; + }); + + return EvaluateExpression(replacedDesc); + } + + private static SLEntity EvaluateExpression(string expression) + { + var values = new Stack(); // 값을 저장하는 스택 (숫자 또는 문자열) + var operators = new Stack(); // 연산자를 저장하는 스택 + + var i = 0; + while (i < expression.Length) + { + if (char.IsDigit(expression[i])) // 숫자 처리 + { + var number = ""; + while (i < expression.Length && (char.IsDigit(expression[i]) || expression[i] == '.')) + { + number += expression[i]; + i++; + } + values.Push(float.Parse(number)); + } + else if (expression[i] == '"') // 문자열 처리 + { + // 문자열 리터럴 처리 (큰 따옴표로 감싸진 문자열) + i++; + var str = ""; + while (i < expression.Length && expression[i] != '"') + { + str += expression[i]; + i++; + } + i++; // 닫는 따옴표 넘기기 + values.Push(str); + } + else if (expression[i] == '(') + { + operators.Push('('); + i++; + } + else if (expression[i] == ')') + { + while (operators.Peek() != '(') + { + values.Push(ApplyOperator(values.Pop(), values.Pop(), operators.Pop())); + } + operators.Pop(); // '(' 제거 + i++; + } + else if (IsOperator(expression[i])) + { + // 연산자 처리 + while (operators.Count > 0 && GetPrecedence(operators.Peek()) >= GetPrecedence(expression[i])) + { + values.Push(ApplyOperator(values.Pop(), values.Pop(), operators.Pop())); + } + operators.Push(expression[i]); + i++; + } + else + { + // 공백 등 기타 문자는 그냥 넘김 + i++; + } + } + + while (operators.Count > 0) + { + values.Push(ApplyOperator(values.Pop(), values.Pop(), operators.Pop())); + } + + return values.Pop(); + } + + // 연산자 우선순위 + private static int GetPrecedence(char op) + { + if (op == '+' || op == '-') return 1; + if (op == '*' || op == '/') return 2; + return 0; + } + + // 연산자 확인 + private static bool IsOperator(char c) + { + return c == '+' || c == '-' || c == '*' || c == '/'; + } + + // 연산자 적용 + private static SLEntity ApplyOperator(SLEntity right, SLEntity left, char op) + { + return op switch + { + '+' => left + right, + '-' => left - right, + '*' => left * right, + '/' => left / right, + _ => SLEntity.Empty, + }; + } + + public static void ApplyEvaluate(SLEntity player, SLEntity current) + { + foreach (var c in current) + { + if (c.IsValue && c.IsNumeric == false) + { + current[c.ID] = Evaluate(player, c); + } + else if (c.IsValue == false) + { + ApplyEvaluate(player, c); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/Evaluator.cs.meta b/Assets/_Datas/SLShared/SLSystem/Evaluator.cs.meta new file mode 100644 index 000000000..660e710c7 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/Evaluator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5cad9ae5f1395af4187eb9369a165695 \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs b/Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs new file mode 100644 index 000000000..7ec3119b6 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs @@ -0,0 +1,209 @@ +using System; +using Superlazy; + +public static class SLDateTimeUtil +{ + private const double oADateMaxAsDouble = 2958466.0; + private const double oADateMinAsDouble = -657435.0; + private const int millisPerSecond = 1000; + private const int millisPerMinute = millisPerSecond * 60; + private const int millisPerHour = millisPerMinute * 60; + private const int millisPerDay = millisPerHour * 24; + + // Number of 100ns ticks per time unit + private const long ticksPerMillisecond = 10000; + + private const long ticksPerSecond = ticksPerMillisecond * 1000; + private const long ticksPerMinute = ticksPerSecond * 60; + private const long ticksPerHour = ticksPerMinute * 60; + private const long ticksPerDay = ticksPerHour * 24; + + // Number of days in a non-leap year + private const int daysPerYear = 365; + + // Number of days in 4 years + private const int daysPer4Years = daysPerYear * 4 + 1; // 1461 + + // Number of days in 100 years + private const int daysPer100Years = daysPer4Years * 25 - 1; // 36524 + + // Number of days in 400 years + private const int daysPer400Years = daysPer100Years * 4 + 1; // 146097 + + // Number of days from 1/1/0001 to 12/31/1600 + + // Number of days from 1/1/0001 to 12/30/1899 + private const int daysTo1899 = daysPer400Years * 4 + daysPer100Years * 3 - 367; + + // Number of days from 1/1/0001 to 12/31/1969 + + // Number of days from 1/1/0001 to 12/31/9999 + private const int daysTo10000 = daysPer400Years * 25 - 366; // 3652059 + + private const long doubleDateOffset = daysTo1899 * ticksPerDay; + private const long maxMillis = (long)daysTo10000 * millisPerDay; + + private const long oADateMinAsTicks = (daysPer100Years - daysPerYear) * ticksPerDay; + + private static long DoubleDateToTicks(double value) + { + // The check done this way will take care of NaN + if (!(value < oADateMaxAsDouble) || !(value > oADateMinAsDouble)) + { + return 0; + } + + // Conversion to long will not cause an overflow here, as at this point the "value" is in between OADateMinAsDouble and OADateMaxAsDouble + var millis = (long)(value * millisPerDay + (value >= 0 ? 0.5 : -0.5)); + // The interesting thing here is when you have a value like 12.5 it all positive 12 days and 12 hours from 01/01/1899 + // However if you a value of -12.25 it is minus 12 days but still positive 6 hours, almost as though you meant -11.75 all negative + // This line below fixes up the millis in the negative case + if (millis < 0) + { + millis -= (millis % millisPerDay) * 2; + } + + millis += doubleDateOffset / ticksPerMillisecond; + + if (millis < 0 || millis >= maxMillis) + { + return 0; + } + return millis * ticksPerMillisecond; + } + + private static double TicksToOADate(long value) + { + if (value == 0) + return 0.0; // Returns OleAut's zero'ed date value. + if (value < ticksPerDay) // This is a fix for VB. They want the default day to be 1/1/0001 rathar then 12/30/1899. + value += doubleDateOffset; // We could have moved this fix down but we would like to keep the bounds check. + if (value < oADateMinAsTicks) + throw new OverflowException(); + // Currently, our max date == OA's max date (12/31/9999), so we don't + // need an overflow check in that direction. + var millis = (value - doubleDateOffset) / ticksPerMillisecond; + if (millis < 0) + { + var frac = millis % millisPerDay; + if (frac != 0) millis -= (millisPerDay + frac) * 2; + } + return Math.Round((double)millis / millisPerDay, 10); + } + + public static DateTime FromSLDate(double d) + { + return new DateTime(DoubleDateToTicks(d), DateTimeKind.Unspecified); + } + + public static DateTime FromSLDateToKRDate(double d) + { + return (new DateTime(DoubleDateToTicks(d), DateTimeKind.Unspecified)).AddHours(9); + } + + public static DateTime ToDateTime(this SLEntity value) + { + return new DateTime(DoubleDateToTicks(value), DateTimeKind.Unspecified); + } + + public static DateTime ToLocalDateTime(this SLEntity value) + { + return new DateTime(DoubleDateToTicks(value), DateTimeKind.Unspecified).ToLocalTime(); + } + + public static DateTime ToDayStart(this DateTime dt) + { + return new DateTime(dt.Year, dt.Month, dt.Day); + } + + public static DateTime ToKRInitTime(this DateTime dt) + { + return dt.AddDays(1).AddHours(3).ToDayStart().AddHours(-3); + } + + public static DateTime ToRankingEndTime(this DateTime dt) + { + return dt.AddDays(1).AddHours(3).AddMinutes(15).ToDayStart().AddHours(-3).AddMinutes(-15); + } + + public static DateTime ToWeekliyInitTime(this DateTime dt) + { + if (dt < dt.ToKRInitTime().AddDays(-1)) + { + return dt.ToKRInitTime().AddDays(-1); + } + else + { + var delta = 7 + DayOfWeek.Monday - dt.DayOfWeek; + return dt.AddDays(delta).AddHours(3).ToDayStart().AddHours(-3); + } + } + + public static double ToSLDate(this DateTime dt) + { + return TicksToOADate(dt.Ticks); + } + + public static double FromKRDateToSLDate(this DateTime dt) + { + return TicksToOADate(dt.AddHours(-9).Ticks); + } + + public static string ToSLDateString(this DateTime dt) + { + return dt.ToString("yyyy-MM-dd"); + } + + public static string ToSLDateTimeString(this DateTime dt) + { + return dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + + public static string ToSLDateTimeString(this DateTimeOffset dt) + { + return dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + + public static bool IsDateIn(DateTime date, SLEntity checkDate) + { + var start = new DateTime(checkDate["YStart"], checkDate["MStart"], checkDate["DStart"], checkDate["HStart"], 0, 0); + + if (checkDate["YEnd"] == false) checkDate["YEnd"] = checkDate["YStart"]; + if (checkDate["MEnd"] == false) checkDate["MEnd"] = checkDate["MStart"]; + if (checkDate["DEnd"] == false) checkDate["DEnd"] = checkDate["DStart"]; + if (checkDate["HEnd"] == false) checkDate["HEnd"] = checkDate["HStart"]; + + var end = new DateTime(checkDate["YEnd"], checkDate["MEnd"], checkDate["DEnd"], checkDate["HEnd"], 0, 0); + return date >= start && date < end; + } +} + +public abstract class SLDateTime +{ + private static SLDateTime instance; + + public static void Init(SLDateTime inst) + { + instance = inst; + } + + public static DateTime Now + { + get + { + if (instance == null) return DateTime.UtcNow; + + return instance.GetNow(); + } + set + { + if (instance == null) return; + + instance.SetNow(value); + } + } + + protected abstract DateTime GetNow(); + + protected abstract void SetNow(DateTime now); +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs.meta new file mode 100644 index 000000000..4c26104e3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLDateTimeUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39c2b8bb3f8be6144a6fad50799fa543 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity.meta b/Assets/_Datas/SLShared/SLSystem/SLEntity.meta new file mode 100644 index 000000000..a8b561443 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca529b11f40987640bad27af13b9e13a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs b/Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs new file mode 100644 index 000000000..d5ad3fc37 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs @@ -0,0 +1,670 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Superlazy +{ + internal abstract class SLContainerBase : SLEntity + { + public override string ID => id; + + protected SLContainerBase parent; + protected string id; + } + + internal class SLContainer : SLContainerBase + { + private SLEntity original; + private Dictionary attributes; + private HashSet removed; + private List links; + private bool dangled; + + public SLContainer(SLContainerBase original, SLContainerBase parent, string id) + { + this.original = original; + this.parent = parent; + this.id = id; + + if (original.IsNullOrFalse() && this.parent is null == false) + { + dangled = true; + } + } + + internal override SLEntity ToChild(SLContainerBase parent, string id) + { + if (id == null && parent is null) // 삭제시 + { + dangled = true; + + attributes?.Clear(); + removed?.Clear(); + original = null; + + if (links != null) + { + foreach (var link in links) + { + link.DestroyLink(); + } + } + } + else + { + if (dangled == false && this.parent is null == false) + { + return Clone().ToChild(parent, id); + } + else + { + dangled = false; + this.parent = parent; + this.id = id; + } + } + + return this; + } + + public override bool IsNumeric + { + get + { + if (IsExist()) + { + SLLog.Error($"this is not value : {id}"); + } + return false; + } + } + + public override bool IsValue => false; + + internal override bool IsExist() + { + if (dangled && (parent?.HasChild(id) ?? false)) return true; + + if ((attributes?.Count ?? 0) != 0) return true; + + if (original.IsNullOrFalse()) return false; + + if ((removed?.Count ?? 0) == 0) return true; + if (original.Any(e => removed.Contains(e.ID) == false)) return true; + + return false; + } + + public override bool HasChild(string attributeKey) + { + if (attributeKey == "ID") return true; + if (dangled && (parent?.HasChild(id) ?? false)) return parent[id].HasChild(attributeKey); + + if (removed?.Contains(attributeKey) ?? false) return false; + if (attributes?.ContainsKey(attributeKey) ?? false) return true; + + if (original.IsNullOrFalse() == false) + { + return original.HasChild(attributeKey); + } + + return false; + } + + public override IEnumerator GetEnumerator() + { + if (dangled && (parent?.HasChild(id) ?? false)) + { + foreach (var value in parent[id]) + { + yield return value; + } + + yield break; + } + + if (attributes?.Count > 0) + { + var keys = new List(attributes.Keys); + + foreach (var key in keys) + { + yield return attributes[key]; + } + } + + if (original) + { + foreach (var child in original.Where(e => (removed?.Contains(e.ID) ?? false) == false && (attributes?.ContainsKey(e.ID) ?? false) == false).ToList()) + { + yield return this[child.ID]; + } + } + } + + internal override double GetDouble() + { + if (dangled && (parent?.HasChild(id) ?? false)) + { + return parent[id].GetDouble(); + } + + if (IsExist()) + { + SLLog.Error($"this is not value : {id}"); + } + + return 0; + } + + internal override int GetInt() + { + if (dangled && (parent?.HasChild(id) ?? false)) + { + return parent[id].GetInt(); + } + + if (IsExist()) + { + SLLog.Error($"this is not value : {id}"); + } + + return 0; + } + + internal override string GetString() + { + if (dangled && (parent?.HasChild(id) ?? false)) + { + return parent[id].GetString(); + } + + if (IsExist() == false) + { + return string.Empty; + } + + return $"{id}[{attributes?.Count ?? 0}]"; + } + + internal override object GetObj() + { + if (dangled && (parent?.HasChild(id) ?? false)) + { + return parent[id].GetString(); + } + + SLLog.Error($"this is not value : {id}"); + return false; + } + + protected override int GetEntityHashCode() + { + if (dangled && (parent?.HasChild(id) ?? false)) return parent[id].GetHashCode(); + if (attributes != null && removed != null) return attributes.GetHashCode() & removed.GetHashCode(); // TODO: 딕셔너리 대신 정밀한 해시코드를 만들어야함(성능이슈 체크) + if (attributes != null) return attributes.GetHashCode(); // TODO: 딕셔너리 대신 정밀한 해시코드를 만들어야함(성능이슈 체크) + if (removed != null) return removed.GetHashCode(); // TODO: 딕셔너리 대신 정밀한 해시코드를 만들어야함(성능이슈 체크) + if (original.IsNullOrFalse() == false) return original.GetHashCode(); + return 0; + } + + public override SLEntity Link() + { + if (dangled) + { + if (parent?.HasChild(id) ?? false) return parent[id].Link(); + + SLLog.Error("Can't make empty link"); + return false; + } + + if (links == null) + { + links = new List(); + } + + var unusedLink = links.Find(l => l.Unused); + if (unusedLink is null == false) + { + return unusedLink; + } + + var newLink = new SLContainerLink(this); + links.Add(newLink); + return newLink; + } + + public override SLEntity Override() + { + if (dangled) + { + if (parent?.HasChild(id) ?? false) return parent[id].Override(); + + SLLog.Error("Can't make empty override"); + return false; + } + + return new SLContainer(this, null, null); + } + + public override SLEntity this[string attributeKey] + { + get + { + if (attributeKey == "ID") return ID; + + if (dangled) + { + if (parent?.HasChild(id) ?? false) return parent[id][attributeKey]; + return new SLContainer(null, this, attributeKey); // 댕글 + } + + if (removed?.Contains(attributeKey) ?? false) + { + return new SLContainer(null, this, attributeKey); + } + + if (attributes?.ContainsKey(attributeKey) ?? false) + { + return attributes[attributeKey]; + } + + if (original?.HasChild(attributeKey) ?? false) + { + var originalValue = original[attributeKey]; + if (originalValue.IsValue) + { + return originalValue; + } + else + { + if (attributes == null) attributes = new Dictionary(); + attributes[attributeKey] = originalValue.Override().ToChild(this, attributeKey); + return attributes[attributeKey]; + } + } + + return new SLContainer(null, this, attributeKey); + } + + set + { + // 댕글인경우 + if (dangled) + { + if (parent?.HasChild(id) ?? false) // 다른개체로 교체된 댕글 + { + parent[id][attributeKey] = value; + return; + } + + if (value.IsNullOrFalse()) // 삭제 + { + return; + } + + // 새로 등록 + attributes = new Dictionary(); + + Modified(attributeKey); + attributes[attributeKey] = value.ToChild(this, attributeKey); + + if (parent is null) return; + + parent[id] = this; + } + else + { + if (attributes?.ContainsKey(attributeKey) ?? false) // 키가 있는 경우 + { + if (value.IsNullOrFalse()) // 삭제 + { + { + Modified(attributeKey); + var v = attributes[attributeKey]; + attributes.Remove(attributeKey); + v.ToChild(null, null); + } + + if (removed == null) removed = new HashSet(); + removed.Add(attributeKey); + + if (IsExist() == false) + { + if (parent is null == false) + { + parent[id] = null; + } + } + } + else // 변경 + { + if (value == attributes[attributeKey]) return; + + { + Modified(attributeKey); + var v = attributes[attributeKey]; + attributes.Remove(attributeKey); + v.ToChild(null, null); + } + + attributes[attributeKey] = value.ToChild(this, attributeKey); + } + } + else // 키가 없는경우 + { + if (original?.HasChild(attributeKey) ?? false) // 덮어쓰기 + { + if (value.IsNullOrFalse()) // 삭제추가 + { + if (removed == null) removed = new HashSet(); + Modified(attributeKey); + removed.Add(attributeKey); + + if (IsExist() == false) + { + if (parent is null == false) + { + parent[id] = null; + } + } + } + else // 추가 + { + if (attributes == null) attributes = new Dictionary(); + + if (removed?.Contains(attributeKey) ?? false) + { + removed.Remove(attributeKey); + } + + { + Modified(attributeKey); + attributes[attributeKey] = value.ToChild(this, attributeKey); + } + } + } + else + { + // 추가 + if (value.IsNullOrFalse()) return; + + if (attributes == null) attributes = new Dictionary(); + + Modified(attributeKey); + attributes[attributeKey] = value.ToChild(this, attributeKey); + if (removed != null && removed.Contains(attributeKey)) + { + removed.Remove(attributeKey); + } + } + } + } + } + } + + public override SLEntity Clone() + { + if (dangled) + { + if (parent?.HasChild(id) ?? false) return parent[id].Clone(); + SLLog.Error($"Can't clone dangle object: {id}"); + return false; + } + + var ret = new SLContainer(null, null, null); + + foreach (var child in this) + { + ret[child.ID] = child.Clone(); + } + + return ret; + } + + internal override void Move(SLEntity parent, string newID) + { + if (dangled) + { + if (parent?.HasChild(id) ?? false) this.parent[id].Move(parent, newID); + SLLog.Error($"Can't move dangle object : {id} -> {newID}"); + return; + } + + if (id != null) // 이미 루트가 없다면 + { + var attrTemp = attributes; + this.parent[id] = false; + + attributes = attrTemp; + id = null; + } + + parent[id] = this; + } + + private HashSet modified; + + public override bool IsModified(string child) + { + var ret = false; + ret |= original?.IsModified(child) ?? false; + if (child == null) ret |= (modified?.Count ?? 0) != 0; + else ret |= modified?.Contains(child) ?? false; + + return ret; + } + + public override void EndModified() + { + modified?.Clear(); + } + + private void Modified(string child) + { + if (modified == null) modified = new HashSet(); + modified.Add(child); + } + } + + internal class SLContainerLink : SLContainerBase + { + public bool Unused => id == null; + private SLContainer original; + + public SLContainerLink(SLContainer original) + { + this.original = original; + } + + internal override SLEntity ToChild(SLContainerBase parent, string id) + { + if (id == null && parent is null) // 삭제시 + { + this.parent = null; + this.id = null; + return this; + } + + if (this.parent) return Clone().ToChild(parent, id); + + this.parent = parent; + this.id = id; + return this; + } + + internal void DestroyLink() + { + if (Unused) return; + parent[id] = false; + original = null; + } + + public override bool IsNumeric + { + get + { + SLLog.Error($"this is not value : {id}"); + return false; + } + } + + public override bool IsValue + { + get + { + if (original.IsNullOrFalse()) return true; + return false; + } + } + + internal override bool IsExist() + { + if (original.IsNullOrFalse()) return false; + return true; + } + + public override bool HasChild(string attributeKey) + { + if (original.IsNullOrFalse()) return false; + return original.HasChild(attributeKey); + } + + public override IEnumerator GetEnumerator() + { + if (original.IsNullOrFalse()) return Enumerable.Empty().GetEnumerator(); + return original.GetEnumerator(); + } + + internal override double GetDouble() + { + SLLog.Error($"this is not value : {id}"); + return 0; + } + + internal override int GetInt() + { + SLLog.Error($"this is not value : {id}"); + return 0; + } + + internal override object GetObj() + { + SLLog.Error($"this is not value : {id}"); + return false; + } + + public override SLEntity Link() + { + if (original.IsNullOrFalse()) + { + SLLog.Error($"Destroyed Link, {id}"); + return null; + } + + return original.Link(); + } + + public override SLEntity Override() + { + if (original.IsNullOrFalse()) + { + SLLog.Error($"Destroyed Link, {id}"); + return null; + } + + return original.Override(); + } + + internal override string GetString() + { + if (original.IsNullOrFalse()) return string.Empty; + return $"{id} - {original}"; + } + + public override SLEntity this[string attributeKey] + { + get + { + if (attributeKey == "ID") return ID; + + if (original.IsNullOrFalse()) + { + if (parent?.HasChild(id) ?? false) return parent[id][attributeKey]; + return false; + } + + return original[attributeKey]; + } + + set + { + if (original.IsNullOrFalse()) + { + if (parent?.HasChild(id) ?? false) parent[id][attributeKey] = value; + + // 빈객체에 값을 넣어도 이값을 다시참조할 방법이 없음 + SLLog.Error($"Dangle Link. Can't set new Value : {attributeKey}-{value}"); + return; + } + else + { + original[attributeKey] = value; + } + } + } + + public override SLEntity Clone() + { + if (original.IsNullOrFalse()) + { + SLLog.Error($"Destroyed Link, {id}"); + return null; + } + + return original.Clone(); + } + + internal override void Move(SLEntity parent, string id) + { + if (original.IsNullOrFalse()) + { + SLLog.Error($"Destroyed Link, {id}"); + return; + } + + this.parent[id] = null; + parent[id] = this; + } + + protected override int GetEntityHashCode() + { + if (original.IsNullOrFalse()) + { + SLLog.Error($"Destroyed Link, {id}"); + return 0; + } + + return original.GetHashCode(); + } + + public override bool IsModified(string child) + { + if (original.IsNullOrFalse()) + { + return false; + } + + return original.IsModified(child); + } + + public override void EndModified() + { + if (original.IsNullOrFalse()) + { + return; + } + + original.EndModified(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs.meta new file mode 100644 index 000000000..0c0afd71c --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/Container.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dae3057243a613a4b901c956ca43e60e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs b/Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs new file mode 100644 index 000000000..990e1698c --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs @@ -0,0 +1,941 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Superlazy +{ + public abstract class SLEntity : IEnumerable, IComparable + { + public abstract bool IsNumeric { get; } + public abstract bool IsValue { get; } + public abstract string ID { get; } + + public abstract bool IsModified(string child); + + public abstract void EndModified(); + + public abstract bool HasChild(string attributeKey); + + internal abstract object GetObj(); + + internal abstract int GetInt(); + + internal abstract double GetDouble(); + + internal abstract string GetString(); + + internal abstract bool IsExist(); + + protected abstract int GetEntityHashCode(); + + internal static IEnumerable emptyEnumerable = Enumerable.Empty(); + + public abstract IEnumerator GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public SLEntity this[SLEntity attributeKey] + { + get + { + if (attributeKey.IsNullOrFalse()) + { + SLLog.Warn($"attributeKey shouldn't null obj {ToString()}, {ID}"); +#if SERVER && LOCALTEST + throw new Exception(); +#endif + return Empty; + } + + return this[attributeKey.ToString()]; + } + set + { + if (attributeKey.IsNullOrFalse()) + { + SLLog.Warn($"attributeKey shouldn't null obj {ToString()}, {ID}"); +#if SERVER && LOCALTEST + throw new Exception(); +#endif + return; + } + + this[attributeKey.ToString()] = value; + } + } + + public abstract SLEntity this[string attributeKey] + { + get; + set; + } + + public abstract SLEntity Clone(); + + public abstract SLEntity Link(); + + public abstract SLEntity Override(); + + public static SLEntity Empty => new SLContainer(null, null, null); + + public static implicit operator double(SLEntity v) + { + return v.GetDouble(); + } + + public static implicit operator float(SLEntity v) + { + return (float)v.GetDouble(); + } + + public static implicit operator int(SLEntity v) + { + return v.GetInt(); + } + + public static implicit operator string(SLEntity v) + { + return v.GetString(); + } + + public static implicit operator bool(SLEntity v) + { + return v != default(SLEntity) && v.IsExist(); + } + + //public static implicit operator Vector2(SLEntity v) + //{ + // return new Vector2(v["X"], v["Y"]); + //} + + public static bool operator <(SLEntity lhs, SLEntity rhs) + { + if (lhs == null && rhs == null) + { + return 0 < 0; + } + else if (lhs.IsNullOrFalse()) + { + return 0 < rhs.GetDouble(); + } + else if (rhs.IsNullOrFalse()) + { + return lhs.GetDouble() < 0; + } + else + { + return lhs.GetDouble() < rhs.GetDouble(); + } + } + + public static bool operator >(SLEntity lhs, SLEntity rhs) + { + if (lhs == null && rhs == null) + { + return 0 > 0; + } + else if (lhs.IsNullOrFalse()) + { + return 0 > rhs.GetDouble(); + } + else if (rhs.IsNullOrFalse()) + { + return lhs.GetDouble() > 0; + } + + return lhs.GetDouble() > rhs.GetDouble(); + } + + public static bool operator <=(SLEntity lhs, SLEntity rhs) + { + if (lhs == null && rhs == null) + { + return 0 <= 0; + } + else if (lhs.IsNullOrFalse()) + { + return 0 <= rhs.GetDouble(); + } + else if (rhs.IsNullOrFalse()) + { + return lhs.GetDouble() <= 0; + } + else + { + return lhs.GetDouble() <= rhs.GetDouble(); + } + } + + public static bool operator >=(SLEntity lhs, SLEntity rhs) + { + if (lhs == null && rhs == null) + { + return 0 >= 0; + } + else if (lhs.IsNullOrFalse()) + { + return 0 >= rhs.GetDouble(); + } + else if (rhs.IsNullOrFalse()) + { + return lhs.GetDouble() >= 0; + } + else + { + return lhs.GetDouble() >= rhs.GetDouble(); + } + } + + public static SLEntity operator +(SLEntity single) + { + return single; + } + + public static SLEntity operator -(SLEntity single) + { + if (single.IsNullOrFalse()) + { + return 0; + } + + return -single.GetDouble(); + } + + public static SLEntity operator +(SLEntity lhs, SLEntity rhs) + { + if (lhs.IsNullOrFalse()) + { + return rhs; + } + + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (lhs.IsNumeric && rhs.IsNumeric) + return lhs.GetDouble() + rhs.GetDouble(); + + return lhs.ToString() + rhs.ToString(); + } + + public static double operator +(double lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (rhs.IsNumeric) + return lhs + rhs.GetDouble(); + + return lhs; + } + + public static double operator +(SLEntity lhs, double rhs) + { + if (lhs.IsNullOrFalse()) + { + return rhs; + } + + if (lhs.IsNumeric) + return lhs.GetDouble() + rhs; + + return rhs; + } + + public static float operator +(float lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (rhs.IsNumeric) + return lhs + (float)rhs.GetDouble(); + + return lhs; + } + + public static float operator +(SLEntity lhs, float rhs) + { + if (lhs.IsNullOrFalse()) + { + return rhs; + } + + if (lhs.IsNumeric) + return (float)lhs.GetDouble() + rhs; + + return rhs; + } + + public static int operator +(SLEntity lhs, int rhs) + { + if (lhs.IsNullOrFalse()) + { + return rhs; + } + + if (lhs.IsNumeric) + return lhs.GetInt() + rhs; + + return rhs; + } + + public static int operator +(int lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (rhs.IsNumeric) + return lhs + rhs.GetInt(); + + return lhs; + } + + public static SLEntity operator -(SLEntity lhs, SLEntity rhs) + { + if (lhs.IsNullOrFalse()) + { + if (rhs == default(SLEntity)) return 0; + return -rhs.GetDouble(); + } + + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + return lhs.GetDouble() - rhs.GetDouble(); + } + + public static double operator -(double lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (rhs.IsNumeric) + return lhs - rhs.GetDouble(); + + return lhs; + } + + public static double operator -(SLEntity lhs, double rhs) + { + if (lhs.IsNullOrFalse()) + { + return -rhs; + } + + if (lhs.IsNumeric) + return lhs.GetDouble() - rhs; + + return -rhs; + } + + public static float operator -(float lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (rhs.IsNumeric) + return lhs - (float)rhs.GetDouble(); + + return lhs; + } + + public static float operator -(SLEntity lhs, float rhs) + { + if (lhs.IsNullOrFalse()) + { + return -rhs; + } + + if (lhs.IsNumeric) + return (float)lhs.GetDouble() - rhs; + + return -rhs; + } + + public static int operator -(SLEntity lhs, int rhs) + { + if (lhs.IsNullOrFalse()) + { + return -rhs; + } + + if (lhs.IsNumeric) + return lhs.GetInt() - rhs; + + return rhs; + } + + public static int operator -(int lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return lhs; + } + + if (rhs.IsNumeric) + return lhs - rhs.GetInt(); + + return lhs; + } + + public static SLEntity operator *(SLEntity lhs, SLEntity rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs.IsNullOrFalse()) + { + return 0; + } + + if (lhs.IsNumeric && rhs.IsNumeric) + return lhs.GetDouble() * rhs.GetDouble(); + + return 0; + } + + public static double operator *(double lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs.IsNumeric) + return lhs * rhs.GetDouble(); + + return 0; + } + + public static double operator *(SLEntity lhs, double rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (lhs.IsNumeric) + return lhs.GetDouble() * rhs; + + return 0; + } + + public static float operator *(float lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs.IsNumeric) + return lhs * (float)rhs.GetDouble(); + + return 0; + } + + public static float operator *(SLEntity lhs, float rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (lhs.IsNumeric) + return (float)lhs.GetDouble() * rhs; + + return 0; + } + + public static int operator *(SLEntity lhs, int rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (lhs.IsNumeric) + return lhs.GetInt() * rhs; + + return 0; + } + + public static int operator *(int lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs.IsNumeric) + return lhs * rhs.GetInt(); + + return 0; + } + + public static SLEntity operator /(SLEntity lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + SLLog.Warn($"Divide by 0 {lhs?.GetDouble() ?? 0} / 0"); + + if (lhs.IsNullOrFalse()) return 0; + else return Math.Sign(lhs.GetDouble()) * double.PositiveInfinity; + } + + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs.IsNumeric == false || rhs == 0.0) + { + SLLog.Warn($"Divide by 0 {lhs.GetDouble()} / 0"); + return Math.Sign(lhs.GetDouble()) * double.PositiveInfinity; + } + + return lhs.GetDouble() / rhs.GetDouble(); + } + + public static double operator /(double lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return Math.Sign(lhs) * double.PositiveInfinity; + } + + if (rhs.IsNumeric == false || rhs == 0.0) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return Math.Sign(lhs) * double.PositiveInfinity; + } + + return lhs / rhs.GetDouble(); + } + + public static double operator /(SLEntity lhs, double rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs == 0.0) + { + SLLog.Warn($"Divide by 0 {lhs.GetDouble()} / 0"); + return Math.Sign(lhs.GetDouble()) * double.PositiveInfinity; + } + + return lhs.GetDouble() / rhs; + } + + public static float operator /(float lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return Math.Sign(lhs) * float.PositiveInfinity; + } + + if (rhs.IsNumeric == false || rhs == 0.0) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return Math.Sign(lhs) * float.PositiveInfinity; + } + + return lhs / (float)rhs.GetDouble(); + } + + public static float operator /(SLEntity lhs, float rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs == 0.0) + { + SLLog.Warn($"Divide by 0 {lhs.GetDouble()} / 0"); + return Math.Sign(lhs.GetDouble()) * float.PositiveInfinity; + } + + return (float)lhs.GetDouble() / rhs; + } + + public static int operator /(int lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return lhs > 0 ? int.MaxValue : int.MinValue; + } + + if (rhs.IsNumeric == false || rhs == 0.0) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return lhs > 0 ? int.MaxValue : int.MinValue; + } + + return lhs / rhs.GetInt(); + } + + public static int operator /(SLEntity lhs, int rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs == 0) + { + SLLog.Warn($"Divide by 0 {lhs.GetInt()} / 0"); + return lhs.GetInt() > 0 ? int.MaxValue : int.MinValue; + } + + return lhs.GetInt() / rhs; + } + + public static int operator %(SLEntity lhs, SLEntity rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs.IsNullOrFalse()) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return lhs; + } + + return lhs.GetInt() % rhs.GetInt(); + } + + public static int operator %(SLEntity lhs, int rhs) + { + if (lhs.IsNullOrFalse()) + { + return 0; + } + + if (rhs == 0) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return lhs; + } + + return lhs.GetInt() % rhs; + } + + public static int operator %(int lhs, SLEntity rhs) + { + if (lhs == 0) + { + return 0; + } + + if (rhs.IsNullOrFalse() || rhs.GetInt() == 0) + { + SLLog.Warn($"Divide by 0 {lhs} / 0"); + return lhs; + } + + return lhs % rhs.GetInt(); + } + + public static bool operator ==(SLEntity lhs, bool rhs) + { + if (lhs.IsNullOrFalse()) return !rhs; + else return lhs.IsExist() == rhs; + } + + public static bool operator !=(SLEntity lhs, bool rhs) + { + return !(lhs == rhs); + } + + public static bool operator ==(bool lhs, SLEntity rhs) + { + if (rhs.IsNullOrFalse()) return !lhs; + else return rhs.IsExist() == rhs; + } + + public static bool operator !=(bool lhs, SLEntity rhs) + { + return !(rhs == lhs); + } + + //public static bool operator ==(SLEntity lhs, string rhs) + //{ + // return (string)lhs == rhs; + //} + + //public static bool operator !=(SLEntity lhs, string rhs) + //{ + // return !(lhs == rhs); + //} + + //public static bool operator ==(string lhs, SLEntity rhs) + //{ + // return rhs == lhs; + //} + + //public static bool operator !=(string lhs, SLEntity rhs) + //{ + // return !(rhs == lhs); + //} + + public static bool operator ==(SLEntity lhs, int rhs) + { + return lhs.GetInt() == rhs; + } + + public static bool operator !=(SLEntity lhs, int rhs) + { + return !(lhs == rhs); + } + + public static bool operator ==(int lhs, SLEntity rhs) + { + return rhs == lhs; + } + + public static bool operator !=(int lhs, SLEntity rhs) + { + return !(rhs == lhs); + } + + public static bool operator ==(SLEntity lhs, double rhs) + { + return lhs.GetDouble() == rhs; + } + + public static bool operator !=(SLEntity lhs, double rhs) + { + return !(lhs == rhs); + } + + public static bool operator ==(double lhs, SLEntity rhs) + { + return rhs == lhs; + } + + public static bool operator !=(double lhs, SLEntity rhs) + { + return !(rhs == lhs); + } + + public static bool operator ==(SLEntity lhs, float rhs) + { + return lhs.GetDouble() == rhs; + } + + public static bool operator !=(SLEntity lhs, float rhs) + { + return !(lhs == rhs); + } + + public static bool operator ==(float lhs, SLEntity rhs) + { + return rhs == lhs; + } + + public static bool operator !=(float lhs, SLEntity rhs) + { + return !(rhs == lhs); + } + + //public static bool operator ==(SLEntity lhs, SLVector2 rhs) + //{ + // return lhs["X"] == rhs.X && lhs["Y"] == rhs.Y; + //} + + //public static bool operator !=(SLEntity lhs, SLVector2 rhs) + //{ + // return lhs["X"] != rhs.X || lhs["Y"] != rhs.Y; + //} + + public static bool operator ==(SLEntity lhs, SLEntity rhs) + { + if (ReferenceEquals(lhs, rhs))// 동일 객체 + { + return true; + } + else if (lhs is null || lhs.IsExist() == false) // 빈객체 + { + return rhs is null || rhs.IsExist() == false; + } + else if (rhs is null || rhs.IsExist() == false) + { + return lhs is null || lhs.IsExist() == false; + } + else if (lhs.IsValue && rhs.IsValue) // 값비교 + { + if (lhs.IsNumeric == false || rhs.IsNumeric == false) + { + return lhs.GetString().Equals(rhs.GetString()); + } + else + { + return lhs.GetDouble() == rhs.GetDouble(); + } + } + else if (lhs.IsValue == false && rhs.IsValue == false) // 리스트 + { + var lhsCount = lhs.Count(); + var rhsCount = rhs.Count(); + + if (lhsCount != rhsCount) + { + return false; + } + + foreach (var l in lhs) + { + if (rhs.HasChild(l.ID) == false) + { + return false; + } + + if (l != rhs[l.ID]) + { + return false; + } + } + + return true; + } + + return false; + } + + public static bool operator !=(SLEntity lhs, SLEntity rhs) + { + return !(lhs == rhs); + } + + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + if ((obj is SLEntity) == false) + { + return obj.Equals(GetObj()); + } + + return (obj as SLEntity) == this; + } + + public override int GetHashCode() + { + return GetEntityHashCode(); + } + + public static implicit operator SLEntity(string v) + { + if (v == null || v == string.Empty) return Empty; + else return new SLValueString(v); + } + + public static implicit operator SLEntity(int v) + { + return new SLValueInt(v); + } + + public static implicit operator SLEntity(double v) + { + return new SLValueDouble(v); + } + + private static SLEntity boolTrue = new SLValueString("True"); + //private static SLEntity boolFalse = new SLContainer(null, null, null); + + public static implicit operator SLEntity(bool v) + { + if (v) + { + if (boolTrue.ID != null) boolTrue = new SLValueString("True"); + return boolTrue; + } + else + { + return new SLContainer(null, null, null); // TODO: 성능상 수정 필요 + //if (boolFalse || boolFalse.ID != null) boolFalse = new SLContainer(null, null, null); + //boolFalse.test = true; + //return boolFalse; + } + } + + //public static implicit operator SLEntity(SLVector2 v) + //{ + // var ret = new SLContainer(null); + // ret["X"] = v.X; + // ret["Y"] = v.Y; + // return ret; + //} + + public override string ToString() + { + return GetString(); + } + + public int CompareTo(SLEntity other) + { + if (IsValue == false || other.IsValue == false) + { + // TODO: 리스트 비교 필요 + return GetHashCode().CompareTo(other.GetHashCode()); + } + + if (IsNumeric && other.IsNumeric) + { + return GetDouble().CompareTo(other.GetDouble()); + } + + var thisSTR = GetString(); + var otherSTR = other.GetString(); + return thisSTR.CompareTo(otherSTR); + } + + internal abstract SLEntity ToChild(SLContainerBase parent, string id); + + internal abstract void Move(SLEntity parent, string id); + + public static bool Changed(SLEntity lhs, SLEntity rhs) + { + if (lhs is null && rhs is null) return false; + + if (lhs.IsNullOrFalse()) return rhs; + if (rhs.IsNullOrFalse()) return lhs; + + if (lhs.IsValue && rhs.IsValue) + { + return lhs != rhs; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs.meta new file mode 100644 index 000000000..f2463d6a3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/SLEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5bdef6b90662bde4696cc01d0ac24899 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs b/Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs new file mode 100644 index 000000000..71b375103 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Superlazy +{ + public static class Utility + { + public static bool IsNullOrFalse(this SLEntity entity) + { + return entity == null || entity.IsExist() == false; + } + + public static string CombinePath(this string first, string add) + { + if (first == null || first == string.Empty) + { + return add; + } + else if (add == null || add == string.Empty) + { + return first; + } + else + { + return new StringBuilder().Append(first).Append('.').Append(add).ToString(); + } + } + + public static void RemoveIf(this SLEntity entity, Func onRemove) + { + var removes = new List(); + foreach (var e in entity) + { + if (onRemove(e)) removes.Add(e.ID); + } + + foreach (var r in removes) + { + entity[r] = false; + } + } + + public static bool Contains(this SLEntity entity, string value) + { + return entity.ToString().Contains(value); + } + + public static SLEntity Get(this SLEntity entity, string path) + { + if (entity == null) return false; + var list = entity; + + var oldIdx = 0; + var idx = path.IndexOf('.'); + var len = path.Length; + while (idx != -1 && oldIdx < len) + { + { + var sub = path.Substring(oldIdx, idx - oldIdx); + if (string.IsNullOrEmpty(sub) == false) + list = list[sub]; + } + + oldIdx = idx + 1; + if (idx + 1 >= path.Length) + { + idx = -1; + } + else + { + idx = path.IndexOf('.', idx + 1); + } + } + + if (oldIdx < len) + { + var sub = path.Substring(oldIdx); + if (string.IsNullOrEmpty(sub) == false) + list = list[sub]; + } + + return list; + } + + public static void Set(this SLEntity entity, string path, SLEntity value) + { + var list = entity; + var oldIdx = 0; + var idx = path.IndexOf('.'); + var len = path.Length; + + if (idx == -1) + { + list[path] = value; + return; + } + + while (idx != -1 && oldIdx < len) + { + { + var sub = path.Substring(oldIdx, idx - oldIdx); + if (string.IsNullOrEmpty(sub) == false) + list = list[sub]; + } + + oldIdx = idx + 1; + if (idx + 1 >= path.Length) + { + idx = -1; + } + else + { + idx = path.IndexOf('.', idx + 1); + } + } + + if (oldIdx < len) + { + var sub = path.Substring(oldIdx); + if (string.IsNullOrEmpty(sub) == false) + { + list[sub] = value; + } + else + { + SLLog.Error($"Set Can't end with . : {path}"); + } + } + } + + public static bool IsLeft(this SLEntity entity, string value) + { + return entity.ToString().IsLeft(value); + } + + public static bool IsLeft(this string str, string value) + { + if (str.Length >= value.Length) + { + for (var i = 0; i < value.Length; i++) + { + if (str[i] != value[i]) + return false; + } + + return true; + } + + return false; + } + + public static bool IsRight(this SLEntity entity, string value) + { + return entity.ToString().IsRight(value); + } + + public static bool IsRight(this string str, string value) + { + if (str.Length >= value.Length) + { + for (var i = 1; i <= value.Length; ++i) + { + if (str[str.Length - i] != value[value.Length - i]) + return false; + } + + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs.meta new file mode 100644 index 000000000..58c3cd4fc --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/Utility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f3af70c1c51ef34aaca98e9b7dd64ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs b/Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs new file mode 100644 index 000000000..02822eb14 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs @@ -0,0 +1,214 @@ +using System.Collections.Generic; + +namespace Superlazy +{ + internal abstract class SLValue : SLEntity + { + public override bool IsValue => true; + protected string id; + public override string ID => id; + + public override SLEntity Link() + { + SLLog.Error($"Value can't be link {id}: {GetString()}"); + return false; + } + + public override SLEntity Override() + { + SLLog.Error($"Value can't be override {id}: {GetString()}"); + return false; + } + + internal override bool IsExist() + { + return true; + } + + public override bool HasChild(string attributeKey) + { + if (attributeKey == "ID") return true; + + SLLog.Error($"Can't get Child in Value {id}: {GetObj()}"); + return false; + } + + public override IEnumerator GetEnumerator() + { + SLLog.Error($"Can't get Enumerator in Value {id}: {GetObj()}"); + yield break; + } + + public override SLEntity this[string attributeKey] + { + get + { + if (attributeKey == "ID") return ID; + + SLLog.Error($"Can't get attribute in Value {id}: {GetObj()}"); + return false; + } + + set => SLLog.Error($"Can't set attribute in Value : {GetObj()}"); + } + + internal override SLEntity ToChild(SLContainerBase parent, string id) + { + if (this.id != null) + { + return Clone().ToChild(parent, id); + } + + this.id = id; + return this; + } + + internal override void Move(SLEntity parent, string id) + { + this.id = id; + parent[id] = this; + } + + //public override SLEntity Clone() + //{ + // return this; + //} + + public override bool IsModified(string child) + { + return false; + } + + public override void EndModified() + { + } + } + + internal class SLValueDouble : SLValue + { + private readonly double value; + + public SLValueDouble(double value) + { + this.value = value; + } + + public override bool IsNumeric => true; + + internal override double GetDouble() + { + return value; + } + + internal override int GetInt() + { + return (int)value; + } + + internal override object GetObj() + { + return value; + } + + protected override int GetEntityHashCode() + { + return value.GetHashCode(); + } + + internal override string GetString() + { + return value.ToString(); + } + + public override SLEntity Clone() + { + return new SLValueDouble(value); + } + } + + internal class SLValueInt : SLValue + { + private readonly int value; + + public SLValueInt(int value) + { + this.value = value; + } + + public override bool IsNumeric => true; + + internal override double GetDouble() + { + return value; + } + + internal override int GetInt() + { + return value; + } + + internal override object GetObj() + { + return value; + } + + protected override int GetEntityHashCode() + { + return value.GetHashCode(); + } + + internal override string GetString() + { + return value.ToString(); + } + + public override SLEntity Clone() + { + return new SLValueInt(value); + } + } + + internal class SLValueString : SLValue + { + private readonly string value; + + public SLValueString(string value) + { + this.value = value; + } + + public override bool IsNumeric => false; + + internal override double GetDouble() + { + SLLog.Warn($"String is not number {ID}:{GetString()}"); + return 0; + } + + internal override int GetInt() + { + SLLog.Warn($"String is not number {ID}:{GetString()}"); + return 0; + } + + internal override object GetObj() + { + return value; + } + + protected override int GetEntityHashCode() + { + return value.GetHashCode(); + } + + internal override string GetString() + { + return value; + } + + public override SLEntity Clone() + { + return new SLValueString(value); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs.meta new file mode 100644 index 000000000..77a4f46d4 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntity/Values.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7693ce3a928c5f34e91b146b25143394 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace.meta new file mode 100644 index 000000000..e73fdade6 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5ff8197c3c3246f40aac024382d634fe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader.meta new file mode 100644 index 000000000..c3c9f9a46 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 62e4bd6c3eb89044f82f7ecdb26d960f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs new file mode 100644 index 000000000..25522c74c --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs @@ -0,0 +1,476 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Superlazy.Loader +{ + public static class JsonLoader + { + private static void ParseElement(SLEntity context, string token, string tokenName, bool quoted) + { + if (context.HasChild(tokenName)) + { + throw new Exception($"{tokenName} Already has child"); + } + + if (quoted) + { + context[tokenName] = token; + return; + } + + { + if (double.TryParse(token, out var val)) + { + if (val < 1 || val > int.MaxValue || (token.IsRight(".0") == false && token.Contains('.'))) + { + context[tokenName] = val; + } + else + { + context[tokenName] = (int)val; + } + } + else + { + context[tokenName] = token; + } + } + } + + public static SLEntity Parse(SLEntity root, string aJSON) + { + var stack = new Stack(); + SLEntity context = null; + var i = 0; + var Token = new StringBuilder(); + var TokenName = ""; + var QuoteMode = false; + var TokenIsQuoted = false; + while (i < aJSON.Length) + { + switch (aJSON[i]) + { + case '{': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + if (context is null) + { + stack.Push(root); + } + else + { + stack.Push(context[TokenName]); + } + + TokenName = ""; + Token.Length = 0; + context = stack.Peek(); + break; + + case '[': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + stack.Push(SLEntity.Empty); + if (context != null) + { + context[TokenName] = stack.Peek(); + } + + TokenName = ""; + Token.Length = 0; + context = stack.Peek(); + break; + + case '}': + case ']': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + if (stack.Count == 0) + throw new Exception("JSON Parse: Too many closing brackets\n" + aJSON); + + stack.Peek().EndModified(); + stack.Pop(); + if (Token.Length > 0 || TokenIsQuoted) + { + ParseElement(context, Token.ToString(), TokenName, TokenIsQuoted); + TokenIsQuoted = false; + } + + TokenName = ""; + Token.Length = 0; + if (stack.Count > 0) + context = stack.Peek(); + break; + + case ':': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + TokenName = Token.ToString(); + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '"': + QuoteMode ^= true; + TokenIsQuoted |= QuoteMode; + break; + + case ',': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + if (Token.Length > 0 || TokenIsQuoted) + { + ParseElement(context, Token.ToString(), TokenName, TokenIsQuoted); + } + + TokenName = ""; + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '\r': + case '\n': + break; + + case ' ': + case '\t': + if (QuoteMode) + Token.Append(aJSON[i]); + break; + + case '\\': + ++i; + if (QuoteMode) + { + var C = aJSON[i]; + switch (C) + { + case 't': + Token.Append('\t'); + break; + + case 'r': + Token.Append('\r'); + break; + + case 'n': + Token.Append('\n'); + break; + + case 'b': + Token.Append('\b'); + break; + + case 'f': + Token.Append('\f'); + break; + + case 'u': + { + var s = aJSON.Substring(i + 1, 4); + Token.Append((char)int.Parse( + s, + System.Globalization.NumberStyles.AllowHexSpecifier)); + i += 4; + break; + } + + default: + Token.Append(C); + break; + } + } + + break; + + default: + Token.Append(aJSON[i]); + break; + } + + ++i; + } + + if (QuoteMode) + { + throw new Exception("JSON Parse: Quotation marks seems to be messed up.\n" + aJSON); + } + + return context; + } + + public static SLEntity LoadJson(string v) + { + var obj = Parse(SLEntity.Empty, v); + return obj; + } + + public static SLEntity LoadJson(SLEntity root, string v) + { + var obj = Parse(root, v); + return obj; + } + + public static string SaveToJson(SLEntity entity, int sortDepth = -1, bool indent = false) + { + if (indent) + { + var collection = SaveObj(null, entity, new StringBuilder(1024 * 1024 * 5), 0, sortDepth); + return collection.ToString(); + } + else + { + var collection = SaveObjNoIndent(null, entity, new StringBuilder(1024 * 1024 * 5), 0, sortDepth); + return collection.ToString(); + } + } + + private static StringBuilder SaveObj(string id, SLEntity entity, StringBuilder builder, int depth, int sortDepth = -1) + { + if (depth > 0) builder.Append(' ', depth); + + if (entity.IsValue) + { + if (entity.IsNumeric) + { + builder.Append('\"').Append(id).Append("\": ").Append(RoundAndFormat(entity)); + } + else + { + builder.Append('\"').Append(id).Append("\": \""); + foreach (var ch in entity.ToString()) + { + if (ch == '"') + { + builder.Append("\\\""); + } + else if (ch == '\n') + { + builder.Append("\\n"); + } + else + { + builder.Append(ch); + } + } + + builder.Append("\""); + } + } + else + { + if (depth != 0) + { + builder.Append('\"').Append(id).Append("\": {\r\n"); + } + else + { + builder.Append("{\r\n"); + } + + if (sortDepth != -1 && depth > sortDepth) + { + var count = entity.Count(); + foreach (var child in entity.OrderBy(u => + { + if (int.TryParse(u.ID, out var numberID)) + { + return string.Format("{0}{1:0000}", u.IsValue ? 0 : 1, numberID); + } + + return string.Format("{0}{1}", u.IsValue ? 0 : 1, u.ID); + })) + { + SaveObj(child.ID, child, builder, depth + 2, sortDepth); + count -= 1; + + if (count > 0) + { + builder.Append(",\r\n"); + } + else + { + builder.Append("\r\n"); + } + } + } + else + { + var count = entity.Count(); + foreach (var child in entity) + { + SaveObj(child.ID, child, builder, depth + 2, sortDepth); + count -= 1; + + if (count > 0) + { + builder.Append(",\r\n"); + } + else + { + builder.Append("\r\n"); + } + } + } + + if (depth > 0) builder.Append(' ', depth); + builder.Append('}'); + } + + return builder; + } + + private static StringBuilder SaveObjNoIndent(string id, SLEntity entity, StringBuilder builder, int depth, int sortDepth = -1) + { + if (entity.IsValue) + { + if (entity.IsNumeric) + { + //builder.Append('\"').Append(id).Append("\":").Append(RoundAndFormat(entity)); // 인덴트가 없다는건 데이터 전송용이므로 빠르게 진행 + builder.Append('\"').Append(id).Append("\":").Append(entity.ToString()); + } + else + { + builder.Append('\"').Append(id).Append("\":\""); + foreach (var ch in entity.ToString()) + { + if (ch == '"') + { + builder.Append("\\\""); + } + else if (ch == '\n') + { + builder.Append("\\n"); + } + else + { + builder.Append(ch); + } + } + + builder.Append("\""); + } + } + else + { + if (depth != 0) + { + builder.Append('\"').Append(id).Append("\":{"); + } + else + { + builder.Append("{"); + } + + if (sortDepth != -1 && depth > sortDepth) + { + var count = entity.Count(); + foreach (var child in entity.OrderBy(u => + { + if (int.TryParse(u.ID, out var numberID)) + { + return string.Format("{0}{1:0000}", u.IsValue ? 0 : 1, numberID); + } + + return string.Format("{0}{1}", u.IsValue ? 0 : 1, u.ID); + })) + { + SaveObjNoIndent(child.ID, child, builder, depth + 2, sortDepth); + count -= 1; + + if (count > 0) + { + builder.Append(","); + } + } + } + else + { + var count = entity.Count(); + foreach (var child in entity) + { + SaveObjNoIndent(child.ID, child, builder, depth + 2, sortDepth); + count -= 1; + + if (count > 0) + { + builder.Append(","); + } + } + } + + builder.Append('}'); + } + + return builder; + } + + public static string RoundAndFormat(SLEntity num) + { + var str = num.ToString(); + var decimalIndex = str.IndexOf('.'); + if (decimalIndex >= 0) + { + // 2자리까지 반올림 대상으로 한다 + var roundIndex = str.Length - 2; + if (str[roundIndex] != '9' && str[roundIndex] != '0') + { + roundIndex = str.Length - 3; + } + + var nine = false; + if (str[roundIndex] == '9') nine = true; + while (roundIndex > decimalIndex && ((nine && str[roundIndex] == '9') || (nine == false && str[roundIndex] == '0'))) + { + roundIndex--; + } + + if (str.Length > decimalIndex + 6 && roundIndex <= decimalIndex) + { + // 반올림정수 + return ((int)Math.Round((double)num)).ToString(); + } + + if (str.Length < decimalIndex + 6) + { + return str; // 단순한 소수 + } + + if (roundIndex < str.Length - 6) // 3~4자리 이상 반복 + { + return string.Format("{0:f" + (roundIndex - decimalIndex) + "}", (double)num); + } + else + { + return str; // 복잡한 소수 + } + } + else + { + return str; // 정수 + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs.meta new file mode 100644 index 000000000..f20a6b3fa --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/JsonLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8dd9a7675f6c7a74b946b262207c0d18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs new file mode 100644 index 000000000..193beaee4 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs @@ -0,0 +1,31 @@ +using System; +using Superlazy; + +namespace SLShared.Entity.Loader +{ + public static class XMLLoader + { + internal static SLEntity LoadFile(string v) + { + //XmlDocument xmldoc = new XmlDocument(); + //xmldoc.LoadXml(xml.text); + //XmlDocument doc = new XmlDocument(); + //doc.Load(dataFolder + fileName); + //string type = doc.DocumentElement.GetAttribute("Name"); + + //if (xmls.ContainsKey(type) == false) + //{ + // xmls[type] = new List(); + //} + //var set = new XMLSet(); + //int dirIdx = doc.BaseURI.IndexOf(dataFolderName) + dataFolderName.Length; + //var file = doc.BaseURI.Substring(dirIdx, doc.BaseURI.Length - dirIdx - ".xml".Length); + + //set.doc = doc; + //set.filePath = file; + //xmls[type].Add(set); + + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs.meta new file mode 100644 index 000000000..35c2d7df3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/XMLLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 787bd37fed2046c4b8983331d51a8eeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs new file mode 100644 index 000000000..6af8c19b6 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Superlazy.Loader +{ + public static class YamlLoader + { + public static SLEntity LoadYaml(string yamlString) + { + var root = SLEntity.Empty; + LoadYaml(root, yamlString); + return root; + } + + public static void LoadYaml(SLEntity root, string yamlString) + { + var currentEntity = root; + var stack = new Stack>(); + + using var reader = new StringReader(yamlString); + string line; + while ((line = reader.ReadLine()) != null) + { + var lineBegin = 0; + while (lineBegin < line.Length && line[lineBegin] == ' ') lineBegin++; + + if (line.Length <= lineBegin) continue; + + var separatorIndex = line.IndexOf(':'); + string key; + var isArray = false; + if (separatorIndex == -1) + { + if (line[lineBegin] == '#') + { + continue; + } + else if (line[lineBegin] == '-') + { + isArray = true;// 배열 전용 처리 + key = null; + separatorIndex = lineBegin; + } + else + { + continue; + } + } + else + { + var commentIndex = line.IndexOf('#'); + if (commentIndex >= 0 && commentIndex <= separatorIndex) // 키에는 주석을 넣을수 없기 때문에 구분자 이전은 주석 + { + continue; // 전체 주석 + } + else + { + key = line.Substring(lineBegin, separatorIndex - lineBegin); + } + } + + var valueBegin = separatorIndex + 2; + var valueEnd = line.Length; + + if (valueBegin + 1 < valueEnd) + { + var commentIndex = line.IndexOf('#', valueBegin, valueEnd - valueBegin); + // TODO: 문자열 내의 #은 인식 못하도록 현재가 문자열 내부인지 체크가 필요 + if (commentIndex >= 0 && (commentIndex == 0 || line[commentIndex - 1] == ' ')) // 빈칸이 하나 있어야 주석임 + { + valueEnd = commentIndex - 1; + } + + while (valueEnd > valueBegin && line[valueBegin] == ' ') + { + valueBegin++; + } + + while (valueEnd >= valueBegin && line[valueEnd - 1] == ' ') + { + valueEnd--; + } + } + + while (stack.Count > 0 && lineBegin <= stack.Peek().Item2) + { + currentEntity.EndModified(); + currentEntity = stack.Pop().Item1; + } + + if (isArray) + { + key = (currentEntity.Count() + 1).ToString(); + } + + if (valueBegin >= valueEnd) + { + stack.Push(new Tuple(currentEntity, lineBegin)); + currentEntity = currentEntity[key]; + } + else + { + var forceString = false; + + if (line[valueBegin] == '"' && line[valueEnd - 1] == '"' || line[valueBegin] == '\'' && line[valueEnd - 1] == '\'') + { + valueBegin += 1; + valueEnd -= 1; + forceString = true; + } + + var value = line.Substring(valueBegin, valueEnd - valueBegin); + + if (forceString) + { + currentEntity[key] = value; + } + else if (int.TryParse(value, out var intValue)) + { + currentEntity[key] = intValue; + } + else if (double.TryParse(value, out var doubleValue)) + { + currentEntity[key] = doubleValue; + } + else + { + currentEntity[key] = value; + } + } + } + + while (stack.Count > 0) + { + currentEntity.EndModified(); + currentEntity = stack.Pop().Item1; + } + } + + public static string SaveToYaml(SLEntity entity, int indentation = 0, bool useArray = false) + { + var yamlString = new StringBuilder(); + + var index = 1; + + IEnumerable childs = entity; + if (useArray && entity.All(x => int.TryParse(x.ID, out _))) + { + childs = entity.OrderBy(x => int.Parse(x.ID)); + } + else // 전부 int로 파싱 못하면, 배열로 처리하지 않음 + { + useArray = false; + } + + foreach (var child in childs) + { + var key = child.ID; + var value = child; + + if (useArray && index > 0 && key == index.ToString()) + { + key = "-"; + ++index; + yamlString.Append(' ', indentation).Append(key); + } + else + { + index = -1; + yamlString.Append(' ', indentation).Append(key).Append(':'); + } + + if (value.IsValue) + { + var valueString = value.ToString(); + var needsQuotes = + value.IsNumeric == false && (int.TryParse(valueString, out _) || double.TryParse(valueString, out _)) // 숫자인데 강제로 문자화 한거거나 + || valueString.Contains('#') || valueString.StartsWith('[') || valueString.StartsWith('{') // 특수문자가 포함되어 있거나, 배열이나 객체로 시작하는 경우 {Value} + || valueString.Contains(':') // "남은 시간: {Value}" 꼴 + || (value.IsNumeric == false && valueString.StartsWith('-')); // "- 어쩌고" 꼴, 배열인것으로 이해 + + yamlString.Append(' '); + + if (needsQuotes) + { + yamlString.Append('"').Append(valueString.Replace("\"", "\\\"")).Append('"'); + } + else + { + yamlString.Append(valueString); + } + + yamlString.Append(Environment.NewLine); + } + else + { + yamlString.Append(Environment.NewLine).Append(SaveToYaml(value, indentation + 2, useArray)); + } + } + + return yamlString.ToString(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs.meta new file mode 100644 index 000000000..0c62e9b18 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/Loader/YamlLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 033bc7e19fe121846bba30e7d4c83c37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs new file mode 100644 index 000000000..75d27c379 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs @@ -0,0 +1,21 @@ +namespace Superlazy +{ + public abstract class SLDataLoader + { + public delegate void LoadEvent(string fileExt, string data); + + protected event LoadEvent OnLoad; + + public void Register(LoadEvent loadAction) + { + OnLoad += loadAction; + } + + public abstract void Load(); + + protected void Load(string fileExt, string data) + { + OnLoad?.Invoke(fileExt, data); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs.meta new file mode 100644 index 000000000..3fb997da7 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLDataLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d36c4befee0ebe540b42adb2d3a165df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs new file mode 100644 index 000000000..06598be5a --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Superlazy +{ + public class SLWindowsLoader : SLDataLoader + { + public string DataPath { get; set; } = "../Data/"; + + public override void Load() + { + if (Directory.Exists(DataPath) == false) + { + SLLog.Warn($"Cant Find Data Path {Directory.GetCurrentDirectory()}{DataPath}"); + return; + } + + //{ + // string[] files = Directory.GetFiles(dataPath, "*.xml", SearchOption.AllDirectories); + // foreach (string fileName in files) + // { + // var fileRoot = Loader.XMLLoader.LoadFile(fileName); + // MergeToRoot(fileName.Replace(dataPath, string.Empty), root, fileRoot); + // } + //} + + { + var files = Directory.GetFiles(DataPath, "*.json", SearchOption.AllDirectories); + foreach (var fileName in files) + { + using var file = File.OpenText(fileName); + try + { + var json = file.ReadToEnd(); + Load("json", json); + } + catch (Exception e) + { + SLLog.Error($"Cannot Parse {fileName}. \n{e.Message}"); + } + } + + files = Directory.GetFiles(DataPath, "*.yaml", SearchOption.AllDirectories); + + var generatedEvents = new List(); + + foreach (var fileName in files) + { + if (fileName.Contains("Generated")) // 생성된 파일들 + { + generatedEvents.Add(fileName); + continue; + } + using var file = File.OpenText(fileName); + try + { + var json = file.ReadToEnd(); + Load("yaml", json); + } + catch (Exception e) + { + SLLog.Error($"Cannot Parse {fileName}. \n{e.Message}"); + } + } + + foreach (var fileName in generatedEvents) + { + using var file = File.OpenText(fileName); + try + { + var json = file.ReadToEnd(); + Load("yaml", json); + } + catch (Exception e) + { + SLLog.Error($"Cannot Parse {fileName}. \n{e.Message}"); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs.meta new file mode 100644 index 000000000..5e959cd7d --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLEntitySpace/SLWindowsLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07385c4442bbc7944a0a44d2d249ce22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs b/Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs new file mode 100644 index 000000000..4929db614 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +namespace Superlazy +{ + public static class SLLinqUtil + { + public static TSource MinBy(this IEnumerable source, Func selector) + { + return source.MinBy(selector, null); + } + + public static TSource MinBy(this IEnumerable source, Func selector, IComparer comparer) + { + if (source == null) throw new ArgumentNullException("source"); + if (selector == null) throw new ArgumentNullException("selector"); + if (comparer == null) comparer = Comparer.Default; + + using var sourceIterator = source.GetEnumerator(); + if (!sourceIterator.MoveNext()) + { + throw new InvalidOperationException("Sequence contains no elements"); + } + + var min = sourceIterator.Current; + var minKey = selector(min); + while (sourceIterator.MoveNext()) + { + var candidate = sourceIterator.Current; + var candidateProjected = selector(candidate); + if (comparer.Compare(candidateProjected, minKey) < 0) + { + min = candidate; + minKey = candidateProjected; + } + } + + return min; + } + + public static TSource MaxBy(this IEnumerable source, Func selector) + { + return source.MaxBy(selector, null); + } + + public static TSource MaxBy(this IEnumerable source, Func selector, IComparer comparer) + { + if (source == null) throw new ArgumentNullException("source"); + if (selector == null) throw new ArgumentNullException("selector"); + if (comparer == null) comparer = Comparer.Default; + + using var sourceIterator = source.GetEnumerator(); + if (!sourceIterator.MoveNext()) + { + throw new InvalidOperationException("Sequence contains no elements"); + } + + var max = sourceIterator.Current; + var maxKey = selector(max); + while (sourceIterator.MoveNext()) + { + var candidate = sourceIterator.Current; + var candidateProjected = selector(candidate); + if (comparer.Compare(candidateProjected, maxKey) > 0) + { + max = candidate; + maxKey = candidateProjected; + } + } + + return max; + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs.meta new file mode 100644 index 000000000..8626b5313 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLLinqUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57b616d93fdce604b9a2ec4e0624e0c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLRandom.cs b/Assets/_Datas/SLShared/SLSystem/SLRandom.cs new file mode 100644 index 000000000..cd77bad7d --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLRandom.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Superlazy +{ + public class SLRandom + { + private readonly SLEntity root; + private readonly string key; + + public SLRandom(SLEntity root, string key) + { + this.root = root; + this.key = key; + if (this.root[key] == false) + { + this.root[key] = new Random().Next(); + } + } + + public int Next(int min, int max) + { + int seed = root[key]; + root[key] = new Random(seed).Next(); + + return new Random(seed).Next(min, max); + } + + public double NextDouble() + { + int seed = root[key]; + root[key] = new Random(seed).Next(); + + return new Random(seed).NextDouble(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLRandom.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLRandom.cs.meta new file mode 100644 index 000000000..32661c447 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLRandom.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 182fb08274d05ac488182eaea27ed2fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef b/Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef new file mode 100644 index 000000000..09cee023f --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef @@ -0,0 +1,16 @@ +{ + "name": "SLSystem", + "rootNamespace": "", + "references": [ + "GUID:f0e91edf4495c2045b34f001325cddb3" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef.meta b/Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef.meta new file mode 100644 index 000000000..01fbf3ec6 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystem.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 873a4b43aaa8c8440bf2b49356e666ff +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystem.cs b/Assets/_Datas/SLShared/SLSystem/SLSystem.cs new file mode 100644 index 000000000..e9f06d0f3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystem.cs @@ -0,0 +1,41 @@ +using System; +using Superlazy.Loader; + +namespace Superlazy +{ + public class SLSystem + { + public void Init(SLDataLoader loader, DateTime now) + { + Load(loader); + } + + protected virtual void Load(SLDataLoader loader) + { + Data = SLEntity.Empty; + loader.Register(LoadJson); + SLLog.Info("Data Loaded"); + } + + protected virtual void LoadJson(string fileExt, string json) + { + if (fileExt == "json") + { + JsonLoader.LoadJson(Data, json); + } + else if (fileExt == "yaml") + { + YamlLoader.LoadYaml(Data, json); + } + } + + public static SLEntity Data { get; private set; } + public static DateTime Now => DateTime.UtcNow; + + //public static DateTime Now + //{ + // get => SLDateTime.Now; + // private set => SLDateTime.Now = value; + //} + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystem.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLSystem.cs.meta new file mode 100644 index 000000000..26e0dc788 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f601fedd9e1c0548826ac041c8f37ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystem.csproj.meta b/Assets/_Datas/SLShared/SLSystem/SLSystem.csproj.meta new file mode 100644 index 000000000..09c09cd80 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystem.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b2f8a18eb8f672b4fae8e545a0e287af +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs b/Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs new file mode 100644 index 000000000..22bc96423 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Superlazy +{ + public static class SLSystemUtility + { + public static void CreateTypeList(this List ret) where T : class + { + ForeachTypeof(typeof(T), (a, t) => + { + ret.Add(t); + }); + } + + public static void CreateInstanceList(this List ret) where T : class + { + ForeachTypeof(typeof(T), (a, t) => + { + var obj = a.CreateInstance(t.FullName); + ret.Add(obj as T); + }); + } + + public static void CreateInstanceList(this List ret, string[] typeNames) where T : class + { + ForeachTypeof(typeof(T), (a, t) => + { + if (typeNames.FirstOrDefault(n => n == t.FullName) == null) return; + + var obj = a.CreateInstance(t.FullName); + ret.Add(obj as T); + }); + } + + public static void CreateInstanceDictionary(this Dictionary ret) where T : class + { + ForeachTypeof(typeof(T), (a, t) => + { + var obj = a.CreateInstance(t.FullName); + ret.Add(t.Name, obj as T); + }); + } + + public static void CreateInstanceDictionary(this Dictionary ret, string[] typeNames) where T : class + { + ForeachTypeof(typeof(T), (a, t) => + { + if (typeNames.FirstOrDefault(n => n == t.FullName) == null) return; + + var obj = a.CreateInstance(t.FullName); + ret.Add(t.Name, obj as T); + }); + } + + public static void CreateMethodInfoDictionary(this Dictionary> ret, bool useReturn, params Type[] argTypes) where T : class + { + ForeachTypeof(typeof(T), (a, t) => + { + var methodArr = t.GetMethods(); + var methodDic = new Dictionary(); + for (var i = 0; i < methodArr.Length; i++) + { + var method = methodArr[i]; + var parameters = method.GetParameters(); + if (parameters.CompareLengthAndTypes(argTypes.Skip(useReturn ? 1 : 0).ToArray()) && (useReturn ? method.ReturnType == argTypes[0] : method.ReturnType == typeof(void))) + methodDic.Add(method.Name, method); + } + ret.Add(t.Name, methodDic); + }); + } + + public static void CreateCommandInfoToBaseType(this Dictionary ret, T instance, params Type[] argTypes) where T : class + { + var methodArr = instance.GetType().GetMethods(); + for (var i = 0; i < methodArr.Length; i++) + { + var method = methodArr[i]; + var parameters = method.GetParameters(); + if (parameters.CompareLengthAndTypes(typeof(SLEntity))) + { + ret.Add(string.Format("{0}/{1}", instance.GetType().Name, method.Name), (instance, method)); + } + } + } + + public static List GetMethods() where T : class + { + var methods = new List(); + var rootType = typeof(T); + + ForeachTypeof(typeof(T), (a, t) => + { + foreach (var m in t.GetMethods()) + { + methods.Add(m); + } + }); + + return methods; + } + + private static bool IsInherited(this Type t, Type inherited) + { + if (t.BaseType == null || t.BaseType == typeof(object)) + return false; + + if (t.BaseType == inherited) + return true; + + return t.BaseType.IsInherited(inherited); + } + + private static Type GetRootBaseType(Type t) + { + if (t.BaseType == null || t.BaseType == typeof(object)) + return t; + else + return GetRootBaseType(t.BaseType); + } + + public static bool CompareLengthAndTypes(this ParameterInfo[] parameters, params Type[] types) + { + if (parameters.Length != types.Length) + return false; + + for (var i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType != types[i]) + { + return false; + } + } + + return true; + } + + public static T CreateInstance(string typeName) where T : class + { + var type = AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetTypes().FirstOrDefault(t => t.Name == typeName)) + .FirstOrDefault(t => t != null); + if (type == null) return null; + + return type.Assembly.CreateInstance(type.FullName) as T; + } + + private static void ForeachTypeof(Type rootType, Action action) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + if (a.FullName.IsLeft("Mono.")) continue; + if (a.FullName.IsLeft("SyntaxTree.")) continue; + if (a.FullName.IsLeft("nunit.")) continue; + if (a.FullName.IsLeft("Unity.")) continue; + if (a.FullName.IsLeft("ICSharpCode.")) continue; + if (a.FullName.IsLeft("Newtonsoft.")) continue; + if (a.FullName.IsLeft("Facebook")) continue; + if (a.FullName.IsLeft("winrt")) continue; + if (a.FullName.IsLeft("Tizen")) continue; + if (a.FullName.IsLeft("Apple")) continue; + if (a.FullName.IsLeft("Security")) continue; + if (a.FullName.IsLeft("Stores")) continue; + if (a.FullName.IsLeft("Purchasing.")) continue; + if (a.FullName.IsLeft("UnityStore")) continue; + if (a.FullName.IsLeft("Editor")) continue; + if (a.FullName.IsLeft("ChannelPurchase")) continue; + if (a.FullName.IsLeft("Google.")) continue; + if (a.FullName.IsLeft("UnityScript")) continue; + if (a.FullName.IsLeft("Boo.Lan")) continue; + if (a.FullName.IsLeft("System")) continue; + if (a.FullName.IsLeft("I18N")) continue; + if (a.FullName.IsLeft("UnityEngine")) continue; + if (a.FullName.IsLeft("UnityEditor")) continue; + if (a.FullName.IsLeft("mscorlib")) continue; + if (a.FullName.IsLeft("NiceIO")) continue; + if (a.FullName.IsLeft("PP")) continue; + if (a.FullName.IsLeft("PlayerBuild")) continue; + if (a.FullName.IsLeft("AndroidPlayerBuild")) continue; + if (a.FullName.IsLeft("Ex")) continue; + if (a.FullName.IsLeft("ScriptCompilation")) continue; + if (a.FullName.IsLeft("Anonymously")) continue; + + foreach (var t in a.GetTypes()) + { + if (t.IsAbstract) continue; + if (t.IsClass == false) continue; + if (rootType.IsAssignableFrom(t) == false) continue; + + action(a, t); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs.meta b/Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs.meta new file mode 100644 index 000000000..fdb1ce9cc --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/SLSystemUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47baa16888b22b848b00adbfe04cf1e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/UnitComponent.meta b/Assets/_Datas/SLShared/SLSystem/UnitComponent.meta new file mode 100644 index 000000000..ae0e8a4a3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/UnitComponent.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a0a9b72590e6ba4eb531b8cabebcf20 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLSystem/package.json b/Assets/_Datas/SLShared/SLSystem/package.json new file mode 100644 index 000000000..8bbb4c263 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.superlazy.slsystem", + "displayName": "SL System", + "description": "SL System", + "version": "1.0.0", + "unity": "2018.2", + "license": "MIT", + "dependencies": { + "com.superlazy.sllogger": "latest" + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLSystem/package.json.meta b/Assets/_Datas/SLShared/SLSystem/package.json.meta new file mode 100644 index 000000000..37adad196 --- /dev/null +++ b/Assets/_Datas/SLShared/SLSystem/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d32afcdffb5cbad43a530a5f7ce853bb +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity.meta b/Assets/_Datas/SLShared/SLUnity.meta new file mode 100644 index 000000000..5df04b1e3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 499cd1f0654e08446927bf1f137e8c19 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs b/Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs new file mode 100644 index 000000000..338992f3a --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs @@ -0,0 +1,20 @@ +using UnityEngine; +using UnityEngine.Rendering.Universal; + +[RequireComponent(typeof(Camera))] +public class CustomSceneCamera : MonoBehaviour +{ + private void OnEnable() + { + // add stack to camera + var cam = GetComponent(); + SLGame.Camera.GetUniversalAdditionalCameraData().cameraStack.Insert(0, cam); + } + + private void OnDisable() + { + // remove stack to camera + var cam = GetComponent(); + SLGame.Camera.GetUniversalAdditionalCameraData().cameraStack.Remove(cam); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs.meta b/Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs.meta new file mode 100644 index 000000000..75059b393 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/CustomSceneCamera.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 097f62a8d87588f498dcbf5c614e2cb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor.meta b/Assets/_Datas/SLShared/SLUnity/Editor.meta new file mode 100644 index 000000000..1bd8faf18 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b528e8b40736de4408eb6b9f053c6571 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs b/Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs new file mode 100644 index 000000000..a9351b440 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs @@ -0,0 +1,39 @@ +//using UnityEngine; +//using UnityEditor; +//using Unity.EditorCoroutines.Editor; + +//using System.Collections; +//using System.IO; +//using System.Text; + +//public class FixTexturePlatformSettings : AssetPostprocessor +//{ +// private void OnPostprocessTexture(Texture2D texture) +// { +// EditorCoroutineUtility.StartCoroutine(Fix($"{assetPath}.meta"), this); +// } + +// private IEnumerator Fix(string metafile) +// { +// // Wait for .meta to be created +// while (!File.ReadAllText(metafile).Contains("platformSettings:")) +// yield return null; + +// // Read .meta file +// var original = File.ReadAllText(metafile); +// var meta = new StringBuilder(original); + +// if (meta.ToString().Contains("iPhone")) +// { +// meta.Replace("iPhone", "iOS"); +// Debug.Log("Replaced iPhone to iOS"); +// } + +// // Save .meta file +// if (meta.ToString() != original) +// { +// File.WriteAllText(metafile, meta.ToString()); +// AssetDatabase.Refresh(); +// } +// } +//} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs.meta new file mode 100644 index 000000000..6337c5e0c --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/FixTexturePlatformSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1840d074593b59249bc7f1035d9c6259 \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs new file mode 100644 index 000000000..b9b005816 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs @@ -0,0 +1,124 @@ +using System.Diagnostics; +using UnityEditor; + +public class SLAssetPostprocessor : AssetPostprocessor +{ + private void OnPreprocessModel() + { + var importer = assetImporter as ModelImporter; + var upperPath = importer.assetPath.ToUpper(); + + if (upperPath.Contains("ASSETS/RAW/")) + { + } + + if (upperPath.Contains("ASSETS/RAW/UNITS/") && upperPath.Contains("_") == false && upperPath.Contains(".FBX")) + { + var fileName = SLFileUtility.FileName(upperPath); + var folderName = SLFileUtility.FolderName(upperPath); + if (fileName == folderName) + { + SLAssetPostprocessorModel.OnPreprocessModel(importer); + } + } + else if (upperPath.Contains("ASSETS/RAW/UNITS/") && upperPath.Contains("_") && upperPath.Contains(".FBX")) + { + SLAssetPostprocessorAnim.OnPreprocessModel(importer); + } + else if (upperPath.Contains("ASSETS/RAW/UNITS/") && upperPath.Contains("_PREFAB.PREFAB")) + { + SLAssetPostprocessorModel.OnPreprocessModel(importer); + } + else if (upperPath.Contains("ASSETS/RAW/EFFECTS/")) + { + SLAssetPostprocessorEffect.OnPreprocessModel(importer); + } + } + + private void OnPreprocessAnimation() + { + var importer = assetImporter as ModelImporter; + var upperPath = importer.assetPath.ToUpper(); + if (upperPath.Contains("ASSETS/RAW/UNITS/") && upperPath.Contains("_") && upperPath.Contains(".FBX")) + { + SLAssetPostprocessorAnim.OnPreprocessAnim(importer); + } + } + + private void OnPreprocessTexture() + { + var importer = assetImporter as TextureImporter; + + var upperPath = importer.assetPath.ToUpper(); + + if (upperPath.Contains("ASSETS/RAW/Units/")) + { + SLAssetPostprocessorModel.OnPreprocessTexture(importer); + } + + if (upperPath.Contains("ASSETS/RAW/SPRITES/")) + { + SLAssetPostprocessorSprite.OnPreprocessTexture(importer); + } + } + + public static void OnPostprocessAllAssets(string[] importedAssets, string[] deleteAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + foreach (var path in deleteAssets) + { + PostRem(path); + } + + var index = 0; + foreach (var path in movedFromAssetPaths) + { + PostRem(path, movedAssets[index]); + ++index; + } + + foreach (var path in movedAssets) + { + PostAdd(path); + } + + foreach (var path in importedAssets) + { + PostAdd(path); + } + + SLAssetPostprocessorModel.BuildTarget(); + SLAssetPostprocessorSprite.BuildTarget(); + } + + private static void PostRem(string path, string movePath = "") + { + try + { + SLAssetPostprocessorModel.OnRemove(path); + SLAssetPostprocessorAnim.OnRemove(path); + SLAssetPostprocessorEffect.OnRemove(path); + SLAssetPostprocessorSprite.OnRemove(path, movePath); + SLAssetPostprocessorScene.OnRemove(path); + } + catch (System.Exception e) + { + UnityEngine.Debug.LogError("Can't remove " + path + "\n" + e); + } + } + + private static void PostAdd(string path) + { + try + { + SLAssetPostprocessorModel.OnAdd(path); + SLAssetPostprocessorAnim.OnAdd(path); + SLAssetPostprocessorEffect.OnAdd(path); + SLAssetPostprocessorSprite.OnAdd(path); + SLAssetPostprocessorScene.OnAdd(path); + } + catch (System.Exception e) + { + UnityEngine.Debug.LogError("Can't import " + path + "\n" + e); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs.meta new file mode 100644 index 000000000..9e0294d39 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0e900ab5eb37a3469e1c230f61f28f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs new file mode 100644 index 000000000..125e30f74 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs @@ -0,0 +1,79 @@ +using Superlazy; +using UnityEditor; +using UnityEngine; + +public static class SLAssetPostprocessorAnim +{ + private static string GetDestPath(string path) + { + return SLFileUtility.FolderPath(SLFileUtility.FolderPath(path)).Replace("/Raw/", "/Addressables/") + "/" + SLFileUtility.FileName(path) + ".anim"; + } + + public static void OnPreprocessModel(ModelImporter importer) + { + importer.materialImportMode = ModelImporterMaterialImportMode.None; + importer.importAnimation = true; + importer.generateSecondaryUV = false; + } + + internal static void OnPreprocessAnim(ModelImporter importer) + { + var clips = new ModelImporterClipAnimation[importer.defaultClipAnimations.Length]; + var i = 0; + foreach (var clip in importer.defaultClipAnimations) + { + clips[i] = clip; + clips[i].name = SLFileUtility.FileName(importer.assetPath); + i += 1; + } + importer.clipAnimations = clips; + } + + public static void OnRemove(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && SLFileUtility.FileName(path).Contains("_") && upperPath.Contains(".FBX")) + { + var newPath = GetDestPath(path); + AssetDatabase.DeleteAsset(newPath); + } + + if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && SLFileUtility.FileName(path).Contains("_") && upperPath.Contains(".ANIM")) + { + var newPath = GetDestPath(path); + AssetDatabase.DeleteAsset(newPath); + } + } + + public static void OnAdd(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && SLFileUtility.FileName(path).Contains("_") && upperPath.Contains(".FBX")) + { + CreateAnimation(path, GetDestPath(path)); + } + + if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && SLFileUtility.FileName(path).Contains("_") && upperPath.Contains(".ANIM")) + { + AssetDatabase.CopyAsset(path, GetDestPath(path)); + } + } + + public static void CreateAnimation(string path, string destPath) + { + SLFileUtility.MakeFolderFromFilePath(destPath); + + var motion = AssetDatabase.LoadAssetAtPath(path); + + if (motion == null) + { + Debug.LogError("Cant load motion: " + path); + return; + } + + var newMotion = Object.Instantiate(motion); + AssetDatabase.CreateAsset(newMotion, destPath); + + Debug.Log("Animation Build : " + destPath, newMotion); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs.meta new file mode 100644 index 000000000..59bb630af --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorAnim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2467d52cdb48ded4fb37d3ace7c45b16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs new file mode 100644 index 000000000..6f728d00c --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs @@ -0,0 +1,79 @@ +using Superlazy; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.U2D; +using UnityEngine; +using Object = UnityEngine.Object; + +public interface IPostProcessorEffect +{ + void OnCreateEffectPrefab(GameObject root); +} + +public static class SLAssetPostprocessorEffect +{ + public static void OnPreprocessModel(ModelImporter importer) + { + importer.materialImportMode = ModelImporterMaterialImportMode.None; + } + + public static void CreateEffect(string path, string destPath) + { + var effectCreateComponents = new List(); + effectCreateComponents.CreateInstanceList(); + + var originalPrefab = AssetDatabase.LoadMainAssetAtPath(path) as GameObject; + var obj = Object.Instantiate(originalPrefab); + + if (obj == null) return; + + var root = new GameObject(); + root.SetActive(false); + obj.transform.localPosition = Vector3.zero; + obj.transform.localRotation = Quaternion.identity; + obj.transform.SetParent(root.transform); + obj.name = originalPrefab.name; + + var res = root.AddComponent(); + + foreach (var comp in effectCreateComponents) + { + comp.OnCreateEffectPrefab(root); + } + + res.Init(); + + SLFileUtility.MakeFolderFromFilePath(destPath); + var prefab = PrefabUtility.SaveAsPrefabAsset(root, destPath); + prefab.SetActive(true); + Object.DestroyImmediate(root); + + Debug.Log("Build : " + destPath, prefab); + } + + public static void OnRemove(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.IsLeft("ASSETS/RAW/EFFECTS") && upperPath.Contains(".PREFAB")) + { + var newPath = GetDestPath(path); + AssetDatabase.DeleteAsset(newPath); + } + } + + private static string GetDestPath(string path) + { + var name = SLFileUtility.FileName(path); + return SLFileUtility.FolderPath(path).Replace("/Raw/", "/Addressables/") + "/" + name + ".prefab"; + } + + public static void OnAdd(string path) + { + var upperPath = path.ToUpper(); + if (SLFileUtility.FolderPath(path).IsLeft("Assets/Raw/Effects") && upperPath.Contains(".PREFAB")) + { + var newPath = GetDestPath(path); + CreateEffect(path, newPath); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs.meta new file mode 100644 index 000000000..28f7b901d --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 124d528fadb06c1429ff11803a503baa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs new file mode 100644 index 000000000..0d67a3165 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using System.Linq; +using Superlazy; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; + +public interface IPostProcessorModel +{ + void OnCreatePrefab(string path, GameObject model); +} + +public static class SLAssetPostprocessorModel +{ + private static readonly HashSet targetPaths = new HashSet(); + + private static string GetDestPath(string path) + { + return SLFileUtility.FolderPath(path).Replace("/Raw/", "/Addressables/") + ".prefab"; + } + + private static string GetPrefabPath(string path) + { + var targetName = SLFileUtility.FolderName(path); + var targetPath = SLFileUtility.FolderPath(path); + + if (SLFileUtility.FolderName(path) == "Materials") + { + targetName = SLFileUtility.FolderName(targetPath); + targetPath = SLFileUtility.FolderPath(targetPath); + } + + return targetPath + "/" + targetName + ".prefab"; + } + + private static string GetTargetFBX(string path) + { + var targetName = SLFileUtility.FolderName(path); + var targetPath = SLFileUtility.FolderPath(path); + + if (SLFileUtility.FolderName(path) == "Materials") + { + targetName = SLFileUtility.FolderName(targetPath); + targetPath = SLFileUtility.FolderPath(targetPath); + } + + return targetPath + "/" + targetName + ".FBX"; + } + + public static void OnPreprocessModel(ModelImporter importer) + { + importer.materialImportMode = ModelImporterMaterialImportMode.None; + importer.generateSecondaryUV = false; + importer.importAnimation = false; + importer.animationType = ModelImporterAnimationType.Generic; + importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel; + } + + public static void OnRemove(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && (upperPath.Contains(".FBX") || upperPath.Contains(".MAT"))) + { + if (SLFileUtility.FolderName(path) != SLFileUtility.FileName(path)) return; + var newPath = GetDestPath(path); + AssetDatabase.DeleteAsset(newPath); + + var prefabPath = GetPrefabPath(path); + AssetDatabase.DeleteAsset(prefabPath); + + targetPaths.Add(GetTargetFBX(path)); // 삭제되어도 리빌드 + } + } + + public static void OnAdd(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && (upperPath.Contains(".FBX") || upperPath.Contains(".MAT"))) + { + if (SLFileUtility.FolderName(path) != SLFileUtility.FileName(path)) return; + targetPaths.Add(GetTargetFBX(path)); + } + else if (upperPath.IsLeft("ASSETS/RAW/UNITS/") && upperPath.Contains("_PREFAB.PREFAB")) + { + targetPaths.Add(GetPrefabPath(path).Replace(".prefab", "_Prefab.prefab")); + } + } + + public static void CreatePrefab(string path, string destPath) + { + try + { + var modelCreateComponents = new List(); + modelCreateComponents.CreateInstanceList(); + var go = AssetDatabase.LoadAssetAtPath(path); + + if (go == null) return; + + var prefab = new GameObject(go.name); + var model = Object.Instantiate(go); + model.name = "Model"; + model.transform.SetParent(prefab.transform); + model.AddComponent(); + + var guids = AssetDatabase.FindAssets("t:Material", new string[] { SLFileUtility.FolderPath(path) }); + var materials = guids.Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))).ToDictionary(m => m.name); + + foreach (var renderer in model.GetComponentsInChildren()) + { + var sharedMaterials = new Material[renderer.sharedMaterials.Length]; // Material을 직접 엘리먼트로 대입하면 안되고 배열을 통으로 넣어야함 + for (var i = 0; i < renderer.sharedMaterials.Length; ++i) + { + if (materials.TryGetValue($"{renderer.name}_{i + 1}", out var numMat)) + { + sharedMaterials[i] = numMat; + } + else if (materials.TryGetValue(renderer.name, out var mat)) + { + sharedMaterials[i] = mat; + } + else if (materials.TryGetValue(go.name, out var defaultMat)) + { + sharedMaterials[i] = defaultMat; + } + else + { + sharedMaterials[i] = renderer.sharedMaterials[i]; + // 에러인가? 잠깐 파일 안갖다놨을뿐인가? + } + } + renderer.sharedMaterials = sharedMaterials; + } + + var res = prefab.AddComponent(); + + foreach (var comp in modelCreateComponents) + { + comp.OnCreatePrefab(destPath, model); + } + + res.Init(); + + SLFileUtility.MakeFolderFromFilePath(destPath); + + { + // Make Duumy(for test) + var dummyObj = Object.Instantiate(model); + var anim = dummyObj.GetComponentInChildren(); + var controller = AnimatorController.CreateAnimatorControllerAtPath(GetPrefabPath(path).Replace(".prefab", ".controller")); + + anim.runtimeAnimatorController = controller; + var rootStateMachine = controller.layers[0].stateMachine; + + var clipids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { SLFileUtility.FolderPath(path) }); + foreach (var guid in clipids) + { + var clip = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); + if (clip != null) + { + var state = rootStateMachine.AddState(clip.name); + state.motion = clip; + } + } + + var dummy = PrefabUtility.SaveAsPrefabAssetAndConnect(dummyObj, GetPrefabPath(path), InteractionMode.AutomatedAction); + + Object.DestroyImmediate(dummyObj, false); + } + + AssetDatabase.DeleteAsset(destPath); + var newPreafb = PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, destPath, InteractionMode.AutomatedAction); + + Object.DestroyImmediate(prefab, false); + Debug.Log("Build : " + destPath, newPreafb); + } + catch (System.Exception e) + { + Debug.LogError("Cant build " + destPath + "\n" + e); + } + } + + public static void BuildTarget() + { + foreach (var path in targetPaths) + { + CreatePrefab(path, GetDestPath(path)); + } + + targetPaths.Clear(); + } + + internal static void OnPreprocessTexture(TextureImporter importer) + { + importer.sRGBTexture = true; + importer.mipmapEnabled = false; + importer.streamingMipmaps = false; + importer.wrapMode = TextureWrapMode.Clamp; + importer.filterMode = FilterMode.Bilinear; + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs.meta new file mode 100644 index 000000000..3f2a26b78 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68886d6dc8c664d4599d806c35f64197 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs new file mode 100644 index 000000000..4760cc9c4 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs @@ -0,0 +1,36 @@ +using UnityEditor; + +public static class SLAssetPostprocessorScene +{ + private static string GetDestPath(string path) + { + return path.Replace("/Raw/", "/Addressables/"); + } + + public static void OnRemove(string path) + { + var upperPath = path.ToUpper(); + + if (upperPath.Contains("ASSETS/RAW/SCENES/") == false || upperPath.Contains(".UNITY") == false) return; + + var destPath = GetDestPath(path); + AssetDatabase.DeleteAsset(destPath); + } + + public static void OnAdd(string path) + { + var upperPath = path.ToUpper(); + + if (upperPath.Contains("ASSETS/RAW/SCENES/") == false || upperPath.Contains(".UNITY") == false) return; + + var destPath = GetDestPath(path); + AssetDatabase.DeleteAsset(destPath); + + SLFileUtility.MakeFolderFromFilePath(destPath); + + AssetDatabase.CopyAsset(path, destPath); + //var scene = AssetDatabase.LoadAssetAtPath(destPath); + + // TODO: 추가 처리 + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs.meta new file mode 100644 index 000000000..71cc83382 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorScene.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a74e129d4cb7fb545a1bdf52599ce887 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs new file mode 100644 index 000000000..3535971c8 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs @@ -0,0 +1,180 @@ +using System.Collections.Generic; +using System.IO; +using Superlazy; +using UnityEditor; +using UnityEditor.U2D; +using UnityEngine; +using UnityEngine.U2D; + +public interface IPostProcessorSpriteAtlas +{ + void OnAddSprite(SpriteAtlasAsset atlas); +} + +public static class SLAssetPostprocessorSprite +{ + private static readonly HashSet targetPaths = new HashSet(); + + public static void OnPreprocessTexture(TextureImporter importer) + { + importer.textureType = TextureImporterType.Sprite; + importer.spriteImportMode = SpriteImportMode.Single; + + importer.sRGBTexture = true; + importer.isReadable = false; + importer.mipmapEnabled = false; + importer.streamingMipmaps = false; + importer.wrapMode = TextureWrapMode.Clamp; + importer.filterMode = FilterMode.Bilinear; + + importer.textureCompression = TextureImporterCompression.Uncompressed; + importer.crunchedCompression = false; + importer.spritePixelsPerUnit = 100; + + var textureSettings = new TextureImporterSettings(); + importer.ReadTextureSettings(textureSettings); + textureSettings.spriteMeshType = SpriteMeshType.FullRect; + textureSettings.spriteExtrude = 2; + importer.SetTextureSettings(textureSettings); + } + + public static void OnRemove(string path, string movePath) + { + var upperPath = path.ToUpper(); + if (upperPath.Contains("ASSETS/RAW/SPRITES/") == false || upperPath.Contains(".PNG") == false) return; + + if (targetPaths.Contains(SLFileUtility.FolderPath(path)) == false) + { + targetPaths.Add(SLFileUtility.FolderPath(path)); + } + } + + public static void OnAdd(string path) + { + var upperPath = path.ToUpper(); + if (upperPath.Contains("ASSETS/RAW/SPRITES/") == false || upperPath.Contains(".PNG") == false) return; + + if (targetPaths.Contains(SLFileUtility.FolderPath(path)) == false) + { + targetPaths.Add(SLFileUtility.FolderPath(path)); + } + } + + public static void CreateAtlas(string path, string destPath) + { + var oldAtlas = AssetDatabase.LoadAssetAtPath(destPath); + + if (oldAtlas != null) + { + AssetDatabase.DeleteAsset(destPath); + } + + var di = new DirectoryInfo(path); + if (di.Exists == false) return; + + var objects = new List(); + foreach (var file in di.GetFiles()) + { + if (file.Name.ToUpper().IsRight(".PNG") == false) continue; + var filePath = path + "/" + file.Name; + var fileName = file.Name.Substring(0, file.Name.Length - ".png".Length); + + var sprite = AssetDatabase.LoadAssetAtPath(filePath); + var maxSize = sprite.rect.size.x > sprite.rect.size.y ? sprite.rect.size.x : sprite.rect.size.y; + if (maxSize > 1024) + { + CreateSingleAtlas(filePath, path.Replace("/Raw/", "/Addressables/") + $"_{fileName}.spriteatlasv2"); + continue; + } + + objects.Add(sprite); + } + + if (objects.Count == 0) return; + + SLFileUtility.MakeFolderFromFilePath(destPath); + var atlas = new SpriteAtlasAsset(); + + var spriteAtlasComponents = new List(); + spriteAtlasComponents.CreateInstanceList(); + foreach (var component in spriteAtlasComponents) + { + component.OnAddSprite(atlas); + } + + atlas.Add(objects.ToArray()); + + SpriteAtlasAsset.Save(atlas, destPath); + AssetDatabase.Refresh(); + + var sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(destPath); + sai.packingSettings = new SpriteAtlasPackingSettings + { + enableRotation = false, + enableTightPacking = false, + enableAlphaDilation = false, + padding = 4, + blockOffset = 0 + }; + + sai.textureSettings = new SpriteAtlasTextureSettings + { + filterMode = FilterMode.Bilinear, + sRGB = true, + generateMipMaps = false + }; + } + + public static void CreateSingleAtlas(string path, string destPath) + { + var oldAtlas = AssetDatabase.LoadAssetAtPath(destPath); + + if (oldAtlas != null) + { + AssetDatabase.DeleteAsset(destPath); + } + + SLFileUtility.MakeFolderFromFilePath(destPath); + var atlas = new SpriteAtlasAsset(); + + var sprite = AssetDatabase.LoadAssetAtPath(path); + atlas.Add(new Object[] { sprite }); + + var spriteAtlasComponents = new List(); + spriteAtlasComponents.CreateInstanceList(); + foreach (var component in spriteAtlasComponents) + { + component.OnAddSprite(atlas); + } + + SpriteAtlasAsset.Save(atlas, destPath); + AssetDatabase.Refresh(); + + var sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(destPath); + sai.packingSettings = new SpriteAtlasPackingSettings + { + enableRotation = false, + enableTightPacking = false, + enableAlphaDilation = false, + padding = 4, + blockOffset = 0 + }; + + sai.textureSettings = new SpriteAtlasTextureSettings + { + filterMode = FilterMode.Bilinear, + sRGB = true, + generateMipMaps = false + }; + } + + public static void BuildTarget() + { + foreach (var path in targetPaths) + { + CreateAtlas(path, path.Replace("/Raw/", "/Addressables/") + ".spriteatlasv2"); + } + + targetPaths.Clear(); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs.meta new file mode 100644 index 000000000..90294e410 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLAssetPostprocessorSprite.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 578d2e49b250ea0409205fc26da997bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild.meta new file mode 100644 index 000000000..257ff2cc1 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 61b06882e13c02a4dae1dfa10a72848a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs new file mode 100644 index 000000000..da5acc685 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using Superlazy; +using Superlazy.Loader; +using UnityEditor; +using UnityEditor.AddressableAssets; +using UnityEditor.AddressableAssets.Build; +using UnityEditor.AddressableAssets.Settings; +using UnityEditor.Callbacks; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +#if UNITY_IOS +using UnityEditor.iOS.Xcode; +#endif + +public class SLAppBuild +{ + public static string dataAssetPath = "Addressables/Data"; + private static readonly SLEntity options = SLEntity.Empty; + + private enum SLAppBuildTarget + { + NONE = -1, + iOS = 0, + Android = 4, + Android_x86 = 5, + iOS_Cheat = 8, + Android_Cheat = 9 + } + + private static void ParseCommandLine() + { + var args = Environment.GetCommandLineArgs(); + + for (var i = 0; i < args.Length; ++i) + { + if (args[i].IsLeft("-")) + { + if (args[i + 1].Contains(",")) + { + var splits = args[i + 1].Split(','); + + for (var j = 0; j < splits.Count(); ++j) + { + options[args[i].Replace("-", "")][j.ToString()] = splits[j]; + } + + ++i; + } + else + { + options[args[i].Replace("-", "")] = args[i + 1]; + ++i; + } + } + } + } + + public static void BuildAndroidAPK() + { + PreBuild(); + + PlayerSettings.Android.bundleVersionCode = options["BuildSettings"]["BuildVersion"]; + + PlayerSettings.Android.keystorePass = options["BuildSettings"]["Android"]["KeystorePass"]; + PlayerSettings.Android.keyaliasName = options["BuildSettings"]["Android"]["KeyaliasName"]; + PlayerSettings.Android.keyaliasPass = options["BuildSettings"]["Android"]["KeyaliasPass"]; + + EditorUserBuildSettings.buildAppBundle = false; + var outputName = Application.productName + ".apk"; + + GenericBuild(outputName, BuildTargetGroup.Android, BuildTarget.Android, BuildOptions.None); + } + + public static void BuildiOSIPA() + { + PreBuild(); + + var targetDir = "./build"; + + PlayerSettings.iOS.buildNumber = options["BuildSettings"]["BuildVersion"]; + + var opt = BuildOptions.None; + + PlayerSettings.statusBarHidden = true; + + if (Directory.Exists(targetDir)) + { + Directory.Delete(targetDir, true); + } + + if (Directory.Exists(targetDir) == false) + { + Directory.CreateDirectory(targetDir); + } + + GenericBuild(targetDir, BuildTargetGroup.iOS, BuildTarget.iOS, opt); + } + + public static void PreBuild() + { + LoadBuildSettings(); + ParseCommandLine(); + + UpdateDataScript(); + UpdateAddressable(); + + UpdateLoaderID(); + } + + private static void GenericBuild(string target_path, BuildTargetGroup buildTargetGroup, BuildTarget build_target, BuildOptions build_options) + { + var levels = GetLevelsFromBuildSettings(); + EditorUserBuildSettings.SwitchActiveBuildTarget(buildTargetGroup, build_target); + + // TODO: NamedBuildTarget으로 개선 + if (options["Def"]) + { + // 기존 심볼들 + var existingDefs = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup) + .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(d => d.Trim()) + .ToHashSet(); + + // 새로 추가할 심볼들 + var newDefs = options["Def"].ToString() + .Split(";", StringSplitOptions.RemoveEmptyEntries) + .Select(d => d.Trim()); + + // 병합 후 중복 제거 + foreach (var def in newDefs) + existingDefs.Add(def); + + // 최종 적용 + var mergedDefs = string.Join(";", existingDefs); + PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, mergedDefs); + } + + var res = BuildPipeline.BuildPlayer(levels, target_path, build_target, build_options); + + if (res.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded) + { + throw new Exception("BuildPlayer failure: " + res.summary.ToString()); + } + } + + private static string[] GetLevelsFromBuildSettings() + { + var levels = new List(); + + foreach (var scene in EditorBuildSettings.scenes) + { + levels.Add(scene.path); + } + + return levels.ToArray(); + } + + public static void LoadBuildSettings() + { + var path = Path.Combine(Application.dataPath, "SLBuild", "BuildSettings.json"); + + options["BuildSettings"] = JsonLoader.LoadJson(File.ReadAllText(path))["BuildSettings"]; + } + + public static void UpdateDataScript() + { + FileUtil.DeleteFileOrDirectory("Assets/Addressables/Data/"); + + var destDir = Path.Combine(Application.dataPath, dataAssetPath); + + Directory.CreateDirectory(destDir); + + var loader = SLSystemUtility.CreateInstance("SLWindowsLoader"); + var system = SLSystemUtility.CreateInstance("SLSystem"); + system.Init(loader, DateTime.UtcNow); + loader.Load(); + + foreach (var session in SLSystem.Data) + { + var fileName = session.ID + ".json"; + var paths = Path.Combine(destDir, fileName).Split(Path.DirectorySeparatorChar); + var path = ""; + + for (var i = 0; i < paths.Length - 1; ++i) + { + path += paths[i] + Path.DirectorySeparatorChar; + if (Directory.Exists(path) == false) + { + Directory.CreateDirectory(path); + } + } + + var saveData = SLEntity.Empty; + saveData[session.ID] = session; + var text = JsonLoader.SaveToJson(saveData); + SLFileUtility.MakeFolderFromFilePath(Path.Combine(destDir, fileName)); + File.WriteAllText(Path.Combine(destDir, fileName), text, Encoding.UTF8); + } + + AssetDatabase.Refresh(); + } + + private static void UpdateAddressable() + { + AssetDatabase.ImportAsset("Assets/Addressables", ImportAssetOptions.ImportRecursive); + AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); + + var settings + = AddressableAssetSettingsDefaultObject.Settings; + + settings.activeProfileId + = settings.profileSettings.GetProfileId("Default"); + var builder + = AssetDatabase.LoadAssetAtPath("Assets/AddressableAssetsData/DataBuilders/BuildScriptPackedMode.asset") as IDataBuilder; + + settings.ActivePlayerDataBuilderIndex + = settings.DataBuilders.IndexOf((ScriptableObject)builder); + + AddressableAssetSettings.BuildPlayerContent(out var result); + + if (!string.IsNullOrEmpty(result.Error)) + throw new Exception(result.Error); + AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); + } + + public static void UpdateLoaderID() + { + EditorSceneManager.OpenScene("Assets/Scenes/Game.unity"); + var slunity = UnityEngine.Object.FindFirstObjectByType(); + if (slunity != null) + { + slunity.loaderID = "SLUnityDataLoader"; + } + + EditorSceneManager.SaveScene(SceneManager.GetActiveScene()); + } + + [PostProcessBuild(999)] + public static void OnPostProcessBuild(BuildTarget buildTarget, string path) + { + if (buildTarget == BuildTarget.StandaloneWindows64) + { + var folderPath = SLFileUtility.FolderPath(path); + + var backupFolder = folderPath + "/" + Application.productName + "_BackUpThisFolder_ButDontShipItWithYourGame"; + if (Directory.Exists(backupFolder)) + { + Directory.Delete(backupFolder, true); + } + + var burstDebugFolder = folderPath + "/" + Application.productName + "_BurstDebugInformation_DoNotShip"; + if (Directory.Exists(burstDebugFolder)) + { + Directory.Delete(burstDebugFolder, true); + } + + var fileName = Application.productName; + if (options["FileName"]) + { + fileName = options["FileName"]; + } + + if (File.Exists(folderPath + $"/../{fileName}.zip")) + { + File.Delete(folderPath + $"/../{fileName}.zip"); + } + +; + ZipFile.CreateFromDirectory(folderPath, $"./{fileName}.zip"); + } + + if (buildTarget == BuildTarget.iOS) + { +#if UNITY_IOS + string projectPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj"; + + UnityEditor.iOS.Xcode.PBXProject pbxProject = new UnityEditor.iOS.Xcode.PBXProject(); + pbxProject.ReadFromFile(projectPath); + + string target = pbxProject.GetUnityMainTargetGuid(); + pbxProject.SetBuildProperty(target, "ENABLE_BITCODE", "NO"); + pbxProject.SetBuildProperty(target, "DEBUG_INFORMATION_FORMAT", "dwarf"); + + if (options["iOSProvisionID"]) + { + pbxProject.SetBuildProperty(target, "PROVISIONING_PROFILE", options["iOSProvisionID"]); + } + + if (options["iOSProfileSpecifier"]) + { + pbxProject.SetBuildProperty(target, "PROVISIONING_PROFILE_SPECIFIER", options["iOSProfileSpecifier"]); + } + + if (options["iOSTeamID"]) + { + pbxProject.SetBuildProperty(target, "DEVELOPMENT_TEAM", options["iOSTeamID"]); + } + + if (options["iOSDistribution"]) + { + pbxProject.SetBuildProperty(target, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "iPhone Distribution"); + } + + pbxProject.WriteToFile(projectPath); + + { + string plistPath = path + "/Info.plist"; + PlistDocument plist = new PlistDocument(); + plist.ReadFromString(File.ReadAllText(plistPath)); + + PlistElementDict rootDict = plist.root; + rootDict.SetBoolean("ITSAppUsesNonExemptEncryption", false); + + File.WriteAllText(plistPath, plist.WriteToString()); + } +#endif + } + } + + public static void BuildWindows() + { + PreBuild(); + + var targetDir = "./build"; + + if (Directory.Exists(targetDir)) + { + Directory.Delete(targetDir, true); + } + + if (Directory.Exists(targetDir) == false) + { + Directory.CreateDirectory(targetDir); + } + + GenericBuild(targetDir + "/" + Application.productName + ".exe", BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64, BuildOptions.None); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs.meta new file mode 100644 index 000000000..b6b53d46f --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLAppBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76ab71d797cc1d548b395605811ee2f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs new file mode 100644 index 000000000..3e38ed752 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs @@ -0,0 +1,34 @@ +using UnityEditor; +using UnityEditor.AddressableAssets.Settings; + +namespace Assets.Scripts.Editor +{ + [InitializeOnLoad] + public class SLBuildMenu + { + [MenuItem("SLBuild/Build Assets")] + public static void BuildAndroidAssets() + { + AssetDatabase.ImportAsset("Assets/Addressables", ImportAssetOptions.ImportRecursive); + AddressableAssetSettings.BuildPlayerContent(); + } + + [MenuItem("SLBuild/Build APK")] + public static void BuildAndroidAPK() + { + SLAppBuild.BuildAndroidAPK(); + } + + [MenuItem("SLBuild/Build iOS IPA")] + public static void BuildiOSIPA() + { + SLAppBuild.BuildiOSIPA(); + } + + [MenuItem("SLBuild/Build Windows")] + public static void BuildWindows() + { + SLAppBuild.BuildWindows(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs.meta new file mode 100644 index 000000000..7eb09c557 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLBuild/SLBuildMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70051d36fdf85b64d9059a5ca0423c78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs new file mode 100644 index 000000000..e3699a084 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; + +public static class SLFileUtility +{ + public static bool DirectoryCopy(string sourceDirName, string destDirName, bool overwrite, bool copySubDirs, bool displayProgress = false, string pattern = "*.*") + { + var sourceDir = new DirectoryInfo(sourceDirName); + + if (!sourceDir.Exists) + { + return false; + //throw new DirectoryNotFoundException( + // "Source directory does not exist or could not be found: " + // + sourceDirName); + } + + var destDir = new DirectoryInfo(destDirName); + if (!destDir.Exists) + { + destDir = Directory.CreateDirectory(destDirName); + AssetDatabase.Refresh(); + } + + if (copySubDirs) + { + var dirs = sourceDir.GetDirectories(); + for (var i = 0; i < dirs.Length; i++) + { + var subdir = dirs[i]; + + var temppath = Path.Combine(destDirName, subdir.Name); + if (DirectoryCopy(subdir.FullName, temppath, overwrite, copySubDirs, displayProgress, pattern) == false) + return false; + } + } + + var files = sourceDir.GetFiles(pattern); + for (var i = 0; i < files.Length; i++) + { + var file = files[i]; + + if (displayProgress) + { + if (EditorUtility.DisplayCancelableProgressBar( + sourceDir.Name + " > " + destDir.Name, + file.Name, + i / (float)files.Length)) + { + EditorUtility.ClearProgressBar(); + return false; + } + } + + var temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, overwrite); + } + EditorUtility.ClearProgressBar(); + + AssetDatabase.Refresh(); + return true; + } + + public static void SearchDirectory(string sourceDirName, bool doRecursive, ref List pathList) + { + var dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException( + "Source directory does not exist or could not be found: " + + sourceDirName); + } + + var dirs = dir.GetDirectories(); + + var files = dir.GetFiles(); + foreach (var file in files) + { + pathList.Add(file.FullName); + } + + if (doRecursive) + { + foreach (var subdir in dirs) + { + pathList.Add(subdir.FullName); + SearchDirectory(subdir.FullName, doRecursive, ref pathList); + } + } + } + + public static string FixPath(string path) + { + path = path.Replace('\\', '/'); + path = path.Replace("//", "/"); + while (path.Length > 0 && path[0] == '/') + { + path = path.Remove(0, 1); + } + return path; + } + + public static string FileName(string path) + { + return Path.GetFileNameWithoutExtension(path); + } + + public static string FolderPath(string path) + { + return FixPath(Path.GetDirectoryName(path)); + } + + public static string FolderName(string path) + { + var dirPath = FolderPath(path); + return FixPath(dirPath.Substring(dirPath.LastIndexOf('/') + 1)); + } + + public static string CombinePaths(params string[] paths) + { + var path = ""; + for (var i = 0; i < paths.Length; i++) + { + path = Path.Combine(path, FixPath(paths[i])); + } + return FixPath(path); + } + + public static string RelativePath(string path) + { + path = FixPath(path); + if (path.StartsWith("Assets")) + { + return path; + } + if (path.StartsWith(FixPath(Application.dataPath))) + { + return "Assets" + path.Substring(FixPath(Application.dataPath).Length); + } + else + { + return ""; + } + } + + public static void MakeFolderFromFilePath(string filePath) + { + var paths = FixPath(filePath).Split('/'); + var path = ""; + + for (var i = 0; i < paths.Length - 1; ++i) + { + path += paths[i] + "/"; + if (Directory.Exists(path) == false) + { + Directory.CreateDirectory(path); + AssetDatabase.Refresh(); + } + } + } + + public static DateTime GetLastWriteTime(string filePath) + { + var fileWriteTime = File.GetLastWriteTime(filePath); + var metaWriteTime = File.GetLastWriteTime(filePath + ".meta"); + + var result = DateTime.Compare(fileWriteTime, metaWriteTime); + if (result < 0) + { + // file이 meta보다 빠름. + return metaWriteTime; + } + else + { + return fileWriteTime; + } + } + + public static void AttrToNormal(string targetDir) + { + File.SetAttributes(targetDir, FileAttributes.Normal); + + var files = Directory.GetFiles(targetDir); + var dirs = Directory.GetDirectories(targetDir); + + foreach (var file in files) + { + try + { + File.SetAttributes(file, FileAttributes.Normal); + } + catch + { + } + } + + foreach (var dir in dirs) + { + AttrToNormal(dir); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs.meta new file mode 100644 index 000000000..b52a4ba1c --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLFileUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b67bd58dfce671f41947e39a6f5f70bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs new file mode 100644 index 000000000..f42f8e748 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Superlazy +{ + [CustomEditor(typeof(SLResourceObject))] + public class SLResourceObjectEditor : Editor + { + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + var resourceObject = (SLResourceObject)target; + if (GUILayout.Button("Build Data")) + { + resourceObject.Init(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs.meta new file mode 100644 index 000000000..8d0bd61be --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLResourceObjectEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 326bfe8b78262674cab28d020b02f541 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI.meta new file mode 100644 index 000000000..f50dd77b1 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ab118eb6615efc418722a3a38ae362c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs new file mode 100644 index 000000000..cc1d20d1b --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Linq; +using Superlazy; +using Superlazy.UI; +using UnityEditor; +using UnityEngine; + +public static class SLBindingEntityViewUtil +{ + private static SLUIComponent currentComponent; + private static readonly Dictionary sessionView = new Dictionary(); + + public static void ViewEntity(this SLUIComponent component) + { + if (Application.isPlaying == false) return; + if (component.isActiveAndEnabled == false) return; + + var parent = component.bindParent; + if (component is SLUIObjectOnOff) + { + parent = component as SLUIObjectOnOff; + } + + if (parent == null) return; + + var on = EditorGUILayout.Foldout(component == currentComponent, parent.BindPath); + if (on) + { + if (currentComponent != component) sessionView.Clear(); + currentComponent = component; + SLBindingEntityView.UpdateView(SLGame.Session.Get(parent.BindPath), "", 1, sessionView); + } + } +} + +public class SLBindingEntityView +{ + public static void UpdateView(SLEntity e, string prefix, int depth, Dictionary view) + { + var changed = SLEntity.Empty; + ViewTree(e, prefix, depth, view, changed); + ChangeView(e, changed); + } + + private static void ViewTree(SLEntity e, string prefix, int depth, Dictionary view, SLEntity changed) + { + foreach (var tree in e.OrderBy(child => child.IsValue ? "B" + child.ID : "A" + child.ID)) + { + var id = prefix + tree.ID; + if (view.ContainsKey(id) == false) + { + view[id] = false; + } + + if (tree.IsValue == false) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("", GUILayout.Width(10 * depth)); + view[id] = EditorGUILayout.Foldout(view[id], tree.ToString()); + EditorGUILayout.EndHorizontal(); + + if (view[id]) + { + ViewTree(tree, id + ".", depth + 1, view, changed[tree.ID]); + } + } + else + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("", GUILayout.Width(10 * depth)); + string changeValue; + if (tree.ToString() == "True") + { + changeValue = EditorGUILayout.Toggle(tree.ID, e[tree.ID]).ToString(); + } + else if (tree.IsNumeric) + { + changeValue = EditorGUILayout.DoubleField(tree.ID, e[tree.ID]).ToString(); + } + else + { + changeValue = EditorGUILayout.TextField(tree.ID, e[tree.ID]); + } + + if (changeValue != e[tree.ID]) + { + changed[tree.ID] = changeValue; + } + + EditorGUILayout.EndHorizontal(); + } + } + } + + private static void ChangeView(SLEntity e, SLEntity changed) + { + if (changed) + { + foreach (var entity in changed) + { + if (entity.IsValue == false) + { + ChangeView(e[entity.ID], entity); + } + else + { + if (e[entity.ID].IsNumeric) + { + e[entity.ID] = double.Parse(entity); + } + else if (entity == "False") + { + e[entity.ID] = false; + } + else + { + e[entity.ID] = entity; + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs.meta new file mode 100644 index 000000000..4dd8a2bdf --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLBindingEntityView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a38ef110358063428131cbc04847832 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs new file mode 100644 index 000000000..1cc60fcc8 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs @@ -0,0 +1,79 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.InputSystem.OnScreen; +using UnityEngine.Serialization; + +namespace Superlazy.UI +{ + public class SLScreenStick : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler + { + public void OnPointerDown(PointerEventData eventData) + { + if (eventData == null) + throw new System.ArgumentNullException(nameof(eventData)); + + RectTransformUtility.ScreenPointToLocalPointInRectangle(transform.parent.GetComponentInParent(), eventData.position, eventData.pressEventCamera, out pointerDownPos); + padImagePos = rangeImage.position; + rangeImage.position = eventData.position; + } + + public void OnDrag(PointerEventData eventData) + { + if (eventData == null) + throw new System.ArgumentNullException(nameof(eventData)); + + RectTransformUtility.ScreenPointToLocalPointInRectangle(transform.parent.GetComponentInParent(), eventData.position, eventData.pressEventCamera, out var position); + var delta = position - pointerDownPos; + + delta = Vector2.ClampMagnitude(delta, MovementRange); + padImage.anchoredPosition = startPos + (Vector3)delta; + + var newPos = new Vector2(delta.x / MovementRange, delta.y / MovementRange); + SendValueToControl(newPos); + } + + public void OnPointerUp(PointerEventData eventData) + { + SendValueToControl(Vector2.zero); + padImage.anchoredPosition = startPos; + rangeImage.position = padImagePos; + } + + private void Start() + { + startPos = padImage.anchoredPosition; + } + + public float MovementRange + { + get => movementRange; + set => movementRange = value; + } + + [FormerlySerializedAs("movementRange")] + [SerializeField] + private float movementRange = 50; + + [FormerlySerializedAs("PadImage")] + [SerializeField] + private readonly RectTransform padImage; + + [FormerlySerializedAs("RangeImage")] + [SerializeField] + private readonly Transform rangeImage; + + [FormerlySerializedAs("ControlPath")] + [SerializeField] + private new string controlPath; + + private Vector3 startPos; + private Vector3 padImagePos; + private Vector2 pointerDownPos; + + protected override string controlPathInternal + { + get => controlPath; + set => controlPath = value; + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs.meta new file mode 100644 index 000000000..dcd46c184 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLScreenStick.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c80ee5f89e1f6543a183985abbeaae2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs new file mode 100644 index 000000000..aa13fdc60 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIButton))] + public class SLUIButtonEditor : Editor + { + private SLUIButton component; + private string[] methods; + + private static string filter; + + private void OnEnable() + { + component = target as SLUIButton; + methods = SLUIEditorUtil.GetAllCommands(); + } + + public override void OnInspectorGUI() + { + if (component.button != null) + { + UnityEngine.Events.UnityAction p = component.ButtonAction; + UnityEditor.Events.UnityEventTools.RemovePersistentListener(component.button.onClick, p); + } + + if (component.bindParent == null) + { + EditorGUILayout.LabelField("Can't find session Entity"); + } + else + { + EditorGUILayout.LabelField("SessionEntity : " + component.bindParent.BindPath); + } + + filter = EditorGUILayout.TextField("Binding Command Filter", filter); + + var filterMethods = methods; + if (string.IsNullOrEmpty(filter) == false) + { + filterMethods = methods.Where(name => name.ToLower().Contains(filter.ToLower())).ToArray(); + } + + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.LabelField("Binding Command : " + component.command); + var currMethod = Array.FindIndex(filterMethods, methodName => methodName == component.command); + currMethod = currMethod < 0 ? 0 : currMethod; // None 표기용 + var newMethod = EditorGUILayout.Popup(currMethod, filterMethods); + if (newMethod > 0) + { + component.command = filterMethods[newMethod]; + } + else + { + component.command = ""; + } + } + + EditorGUILayout.EndHorizontal(); + + if (component.comparison == null) component.comparison = new SLValueComparison(); + if (component.bindParent) component.comparison.OnInspector(component.bindParent); + + if (component.comparison.useCheckValue) + { + component.useGrayScale = EditorGUILayout.Toggle("Use GrayScale Disable", component.useGrayScale); + } + + component.focus = EditorGUILayout.Toggle("focus", component.focus); + + if (string.IsNullOrEmpty(component.focusBind) == false) EditorGUILayout.LabelField($"focus bind{component.bindParent.BindPath}.{component.focusBind}"); + component.focusBind = EditorGUILayout.TextField("focus bind", component.focusBind); + + component.selectIsHover = EditorGUILayout.Toggle("Select is hover", component.selectIsHover); + + component.fastClick = EditorGUILayout.Toggle("fast click", component.fastClick); + + component.clickSound = EditorGUILayout.TextField("click sound", component.clickSound); + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs.meta new file mode 100644 index 000000000..c62b369a6 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIButtonEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7327a9c6eb336704b91d3d3f301298ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs new file mode 100644 index 000000000..875acb721 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs @@ -0,0 +1,22 @@ +using UnityEditor; + +namespace Superlazy.UI +{ + [CustomEditor(typeof(SLUIComponent), true)] + public class SLUIComponentEditor : Editor + { + private SLUIComponent component; + + private void OnEnable() + { + component = target as SLUIComponent; + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + component.ViewEntity(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs.meta new file mode 100644 index 000000000..cc3743ac8 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIComponentEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b1dca5f686102b459e9fb70a35baf51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs new file mode 100644 index 000000000..c819ffc1e --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIDragButton))] + public class SLUIDragButtonEditor : Editor + { + private SLUIDragButton component; + private string[] methods; + + private static string filter; + + private void OnEnable() + { + component = target as SLUIDragButton; + methods = SLUIEditorUtil.GetAllCommands(); + } + + public override void OnInspectorGUI() + { + if (component.button != null) + { + UnityEngine.Events.UnityAction p = component.ButtonAction; + UnityEditor.Events.UnityEventTools.RemovePersistentListener(component.button.onClick, p); + } + + if (component.bindParent == null) + { + EditorGUILayout.LabelField("Can't find session Entity"); + } + else + { + EditorGUILayout.LabelField("SessionEntity : " + component.bindParent.BindPath); + } + + filter = EditorGUILayout.TextField("Binding Command Filter", filter); + + var filterMethods = methods; + if (string.IsNullOrEmpty(filter) == false) + { + filterMethods = methods.Where(name => name.ToLower().Contains(filter.ToLower())).ToArray(); + } + + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.LabelField("Binding Command : " + component.command); + var currMethod = Array.FindIndex(filterMethods, methodName => methodName == component.command); + currMethod = currMethod < 0 ? 0 : currMethod; // None 표기용 + var newMethod = EditorGUILayout.Popup(currMethod, filterMethods); + if (newMethod > 0) + { + component.command = filterMethods[newMethod]; + } + else + { + component.command = ""; + } + } + + EditorGUILayout.EndHorizontal(); + + if (component.comparison == null) component.comparison = new SLValueComparison(); + if (component.bindParent) component.comparison.OnInspector(component.bindParent); + + if (component.comparison.useCheckValue) + { + component.useGrayScale = EditorGUILayout.Toggle("Use GrayScale Disable", component.useGrayScale); + } + + component.focus = EditorGUILayout.Toggle("focus", component.focus); + + if (string.IsNullOrEmpty(component.focusBind) == false) EditorGUILayout.LabelField($"focus bind{component.bindParent.BindPath}.{component.focusBind}"); + component.focusBind = EditorGUILayout.TextField("focus bind", component.focusBind); + + component.fastClick = EditorGUILayout.Toggle("fast click", component.fastClick); + + component.clickSound = EditorGUILayout.TextField("click sound", component.clickSound); + + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.LabelField("Cancel Command : " + component.cancelCommand); + var currMethod = Array.FindIndex(filterMethods, methodName => methodName == component.cancelCommand); + currMethod = currMethod < 0 ? 0 : currMethod; // None 표기용 + var newMethod = EditorGUILayout.Popup(currMethod, filterMethods); + if (newMethod > 0) + { + component.cancelCommand = filterMethods[newMethod]; + } + else + { + component.cancelCommand = ""; + } + } + + EditorGUILayout.EndHorizontal(); + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs.meta new file mode 100644 index 000000000..6dfc7fe9a --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIDragButtonEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f85962de31620974db815db221005a57 \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs new file mode 100644 index 000000000..1b60d47d7 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs @@ -0,0 +1,124 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIEntity))] + public class SLUIEntityEditor : Editor + { + private SLUIEntity component; + private static string filter; + private static string[] methods; + + private void OnEnable() + { + component = target as SLUIEntity; + + methods = SLUIEditorUtil.GetAllCommands(); + } + + public override void OnInspectorGUI() + { + BindCheck(); + + component.useOnOff = EditorGUILayout.Toggle("Use On Off", component.useOnOff); + + if (component.useOnOff) + { + if (component.comparison == null) component.comparison = new SLValueComparison(); + component.comparison.OnInspector(component); + + filter = EditorGUILayout.TextField("Binding Command Filter", filter); + + var filterMethods = methods; + if (string.IsNullOrEmpty(filter) == false) + { + filterMethods = methods.Where(name => name.ToLower().Contains(filter.ToLower())).ToArray(); + } + + { + var currMethod = Array.FindIndex(filterMethods, 0, methodName => methodName == component.onCommand); + + var newMethod = EditorGUILayout.Popup("Turn On : " + component.onCommand, currMethod, filterMethods); + if (newMethod > 0) + { + component.onCommand = filterMethods[newMethod]; + } + else + { + component.onCommand = ""; + } + } + { + var currMethod = Array.FindIndex(filterMethods, 0, methodName => methodName == component.offCommand); + + var newMethod = EditorGUILayout.Popup("Turn Off : " + component.offCommand, currMethod, filterMethods); + if (newMethod > 0) + { + component.offCommand = filterMethods[newMethod]; + } + else + { + component.offCommand = ""; + } + } + } + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + + public void BindCheck() + { + EditorGUILayout.LabelField("Bind :" + component.BindPath); + + var parent = component.transform.parent?.GetComponentInParent(); + component.inherit = EditorGUILayout.Toggle("Use Inherit : " + parent?.BindPath, component.inherit); + if (parent != null) + { + component.bindParent = parent; + } + else + { + component.bindParent = null; + } + + if (component.inherit) + { + EditorGUILayout.ObjectField("Inherit", component.bindParent, typeof(SLUIObjectOnOff), false); + } + + if (component.bindParent == null) // no inherit + { + component.bind = EditorGUILayout.TextField("Bind", component.bind); + } + else if (component.bindParent is SLUIList) // list inherit + { + if (Application.isPlaying == false) + { + if (component.inherit == false || component.bind != "[~]" || component.useOnOff) + { + EditorUtility.SetDirty(component); + } + + component.inherit = true; + component.bind = "[~]"; + component.useOnOff = false; + } + } + else // entity inherit + { + component.bind = EditorGUILayout.TextField("Bind", component.bind); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs.meta new file mode 100644 index 000000000..9d586ea4a --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIEntityEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 71366620685ac88418d07ba51c8c7bcd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs new file mode 100644 index 000000000..354df74a2 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs @@ -0,0 +1,32 @@ +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIFade))] + public class SLUIFadeEditor : Editor + { + private SLUIFade component; + + private void OnEnable() + { + component = target as SLUIFade; + } + + public override void OnInspectorGUI() + { + component.fadeInTime = EditorGUILayout.FloatField("Fade In(On) Time", component.fadeInTime); + component.fadeOutTime = EditorGUILayout.FloatField("Fade Out(Off) Time", component.fadeOutTime); + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs.meta new file mode 100644 index 000000000..920f35412 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIFadeEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab626f98682b21c4aab1a2e9f73ac359 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs new file mode 100644 index 000000000..9f8f1a639 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIInputField))] + public class SLUIInputFieldEditor : Editor + { + private SLUIInputField component; + private string[] methods; + private static string filter; + + private void OnEnable() + { + component = target as SLUIInputField; + methods = SLUIEditorUtil.GetAllCommands(); + } + + public override void OnInspectorGUI() + { + if (component.bindParent == null) + { + EditorGUILayout.LabelField("Can't find session Entity"); + } + else + { + component.bindingValue = EditorGUILayout.TextField("Binding Value", component.bindingValue); + component.isNumber = EditorGUILayout.Toggle("Is Number", component.isNumber); + + component.useChangeValue = EditorGUILayout.Toggle("Use ChangeValue", component.useChangeValue); + + if (component.useChangeValue) + { + filter = EditorGUILayout.TextField("Binding Command Filter", filter); + + var filterMethods = methods; + if (string.IsNullOrEmpty(filter) == false) + { + filterMethods = methods.Where(name => name.ToLower().Contains(filter.ToLower())).ToArray(); + } + + var currMethod = Array.FindIndex(filterMethods, 0, methodName => methodName == component.changeCommand); + + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.LabelField("Change Command : " + component.changeCommand); + var newMethod = EditorGUILayout.Popup(currMethod, filterMethods); + if (newMethod > 0) + { + component.changeCommand = filterMethods[newMethod]; + } + else + { + component.changeCommand = ""; + } + } + + EditorGUILayout.EndHorizontal(); + } + else + { + component.changeCommand = ""; + } + + if (component.comparison == null) component.comparison = new SLValueComparison(); + component.comparison.OnInspector(component.bindParent); + } + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs.meta new file mode 100644 index 000000000..e68abdb61 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIInputFieldEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: daf19569fb1ea37448d6cccdfa9fa3fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs new file mode 100644 index 000000000..df5c3edd7 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs @@ -0,0 +1,99 @@ +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIList))] + public class SLUIListEditor : Editor + { + private SLUIList component; + + private void OnEnable() + { + component = target as SLUIList; + } + + public override void OnInspectorGUI() + { + BindCheck(); + + component.listElement = component.GetComponentInChildren(true); + EditorGUILayout.ObjectField("Bind Session", component.listElement, typeof(SLUIEntity), true); + + component.useSort = EditorGUILayout.Toggle("Use Sort", component.useSort); + if (component.useSort) + { + var idx = component.descending ? 1 : 0; + idx = EditorGUILayout.Popup("Order", idx, new string[] { "A-Z", "Z-A" }); + component.descending = idx == 1; + component.sortKey = EditorGUILayout.TextField("Sort Key : ", component.sortKey); + } + + component.bindMaxCount = EditorGUILayout.TextField("Bind Max Bind", component.bindMaxCount); + component.bindMinCount = EditorGUILayout.TextField("Bind Min Bind", component.bindMinCount); + + component.addDelay = EditorGUILayout.FloatField("Add Delay(0 to stop)", component.addDelay); + + if (component.comparison == null) component.comparison = new SLValueComparison(); + if (component.listElement != null) + { + if (component.listElement.inherit == false || component.listElement.bind != "[~]" || component.listElement.useOnOff) + { + EditorUtility.SetDirty(component.listElement); + } + + component.listElement.useOnOff = false; + component.listElement.inherit = true; + + component.comparison.OnInspector(component.listElement); + } + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + + public void BindCheck() + { + EditorGUILayout.LabelField("Bind :" + component.BindPath); + + var parent = component.transform.parent?.GetComponentInParent(); + component.inherit = EditorGUILayout.Toggle("Use Inherit : " + parent?.BindPath, component.inherit); + if (parent != null) + { + component.bindParent = parent; + } + else + { + component.bindParent = null; + } + + if (component.inherit) + { + EditorGUILayout.ObjectField("Inherit", component.bindParent, typeof(SLUIObjectOnOff), false); + } + + if (component.bindParent == null) // no inherit + { + component.bind = EditorGUILayout.TextField("Bind", component.bind); + } + else if (component.bindParent is SLUIList) // list inherit + { + if (Application.isPlaying == false) + { + EditorGUILayout.LabelField("Cant bind list in list :" + component.BindPath); + } + } + else // entity inherit + { + component.bind = EditorGUILayout.TextField("Bind", component.bind); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs.meta new file mode 100644 index 000000000..2af5c7b62 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIListEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca482e59fda34cc4089895340c1d9d8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs new file mode 100644 index 000000000..00a9bd9a2 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs @@ -0,0 +1,46 @@ +using UnityEditor; +using UnityEngine; + +namespace Superlazy.UI +{ + [CustomEditor(typeof(SLUIResource))] + public class SLUIResourceEditor : Editor + { + private SLUIResource component; + + private GameObject obj; + private Editor preview; + + private void OnEnable() + { + component = target as SLUIResource; + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + var newObj = AssetDatabase.LoadAssetAtPath($"Assets/Addressables/{component.effectBind}.prefab"); + + if (obj != newObj) + { + obj = newObj; + if (preview != null) DestroyImmediate(preview); + } + + if (obj == null) + { + EditorGUILayout.HelpBox("Can't Find Prefab", MessageType.Error); + } + else + { + if (preview == null) + { + preview = Editor.CreateEditor(obj); + } + + preview.OnInteractivePreviewGUI(GUILayoutUtility.GetRect(200, 200), new GUIStyle()); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs.meta new file mode 100644 index 000000000..d86fef34e --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIResourceEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4bca9eb28ee7dc4409fc5c03bab792c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs new file mode 100644 index 000000000..57ed5f98e --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Superlazy.UI +{ + public static class SLUIEditorUtil + { + public static string[] GetAllCommands() + { + var methods = new Dictionary>(); + methods.CreateMethodInfoDictionary(false, typeof(SLEntity)); + + var components = new List(); + components.CreateInstanceList(); + + var commands = new Dictionary(); + commands.Add("None", (null, null)); + foreach (var component in components) + { + commands.CreateCommandInfoToBaseType(component, typeof(SLEntity)); + } + + return commands.Keys.ToArray(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs.meta new file mode 100644 index 000000000..cb3ad8897 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2672498a4a5f6f545b6c6e7a133846ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs new file mode 100644 index 000000000..a21ef68c0 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy.UI +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(SLUIValue))] + public class SLUIValueEditor : Editor + { + private SLUIValue component; + + private void OnEnable() + { + component = target as SLUIValue; + } + + public override void OnInspectorGUI() + { + if (component.bindParent == null) + { + EditorGUILayout.LabelField("Can't find session Entity"); + } + else + { + EditorGUILayout.LabelField("Bind : " + component.bindParent.BindPath); + } + + if (component.text != null) + { + component.useRaw = EditorGUILayout.Toggle("UseRaw", component.useRaw); + + component.bindingValue = EditorGUILayout.TextField("Binding[Text]", component.bindingValue); + if (string.IsNullOrEmpty(component.bindingValue) == false) + { + component.gameObject.name = component.bindingValue; + } + if (component.useRaw == false) + { + component.prefix = EditorGUILayout.TextField("Prefix", component.prefix); + component.postfix = EditorGUILayout.TextField("Postfix", component.postfix); + + component.userInput = EditorGUILayout.Toggle("User Input", component.userInput); + + component.tagID ??= string.Empty; + + var formats = new List(); + formats.CreateInstanceList(); + + var optionSTRs = new List(formats.Select(p => p.ToString() + "(" + p.Tag + ")")); + var currOption = formats.FindIndex(pair => pair.Tag == component.tagID); + + var newOption = EditorGUILayout.Popup("TagOption", currOption, optionSTRs.ToArray()); + component.tagID = formats.ElementAt(newOption).Tag; + } + + component.ignoreColorTag = EditorGUILayout.Toggle("Ignore Color Tag", component.ignoreColorTag); + + component.useUpdate = EditorGUILayout.Toggle("Use Update Value", component.useUpdate); + + if (component.useUpdate) + { + component.useTypingEffect = false; + component.typingEndBind = string.Empty; + component.typingEndState = string.Empty; + component.startDelay = 0.2f; + component.delay = 0.03f; + } + else + { + component.useTypingEffect = EditorGUILayout.Toggle("Use Typing Effect", component.useTypingEffect); + if (component.useTypingEffect) + { + component.startDelay = EditorGUILayout.FloatField("Typing Effect Delay", component.startDelay); + component.delay = EditorGUILayout.FloatField("Typing Effect Duration", component.delay); + component.typingEndBind = EditorGUILayout.TextField("Typing End Bind", component.typingEndBind); + + if (string.IsNullOrEmpty(component.typingEndState) == false) + { + EditorGUILayout.LabelField($"On Typing End : Session.UIState.{component.typingEndState} = true"); + } + component.typingEndState = EditorGUILayout.TextField("Typing Effect EndState", component.typingEndState); + } + } + + component.useSmoothing = EditorGUILayout.Toggle("Use Smoothing", component.useSmoothing); + if (component.useSmoothing) + { + component.smoothingDelay = EditorGUILayout.FloatField("Delay", component.smoothingDelay); + component.beforeSmoothingValue = EditorGUILayout.TextField("BeforeValue", component.beforeSmoothingValue); + } + } + else if (component.image != null) + { + component.bindingValue = EditorGUILayout.TextField("Binding[Image]", component.bindingValue); + + var formats = new List(); + formats.CreateInstanceList(); + + component.tagID ??= string.Empty; + + var optionSTRs = new List(formats.Select(p => p.ToString() + "(" + p.Tag + ")")); + var currOption = formats.FindIndex(pair => pair.Tag == component.tagID); + + var newOption = EditorGUILayout.Popup("TagOption", currOption, optionSTRs.ToArray()); + component.tagID = formats.ElementAt(newOption).Tag; + } + else if (component.slider != null) + { + component.bindingValue = EditorGUILayout.TextField("Bind Text[Current]", component.bindingValue); + component.bindingMaxValue = EditorGUILayout.TextField("Bind Text[Max]", component.bindingMaxValue); + + component.useSmoothing = EditorGUILayout.Toggle("Use Smoothing", component.useSmoothing); + if (component.useSmoothing) + { + component.smoothingDelay = EditorGUILayout.FloatField("Delay", component.smoothingDelay); + } + } + else + { + EditorGUILayout.LabelField("Can't find UI Component"); + } + + component.ViewEntity(); + + if (GUI.changed && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs.meta new file mode 100644 index 000000000..1dcc0841d --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLUIValueEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f5fa502db026bd42a242c010d106196 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs new file mode 100644 index 000000000..29320afdd --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs @@ -0,0 +1,54 @@ +using UnityEditor; + +namespace Superlazy.UI +{ + public static class SLValueComparisonInspector + { + public static void OnInspector(this SLValueComparison comp, SLUIObjectOnOff session, string inspectorName = "Check Value") + { + comp.checkValue = EditorGUILayout.TextField(inspectorName, comp.checkValue); + comp.useCheckValue = string.IsNullOrEmpty(comp.checkValue) == false; + if (comp.useCheckValue == false) + { + EditorGUILayout.LabelField("[" + session.BindPath + "] Check"); + } + else + { + var checkValuePath = + comp.checkValue.IsLeft(".") || + double.TryParse(comp.checkValue, out _) || + comp.checkValue == "True" || + comp.checkValue == "False" ? + comp.checkValue : session.BindPath.CombinePath(comp.checkValue); + + if (checkValuePath.IsLeft(".")) + { + checkValuePath = checkValuePath.Substring(1); + } + + comp.useCompValue = EditorGUILayout.Toggle("Use Comparison", comp.useCompValue); + if (comp.useCompValue) + { + EditorGUILayout.BeginHorizontal(); + { + var comps = new string[] { ">", "<", "==", "!=", ">=", "<=" }; + var type = (int)comp.compType; + var newComp = EditorGUILayout.Popup("[" + checkValuePath + "]", type, comps); + + if (newComp >= 0) + { + comp.compType = (SLValueComparison.CompType)newComp; + comp.compValue = EditorGUILayout.TextField(comp.compValue); + comp.useCheckValue = comp.checkValue != ""; + } + } + EditorGUILayout.EndHorizontal(); + } + else + { + EditorGUILayout.LabelField("[" + checkValuePath + "] Check"); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs.meta new file mode 100644 index 000000000..6ef196751 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUI/SLValueComparisonInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bde4ea02cb1a2cb448daedf6be82ec4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef new file mode 100644 index 000000000..f18b7ff39 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef @@ -0,0 +1,27 @@ +{ + "name": "SLUnityEditor", + "rootNamespace": "", + "references": [ + "GUID:775573db2ba530e429a6432260c2ed34", + "GUID:69448af7b92c7f342b298e06a37122aa", + "GUID:9e24947de15b9834991c9d8411ea37cf", + "GUID:f0e91edf4495c2045b34f001325cddb3", + "GUID:873a4b43aaa8c8440bf2b49356e666ff", + "GUID:75469ad4d38634e559750d17036d5f7c", + "GUID:343deaaf83e0cee4ca978e7df0b80d21", + "GUID:2bafac87e7f4b9b418d9448d219b01ab", + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:478a2357cc57436488a56e564b08d223" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef.meta new file mode 100644 index 000000000..1697f4bae --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 195e490de97086945912e32bd0fa79cc +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.csproj.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.csproj.meta new file mode 100644 index 000000000..b19c7b021 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityEditor.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7a6f2da32e6912b479ae93dbf3821eb2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs new file mode 100644 index 000000000..c04981870 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs @@ -0,0 +1,392 @@ +using System.Collections.Generic; +using System.Linq; +using Superlazy.Loader; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace Superlazy +{ + [CustomEditor(typeof(SLUnity))] + public class SLUnityInspector : Editor + { + private SLUnity component; + private bool sessionRoot; + private readonly Dictionary sessionView = new Dictionary(); + + private bool dataRoot; + private readonly Dictionary dataView = new Dictionary(); + + private string sessionSearch; + + [MenuItem("Window/SLUnity %#W")] + public static void ShowWindow() + { + Selection.activeObject = FindFirstObjectByType(); + } + + private void OnEnable() + { + component = target as SLUnity; + + //if (component.managers != null && component.managers.Length > 0 && component.managers[0] == "All") + //{ + // var behaviors = new List(); + // behaviors.CreateTypeList(); + // foreach (var type in behaviors) + // { + // useManagers.Add(type.FullName); + // } + //} + //else + //{ + // foreach (var managerName in component.managers) + // { + // useManagers.Add(managerName); + // } + //} + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + var requireSave = false; + + //component.Loader + + //SLUnityBoot.mobileData = EditorGUILayout.Toggle("Mobile Data", SLUnityBoot.mobileData); + //if (SLUnityBoot.mobileData == false) + //{ + // SLUnityBoot.dataPath = EditorGUILayout.TextField("Data Path", SLUnityBoot.dataPath); + //} + //SLUnityBoot.UpdateData(); + + EditorGUILayout.Separator(); + + //var behaviors = new List(); + //behaviors.CreateTypeList(); + //var subject = "SLManager :"; + //if (component.managers != null && component.managers.Length > 0 && component.managers[0] == "All") + //{ + // subject += " All"; + //} + //EditorGUILayout.LabelField(subject); + //var oldCount = useManagers.Count; + //foreach (var type in behaviors) + //{ + // bool old = useManagers.Contains(type.FullName); + // var use = EditorGUILayout.Toggle(type.FullName, old); + // if (use != old) + // { + // requireSave = true; + // if (use == false) + // { + // useManagers.Remove(type.FullName); + // } + // else + // { + // useManagers.Add(type.FullName); + // } + // } + //} + //if (useManagers.Count != oldCount) + //{ + // if (behaviors.Count == useManagers.Count) + // { + // component.managers = new string[] { "All" }; + // } + // else + // { + // component.managers = useManagers.ToArray(); + // } + //} + + EditorGUILayout.Separator(); + + ShowDataTree(); + + EditorGUILayout.Separator(); + + sessionSearch = EditorGUILayout.TextField("Search", sessionSearch); + if (sessionSearch == null || sessionSearch == string.Empty) + { + ShowSessionTree(); + } + else + { + ShowSessionSearch(sessionSearch); + } + + //var caches = SLGame.SessionCacheKeys; + //sessionCache = EditorGUILayout.Foldout(sessionCache, "SessionCache(" + caches.Count() + ")"); + //if (sessionCache) + //{ + // foreach (var key in caches) + // { + // EditorGUILayout.LabelField(key); + // } + //} + + ViewPlayerPref(); + + if (requireSave && Application.isPlaying == false) + { + EditorUtility.SetDirty(component); + EditorSceneManager.MarkAllScenesDirty(); + } + } + + private void ShowSessionTree() + { + sessionRoot = EditorGUILayout.Foldout(sessionRoot, "Session"); + if (sessionRoot && SLGame.Session) + { + var changed = SLEntity.Empty; + ViewTree(SLGame.Session, "", 1, sessionView, changed); + ChangeView(SLGame.Session, changed); + } + + Repaint(); + } + + private void ShowDataTree() + { + dataRoot = EditorGUILayout.Foldout(dataRoot, "Data"); + if (dataRoot && SLSystem.Data) + { + var changed = SLEntity.Empty; + ViewTree(SLSystem.Data, "", 1, dataView, changed); + ChangeView(SLSystem.Data, changed); + } + Repaint(); + } + + private void ViewTree(SLEntity e, string prefix, int depth, Dictionary view, SLEntity changed) + { + foreach (var tree in e.OrderBy(child => child.IsValue ? "B" + child.ID : "A" + child.ID)) + { + var id = prefix + tree.ID; + if (view.ContainsKey(id) == false) + { + view[id] = false; + } + if (tree.IsValue == false) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("", GUILayout.Width(10 * depth)); + view[id] = EditorGUILayout.Foldout(view[id], tree.ToString()); + EditorGUILayout.EndHorizontal(); + + if (view[id]) + { + ViewTree(tree, id + ".", depth + 1, view, changed[tree.ID]); + } + } + else + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("", GUILayout.Width(10 * depth)); + string changeValue; + if (tree.ToString() == "True") + { + changeValue = EditorGUILayout.Toggle(tree.ID, e[tree.ID]).ToString(); + } + else if (tree.IsNumeric) + { + changeValue = EditorGUILayout.DoubleField(tree.ID, e[tree.ID]).ToString(); + } + else + { + changeValue = EditorGUILayout.TextField(tree.ID, e[tree.ID]); + } + if (changeValue != e[tree.ID]) + { + changed[tree.ID] = changeValue; + } + EditorGUILayout.EndHorizontal(); + } + } + } + + private void ChangeView(SLEntity e, SLEntity changed) + { + if (changed) + { + foreach (var entity in changed) + { + if (entity.IsValue == false) + { + ChangeView(e[entity.ID], entity); + } + else + { + if (e[entity.ID].IsNumeric) + { + e[entity.ID] = double.Parse(entity); + } + else if (entity == "False") + { + e[entity.ID] = false; + } + else + { + e[entity.ID] = entity; + } + } + } + } + } + + private void ShowSessionSearch(string sessionSearch) + { + SessionSearch(SLGame.Session, sessionSearch, null); + } + + private void SessionSearch(SLEntity session, string sessionSearch, string path) + { + foreach (var child in session) + { + if (path != null) + { + var newPath = path + "." + child.ID; + if (child.IsValue) + { + if (newPath.Contains(sessionSearch)) + { + EditorGUILayout.LabelField(newPath + ": " + child); + } + } + else + { + SessionSearch(child, sessionSearch, newPath); + } + } + else + { + if (child.IsValue) + { + if (child.ID.Contains(sessionSearch)) + { + EditorGUILayout.LabelField(child.ID + ": " + child); + } + } + else + { + SessionSearch(child, sessionSearch, child.ID); + } + } + } + } + +#if UNITY_EDITOR_WIN + private bool viewPref; + private SLEntity pref; + private Dictionary> prefView = new Dictionary>(); + private Dictionary prefOn = new Dictionary(); +#endif + + private void ViewPlayerPref() + { +#if UNITY_EDITOR_WIN + if (Application.platform != RuntimePlatform.WindowsEditor) return; + viewPref = EditorGUILayout.Foldout(viewPref, "PlayerPref"); + + if (viewPref) + { + if (GUILayout.Button("Refresh")) + { + pref = null; + } + + if (pref == null) + { + MakePref(); + } + + foreach (var value in pref) + { + if (value.ID.IsLeft("unity") || value.ID.IsLeft("Unity")) continue; + + using (new EditorGUILayout.HorizontalScope()) + { + if (value.IsValue) + { + EditorGUILayout.LabelField(value.ID + " " + value); + } + else + { + if (prefOn.ContainsKey(value.ID) == false) prefOn[value.ID] = false; + prefOn[value.ID] = EditorGUILayout.Foldout(prefOn[value.ID], value.ID); + if (prefOn[value.ID]) + { + if (prefView.ContainsKey(value.ID) == false) prefView[value.ID] = new Dictionary(); + SLBindingEntityView.UpdateView(value, "", 1, prefView[value.ID]); + Repaint(); + } + } + + if (GUILayout.Button("Remove", GUILayout.Width(200))) + { + PlayerPrefs.DeleteKey(value.ID); + pref = null; + return; + } + } + } + } + } + + private void MakePref() + { + Microsoft.Win32.RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Unity\\UnityEditor\\" + PlayerSettings.companyName + "\\" + PlayerSettings.productName); + if (registryKey != null) + { + string[] valueNames = registryKey.GetValueNames(); + + pref = SLEntity.Empty; + + // Parse and convert the registry saved player prefs into our array + //int i = 0; + foreach (string valueName in valueNames) + { + string key = valueName; + + // Remove the _h193410979 style suffix used on player pref keys in Windows registry + int index = key.LastIndexOf("_"); + key = key.Remove(index, key.Length - index); + + // Get the value from the registry + object ambiguousValue = registryKey.GetValue(valueName); + + // Unfortunately floats will come back as an int (at least on 64 bit) because the float is stored as + // 64 bit but marked as 32 bit - which confuses the GetValue() method greatly! + if (ambiguousValue.GetType() == typeof(int)) + { + // If the player pref is not actually an int then it must be a float, this will evaluate to true + // (impossible for it to be 0 and -1 at the same time) + if (PlayerPrefs.GetInt(key, -1) == -1 && PlayerPrefs.GetInt(key, 0) == 0) + { + // Fetch the float value from PlayerPrefs in memory + pref[key] = PlayerPrefs.GetFloat(key); + } + else + { + pref[key] = (int)ambiguousValue; + } + } + else if (ambiguousValue.GetType() == typeof(byte[])) + { + // On Unity 5 a string may be stored as binary, so convert it back to a string + pref[key] = System.Text.Encoding.UTF8.GetString((byte[])ambiguousValue); + if (pref[key].IsLeft("{")) + { + pref[key] = JsonLoader.LoadJson(pref[key]); + } + } + } + } +#endif + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs.meta new file mode 100644 index 000000000..78f694a52 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3de72f68b2acfa045879979dcfcf7948 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs new file mode 100644 index 000000000..9c571e417 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs @@ -0,0 +1,127 @@ +using System.Diagnostics; +using System.Linq; +using Superlazy; +using UnityEngine; +using Debug = UnityEngine.Debug; +using Object = UnityEngine.Object; + +public class SLUnityLogger : ISLLogger +{ + private readonly string[] hideClasses = new string[] { "SLSystem", "SLGame", "SLEntity", "SLUnityLogger", "SLLog", "SLContainer", "SLValue" }; + public string[] hideKeys = new string[0]; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + private static void Load() + { + SLLog.Logger = new SLUnityLogger(); + } + + public void Info(string format, params object[] args) + { + var trace = new StackTrace(true); + var frames = trace.GetFrames(); + var frame = frames.FirstOrDefault(f => hideClasses.Any(hc => f.GetMethod().DeclaringType.Name == hc) == false); + var fullText = $"[{frame.GetMethod().DeclaringType.Name}.{frame.GetMethod().Name}:{frame.GetFileLineNumber()}]{string.Format(format, args)}"; + + if (hideKeys.Where(k => fullText.Contains(k)).FirstOrDefault() != null) return; + +#if UNITY_EDITOR + //UnityEditor.EditorUtility.DisplayDialog("Info", fullText, "확인", UnityEditor.DialogOptOutDecisionType.ForThisSession, "UnityLoggerDialog"); +#endif + + if (args.Length > 0 && args[0] is Object obj) + { + Debug.Log(fullText, obj); + } + else + { + Debug.Log(fullText); + } + } + + public void Warn(string format, params object[] args) + { + var trace = new StackTrace(true); + var frames = trace.GetFrames(); + var frame = frames.FirstOrDefault(f => hideClasses.Any(hc => f.GetMethod().DeclaringType.Name == hc) == false); + var fullText = $"[{frame.GetMethod().DeclaringType.Name}.{frame.GetMethod().Name}:{frame.GetFileLineNumber()}]{string.Format(format, args)}"; + +#if UNITY_EDITOR + UnityEditor.EditorUtility.DisplayDialog("Warn", fullText, "확인", UnityEditor.DialogOptOutDecisionType.ForThisSession, "UnityLoggerDialog"); +#endif + + if (args.Length > 0 && args[0] is Object obj) + { + Debug.LogWarning(fullText, obj); + } + else + { + Debug.LogWarning(fullText); + } + } + + public void Error(string format, params object[] args) + { + var trace = new StackTrace(true); + var frames = trace.GetFrames(); + var frame = frames.FirstOrDefault(f => hideClasses.Any(hc => f.GetMethod().DeclaringType.Name == hc) == false); + var fullText = $"[{frame.GetMethod().DeclaringType.Name}.{frame.GetMethod().Name}:{frame.GetFileLineNumber()}]{string.Format(format, args)}"; + +#if UNITY_EDITOR + UnityEditor.EditorUtility.DisplayDialog("Warn", fullText, "확인", UnityEditor.DialogOptOutDecisionType.ForThisSession, "UnityLoggerDialog"); +#endif + + if (args.Length > 0 && args[0] is Object obj) + { + Debug.LogError(fullText, obj); + } + else + { + Debug.LogError(fullText); + } + } + + //private void OnUnityLog(string condition, string stackTrace, UnityEngine.LogType errorType, bool isThread) + //{ + // string type = null; + // switch (errorType) + // { + // case LogType.Assert: + // type = "Debug"; + // break; + + // case LogType.Error: + // case LogType.Exception: + // type = "Error"; + // break; + + // case LogType.Log: + // type = "Log"; + // break; + + // case LogType.Warning: + // type = "Warn"; + // break; + // } + + // OnUnityLog(type, condition, stackTrace); + //} + + // public void OnUnityLog(string type, string condition, string stackTrace) + // { + // if (type == "Error" && SLSystem.GetConfig("BuildTag").ToString().Contains("SL")) + // { + // NMGameSaver.SetOption("Debug", true); + // } + + //#if !UNITY_EDITOR + // condition += "\n" + stackTrace; + //#endif + // if (filter[type]) + // { + // logs.Add(new KeyValuePair(type, condition)); + // logScroll.y = float.MaxValue; + // filter["Session"] = false; + // } + // } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs.meta new file mode 100644 index 000000000..f48209f6b --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/SLUnityLogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a7b2577124c0c244bb51fc6929ffba5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/Tools.meta b/Assets/_Datas/SLShared/SLUnity/Editor/Tools.meta new file mode 100644 index 000000000..8ba191c0b --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/Tools.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c1cf0db66f9b0424cb6d62dd7eed6667 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs b/Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs new file mode 100644 index 000000000..cc34ccf2c --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; + +namespace SLUnityEditor.Tools +{ + public class AnimatorUtil + { + [MenuItem("Assets/Nest AnimationClips in Controller", true)] + public static bool NestAnimClipsValidate() => Selection.activeObject != null && Selection.activeObject.GetType() == typeof(AnimatorController); + + [MenuItem("Assets/Nest AnimationClips in Controller")] + public static void NestAnimClips() + { + var anim_controller = (AnimatorController)Selection.activeObject; + if (anim_controller == null) return; + + // Get all objects currently in Controller asset, we'll destroy them later + var objects = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(anim_controller)); + + AssetDatabase.SaveAssets(); + + // Add animations from all animation layers, without duplicating them + var oldToNew = new Dictionary(); + foreach (var layer in anim_controller.layers) + { + foreach (var state in layer.stateMachine.states) + { + var old = state.state.motion as AnimationClip; + if (old == null) continue; + + if (!oldToNew.ContainsKey(old)) // New animation in list - create new instance + { + var newClip = UnityEngine.Object.Instantiate(old) as AnimationClip; + newClip.name = old.name; + AssetDatabase.AddObjectToAsset(newClip, anim_controller); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(newClip)); + oldToNew[old] = newClip; + Debug.Log("Nested animation clip: " + newClip.name); + } + + state.state.motion = oldToNew[old]; + } + } + + // Destroy all old AnimationClips in asset + for (var i = 0; i < objects.Length; i++) + { + if (objects[i].GetType() == typeof(AnimationClip)) + { + UnityEngine.Object.DestroyImmediate(objects[i], true); + } + } + AssetDatabase.SaveAssets(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs.meta b/Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs.meta new file mode 100644 index 000000000..c4f1553d1 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/Editor/Tools/AnimatorUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a132e0ae02aa9e40abd4e47931a26f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLContextController.cs b/Assets/_Datas/SLShared/SLUnity/SLContextController.cs new file mode 100644 index 000000000..8fa22b387 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLContextController.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace Superlazy +{ + public abstract class SLContextController : MonoBehaviour + { + public abstract void Init(); + + public abstract void Clear(); + + public abstract void SetContext(SLEntity context); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLContextController.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLContextController.cs.meta new file mode 100644 index 000000000..da65fb3b0 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLContextController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd46c498f52854e4689781b8e1bac13f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLGame.cs b/Assets/_Datas/SLShared/SLUnity/SLGame.cs new file mode 100644 index 000000000..b8f44a9d5 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLGame.cs @@ -0,0 +1,483 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Superlazy; +using UnityEngine; + +public class SLGame +{ + public static SLEntity Session { get; private set; } + + public static IEnumerable SessionCacheKeys => sessionCache.Keys; + + public static Camera Camera { get; private set; } + + private class SessionCache + { + public bool validState = false; + public string id = null; + public SessionCache parent = null; + public SLEntity sessionCache; + public List sessionHandlers; + public float duration; + public bool isChanged; + } + + private static bool currValidState = false; + private static readonly Dictionary sessionCache = new Dictionary(); + private static readonly List sessionCacheIndex = new List(); + + private static int handlerIndex = 0; + private static readonly Dictionary sessionHandlers = new Dictionary(); + private static readonly Dictionary sessionCounters = new Dictionary(); + private static readonly SortedList runHandlers = new SortedList(); + + private static Dictionary gameComponents; + private static List activeComponents; + private static List readyActiveComponents; + + private static Dictionary commands; + private static List<(string command, SLEntity entity)> commandQueue; + private static bool canCommand = false; // 커맨드 즉시 실행 or queue + private static bool updateSessionCache = false; // 세션 캐시 업데이트 이후에는 필요시 핸들러를 즉시 실행 + + public static SLEntity SessionGet(string path) + { + if (sessionCache.TryGetValue(path, out var cache)) + { + cache.duration = 2f; + var parent = cache.parent; + while (parent != null) + { + parent.duration = 2f; + parent = parent.parent; + } + + if (cache.validState == currValidState) + { + return cache.sessionCache; + } + else + { + SLLog.Error($"{path} is expired."); + return Session.Get(path); + } + } + else + { + string parentPath = null; + var subLength = path.LastIndexOf('.'); + if (subLength > 0) + { + parentPath = path.Substring(0, subLength); + SessionGet(parentPath); + } + + var ret = Session.Get(path); + sessionCache[path] = new SessionCache + { + id = path.Substring(subLength + 1, path.Length - subLength - 1), + parent = parentPath != null ? sessionCache[parentPath] : sessionCache[""], + duration = 2f, + validState = currValidState, + sessionCache = ret + }; + sessionCacheIndex.Add(path); + return ret; + } + } + + private readonly List removeKeyIndex = new List(); + + private void SessionCacheUpdate() + { + currValidState = !currValidState; + var cacheCount = sessionCacheIndex.Count; + for (var i = 0; i < cacheCount; i++) + { + var c = sessionCacheIndex[i]; + var cache = sessionCache[c]; + var newValue = cache.sessionCache; // 현재값 + + var update = false; + if (cache.parent != null) + { + if (cache.parent.sessionCache.HasChild(cache.id)) + { + newValue = cache.parent.sessionCache[cache.id]; + } + else if (cache.sessionCache) // 자식이 없고 캐시는 있는경우 삭제를 위해 // 아이디가 null이라면 삭제된거라 다시 댕글 건다 // 근데 아이디는 상관없음 + { + newValue = false; + } + + update |= cache.parent.isChanged; + update |= cache.parent.sessionCache.IsModified(cache.id); + } + else + { + if (string.IsNullOrEmpty(c)) + { + newValue = Session; + } + else + { + if (Session.HasChild(c)) + { + newValue = Session[c]; + } + else if (cache.sessionCache) // 자식이 없고 캐시는 있는경우 삭제를 위해 + { + newValue = false; + } + } + } + + cache.validState = currValidState; + // 상위가 변경되거나/업데이트틱이 지났다면 캐시 변경 + // 삭제/변경되었다면 캐시가 바뀜 + // 추가/변경 되었다면 뉴가 바뀜 + + update |= SLEntity.Changed(cache.sessionCache, newValue); + + if (update) + { + cache.isChanged = true; + + cache.sessionCache = newValue; + + if (cache.sessionHandlers != null) + { + foreach (var handler in cache.sessionHandlers) + { + var handle = sessionHandlers[handler]; + if (runHandlers.ContainsKey(handle) == false) runHandlers[handle] = handler; + } + } + } + else if (cache.sessionCache.IsModified(null)) // 추가 삭제인경우 아래로 전파하진 않는다 + { + cache.sessionCache = newValue; + if (cache.sessionHandlers != null) + { + foreach (var handler in cache.sessionHandlers) + { + var handle = sessionHandlers[handler]; + if (runHandlers.ContainsKey(handle) == false) runHandlers[handle] = handler; + } + } + } + + if (cache.sessionHandlers == null || cache.sessionHandlers.Count == 0) + { + if (cache.duration < 0) + { + removeKeyIndex.Add(i); + } + else + { + cache.duration -= Time.unscaledDeltaTime; + } + } + else + { + // 부모의 유지시간 갱신 + var parent = cache.parent; + while (parent != null) + { + parent.duration = parent.duration > 2f ? parent.duration : 2f; + parent = parent.parent; + } + } + } + + for (var i = 0; i < cacheCount; i++) + { + var c = sessionCacheIndex[i]; + var cache = sessionCache[c]; + cache.sessionCache.EndModified(); + cache.isChanged = false; + } + + for (var i = removeKeyIndex.Count - 1; i >= 0; i--) + { + var key = sessionCacheIndex[removeKeyIndex[i]]; + var duration = sessionCache[key].duration; + if (duration <= 0) + { + sessionCache.Remove(key); + sessionCacheIndex.RemoveAt(removeKeyIndex[i]); + } + } + + removeKeyIndex.Clear(); + + while (runHandlers.Count != 0) + { + var handler = runHandlers.Values[0]; + runHandlers.RemoveAt(0); + handler.Invoke(); + } + } + + public static void AddNotify(string sessionPath, Action onChange) + { + SessionGet(sessionPath); + + if (sessionCache[sessionPath].sessionHandlers == null) + { + sessionCache[sessionPath].sessionHandlers = new List(); + } + + sessionCache[sessionPath].sessionHandlers.Add(onChange); + + if (sessionHandlers.ContainsKey(onChange) == false) + { + sessionHandlers[onChange] = handlerIndex; + handlerIndex += 1; + sessionCounters[onChange] = 0; + } + + sessionCounters[onChange] += 1; + + var handlerId = sessionHandlers[onChange]; + + if (runHandlers.ContainsKey(handlerId) == false) + { + runHandlers[handlerId] = onChange; + } + + if (updateSessionCache == false) // 코루틴 등에서 첫프레임 실행 보장 + { + while (runHandlers.Count != 0) + { + var handler = runHandlers.Values[0]; + runHandlers.RemoveAt(0); + handler.Invoke(); + } + } + } + + public static void RemoveNotify(string sessionPath, Action onChange) + { + sessionCounters[onChange] -= 1; + if (sessionCounters[onChange] == 0) + { + sessionHandlers.Remove(onChange); + sessionCounters.Remove(onChange); + } + + if (sessionCache.TryGetValue(sessionPath, out var cache)) + { + cache.sessionHandlers.Remove(onChange); + } + else + { + SLLog.Error($"Cannot RemoveNotify : {sessionPath}", onChange.Target); + } + } + + public SLGame(string[] managers, string main) + { + var sessionRoot = SLEntity.Empty; + sessionRoot["Session"]["Data"] = SLSystem.Data; + Session = sessionRoot["Session"]; + + Camera = Camera.main; + + sessionCache[""] = new SessionCache + { + id = "", + parent = null, + duration = float.PositiveInfinity, + validState = currValidState, + sessionCache = Session + }; + sessionCacheIndex.Add(""); + + gameComponents = new Dictionary(); + commandQueue = new List<(string command, SLEntity entity)>(); + activeComponents = new List(); + readyActiveComponents = new List(); + gameComponents.CreateInstanceDictionary(managers); + commands = new Dictionary(); + foreach (var component in gameComponents) + { + commands.CreateCommandInfoToBaseType(component.Value, typeof(SLEntity)); + } + + if (main == string.Empty) + { + main = managers[0]; + } + + ActiveComponent(main, true); + } + + internal void Update() + { + Session["Time"] += 1; + + foreach (var component in readyActiveComponents) + { + activeComponents.Add(component); + } + + readyActiveComponents.Clear(); + + canCommand = true; + updateSessionCache = true; + + foreach (var command in commandQueue) + { +#if UNITY_EDITOR + try + { +#endif + Command(command.command, command.entity); +#if UNITY_EDITOR + } + catch (Exception e) + { + SLLog.Error($"Can't Run [{command.command}]. {e.Message} {e.StackTrace}"); + } +#endif + } + + foreach (var component in activeComponents) + { +#if UNITY_EDITOR + try + { +#endif + component.Update(); +#if UNITY_EDITOR + } + catch (Exception e) + { + SLLog.Error($"Can't Update [{component.GetType().Name}]. {e.Message} {e.StackTrace}"); + } +#endif + } + + commandQueue.Clear(); + + activeComponents.RemoveAll(c => + { + if (c.Active == false) + { +#if UNITY_EDITOR + try + { +#endif + c.End(); +#if UNITY_EDITOR + } + catch (Exception e) + { + SLLog.Error($"Can't Update [{c.GetType().Name}]. {e.Message} {e.StackTrace}"); + } +#endif + return true; + } + return false; + }); + + canCommand = false; + + SessionCacheUpdate(); + + updateSessionCache = false; + } + + public static void ActiveComponent(string component, bool active) + { + // TODO: 중복 추가등 체크필요가 있을까? + var instance = gameComponents[component]; + if (active) + { + if (instance.Active == false) + { + if (activeComponents.Contains(instance)) // 이번 프레임에 삭제된 경우 + { + instance.End(); + } + else + { + readyActiveComponents.Add(instance); + } + instance.Active = true; + instance.Begin(); + } + } + else + { + instance.Active = false; + } + } + + public static void ActiveComponent(bool active) where T : SLGameComponent + { + ActiveComponent(typeof(T).Name, active); + } + + public static void Command(string commandName, SLEntity entity) + { +#if UNITY_EDITOR + try + { +#endif + if (commands.ContainsKey(commandName) == false) + { + SLLog.Error($"Can't Run [{commandName}]"); + return; + } + + if (canCommand == false) + { + commandQueue.Add((commandName, entity)); + return; + } + + var (component, method) = commands[commandName]; + + if (component.Active == false) + { + SLLog.Error($"Can't Run [{commandName}]. Component is not active"); + return; + } + method.Invoke(component, new[] { entity }); +#if UNITY_EDITOR + } + catch (Exception e) + { + var message = $"Can't Run [{commandName}]. \n{e.Message} \n{e.StackTrace}"; + var inner = e.InnerException; + while (inner != null) + { + message += $"\n{inner.Message} \n{inner.StackTrace}"; + inner = inner.InnerException; + } + SLLog.Error(message); + } +#endif + } + + public static void Event(string eventName, SLEntity context) + { + if (context == false) + { + context = SLEntity.Empty; + context["EventName"] = eventName; + } + var id = Session["Events"].Get(eventName).Count(); + context = context.Override(); + Session["Events"].Set($"{eventName}.{id}", context); + } + + public static void ClearEvent() + { + Session["Events"] = false; + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLGame.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLGame.cs.meta new file mode 100644 index 000000000..cc40035cb --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLGame.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65c4579dd6948da4fa59f0d03e51717d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs b/Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs new file mode 100644 index 000000000..9b7ccf23d --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs @@ -0,0 +1,13 @@ +using Superlazy; + +public abstract class SLGameComponent +{ + public static SLEntity Player => SLGame.Session["Player"]; + public bool Active { get; internal set; } + + public abstract void Begin(); + + public abstract void Update(); + + public abstract void End(); +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs.meta new file mode 100644 index 000000000..47479cd31 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLGameComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56d0350b0c71c314c8e379c2e2219ed7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs b/Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs new file mode 100644 index 000000000..9df1c4772 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs @@ -0,0 +1,206 @@ +using System.IO; +using System.IO.Compression; +using System.Text; +using Superlazy; +using Superlazy.Loader; +using UnityEngine; + +public interface IGameSaver +{ + public void Init(); + + void Set(string key, SLEntity value); + + SLEntity Get(string key, SLEntity defaultValue); +} + +public class RuntimeSaver : IGameSaver +{ + private SLEntity entity; + + public void Init() + { + if (File.Exists(@"./Saves/Save.sav")) + { + var yaml = LoadCompressedTextFromFile(@"./Saves/Save.sav"); + entity = YamlLoader.LoadYaml(yaml); + } + else + { + var text = PlayerPrefs.GetString("Save", ""); + entity = YamlLoader.LoadYaml(text); + } + } + + public SLEntity Get(string path, SLEntity defaultValue) + { + if (defaultValue == null) return entity.Get(path); + if (entity.Get(path)) return entity.Get(path); + + return defaultValue; + } + + public void Set(string path, SLEntity value) + { + entity.Set(path, value); + SaveCompressedTextToFile(YamlLoader.SaveToYaml(entity), @"./Saves/Save.sav"); + } + + private string LoadCompressedTextFromFile(string filePath) + { + using var fileStream = File.OpenRead(filePath); + using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress); + using var resultStream = new MemoryStream(); + gzipStream.CopyTo(resultStream); + var decompressedData = resultStream.ToArray(); + return Encoding.UTF8.GetString(decompressedData); + } + + private void SaveCompressedTextToFile(string text, string filePath) + { + // 압축할 텍스트를 UTF8 바이트 배열로 변환 + var data = Encoding.UTF8.GetBytes(text); + + // 경로가 없으면 디렉토리 생성 + var directory = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // 파일 스트림 열고 압축된 데이터를 기록 + using var fileStream = File.Create(filePath); + using var gzipStream = new GZipStream(fileStream, CompressionMode.Compress); + gzipStream.Write(data, 0, data.Length); + } +} + +public class RuntimePlayerPrefSaver : IGameSaver +{ + private SLEntity entity; + + public void Init() + { + var text = PlayerPrefs.GetString("SaveData", ""); + entity = JsonLoader.LoadJson(text); + } + + public SLEntity Get(string path, SLEntity defaultValue) + { + if (defaultValue == null) return entity.Get(path); + if (entity.Get(path)) return entity.Get(path); + + return defaultValue; + } + + public void Set(string path, SLEntity value) + { + entity.Set(path, value); + PlayerPrefs.SetString("SaveData", JsonLoader.SaveToJson(entity, -1, true)); + } +} + +public class PlayerPrefOptionSaver : IGameSaver +{ + private SLEntity entity; + + public void Init() + { + var text = PlayerPrefs.GetString("Options", ""); + entity = YamlLoader.LoadYaml(text); + } + + public SLEntity Get(string path, SLEntity defaultValue) + { + if (defaultValue == null) return entity.Get(path); + if (entity.Get(path)) return entity.Get(path); + + return defaultValue; + } + + public void Set(string path, SLEntity value) + { + entity.Set(path, value); + PlayerPrefs.SetString("Options", YamlLoader.SaveToYaml(entity)); + } +} + +public class FileSaver : IGameSaver +{ + private SLEntity entity; + private readonly string filePath; + + public FileSaver(string path) + { + filePath = path; + } + + public void Init() + { + if (File.Exists(filePath)) + { + using var file = File.OpenText(filePath); + var yaml = file.ReadToEnd(); + entity = YamlLoader.LoadYaml(yaml); + } + else + { + entity = SLEntity.Empty; + } + } + + public SLEntity Get(string path, SLEntity defaultValue) + { + if (defaultValue == null) return entity.Get(path); + if (entity.Get(path)) return entity.Get(path); + + return defaultValue; + } + + public void Set(string path, SLEntity value) + { + entity.Set(path, value); + var yaml = YamlLoader.SaveToYaml(entity); + File.WriteAllText(this.filePath, yaml); + } +} + +// TODO: SLSystem과 통합 +public static class SLGameSaver +{ + private static IGameSaver saver = new DefaultSaver(); + private static IGameSaver optionSaver = new DefaultSaver(); + + public static void Init(IGameSaver inst, IGameSaver optionInst) + { + saver = inst; + saver.Init(); + + optionSaver = optionInst; + optionSaver.Init(); + } + + public static void Set(string key, SLEntity value) => saver.Set(key, value); + + public static SLEntity Get(string key, SLEntity deafaultValue = null) => saver.Get(key, deafaultValue); + + public static void OptionSet(string key, SLEntity value) => optionSaver.Set(key, value); + + public static SLEntity OptionGet(string key, SLEntity deafaultValue = null) => optionSaver.Get(key, deafaultValue); +} + +public class DefaultSaver : IGameSaver +{ + public void Init() + { + } + + public SLEntity Get(string path, SLEntity defaultValue) + { + return defaultValue; + } + + public void Set(string path, SLEntity value) + { + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs.meta new file mode 100644 index 000000000..775988f16 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLGameSaver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d80af66251202934d925faaef8ceab80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLOption.cs b/Assets/_Datas/SLShared/SLUnity/SLOption.cs new file mode 100644 index 000000000..ba1f37e33 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLOption.cs @@ -0,0 +1,64 @@ +using System; +using System.Globalization; +using UnityEngine; + +namespace Superlazy +{ + internal class SLOption + { + internal static void Init() + { + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + if (PlayerPrefs.HasKey("AppID") == false) + { + PlayerPrefs.SetString("AppID", Guid.NewGuid().ToString()); + PlayerPrefs.Save(); + } + } + + public static void InitOption(string key, SLEntity option) + { + if (PlayerPrefs.HasKey(OptionString(key))) + { + SetOption(key, PlayerPrefs.GetString(OptionString(key))); + } + else + { + SetOption(key, option); + } + } + + public static void SetOption(string key, SLEntity option) + { + if (option.ToString() == string.Empty) + { + option = false; + } + SLGame.Session["Options"][key] = option; + PlayerPrefs.SetString(OptionString(key), option); + } + + public static SLEntity GetOption(string key) + { + if (SLGame.Session["Options"][key]) + { + return SLGame.Session["Options"][key]; + } + else + { + SLLog.Error($"Option not found: {key}"); + return SLEntity.Empty; + } + } + + private static string OptionString(string key) + { + return "Option_" + key; + } + + internal static void Save() + { + PlayerPrefs.Save(); + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLOption.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLOption.cs.meta new file mode 100644 index 000000000..a44f0b19e --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLOption.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6bc8ae0000d83bf4ea2621bde88d68ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLResource.cs b/Assets/_Datas/SLShared/SLUnity/SLResource.cs new file mode 100644 index 000000000..87e53582f --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLResource.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using Superlazy; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.ResourceManagement.ResourceLocations; +using UnityEngine.ResourceManagement.ResourceProviders; +using UnityEngine.SceneManagement; +using UnityEngine.U2D; + +public static class SLResources +{ + private static Dictionary> unusedResourceObjects; + private static Dictionary> unusedPrefabs; + private static Transform unusedRoot; + + public static SLResourceObject CreateInstance(string prefabName, Transform parent = null) + { + if (unusedResourceObjects != null && unusedResourceObjects.ContainsKey(prefabName) && unusedResourceObjects[prefabName].Count > 0) + { + var instance = unusedResourceObjects[prefabName].Dequeue(); + instance.transform.SetParent(parent, false); + return instance; + } + else + { + var obj = GetPrefab(prefabName); + if (obj == null) + { + SLLog.Error($"Can't Find Preafb. [{prefabName}]"); + return null; + } + + var objectInstance = Object.Instantiate(obj, parent); + + objectInstance.name = prefabName; + var instance = objectInstance.GetComponent(); + + if (instance == null) + { + SLLog.Error($"Can't Find SLResourceObject. [{prefabName}]"); + return null; + } + return instance; + } + } + + internal static void DestroyInstance(this SLResourceObject go) + { + unusedResourceObjects ??= new Dictionary>(); + + if (unusedRoot == null) + { + unusedRoot = new GameObject("UnusedInstance").transform; + unusedRoot.gameObject.SetActive(false); + } + + go.transform.SetParent(unusedRoot, false); + if (unusedResourceObjects.ContainsKey(go.name) == false) + { + unusedResourceObjects.Add(go.name, new Queue()); + } + + unusedResourceObjects[go.name].Enqueue(go); + } + + public static GameObject CreatePrefabInstance(string prefabName, Transform parent = null) + { + if (unusedPrefabs != null && unusedPrefabs.ContainsKey(prefabName) && unusedPrefabs[prefabName].Count > 0) + { + var instance = unusedPrefabs[prefabName].Dequeue(); + instance.transform.SetParent(parent, false); + return instance; + } + else + { + var obj = GetPrefab(prefabName); + if (obj == null) + { + SLLog.Error($"Can't Find Preafb. [{prefabName}]"); + return null; + } + + var instance = Object.Instantiate(obj, parent); + + instance.name = prefabName; + return instance; + } + } + + public static void Destroy(this GameObject go) + { + unusedPrefabs ??= new Dictionary>(); + + if (unusedRoot == null) + { + unusedRoot = new GameObject("UnusedInstance").transform; + unusedRoot.gameObject.SetActive(false); + } + + go.transform.SetParent(unusedRoot, false); + if (unusedPrefabs.ContainsKey(go.name) == false) + { + unusedPrefabs.Add(go.name, new Queue()); + } + + unusedPrefabs[go.name].Enqueue(go); + } + + public static GameObject GetPrefab(string prefabName) + { + if (string.IsNullOrEmpty(prefabName)) return null; + var obj = Load(prefabName); + return obj; + } + + public static Sprite GetSprite(string spritePath) + { + if (string.IsNullOrEmpty(spritePath)) return null; + if (spritePath.Contains('/') == false) return null; + + //TODO: 문자열 분할이 일어나지 않게끔 캐싱 + var seperateIdx = spritePath.LastIndexOf('/'); + var atlasPath = spritePath.Substring(0, seperateIdx); + var spriteName = spritePath.Substring(seperateIdx + 1); + + { + // check big + var bigPath = $"{atlasPath}_{spriteName}"; + if (AddressableResourceExists(bigPath)) + { + return Load(bigPath).GetSprite(spriteName); + } + } + + return Load(atlasPath).GetSprite(spriteName); + } + + public static RuntimeAnimatorController GetAnimatorController(string controler) + { + return Load(controler); + } + + public static IList LoadAll(string path) where T : Object + { + return Addressables.LoadAssetsAsync(path, (t) => { }).WaitForCompletion(); + } + + public static T Load(string resourceName) where T : Object + { +#if UNITY_EDITOR + try + { +#endif + //if (AddressableResourceExists(resourceName) == false) return default; // TODO: 원인 파악해서 에디터에서 에러 정상발생하도록 수정 + + return Addressables.LoadAssetAsync(resourceName).WaitForCompletion(); +#if UNITY_EDITOR + } + catch + { + SLLog.Error($"Can't Find Resource. [{resourceName}]"); + return default; + } +#endif + } + + public static AsyncOperationHandle LoadScene(string resourceName, LoadSceneMode mode, bool active = false) + { +#if UNITY_EDITOR + try + { +#endif + // if (AddressableResourceExists(resourceName) == false) return default; // TODO: 원인 파악해서 에디터에서 에러 정상발생하도록 수정 + return Addressables.LoadSceneAsync(resourceName, mode, active); +#if UNITY_EDITOR + } + catch + { + SLLog.Error($"Can't Find Resource. [{resourceName}]"); + return default; + } +#endif + } + + public static AsyncOperationHandle UnloadScene(SceneInstance inst) + { + return Addressables.UnloadSceneAsync(inst); + } + + private static bool AddressableResourceExists(object key) // TODO: 성능이슈 개선필요 // TODO: 원인 파악해서 에디터에서 에러 정상발생하도록 수정 + { + foreach (var l in Addressables.ResourceLocators) + { + if (l.Locate(key, typeof(T), out var _)) + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLResource.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLResource.cs.meta new file mode 100644 index 000000000..20b1b798a --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLResource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e74d3b038b9a4547b3155f43db0e214 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs b/Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs new file mode 100644 index 000000000..38c73fbcf --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs @@ -0,0 +1,188 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Superlazy +{ + public class SLResourceObject : MonoBehaviour + { + public Animator[] anims; + public Renderer[] renderers; + public ParticleSystem[] particles; + public TrailRenderer[] trails; + public SLStateController[] stateControllers; + public SLContextController[] contextControllers; + public float destroyDelay; + + private Dictionary transforms; // 자주 안쓰일듯해 필요할때 캐싱 + + public void Init() + { + anims = GetComponentsInChildren(); + renderers = GetComponentsInChildren().Where(r => r is MeshRenderer || r is SkinnedMeshRenderer).ToArray(); + particles = GetComponentsInChildren(); + trails = GetComponentsInChildren(); + stateControllers = GetComponentsInChildren(); + contextControllers = GetComponentsInChildren(); + + foreach (var a in anims) + { + a.keepAnimatorStateOnDisable = true; + a.updateMode = AnimatorUpdateMode.Normal; + a.cullingMode = AnimatorCullingMode.AlwaysAnimate; + } + + // TODO: destroyDelay 를 컨트롤하는 별도의 상수 개념 추가 + foreach (var t in trails) + { + destroyDelay = Mathf.Max(destroyDelay, t.time); + } + + foreach (var p in particles) + { + var main = p.main; + if (main.loop) continue; + var lt = main.startLifetime; + switch (main.startLifetime.mode) + { + case ParticleSystemCurveMode.Constant: + destroyDelay = Mathf.Max(destroyDelay, lt.constant); + break; + + case ParticleSystemCurveMode.TwoConstants: + destroyDelay = Mathf.Max(destroyDelay, lt.constantMax, lt.constantMin); + break; + + case ParticleSystemCurveMode.Curve: + case ParticleSystemCurveMode.TwoCurves: + destroyDelay = Mathf.Max(destroyDelay, lt.curveMultiplier * main.startLifetimeMultiplier); + break; + } + } + + foreach (var c in stateControllers) + { + c.Init(); + } + + foreach (var c in contextControllers) + { + c.Init(); + } + } + + public void OnEnable() + { + foreach (var r in renderers) + { + r.enabled = true; + } + + foreach (var p in particles) + { + p.Clear(); + p.Play(); + } + + foreach (var t in trails) + { + t.Clear(); + t.emitting = true; + } + + foreach (var c in stateControllers) + { + c.Clear(); + } + + foreach (var c in contextControllers) + { + c.Clear(); + } + } + + public void Destroy(bool force = false) + { + foreach (var r in renderers) + { + r.enabled = false; + } + + foreach (var p in particles) + { + p.Stop(); + } + + foreach (var t in trails) + { + t.emitting = false; + } + + foreach (var c in stateControllers) + { + c.Clear(); + } + + foreach (var c in contextControllers) + { + c.Clear(); + } + + if (force == false && destroyDelay > 0.0f && gameObject.activeInHierarchy) + { + StartCoroutine(DelayEnd()); + } + else + { + SLResources.DestroyInstance(this); + } + } + + private IEnumerator DelayEnd() + { + transform.SetParent(null, true); + yield return new WaitForSeconds(destroyDelay); + transform.localPosition = Vector3.zero; + transform.localRotation = Quaternion.identity; + transform.localScale = Vector3.one; + SLResources.DestroyInstance(this); + } + + public void SetKeyword(string state, bool enable = true) + { + foreach (var c in stateControllers) + { + c.EnableKeyword(state, enable); + } + } + + public void SetContext(SLEntity context) + { + foreach (var c in contextControllers) + { + c.SetContext(context); + } + } + + public Transform GetTransform(string name) + { + if (transforms == null) + { + transforms = new Dictionary(); + foreach (var t in GetComponentsInChildren()) + { + if (transforms.ContainsKey(t.name)) continue; + transforms[t.name] = t; + } + } + + if (transforms.ContainsKey(name)) + { + return transforms[name]; + } + + return transform; // 없다면 Root + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs.meta new file mode 100644 index 000000000..ff7bd4c7b --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLResourceObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f793fbf2fb02714bb5f8982d3419f2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLSceneController.cs b/Assets/_Datas/SLShared/SLUnity/SLSceneController.cs new file mode 100644 index 000000000..fa6b76f12 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLSceneController.cs @@ -0,0 +1,160 @@ +using Superlazy; +using System.Collections; +using UnityEngine; +using UnityEngine.ResourceManagement.ResourceProviders; +using UnityEngine.SceneManagement; + +public class SLSceneController : MonoBehaviour +{ + public static void Init() + { + inst = new GameObject("SceneController").AddComponent(); + } + + public static bool Loaded { get; private set; } = true; + private static SLSceneController inst; + + private SceneInstance oldScene; + private SceneInstance currentScene; + private bool setupEnd = false; + private bool activeEnd = false; + + private string scene = ""; + + public static void ChangeScene(string newScene) // 구버전 하위 호환성 + { + inst.SetupAndChange(newScene); + } + + public static void SetupScene(string newScene) + { + inst.Setup(newScene); + } + + public static void ChangeScene() + { + inst.Change(); + } + + private void Setup(string newScene) + { + if (newScene == scene) + { + return; + } + + if (Loaded == false) + { + SLLog.Error($"Already Load [{scene}] Can't Load [{newScene}]"); + return; + } + + scene = newScene; + Loaded = false; + setupEnd = false; + activeEnd = false; + + oldScene = currentScene; + currentScene = default; + + if (string.IsNullOrEmpty(newScene) == false) + { + var handler = SLResources.LoadScene(newScene, LoadSceneMode.Additive); + + handler.Completed += (op) => + { + currentScene = op.Result; + foreach (var rootObj in currentScene.Scene.GetRootGameObjects()) + { + rootObj.SetActive(false); + } + setupEnd = true; + }; + } + else + { + setupEnd = true; + } + } + + private void Change() + { + StartCoroutine(ChangeSceneCoroutine()); + } + + private IEnumerator ChangeSceneCoroutine() + { + yield return new WaitUntil(() => setupEnd); + + if (oldScene.Scene.IsValid()) + { + var deactiveHandler = SLResources.UnloadScene(oldScene); + + deactiveHandler.Completed += (op) => + { + oldScene = default; + }; + } + + if (currentScene.Scene.IsValid()) + { + var activeHandler = currentScene.ActivateAsync(); + activeHandler.completed += (op) => + { + foreach (var rootObj in currentScene.Scene.GetRootGameObjects()) + { + rootObj.SetActive(true); + } + activeEnd = true; + }; + } + else + { + //activeEnd = true; + } + + yield return new WaitUntil(() => oldScene.Scene.IsValid() == false && activeEnd); + + LightProbes.Tetrahedralize(); + + Loaded = true; + } + + private void SetupAndChange(string newScene) + { + if (newScene == scene) + { + return; + } + + if (Loaded == false) + { + SLLog.Error($"Already Load [{scene}] Can't Load [{newScene}]"); + return; + } + + StartCoroutine(ChangeRoutine(newScene)); + } + + private IEnumerator ChangeRoutine(string newScene) + { + Loaded = false; + if (string.IsNullOrEmpty(scene) == false) + { + yield return SLResources.UnloadScene(currentScene); + } + + scene = newScene; + + if (string.IsNullOrEmpty(scene) == false) + { + var handler = SLResources.LoadScene(scene, LoadSceneMode.Additive, true); + yield return handler; + currentScene = handler.Result; + } + + LightProbes.Tetrahedralize(); + + Loaded = true; + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLSceneController.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLSceneController.cs.meta new file mode 100644 index 000000000..2deaa86e6 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLSceneController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abc1374404c136148807b6d2e6fe2440 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLSound.cs b/Assets/_Datas/SLShared/SLUnity/SLSound.cs new file mode 100644 index 000000000..daa4edbab --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLSound.cs @@ -0,0 +1,295 @@ +using Superlazy; +using System.Collections.Generic; +using UnityEngine; + +public class SLSound : SLGameComponent +{ + private static GameObject soundRoot; + + private static bool mute = false; + + // 재생 관리를 위한 큐 + private static readonly Dictionary> enableObjects = new Dictionary>(); + + // 페이드 + private static readonly List fades = new List(); + + private static readonly List removes = new List(); + + // 리소스 풀 + private static int unusedCount = 0; + + private static readonly Dictionary> unused = new Dictionary>(); + + // mute 정보 + private static readonly HashSet muteTagList = new HashSet(); + + private static readonly List currentFramePlays = new List(); + + private static readonly Dictionary delaySounds = new Dictionary(); + + private static AudioSource GetAudioSource(string clipName) + { + if (currentFramePlays.Contains(clipName)) return null; + + currentFramePlays.Add(clipName); + + AudioSource audioSource = null; + if (unused.ContainsKey(clipName) && unused[clipName].Count != 0) + { + audioSource = unused[clipName].Dequeue(); + --unusedCount; + } + else + { + //TODO : 추후 사운드도 ResourceManager 로 이관해야함 + var sound = SLResources.Load(clipName); + if (sound != null) + { + sound.name = clipName; + audioSource = soundRoot.AddComponent(); + audioSource.clip = sound; + } + } + + return audioSource; + } + + public static void MuteSounds(bool mute) + { + SLSound.mute = mute; + } + + public static void ClearPool() + { + foreach (var queue in unused) + { + foreach (var source in queue.Value) + { + Object.Destroy(source); + } + } + unused.Clear(); + unusedCount = 0; + } + + public static void PlaySound(string clipName, string tag = "Effect", bool loop = false, bool unique = false, bool reset = true, float volume = 1, float delay = 0) + { + if (string.IsNullOrEmpty(clipName)) return; + + if (clipName == "Off") return; // TODO: Off라는 이름의 사운드가 없어 임시 처리. 추후 수정 필요 + + if (soundRoot == null) return; + + if (tag == "BGM") + { + volume *= SLGame.Session["Option"]["Volume"]["BGM"] * 0.1f; + } + else + { + volume *= SLGame.Session["Option"]["Volume"]["SE"] * 0.1f; + } + + if (delay > 0) + { + SLEntity context = SLEntity.Empty; + context["Tage"] = tag; + context["Loop"] = loop; + context["Unique"] = unique; + context["Reset"] = reset; + context["Volume"] = volume; + context["Delay"] = delay; + delaySounds.Add(clipName, context); + return; + } + + var exist = false; + if (unique) + { + if (enableObjects.ContainsKey(tag)) + { + var stopList = new List(); + foreach (var source in enableObjects[tag]) + { + if (reset == false && source.clip.name == clipName) + { + exist = true; + source.volume = volume; + continue; + } + + if (source.loop) + { + fades.Add(source); + } + else + { + source.Stop(); + removes.Add(source); + } + stopList.Add(source); + } + foreach (var source in stopList) + { + enableObjects[tag].Remove(source); + } + } + } + if (exist) return; + if (tag != "BGM" && tag != "Weather" && IsMuteTag(tag)) return; + + if (enableObjects.ContainsKey(tag) && tag != "TextSound") + { + if (enableObjects[tag].Count > 4) return; + } + + var audioSource = GetAudioSource(clipName); + + if (audioSource != null) + { + // 여기서 각종 옵션을 제어한다. + { + audioSource.loop = loop; + audioSource.volume = volume; + audioSource.mute = IsMuteTag(tag); + } + audioSource.enabled = true; + audioSource.Play(); + + if (enableObjects.ContainsKey(tag) == false) + enableObjects[tag] = new List(); + enableObjects[tag].Add(audioSource); + } + } + + public static void StopSound(string tag) + { + if (enableObjects.ContainsKey(tag)) + { + foreach (var source in enableObjects[tag]) + { + if (source.loop) + { + fades.Add(source); + } + else + { + source.Stop(); + removes.Add(source); + } + } + enableObjects[tag].Clear(); + } + } + + public static void SetMute(string tag, bool mute) + { + if (mute) muteTagList.Add(tag); + else muteTagList.Remove(tag); + } + + private static bool IsMuteTag(string tag) + { + if (mute) return true; + + if (tag == "BGM" || tag == "Weather") + { + return muteTagList.Contains("BGM"); + } + else + { + return muteTagList.Contains("Effect"); + } + } + + public static void VolumeChange(float before, float next) + { + foreach (var clip in enableObjects["BGM"]) + { + clip.volume = before == 0f ? next * 0.1f : clip.volume * next / before; + } + } + + public override void Begin() + { + if (soundRoot == null) + { + soundRoot = new GameObject("SoundRoot"); + Object.DontDestroyOnLoad(soundRoot); + } + } + + public override void Update() + { + foreach (var sourceList in enableObjects) + { + foreach (var audioSource in sourceList.Value) + { + if (audioSource.clip.loadInBackground == false && audioSource.isPlaying == false) + { + removes.Add(audioSource); + } + else + { + audioSource.mute = IsMuteTag(sourceList.Key); + } + } + sourceList.Value.RemoveAll(u => u.clip.loadInBackground == false && u.isPlaying == false); + } + + foreach (var source in fades) + { + source.volume -= Time.unscaledDeltaTime; + if (source.volume <= 0) + { + source.Stop(); + removes.Add(source); + } + } + fades.RemoveAll(u => u.volume <= 0); + + foreach (var audioSource in removes) + { + var name = audioSource.clip.name; + + if (unused.ContainsKey(name) == false) + { + unused[name] = new Queue(); + } + unused[name].Enqueue(audioSource); + ++unusedCount; + audioSource.enabled = false; + } + removes.Clear(); + + if (unusedCount >= 30) + { + ClearPool(); + } + + if (delaySounds.Count > 0) + { + var removeDelaySounds = new List(); + foreach (var delaySound in delaySounds) + { + delaySound.Value["Delay"] -= Time.unscaledDeltaTime; + if (delaySound.Value["Delay"] <= 0) + { + var context = delaySound.Value; + PlaySound(delaySound.Key, context["Tag"], context["Loop"], context["Unique"], context["Reset"], context["Volume"]); + removeDelaySounds.Add(delaySound.Key); + } + } + foreach (var key in removeDelaySounds) + { + delaySounds.Remove(key); + } + } + + currentFramePlays.Clear(); + } + + public override void End() + { + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLSound.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLSound.cs.meta new file mode 100644 index 000000000..80fc1a1d9 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLSound.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23c5c799fa084534ba4b33858a61377b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLStateController.cs b/Assets/_Datas/SLShared/SLUnity/SLStateController.cs new file mode 100644 index 000000000..d0835c4d3 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLStateController.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace Superlazy +{ + public abstract class SLStateController : MonoBehaviour + { + public abstract void Init(); + + public abstract void Clear(); + + public abstract void EnableKeyword(string quest, bool enable); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLStateController.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLStateController.cs.meta new file mode 100644 index 000000000..d9447e47e --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLStateController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ff9a45a2b64588469a47677133d63e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLString.cs b/Assets/_Datas/SLShared/SLUnity/SLString.cs new file mode 100644 index 000000000..2a2eed626 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLString.cs @@ -0,0 +1,394 @@ +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using Superlazy; + +public static class SLString +{ + private static readonly List tags = new List() + { + new char[]{ 's', 'c' }, + new char[]{ '/', 's', 'c' }, + new char[]{ 's', 'c', 'o', 'l', 'o', 'r' }, + new char[]{ '/', 's', 'c', 'o', 'l', 'o', 'r' }, + new char[]{ 'n', 'l', }, + new char[]{ 's', 't', 'r' } + }; + + private static readonly List koreanPostpositionStrTags = new List() + { + new string[] { "을(를)", "을", "를" }, + new string[] { "을/를", "을", "를" }, + new string[] { "이(가)", "이", "가" }, + new string[] { "이/가", "이", "가" }, + new string[] { "은(는)", "은", "는" }, + new string[] { "은/는", "은", "는" }, + new string[] { "과(와)", "과", "와" }, + new string[] { "과/와", "과", "와" } + }; + + private static readonly string englishPluralStrTags = "(s)"; + + public static string GetString(string str, SLEntity context) + { + return Translation(str, context); + } + + private static string Translation(string str, SLEntity context) + { + if (string.IsNullOrEmpty(str)) return string.Empty; + + if (str.IsLeft("STR_") == false) return Format(str, context); + + var strKey = SLSystem.Data["Strings"][str]; + var lang = SLGame.Session["Option"]["Language"]; + + if (strKey[lang]) + { + return Format(strKey[lang], context); + } + else if (strKey["EN"]) + { + return Format(strKey["EN"], context); + } + else if (strKey["KR"]) + { + return Format(strKey["KR"], context); + } + else + { + return Format(str, context); + } + } + + private static string Format(string value, SLEntity context) + { + var tagStart = value.LastIndexOf('{', value.Length - 1); + + if (tagStart != -1) + { + var tagEnd = value.IndexOf('}', tagStart); + if (tagEnd == -1) + { + SLLog.Error($"Tagging Error [{value}]"); + return value; + } + + var sb = new StringBuilder(value); + + var tagParamIdx = value.IndexOf(':', tagStart + 1, tagEnd - tagStart - 1); + + string tagAttribute; + string tagType; + if (tagParamIdx != -1) + { + tagAttribute = sb.ToString(tagStart + 1, tagParamIdx - tagStart - 1); + tagType = sb.ToString(tagParamIdx + 1, tagEnd - tagParamIdx - 1); + } + else + { + tagAttribute = sb.ToString(tagStart + 1, tagEnd - tagStart - 1); + tagType = string.Empty; + } + var format = Translation(SLTag.Apply(context.Get(tagAttribute), tagType), context); + + sb.Remove(tagStart, tagEnd - tagStart + 1); + sb.Insert(tagStart, format); + return Format(sb.ToString(), context); // 태그를 반복적으로 적용 + } + else + { + return CustomTag(value, context); + } + } + + private static string CustomTag(string str, SLEntity context) + { + if (string.IsNullOrEmpty(str)) return str; + + var sb = new StringBuilder(GetTagApplyString(str, context)); + + return PostPositionCheck(sb); + } + + private static string GetTagApplyString(string str, SLEntity context) + { + if (string.IsNullOrEmpty(str)) return str; + + var tagStart = str.LastIndexOf('<', str.Length - 1); + + var hasTag = tagStart != -1; + // end + + if (hasTag == false) + { + return str; + } + + var tagEnd = str.IndexOf('>', tagStart); + if (tagEnd == -1) + { + SLLog.Error($"Tagging Error [{str}]"); + return str; + } + + var sb = new StringBuilder(str); + + if (TagCheck(sb, tagStart, tagEnd, tags[0])) + { + var colorTag = SLSystem.Data["SpecialColors"][sb.ToString(tagStart + 4, tagEnd - tagStart - 4)].ToString(); + sb.Remove(tagStart + 1, tagEnd - tagStart - 1); + sb.Insert(tagStart + 1, colorTag); + sb.Insert(tagStart + 1, "color="); + } + else if (TagCheck(sb, tagStart, tagEnd, tags[1])) + { + sb.Remove(tagStart + 2, tagEnd - tagStart - 2); + sb.Insert(tagStart + 2, "color"); + } + else if (TagCheck(sb, tagStart, tagEnd, tags[2])) + { + var colorTag = SLSystem.Data["SpecialColors"][sb.ToString(tagStart + 8, tagEnd - tagStart - 8)].ToString(); + sb.Remove(tagStart + 1, tagEnd - tagStart - 1); + sb.Insert(tagStart + 1, colorTag); + sb.Insert(tagStart + 1, "color="); + } + else if (TagCheck(sb, tagStart, tagEnd, tags[3])) + { + sb.Remove(tagStart + 2, tagEnd - tagStart - 2); + sb.Insert(tagStart + 2, "color"); + } + else if (TagCheck(sb, tagStart, tagEnd, tags[4])) + { + sb.Remove(tagStart, tagEnd - tagStart + 1); + sb.Insert(tagStart, "\n"); + } + else if (TagCheck(sb, tagStart, tagEnd, tags[5])) + { + var newString = sb.ToString(tagStart + 5, tagEnd - tagStart - 5); + + sb.Remove(tagStart, tagEnd - tagStart + 1); + sb.Insert(tagStart, Translation(newString, context)); + } + + var left = GetTagApplyString(sb.ToString(0, tagStart), context); // 태그 반복 + sb.Remove(0, tagStart); + sb.Insert(0, left); + + return sb.ToString(); + } + + private static string PostPositionCheck(StringBuilder sb) + { + var lang = SLGame.Session["Option"]["Language"]; + if (lang == "KR") + { + foreach (var tag in koreanPostpositionStrTags) + { + int index = LastIndexOf(sb, tag[0]); + while (index != -1) + { + // index가 0보다 작으면 이전 문자가 없으므로 안전하게 체크 + if (index - 1 < 0) + break; + + char priorWord = sb[index - 1]; + string correction = HasEndConsonant(priorWord) ? tag[1] : tag[2]; + int tagLength = tag[0].Length; + + sb.Remove(index, tagLength); + sb.Insert(index, correction); + + // sb를 직접 검색하여 갱신 + index = LastIndexOf(sb, tag[0]); + } + } + } + else if (lang == "EN") + { + int index = LastIndexOf(sb, englishPluralStrTags); + while (index != -1) + { + int blankEndIndex = LastIndexOf(sb, ' ', index - 1); + int blankStartIndex = LastIndexOf(sb, ' ', blankEndIndex - 1); + if (blankEndIndex == -1 || blankStartIndex == -1) + break; + + string numberStr = Substring(sb, blankStartIndex + 1, blankEndIndex - blankStartIndex - 1); + + // 공백 및 기타 whitespace 처리 + numberStr = Regex.Replace(numberStr, @"\s+", " "); + int blankIndex = numberStr.LastIndexOf(' '); + if (blankIndex != -1) + { + numberStr = numberStr.Substring(blankIndex + 1); + } + + bool plural; + string numberStrLower = numberStr.ToLower(); + if (numberStrLower == "one" || numberStrLower == "a") + { + plural = false; + } + else + { + if (!int.TryParse(numberStr, out var number)) + { + plural = true; + } + else + { + plural = number != 1; + } + } + + sb.Remove(index, englishPluralStrTags.Length); + if (plural) + { + sb.Insert(index, 's'); + } + + index = LastIndexOf(sb, englishPluralStrTags); + } + } + + return ColorTagCheck(sb); + } + + private static bool HasEndConsonant(char word) + { + if (word < 0xAC00) return false; + return (word - 0xAC00) % 28 + 0x11a7 != 4519; + } + + private static bool TagCheck(StringBuilder sb, int tagStart, int tagEnd, char[] tag) + { + var cnt = tag.Length; + if (tagEnd - tagStart - 1 < tag.Length) return false; + for (var i = 0; i < cnt; ++i) + { + if (sb[i + tagStart + 1] != tag[i] && sb[i + tagStart + 1] != char.ToUpper(tag[i])) return false; + } + + return true; + } + + private static string ColorTagCheck(StringBuilder sb) + { + int bracketStart; + var currentSearchPosition = 0; + + while (currentSearchPosition < sb.Length && (bracketStart = IndexOf(sb, '[', currentSearchPosition)) != -1) + { + var bracketEnd = IndexOf(sb, ']', bracketStart); + if (bracketEnd == -1) + { + SLLog.Error($"Bracket Tagging Error [{sb}]"); + break; + } + + var colorCode = SLSystem.Data["SpecialColors"]["Default"]; + + var splitEnd = IndexOf(sb, '|', bracketStart); + if (splitEnd == -1 || splitEnd > bracketEnd) // 구분선이 없다면 + { + splitEnd = bracketStart; + } + else + { + colorCode = SLSystem.Data["SpecialColors"][sb.ToString(bracketStart + 1, splitEnd - bracketStart - 1)]; + } + + var formattedText = $"[{sb.ToString(splitEnd + 1, bracketEnd - splitEnd - 1)}]"; + + sb.Remove(bracketStart, bracketEnd - bracketStart + 1); + sb.Insert(bracketStart, formattedText); + + currentSearchPosition = bracketStart + formattedText.Length; + } + return sb.ToString(); + } + + public static List GetTags(string text, SLEntity context) + { + var list = new List(); + int bracketStart; + var currentSearchPosition = 0; + + text = GetString(text, context); + + while (currentSearchPosition < text.Length && (bracketStart = text.IndexOf('[', currentSearchPosition)) != -1) + { + var bracketEnd = text.IndexOf(']', bracketStart); + if (bracketEnd == -1) + { + SLLog.Error($"Bracket Tagging Error [{text}]"); + break; + } + + var originalText = text.Substring(bracketStart + 1, bracketEnd - bracketStart - 1); + + if (originalText.Contains('|')) // 태그붙음 + { + originalText = originalText.Substring(originalText.IndexOf('|') + 1); + } + + var tagID = Regex.Replace(originalText, @"[0-9\s!\""#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~×]", ""); // TODO: 추후에는 실제 태그 ID 변경 + + if (string.IsNullOrEmpty(tagID) == false) + { + var tag = SLSystem.Data["Tags"][tagID]; + if (tag["Desc"]) + { + list.Add(tagID); + } + } + + currentSearchPosition = bracketStart + bracketEnd - bracketStart + 1; + } + return list; + } + + private static int LastIndexOf(StringBuilder sb, string value) + { + if (value.Length == 0) return sb.Length; + for (int i = sb.Length - value.Length; i >= 0; i--) + { + bool found = true; + for (int j = 0; j < value.Length; j++) + { + if (sb[i + j] != value[j]) + { + found = false; + break; + } + } + if (found) return i; + } + return -1; + } + + private static int IndexOf(StringBuilder sb, char value, int startIndex = 0) + { + for (int i = startIndex; i < sb.Length; i++) + { + if (sb[i] == value) + return i; + } + return -1; + } + + private static int LastIndexOf(StringBuilder sb, char c, int startIndex) + { + for (int i = startIndex; i >= 0; i--) + { + if (sb[i] == c) return i; + } + return -1; + } + + private static string Substring(StringBuilder sb, int start, int length) + { + return sb.ToString(start, length); + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLString.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLString.cs.meta new file mode 100644 index 000000000..1d943e436 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLString.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a0e37df2d608964cb496d02386afcc1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLTag.cs b/Assets/_Datas/SLShared/SLUnity/SLTag.cs new file mode 100644 index 000000000..f9d81e792 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLTag.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Superlazy; +using UnityEngine; + +public abstract class SLTag +{ + private static Dictionary formats; + + public static void Init(IEnumerable formats = null) + { + if (SLTag.formats != null) return; + + if (formats == null) formats = Enumerable.Empty(); + + SLTag.formats = new Dictionary(); + + foreach (var format in formats) + { + if (SLTag.formats.ContainsKey(format.Tag)) + { + SLLog.Warn($"Options Duplicated{SLTag.formats[format.Tag]}, {format} : {format.Tag}"); + } + + SLTag.formats[format.Tag] = format; + } + } + + public static string Apply(SLEntity value, string tagType) + { + if (formats == null) return value.ToString(); + if (formats.ContainsKey(tagType)) + { + return formats[tagType].GetValue(value); + } + + return value.ToString(); + } + + public abstract string GetValue(SLEntity value); + + public abstract string Tag { get; } +} + +public class SLTagDefault : SLTag +{ + public override string Tag => string.Empty; + + public override string GetValue(SLEntity value) + { + return value; + } +} + +public class SLSecond : SLTag +{ + public override string Tag => "S"; + + public override string GetValue(SLEntity value) + { + return (Mathf.FloorToInt(value / 6.0f) * 0.1f).ToString(); + } +} + +public class SLRemainTime : SLTag +{ + public override string Tag => "RT"; + + public override string GetValue(SLEntity value) + { + var date = value.ToDateTime(); + + var delta = date - SLSystem.Now; + + if (delta.TotalDays >= 1) + { + return $"{delta.Days}"; + } + else if (delta.Hours >= 1) + { + return $"{delta.Hours}"; + } + else if (delta.Minutes >= 1) + { + return $"{delta.Minutes}"; + } + else + { + return $"{delta.Seconds}"; + } + } +} + +//} + +//public class SLMinute : SLStringFormat +//{ +// public override string Tag => "M"; + +// public override string GetValue(SLEntity value) +// { +// return SLMath.Ceiling(value / 60.0); +// } +//} + +//public class SLHour : SLStringFormat +//{ +// public override string Tag => "H"; + +// public override string GetValue(SLEntity value) +// { +// return SLMath.Ceiling(value / 60.0 / 60.0); +// } +//} + +//public class SLStringRoughAgoTime : SLStringFormat +//{ +// public override string Tag => "RAT"; + +// public override string GetValue(SLEntity value) +// { +// var entity = SLEntity.Empty; + +// if (value <= 0) +// { +// entity["Value"] = 0; +// return ""; +// } + +// var delta = SLSystem.Now - value.ToDateTime(); + +// var result = ""; + +// if (delta.TotalDays >= 1) +// { +// entity["Value"] = SLMath.Floor(delta.TotalDays); +// result = SLSystem.GetString("STR_DayAgo", entity); +// } +// else if (delta.Hours >= 1) +// { +// entity["Value"] = delta.Hours; +// result = SLSystem.GetString("STR_HourAgo", entity); +// } +// else if (delta.Minutes >= 1) +// { +// entity["Value"] = delta.Minutes; +// result = SLSystem.GetString("STR_MinuteAgo", entity); +// } +// else +// { +// entity["Value"] = delta.Seconds; +// result = SLSystem.GetString("STR_LessThanMinuteAgo", entity); +// } + +// return result; +// } +//} + +//public class SLStringRoughLeftTime : SLStringFormat +//{ +// public override string Tag => "RLT"; + +// public override float UpdateTick => 60.0f; + +// public override string GetValue(SLEntity value) +// { +// var entity = SLEntity.Empty; + +// if (value <= 0) +// { +// entity["Value"] = 0; +// return SLSystem.GetString("STR_MinuteLeft", SLEntity.Empty); +// } + +// var delta = value.ToDateTime() - SLSystem.Now; + +// var result = ""; + +// if (delta.TotalDays >= 1) +// { +// entity["Value"] = SLMath.Floor(delta.TotalDays); +// result = SLSystem.GetString("STR_DayLeft", entity); +// } +// else if (delta.Hours >= 1) +// { +// entity["Value"] = delta.Hours; +// result = SLSystem.GetString("STR_HourLeft", entity); +// } +// else if (delta.Minutes >= 1) +// { +// entity["Value"] = delta.Minutes; +// result = SLSystem.GetString("STR_MinuteLeft", entity); +// } +// else +// { +// entity["Value"] = delta.Seconds; +// result = SLSystem.GetString("STR_LessThanMinuteLeft", entity); +// } + +// return result; +// } +//} + +public class SLStringLocalDate : SLTag +{ + public override string Tag => "DATE"; + + public override string GetValue(SLEntity value) + { + return value.ToLocalDateTime().ToString("yyyy.MM.dd"); + } +} + +public class SLStringLocalTime : SLTag +{ + public override string Tag => "TIME"; + + public override string GetValue(SLEntity value) + { + return value.ToLocalDateTime().ToString("HH:mm"); + } +} + +public class SLStringLocalDateTime : SLTag +{ + public override string Tag => "LDT"; + + public override string GetValue(SLEntity value) + { + return value.ToLocalDateTime().ToString("yyyy.MM.dd HH:mm"); + } +} + +public class SLStringAbsoluteNumber : SLTag +{ + public override string Tag => "ABS"; + + public override string GetValue(SLEntity value) + { + return string.Format("{0:N0}", Math.Abs(value)); + } +} + +public class SLStringNaturalNumber : SLTag +{ + public override string Tag => "NN"; + + public override string GetValue(SLEntity value) + { + return string.Format("{0:N0}", (double)value); + } +} + +public class SLStringSignNumber : SLTag +{ + public override string Tag => "SN"; + + public override string GetValue(SLEntity value) + { + if (value >= 0) + { + return string.Format("+{0:N0}", Math.Abs(value)); + } + else + { + return string.Format("-{0:N0}", Math.Abs(value)); + } + } +} + +public class SLStringSignNumberPercent : SLTag +{ + public override string Tag => "SNP"; + + public override string GetValue(SLEntity value) + { + if (value >= 0) + { + return string.Format("+{0:N0}%", Math.Abs(value * 100.0)); + } + else + { + return string.Format("-{0:N0}%", Math.Abs(value * 100.0)); + } + } +} + +public class SLStringToRoman : SLTag +{ + public override string Tag => "RM"; + + public override string GetValue(SLEntity value) + { + return value.RomanNumString(); + } +} + +public static class SLRomanNumber +{ + public static string RomanNumString(this SLEntity val) + { + return ((int)val).RomanNumString(); + } + + public static string RomanNumString(this int value) + { + if (value >= 1000) return "M" + RomanNumString(value - 1000); + if (value >= 900) return "CM" + RomanNumString(value - 900); + if (value >= 500) return "D" + RomanNumString(value - 500); + if (value >= 400) return "CD" + RomanNumString(value - 400); + if (value >= 100) return "C" + RomanNumString(value - 100); + if (value >= 90) return "XC" + RomanNumString(value - 90); + if (value >= 50) return "L" + RomanNumString(value - 50); + if (value >= 40) return "XL" + RomanNumString(value - 40); + if (value >= 10) return "X" + RomanNumString(value - 10); + if (value >= 9) return "IX" + RomanNumString(value - 9); + if (value >= 5) return "V" + RomanNumString(value - 5); + if (value >= 4) return "IV" + RomanNumString(value - 4); + if (value >= 1) return "I" + RomanNumString(value - 1); + return string.Empty; + } +} + +public class SLRound1Value : SLTag +{ + public override string Tag => "R1"; + + public override string GetValue(SLEntity value) + { + return MathF.Round(value, 1).ToString("0.0"); + } +} + +// Option + +public class LanguageName : SLTag +{ + public override string Tag => "LANGUAGENAME"; + + public override string GetValue(SLEntity value) + { + return SLSystem.Data["Languages"][value]["Name"]; + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLTag.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLTag.cs.meta new file mode 100644 index 000000000..150cd34b8 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLTag.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b8fe98f09de6ec4bbab6d80895e7724 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLUI.meta b/Assets/_Datas/SLShared/SLUnity/SLUI.meta new file mode 100644 index 000000000..00865da07 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLUI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e939ddad63a1cba41bf419ac88252083 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs b/Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs new file mode 100644 index 000000000..5c23cc9ad --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs @@ -0,0 +1,55 @@ +using UnityEngine; + +namespace Superlazy.UI +{ + public class SLUI2DPosition : SLUIComponent + { + public string bind = "Position"; + public bool screenPos = false; + + private RectTransform t; + private Canvas canvas; + private RectTransform parent; + + protected override void Validate() + { + t = GetComponent(); + parent = t.parent as RectTransform; + canvas = GetComponentInParent(); + } + + protected override void Init() + { + } + + protected override void Enable() + { + SLGame.AddNotify(bindParent.BindPath.CombinePath(bind), OnChange); + } + + protected override void Disable() + { + SLGame.RemoveNotify(bindParent.BindPath.CombinePath(bind), OnChange); + } + + private void OnChange() + { + if (bindParent.Active == false) + return; + + if (screenPos) + { + var pos = SLGame.SessionGet(bindParent.BindPath).Get(bind).ToVector2(); + + if (RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, pos, canvas.worldCamera, out var localPoint)) + { + t.anchoredPosition = localPoint; + } + } + else + { + t.anchoredPosition = SLGame.SessionGet(bindParent.BindPath).Get(bind).ToVector2(); + } + } + } +} \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs.meta b/Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs.meta new file mode 100644 index 000000000..c59af16d2 --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLUI/SLUI2DPosition.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b862e57d3f12cc94b88bdf98e9025af6 \ No newline at end of file diff --git a/Assets/_Datas/SLShared/SLUnity/SLUI/SLUIButton.cs b/Assets/_Datas/SLShared/SLUnity/SLUI/SLUIButton.cs new file mode 100644 index 000000000..55b25e4bf --- /dev/null +++ b/Assets/_Datas/SLShared/SLUnity/SLUI/SLUIButton.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TMPro; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace Superlazy.UI +{ + [RequireComponent(typeof(Button))] + public class SLUIButton : SLUIComponent, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, ISelectHandler, IDeselectHandler + { + public static Material grayMaterial; + public static Material grayTextMaterial; + public static DateTime globalClickedTime = DateTime.MinValue; + public static PointerEventData currentPoint; + + public string command; + public bool useGrayScale; + public bool focus; + public string focusBind; + public bool selectIsHover; + public bool fastClick; + public SLValueComparison comparison; + + public string clickSound = "SoundEffect/SE_UI_click"; + + [NonSerialized] + public Button button; + + private Graphic[] childImages; + private Color[] childImageColors; // TODO: SLUIColorSelect 대비해야함 + private TextMeshProUGUI[] childTexts; + private Color[] childTextColors; + private DateTime clickedTime = DateTime.MinValue; + + private enum ButtonState + { + Normal, + Highlighted, + Pressed, + Disabled + } + + private ButtonState currentButtonState; + + private bool isPointerInside; + private bool isPointerDown; + + protected override void Validate() + { + button = GetComponent