From c5bcf265d0aea25804869feec951aa7a1962abc9 Mon Sep 17 00:00:00 2001 From: dal4segno Date: Sat, 31 Jan 2026 20:49:23 +0900 Subject: [PATCH] =?UTF-8?q?=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20?= =?UTF-8?q?=EB=A9=80=ED=8B=B0=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=8C=80?= =?UTF-8?q?=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 7 - .gitignore | 6 +- Assembly-CSharp-Editor.csproj | 73 ++- Assembly-CSharp.csproj | 85 ++- Assets/Animations/Attack.anim | 9 +- Assets/Prefabs/Bat.prefab | 124 ++++ Assets/Prefabs/Bat.prefab.meta | 7 + Assets/Prefabs/Player.prefab | 149 ++++- Assets/Prefabs/Wall.prefab | 14 +- Assets/Resources.meta | 8 + Assets/Resources/Prefabs.meta | 8 + Assets/Resources/Prefabs/Bat.prefab | 124 ++++ Assets/Resources/Prefabs/Bat.prefab.meta | 7 + Assets/Resources/Prefabs/Pickaxe.prefab | 124 ++++ Assets/Resources/Prefabs/Pickaxe.prefab.meta | 7 + Assets/Scenes/GameMain.unity | 201 +++++- Assets/Scripts/AttackAction.cs | 58 +- Assets/Scripts/AttackAction.cs.meta | 2 +- Assets/Scripts/AutoHost.cs | 67 +- Assets/Scripts/AutoTargetSystem.cs | 2 +- Assets/Scripts/Building.cs | 391 ++++-------- Assets/Scripts/BuildingFoundation.cs | 262 ++++---- Assets/Scripts/BuildingManager.cs | 208 +++---- Assets/Scripts/BuildingPlacement.cs | 115 ++-- Assets/Scripts/Core.cs | 18 +- Assets/Scripts/DamageableNetworkBehaviour.cs | 162 +++++ .../DamageableNetworkBehaviour.cs.meta | 2 + Assets/Scripts/EnemyAIController.cs | 8 +- Assets/Scripts/EnemyUnit.cs | 40 +- Assets/Scripts/EquipmentSocket.cs | 176 ++++-- Assets/Scripts/FogOfWarSystem.cs | 4 +- Assets/Scripts/GameConstants.cs | 34 ++ Assets/Scripts/GameConstants.cs.meta | 2 + Assets/Scripts/GameResourceUI.cs | 2 +- Assets/Scripts/GlobalTimer.cs | 20 +- Assets/Scripts/InputActionManager.cs | 54 ++ Assets/Scripts/InputActionManager.cs.meta | 2 + Assets/Scripts/NetworkConnectionHandler.cs | 11 +- Assets/Scripts/NetworkManagerUI.cs.meta | 11 +- Assets/Scripts/NetworkPlayerController.cs | 572 +++++++++--------- .../Scripts/NetworkPlayerController.cs.meta | 11 +- Assets/Scripts/NetworkSpawnHelper.cs | 55 ++ Assets/Scripts/NetworkSpawnHelper.cs.meta | 2 + Assets/Scripts/NetworkSpawnManager.cs | 8 +- Assets/Scripts/PlayerActionSystem.cs | 41 +- Assets/Scripts/PlayerController.cs.meta | 11 +- Assets/Scripts/PlayerInteraction.cs | 223 +++---- Assets/Scripts/PlayerInventory.cs | 73 +++ Assets/Scripts/PlayerInventory.cs.meta | 2 + Assets/Scripts/PlayerResourceInventory.cs | 76 --- .../Scripts/PlayerResourceInventory.cs.meta | 2 - Assets/Scripts/PlayerSpawnPositionSetter.cs | 103 ++++ .../Scripts/PlayerSpawnPositionSetter.cs.meta | 2 + Assets/Scripts/Resource.cs | 14 +- Assets/Scripts/ResourcePickup.cs | 10 +- Assets/Scripts/SmartAutoHost.cs | 52 ++ Assets/Scripts/SmartAutoHost.cs.meta | 2 + Assets/Scripts/TeamGate.cs | 2 +- Assets/Scripts/TeamMemberNetworkBehaviour.cs | 62 ++ .../TeamMemberNetworkBehaviour.cs.meta | 2 + Assets/UserChoices.choices | 36 ++ Assets/UserChoices.choices.meta | 7 + ExternAttributes.Editor.csproj | 5 +- FlatKit.Utils.Editor.csproj | 5 +- Northbound.sln | 44 ++ Packages/manifest.json | 6 +- Packages/packages-lock.json | 111 +++- ProjectSettings/VirtualProjectsConfig.json | 4 + ...y.RenderPipelines.Universal.Runtime.csproj | 11 +- 69 files changed, 2766 insertions(+), 1392 deletions(-) delete mode 100644 .claude/settings.local.json create mode 100644 Assets/Prefabs/Bat.prefab create mode 100644 Assets/Prefabs/Bat.prefab.meta create mode 100644 Assets/Resources.meta create mode 100644 Assets/Resources/Prefabs.meta create mode 100644 Assets/Resources/Prefabs/Bat.prefab create mode 100644 Assets/Resources/Prefabs/Bat.prefab.meta create mode 100644 Assets/Resources/Prefabs/Pickaxe.prefab create mode 100644 Assets/Resources/Prefabs/Pickaxe.prefab.meta create mode 100644 Assets/Scripts/DamageableNetworkBehaviour.cs create mode 100644 Assets/Scripts/DamageableNetworkBehaviour.cs.meta create mode 100644 Assets/Scripts/GameConstants.cs create mode 100644 Assets/Scripts/GameConstants.cs.meta create mode 100644 Assets/Scripts/InputActionManager.cs create mode 100644 Assets/Scripts/InputActionManager.cs.meta create mode 100644 Assets/Scripts/NetworkSpawnHelper.cs create mode 100644 Assets/Scripts/NetworkSpawnHelper.cs.meta create mode 100644 Assets/Scripts/PlayerInventory.cs create mode 100644 Assets/Scripts/PlayerInventory.cs.meta delete mode 100644 Assets/Scripts/PlayerResourceInventory.cs delete mode 100644 Assets/Scripts/PlayerResourceInventory.cs.meta create mode 100644 Assets/Scripts/PlayerSpawnPositionSetter.cs create mode 100644 Assets/Scripts/PlayerSpawnPositionSetter.cs.meta create mode 100644 Assets/Scripts/SmartAutoHost.cs create mode 100644 Assets/Scripts/SmartAutoHost.cs.meta create mode 100644 Assets/Scripts/TeamMemberNetworkBehaviour.cs create mode 100644 Assets/Scripts/TeamMemberNetworkBehaviour.cs.meta create mode 100644 Assets/UserChoices.choices create mode 100644 Assets/UserChoices.choices.meta create mode 100644 Northbound.sln create mode 100644 ProjectSettings/VirtualProjectsConfig.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index d0df7d3..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(tree:*)" - ] - } -} diff --git a/.gitignore b/.gitignore index e23a945..e756a88 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,8 @@ [Uu]ser[Ss]ettings/ .vs -.vs/* \ No newline at end of file +.vs/* +.vscode +.claude +Assets/_Recovery +Assets/_Recovery.meta diff --git a/Assembly-CSharp-Editor.csproj b/Assembly-CSharp-Editor.csproj index 9241097..478f335 100644 --- a/Assembly-CSharp-Editor.csproj +++ b/Assembly-CSharp-Editor.csproj @@ -43,7 +43,6 @@ 6000.3.5f2 - @@ -621,6 +620,10 @@ C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll False + + Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll + False + Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll False @@ -1213,10 +1216,22 @@ Library\ScriptAssemblies\Unity.Multiplayer.Tools.MetricTestData.dll False + + Library\ScriptAssemblies\Unity.Services.QoS.dll + False + + + Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Shared.dll + False + Library\ScriptAssemblies\Unity.Rendering.LightTransport.Editor.dll False + + Library\ScriptAssemblies\Unity.Services.Core.dll + False + Library\ScriptAssemblies\Unity.Netcode.Runtime.dll False @@ -1245,14 +1260,14 @@ Library\ScriptAssemblies\PPv2URPConverters.dll False - - Library\ScriptAssemblies\Unity.2D.Common.Editor.dll - False - Library\ScriptAssemblies\Unity.AI.Navigation.Updater.dll False + + Library\ScriptAssemblies\Unity.2D.Common.Editor.dll + False + Library\ScriptAssemblies\Unity.AI.Navigation.Editor.dll False @@ -1277,6 +1292,10 @@ Library\ScriptAssemblies\Unity.RenderPipelines.Universal.2D.Runtime.dll False + + Library\ScriptAssemblies\Unity.Services.Core.Components.dll + False + Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Configuration.dll False @@ -1289,8 +1308,12 @@ Library\ScriptAssemblies\Unity.2D.Aseprite.Common.dll False - - Library\ScriptAssemblies\Unity.Rider.Editor.dll + + Library\ScriptAssemblies\Unity.Cursor.Editor.dll + False + + + Library\ScriptAssemblies\Unity.Services.Authentication.dll False @@ -1301,10 +1324,22 @@ Library\ScriptAssemblies\Unity.2D.Aseprite.Editor.dll False + + Library\ScriptAssemblies\Unity.Rider.Editor.dll + False + Library\ScriptAssemblies\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll False + + Library\ScriptAssemblies\Unity.Services.Authentication.Editor.Shared.dll + False + + + Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Matchmaker.Authoring.dll + False + Library\ScriptAssemblies\Unity.VisualScripting.Flow.dll False @@ -1317,6 +1352,10 @@ Library\ScriptAssemblies\Unity.2D.Tilemap.Extras.Editor.dll False + + Library\ScriptAssemblies\Unity.Services.Authentication.PlayerAccounts.dll + False + Library\ScriptAssemblies\Unity.VisualStudio.Editor.dll False @@ -1349,6 +1388,14 @@ Library\ScriptAssemblies\UnityEngine.UI.dll False + + Library\ScriptAssemblies\Unity.Services.Core.Environments.dll + False + + + Library\ScriptAssemblies\Unity.Services.Multiplayer.dll + False + Library\ScriptAssemblies\com.unity.multiplayer.tools.window.dll False @@ -1485,10 +1532,18 @@ Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetVis.Editor.Visualization.dll False + + Library\ScriptAssemblies\Unity.Services.Core.Analytics.dll + False + Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Editor.dll False + + Library\ScriptAssemblies\Unity.Multiplayer.Center.NetcodeForGameObjectsExample.dll + False + Library\ScriptAssemblies\Unity.Multiplayer.Tools.DependencyInjection.UIElements.dll False @@ -1553,6 +1608,10 @@ Library\ScriptAssemblies\Unity.Multiplayer.Center.Common.dll False + + Library\ScriptAssemblies\Unity.Multiplayer.Center.Integrations.dll + False + Library\ScriptAssemblies\Unity.Burst.dll False diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 3ee2004..1800786 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -43,16 +43,13 @@ 6000.3.5f2 - - - @@ -64,10 +61,12 @@ + + @@ -81,9 +80,12 @@ + + + + - @@ -93,15 +95,18 @@ + + + @@ -687,6 +692,10 @@ C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Managed\UnityEngine\UnityEditor.XRModule.dll False + + Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll + False + Library\PackageCache\com.unity.collections@aea9d3bd5e19\Unity.Collections.LowLevel.ILSupport\Unity.Collections.LowLevel.ILSupport.dll False @@ -1231,10 +1240,22 @@ Library\ScriptAssemblies\Unity.Multiplayer.Tools.MetricTestData.dll False + + Library\ScriptAssemblies\Unity.Services.QoS.dll + False + + + Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Shared.dll + False + Library\ScriptAssemblies\Unity.Rendering.LightTransport.Editor.dll False + + Library\ScriptAssemblies\Unity.Services.Core.dll + False + Library\ScriptAssemblies\Unity.Netcode.Runtime.dll False @@ -1263,14 +1284,14 @@ Library\ScriptAssemblies\PPv2URPConverters.dll False - - Library\ScriptAssemblies\Unity.2D.Common.Editor.dll - False - Library\ScriptAssemblies\Unity.AI.Navigation.Updater.dll False + + Library\ScriptAssemblies\Unity.2D.Common.Editor.dll + False + Library\ScriptAssemblies\Unity.AI.Navigation.Editor.dll False @@ -1295,6 +1316,10 @@ Library\ScriptAssemblies\Unity.RenderPipelines.Universal.2D.Runtime.dll False + + Library\ScriptAssemblies\Unity.Services.Core.Components.dll + False + Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Configuration.dll False @@ -1307,8 +1332,12 @@ Library\ScriptAssemblies\Unity.2D.Aseprite.Common.dll False - - Library\ScriptAssemblies\Unity.Rider.Editor.dll + + Library\ScriptAssemblies\Unity.Cursor.Editor.dll + False + + + Library\ScriptAssemblies\Unity.Services.Authentication.dll False @@ -1319,10 +1348,22 @@ Library\ScriptAssemblies\Unity.2D.Aseprite.Editor.dll False + + Library\ScriptAssemblies\Unity.Rider.Editor.dll + False + Library\ScriptAssemblies\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll False + + Library\ScriptAssemblies\Unity.Services.Authentication.Editor.Shared.dll + False + + + Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Matchmaker.Authoring.dll + False + Library\ScriptAssemblies\Unity.VisualScripting.Flow.dll False @@ -1335,6 +1376,10 @@ Library\ScriptAssemblies\Unity.2D.Tilemap.Extras.Editor.dll False + + Library\ScriptAssemblies\Unity.Services.Authentication.PlayerAccounts.dll + False + Library\ScriptAssemblies\Unity.VisualStudio.Editor.dll False @@ -1367,6 +1412,14 @@ Library\ScriptAssemblies\UnityEngine.UI.dll False + + Library\ScriptAssemblies\Unity.Services.Core.Environments.dll + False + + + Library\ScriptAssemblies\Unity.Services.Multiplayer.dll + False + Library\ScriptAssemblies\com.unity.multiplayer.tools.window.dll False @@ -1503,10 +1556,18 @@ Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetVis.Editor.Visualization.dll False + + Library\ScriptAssemblies\Unity.Services.Core.Analytics.dll + False + Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Editor.dll False + + Library\ScriptAssemblies\Unity.Multiplayer.Center.NetcodeForGameObjectsExample.dll + False + Library\ScriptAssemblies\Unity.Multiplayer.Tools.DependencyInjection.UIElements.dll False @@ -1571,6 +1632,10 @@ Library\ScriptAssemblies\Unity.Multiplayer.Center.Common.dll False + + Library\ScriptAssemblies\Unity.Multiplayer.Center.Integrations.dll + False + Library\ScriptAssemblies\Unity.Burst.dll False diff --git a/Assets/Animations/Attack.anim b/Assets/Animations/Attack.anim index e4caff3..c7d3275 100644 --- a/Assets/Animations/Attack.anim +++ b/Assets/Animations/Attack.anim @@ -27137,13 +27137,20 @@ AnimationClip: floatParameter: 0 intParameter: 0 messageOptions: 0 - - time: 1.0333333 + - time: 1 functionName: OnUnequipWeapon data: handslot.r objectReferenceParameter: {fileID: 0} floatParameter: 0 intParameter: 0 messageOptions: 0 + - time: 1.0333333 + functionName: OnAttackHit + data: + objectReferenceParameter: {fileID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 - time: 1.0666667 functionName: OnAttackComplete data: diff --git a/Assets/Prefabs/Bat.prefab b/Assets/Prefabs/Bat.prefab new file mode 100644 index 0000000..e470002 --- /dev/null +++ b/Assets/Prefabs/Bat.prefab @@ -0,0 +1,124 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6430526095172430446 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5247455493693100088} + - component: {fileID: 1386822053491278300} + - component: {fileID: 337360336842274253} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5247455493693100088 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6430526095172430446} + 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: [] + m_Father: {fileID: 6825883197719469110} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &1386822053491278300 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6430526095172430446} + m_Mesh: {fileID: 716655249603938094, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3} +--- !u!23 &337360336842274253 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6430526095172430446} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: d64c307f1b4197c44970c29f9845c245, 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: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &9113353226622644240 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6825883197719469110} + m_Layer: 0 + m_Name: Bat + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6825883197719469110 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9113353226622644240} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -5.09173, y: 2, z: -37.867} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 5247455493693100088} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Prefabs/Bat.prefab.meta b/Assets/Prefabs/Bat.prefab.meta new file mode 100644 index 0000000..05500b3 --- /dev/null +++ b/Assets/Prefabs/Bat.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 66fa238e7512e1547bf2ef4b62d76afd +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab index 4f292c3..705d609 100644 --- a/Assets/Prefabs/Player.prefab +++ b/Assets/Prefabs/Player.prefab @@ -10,16 +10,18 @@ GameObject: m_Component: - component: {fileID: 5887522270574905679} - component: {fileID: 2636831972010436653} - - component: {fileID: 3792365921352178844} + - component: {fileID: 5989504606015899400} - component: {fileID: 1698609800605343773} - component: {fileID: 3007098678582223509} - component: {fileID: 1883169379180791275} - component: {fileID: 8729870597719024730} - component: {fileID: 5217638038410020423} - - component: {fileID: 9062880697264624749} + - component: {fileID: 2753625045547947614} - component: {fileID: 6066313428661204362} - - component: {fileID: 2443072964133329520} - component: {fileID: 2148255267416253297} + - component: {fileID: 7256821725794344131} + - component: {fileID: 5045194683846919184} + - component: {fileID: 7246316046087380146} m_Layer: 9 m_Name: Player m_TagString: Untagged @@ -55,7 +57,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 4211758632 + GlobalObjectIdHash: 1360081626 InScenePlacedSourceGlobalObjectIdHash: 4211758632 DeferredDespawnTick: 0 Ownership: 1 @@ -68,7 +70,7 @@ MonoBehaviour: AutoObjectParentSync: 1 SyncOwnerTransformWhenParented: 1 AllowOwnerToParent: 0 ---- !u!114 &3792365921352178844 +--- !u!114 &5989504606015899400 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -77,15 +79,15 @@ MonoBehaviour: m_GameObject: {fileID: 1314983689436087486} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9e8f7d6c5b4a3d2e1f0a9b8c7d6e5f4a, type: 3} + m_Script: {fileID: 11500000, guid: 19b1385c0dcb77240b2cfb3c6b10f717, type: 3} m_Name: - m_EditorClassIdentifier: Assembly-CSharp::NetworkPlayerController + m_EditorClassIdentifier: Assembly-CSharp::Northbound.NetworkPlayerController ShowTopMostFoldoutHeaderGroup: 1 moveSpeed: 5 rotationSpeed: 10 initialTeam: 1 maxHealth: 100 - showHealthBar: 1 + showHealthBar: 0 damageEffectPrefab: {fileID: 0} deathEffectPrefab: {fileID: 0} --- !u!95 &1698609800605343773 @@ -175,7 +177,7 @@ MonoBehaviour: interactableLayer: serializedVersion: 2 m_Bits: 128 - rayOrigin: {fileID: 0} + rayOrigin: {fileID: 6366268612314379957} useForwardDirection: 1 playAnimations: 1 useAnimationEvents: 1 @@ -196,9 +198,9 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerActionSystem ShowTopMostFoldoutHeaderGroup: 1 actionComponents: - - {fileID: 9062880697264624749} + - {fileID: 2753625045547947614} playAnimations: 1 ---- !u!114 &9062880697264624749 +--- !u!114 &2753625045547947614 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -207,23 +209,23 @@ MonoBehaviour: m_GameObject: {fileID: 1314983689436087486} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 66ec3984614d8a64b8eae821376d038d, type: 3} + m_Script: {fileID: 11500000, guid: c467f66382052bc429affc5cb01d17db, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::Northbound.AttackAction ShowTopMostFoldoutHeaderGroup: 1 - attackRange: 3 - attackDamage: 100 + attackRange: 2 + attackDamage: 10 attackCooldown: 0.5 attackableLayer: serializedVersion: 2 - m_Bits: 4294967295 + m_Bits: 256 attackAnimationTrigger: Attack useAnimationEvents: 1 blockDuringAnimation: 1 useEquipment: 1 equipmentData: socketName: handslot.r - equipmentPrefab: {fileID: 919132149155446097, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3} + equipmentPrefab: {fileID: 9113353226622644240, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} attachOnStart: 1 detachOnEnd: 1 keepEquipped: 0 @@ -243,24 +245,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ac908541bf903c745a1794d409a5f048, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::Northbound.EquipmentSocket + ShowTopMostFoldoutHeaderGroup: 1 sockets: - socketName: handslot.r socketTransform: {fileID: 2844947653216056832} currentEquipment: {fileID: 0} ---- !u!114 &2443072964133329520 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1314983689436087486} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3c64072402b0a3f46a674eb73c5541ac, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerResourceInventory - ShowTopMostFoldoutHeaderGroup: 1 - maxResourceCapacity: 50 + equipmentPrefabs: + - {fileID: 9113353226622644240, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + - {fileID: 7492862814627084760, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} --- !u!114 &2148255267416253297 MonoBehaviour: m_ObjectHideFlags: 0 @@ -275,6 +267,98 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerVisionProvider ShowTopMostFoldoutHeaderGroup: 1 visionRange: 10 +--- !u!114 &7256821725794344131 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1314983689436087486} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkTransform + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AutoOwnerAuthorityTickOffset: 1 + PositionInterpolationType: 0 + RotationInterpolationType: 0 + ScaleInterpolationType: 0 + PositionLerpSmoothing: 1 + PositionMaxInterpolationTime: 0.1 + RotationLerpSmoothing: 1 + RotationMaxInterpolationTime: 0.1 + ScaleLerpSmoothing: 1 + ScaleMaxInterpolationTime: 0.1 + AuthorityMode: 0 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 0 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 0 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 0 +--- !u!114 &5045194683846919184 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1314983689436087486} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkAnimator + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 0 + m_Animator: {fileID: 1698609800605343773} + TransitionStateInfoList: [] + AnimatorParameterEntries: + ParameterEntries: + - name: MoveSpeed + NameHash: 526065662 + Synchronize: 1 + ParameterType: 1 + - name: Mining + NameHash: 577859424 + Synchronize: 1 + ParameterType: 9 + - name: Attack + NameHash: 1080829965 + Synchronize: 1 + ParameterType: 9 + AnimatorParametersExpanded: 0 +--- !u!114 &7246316046087380146 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1314983689436087486} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 90231c209cbef84469511de397004be9, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerInventory + ShowTopMostFoldoutHeaderGroup: 1 + maxResourceCapacity: 100 --- !u!1001 &1445453803682481668 PrefabInstance: m_ObjectHideFlags: 0 @@ -502,3 +586,8 @@ Transform: m_CorrespondingSourceObject: {fileID: -5515783359193845756, guid: 4652a9058e767d142b3e889c2983fa9a, type: 3} m_PrefabInstance: {fileID: 1445453803682481668} m_PrefabAsset: {fileID: 0} +--- !u!4 &6366268612314379957 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 5500811601672728753, guid: 4652a9058e767d142b3e889c2983fa9a, type: 3} + m_PrefabInstance: {fileID: 1445453803682481668} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/Prefabs/Wall.prefab b/Assets/Prefabs/Wall.prefab index 3655c3f..5d12691 100644 --- a/Assets/Prefabs/Wall.prefab +++ b/Assets/Prefabs/Wall.prefab @@ -86,7 +86,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 1061286994 + GlobalObjectIdHash: 1574233340 InScenePlacedSourceGlobalObjectIdHash: 1061286994 DeferredDespawnTick: 0 Ownership: 1 @@ -112,17 +112,18 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::Northbound.Building ShowTopMostFoldoutHeaderGroup: 1 + maxHealth: 100 + showHealthBar: 1 + damageEffectPrefab: {fileID: 0} + destroyEffectPrefab: {fileID: 0} + effectSpawnPoint: {fileID: 0} buildingData: {fileID: 11400000, guid: 0e495d169ee3bce449f4b1aea83d6818, type: 2} gridPosition: {x: 0, y: 0, z: 0} rotation: 0 initialTeam: 1 initialOwnerId: 1 useInitialOwner: 1 - showHealthBar: 1 healthBarPrefab: {fileID: 0} - destroyEffectPrefab: {fileID: 0} - damageEffectPrefab: {fileID: 0} - effectSpawnPoint: {fileID: 0} showGridBounds: 1 gridBoundsColor: {r: 0, g: 1, b: 1, a: 1} --- !u!114 &9023294375343742146 @@ -140,6 +141,9 @@ MonoBehaviour: showInExploredAreas: 1 updateInterval: 0.2 renderers: [] + enableDistantVisibility: 1 + heightVisibilityMultiplier: 2 + minHeightForDistantVisibility: 3 useExploredMaterial: 0 exploredMaterial: {fileID: 0} --- !u!1001 &8926581783111832504 diff --git a/Assets/Resources.meta b/Assets/Resources.meta new file mode 100644 index 0000000..5d91ef4 --- /dev/null +++ b/Assets/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0aef2ea1f5e3aed4d839d26090fcb09c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Prefabs.meta b/Assets/Resources/Prefabs.meta new file mode 100644 index 0000000..454c45f --- /dev/null +++ b/Assets/Resources/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d69632731200bd4a86a369c95ee485b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Prefabs/Bat.prefab b/Assets/Resources/Prefabs/Bat.prefab new file mode 100644 index 0000000..0a46575 --- /dev/null +++ b/Assets/Resources/Prefabs/Bat.prefab @@ -0,0 +1,124 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6430526095172430446 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5247455493693100088} + - component: {fileID: 1386822053491278300} + - component: {fileID: 337360336842274253} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5247455493693100088 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6430526095172430446} + 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: [] + m_Father: {fileID: 6825883197719469110} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &1386822053491278300 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6430526095172430446} + m_Mesh: {fileID: 716655249603938094, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3} +--- !u!23 &337360336842274253 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6430526095172430446} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 726c1e9087356f74594664341c681f12, 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: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &9113353226622644240 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6825883197719469110} + m_Layer: 0 + m_Name: Bat + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6825883197719469110 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9113353226622644240} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -5.09173, y: 2, z: -37.867} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 5247455493693100088} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Resources/Prefabs/Bat.prefab.meta b/Assets/Resources/Prefabs/Bat.prefab.meta new file mode 100644 index 0000000..aea299c --- /dev/null +++ b/Assets/Resources/Prefabs/Bat.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 63b758cca5ef327449b0debcea47b006 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Prefabs/Pickaxe.prefab b/Assets/Resources/Prefabs/Pickaxe.prefab new file mode 100644 index 0000000..87cb517 --- /dev/null +++ b/Assets/Resources/Prefabs/Pickaxe.prefab @@ -0,0 +1,124 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &7492862814627084760 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3311562868863992538} + m_Layer: 0 + m_Name: Pickaxe + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3311562868863992538 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7492862814627084760} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -4.36068, y: 2, z: -36.9} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3026504303214829544} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &8843044757232415404 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3026504303214829544} + - component: {fileID: 5528544188432120844} + - component: {fileID: 8380855244601599694} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3026504303214829544 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8843044757232415404} + 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: [] + m_Father: {fileID: 3311562868863992538} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5528544188432120844 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8843044757232415404} + m_Mesh: {fileID: 1616382711435183797, guid: 804d477fc7f114c498aa6f95452be893, type: 3} +--- !u!23 &8380855244601599694 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8843044757232415404} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: d64c307f1b4197c44970c29f9845c245, 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: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} diff --git a/Assets/Resources/Prefabs/Pickaxe.prefab.meta b/Assets/Resources/Prefabs/Pickaxe.prefab.meta new file mode 100644 index 0000000..498570b --- /dev/null +++ b/Assets/Resources/Prefabs/Pickaxe.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4e920eca75dd2f44685f2be6a29e8b43 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/GameMain.unity b/Assets/Scenes/GameMain.unity index 5249b3a..6a0798e 100644 --- a/Assets/Scenes/GameMain.unity +++ b/Assets/Scenes/GameMain.unity @@ -119,6 +119,80 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &48181808 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 48181811} + - component: {fileID: 48181809} + - component: {fileID: 48181810} + m_Layer: 0 + m_Name: PlayerSpawnPositionSetter + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &48181809 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 48181808} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject + GlobalObjectIdHash: 2243665744 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 0 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!114 &48181810 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 48181808} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 49cd9c4e7c611b04c8740c9e049129b9, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerSpawnPositionSetter + ShowTopMostFoldoutHeaderGroup: 1 + spawnPoints: [] + useRandomSpawn: 0 + findSpawnPointsAutomatically: 1 +--- !u!4 &48181811 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 48181808} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.03658, y: 1.00002, z: -4.65261} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &61373298 GameObject: m_ObjectHideFlags: 0 @@ -2102,7 +2176,7 @@ MonoBehaviour: LoadSceneTimeOut: 120 SpawnTimeout: 10 EnableNetworkLogs: 1 - NetworkTopology: 0 + NetworkTopology: 1 UseCMBService: 0 AutoSpawnPlayerPrefabClientSide: 1 NetworkMessageMetrics: 1 @@ -2440,12 +2514,12 @@ MonoBehaviour: spawnWeight: 10 minCount: 0 maxCount: 228 - density: 0.02 + density: 0.04 maxTotalObstacles: 6400 minDistanceBetweenObstacles: 4 alignToTerrain: 1 randomRotation: 1 - scaleVariation: 0.197 + scaleVariation: 0.2 maxAttempts: 50 checkCollision: 1 collisionLayers: @@ -3042,6 +3116,63 @@ Transform: m_CorrespondingSourceObject: {fileID: 922888705413710451, guid: 5662d0b0d0eb5f54290edd8dd0980b57, type: 3} m_PrefabInstance: {fileID: 2098115307} m_PrefabAsset: {fileID: 0} +--- !u!1001 &957391555319539118 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalPosition.x + value: -5.09173 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalPosition.y + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalPosition.z + value: -37.867 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6825883197719469110, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 9113353226622644240, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} + propertyPath: m_Name + value: Bat + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 66fa238e7512e1547bf2ef4b62d76afd, type: 3} --- !u!1001 &1440648431994998967 PrefabInstance: m_ObjectHideFlags: 0 @@ -3383,6 +3514,10 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: + - target: {fileID: -7869551286978933574, guid: f395fcc064a3a834ba957327f1387c19, type: 3} + propertyPath: equipmentData.equipmentPrefab + value: + objectReference: {fileID: 7492862814627084760, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} - target: {fileID: 3247786716306397435, guid: f395fcc064a3a834ba957327f1387c19, type: 3} propertyPath: m_LocalPosition.x value: -30 @@ -3502,6 +3637,63 @@ Transform: m_CorrespondingSourceObject: {fileID: 5749230937810543840, guid: 88f7f1e8a019b674498ab5fd494c1d34, type: 3} m_PrefabInstance: {fileID: 6204924723497734287} m_PrefabAsset: {fileID: 0} +--- !u!1001 &6296397022839506560 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalPosition.x + value: -4.36068 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalPosition.y + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalPosition.z + value: -36.9 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3311562868863992538, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7492862814627084760, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} + propertyPath: m_Name + value: Pickaxe + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 4e920eca75dd2f44685f2be6a29e8b43, type: 3} --- !u!114 &8940572951313384066 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 1287070985890992582, guid: e56926eda34629f4fbf3e4c53f0f8bd4, type: 3} @@ -3603,3 +3795,6 @@ SceneRoots: - {fileID: 1442785555} - {fileID: 1440648431994998967} - {fileID: 1061936651} + - {fileID: 48181811} + - {fileID: 957391555319539118} + - {fileID: 6296397022839506560} diff --git a/Assets/Scripts/AttackAction.cs b/Assets/Scripts/AttackAction.cs index 6c8f5fd..c524cd0 100644 --- a/Assets/Scripts/AttackAction.cs +++ b/Assets/Scripts/AttackAction.cs @@ -31,8 +31,14 @@ namespace Northbound private Animator _animator; private ITeamMember _teamMember; private EquipmentSocket _equipmentSocket; - private bool _isAttacking = false; + private NetworkVariable _isAttacking = new NetworkVariable( + false, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Owner + ); private bool _isWeaponEquipped = false; + private float _attackStartTime; + private const float ATTACK_TIMEOUT = 2f; // Auto-reset if animation event doesn't fire private void Awake() { @@ -43,7 +49,7 @@ namespace Northbound public bool CanExecute(ulong playerId) { - if (blockDuringAnimation && _isAttacking) + if (blockDuringAnimation && _isAttacking.Value) return false; return Time.time - _lastAttackTime >= attackCooldown; @@ -55,7 +61,10 @@ namespace Northbound return; _lastAttackTime = Time.time; - _isAttacking = true; + _attackStartTime = Time.time; + + // Owner writes directly (Owner permission on _isAttacking) + _isAttacking.Value = true; // 장비 장착 (애니메이션 이벤트 사용 안 할 경우) if (!useAnimationEvents && useEquipment && !_isWeaponEquipped) @@ -73,7 +82,7 @@ namespace Northbound if (_animator == null || string.IsNullOrEmpty(attackAnimationTrigger)) { PerformAttack(); - _isAttacking = false; + _isAttacking.Value = false; } } @@ -168,7 +177,10 @@ namespace Northbound public void OnAttackComplete() { - _isAttacking = false; + if (IsOwner) + { + _isAttacking.Value = false; + } if (useEquipment && equipmentData != null && equipmentData.detachOnEnd && !equipmentData.keepEquipped) { @@ -183,16 +195,10 @@ namespace Northbound private void AttachWeapon(string socketName = null) { if (_equipmentSocket == null || equipmentData == null) - { - Debug.LogWarning("[AttackAction] EquipmentSocket 또는 EquipmentData가 없습니다."); return; - } if (equipmentData.equipmentPrefab == null) - { - Debug.LogWarning("[AttackAction] 무기 프리팹이 설정되지 않았습니다."); return; - } string socket = socketName ?? equipmentData.socketName; _equipmentSocket.AttachToSocket(socket, equipmentData.equipmentPrefab); @@ -251,6 +257,34 @@ namespace Northbound Gizmos.DrawWireSphere(attackOrigin, attackRange); } + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + // Subscribe to attack state changes + _isAttacking.OnValueChanged += OnAttackStateChanged; + } + + public override void OnNetworkDespawn() + { + _isAttacking.OnValueChanged -= OnAttackStateChanged; + base.OnNetworkDespawn(); + } + + private void OnAttackStateChanged(bool previousValue, bool newValue) + { + } + + public void Update() + { + if (!IsOwner) return; + + if (_isAttacking.Value && Time.time - _attackStartTime > ATTACK_TIMEOUT) + { + _isAttacking.Value = false; + } + } + public override void OnDestroy() { // 무기 정리 @@ -262,6 +296,6 @@ namespace Northbound base.OnDestroy(); } - public bool IsAttacking => _isAttacking; + public bool IsAttacking => _isAttacking.Value; } } \ No newline at end of file diff --git a/Assets/Scripts/AttackAction.cs.meta b/Assets/Scripts/AttackAction.cs.meta index fcc99fe..8a9c8bd 100644 --- a/Assets/Scripts/AttackAction.cs.meta +++ b/Assets/Scripts/AttackAction.cs.meta @@ -1,2 +1,2 @@ fileFormatVersion: 2 -guid: 66ec3984614d8a64b8eae821376d038d \ No newline at end of file +guid: c467f66382052bc429affc5cb01d17db \ No newline at end of file diff --git a/Assets/Scripts/AutoHost.cs b/Assets/Scripts/AutoHost.cs index 8f51ca7..17d616a 100644 --- a/Assets/Scripts/AutoHost.cs +++ b/Assets/Scripts/AutoHost.cs @@ -3,24 +3,63 @@ using Unity.Netcode; public class AutoHost : MonoBehaviour { - // 에디터에서만 작동하도록 설정 +#if UNITY_EDITOR + private bool _hasStarted = false; + void Start() { -#if UNITY_EDITOR - // 1. NetworkManager가 씬에 존재하는지 확인 - if (NetworkManager.Singleton != null) - { - // 2. 이미 서버나 클라이언트가 실행 중이 아닐 때만 실행 - if (!NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient) - { - NetworkManager.Singleton.StartHost(); - Debug.Log("[AutoHost] 에디터 전용 호스트 자동 시작됨"); - } - } - else + if (NetworkManager.Singleton == null) { Debug.LogError("[AutoHost] NetworkManager를 찾을 수 없습니다!"); + return; } -#endif + + if (NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsClient) + { + return; + } + + TryStartAsHost(); } + + private void TryStartAsHost() + { + try + { + NetworkManager.Singleton.StartHost(); + _hasStarted = true; + Debug.Log("[AutoHost] 호스트로 시작됨 (MAIN EDITOR)"); + } + catch (System.Exception e) + { + Debug.Log($"[AutoHost] 호스트 시작 실패: {e.Message}"); + Debug.Log("[AutoHost] 클라이언트 모드로 전환..."); + + NetworkManager.Singleton.Shutdown(); + TryStartAsClient(); + } + } + + private void TryStartAsClient() + { + try + { + NetworkManager.Singleton.StartClient(); + _hasStarted = true; + Debug.Log("[AutoHost] 클라이언트로 연결됨 (SECONDARY EDITOR)"); + } + catch (System.Exception e) + { + Debug.LogError($"[AutoHost] 클라이언트 연결 실패: {e.Message}"); + } + } + + private void OnDestroy() + { + if (NetworkManager.Singleton != null) + { + NetworkManager.Singleton.Shutdown(); + } + } +#endif } \ No newline at end of file diff --git a/Assets/Scripts/AutoTargetSystem.cs b/Assets/Scripts/AutoTargetSystem.cs index dccb706..6c0e06d 100644 --- a/Assets/Scripts/AutoTargetSystem.cs +++ b/Assets/Scripts/AutoTargetSystem.cs @@ -44,7 +44,7 @@ namespace Northbound private void Update() { - if (!IsServer) return; + if (!IsOwner) return; if (_teamMember == null) return; if (Time.time - _lastAttackTime >= attackInterval) diff --git a/Assets/Scripts/Building.cs b/Assets/Scripts/Building.cs index 279f776..7f32ca6 100644 --- a/Assets/Scripts/Building.cs +++ b/Assets/Scripts/Building.cs @@ -4,9 +4,9 @@ using UnityEngine; namespace Northbound { - public class Building : NetworkBehaviour, IDamageable, IVisionProvider, ITeamMember + public class Building : DamageableNetworkBehaviour, IVisionProvider, ITeamMember { - [Header("References")] + [Header("Building Data")] public BuildingData buildingData; [Header("Runtime Info")] @@ -14,52 +14,34 @@ namespace Northbound public int rotation; // 0-3 (0=0°, 1=90°, 2=180°, 3=270°) [Header("Team")] - [Tooltip("건물의 팀 (플레이어/적대세력/몬스터/중립)")] + [Tooltip("Building team (Player/Enemy/Monster/Neutral)")] public TeamType initialTeam = TeamType.Player; [Header("Ownership (for pre-placed buildings)")] - [Tooltip("씬에 미리 배치된 건물의 경우 여기서 소유자 설정 (0 = 중립, 1+ = 플레이어 ID)")] + [Tooltip("For pre-placed buildings, set owner here (0 = neutral, 1+ = player ID)")] public ulong initialOwnerId = 0; - [Tooltip("사전 배치 건물인가요? 체크하면 initialOwnerId를 사용합니다")] + [Tooltip("Is this a pre-placed building? If checked, uses initialOwnerId")] public bool useInitialOwner = false; - [Header("Health")] - public bool showHealthBar = true; + [Header("Health Bar")] public GameObject healthBarPrefab; - [Header("Visual Effects")] - public GameObject destroyEffectPrefab; - public GameObject damageEffectPrefab; - public Transform effectSpawnPoint; - [Header("Debug")] public bool showGridBounds = true; public Color gridBoundsColor = Color.cyan; - // 현재 체력 - private NetworkVariable _currentHealth = new NetworkVariable( - 0, - NetworkVariableReadPermission.Everyone, - NetworkVariableWritePermission.Server - ); - - // 건물 소유자 (사전 배치 건물 또는 동적 건물 모두 지원) private NetworkVariable _ownerId = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); - // 건물 팀 private NetworkVariable _team = new NetworkVariable( TeamType.Neutral, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); - // 이벤트 - public event Action OnHealthChanged; // (current, max) - public event Action OnDestroyed; public event Action OnTeamChanged; private BuildingHealthBar _healthBar; @@ -70,63 +52,54 @@ namespace Northbound { base.OnNetworkSpawn(); - if (IsServer) + if (IsOwner) { - // 체력 초기화 - if (_currentHealth.Value == 0) + if (maxHealth == 0) { - _currentHealth.Value = buildingData != null ? buildingData.maxHealth : 100; + maxHealth = buildingData != null ? buildingData.maxHealth : 100; + _currentHealth.Value = maxHealth; } - // 팀 초기화 if (_team.Value == TeamType.Neutral) { _team.Value = initialTeam; } - // 소유자 초기화 (사전 배치 건물 체크) if (useInitialOwner && _ownerId.Value == 0) { _ownerId.Value = initialOwnerId; - // Debug.Log($"[Building] 사전 배치 건물 '{buildingData?.buildingName ?? gameObject.name}' 소유자: {initialOwnerId}, 팀: {_team.Value}"); } else if (!useInitialOwner && _ownerId.Value == 0) { - // 동적 생성 건물은 NetworkObject의 Owner 사용 _ownerId.Value = OwnerClientId; } - + _lastRegenTime = Time.time; - // FogOfWar 시스템에 시야 제공자로 등록 if (buildingData != null && buildingData.providesVision) { FogOfWarSystem.Instance?.RegisterVisionProvider(this); } } - // 이벤트 구독 - _currentHealth.OnValueChanged += OnHealthValueChanged; _team.OnValueChanged += OnTeamValueChanged; - // 체력바 생성 if (showHealthBar && healthBarPrefab != null) { - CreateHealthBar(); + base.InitializeHealthBar(); } - // 초기 체력 UI 업데이트 UpdateHealthUI(); UpdateTeamVisuals(); } public override void OnNetworkDespawn() { - _currentHealth.OnValueChanged -= OnHealthValueChanged; + base.OnNetworkDespawn(); + _team.OnValueChanged -= OnTeamValueChanged; - // FogOfWar 시스템에서 제거 - if (IsServer && buildingData != null && buildingData.providesVision) + if (IsOwner && buildingData != null && buildingData.providesVision) { FogOfWarSystem.Instance?.UnregisterVisionProvider(this); } @@ -134,38 +107,33 @@ namespace Northbound private void Update() { - if (!IsServer || buildingData == null) + if (!IsOwner || buildingData == null) return; - // 자동 체력 회복 - if (buildingData.autoRegenerate && _currentHealth.Value < buildingData.maxHealth) + if (buildingData.autoRegenerate && _currentHealth.Value < maxHealth) { if (Time.time - _lastRegenTime >= 1f) { - int regenAmount = Mathf.Min(buildingData.regenPerSecond, buildingData.maxHealth - _currentHealth.Value); + int regenAmount = Mathf.Min(buildingData.regenPerSecond, maxHealth - _currentHealth.Value); _currentHealth.Value += regenAmount; _lastRegenTime = Time.time; } } } - /// - /// 건물 초기화 (BuildingManager가 동적 생성 시 호출) - /// public void Initialize(BuildingData data, Vector3Int gridPos, int rot, ulong ownerId, TeamType team = TeamType.Player) { buildingData = data; gridPosition = gridPos; rotation = rot; - // 이미 스폰된 경우 - if (IsServer && IsSpawned) + if (IsOwner && IsSpawned) { - _currentHealth.Value = data.maxHealth; + maxHealth = data.maxHealth; + _currentHealth.Value = maxHealth; _ownerId.Value = ownerId; _team.Value = team; - - // 시야 제공자 등록 + if (data.providesVision) { FogOfWarSystem.Instance?.RegisterVisionProvider(this); @@ -175,22 +143,28 @@ namespace Northbound _isInitialized = true; } - /// - /// 건물 소유권 변경 (점령 등) - /// public void SetOwner(ulong newOwnerId, TeamType newTeam) { - if (!IsServer) return; + if (!IsOwner) + { + SetOwnerServerRpc(newOwnerId, newTeam); + return; + } + SetOwnerServerRpc(newOwnerId, newTeam); + } + + [ServerRpc] + private void SetOwnerServerRpc(ulong newOwnerId, TeamType newTeam) + { ulong previousOwner = _ownerId.Value; TeamType previousTeam = _team.Value; - + _ownerId.Value = newOwnerId; _team.Value = newTeam; - Debug.Log($"[Building] {buildingData?.buildingName ?? "건물"} 소유권 변경: {previousOwner} → {newOwnerId}, 팀: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(newTeam)}"); + Debug.Log($"[Building] {buildingData?.buildingName ?? "Building"} ownership changed: {previousOwner} → {newOwnerId}, team: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(newTeam)}"); - // 시야 제공자 재등록 (소유자가 바뀌었으므로) if (buildingData != null && buildingData.providesVision) { FogOfWarSystem.Instance?.UnregisterVisionProvider(this); @@ -204,7 +178,18 @@ namespace Northbound public void SetTeam(TeamType team) { - if (!IsServer) return; + if (!IsOwner) + { + SetTeamServerRpc(team); + return; + } + + SetTeamServerRpc(team); + } + + [ServerRpc] + private void SetTeamServerRpc(TeamType team) + { _team.Value = team; } @@ -212,17 +197,12 @@ namespace Northbound { OnTeamChanged?.Invoke(newValue); UpdateTeamVisuals(); - Debug.Log($"[Building] {buildingData?.buildingName ?? "건물"} 팀 변경: {TeamManager.GetTeamName(previousValue)} → {TeamManager.GetTeamName(newValue)}"); + Debug.Log($"[Building] {buildingData?.buildingName ?? "Building"} team changed: {TeamManager.GetTeamName(previousValue)} → {TeamManager.GetTeamName(newValue)}"); } private void UpdateTeamVisuals() { - // 팀 색상으로 건물 외곽선이나 이펙트 변경 가능 - // 예: Renderer의 emission 색상 변경 Color teamColor = TeamManager.GetTeamColor(_team.Value); - - // 여기에 실제 비주얼 업데이트 로직 추가 - // 예: outline shader, emission, particle system 색상 등 } #endregion @@ -240,65 +220,85 @@ namespace Northbound public bool IsActive() { - // 건물이 스폰되어 있고, 파괴되지 않았으며, 시야 제공 설정이 켜져있어야 함 - return IsSpawned && !IsDestroyed() && buildingData != null && buildingData.providesVision; + return IsSpawned && !IsDead() && buildingData != null && buildingData.providesVision; } #endregion - #region IDamageable Implementation + #region IDamageable Overrides - public void TakeDamage(int damage, ulong attackerId) + protected override void Die(ulong killerId) { - if (!IsServer) + base.Die(killerId); + + if (!IsOwner) return; + + Debug.Log($"[Building] {buildingData?.buildingName ?? "Building"} ({TeamManager.GetTeamName(_team.Value)}) destroyed! Attacker: {killerId}"); + + InvokeOnDestroyed(); + NotifyDestroyedClientRpc(); + + if (buildingData != null && buildingData.providesVision) { - // 클라이언트는 서버에 요청 - TakeDamageServerRpc(damage, attackerId); + FogOfWarSystem.Instance?.UnregisterVisionProvider(this); + } + + if (BuildingManager.Instance != null) + { + BuildingManager.Instance.RemoveBuilding(this); + } + + Invoke(nameof(DespawnBuilding), 0.5f); + } + + private void DespawnBuilding() + { + if (IsOwner && NetworkObject != null) + { + NetworkObject.Despawn(true); + } + } + + [ClientRpc] + private void NotifyDestroyedClientRpc() + { + if (!IsOwner) + { + InvokeOnDestroyed(); + } + } + + public override void TakeDamage(int damage, ulong attackerId) + { + if (!IsOwner) + { + TakeDamageOwnerRpc(damage, attackerId); return; } - // 무적 건물 if (buildingData != null && buildingData.isIndestructible) { - Debug.Log($"[Building] {buildingData.buildingName}은(는) 무적입니다."); + Debug.Log($"[Building] {buildingData.buildingName} is indestructible."); return; } - // 이미 파괴됨 - if (_currentHealth.Value <= 0) - return; - - // 공격자의 팀 확인 (팀 공격 방지) var attackerObj = NetworkManager.Singleton.SpawnManager.SpawnedObjects[attackerId]; var attackerTeamMember = attackerObj?.GetComponent(); - + if (attackerTeamMember != null) { if (!TeamManager.CanAttack(attackerTeamMember, this)) { - Debug.Log($"[Building] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다."); + Debug.Log($"[Building] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} team cannot attack {TeamManager.GetTeamName(_team.Value)} team."); return; } } - // 데미지 적용 - int actualDamage = Mathf.Min(damage, _currentHealth.Value); - _currentHealth.Value -= actualDamage; - - Debug.Log($"[Building] {buildingData?.buildingName ?? "건물"} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{buildingData?.maxHealth ?? 100}"); - - // 데미지 이펙트 - ShowDamageEffectClientRpc(); - - // 체력이 0이 되면 파괴 - if (_currentHealth.Value <= 0) - { - DestroyBuilding(attackerId); - } + base.TakeDamage(damage, attackerId); } - [Rpc(SendTo.Server)] - private void TakeDamageServerRpc(int damage, ulong attackerId) + [Rpc(SendTo.Owner)] + private void TakeDamageOwnerRpc(int damage, ulong attackerId) { TakeDamage(damage, attackerId); } @@ -307,179 +307,63 @@ namespace Northbound #region Health Management - /// - /// 건물 파괴 - /// - private void DestroyBuilding(ulong attackerId) + public new int GetMaxHealth() { - if (!IsServer) - return; - - Debug.Log($"[Building] {buildingData?.buildingName ?? "건물"} ({TeamManager.GetTeamName(_team.Value)})이(가) 파괴되었습니다! (공격자: {attackerId})"); - - // 파괴 이벤트 발생 - OnDestroyed?.Invoke(); - NotifyDestroyedClientRpc(); - - // 파괴 이펙트 - ShowDestroyEffectClientRpc(); - - // FogOfWar 시스템에서 제거 - if (buildingData != null && buildingData.providesVision) - { - FogOfWarSystem.Instance?.UnregisterVisionProvider(this); - } - - // BuildingManager에서 제거 - if (BuildingManager.Instance != null) - { - BuildingManager.Instance.RemoveBuilding(this); - } - - // 네트워크 오브젝트 파괴 (약간의 딜레이) - Invoke(nameof(DespawnBuilding), 0.5f); + return buildingData != null ? buildingData.maxHealth : 100; } - private void DespawnBuilding() + public new float GetHealthPercentage() { - if (IsServer && NetworkObject != null) - { - NetworkObject.Despawn(true); - } + int maxHp = GetMaxHealth(); + return maxHp > 0 ? (float)_currentHealth.Value / maxHp : 0f; } - /// - /// 체력 회복 - /// - public void Heal(int amount) - { - if (!IsServer) - return; - - if (buildingData == null) - return; - - int healAmount = Mathf.Min(amount, buildingData.maxHealth - _currentHealth.Value); - _currentHealth.Value += healAmount; - - Debug.Log($"[Building] {buildingData.buildingName}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{buildingData.maxHealth}"); - } - - /// - /// 현재 체력 - /// - public int GetCurrentHealth() => _currentHealth.Value; - - /// - /// 최대 체력 - /// - public int GetMaxHealth() => buildingData != null ? buildingData.maxHealth : 100; - - /// - /// 체력 비율 (0.0 ~ 1.0) - /// - public float GetHealthPercentage() - { - int maxHealth = GetMaxHealth(); - return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f; - } - - /// - /// 파괴되었는지 여부 - /// public bool IsDestroyed() => _currentHealth.Value <= 0; #endregion #region Health UI - private void CreateHealthBar() + protected override void InitializeHealthBar() { if (_healthBar != null) return; - GameObject healthBarObj = Instantiate(healthBarPrefab, transform); - _healthBar = healthBarObj.GetComponent(); - - if (_healthBar != null) + if (healthBarPrefab != null) { - _healthBar.Initialize(this); + GameObject healthBarObj = Instantiate(healthBarPrefab, transform); + _healthBar = healthBarObj.GetComponent(); + + if (_healthBar != null) + { + _healthBar.Initialize(this); + } } } - private void UpdateHealthUI() + protected override void UpdateHealthUI() { if (_healthBar != null) { _healthBar.UpdateHealth(_currentHealth.Value, GetMaxHealth()); } - OnHealthChanged?.Invoke(_currentHealth.Value, GetMaxHealth()); - } - - private void OnHealthValueChanged(int previousValue, int newValue) - { - UpdateHealthUI(); - } - - #endregion - - #region Visual Effects - - [ClientRpc] - private void ShowDamageEffectClientRpc() - { - if (damageEffectPrefab != null) - { - Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform; - GameObject effect = Instantiate(damageEffectPrefab, spawnPoint.position, spawnPoint.rotation); - Destroy(effect, 2f); - } - } - - [ClientRpc] - private void ShowDestroyEffectClientRpc() - { - if (destroyEffectPrefab != null) - { - Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform; - GameObject effect = Instantiate(destroyEffectPrefab, spawnPoint.position, spawnPoint.rotation); - Destroy(effect, 3f); - } - } - - [ClientRpc] - private void NotifyDestroyedClientRpc() - { - if (!IsServer) - { - OnDestroyed?.Invoke(); - } + InvokeOnHealthChanged(_currentHealth.Value, GetMaxHealth()); } #endregion #region Grid Bounds - /// - /// Gets the grid-based bounds (from BuildingData width/length/height) - /// This is used for placement validation, NOT the actual collider bounds - /// Bounds are slightly shrunk to allow adjacent buildings to touch - /// public Bounds GetGridBounds() { if (buildingData == null) return new Bounds(transform.position, Vector3.one); Vector3 gridSize = buildingData.GetSize(rotation); - - // Shrink slightly to allow buildings to be adjacent without Intersects() returning true Vector3 shrunkSize = gridSize - Vector3.one * 0.01f; return new Bounds(transform.position + Vector3.up * gridSize.y * 0.5f, shrunkSize); } - /// - /// Legacy method, use GetGridBounds() instead - /// public Bounds GetBounds() { return GetGridBounds(); @@ -495,50 +379,15 @@ namespace Northbound Bounds bounds = GetGridBounds(); - // 팀 색상으로 표시 Color teamColor = Application.isPlaying ? TeamManager.GetTeamColor(_team.Value) : TeamManager.GetTeamColor(initialTeam); Gizmos.color = new Color(teamColor.r, teamColor.g, teamColor.b, 0.3f); Gizmos.DrawWireCube(bounds.center, bounds.size); - } - private void OnDrawGizmosSelected() - { - if (buildingData == null) return; + Gizmos.color = teamColor; + Gizmos.DrawWireSphere(transform.position + Vector3.up * 2f, 0.5f); - Bounds bounds = GetGridBounds(); - Gizmos.color = Color.yellow; - Gizmos.DrawWireCube(bounds.center, bounds.size); - - // Draw grid position - if (BuildingManager.Instance != null) - { - Vector3 worldPos = BuildingManager.Instance.GridToWorld(gridPosition); - Gizmos.color = Color.magenta; - Gizmos.DrawSphere(worldPos, 0.2f); - } - - // Draw vision range (if provides vision) - if (buildingData.providesVision) - { - Gizmos.color = Color.cyan; - Gizmos.DrawWireSphere(transform.position, buildingData.visionRange); - } - - // Draw team info label - #if UNITY_EDITOR - if (Application.isPlaying) - { - string teamName = TeamManager.GetTeamName(_team.Value); - UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, - $"Owner: {_ownerId.Value}\nTeam: {teamName}"); - } - else if (useInitialOwner) - { - string teamName = TeamManager.GetTeamName(initialTeam); - UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, - $"Initial Owner: {initialOwnerId}\nTeam: {teamName}"); - } - #endif + UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, + $"{buildingData.buildingName}\nHP: {_currentHealth.Value}/{maxHealth}"); } #endregion diff --git a/Assets/Scripts/BuildingFoundation.cs b/Assets/Scripts/BuildingFoundation.cs index 6b3ca48..d81088f 100644 --- a/Assets/Scripts/BuildingFoundation.cs +++ b/Assets/Scripts/BuildingFoundation.cs @@ -75,22 +75,32 @@ namespace Northbound /// public void Initialize(BuildingData data, Vector3Int pos, int rot, ulong ownerId, TeamType team) { - if (!IsServer) return; + if (!IsOwner) + { + InitializeServerRpc(data != null ? data.buildingName : "", pos.x, pos.y, pos.z, rot, ownerId, team); + return; + } - buildingData = data; - gridPosition = pos; + InitializeServerRpc(data != null ? data.buildingName : "", pos.x, pos.y, pos.z, rot, ownerId, team); + } + + [ServerRpc] + private void InitializeServerRpc(string buildingName, int posX, int posY, int posZ, int rot, ulong ownerId, TeamType team) + { + buildingData = BuildingManager.Instance?.availableBuildings.Find(b => b != null && b.buildingName == buildingName); + gridPosition = new Vector3Int(posX, posY, posZ); rotation = rot; _ownerId.Value = ownerId; _team.Value = team; _currentProgress.Value = 0f; // BuildingData의 크기를 기반으로 스케일 설정 - Vector3 size = data.GetSize(rot); - + Vector3 size = buildingData != null ? buildingData.GetSize(rot) : Vector3.one; + // foundationVisual의 스케일만 조정 (토대 자체의 pivot은 중앙에 유지) if (foundationVisual != null) { - // 토대 비주얼을 건물 크기에 맞게 조정 (높이는 얇게) + // 토대 높이를 건물 크기에 맞게 조정 (높이는 얇게) foundationVisual.transform.localScale = new Vector3(size.x, 0.2f, size.z); foundationVisual.transform.localPosition = new Vector3(0, 0.1f, 0); // 바닥에서 약간 위 } @@ -102,12 +112,31 @@ namespace Northbound _collider = gameObject.AddComponent(); } - // 상호작용 가능한 크기로 설정 (전체 건물 높이가 아닌 접근 가능한 크기) + // 상호작용 가능한 크기로 설정 (전체 건물 높이가 아니라 접근 가능한 크기) _collider.size = new Vector3(size.x, 2f, size.z); // 높이를 2m로 설정하여 상호작용 가능 _collider.center = new Vector3(0, 1f, 0); // 중심을 1m 높이에 배치 _collider.isTrigger = false; // Trigger가 아닌 일반 Collider로 설정 - Debug.Log($"[BuildingFoundation] 토대 생성: {data.buildingName}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {team}"); + Debug.Log($"[BuildingFoundation] 토대 생성: {buildingData?.buildingName ?? "Building"}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {TeamManager.GetTeamName(team)}"); + } + + private void UpdateProgressBar() + { + if (buildingData == null || _progressBarInstance == null) + return; + + float progress = buildingData.requiredWorkAmount > 0 ? _currentProgress.Value / buildingData.requiredWorkAmount : 1f; + + // 간단한 progress bar update - 필요한 경우 BuildingProgressBar 컴포넌트 사용 + var progressBarTransform = _progressBarInstance.transform; + progressBarTransform.localScale = new Vector3(progress, 1f, 1f); + } + + private void OnProgressValueChanged(float previousValue, float newValue) + { + UpdateProgressBar(); + float max = buildingData != null ? buildingData.requiredWorkAmount : 100f; + OnProgressChanged?.Invoke(newValue, max); } /// @@ -123,6 +152,11 @@ namespace Northbound return new Bounds(transform.position + Vector3.up * size.y * 0.5f, size); } + public Bounds GetBounds() + { + return GetGridBounds(); + } + #region IInteractable Implementation public bool CanInteract(ulong playerId) @@ -139,7 +173,7 @@ namespace Northbound Debug.Log($"[BuildingFoundation] Already completed"); return false; } - + // 같은 팀만 건설 가능 - 플레이어의 팀을 가져와서 비교 TeamType playerTeam = GetPlayerTeam(playerId); if (playerTeam != _team.Value) @@ -153,7 +187,8 @@ namespace Northbound public void Interact(ulong playerId) { - if (!IsServer || buildingData == null) return; + if (!IsServer || buildingData == null) + return; if (!CanInteract(playerId)) return; @@ -163,45 +198,71 @@ namespace Northbound // 건설 진행 _currentProgress.Value += buildingData.workPerInteraction; - Debug.Log($"[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%)"); + Debug.Log($"[BuildingFoundation] {buildingData.buildingName} 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount}"); - // 완성 체크 + // 완료 체크 if (_currentProgress.Value >= buildingData.requiredWorkAmount) { - CompleteConstruction(); + CompleteConstruction(playerId); } } + private void CompleteConstruction(ulong playerId) + { + Debug.Log($"[BuildingFoundation] {buildingData.buildingName} 건설 완료! 완성자: {playerId}"); + + OnConstructionComplete?.Invoke(); + + // 토대 디스폰 + if (IsServer && NetworkObject != null) + { + NetworkObject.Despawn(true); + } + + // 상호작용 UI 제거 + if (_progressBarInstance != null) + { + Destroy(_progressBarInstance); + _progressBarInstance = null; + } + } + + private TeamType GetPlayerTeam(ulong playerId) + { + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(playerId, out NetworkObject playerObj)) + { + var teamMember = playerObj.GetComponent(); + if (teamMember != null) + { + return teamMember.GetTeam(); + } + } + return TeamType.Neutral; + } + + #endregion + + #region IInteractable Implementation - Getters + public string GetInteractionPrompt() { if (buildingData == null) - return "[E] 건설하기"; - - float percentage = (_currentProgress.Value / buildingData.requiredWorkAmount) * 100f; - return $"[E] {buildingData.buildingName} 건설 ({percentage:F0}%)"; + return "건설하기"; + float workNeeded = buildingData.requiredWorkAmount - _currentProgress.Value; + float interactionsNeeded = Mathf.Ceil(workNeeded / buildingData.workPerInteraction); + return $"[{interactionsNeeded}] 건설하기"; } public string GetInteractionAnimation() { - // BuildingData에서 애니메이션 트리거 가져오기 - if (buildingData != null && !string.IsNullOrEmpty(buildingData.constructionAnimationTrigger)) - { + if (buildingData != null && buildingData.constructionAnimationTrigger != null) return buildingData.constructionAnimationTrigger; - } - - // 기본값: 빈 문자열 (애니메이션 없음) - return ""; + return null; } public EquipmentData GetEquipmentData() { - // BuildingData에 건설 도구가 정의되어 있으면 반환 - if (buildingData != null && buildingData.constructionEquipment != null) - { - return buildingData.constructionEquipment; - } - - return null; // 특별한 도구 불필요 + return buildingData != null ? buildingData.constructionEquipment : null; } public Transform GetTransform() @@ -213,136 +274,25 @@ namespace Northbound #region ITeamMember Implementation - public TeamType GetTeam() - { - return _team.Value; - } + public TeamType GetTeam() => _team.Value; public void SetTeam(TeamType team) { - if (!IsServer) return; + if (!IsOwner) + { + SetTeamServerRpc(team); + return; + } + + SetTeamServerRpc(team); + } + + [ServerRpc] + private void SetTeamServerRpc(TeamType team) + { _team.Value = team; } #endregion - - /// - /// 플레이어의 팀 가져오기 - /// - private TeamType GetPlayerTeam(ulong playerId) - { - // 플레이어의 NetworkObject 찾기 - if (NetworkManager.Singleton != null && NetworkManager.Singleton.SpawnManager != null) - { - if (NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client)) - { - if (client.PlayerObject != null) - { - var teamMember = client.PlayerObject.GetComponent(); - if (teamMember != null) - { - return teamMember.GetTeam(); - } - } - } - } - - // 기본값: 플레이어 팀 - return TeamType.Player; - } - - private void CompleteConstruction() - { - if (!IsServer) return; - - Debug.Log($"[BuildingFoundation] 건물 완성! {buildingData.buildingName}"); - - OnConstructionComplete?.Invoke(); - - // BuildingManager에서 토대 제거 - var buildingManager = BuildingManager.Instance; - if (buildingManager != null) - { - buildingManager.RemoveFoundation(this); - } - - // 완성된 건물 생성 - SpawnCompletedBuilding(); - - // 토대 제거 - if (NetworkObject != null) - { - NetworkObject.Despawn(true); - } - } - - private void SpawnCompletedBuilding() - { - if (!IsServer || buildingData == null || buildingData.prefab == null) - return; - - // BuildingManager를 통해 건물 생성 - var buildingManager = BuildingManager.Instance; - if (buildingManager != null) - { - buildingManager.SpawnCompletedBuildingServerRpc( - buildingData.name, - gridPosition, - rotation, - _ownerId.Value, - _team.Value - ); - } - else - { - Debug.LogError("[BuildingFoundation] BuildingManager를 찾을 수 없습니다!"); - } - } - - private void OnProgressValueChanged(float oldValue, float newValue) - { - if (buildingData != null) - { - OnProgressChanged?.Invoke(newValue, buildingData.requiredWorkAmount); - } - UpdateProgressBar(); - } - - private void UpdateProgressBar() - { - if (_progressBarInstance == null || buildingData == null) return; - - // 진행바 UI 업데이트 (BuildingHealthBar와 유사한 구조 사용 가능) - var progressBar = _progressBarInstance.GetComponent(); - if (progressBar != null) - { - // BuildingHealthBar를 재사용하여 진행도 표시 - progressBar.UpdateHealth((int)_currentProgress.Value, (int)buildingData.requiredWorkAmount); - } - } - - private void OnDrawGizmos() - { - if (buildingData == null) return; - - // 건물 경계 표시 (노란색) - Gizmos.color = Color.yellow; - Vector3 size = buildingData.GetSize(rotation); - Gizmos.DrawWireCube(transform.position + Vector3.up * size.y * 0.5f, size); - - // Collider 경계 표시 (초록색) - if (_collider != null) - { - Gizmos.color = Color.green; - Gizmos.DrawWireCube(transform.position + _collider.center, _collider.size); - } - - // 상호작용 가능 여부 표시 - if (_currentProgress.Value < (buildingData?.requiredWorkAmount ?? 100f)) - { - Gizmos.color = Color.cyan; - Gizmos.DrawSphere(transform.position + Vector3.up, 0.3f); - } - } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/BuildingManager.cs b/Assets/Scripts/BuildingManager.cs index 27d0072..b904d4f 100644 --- a/Assets/Scripts/BuildingManager.cs +++ b/Assets/Scripts/BuildingManager.cs @@ -16,7 +16,7 @@ namespace Northbound public List availableBuildings = new List(); [Header("Foundation Settings")] - public GameObject foundationPrefab; // 토대 프리팹 (Inspector에서 할당) + public GameObject foundationPrefab; private List placedBuildings = new List(); private List placedFoundations = new List(); @@ -58,6 +58,53 @@ namespace Northbound ); } + private bool ValidateClient(ulong clientId) + { + if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId)) + { + Debug.LogWarning($"[BuildingManager] 유효하지 않은 클라이언트 ID: {clientId}"); + return false; + } + return true; + } + + private bool ValidateBuildingIndex(int buildingIndex) + { + if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count) + { + Debug.LogWarning($"[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}"); + return false; + } + return true; + } + + private BuildingData GetBuildingData(int buildingIndex) + { + BuildingData data = availableBuildings[buildingIndex]; + if (data == null || data.prefab == null) + { + Debug.LogWarning($"[BuildingManager] 건물 데이터가 유효하지 않습니다."); + return null; + } + return data; + } + + private void SetupSpawnedObject(GameObject obj, ulong ownerId) + { + if (obj.GetComponent() == null) + { + var visibility = obj.AddComponent(); + visibility.showInExploredAreas = true; + visibility.updateInterval = 0.2f; + } + + NetworkObject netObj = obj.GetComponent(); + if (netObj != null) + { + netObj.SpawnWithOwnership(ownerId); + } + } + public bool IsValidPlacement(BuildingData data, Vector3 position, int rotation, out Vector3 groundPosition) { groundPosition = position; @@ -141,8 +188,10 @@ namespace Northbound { groundPosition = position; - // Raycast down to find ground - if (Physics.Raycast(position + Vector3.up * 10f, Vector3.down, out RaycastHit hit, 20f, groundLayer)) + float originHeight = 10f; + float rayDistance = 20f; + Vector3 rayOrigin = position + Vector3.up * originHeight; + if (Physics.Raycast(rayOrigin, Vector3.down, out RaycastHit hit, rayDistance, groundLayer)) { groundPosition = hit.point; return true; @@ -169,30 +218,22 @@ namespace Northbound [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void PlaceBuildingServerRpc(int buildingIndex, Vector3 position, int rotation, ulong requestingClientId) { - // 보안 검증 1: 유효한 클라이언트인지 확인 - if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(requestingClientId)) + if (!ValidateClient(requestingClientId)) { - Debug.LogWarning($"[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}"); return; } - // 보안 검증 2: 건물 인덱스 유효성 확인 - if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count) + if (!ValidateBuildingIndex(buildingIndex)) { - Debug.LogWarning($"[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex} (클라이언트: {requestingClientId})"); return; } - BuildingData data = availableBuildings[buildingIndex]; - - // 보안 검증 3: 건물 데이터 유효성 확인 - if (data == null || data.prefab == null) + BuildingData data = GetBuildingData(buildingIndex); + if (data == null) { - Debug.LogWarning($"[BuildingManager] 건물 데이터가 유효하지 않습니다. (클라이언트: {requestingClientId})"); return; } - // 배치 가능 여부 확인 if (!IsValidPlacement(data, position, rotation, out Vector3 snappedPosition)) { Debug.LogWarning($"[BuildingManager] 건물 배치 불가능 위치 (클라이언트: {requestingClientId})"); @@ -200,41 +241,23 @@ namespace Northbound } Vector3Int gridPosition = WorldToGrid(snappedPosition); - - // 건물 생성 GameObject buildingObj = Instantiate(data.prefab, snappedPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0)); - NetworkObject netObj = buildingObj.GetComponent(); - // Add FogOfWarVisibility component to hide buildings in unexplored areas - if (buildingObj.GetComponent() == null) + SetupSpawnedObject(buildingObj, requestingClientId); + + Building building = buildingObj.GetComponent(); + if (building == null) { - var visibility = buildingObj.AddComponent(); - visibility.showInExploredAreas = true; // Buildings remain visible in explored areas - visibility.updateInterval = 0.2f; - } - - if (netObj != null) - { - // 건물의 소유자를 설정 - netObj.SpawnWithOwnership(requestingClientId); - - Building building = buildingObj.GetComponent(); - if (building == null) - { - building = buildingObj.AddComponent(); - } - - // 건물 초기화 - building.Initialize(data, gridPosition, rotation, requestingClientId); - placedBuildings.Add(building); - - Debug.Log($"[BuildingManager] {data.buildingName} 건설 완료 (소유자: {requestingClientId}, 위치: {gridPosition})"); - } - else - { - Debug.LogError($"[BuildingManager] NetworkObject 컴포넌트가 없습니다! (Prefab: {data.prefab.name})"); + Debug.LogError($"[BuildingManager] Building prefab must have Building component! (Prefab: {data.prefab.name})"); + buildingObj.GetComponent()?.Despawn(true); Destroy(buildingObj); + return; } + + building.Initialize(data, gridPosition, rotation, requestingClientId); + placedBuildings.Add(building); + + Debug.Log($"[BuildingManager] {data.buildingName} 건설 완료 (소유자: {requestingClientId}, 위치: {gridPosition})"); } public void RemoveBuilding(Building building) @@ -321,37 +344,28 @@ namespace Northbound [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void PlaceFoundationServerRpc(int buildingIndex, Vector3 position, int rotation, ulong requestingClientId) { - // 보안 검증 1: 유효한 클라이언트인지 확인 - if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(requestingClientId)) + if (!ValidateClient(requestingClientId)) { - Debug.LogWarning($"[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}"); return; } - // 보안 검증 2: 건물 인덱스 유효성 확인 - if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count) + if (!ValidateBuildingIndex(buildingIndex)) { - Debug.LogWarning($"[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}"); return; } - BuildingData data = availableBuildings[buildingIndex]; - - // 보안 검증 3: 건물 데이터 유효성 확인 + BuildingData data = GetBuildingData(buildingIndex); if (data == null) { - Debug.LogWarning($"[BuildingManager] 건물 데이터가 유효하지 않습니다."); return; } - // 토대 프리팹 확인 if (foundationPrefab == null) { Debug.LogError("[BuildingManager] foundationPrefab이 설정되지 않았습니다!"); return; } - // 배치 가능 여부 확인 if (!IsValidPlacement(data, position, rotation, out Vector3 snappedPosition)) { Debug.LogWarning($"[BuildingManager] 토대 배치 불가능 위치"); @@ -359,43 +373,23 @@ namespace Northbound } Vector3Int gridPosition = WorldToGrid(snappedPosition); - - // 플레이어 팀 가져오기 TeamType playerTeam = GetPlayerTeam(requestingClientId); - // 토대 생성 GameObject foundationObj = Instantiate(foundationPrefab, snappedPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0)); - NetworkObject netObj = foundationObj.GetComponent(); - // Add FogOfWarVisibility component to hide foundations in unexplored areas - if (foundationObj.GetComponent() == null) + SetupSpawnedObject(foundationObj, requestingClientId); + + BuildingFoundation foundation = foundationObj.GetComponent(); + if (foundation != null) { - var visibility = foundationObj.AddComponent(); - visibility.showInExploredAreas = true; // Foundations remain visible in explored areas - visibility.updateInterval = 0.2f; - } - - if (netObj != null) - { - netObj.SpawnWithOwnership(requestingClientId); - - BuildingFoundation foundation = foundationObj.GetComponent(); - if (foundation != null) - { - foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam); - placedFoundations.Add(foundation); // 토대 목록에 추가 - Debug.Log($"[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})"); - } - else - { - Debug.LogError("[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!"); - netObj.Despawn(true); - } + foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam); + placedFoundations.Add(foundation); + Debug.Log($"[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})"); } else { - Debug.LogError("[BuildingManager] NetworkObject 컴포넌트가 없습니다!"); - Destroy(foundationObj); + Debug.LogError("[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!"); + foundationObj.GetComponent()?.Despawn(true); } } @@ -427,7 +421,6 @@ namespace Northbound [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] public void SpawnCompletedBuildingServerRpc(string buildingDataName, Vector3Int gridPosition, int rotation, ulong ownerId, TeamType team) { - // BuildingData 찾기 BuildingData data = availableBuildings.Find(b => b.name == buildingDataName); if (data == null || data.prefab == null) { @@ -436,40 +429,23 @@ namespace Northbound } Vector3 worldPosition = GridToWorld(gridPosition); - - // 완성된 건물 생성 GameObject buildingObj = Instantiate(data.prefab, worldPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0)); - NetworkObject netObj = buildingObj.GetComponent(); - // Add FogOfWarVisibility component to hide buildings in unexplored areas - if (buildingObj.GetComponent() == null) + SetupSpawnedObject(buildingObj, ownerId); + + Building building = buildingObj.GetComponent(); + if (building == null) { - var visibility = buildingObj.AddComponent(); - visibility.showInExploredAreas = true; // Buildings remain visible in explored areas - visibility.updateInterval = 0.2f; - } - - if (netObj != null) - { - netObj.SpawnWithOwnership(ownerId); - - Building building = buildingObj.GetComponent(); - if (building == null) - { - building = buildingObj.AddComponent(); - } - - // 건물 초기화 - building.Initialize(data, gridPosition, rotation, ownerId); - placedBuildings.Add(building); - - Debug.Log($"[BuildingManager] {data.buildingName} 건설 완료! (소유자: {ownerId}, 위치: {gridPosition}, 팀: {team})"); - } - else - { - Debug.LogError($"[BuildingManager] NetworkObject 컴포넌트가 없습니다!"); + Debug.LogError($"[BuildingManager] Building prefab must have Building component! (Prefab: {data.prefab.name})"); + buildingObj.GetComponent()?.Despawn(true); Destroy(buildingObj); + return; } + + building.Initialize(data, gridPosition, rotation, ownerId); + placedBuildings.Add(building); + + Debug.Log($"[BuildingManager] {data.buildingName} 건설 완료! (소유자: {ownerId}, 위치: {gridPosition}, 팀: {team})"); } } } diff --git a/Assets/Scripts/BuildingPlacement.cs b/Assets/Scripts/BuildingPlacement.cs index 8022b63..2bdc179 100644 --- a/Assets/Scripts/BuildingPlacement.cs +++ b/Assets/Scripts/BuildingPlacement.cs @@ -225,61 +225,71 @@ namespace Northbound private void CreatePreview() { + if (!ValidateBuildingData(selectedBuildingIndex, out BuildingData data)) + { + return; + } + + previewObject = Instantiate(data.prefab); + SetupPreviewObject(previewObject, validMaterial); + Debug.Log($"[BuildingPlacement] 프리뷰 생성됨: {data.buildingName}"); + } + + private bool ValidateBuildingData(int index, out BuildingData data) + { + data = null; + if (BuildingManager.Instance == null) { Debug.LogWarning("[BuildingPlacement] BuildingManager가 없습니다."); - return; + return false; } - if (selectedBuildingIndex < 0 || selectedBuildingIndex >= BuildingManager.Instance.availableBuildings.Count) + if (index < 0 || index >= BuildingManager.Instance.availableBuildings.Count) { - Debug.LogWarning($"[BuildingPlacement] 유효하지 않은 건물 인덱스: {selectedBuildingIndex}"); - return; + Debug.LogWarning($"[BuildingPlacement] 유효하지 않은 건물 인덱스: {index}"); + return false; } - BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex]; + data = BuildingManager.Instance.availableBuildings[index]; if (data == null || data.prefab == null) { Debug.LogWarning("[BuildingPlacement] BuildingData 또는 Prefab이 없습니다."); - return; + return false; } - // 완성 건물 프리팹으로 프리뷰 생성 (사용자가 완성 모습을 볼 수 있도록) - previewObject = Instantiate(data.prefab); - - // Remove NetworkObject component from preview - NetworkObject netObj = previewObject.GetComponent(); + return true; + } + + private void SetupPreviewObject(GameObject previewObj, Material material) + { + NetworkObject netObj = previewObj.GetComponent(); if (netObj != null) { Destroy(netObj); } - // Remove Building component from preview - Building building = previewObject.GetComponent(); + Building building = previewObj.GetComponent(); if (building != null) { Destroy(building); } - // Apply ghost materials - previewRenderers = previewObject.GetComponentsInChildren(); - foreach (var renderer in previewRenderers) + Renderer[] renderers = previewObj.GetComponentsInChildren(); + foreach (var renderer in renderers) { Material[] mats = new Material[renderer.materials.Length]; for (int i = 0; i < mats.Length; i++) { - mats[i] = validMaterial; + mats[i] = material; } renderer.materials = mats; } - // Disable colliders in preview - foreach (var collider in previewObject.GetComponentsInChildren()) + foreach (var collider in previewObj.GetComponentsInChildren()) { collider.enabled = false; } - - Debug.Log($"[BuildingPlacement] 프리뷰 생성됨: {data.buildingName}"); } private void DestroyPreview() @@ -297,29 +307,33 @@ namespace Northbound return; Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()); - + if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer)) { BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex]; - // Check if placement is valid bool isValid = BuildingManager.Instance.IsValidPlacement(data, hit.point, currentRotation, out Vector3 snappedPosition); - // Update preview position (placementOffset 적용) previewObject.transform.position = snappedPosition + data.placementOffset; previewObject.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0); - // Update material based on validity Material targetMat = isValid ? validMaterial : invalidMaterial; - foreach (var renderer in previewRenderers) + UpdatePreviewMaterials(previewRenderers, targetMat); + } + } + + private void UpdatePreviewMaterials(Renderer[] renderers, Material material) + { + if (renderers == null) return; + + foreach (var renderer in renderers) + { + Material[] mats = new Material[renderer.materials.Length]; + for (int i = 0; i < mats.Length; i++) { - Material[] mats = new Material[renderer.materials.Length]; - for (int i = 0; i < mats.Length; i++) - { - mats[i] = targetMat; - } - renderer.materials = mats; + mats[i] = material; } + renderer.materials = mats; } } @@ -419,16 +433,13 @@ namespace Northbound } BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex]; - - // 드래그 영역 계산 + Vector3 dragEndPosition = hit.point; List positions = CalculateDragBuildingPositions(dragStartPosition, dragEndPosition, data); - // 기존 프리뷰 정리 ClearDragPreviews(); - - // 새로운 프리뷰 생성 dragBuildingPositions.Clear(); + foreach (var pos in positions) { if (dragPreviewObjects.Count >= maxDragBuildingCount) @@ -440,38 +451,14 @@ namespace Northbound bool isValid = BuildingManager.Instance.IsValidPlacement(data, pos, currentRotation, out Vector3 snappedPosition); GameObject preview = Instantiate(data.prefab); - - // Remove NetworkObject and Building components - if (preview.GetComponent() != null) - Destroy(preview.GetComponent()); - if (preview.GetComponent() != null) - Destroy(preview.GetComponent()); + Material targetMat = isValid ? validMaterial : invalidMaterial; + SetupPreviewObject(preview, targetMat); - // Set position and rotation preview.transform.position = snappedPosition + data.placementOffset; preview.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0); - // Apply materials - Material targetMat = isValid ? validMaterial : invalidMaterial; - Renderer[] renderers = preview.GetComponentsInChildren(); - foreach (var renderer in renderers) - { - Material[] mats = new Material[renderer.materials.Length]; - for (int i = 0; i < mats.Length; i++) - { - mats[i] = targetMat; - } - renderer.materials = mats; - } - - // Disable colliders - foreach (var collider in preview.GetComponentsInChildren()) - { - collider.enabled = false; - } - dragPreviewObjects.Add(preview); - + if (isValid) { dragBuildingPositions.Add(snappedPosition); diff --git a/Assets/Scripts/Core.cs b/Assets/Scripts/Core.cs index fe0cfd1..d7a0efa 100644 --- a/Assets/Scripts/Core.cs +++ b/Assets/Scripts/Core.cs @@ -50,7 +50,7 @@ namespace Northbound public override void OnNetworkSpawn() { - if (IsServer) + if (IsOwner) { _totalResources.Value = 0; _currentHealth.Value = maxHealth; @@ -88,7 +88,7 @@ namespace Northbound public void TakeDamage(int damage, ulong attackerId) { - if (!IsServer) return; + if (!IsOwner) return; if (_currentHealth.Value <= 0) return; int actualDamage = Mathf.Min(damage, _currentHealth.Value); @@ -108,7 +108,7 @@ namespace Northbound private void OnCoreDestroyed() { - if (!IsServer) return; + if (!IsOwner) return; Debug.Log($"[Core] 코어가 파괴되었습니다! 게임 오버!"); @@ -174,7 +174,7 @@ namespace Northbound /// public void AddResource(int amount) { - if (!IsServer) return; + if (!IsOwner) return; if (!unlimitedStorage) { @@ -206,7 +206,7 @@ namespace Northbound { if (client.PlayerObject != null) { - var playerInventory = client.PlayerObject.GetComponent(); + var playerInventory = client.PlayerObject.GetComponent(); if (playerInventory != null) { // 플레이어가 자원을 가지고 있어야 함 @@ -264,10 +264,10 @@ namespace Northbound if (playerObject == null) return; - var playerInventory = playerObject.GetComponent(); + var playerInventory = playerObject.GetComponent(); if (playerInventory == null) { - Debug.LogWarning($"플레이어 {playerId}에게 PlayerResourceInventory 컴포넌트가 없습니다."); + Debug.LogWarning($"플레이어 {playerId}에게 PlayerInventory 컴포넌트가 없습니다."); return; } @@ -304,8 +304,8 @@ namespace Northbound return; } - // 플레이어로부터 자원 차감 - playerInventory.RemoveResourceServerRpc(depositAmount); + // 플레이어로부터 자원 차감 (RPC로 owner에게 요청) + playerInventory.RemoveResourcesRpc(depositAmount); // 코어에 자원 추가 _totalResources.Value += depositAmount; diff --git a/Assets/Scripts/DamageableNetworkBehaviour.cs b/Assets/Scripts/DamageableNetworkBehaviour.cs new file mode 100644 index 0000000..999cb20 --- /dev/null +++ b/Assets/Scripts/DamageableNetworkBehaviour.cs @@ -0,0 +1,162 @@ +using System; +using Unity.Netcode; +using UnityEngine; + +namespace Northbound +{ + public abstract class DamageableNetworkBehaviour : NetworkBehaviour, IDamageable + { + [Header("Health Settings")] + [SerializeField] protected int maxHealth = 100; + [SerializeField] protected bool showHealthBar = true; + + [Header("Visual Effects")] + [SerializeField] protected GameObject damageEffectPrefab; + [SerializeField] protected GameObject destroyEffectPrefab; + [SerializeField] protected Transform effectSpawnPoint; + + protected NetworkVariable _currentHealth = new NetworkVariable( + 0, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Server + ); + + public event Action OnHealthChanged; + public event Action OnDestroyed; + + protected void InvokeOnHealthChanged(int currentHealth, int maxHealth) + { + OnHealthChanged?.Invoke(currentHealth, maxHealth); + } + + protected void InvokeOnDestroyed() + { + OnDestroyed?.Invoke(); + } + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + if (IsOwner) + { + InitializeHealthServerRpc(maxHealth); + } + + _currentHealth.OnValueChanged += OnHealthValueChanged; + InitializeHealthBar(); + UpdateHealthUI(); + } + + [ServerRpc] + private void InitializeHealthServerRpc(int health) + { + if (_currentHealth.Value == 0) + _currentHealth.Value = health; + } + + public override void OnNetworkDespawn() + { + _currentHealth.OnValueChanged -= OnHealthValueChanged; + base.OnNetworkDespawn(); + } + + protected virtual void InitializeHealthBar() + { + } + + protected virtual void UpdateHealthUI() + { + } + + public virtual void TakeDamage(int damage, ulong attackerId) + { + if (!IsOwner) + { + TakeDamageServerRpc(damage, attackerId); + return; + } + + TakeDamageServerRpc(damage, attackerId); + } + + [ServerRpc] + private void TakeDamageServerRpc(int damage, ulong attackerId) + { + if (_currentHealth.Value <= 0) + return; + + int actualDamage = Mathf.Min(damage, _currentHealth.Value); + _currentHealth.Value -= actualDamage; + + Debug.Log($"[{GetType().Name}] {gameObject.name} received {actualDamage} damage. Health: {_currentHealth.Value}/{maxHealth}"); + + ShowDamageEffectClientRpc(); + + if (_currentHealth.Value <= 0) + { + Die(attackerId); + } + } + + protected virtual void Die(ulong killerId) + { + Debug.Log($"[{GetType().Name}] {gameObject.name} destroyed! Killer: {killerId}"); + OnDestroyed?.Invoke(); + ShowDeathEffectClientRpc(); + } + + [ClientRpc] + protected void ShowDamageEffectClientRpc() + { + if (damageEffectPrefab != null) + { + Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform; + GameObject effect = Instantiate(damageEffectPrefab, spawnPoint.position + Vector3.up, Quaternion.identity); + Destroy(effect, 2f); + } + } + + [ClientRpc] + protected void ShowDeathEffectClientRpc() + { + if (destroyEffectPrefab != null) + { + Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform; + GameObject effect = Instantiate(destroyEffectPrefab, spawnPoint.position, Quaternion.identity); + Destroy(effect, 3f); + } + } + + protected virtual void OnHealthValueChanged(int previousValue, int newValue) + { + OnHealthChanged?.Invoke(newValue, maxHealth); + UpdateHealthUI(); + } + + public int GetCurrentHealth() => _currentHealth.Value; + public int GetMaxHealth() => maxHealth; + public float GetHealthPercentage() => maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f; + public bool IsDead() => _currentHealth.Value <= 0; + + public virtual void Heal(int amount) + { + if (!IsOwner) + { + HealServerRpc(amount); + return; + } + + HealServerRpc(amount); + } + + [ServerRpc] + private void HealServerRpc(int amount) + { + int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value); + _currentHealth.Value += healAmount; + + Debug.Log($"[{GetType().Name}] {gameObject.name} healed {healAmount}. Health: {_currentHealth.Value}/{maxHealth}"); + } + } +} diff --git a/Assets/Scripts/DamageableNetworkBehaviour.cs.meta b/Assets/Scripts/DamageableNetworkBehaviour.cs.meta new file mode 100644 index 0000000..5999993 --- /dev/null +++ b/Assets/Scripts/DamageableNetworkBehaviour.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7aeb2eee85ba3c42ab4d53ef28f96fc \ No newline at end of file diff --git a/Assets/Scripts/EnemyAIController.cs b/Assets/Scripts/EnemyAIController.cs index 8b89cf0..3516173 100644 --- a/Assets/Scripts/EnemyAIController.cs +++ b/Assets/Scripts/EnemyAIController.cs @@ -72,13 +72,13 @@ namespace Northbound private NetworkVariable _currentState = new NetworkVariable( EnemyAIState.Idle, NetworkVariableReadPermission.Everyone, - NetworkVariableWritePermission.Server + NetworkVariableWritePermission.Owner ); private NetworkVariable _targetPlayerId = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, - NetworkVariableWritePermission.Server + NetworkVariableWritePermission.Owner ); private GameObject _cachedTargetPlayer; @@ -91,7 +91,7 @@ namespace Northbound _enemyUnit = GetComponent(); _originPosition = transform.position; - if (IsServer) + if (IsOwner) { // NavMeshAgent 초기 설정 _agent.speed = moveSpeed; @@ -121,7 +121,7 @@ namespace Northbound private void Update() { - if (!IsServer) return; + if (!IsOwner) return; if (!_agent.isOnNavMesh) return; switch (_currentState.Value) diff --git a/Assets/Scripts/EnemyUnit.cs b/Assets/Scripts/EnemyUnit.cs index 1319744..16f7d73 100644 --- a/Assets/Scripts/EnemyUnit.cs +++ b/Assets/Scripts/EnemyUnit.cs @@ -36,13 +36,21 @@ namespace Northbound { base.OnNetworkSpawn(); - if (IsServer) + if (IsOwner) { - _currentHealth.Value = maxHealth; - _team.Value = enemyTeam; + InitializeServerRpc(maxHealth, enemyTeam); } } + [ServerRpc] + private void InitializeServerRpc(int health, TeamType team) + { + if (_currentHealth.Value == 0) + _currentHealth.Value = health; + if (_team.Value == TeamType.Neutral) + _team.Value = team; + } + public override void OnNetworkDespawn() { base.OnNetworkDespawn(); @@ -52,7 +60,18 @@ namespace Northbound public void TakeDamage(int damage, ulong attackerId) { - if (!IsServer) return; + if (!IsOwner) + { + TakeDamageServerRpc(damage, attackerId); + return; + } + + TakeDamageServerRpc(damage, attackerId); + } + + [ServerRpc] + private void TakeDamageServerRpc(int damage, ulong attackerId) + { if (_currentHealth.Value <= 0) return; // 공격자의 팀 확인 @@ -128,7 +147,18 @@ namespace Northbound public void SetTeam(TeamType team) { - if (!IsServer) return; + if (!IsOwner) + { + SetTeamServerRpc(team); + return; + } + + SetTeamServerRpc(team); + } + + [ServerRpc] + private void SetTeamServerRpc(TeamType team) + { _team.Value = team; } diff --git a/Assets/Scripts/EquipmentSocket.cs b/Assets/Scripts/EquipmentSocket.cs index a54c3e0..8043381 100644 --- a/Assets/Scripts/EquipmentSocket.cs +++ b/Assets/Scripts/EquipmentSocket.cs @@ -1,29 +1,31 @@ using UnityEngine; using System.Collections.Generic; +using Unity.Netcode; namespace Northbound { - /// - /// 플레이어의 장비 소켓 관리 (손, 등, 허리 등) - /// - public class EquipmentSocket : MonoBehaviour + public class EquipmentSocket : NetworkBehaviour { [System.Serializable] public class Socket { - public string socketName; // "RightHand", "LeftHand", "Back" 등 - public Transform socketTransform; // 실제 본 Transform - [HideInInspector] public GameObject currentEquipment; // 현재 장착된 장비 + public string socketName; + public Transform socketTransform; + [HideInInspector] public GameObject currentEquipment; } [Header("Available Sockets")] public List sockets = new List(); + [Header("Equipment Prefabs")] + public GameObject[] equipmentPrefabs; + private Dictionary _socketDict = new Dictionary(); + private Dictionary _prefabDict = new Dictionary(); private void Awake() { - // 빠른 검색을 위한 딕셔너리 생성 + _socketDict.Clear(); foreach (var socket in sockets) { if (!string.IsNullOrEmpty(socket.socketName)) @@ -31,82 +33,124 @@ namespace Northbound _socketDict[socket.socketName] = socket; } } - } - /// - /// 소켓에 장비 부착 - /// - public GameObject AttachToSocket(string socketName, GameObject equipmentPrefab) - { - if (!_socketDict.TryGetValue(socketName, out Socket socket)) + _prefabDict.Clear(); + if (equipmentPrefabs != null) { - Debug.LogWarning($"소켓을 찾을 수 없습니다: {socketName}"); - return null; - } - - if (socket.socketTransform == null) - { - Debug.LogWarning($"소켓 Transform이 없습니다: {socketName}"); - return null; - } - - // 기존 장비 제거 - DetachFromSocket(socketName); - - // 새 장비 생성 - if (equipmentPrefab != null) - { - GameObject equipment = Instantiate(equipmentPrefab, socket.socketTransform); - equipment.transform.localPosition = Vector3.zero; - equipment.transform.localRotation = Quaternion.identity; - socket.currentEquipment = equipment; - - return equipment; - } - - return null; - } - - /// - /// 소켓에서 장비 제거 - /// - public void DetachFromSocket(string socketName) - { - if (!_socketDict.TryGetValue(socketName, out Socket socket)) - return; - - if (socket.currentEquipment != null) - { - Destroy(socket.currentEquipment); - socket.currentEquipment = null; + foreach (var prefab in equipmentPrefabs) + { + if (prefab != null) + { + _prefabDict[prefab.name] = prefab; + } + } } } - /// - /// 모든 소켓에서 장비 제거 - /// - public void DetachAll() + public override void OnNetworkDespawn() { foreach (var socket in sockets) { if (socket.currentEquipment != null) { - Destroy(socket.currentEquipment); + Object.Destroy(socket.currentEquipment); socket.currentEquipment = null; } } + base.OnNetworkDespawn(); + } + + public GameObject AttachToSocket(string socketName, GameObject equipmentPrefab) + { + if (equipmentPrefab != null) + { + AttachToSocketServerRpc(socketName, equipmentPrefab.name); + } + return null; + } + + [Rpc(SendTo.Server)] + private void AttachToSocketServerRpc(string socketName, string prefabName) + { + AttachToSocketClientRpc(socketName, prefabName); + } + + [Rpc(SendTo.ClientsAndHost)] + private void AttachToSocketClientRpc(string socketName, string prefabName) + { + if (!_socketDict.ContainsKey(socketName)) + return; + + var socket = sockets.Find(s => s.socketName == socketName); + if (socket == null || socket.socketTransform == null) + return; + + DetachFromSocketInternal(socketName); + + GameObject prefab = FindPrefab(prefabName); + if (prefab != null) + { + GameObject equipment = Object.Instantiate(prefab, socket.socketTransform); + equipment.transform.localPosition = Vector3.zero; + equipment.transform.localRotation = Quaternion.identity; + socket.currentEquipment = equipment; + } + } + + public void DetachFromSocket(string socketName) + { + DetachFromSocketServerRpc(socketName); + } + + [Rpc(SendTo.Server)] + private void DetachFromSocketServerRpc(string socketName) + { + DetachFromSocketClientRpc(socketName); + } + + [Rpc(SendTo.ClientsAndHost)] + private void DetachFromSocketClientRpc(string socketName) + { + DetachFromSocketInternal(socketName); + } + + private void DetachFromSocketInternal(string socketName) + { + var socket = sockets.Find(s => s.socketName == socketName); + if (socket == null) return; + + if (socket.currentEquipment != null) + { + Object.Destroy(socket.currentEquipment); + socket.currentEquipment = null; + } } - /// - /// 특정 소켓에 장비가 있는지 확인 - /// public bool HasEquipment(string socketName) { - if (_socketDict.TryGetValue(socketName, out Socket socket)) + var socket = sockets.Find(s => s.socketName == socketName); + return socket != null && socket.currentEquipment != null; + } + + public GameObject GetEquipment(string socketName) + { + var socket = sockets.Find(s => s.socketName == socketName); + return socket != null ? socket.currentEquipment : null; + } + + private GameObject FindPrefab(string name) + { + if (_prefabDict.TryGetValue(name, out var prefab)) { - return socket.currentEquipment != null; + return prefab; } - return false; + + prefab = Resources.Load($"Prefabs/{name}"); + if (prefab != null) + { + return prefab; + } + return Resources.Load(name); } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/FogOfWarSystem.cs b/Assets/Scripts/FogOfWarSystem.cs index a49f540..07376c3 100644 --- a/Assets/Scripts/FogOfWarSystem.cs +++ b/Assets/Scripts/FogOfWarSystem.cs @@ -258,7 +258,7 @@ namespace Northbound private void OnClientConnected(ulong clientId) { - if (!IsServer) return; + if (!IsOwner) return; // Ensure fog data exists for this client if (!_serverFogData.ContainsKey(clientId)) @@ -280,7 +280,7 @@ namespace Northbound private void Update() { - if (!IsServer) return; + if (!IsOwner) return; _updateTimer += Time.deltaTime; if (_updateTimer >= updateInterval) diff --git a/Assets/Scripts/GameConstants.cs b/Assets/Scripts/GameConstants.cs new file mode 100644 index 0000000..3c5bcf8 --- /dev/null +++ b/Assets/Scripts/GameConstants.cs @@ -0,0 +1,34 @@ +namespace Northbound +{ + public static class GameConstants + { + public static class Building + { + public const float BoundsShrinkAmount = 0.01f; + public const float MaxPlacementDistance = 100f; + public const int MaxDragBuildingCount = 50; + } + + public static class Physics + { + public const float GroundRaycastOriginHeight = 10f; + public const float GroundRaycastDistance = 20f; + } + + public static class FogOfWar + { + public const float DefaultUpdateInterval = 0.2f; + } + + public static class Player + { + public const float RespawnDelay = 3f; + public const int DefaultMaxHealth = 100; + } + + public static class Material + { + public const int TransparentRenderQueue = 3000; + } + } +} diff --git a/Assets/Scripts/GameConstants.cs.meta b/Assets/Scripts/GameConstants.cs.meta new file mode 100644 index 0000000..52c9a3d --- /dev/null +++ b/Assets/Scripts/GameConstants.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 55b873d6b94628148b3c61c4f5d435fc \ No newline at end of file diff --git a/Assets/Scripts/GameResourceUI.cs b/Assets/Scripts/GameResourceUI.cs index 2d8d985..b5b82f5 100644 --- a/Assets/Scripts/GameResourceUI.cs +++ b/Assets/Scripts/GameResourceUI.cs @@ -133,7 +133,7 @@ namespace Northbound return; } - var inventory = localPlayer.GetComponent(); + var inventory = localPlayer.GetComponent(); if (inventory == null) { playerResourceText.text = playerPrefix + "---"; diff --git a/Assets/Scripts/GlobalTimer.cs b/Assets/Scripts/GlobalTimer.cs index f392a04..237f6c4 100644 --- a/Assets/Scripts/GlobalTimer.cs +++ b/Assets/Scripts/GlobalTimer.cs @@ -67,7 +67,7 @@ namespace Northbound { base.OnNetworkSpawn(); - if (IsServer && autoStart) + if (IsOwner && autoStart) { StartTimer(); } @@ -83,7 +83,7 @@ namespace Northbound private void Update() { - if (!IsServer || !_isRunning.Value) + if (!IsOwner || !_isRunning.Value) return; _currentTime.Value -= Time.deltaTime; @@ -149,7 +149,7 @@ namespace Northbound private void OnCurrentTimeChanged(float previousValue, float newValue) { // 클라이언트에서도 Tick 이벤트 발생 - if (!IsServer) + if (!IsOwner) { OnTimerTick?.Invoke(newValue); } @@ -162,7 +162,7 @@ namespace Northbound /// public void StartTimer() { - if (!IsServer) return; + if (!IsOwner) return; _currentTime.Value = cycleLength; _isRunning.Value = true; @@ -181,7 +181,7 @@ namespace Northbound /// public void PauseTimer() { - if (!IsServer) return; + if (!IsOwner) return; _isRunning.Value = false; @@ -194,7 +194,7 @@ namespace Northbound /// public void ResumeTimer() { - if (!IsServer) return; + if (!IsOwner) return; _isRunning.Value = true; @@ -207,7 +207,7 @@ namespace Northbound /// public void ResetTimer() { - if (!IsServer) return; + if (!IsOwner) return; _currentTime.Value = cycleLength; _cycleCount.Value = 0; @@ -255,7 +255,7 @@ namespace Northbound [ClientRpc] private void NotifyCycleCompleteClientRpc() { - if (!IsServer) + if (!IsOwner) { OnCycleComplete?.Invoke(); } @@ -264,7 +264,7 @@ namespace Northbound [ClientRpc] private void NotifyCycleStartClientRpc(int cycleNumber) { - if (!IsServer) + if (!IsOwner) { OnCycleStart?.Invoke(cycleNumber); } @@ -273,7 +273,7 @@ namespace Northbound [ClientRpc] private void NotifyHalfwayClientRpc() { - if (!IsServer) + if (!IsOwner) { OnHalfwayPoint?.Invoke(cycleLength / 2f); } diff --git a/Assets/Scripts/InputActionManager.cs b/Assets/Scripts/InputActionManager.cs new file mode 100644 index 0000000..411c8b2 --- /dev/null +++ b/Assets/Scripts/InputActionManager.cs @@ -0,0 +1,54 @@ +using Unity.Netcode; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace Northbound +{ + public abstract class InputActionManager : NetworkBehaviour + { + protected PlayerInputActions _inputActions; + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + if (!IsOwner) return; + + InitializeInputActions(); + } + + public override void OnNetworkDespawn() + { + if (IsOwner && _inputActions != null) + { + UnbindInputActions(); + _inputActions.Disable(); + _inputActions.Dispose(); + _inputActions = null; + } + + base.OnNetworkDespawn(); + } + + public override void OnDestroy() + { + if (_inputActions != null) + { + _inputActions.Dispose(); + _inputActions = null; + } + base.OnDestroy(); + } + + protected virtual void InitializeInputActions() + { + _inputActions = new PlayerInputActions(); + _inputActions.Enable(); + BindInputActions(); + } + + protected abstract void BindInputActions(); + + protected abstract void UnbindInputActions(); + } +} diff --git a/Assets/Scripts/InputActionManager.cs.meta b/Assets/Scripts/InputActionManager.cs.meta new file mode 100644 index 0000000..0ae11bb --- /dev/null +++ b/Assets/Scripts/InputActionManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 06c90984b0af66542bd5011748fe67c1 \ No newline at end of file diff --git a/Assets/Scripts/NetworkConnectionHandler.cs b/Assets/Scripts/NetworkConnectionHandler.cs index 569659a..25dc955 100644 --- a/Assets/Scripts/NetworkConnectionHandler.cs +++ b/Assets/Scripts/NetworkConnectionHandler.cs @@ -68,16 +68,11 @@ namespace Northbound NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) { - // 🔍 디버깅: 스폰 포인트 상태 확인 - if (spawnPoints.Count == 0) - { - Debug.LogError($"[Connection] 스폰 포인트가 없습니다! 씬에 PlayerSpawnPoint가 있는지 확인하세요."); - } - response.Approved = true; response.CreatePlayerObject = true; - - // 스폰 위치 설정 + + // AutoSpawnPlayerPrefabClientSide를 false로 설정해야 서버에서 스폰 위치가 적용됩니다 + // 임시로 스폰 위치 설정 response.Position = GetSpawnPosition(request.ClientNetworkId); response.Rotation = GetSpawnRotation(request.ClientNetworkId); diff --git a/Assets/Scripts/NetworkManagerUI.cs.meta b/Assets/Scripts/NetworkManagerUI.cs.meta index 04623a5..74754fe 100644 --- a/Assets/Scripts/NetworkManagerUI.cs.meta +++ b/Assets/Scripts/NetworkManagerUI.cs.meta @@ -1,11 +1,2 @@ fileFormatVersion: 2 -guid: 8d7e6f5c4b3a2d1e0f9a8b7c6d5e4f3a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +guid: 8d7e6f5c4b3a2d1e0f9a8b7c6d5e4f3a \ No newline at end of file diff --git a/Assets/Scripts/NetworkPlayerController.cs b/Assets/Scripts/NetworkPlayerController.cs index 49dcc90..c2ccefd 100644 --- a/Assets/Scripts/NetworkPlayerController.cs +++ b/Assets/Scripts/NetworkPlayerController.cs @@ -1,337 +1,343 @@ using Unity.Netcode; using UnityEngine; -using UnityEngine.InputSystem; using Unity.Cinemachine; -using Northbound; -public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageable +namespace Northbound { - [Header("Movement Settings")] - public float moveSpeed = 5f; - public float rotationSpeed = 10f; - - [Header("Team Settings")] - [SerializeField] private TeamType initialTeam = TeamType.Player; - - [Header("Health Settings")] - [SerializeField] private int maxHealth = 100; - [SerializeField] private bool showHealthBar = true; - - [Header("Visual Effects")] - [SerializeField] private GameObject damageEffectPrefab; - [SerializeField] private GameObject deathEffectPrefab; - - private NetworkVariable _team = new NetworkVariable( - TeamType.Player, - NetworkVariableReadPermission.Everyone, - NetworkVariableWritePermission.Server - ); - - private NetworkVariable _currentHealth = new NetworkVariable( - 100, - NetworkVariableReadPermission.Everyone, - NetworkVariableWritePermission.Server - ); - - private Vector2 _moveInput; - private CharacterController _controller; - private PlayerInputActions _inputActions; - private Animator _animator; - - void Awake() + public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageable { - _controller = GetComponent(); - _animator = GetComponent(); - } + [Header("Movement Settings")] + public float moveSpeed = 5f; + public float rotationSpeed = 10f; - public override void OnNetworkSpawn() - { - base.OnNetworkSpawn(); + [Header("Team Settings")] + [SerializeField] private TeamType initialTeam = TeamType.Player; - // 서버에서 초기화 - if (IsServer) + [Header("Health Settings")] + [SerializeField] private int maxHealth = 100; + [SerializeField] private bool showHealthBar = true; + + [Header("Visual Effects")] + [SerializeField] private GameObject damageEffectPrefab; + [SerializeField] private GameObject deathEffectPrefab; + + private NetworkVariable _team = new NetworkVariable( + TeamType.Player, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Server + ); + + private NetworkVariable _currentHealth = new NetworkVariable( + 100, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Server + ); + + private Vector2 _moveInput; + private CharacterController _controller; + private PlayerInputActions _inputActions; + private Animator _animator; + + void Awake() + { + _controller = GetComponent(); + _animator = GetComponent(); + } + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + Debug.Log($"[Player] {gameObject.name} spawned. OwnerId: {OwnerClientId}, LocalClientId: {NetworkManager.Singleton.LocalClientId}, IsOwner: {IsOwner}, IsServer: {IsServer}"); + + if (IsOwner) + { + SetSpawnPosition(); + InitializePlayerServerRpc(initialTeam, maxHealth); + } + + _currentHealth.OnValueChanged += OnHealthChanged; + + if (!IsOwner) return; + + var vcam = GameObject.FindFirstObjectByType(); + + if (vcam != null) + { + vcam.Follow = transform; + vcam.LookAt = transform; + Debug.Log("[Camera] Camera attached to local player."); + } + + _inputActions = new PlayerInputActions(); + _inputActions.Enable(); + Debug.Log("[Player] Input actions enabled for local player."); + } + + [ServerRpc] + private void InitializePlayerServerRpc(TeamType team, int health) { if (_team.Value == TeamType.Neutral) - { - _team.Value = initialTeam; - } - + _team.Value = team; if (_currentHealth.Value == 0) + _currentHealth.Value = health; + + Debug.Log($"[Player] {gameObject.name} initialized (Team: {TeamManager.GetTeamName(_team.Value)}, HP: {_currentHealth.Value}/{maxHealth})"); + } + + private void SetSpawnPosition() + { + if (PlayerSpawnPositionSetter.Instance == null) { - _currentHealth.Value = maxHealth; + Debug.LogWarning("[Player] PlayerSpawnPositionSetter not found. Using default spawn position."); + return; } - Debug.Log($"[Player] {gameObject.name} 스폰됨 (팀: {TeamManager.GetTeamName(_team.Value)}, 체력: {_currentHealth.Value}/{maxHealth})"); + Vector3 spawnPos = PlayerSpawnPositionSetter.Instance.GetSpawnPosition(OwnerClientId); + Quaternion spawnRot = PlayerSpawnPositionSetter.Instance.GetSpawnRotation(OwnerClientId); + + transform.position = spawnPos; + transform.rotation = spawnRot; + + Debug.Log($"[Player] Spawn position set: {spawnPos}"); } - // 체력 변경 이벤트 구독 - _currentHealth.OnValueChanged += OnHealthChanged; - - if (!IsOwner) return; - - var vcam = GameObject.FindFirstObjectByType(); - - if (vcam != null) + public override void OnNetworkDespawn() { - vcam.Follow = transform; - vcam.LookAt = transform; - Debug.Log("[Camera] 로컬 플레이어에게 카메라가 연결되었습니다."); - } + _currentHealth.OnValueChanged -= OnHealthChanged; - _inputActions = new PlayerInputActions(); - _inputActions.Enable(); - } - - public override void OnNetworkDespawn() - { - _currentHealth.OnValueChanged -= OnHealthChanged; - - if (IsOwner && _inputActions != null) - { - _inputActions.Disable(); - } - - base.OnNetworkDespawn(); - } - - void Update() - { - if (!IsOwner) return; - - // 죽었으면 이동 불가 - if (_currentHealth.Value <= 0) return; - - // 액션/상호작용 중이면 이동 불가 - var attackAction = GetComponent(); - var playerInteraction = GetComponent(); - - bool isActionBlocked = (attackAction != null && attackAction.IsAttacking) || - (playerInteraction != null && playerInteraction.IsInteracting); - - if (isActionBlocked) - { - // 이동 불가 시 애니메이션 속도를 0으로 - if (_animator != null) + if (IsOwner && _inputActions != null) { - _animator.SetFloat("MoveSpeed", 0f); + _inputActions.Disable(); + _inputActions.Dispose(); } - return; + + base.OnNetworkDespawn(); } - _moveInput = _inputActions.Player.Move.ReadValue(); - Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y).normalized; - - if (move.magnitude >= 0.1f) + void Update() { - Quaternion targetRotation = Quaternion.LookRotation(move); - transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); + if (!IsOwner) return; - if (_controller != null) + if (_currentHealth.Value <= 0) return; + + var attackAction = GetComponent(); + var playerInteraction = GetComponent(); + + bool isActionBlocked = (attackAction != null && attackAction.IsAttacking) || + (playerInteraction != null && playerInteraction.IsInteracting); + + if (isActionBlocked) { - _controller.Move(move * moveSpeed * Time.deltaTime); - } - } - - if (_animator != null) - { - _animator.SetFloat("MoveSpeed", move.magnitude); - } - } - - #region ITeamMember Implementation - - public TeamType GetTeam() => _team.Value; - - public void SetTeam(TeamType team) - { - if (!IsServer) return; - - TeamType previousTeam = _team.Value; - _team.Value = team; - Debug.Log($"[Player] 팀 변경: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}"); - } - - #endregion - - #region IDamageable Implementation - - public void TakeDamage(int damage, ulong attackerId) - { - if (!IsServer) return; - - // 이미 죽었으면 무시 - if (_currentHealth.Value <= 0) return; - - // 공격자의 팀 확인 - if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(attackerId, out NetworkObject attackerObj)) - { - var attackerTeamMember = attackerObj.GetComponent(); - if (attackerTeamMember != null) - { - if (!TeamManager.CanAttack(attackerTeamMember, this)) + if (_animator != null) { - Debug.Log($"[Player] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다."); - return; + _animator.SetFloat("MoveSpeed", 0f); + } + return; + } + + _moveInput = _inputActions.Player.Move.ReadValue(); + Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y).normalized; + + if (move.magnitude >= 0.1f) + { + Quaternion targetRotation = Quaternion.LookRotation(move); + transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); + + if (_controller != null) + { + _controller.Move(move * moveSpeed * Time.deltaTime); } } + + if (_animator != null) + { + _animator.SetFloat("MoveSpeed", move.magnitude); + } } - // 데미지 적용 - int actualDamage = Mathf.Min(damage, _currentHealth.Value); - _currentHealth.Value -= actualDamage; + #region ITeamMember Implementation - Debug.Log($"[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}"); + public TeamType GetTeam() => _team.Value; - // 데미지 이펙트 - ShowDamageEffectClientRpc(); - - // 체력이 0이 되면 사망 - if (_currentHealth.Value <= 0) + public void SetTeam(TeamType team) { - Die(attackerId); + if (!IsOwner) return; + + SetTeamServerRpc(team); } - } - private void Die(ulong killerId) - { - if (!IsServer) return; - - Debug.Log($"[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) 사망했습니다! (킬러: {killerId})"); - - // 사망 이펙트 - ShowDeathEffectClientRpc(); - - // 애니메이션 (있는 경우) - if (_animator != null) + [ServerRpc] + private void SetTeamServerRpc(TeamType team) { - _animator.SetTrigger("Die"); + TeamType previousTeam = _team.Value; + _team.Value = team; + Debug.Log($"[Player] 팀 변경: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}"); } - // 일정 시간 후 리스폰 또는 디스폰 - Invoke(nameof(HandleDeath), 3f); - } + #endregion - private void HandleDeath() - { - if (!IsServer) return; + #region IDamageable Implementation - // 여기서 리스폰 로직을 추가하거나 게임 오버 처리 - // 예: 리스폰 위치로 이동 및 체력 회복 - Respawn(); - } - - private void Respawn() - { - if (!IsServer) return; - - // 체력 회복 - _currentHealth.Value = maxHealth; - - // 스폰 포인트로 이동 (PlayerSpawnPoint 활용) - var spawnPoints = FindObjectsByType(FindObjectsSortMode.None); - if (spawnPoints.Length > 0) + public void TakeDamage(int damage, ulong attackerId) { - var spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)]; - transform.position = spawnPoint.transform.position; - transform.rotation = spawnPoint.transform.rotation; + if (!IsOwner) + { + TakeDamageServerRpc(damage, attackerId); + return; + } + + TakeDamageServerRpc(damage, attackerId); } - Debug.Log($"[Player] {gameObject.name} 리스폰!"); - } - - [ClientRpc] - private void ShowDamageEffectClientRpc() - { - if (damageEffectPrefab != null) + [ServerRpc] + private void TakeDamageServerRpc(int damage, ulong attackerId) { - GameObject effect = Instantiate(damageEffectPrefab, transform.position + Vector3.up, Quaternion.identity); - Destroy(effect, 2f); - } - } + if (_currentHealth.Value <= 0) return; - [ClientRpc] - private void ShowDeathEffectClientRpc() - { - if (deathEffectPrefab != null) + if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(attackerId, out NetworkObject attackerObj)) + { + var attackerTeamMember = attackerObj.GetComponent(); + if (attackerTeamMember != null) + { + if (!TeamManager.CanAttack(attackerTeamMember, this)) + { + Debug.Log($"[Player] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다."); + return; + } + } + } + + int actualDamage = Mathf.Min(damage, _currentHealth.Value); + _currentHealth.Value -= actualDamage; + + Debug.Log($"[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}"); + + ShowDamageEffectClientRpc(); + + if (_currentHealth.Value <= 0) + { + Die(attackerId); + } + } + + private void Die(ulong killerId) { - GameObject effect = Instantiate(deathEffectPrefab, transform.position, Quaternion.identity); - Destroy(effect, 3f); + Debug.Log($"[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) 사망했습니다! (킬러: {killerId})"); + + ShowDeathEffectClientRpc(); + + if (_animator != null) + { + _animator.SetTrigger("Die"); + } + + Invoke(nameof(HandleDeath), 3f); } - } - #endregion - - #region Health Management - - /// - /// 현재 체력 - /// - public int GetCurrentHealth() => _currentHealth.Value; - - /// - /// 최대 체력 - /// - public int GetMaxHealth() => maxHealth; - - /// - /// 체력 비율 (0.0 ~ 1.0) - /// - public float GetHealthPercentage() - { - return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f; - } - - /// - /// 죽었는지 여부 - /// - public bool IsDead() => _currentHealth.Value <= 0; - - /// - /// 체력 회복 - /// - public void Heal(int amount) - { - if (!IsServer) return; - - int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value); - _currentHealth.Value += healAmount; - - Debug.Log($"[Player] {gameObject.name}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{maxHealth}"); - } - - private void OnHealthChanged(int previousValue, int newValue) - { - // 체력바 UI 업데이트 또는 체력 변경 시각 효과 - Debug.Log($"[Player] 체력 변경: {previousValue} → {newValue}"); - - // 클라이언트에서도 체력 변경 인지 가능 - if (IsOwner) + private void HandleDeath() { - // UI 업데이트 등 + RespawnServerRpc(); } - } - #endregion - - #region Gizmos - - private void OnDrawGizmosSelected() - { - #if UNITY_EDITOR - if (Application.isPlaying) + [ServerRpc] + private void RespawnServerRpc() { - string teamName = TeamManager.GetTeamName(_team.Value); - UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, - $"Player: {gameObject.name}\nTeam: {teamName}\nHP: {_currentHealth.Value}/{maxHealth}"); - } - else - { - string teamName = TeamManager.GetTeamName(initialTeam); - UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, - $"Player: {gameObject.name}\nTeam: {teamName}\nHP: {maxHealth}/{maxHealth}"); - } - #endif - } + _currentHealth.Value = maxHealth; - #endregion + var spawnPoints = FindObjectsByType(FindObjectsSortMode.None); + if (spawnPoints.Length > 0) + { + var spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)]; + transform.position = spawnPoint.transform.position; + transform.rotation = spawnPoint.transform.rotation; + } + + Debug.Log($"[Player] {gameObject.name} 리스폰!"); + } + + [ClientRpc] + private void ShowDamageEffectClientRpc() + { + if (damageEffectPrefab != null) + { + GameObject effect = Instantiate(damageEffectPrefab, transform.position + Vector3.up, Quaternion.identity); + Destroy(effect, 2f); + } + } + + [ClientRpc] + private void ShowDeathEffectClientRpc() + { + if (deathEffectPrefab != null) + { + GameObject effect = Instantiate(deathEffectPrefab, transform.position, Quaternion.identity); + Destroy(effect, 3f); + } + } + + #endregion + + #region Health Management + + public int GetCurrentHealth() => _currentHealth.Value; + + public int GetMaxHealth() => maxHealth; + + public float GetHealthPercentage() + { + return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f; + } + + public bool IsDead() => _currentHealth.Value <= 0; + + public void Heal(int amount) + { + if (!IsOwner) + { + HealServerRpc(amount); + return; + } + + HealServerRpc(amount); + } + + [ServerRpc] + private void HealServerRpc(int amount) + { + int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value); + _currentHealth.Value += healAmount; + + Debug.Log($"[Player] {gameObject.name}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{maxHealth}"); + } + + private void OnHealthChanged(int previousValue, int newValue) + { + Debug.Log($"[Player] 체력 변경: {previousValue} → {newValue}"); + } + + #endregion + + #region Gizmos + + private void OnDrawGizmosSelected() + { + #if UNITY_EDITOR + if (Application.isPlaying) + { + string teamName = TeamManager.GetTeamName(_team.Value); + UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, + $"Player: {gameObject.name}\nTeam: {teamName}\nHP: {_currentHealth.Value}/{maxHealth}"); + } + else + { + string teamName = TeamManager.GetTeamName(initialTeam); + UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, + $"Player: {gameObject.name}\nTeam: {teamName}\nHP: {maxHealth}/{maxHealth}"); + } + #endif + } + + #endregion + } } diff --git a/Assets/Scripts/NetworkPlayerController.cs.meta b/Assets/Scripts/NetworkPlayerController.cs.meta index fc5a6a8..00e9ed5 100644 --- a/Assets/Scripts/NetworkPlayerController.cs.meta +++ b/Assets/Scripts/NetworkPlayerController.cs.meta @@ -1,11 +1,2 @@ fileFormatVersion: 2 -guid: 9e8f7d6c5b4a3d2e1f0a9b8c7d6e5f4a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +guid: 19b1385c0dcb77240b2cfb3c6b10f717 \ No newline at end of file diff --git a/Assets/Scripts/NetworkSpawnHelper.cs b/Assets/Scripts/NetworkSpawnHelper.cs new file mode 100644 index 0000000..f172c6f --- /dev/null +++ b/Assets/Scripts/NetworkSpawnHelper.cs @@ -0,0 +1,55 @@ +using Unity.Netcode; +using UnityEngine; + +namespace Northbound +{ + public static class NetworkSpawnHelper + { + public static void AddFogOfWarVisibility(GameObject obj, bool showInExploredAreas = true, float updateInterval = 0.2f) + { + if (obj.GetComponent() == null) + { + var visibility = obj.AddComponent(); + visibility.showInExploredAreas = showInExploredAreas; + visibility.updateInterval = updateInterval; + } + } + + public static void PreparePreviewForRendering(GameObject preview) + { + NetworkObject netObj = preview.GetComponent(); + if (netObj != null) + { + Object.DestroyImmediate(netObj); + } + + Building building = preview.GetComponent(); + if (building != null) + { + Object.DestroyImmediate(building); + } + } + + public static void ApplyMaterialToPreview(GameObject preview, Material material) + { + Renderer[] renderers = preview.GetComponentsInChildren(); + foreach (var renderer in renderers) + { + Material[] mats = new Material[renderer.materials.Length]; + for (int i = 0; i < mats.Length; i++) + { + mats[i] = material; + } + renderer.materials = mats; + } + } + + public static void DisableColliders(GameObject obj) + { + foreach (var collider in obj.GetComponentsInChildren()) + { + collider.enabled = false; + } + } + } +} diff --git a/Assets/Scripts/NetworkSpawnHelper.cs.meta b/Assets/Scripts/NetworkSpawnHelper.cs.meta new file mode 100644 index 0000000..b6b47ed --- /dev/null +++ b/Assets/Scripts/NetworkSpawnHelper.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9b6c0b0cbea70604fac8231195b6b7b4 \ No newline at end of file diff --git a/Assets/Scripts/NetworkSpawnManager.cs b/Assets/Scripts/NetworkSpawnManager.cs index 23b3526..bc01b84 100644 --- a/Assets/Scripts/NetworkSpawnManager.cs +++ b/Assets/Scripts/NetworkSpawnManager.cs @@ -16,9 +16,9 @@ namespace Northbound public List spawnPoints = new List(); public bool useRandomSpawn = false; public bool findSpawnPointsAutomatically = true; - + private Dictionary _clientSpawnIndices = new Dictionary(); - private int _nextSpawnIndex = 0; + private NetworkVariable networkNextSpawnIndex = new NetworkVariable(0); private void Awake() { @@ -100,8 +100,8 @@ namespace Northbound { if (!_clientSpawnIndices.ContainsKey(clientId)) { - _clientSpawnIndices[clientId] = _nextSpawnIndex; - _nextSpawnIndex = (_nextSpawnIndex + 1) % spawnPoints.Count; + _clientSpawnIndices[clientId] = networkNextSpawnIndex.Value; + networkNextSpawnIndex.Value = (networkNextSpawnIndex.Value + 1) % spawnPoints.Count; } spawnIndex = _clientSpawnIndices[clientId]; } diff --git a/Assets/Scripts/PlayerActionSystem.cs b/Assets/Scripts/PlayerActionSystem.cs index 4759507..51acf69 100644 --- a/Assets/Scripts/PlayerActionSystem.cs +++ b/Assets/Scripts/PlayerActionSystem.cs @@ -5,10 +5,7 @@ using System.Collections.Generic; namespace Northbound { - /// - /// 상호작용 대상 없이 실행 가능한 액션들을 관리 - /// - public class PlayerActionSystem : NetworkBehaviour + public class PlayerActionSystem : InputActionManager { [Header("Actions")] public List actionComponents = new List(); @@ -16,7 +13,6 @@ namespace Northbound [Header("Animation")] public bool playAnimations = true; - private PlayerInputActions _inputActions; private Dictionary _actions = new Dictionary(); private Animator _animator; @@ -27,9 +23,10 @@ namespace Northbound public override void OnNetworkSpawn() { + base.OnNetworkSpawn(); + if (!IsOwner) return; - // 액션 컴포넌트들을 딕셔너리에 등록 foreach (var component in actionComponents) { if (component is IAction action) @@ -37,21 +34,16 @@ namespace Northbound _actions[action.GetActionName()] = action; } } - - _inputActions = new PlayerInputActions(); - _inputActions.Player.Attack.performed += OnAttack; - // 다른 액션들도 여기에 바인딩 - _inputActions.Enable(); } - public override void OnNetworkDespawn() + protected override void BindInputActions() { - if (IsOwner && _inputActions != null) - { - _inputActions.Player.Attack.performed -= OnAttack; - _inputActions.Disable(); - _inputActions.Dispose(); - } + _inputActions.Player.Attack.performed += OnAttack; + } + + protected override void UnbindInputActions() + { + _inputActions.Player.Attack.performed -= OnAttack; } private void OnAttack(InputAction.CallbackContext context) @@ -65,7 +57,6 @@ namespace Northbound { if (action.CanExecute(OwnerClientId)) { - // 애니메이션 재생 (액션 실행 전) if (playAnimations && _animator != null) { string animTrigger = action.GetActionAnimation(); @@ -79,15 +70,5 @@ namespace Northbound } } } - - override public void OnDestroy() - { - if (_inputActions != null) - { - _inputActions.Dispose(); - } - - base.OnDestroy(); - } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/PlayerController.cs.meta b/Assets/Scripts/PlayerController.cs.meta index f86b8ed..68c3895 100644 --- a/Assets/Scripts/PlayerController.cs.meta +++ b/Assets/Scripts/PlayerController.cs.meta @@ -1,11 +1,2 @@ fileFormatVersion: 2 -guid: 7a3e5b8c4d2f1a9e6b0c3d7e8f1a2b3c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +guid: 7a3e5b8c4d2f1a9e6b0c3d7e8f1a2b3c \ No newline at end of file diff --git a/Assets/Scripts/PlayerInteraction.cs b/Assets/Scripts/PlayerInteraction.cs index 59c6f7a..a6162ba 100644 --- a/Assets/Scripts/PlayerInteraction.cs +++ b/Assets/Scripts/PlayerInteraction.cs @@ -27,231 +27,170 @@ namespace Northbound [Header("Debug")] public bool showDebugRay = true; - + private PlayerInputActions _inputActions; private IInteractable _currentInteractable; private Camera _mainCamera; private Animator _animator; private EquipmentSocket _equipmentSocket; - - private EquipmentData _pendingEquipmentData; - private string _currentEquipmentSocket; - private bool _isInteracting = false; + private bool _isInteracting; + private float _interactionStartTime; + private const float INTERACTION_TIMEOUT = 3f; - // 다른 컴포넌트가 이동 차단 여부를 확인할 수 있도록 public 프로퍼티 제공 public bool IsInteracting => _isInteracting; - - public override void OnNetworkSpawn() + + private void Awake() { - if (!IsOwner) return; - - _mainCamera = Camera.main; _animator = GetComponent(); _equipmentSocket = GetComponent(); + } + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); if (rayOrigin == null) rayOrigin = transform; - + _inputActions = new PlayerInputActions(); _inputActions.Player.Interact.performed += OnInteract; _inputActions.Enable(); } - + public override void OnNetworkDespawn() { - if (IsOwner && _inputActions != null) + if (_inputActions != null) { _inputActions.Player.Interact.performed -= OnInteract; _inputActions.Disable(); _inputActions.Dispose(); } + + base.OnNetworkDespawn(); } - + private void Update() { if (!IsOwner) return; + + if (_isInteracting && Time.time - _interactionStartTime > INTERACTION_TIMEOUT) + { + _isInteracting = false; + } + DetectInteractable(); } - + private void DetectInteractable() { Vector3 origin = rayOrigin.position; Vector3 direction = useForwardDirection ? transform.forward : _mainCamera.transform.forward; Ray ray = new Ray(origin, direction); - + if (showDebugRay) { Debug.DrawRay(ray.origin, ray.direction * interactionRange, _currentInteractable != null ? Color.green : Color.yellow); } - + if (Physics.Raycast(ray, out RaycastHit hit, interactionRange, interactableLayer)) { IInteractable interactable = hit.collider.GetComponent(); if (interactable == null) + { interactable = hit.collider.GetComponentInParent(); - + } + if (interactable != null && interactable.CanInteract(OwnerClientId)) { _currentInteractable = interactable; return; } } - + _currentInteractable = null; } - + private void OnInteract(InputAction.CallbackContext context) { - if (blockDuringAnimation && _isInteracting) + if (blockDuringAnimation && IsInteracting) + return; + + if (_currentInteractable == null) return; - if (_currentInteractable != null) + var equipmentData = _currentInteractable.GetEquipmentData(); + string animTrigger = _currentInteractable.GetInteractionAnimation(); + bool hasAnimation = !string.IsNullOrEmpty(animTrigger); + + if (playAnimations && _animator != null && hasAnimation) { _isInteracting = true; - _pendingEquipmentData = _currentInteractable.GetEquipmentData(); + _interactionStartTime = Time.time; - string animTrigger = _currentInteractable.GetInteractionAnimation(); - bool hasAnimation = !string.IsNullOrEmpty(animTrigger); - - // 장비 장착 (애니메이션 이벤트 사용 안 할 경우) - if (!useAnimationEvents && useEquipment && _equipmentSocket != null && _pendingEquipmentData != null) - { - if (_pendingEquipmentData.attachOnStart && _pendingEquipmentData.equipmentPrefab != null) - { - AttachEquipment(); - - if (_pendingEquipmentData.detachOnEnd) - { - StartCoroutine(DetachEquipmentAfterDelay(2f)); - } - } - } - - // 애니메이션 재생 - if (playAnimations && _animator != null && hasAnimation) + if (useAnimationEvents) { _animator.SetTrigger(animTrigger); } else { - // 애니메이션이 없으면 즉시 상호작용 완료 + _animator.SetTrigger(animTrigger); + _currentInteractable.Interact(OwnerClientId); _isInteracting = false; } - - // 상호작용 실행 (서버에서 처리) + } + else + { _currentInteractable.Interact(OwnerClientId); } } - - // ======================================== - // Animation Event 함수들 - // ======================================== - - public void OnEquipTool() + + public void AttachEquipment() { - if (!useAnimationEvents || !useEquipment) return; - AttachEquipment(); - } + if (!IsOwner) return; + if (_currentInteractable == null || !useEquipment) return; - public void OnEquipTool(string socketName) + var equipmentData = _currentInteractable.GetEquipmentData(); + if (equipmentData == null || !equipmentData.attachOnStart) return; + + if (_equipmentSocket == null) + { + Debug.LogWarning("[PlayerInteraction] EquipmentSocket component not found on player"); + return; + } + + if (equipmentData.equipmentPrefab == null) + { + Debug.LogWarning("[PlayerInteraction] Equipment prefab is null. Assign a prefab in the Resource's EquipmentData in Inspector"); + return; + } + + _equipmentSocket.AttachToSocket(equipmentData.socketName, equipmentData.equipmentPrefab); + } + + public void DetachEquipment() { - if (!useAnimationEvents || !useEquipment) return; - AttachEquipment(socketName); - } + if (!IsOwner) return; + if (_currentInteractable == null || !useEquipment) return; - public void OnUnequipTool() - { - if (!useAnimationEvents || !useEquipment) return; - DetachEquipment(); - } + var equipmentData = _currentInteractable.GetEquipmentData(); + if (equipmentData == null || !equipmentData.detachOnEnd) return; - public void OnUnequipTool(string socketName) - { - if (!useAnimationEvents || !useEquipment) return; - DetachEquipment(socketName); - } + if (_equipmentSocket == null) return; + _equipmentSocket.DetachFromSocket(equipmentData.socketName); + } + public void OnInteractionComplete() { _isInteracting = false; - Debug.Log("[PlayerInteraction] 상호작용 완료"); - } - // ======================================== - // 내부 헬퍼 함수들 - // ======================================== - - private void AttachEquipment(string socketName = null) - { - if (_equipmentSocket == null || _pendingEquipmentData == null) - return; - - if (_pendingEquipmentData.equipmentPrefab == null) - return; - - string socket = socketName ?? _pendingEquipmentData.socketName; - _equipmentSocket.AttachToSocket(socket, _pendingEquipmentData.equipmentPrefab); - _currentEquipmentSocket = socket; - } - - private void DetachEquipment(string socketName = null) - { - if (_equipmentSocket == null) - return; - - string socket = socketName ?? _currentEquipmentSocket; - - if (!string.IsNullOrEmpty(socket)) + if (_currentInteractable != null) { - _equipmentSocket.DetachFromSocket(socket); - - if (socket == _currentEquipmentSocket) - _currentEquipmentSocket = null; - } - } - - private System.Collections.IEnumerator DetachEquipmentAfterDelay(float delay) - { - yield return new WaitForSeconds(delay); - DetachEquipment(); - - if (!useAnimationEvents) - { - _isInteracting = false; - } - } - - private void OnGUI() - { - if (!IsOwner || _currentInteractable == null) return; - - GUIStyle style = new GUIStyle(GUI.skin.label) - { - fontSize = 24, - alignment = TextAnchor.MiddleCenter - }; - style.normal.textColor = Color.white; - - string prompt = _currentInteractable.GetInteractionPrompt(); - - if (_isInteracting) - { - prompt += " (진행 중...)"; - style.normal.textColor = Color.yellow; - } - - GUI.Label(new Rect(Screen.width / 2 - 200, Screen.height - 100, 400, 50), prompt, style); - } - - public override void OnDestroy() - { - if (_inputActions != null) - { - _inputActions.Dispose(); + _currentInteractable.Interact(OwnerClientId); } } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/PlayerInventory.cs b/Assets/Scripts/PlayerInventory.cs new file mode 100644 index 0000000..108f243 --- /dev/null +++ b/Assets/Scripts/PlayerInventory.cs @@ -0,0 +1,73 @@ +using Unity.Netcode; +using UnityEngine; + +namespace Northbound +{ + public class PlayerInventory : NetworkBehaviour + { + [Header("Inventory Settings")] + public int maxResourceCapacity = 100; + + private NetworkVariable resourceCount = new NetworkVariable( + 0, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Owner + ); + + public int CurrentResourceAmount => resourceCount.Value; + public int MaxResourceCapacity => maxResourceCapacity; + + public bool CanAddResource(int amount) + { + return resourceCount.Value + amount <= maxResourceCapacity; + } + + public int GetAvailableSpace() + { + return maxResourceCapacity - resourceCount.Value; + } + + public void AddResources(int amount) + { + if (amount <= 0) return; + + int actualAmount = Mathf.Min(amount, maxResourceCapacity - resourceCount.Value); + resourceCount.Value += actualAmount; + + Debug.Log($"Player {OwnerClientId} added resources: +{actualAmount}, total: {resourceCount.Value}/{maxResourceCapacity}"); + } + + public void RemoveResources(int amount) + { + if (amount <= 0) return; + + int actualAmount = Mathf.Min(amount, resourceCount.Value); + resourceCount.Value -= actualAmount; + + Debug.Log($"Player {OwnerClientId} used resources: -{actualAmount}, total: {resourceCount.Value}/{maxResourceCapacity}"); + } + + public void SetResources(int amount) + { + resourceCount.Value = Mathf.Clamp(amount, 0, maxResourceCapacity); + } + + [Rpc(SendTo.Owner)] + public void AddResourcesRpc(int amount) + { + AddResources(amount); + } + + [Rpc(SendTo.Owner)] + public void RemoveResourcesRpc(int amount) + { + RemoveResources(amount); + } + + [Rpc(SendTo.Owner)] + public void SetResourcesRpc(int amount) + { + SetResources(amount); + } + } +} diff --git a/Assets/Scripts/PlayerInventory.cs.meta b/Assets/Scripts/PlayerInventory.cs.meta new file mode 100644 index 0000000..24dd336 --- /dev/null +++ b/Assets/Scripts/PlayerInventory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 90231c209cbef84469511de397004be9 \ No newline at end of file diff --git a/Assets/Scripts/PlayerResourceInventory.cs b/Assets/Scripts/PlayerResourceInventory.cs deleted file mode 100644 index 63b41ff..0000000 --- a/Assets/Scripts/PlayerResourceInventory.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Unity.Netcode; -using UnityEngine; - -namespace Northbound -{ - /// - /// 플레이어의 자원 인벤토리 관리 - /// - public class PlayerResourceInventory : NetworkBehaviour - { - [Header("Inventory Settings")] - public int maxResourceCapacity = 100; // 최대 자원 보유량 - - private NetworkVariable _currentResourceAmount = new NetworkVariable( - 0, - NetworkVariableReadPermission.Everyone, - NetworkVariableWritePermission.Server - ); - - public int CurrentResourceAmount => _currentResourceAmount.Value; - public int MaxResourceCapacity => maxResourceCapacity; - - /// - /// 자원을 추가할 수 있는지 확인 - /// - public bool CanAddResource(int amount) - { - return _currentResourceAmount.Value + amount <= maxResourceCapacity; - } - - /// - /// 추가 가능한 최대 자원량 계산 - /// - public int GetAvailableSpace() - { - return maxResourceCapacity - _currentResourceAmount.Value; - } - - /// - /// 자원 추가 (서버에서만 호출) - /// - [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] - public void AddResourceServerRpc(int amount) - { - if (amount <= 0) return; - - int actualAmount = Mathf.Min(amount, maxResourceCapacity - _currentResourceAmount.Value); - _currentResourceAmount.Value += actualAmount; - - Debug.Log($"플레이어 {OwnerClientId} - 자원 추가: +{actualAmount}, 현재: {_currentResourceAmount.Value}/{maxResourceCapacity}"); - } - - /// - /// 자원 제거 (서버에서만 호출) - /// - [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] - public void RemoveResourceServerRpc(int amount) - { - if (amount <= 0) return; - - int actualAmount = Mathf.Min(amount, _currentResourceAmount.Value); - _currentResourceAmount.Value -= actualAmount; - - Debug.Log($"플레이어 {OwnerClientId} - 자원 사용: -{actualAmount}, 현재: {_currentResourceAmount.Value}/{maxResourceCapacity}"); - } - - /// - /// 자원 설정 (서버에서만 호출) - /// - [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] - public void SetResourceServerRpc(int amount) - { - _currentResourceAmount.Value = Mathf.Clamp(amount, 0, maxResourceCapacity); - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/PlayerResourceInventory.cs.meta b/Assets/Scripts/PlayerResourceInventory.cs.meta deleted file mode 100644 index 5984f85..0000000 --- a/Assets/Scripts/PlayerResourceInventory.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3c64072402b0a3f46a674eb73c5541ac \ No newline at end of file diff --git a/Assets/Scripts/PlayerSpawnPositionSetter.cs b/Assets/Scripts/PlayerSpawnPositionSetter.cs new file mode 100644 index 0000000..b6159b3 --- /dev/null +++ b/Assets/Scripts/PlayerSpawnPositionSetter.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.Linq; +using Unity.Netcode; +using UnityEngine; + +namespace Northbound +{ + public class PlayerSpawnPositionSetter : NetworkBehaviour + { + public static PlayerSpawnPositionSetter Instance { get; private set; } + + [Header("Spawn Settings")] + public List spawnPoints = new List(); + public bool useRandomSpawn = false; + public bool findSpawnPointsAutomatically = true; + + private void Awake() + { + if (Instance != null && Instance != this) + { + Destroy(gameObject); + return; + } + Instance = this; + + if (findSpawnPointsAutomatically) + { + FindSpawnPoints(); + } + } + + private void FindSpawnPoints() + { + PlayerSpawnPoint[] points = FindObjectsByType(FindObjectsSortMode.None); + var sortedPoints = points.OrderBy(p => p.spawnIndex == -1 ? int.MaxValue : p.spawnIndex); + + spawnPoints.Clear(); + foreach (var point in sortedPoints) + { + if (point.isAvailable) + { + spawnPoints.Add(point.transform); + } + } + + Debug.Log($"[SpawnPositionSetter] {spawnPoints.Count}개의 스폰 포인트를 찾았습니다."); + } + + public Vector3 GetSpawnPosition(ulong clientId) + { + if (spawnPoints.Count == 0) + { + Debug.LogWarning("[SpawnPositionSetter] 스폰 포인트가 없습니다. 기본 위치 반환."); + return Vector3.zero; + } + + int spawnIndex = GetSpawnIndexForClient(clientId); + + Debug.Log($"[SpawnPositionSetter] 클라이언트 {clientId}에게 스폰 인덱스 {spawnIndex} 할당"); + return spawnPoints[spawnIndex].position; + } + + private int GetSpawnIndexForClient(ulong clientId) + { + if (useRandomSpawn) + { + return Random.Range(0, spawnPoints.Count); + } + + int spawnIndex; + if (IsServer) + { + spawnIndex = GetAssignedSpawnIndexServer(clientId); + } + else + { + spawnIndex = (int)(clientId % (ulong)spawnPoints.Count); + } + + return spawnIndex; + } + + private int GetAssignedSpawnIndexServer(ulong clientId) + { + List connectedClientIds = new List(NetworkManager.Singleton.ConnectedClientsIds); + connectedClientIds.Sort(); + + int index = connectedClientIds.IndexOf(clientId); + if (index < 0) index = 0; + + return index % spawnPoints.Count; + } + + public Quaternion GetSpawnRotation(ulong clientId) + { + if (spawnPoints.Count == 0) + return Quaternion.identity; + + int spawnIndex = GetSpawnIndexForClient(clientId); + return spawnPoints[spawnIndex].rotation; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/PlayerSpawnPositionSetter.cs.meta b/Assets/Scripts/PlayerSpawnPositionSetter.cs.meta new file mode 100644 index 0000000..3c48721 --- /dev/null +++ b/Assets/Scripts/PlayerSpawnPositionSetter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 49cd9c4e7c611b04c8740c9e049129b9 \ No newline at end of file diff --git a/Assets/Scripts/Resource.cs b/Assets/Scripts/Resource.cs index f79b949..2c43535 100644 --- a/Assets/Scripts/Resource.cs +++ b/Assets/Scripts/Resource.cs @@ -45,7 +45,7 @@ namespace Northbound public override void OnNetworkSpawn() { - if (IsServer) + if (IsOwner) { _currentResources.Value = maxResources; _lastRechargeTime = Time.time; @@ -54,7 +54,7 @@ namespace Northbound private void Update() { - if (!IsServer) + if (!IsOwner) return; // 자원 충전 로직 @@ -88,7 +88,7 @@ namespace Northbound { if (client.PlayerObject != null) { - var playerInventory = client.PlayerObject.GetComponent(); + var playerInventory = client.PlayerObject.GetComponent(); if (playerInventory != null) { // 플레이어가 받을 수 있는 공간이 없으면 상호작용 불가 @@ -120,10 +120,10 @@ namespace Northbound if (playerObject == null) return; - var playerInventory = playerObject.GetComponent(); + var playerInventory = playerObject.GetComponent(); if (playerInventory == null) { - Debug.LogWarning($"플레이어 {playerId}에게 PlayerResourceInventory 컴포넌트가 없습니다."); + Debug.LogWarning($"플레이어 {playerId}에게 PlayerInventory 컴포넌트가 없습니다."); return; } @@ -147,8 +147,8 @@ namespace Northbound _currentResources.Value -= gatheredAmount; _lastGatheringTime = Time.time; - // 플레이어에게 자원 추가 - playerInventory.AddResourceServerRpc(gatheredAmount); + // 플레이어에게 자원 추가 (RPC로 owner에게 요청) + playerInventory.AddResourcesRpc(gatheredAmount); Debug.Log($"플레이어 {playerId}가 {gatheredAmount} {resourceName}을(를) 채집했습니다. 남은 자원: {_currentResources.Value}"); diff --git a/Assets/Scripts/ResourcePickup.cs b/Assets/Scripts/ResourcePickup.cs index 2a21fee..519148d 100644 --- a/Assets/Scripts/ResourcePickup.cs +++ b/Assets/Scripts/ResourcePickup.cs @@ -42,7 +42,7 @@ namespace Northbound { if (client.PlayerObject != null) { - var playerInventory = client.PlayerObject.GetComponent(); + var playerInventory = client.PlayerObject.GetComponent(); if (playerInventory != null) { // 플레이어가 받을 수 있는 공간이 없으면 상호작용 불가 @@ -80,10 +80,10 @@ namespace Northbound if (playerObject == null) return; - var playerInventory = playerObject.GetComponent(); + var playerInventory = playerObject.GetComponent(); if (playerInventory == null) { - Debug.LogWarning($"플레이어 {playerId}에게 PlayerResourceInventory 컴포넌트가 없습니다."); + Debug.LogWarning($"플레이어 {playerId}에게 PlayerInventory 컴포넌트가 없습니다."); return; } @@ -100,8 +100,8 @@ namespace Northbound return; } - // 플레이어에게 자원 추가 - playerInventory.AddResourceServerRpc(collectedAmount); + // 플레이어에게 자원 추가 (RPC로 owner에게 요청) + playerInventory.AddResourcesRpc(collectedAmount); Debug.Log($"플레이어 {playerId}가 {collectedAmount} {resourceName}을(를) 획득했습니다."); diff --git a/Assets/Scripts/SmartAutoHost.cs b/Assets/Scripts/SmartAutoHost.cs new file mode 100644 index 0000000..3b70a3c --- /dev/null +++ b/Assets/Scripts/SmartAutoHost.cs @@ -0,0 +1,52 @@ +using UnityEngine; +using Unity.Netcode; + +public class SmartAutoHost : MonoBehaviour +{ +#if UNITY_EDITOR + private void Start() + { + if (NetworkManager.Singleton == null) + { + Debug.LogError("[SmartAutoHost] NetworkManager not found!"); + return; + } + + if (NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsClient) + { + return; + } + + bool isMainEditor = IsMainEditor(); + + if (isMainEditor) + { + NetworkManager.Singleton.StartHost(); + Debug.Log("[SmartAutoHost] MAIN EDITOR → Starting as HOST"); + } + else + { + NetworkManager.Singleton.StartClient(); + Debug.Log("[SmartAutoHost] SECONDARY EDITOR → Connecting as CLIENT"); + } + } + + private bool IsMainEditor() + { + string[] args = System.Environment.GetCommandLineArgs(); + return System.Array.Exists(args, arg => arg == "-mainEditor"); + } +#else + private void Start() + { + // 빌드된 버전은 항상 클라이언트 + if (NetworkManager.Singleton != null && + !NetworkManager.Singleton.IsServer && + !NetworkManager.Singleton.IsClient) + { + NetworkManager.Singleton.StartClient(); + Debug.Log("[SmartAutoHost] Build → Connecting as CLIENT"); + } + } +#endif +} diff --git a/Assets/Scripts/SmartAutoHost.cs.meta b/Assets/Scripts/SmartAutoHost.cs.meta new file mode 100644 index 0000000..cbe9936 --- /dev/null +++ b/Assets/Scripts/SmartAutoHost.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 13b6e5c1ebfd6fb4d939d2e99b25fffc \ No newline at end of file diff --git a/Assets/Scripts/TeamGate.cs b/Assets/Scripts/TeamGate.cs index b5f1249..512e27d 100644 --- a/Assets/Scripts/TeamGate.cs +++ b/Assets/Scripts/TeamGate.cs @@ -97,7 +97,7 @@ namespace Northbound private void OnTriggerEnter(Collider other) { // 서버에서만 텔레포트 처리 - if (!IsServer) return; + if (!IsOwner) return; // 어느 트리거에 진입했는지 확인 Collider triggeredCollider = null; diff --git a/Assets/Scripts/TeamMemberNetworkBehaviour.cs b/Assets/Scripts/TeamMemberNetworkBehaviour.cs new file mode 100644 index 0000000..0fc486b --- /dev/null +++ b/Assets/Scripts/TeamMemberNetworkBehaviour.cs @@ -0,0 +1,62 @@ +using Unity.Netcode; +using UnityEngine; + +namespace Northbound +{ + public abstract class TeamMemberNetworkBehaviour : NetworkBehaviour, ITeamMember + { + [SerializeField] protected TeamType initialTeam = TeamType.Player; + + protected NetworkVariable _team = new NetworkVariable( + TeamType.Neutral, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Server + ); + + public event System.Action OnTeamChanged; + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + if (IsOwner) + { + if (_team.Value == TeamType.Neutral) + { + _team.Value = initialTeam; + } + } + + _team.OnValueChanged += OnTeamValueChanged; + UpdateTeamVisuals(); + } + + public override void OnNetworkDespawn() + { + _team.OnValueChanged -= OnTeamValueChanged; + base.OnNetworkDespawn(); + } + + public TeamType GetTeam() => _team.Value; + + public void SetTeam(TeamType team) + { + if (!IsOwner) return; + + TeamType previousTeam = _team.Value; + _team.Value = team; + + Debug.Log($"[{GetType().Name}] {gameObject.name} team changed: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}"); + } + + protected virtual void OnTeamValueChanged(TeamType previousValue, TeamType newValue) + { + OnTeamChanged?.Invoke(newValue); + UpdateTeamVisuals(); + } + + protected virtual void UpdateTeamVisuals() + { + } + } +} diff --git a/Assets/Scripts/TeamMemberNetworkBehaviour.cs.meta b/Assets/Scripts/TeamMemberNetworkBehaviour.cs.meta new file mode 100644 index 0000000..d536936 --- /dev/null +++ b/Assets/Scripts/TeamMemberNetworkBehaviour.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 25aae83d83e1df146ad1b22af79848bb \ No newline at end of file diff --git a/Assets/UserChoices.choices b/Assets/UserChoices.choices new file mode 100644 index 0000000..b32598c --- /dev/null +++ b/Assets/UserChoices.choices @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 18cde282a8d045bf9d245fdcfaa7271b, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Multiplayer.Center.Editor::Unity.Multiplayer.Center.Questionnaire.UserChoicesObject + QuestionnaireVersion: 1.3 + UserAnswers: + Answers: + - QuestionId: Pace + Answers: + - Fast + - QuestionId: Cheating + Answers: + - CheatingNotImportant + - QuestionId: CostSensitivity + Answers: + - BestMargin + - QuestionId: NetcodeArchitecture + Answers: + - ClientServer + - QuestionId: PlayerCount + Answers: + - 4 + Preset: 11 + SelectedSolutions: + SelectedHostingModel: 4 + SelectedNetcodeSolution: 1 diff --git a/Assets/UserChoices.choices.meta b/Assets/UserChoices.choices.meta new file mode 100644 index 0000000..8a1afbc --- /dev/null +++ b/Assets/UserChoices.choices.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 624b6b15c0f5b4c4d912ee2e880c8581 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ExternAttributes.Editor.csproj b/ExternAttributes.Editor.csproj index d74c257..ac29e91 100644 --- a/ExternAttributes.Editor.csproj +++ b/ExternAttributes.Editor.csproj @@ -43,7 +43,6 @@ 6000.3.5f2 - @@ -631,6 +630,10 @@ C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll False + + Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll + False + Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll False diff --git a/FlatKit.Utils.Editor.csproj b/FlatKit.Utils.Editor.csproj index 598b48b..3e99887 100644 --- a/FlatKit.Utils.Editor.csproj +++ b/FlatKit.Utils.Editor.csproj @@ -43,7 +43,6 @@ 6000.3.5f2 - @@ -614,6 +613,10 @@ C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll False + + Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll + False + Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll False diff --git a/Northbound.sln b/Northbound.sln new file mode 100644 index 0000000..5016e48 --- /dev/null +++ b/Northbound.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{5C483496-7666-CA70-B23E-AE514115EE94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.RenderPipelines.Universal.Runtime", "Unity.RenderPipelines.Universal.Runtime.csproj", "{7D4DAA2B-AB80-6E9C-56F6-9BBE627B864B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlatKit.Utils.Editor", "FlatKit.Utils.Editor.csproj", "{9FFF03DB-13D2-F002-5CE2-72F68DD2B255}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExternAttributes.Editor", "ExternAttributes.Editor.csproj", "{2EE5E6D5-274E-E52C-7FC1-8ABD29E99E2F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{EF726A86-D8F8-62E9-98E4-841FF7F5D94D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5C483496-7666-CA70-B23E-AE514115EE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C483496-7666-CA70-B23E-AE514115EE94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C483496-7666-CA70-B23E-AE514115EE94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C483496-7666-CA70-B23E-AE514115EE94}.Release|Any CPU.Build.0 = Release|Any CPU + {7D4DAA2B-AB80-6E9C-56F6-9BBE627B864B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D4DAA2B-AB80-6E9C-56F6-9BBE627B864B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D4DAA2B-AB80-6E9C-56F6-9BBE627B864B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D4DAA2B-AB80-6E9C-56F6-9BBE627B864B}.Release|Any CPU.Build.0 = Release|Any CPU + {9FFF03DB-13D2-F002-5CE2-72F68DD2B255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FFF03DB-13D2-F002-5CE2-72F68DD2B255}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FFF03DB-13D2-F002-5CE2-72F68DD2B255}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FFF03DB-13D2-F002-5CE2-72F68DD2B255}.Release|Any CPU.Build.0 = Release|Any CPU + {2EE5E6D5-274E-E52C-7FC1-8ABD29E99E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2EE5E6D5-274E-E52C-7FC1-8ABD29E99E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EE5E6D5-274E-E52C-7FC1-8ABD29E99E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2EE5E6D5-274E-E52C-7FC1-8ABD29E99E2F}.Release|Any CPU.Build.0 = Release|Any CPU + {EF726A86-D8F8-62E9-98E4-841FF7F5D94D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF726A86-D8F8-62E9-98E4-841FF7F5D94D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF726A86-D8F8-62E9-98E4-841FF7F5D94D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF726A86-D8F8-62E9-98E4-841FF7F5D94D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Packages/manifest.json b/Packages/manifest.json index 5dce0f5..9cfe791 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,5 +1,6 @@ { "dependencies": { + "com.boxqkrtm.ide.cursor": "https://github.com/boxqkrtm/com.unity.ide.cursor.git", "com.unity.2d.animation": "13.0.4", "com.unity.2d.aseprite": "3.0.1", "com.unity.2d.psdimporter": "12.0.1", @@ -15,9 +16,12 @@ "com.unity.ide.visualstudio": "2.0.26", "com.unity.inputsystem": "1.17.0", "com.unity.multiplayer.center": "1.0.1", + "com.unity.multiplayer.center.quickstart": "1.1.1", + "com.unity.multiplayer.playmode": "2.0.1", "com.unity.multiplayer.tools": "2.2.7", - "com.unity.netcode.gameobjects": "2.8.0", + "com.unity.netcode.gameobjects": "2.8.1", "com.unity.render-pipelines.universal": "17.3.0", + "com.unity.services.multiplayer": "2.1.0", "com.unity.test-framework": "1.6.0", "com.unity.timeline": "1.8.10", "com.unity.ugui": "2.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 5b2109e..9c5ba18 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,5 +1,14 @@ { "dependencies": { + "com.boxqkrtm.ide.cursor": { + "version": "https://github.com/boxqkrtm/com.unity.ide.cursor.git", + "depth": 0, + "source": "git", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "hash": "53e1baac3cde90c46934f82ee1d61802d2bd6651" + }, "com.unity.2d.animation": { "version": "13.0.4", "depth": 0, @@ -196,6 +205,25 @@ "com.unity.modules.uielements": "1.0.0" } }, + "com.unity.multiplayer.center.quickstart": { + "version": "1.1.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.uielements": "1.0.0", + "com.unity.multiplayer.center": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.multiplayer.playmode": { + "version": "2.0.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "2.0.2" + }, + "url": "https://packages.unity.com" + }, "com.unity.multiplayer.tools": { "version": "2.2.7", "depth": 0, @@ -212,7 +240,7 @@ "url": "https://packages.unity.com" }, "com.unity.netcode.gameobjects": { - "version": "2.8.0", + "version": "2.8.1", "depth": 0, "source": "registry", "dependencies": { @@ -281,6 +309,87 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.services.authentication": { + "version": "3.6.0", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.services.core": "1.15.1", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.modules.unitywebrequest": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.core": { + "version": "1.16.0", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.modules.androidjni": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.modules.unitywebrequest": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.deployment": { + "version": "1.6.2", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.services.core": "1.15.1", + "com.unity.services.deployment.api": "1.1.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.deployment.api": { + "version": "1.1.3", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.services.multiplayer": { + "version": "2.1.0", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.transport": "2.5.0", + "com.unity.collections": "2.2.1", + "com.unity.services.qos": "1.3.0", + "com.unity.services.core": "1.15.1", + "com.unity.services.wire": "1.4.0", + "com.unity.services.deployment": "1.6.2", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.services.authentication": "3.6.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.qos": { + "version": "1.4.1", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.collections": "1.2.4", + "com.unity.services.core": "1.12.5", + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.services.authentication": "3.5.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.wire": { + "version": "1.4.1", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.services.core": "1.12.5", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.services.authentication": "2.7.4" + }, + "url": "https://packages.unity.com" + }, "com.unity.settings-manager": { "version": "2.1.1", "depth": 2, diff --git a/ProjectSettings/VirtualProjectsConfig.json b/ProjectSettings/VirtualProjectsConfig.json new file mode 100644 index 0000000..a47892c --- /dev/null +++ b/ProjectSettings/VirtualProjectsConfig.json @@ -0,0 +1,4 @@ +{ + "PlayerTags": [], + "version": "6000.3.5f2" +} \ No newline at end of file diff --git a/Unity.RenderPipelines.Universal.Runtime.csproj b/Unity.RenderPipelines.Universal.Runtime.csproj index 00a650f..6c754ae 100644 --- a/Unity.RenderPipelines.Universal.Runtime.csproj +++ b/Unity.RenderPipelines.Universal.Runtime.csproj @@ -43,15 +43,14 @@ 6000.3.5f2 - - + @@ -143,8 +142,8 @@ - + @@ -247,8 +246,8 @@ - + @@ -832,6 +831,10 @@ C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll False + + Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll + False + Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll False