네트워크 멀티플레이 대응

This commit is contained in:
2026-01-31 20:49:23 +09:00
parent 1152093521
commit c5bcf265d0
69 changed files with 2766 additions and 1392 deletions

View File

@@ -1,7 +0,0 @@
{
"permissions": {
"allow": [
"Bash(tree:*)"
]
}
}

6
.gitignore vendored
View File

@@ -8,4 +8,8 @@
[Uu]ser[Ss]ettings/
.vs
.vs/*
.vs/*
.vscode
.claude
Assets/_Recovery
Assets/_Recovery.meta

View File

@@ -43,7 +43,6 @@
<UnityVersion>6000.3.5f2</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\Extensions\Microsoft\Visual Studio Tools for Unity\Analyzers\Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.SourceGenerators.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
@@ -621,6 +620,10 @@
<HintPath>C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="websocket-sharp">
<HintPath>Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualScripting.YamlDotNet">
<HintPath>Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll</HintPath>
<Private>False</Private>
@@ -1213,10 +1216,22 @@
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.MetricTestData.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.QoS">
<HintPath>Library\ScriptAssemblies\Unity.Services.QoS.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Multiplayer.Editor.Shared">
<HintPath>Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Shared.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Rendering.LightTransport.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Rendering.LightTransport.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Netcode.Runtime">
<HintPath>Library\ScriptAssemblies\Unity.Netcode.Runtime.dll</HintPath>
<Private>False</Private>
@@ -1245,14 +1260,14 @@
<HintPath>Library\ScriptAssemblies\PPv2URPConverters.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.2D.Common.Editor">
<HintPath>Library\ScriptAssemblies\Unity.2D.Common.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.AI.Navigation.Updater">
<HintPath>Library\ScriptAssemblies\Unity.AI.Navigation.Updater.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.2D.Common.Editor">
<HintPath>Library\ScriptAssemblies\Unity.2D.Common.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.AI.Navigation.Editor">
<HintPath>Library\ScriptAssemblies\Unity.AI.Navigation.Editor.dll</HintPath>
<Private>False</Private>
@@ -1277,6 +1292,10 @@
<HintPath>Library\ScriptAssemblies\Unity.RenderPipelines.Universal.2D.Runtime.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core.Components">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.Components.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Tools.NetStatsMonitor.Configuration">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Configuration.dll</HintPath>
<Private>False</Private>
@@ -1289,8 +1308,12 @@
<HintPath>Library\ScriptAssemblies\Unity.2D.Aseprite.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Rider.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Rider.Editor.dll</HintPath>
<Reference Include="Unity.Cursor.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Cursor.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Authentication">
<HintPath>Library\ScriptAssemblies\Unity.Services.Authentication.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.AI.Navigation.Editor.ConversionSystem">
@@ -1301,10 +1324,22 @@
<HintPath>Library\ScriptAssemblies\Unity.2D.Aseprite.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Rider.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Rider.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary">
<HintPath>Library\ScriptAssemblies\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Authentication.Editor.Shared">
<HintPath>Library\ScriptAssemblies\Unity.Services.Authentication.Editor.Shared.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Multiplayer.Editor.Matchmaker.Authoring">
<HintPath>Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Matchmaker.Authoring.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualScripting.Flow">
<HintPath>Library\ScriptAssemblies\Unity.VisualScripting.Flow.dll</HintPath>
<Private>False</Private>
@@ -1317,6 +1352,10 @@
<HintPath>Library\ScriptAssemblies\Unity.2D.Tilemap.Extras.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Authentication.PlayerAccounts">
<HintPath>Library\ScriptAssemblies\Unity.Services.Authentication.PlayerAccounts.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualStudio.Editor">
<HintPath>Library\ScriptAssemblies\Unity.VisualStudio.Editor.dll</HintPath>
<Private>False</Private>
@@ -1349,6 +1388,14 @@
<HintPath>Library\ScriptAssemblies\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core.Environments">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.Environments.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Multiplayer">
<HintPath>Library\ScriptAssemblies\Unity.Services.Multiplayer.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="com.unity.multiplayer.tools.window">
<HintPath>Library\ScriptAssemblies\com.unity.multiplayer.tools.window.dll</HintPath>
<Private>False</Private>
@@ -1485,10 +1532,18 @@
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetVis.Editor.Visualization.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core.Analytics">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.Analytics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Tools.NetStatsMonitor.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Center.NetcodeForGameObjectsExample">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Center.NetcodeForGameObjectsExample.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Tools.DependencyInjection.UIElements">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.DependencyInjection.UIElements.dll</HintPath>
<Private>False</Private>
@@ -1553,6 +1608,10 @@
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Center.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Center.Integrations">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Center.Integrations.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Burst">
<HintPath>Library\ScriptAssemblies\Unity.Burst.dll</HintPath>
<Private>False</Private>

View File

@@ -43,16 +43,13 @@
<UnityVersion>6000.3.5f2</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\Extensions\Microsoft\Visual Studio Tools for Unity\Analyzers\Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.SourceGenerators.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="Assets\Scripts\NetworkManagerUI.cs" />
<Compile Include="Assets\Data\Scripts\DataClasses\WaveMasterData.cs" />
<Compile Include="Assets\FlatKit\Demos\[Demo] Desert\Scripts\FloatingMotion.cs" />
<Compile Include="Assets\Scripts\PlayerResourceInventory.cs" />
<Compile Include="Assets\Scripts\ITeamMember.cs" />
<Compile Include="Assets\Scripts\EnemyUnit.cs" />
<Compile Include="Assets\Scripts\PlayerActionSystem.cs" />
@@ -64,10 +61,12 @@
<Compile Include="Assets\Scripts\FogOfWarRenderer.cs" />
<Compile Include="Assets\Scripts\ResourcePickup.cs" />
<Compile Include="Assets\Scripts\PlayerVisionProvider.cs" />
<Compile Include="Assets\Scripts\InputActionManager.cs" />
<Compile Include="Assets\Scripts\PlayerController.cs" />
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\OrbitMotion.cs" />
<Compile Include="Assets\Scripts\BuildingDamageTest.cs" />
<Compile Include="Assets\Scripts\EnemyAIController.cs" />
<Compile Include="Assets\Scripts\GameConstants.cs" />
<Compile Include="Assets\Scripts\EquipmentSocket.cs" />
<Compile Include="Assets\Scripts\TeamManager.cs" />
<Compile Include="Assets\Scripts\TeamGate.cs" />
@@ -81,9 +80,12 @@
<Compile Include="Assets\Scripts\AttackAction.cs" />
<Compile Include="Assets\Scripts\IInteractable.cs" />
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\UvScroller.cs" />
<Compile Include="Assets\Scripts\TeamMemberNetworkBehaviour.cs" />
<Compile Include="Assets\Scripts\PlayerInventory.cs" />
<Compile Include="Assets\Scripts\NetworkSpawnHelper.cs" />
<Compile Include="Assets\Scripts\GlobalTimer.cs" />
<Compile Include="Assets\Scripts\SmartAutoHost.cs" />
<Compile Include="Assets\InputSystem_Actions.cs" />
<Compile Include="Assets\Data\Scripts\DataClasses\MonsterMasterData.cs" />
<Compile Include="Assets\FlatKit\Demos\[Demo] Desert\Scripts\BillboardLineRendererCircle.cs" />
<Compile Include="Assets\Scripts\BuildingHealthBar.cs" />
<Compile Include="Assets\Scripts\PlayerSpawnPoint.cs" />
@@ -93,15 +95,18 @@
<Compile Include="Assets\Scripts\BuildingManager.cs" />
<Compile Include="Assets\Scripts\GhostMaterialTest.cs" />
<Compile Include="Assets\Scripts\BuildingPlacement.cs" />
<Compile Include="Assets\Scripts\PlayerSpawnPositionSetter.cs" />
<Compile Include="Assets\Scripts\Core.cs" />
<Compile Include="Assets\Scripts\EnemyPortal.cs" />
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\LinearMotion.cs" />
<Compile Include="Assets\Scripts\IDamageable.cs" />
<Compile Include="Assets\Scripts\NetworkSpawnManager.cs" />
<Compile Include="Assets\Scripts\FogOfWarSystem.cs" />
<Compile Include="Assets\Data\Scripts\DataClasses\MonsterData.cs" />
<Compile Include="Assets\Scripts\IAction.cs" />
<Compile Include="Assets\Scripts\NetworkPlayerController.cs" />
<Compile Include="Assets\Scripts\PlayerInteraction.cs" />
<Compile Include="Assets\Scripts\DamageableNetworkBehaviour.cs" />
<Compile Include="Assets\Scripts\FogOfWarVisibility.cs" />
<Compile Include="Assets\Scripts\EnemyAIState.cs" />
<Compile Include="Assets\Scripts\EquipmentData.cs" />
@@ -687,6 +692,10 @@
<HintPath>C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Managed\UnityEngine\UnityEditor.XRModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="websocket-sharp">
<HintPath>Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Collections.LowLevel.ILSupport">
<HintPath>Library\PackageCache\com.unity.collections@aea9d3bd5e19\Unity.Collections.LowLevel.ILSupport\Unity.Collections.LowLevel.ILSupport.dll</HintPath>
<Private>False</Private>
@@ -1231,10 +1240,22 @@
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.MetricTestData.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.QoS">
<HintPath>Library\ScriptAssemblies\Unity.Services.QoS.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Multiplayer.Editor.Shared">
<HintPath>Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Shared.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Rendering.LightTransport.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Rendering.LightTransport.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Netcode.Runtime">
<HintPath>Library\ScriptAssemblies\Unity.Netcode.Runtime.dll</HintPath>
<Private>False</Private>
@@ -1263,14 +1284,14 @@
<HintPath>Library\ScriptAssemblies\PPv2URPConverters.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.2D.Common.Editor">
<HintPath>Library\ScriptAssemblies\Unity.2D.Common.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.AI.Navigation.Updater">
<HintPath>Library\ScriptAssemblies\Unity.AI.Navigation.Updater.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.2D.Common.Editor">
<HintPath>Library\ScriptAssemblies\Unity.2D.Common.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.AI.Navigation.Editor">
<HintPath>Library\ScriptAssemblies\Unity.AI.Navigation.Editor.dll</HintPath>
<Private>False</Private>
@@ -1295,6 +1316,10 @@
<HintPath>Library\ScriptAssemblies\Unity.RenderPipelines.Universal.2D.Runtime.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core.Components">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.Components.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Tools.NetStatsMonitor.Configuration">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Configuration.dll</HintPath>
<Private>False</Private>
@@ -1307,8 +1332,12 @@
<HintPath>Library\ScriptAssemblies\Unity.2D.Aseprite.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Rider.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Rider.Editor.dll</HintPath>
<Reference Include="Unity.Cursor.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Cursor.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Authentication">
<HintPath>Library\ScriptAssemblies\Unity.Services.Authentication.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.AI.Navigation.Editor.ConversionSystem">
@@ -1319,10 +1348,22 @@
<HintPath>Library\ScriptAssemblies\Unity.2D.Aseprite.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Rider.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Rider.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary">
<HintPath>Library\ScriptAssemblies\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Authentication.Editor.Shared">
<HintPath>Library\ScriptAssemblies\Unity.Services.Authentication.Editor.Shared.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Multiplayer.Editor.Matchmaker.Authoring">
<HintPath>Library\ScriptAssemblies\Unity.Services.Multiplayer.Editor.Matchmaker.Authoring.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualScripting.Flow">
<HintPath>Library\ScriptAssemblies\Unity.VisualScripting.Flow.dll</HintPath>
<Private>False</Private>
@@ -1335,6 +1376,10 @@
<HintPath>Library\ScriptAssemblies\Unity.2D.Tilemap.Extras.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Authentication.PlayerAccounts">
<HintPath>Library\ScriptAssemblies\Unity.Services.Authentication.PlayerAccounts.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualStudio.Editor">
<HintPath>Library\ScriptAssemblies\Unity.VisualStudio.Editor.dll</HintPath>
<Private>False</Private>
@@ -1367,6 +1412,14 @@
<HintPath>Library\ScriptAssemblies\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core.Environments">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.Environments.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Multiplayer">
<HintPath>Library\ScriptAssemblies\Unity.Services.Multiplayer.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="com.unity.multiplayer.tools.window">
<HintPath>Library\ScriptAssemblies\com.unity.multiplayer.tools.window.dll</HintPath>
<Private>False</Private>
@@ -1503,10 +1556,18 @@
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetVis.Editor.Visualization.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Services.Core.Analytics">
<HintPath>Library\ScriptAssemblies\Unity.Services.Core.Analytics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Tools.NetStatsMonitor.Editor">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.NetStatsMonitor.Editor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Center.NetcodeForGameObjectsExample">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Center.NetcodeForGameObjectsExample.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Tools.DependencyInjection.UIElements">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Tools.DependencyInjection.UIElements.dll</HintPath>
<Private>False</Private>
@@ -1571,6 +1632,10 @@
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Center.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Multiplayer.Center.Integrations">
<HintPath>Library\ScriptAssemblies\Unity.Multiplayer.Center.Integrations.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.Burst">
<HintPath>Library\ScriptAssemblies\Unity.Burst.dll</HintPath>
<Private>False</Private>

View File

@@ -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:

124
Assets/Prefabs/Bat.prefab Normal file
View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 66fa238e7512e1547bf2ef4b62d76afd
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -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

8
Assets/Resources.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0aef2ea1f5e3aed4d839d26090fcb09c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3d69632731200bd4a86a369c95ee485b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 63b758cca5ef327449b0debcea47b006
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4e920eca75dd2f44685f2be6a29e8b43
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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}

View File

@@ -31,8 +31,14 @@ namespace Northbound
private Animator _animator;
private ITeamMember _teamMember;
private EquipmentSocket _equipmentSocket;
private bool _isAttacking = false;
private NetworkVariable<bool> _isAttacking = new NetworkVariable<bool>(
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;
}
}

View File

@@ -1,2 +1,2 @@
fileFormatVersion: 2
guid: 66ec3984614d8a64b8eae821376d038d
guid: c467f66382052bc429affc5cb01d17db

View File

@@ -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("<color=yellow><b>[AutoHost]</b> 에디터 전용 호스트 자동 시작됨</color>");
}
}
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("<color=yellow><b>[AutoHost]</b> 호스트로 시작됨 (MAIN EDITOR)</color>");
}
catch (System.Exception e)
{
Debug.Log($"<color=orange><b>[AutoHost]</b> 호스트 시작 실패: {e.Message}</color>");
Debug.Log("<color=blue><b>[AutoHost]</b> 클라이언트 모드로 전환...</color>");
NetworkManager.Singleton.Shutdown();
TryStartAsClient();
}
}
private void TryStartAsClient()
{
try
{
NetworkManager.Singleton.StartClient();
_hasStarted = true;
Debug.Log("<color=blue><b>[AutoHost]</b> 클라이언트로 연결됨 (SECONDARY EDITOR)</color>");
}
catch (System.Exception e)
{
Debug.LogError($"<color=red><b>[AutoHost]</b> 클라이언트 연결 실패: {e.Message}</color>");
}
}
private void OnDestroy()
{
if (NetworkManager.Singleton != null)
{
NetworkManager.Singleton.Shutdown();
}
}
#endif
}

View File

@@ -44,7 +44,7 @@ namespace Northbound
private void Update()
{
if (!IsServer) return;
if (!IsOwner) return;
if (_teamMember == null) return;
if (Time.time - _lastAttackTime >= attackInterval)

View File

@@ -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<int> _currentHealth = new NetworkVariable<int>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
// 건물 소유자 (사전 배치 건물 또는 동적 건물 모두 지원)
private NetworkVariable<ulong> _ownerId = new NetworkVariable<ulong>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
// 건물 팀
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
TeamType.Neutral,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
// 이벤트
public event Action<int, int> OnHealthChanged; // (current, max)
public event Action OnDestroyed;
public event Action<TeamType> 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($"<color=cyan>[Building] 사전 배치 건물 '{buildingData?.buildingName ?? gameObject.name}' 소유자: {initialOwnerId}, 팀: {_team.Value}</color>");
}
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;
}
}
}
/// <summary>
/// 건물 초기화 (BuildingManager가 동적 생성 시 호출)
/// </summary>
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;
}
/// <summary>
/// 건물 소유권 변경 (점령 등)
/// </summary>
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($"<color=yellow>[Building] {buildingData?.buildingName ?? ""} 소유권 변경: {previousOwner} → {newOwnerId}, : {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(newTeam)}</color>");
Debug.Log($"<color=yellow>[Building] {buildingData?.buildingName ?? "Building"} ownership changed: {previousOwner} → {newOwnerId}, team: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(newTeam)}</color>");
// 시야 제공자 재등록 (소유자가 바뀌었으므로)
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($"<color=cyan>[Building] {buildingData?.buildingName ?? ""} 팀 변경: {TeamManager.GetTeamName(previousValue)} → {TeamManager.GetTeamName(newValue)}</color>");
Debug.Log($"<color=cyan>[Building] {buildingData?.buildingName ?? "Building"} team changed: {TeamManager.GetTeamName(previousValue)} → {TeamManager.GetTeamName(newValue)}</color>");
}
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($"<color=red>[Building] {buildingData?.buildingName ?? "Building"} ({TeamManager.GetTeamName(_team.Value)}) destroyed! Attacker: {killerId}</color>");
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($"<color=yellow>[Building] {buildingData.buildingName}은(는) 무적입니다.</color>");
Debug.Log($"<color=yellow>[Building] {buildingData.buildingName} is indestructible.</color>");
return;
}
// 이미 파괴됨
if (_currentHealth.Value <= 0)
return;
// 공격자의 팀 확인 (팀 공격 방지)
var attackerObj = NetworkManager.Singleton.SpawnManager.SpawnedObjects[attackerId];
var attackerTeamMember = attackerObj?.GetComponent<ITeamMember>();
if (attackerTeamMember != null)
{
if (!TeamManager.CanAttack(attackerTeamMember, this))
{
Debug.Log($"<color=yellow>[Building] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다.</color>");
Debug.Log($"<color=yellow>[Building] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} team cannot attack {TeamManager.GetTeamName(_team.Value)} team.</color>");
return;
}
}
// 데미지 적용
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
_currentHealth.Value -= actualDamage;
Debug.Log($"<color=red>[Building] {buildingData?.buildingName ?? ""} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{buildingData?.maxHealth ?? 100}</color>");
// 데미지 이펙트
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
/// <summary>
/// 건물 파괴
/// </summary>
private void DestroyBuilding(ulong attackerId)
public new int GetMaxHealth()
{
if (!IsServer)
return;
Debug.Log($"<color=red>[Building] {buildingData?.buildingName ?? ""} ({TeamManager.GetTeamName(_team.Value)})이(가) 파괴되었습니다! (공격자: {attackerId})</color>");
// 파괴 이벤트 발생
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;
}
/// <summary>
/// 체력 회복
/// </summary>
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($"<color=green>[Building] {buildingData.buildingName}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{buildingData.maxHealth}</color>");
}
/// <summary>
/// 현재 체력
/// </summary>
public int GetCurrentHealth() => _currentHealth.Value;
/// <summary>
/// 최대 체력
/// </summary>
public int GetMaxHealth() => buildingData != null ? buildingData.maxHealth : 100;
/// <summary>
/// 체력 비율 (0.0 ~ 1.0)
/// </summary>
public float GetHealthPercentage()
{
int maxHealth = GetMaxHealth();
return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f;
}
/// <summary>
/// 파괴되었는지 여부
/// </summary>
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<BuildingHealthBar>();
if (_healthBar != null)
if (healthBarPrefab != null)
{
_healthBar.Initialize(this);
GameObject healthBarObj = Instantiate(healthBarPrefab, transform);
_healthBar = healthBarObj.GetComponent<BuildingHealthBar>();
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
/// <summary>
/// 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
/// </summary>
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);
}
/// <summary>
/// Legacy method, use GetGridBounds() instead
/// </summary>
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

View File

@@ -75,22 +75,32 @@ namespace Northbound
/// </summary>
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<BoxCollider>();
}
// 상호작용 가능한 크기로 설정 (전체 건물 높이가 아 접근 가능한 크기)
// 상호작용 가능한 크기로 설정 (전체 건물 높이가 아니라 접근 가능한 크기)
_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($"<color=yellow>[BuildingFoundation] 토대 생성: {data.buildingName}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {team}</color>");
Debug.Log($"<color=yellow>[BuildingFoundation] 토대 생성: {buildingData?.buildingName ?? "Building"}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {TeamManager.GetTeamName(team)}</color>");
}
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);
}
/// <summary>
@@ -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($"<color=green>[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%)</color>");
Debug.Log($"<color=yellow>[BuildingFoundation] {buildingData.buildingName} 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount}</color>");
// 완 체크
// 완 체크
if (_currentProgress.Value >= buildingData.requiredWorkAmount)
{
CompleteConstruction();
CompleteConstruction(playerId);
}
}
private void CompleteConstruction(ulong playerId)
{
Debug.Log($"<color=green>[BuildingFoundation] {buildingData.buildingName} 건설 완료! 완성자: {playerId}</color>");
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<ITeamMember>();
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
/// <summary>
/// 플레이어의 팀 가져오기
/// </summary>
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<ITeamMember>();
if (teamMember != null)
{
return teamMember.GetTeam();
}
}
}
}
// 기본값: 플레이어 팀
return TeamType.Player;
}
private void CompleteConstruction()
{
if (!IsServer) return;
Debug.Log($"<color=cyan>[BuildingFoundation] 건물 완성! {buildingData.buildingName}</color>");
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<BuildingHealthBar>();
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);
}
}
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Northbound
public List<BuildingData> availableBuildings = new List<BuildingData>();
[Header("Foundation Settings")]
public GameObject foundationPrefab; // 토대 프리팹 (Inspector에서 할당)
public GameObject foundationPrefab;
private List<Building> placedBuildings = new List<Building>();
private List<BuildingFoundation> placedFoundations = new List<BuildingFoundation>();
@@ -58,6 +58,53 @@ namespace Northbound
);
}
private bool ValidateClient(ulong clientId)
{
if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {clientId}</color>");
return false;
}
return true;
}
private bool ValidateBuildingIndex(int buildingIndex)
{
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}</color>");
return false;
}
return true;
}
private BuildingData GetBuildingData(int buildingIndex)
{
BuildingData data = availableBuildings[buildingIndex];
if (data == null || data.prefab == null)
{
Debug.LogWarning($"<color=red>[BuildingManager] 건물 데이터가 유효하지 않습니다.</color>");
return null;
}
return data;
}
private void SetupSpawnedObject(GameObject obj, ulong ownerId)
{
if (obj.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = obj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true;
visibility.updateInterval = 0.2f;
}
NetworkObject netObj = obj.GetComponent<NetworkObject>();
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($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}</color>");
return;
}
// 보안 검증 2: 건물 인덱스 유효성 확인
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
if (!ValidateBuildingIndex(buildingIndex))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex} (클라이언트: {requestingClientId})</color>");
return;
}
BuildingData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
if (data == null || data.prefab == null)
BuildingData data = GetBuildingData(buildingIndex);
if (data == null)
{
Debug.LogWarning($"<color=red>[BuildingManager] 건물 데이터가 유효하지 않습니다. (클라이언트: {requestingClientId})</color>");
return;
}
// 배치 가능 여부 확인
if (!IsValidPlacement(data, position, rotation, out Vector3 snappedPosition))
{
Debug.LogWarning($"<color=yellow>[BuildingManager] 건물 배치 불가능 위치 (클라이언트: {requestingClientId})</color>");
@@ -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<NetworkObject>();
// Add FogOfWarVisibility component to hide buildings in unexplored areas
if (buildingObj.GetComponent<FogOfWarVisibility>() == null)
SetupSpawnedObject(buildingObj, requestingClientId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
var visibility = buildingObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true; // Buildings remain visible in explored areas
visibility.updateInterval = 0.2f;
}
if (netObj != null)
{
// 건물의 소유자를 설정
netObj.SpawnWithOwnership(requestingClientId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
building = buildingObj.AddComponent<Building>();
}
// 건물 초기화
building.Initialize(data, gridPosition, rotation, requestingClientId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
else
{
Debug.LogError($"<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다! (Prefab: {data.prefab.name})</color>");
Debug.LogError($"<color=red>[BuildingManager] Building prefab must have Building component! (Prefab: {data.prefab.name})</color>");
buildingObj.GetComponent<NetworkObject>()?.Despawn(true);
Destroy(buildingObj);
return;
}
building.Initialize(data, gridPosition, rotation, requestingClientId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
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($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}</color>");
return;
}
// 보안 검증 2: 건물 인덱스 유효성 확인
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
if (!ValidateBuildingIndex(buildingIndex))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}</color>");
return;
}
BuildingData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
BuildingData data = GetBuildingData(buildingIndex);
if (data == null)
{
Debug.LogWarning($"<color=red>[BuildingManager] 건물 데이터가 유효하지 않습니다.</color>");
return;
}
// 토대 프리팹 확인
if (foundationPrefab == null)
{
Debug.LogError("<color=red>[BuildingManager] foundationPrefab이 설정되지 않았습니다!</color>");
return;
}
// 배치 가능 여부 확인
if (!IsValidPlacement(data, position, rotation, out Vector3 snappedPosition))
{
Debug.LogWarning($"<color=yellow>[BuildingManager] 토대 배치 불가능 위치</color>");
@@ -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<NetworkObject>();
// Add FogOfWarVisibility component to hide foundations in unexplored areas
if (foundationObj.GetComponent<FogOfWarVisibility>() == null)
SetupSpawnedObject(foundationObj, requestingClientId);
BuildingFoundation foundation = foundationObj.GetComponent<BuildingFoundation>();
if (foundation != null)
{
var visibility = foundationObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true; // Foundations remain visible in explored areas
visibility.updateInterval = 0.2f;
}
if (netObj != null)
{
netObj.SpawnWithOwnership(requestingClientId);
BuildingFoundation foundation = foundationObj.GetComponent<BuildingFoundation>();
if (foundation != null)
{
foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam);
placedFoundations.Add(foundation); // 토대 목록에 추가
Debug.Log($"<color=yellow>[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
else
{
Debug.LogError("<color=red>[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!</color>");
netObj.Despawn(true);
}
foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam);
placedFoundations.Add(foundation);
Debug.Log($"<color=yellow>[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
else
{
Debug.LogError("<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다!</color>");
Destroy(foundationObj);
Debug.LogError("<color=red>[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!</color>");
foundationObj.GetComponent<NetworkObject>()?.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<NetworkObject>();
// Add FogOfWarVisibility component to hide buildings in unexplored areas
if (buildingObj.GetComponent<FogOfWarVisibility>() == null)
SetupSpawnedObject(buildingObj, ownerId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
var visibility = buildingObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true; // Buildings remain visible in explored areas
visibility.updateInterval = 0.2f;
}
if (netObj != null)
{
netObj.SpawnWithOwnership(ownerId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
building = buildingObj.AddComponent<Building>();
}
// 건물 초기화
building.Initialize(data, gridPosition, rotation, ownerId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료! (소유자: {ownerId}, 위치: {gridPosition}, 팀: {team})</color>");
}
else
{
Debug.LogError($"<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다!</color>");
Debug.LogError($"<color=red>[BuildingManager] Building prefab must have Building component! (Prefab: {data.prefab.name})</color>");
buildingObj.GetComponent<NetworkObject>()?.Despawn(true);
Destroy(buildingObj);
return;
}
building.Initialize(data, gridPosition, rotation, ownerId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료! (소유자: {ownerId}, 위치: {gridPosition}, 팀: {team})</color>");
}
}
}

View File

@@ -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<NetworkObject>();
return true;
}
private void SetupPreviewObject(GameObject previewObj, Material material)
{
NetworkObject netObj = previewObj.GetComponent<NetworkObject>();
if (netObj != null)
{
Destroy(netObj);
}
// Remove Building component from preview
Building building = previewObject.GetComponent<Building>();
Building building = previewObj.GetComponent<Building>();
if (building != null)
{
Destroy(building);
}
// Apply ghost materials
previewRenderers = previewObject.GetComponentsInChildren<Renderer>();
foreach (var renderer in previewRenderers)
Renderer[] renderers = previewObj.GetComponentsInChildren<Renderer>();
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<Collider>())
foreach (var collider in previewObj.GetComponentsInChildren<Collider>())
{
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<Vector3> 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<NetworkObject>() != null)
Destroy(preview.GetComponent<NetworkObject>());
if (preview.GetComponent<Building>() != null)
Destroy(preview.GetComponent<Building>());
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<Renderer>();
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>())
{
collider.enabled = false;
}
dragPreviewObjects.Add(preview);
if (isValid)
{
dragBuildingPositions.Add(snappedPosition);

View File

@@ -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($"<color=red>[Core] 코어가 파괴되었습니다! 게임 오버!</color>");
@@ -174,7 +174,7 @@ namespace Northbound
/// </summary>
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<PlayerResourceInventory>();
var playerInventory = client.PlayerObject.GetComponent<PlayerInventory>();
if (playerInventory != null)
{
// 플레이어가 자원을 가지고 있어야 함
@@ -264,10 +264,10 @@ namespace Northbound
if (playerObject == null)
return;
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
var playerInventory = playerObject.GetComponent<PlayerInventory>();
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;

View File

@@ -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<int> _currentHealth = new NetworkVariable<int>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
public event Action<int, int> 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($"<color=red>[{GetType().Name}] {gameObject.name} received {actualDamage} damage. Health: {_currentHealth.Value}/{maxHealth}</color>");
ShowDamageEffectClientRpc();
if (_currentHealth.Value <= 0)
{
Die(attackerId);
}
}
protected virtual void Die(ulong killerId)
{
Debug.Log($"<color=red>[{GetType().Name}] {gameObject.name} destroyed! Killer: {killerId}</color>");
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($"<color=green>[{GetType().Name}] {gameObject.name} healed {healAmount}. Health: {_currentHealth.Value}/{maxHealth}</color>");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b7aeb2eee85ba3c42ab4d53ef28f96fc

View File

@@ -72,13 +72,13 @@ namespace Northbound
private NetworkVariable<EnemyAIState> _currentState = new NetworkVariable<EnemyAIState>(
EnemyAIState.Idle,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
NetworkVariableWritePermission.Owner
);
private NetworkVariable<ulong> _targetPlayerId = new NetworkVariable<ulong>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
NetworkVariableWritePermission.Owner
);
private GameObject _cachedTargetPlayer;
@@ -91,7 +91,7 @@ namespace Northbound
_enemyUnit = GetComponent<EnemyUnit>();
_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)

View File

@@ -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;
}

View File

@@ -1,29 +1,31 @@
using UnityEngine;
using System.Collections.Generic;
using Unity.Netcode;
namespace Northbound
{
/// <summary>
/// 플레이어의 장비 소켓 관리 (손, 등, 허리 등)
/// </summary>
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<Socket> sockets = new List<Socket>();
[Header("Equipment Prefabs")]
public GameObject[] equipmentPrefabs;
private Dictionary<string, Socket> _socketDict = new Dictionary<string, Socket>();
private Dictionary<string, GameObject> _prefabDict = new Dictionary<string, GameObject>();
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;
}
}
}
/// <summary>
/// 소켓에 장비 부착
/// </summary>
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;
}
/// <summary>
/// 소켓에서 장비 제거
/// </summary>
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;
}
}
}
}
/// <summary>
/// 모든 소켓에서 장비 제거
/// </summary>
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;
}
}
/// <summary>
/// 특정 소켓에 장비가 있는지 확인
/// </summary>
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<GameObject>($"Prefabs/{name}");
if (prefab != null)
{
return prefab;
}
return Resources.Load<GameObject>(name);
}
}
}
}

View File

@@ -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)

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 55b873d6b94628148b3c61c4f5d435fc

View File

@@ -133,7 +133,7 @@ namespace Northbound
return;
}
var inventory = localPlayer.GetComponent<PlayerResourceInventory>();
var inventory = localPlayer.GetComponent<PlayerInventory>();
if (inventory == null)
{
playerResourceText.text = playerPrefix + "---";

View File

@@ -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
/// </summary>
public void StartTimer()
{
if (!IsServer) return;
if (!IsOwner) return;
_currentTime.Value = cycleLength;
_isRunning.Value = true;
@@ -181,7 +181,7 @@ namespace Northbound
/// </summary>
public void PauseTimer()
{
if (!IsServer) return;
if (!IsOwner) return;
_isRunning.Value = false;
@@ -194,7 +194,7 @@ namespace Northbound
/// </summary>
public void ResumeTimer()
{
if (!IsServer) return;
if (!IsOwner) return;
_isRunning.Value = true;
@@ -207,7 +207,7 @@ namespace Northbound
/// </summary>
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);
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 06c90984b0af66542bd5011748fe67c1

View File

@@ -68,16 +68,11 @@ namespace Northbound
NetworkManager.ConnectionApprovalRequest request,
NetworkManager.ConnectionApprovalResponse response)
{
// 🔍 디버깅: 스폰 포인트 상태 확인
if (spawnPoints.Count == 0)
{
Debug.LogError($"<color=red>[Connection] 스폰 포인트가 없습니다! 씬에 PlayerSpawnPoint가 있는지 확인하세요.</color>");
}
response.Approved = true;
response.CreatePlayerObject = true;
// 스폰 위치 설정
// AutoSpawnPlayerPrefabClientSide를 false로 설정해야 서버에서 스폰 위치가 적용됩니다
// 임시로 스폰 위치 설정
response.Position = GetSpawnPosition(request.ClientNetworkId);
response.Rotation = GetSpawnRotation(request.ClientNetworkId);

View File

@@ -1,11 +1,2 @@
fileFormatVersion: 2
guid: 8d7e6f5c4b3a2d1e0f9a8b7c6d5e4f3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
guid: 8d7e6f5c4b3a2d1e0f9a8b7c6d5e4f3a

View File

@@ -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<TeamType> _team = new NetworkVariable<TeamType>(
TeamType.Player,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private NetworkVariable<int> _currentHealth = new NetworkVariable<int>(
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<CharacterController>();
_animator = GetComponent<Animator>();
}
[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<TeamType> _team = new NetworkVariable<TeamType>(
TeamType.Player,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private NetworkVariable<int> _currentHealth = new NetworkVariable<int>(
100,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private Vector2 _moveInput;
private CharacterController _controller;
private PlayerInputActions _inputActions;
private Animator _animator;
void Awake()
{
_controller = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
Debug.Log($"<color=cyan>[Player] {gameObject.name} spawned. OwnerId: {OwnerClientId}, LocalClientId: {NetworkManager.Singleton.LocalClientId}, IsOwner: {IsOwner}, IsServer: {IsServer}</color>");
if (IsOwner)
{
SetSpawnPosition();
InitializePlayerServerRpc(initialTeam, maxHealth);
}
_currentHealth.OnValueChanged += OnHealthChanged;
if (!IsOwner) return;
var vcam = GameObject.FindFirstObjectByType<CinemachineCamera>();
if (vcam != null)
{
vcam.Follow = transform;
vcam.LookAt = transform;
Debug.Log("<color=green>[Camera] Camera attached to local player.</color>");
}
_inputActions = new PlayerInputActions();
_inputActions.Enable();
Debug.Log("<color=green>[Player] Input actions enabled for local player.</color>");
}
[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($"<color=cyan>[Player] {gameObject.name} initialized (Team: {TeamManager.GetTeamName(_team.Value)}, HP: {_currentHealth.Value}/{maxHealth})</color>");
}
private void SetSpawnPosition()
{
if (PlayerSpawnPositionSetter.Instance == null)
{
_currentHealth.Value = maxHealth;
Debug.LogWarning("[Player] PlayerSpawnPositionSetter not found. Using default spawn position.");
return;
}
Debug.Log($"<color=cyan>[Player] {gameObject.name} 스폰됨 (팀: {TeamManager.GetTeamName(_team.Value)}, 체력: {_currentHealth.Value}/{maxHealth})</color>");
Vector3 spawnPos = PlayerSpawnPositionSetter.Instance.GetSpawnPosition(OwnerClientId);
Quaternion spawnRot = PlayerSpawnPositionSetter.Instance.GetSpawnRotation(OwnerClientId);
transform.position = spawnPos;
transform.rotation = spawnRot;
Debug.Log($"<color=yellow>[Player] Spawn position set: {spawnPos}</color>");
}
// 체력 변경 이벤트 구독
_currentHealth.OnValueChanged += OnHealthChanged;
if (!IsOwner) return;
var vcam = GameObject.FindFirstObjectByType<CinemachineCamera>();
if (vcam != null)
public override void OnNetworkDespawn()
{
vcam.Follow = transform;
vcam.LookAt = transform;
Debug.Log("<color=green>[Camera] 로컬 플레이어에게 카메라가 연결되었습니다.</color>");
}
_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<AttackAction>();
var playerInteraction = GetComponent<PlayerInteraction>();
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<Vector2>();
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<AttackAction>();
var playerInteraction = GetComponent<PlayerInteraction>();
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($"<color=cyan>[Player] 팀 변경: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}</color>");
}
#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<ITeamMember>();
if (attackerTeamMember != null)
{
if (!TeamManager.CanAttack(attackerTeamMember, this))
if (_animator != null)
{
Debug.Log($"<color=yellow>[Player] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다.</color>");
return;
_animator.SetFloat("MoveSpeed", 0f);
}
return;
}
_moveInput = _inputActions.Player.Move.ReadValue<Vector2>();
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($"<color=red>[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}</color>");
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($"<color=red>[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) 사망했습니다! (킬러: {killerId})</color>");
// 사망 이펙트
ShowDeathEffectClientRpc();
// 애니메이션 (있는 경우)
if (_animator != null)
[ServerRpc]
private void SetTeamServerRpc(TeamType team)
{
_animator.SetTrigger("Die");
TeamType previousTeam = _team.Value;
_team.Value = team;
Debug.Log($"<color=cyan>[Player] 팀 변경: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}</color>");
}
// 일정 시간 후 리스폰 또는 디스폰
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<PlayerSpawnPoint>(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($"<color=green>[Player] {gameObject.name} 리스폰!</color>");
}
[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<ITeamMember>();
if (attackerTeamMember != null)
{
if (!TeamManager.CanAttack(attackerTeamMember, this))
{
Debug.Log($"<color=yellow>[Player] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다.</color>");
return;
}
}
}
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
_currentHealth.Value -= actualDamage;
Debug.Log($"<color=red>[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}</color>");
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($"<color=red>[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) 사망했습니다! (킬러: {killerId})</color>");
ShowDeathEffectClientRpc();
if (_animator != null)
{
_animator.SetTrigger("Die");
}
Invoke(nameof(HandleDeath), 3f);
}
}
#endregion
#region Health Management
/// <summary>
/// 현재 체력
/// </summary>
public int GetCurrentHealth() => _currentHealth.Value;
/// <summary>
/// 최대 체력
/// </summary>
public int GetMaxHealth() => maxHealth;
/// <summary>
/// 체력 비율 (0.0 ~ 1.0)
/// </summary>
public float GetHealthPercentage()
{
return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f;
}
/// <summary>
/// 죽었는지 여부
/// </summary>
public bool IsDead() => _currentHealth.Value <= 0;
/// <summary>
/// 체력 회복
/// </summary>
public void Heal(int amount)
{
if (!IsServer) return;
int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value);
_currentHealth.Value += healAmount;
Debug.Log($"<color=green>[Player] {gameObject.name}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{maxHealth}</color>");
}
private void OnHealthChanged(int previousValue, int newValue)
{
// 체력바 UI 업데이트 또는 체력 변경 시각 효과
Debug.Log($"<color=yellow>[Player] 체력 변경: {previousValue} → {newValue}</color>");
// 클라이언트에서도 체력 변경 인지 가능
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<PlayerSpawnPoint>(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($"<color=green>[Player] {gameObject.name} 리스폰!</color>");
}
[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($"<color=green>[Player] {gameObject.name}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{maxHealth}</color>");
}
private void OnHealthChanged(int previousValue, int newValue)
{
Debug.Log($"<color=yellow>[Player] 체력 변경: {previousValue} → {newValue}</color>");
}
#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
}
}

View File

@@ -1,11 +1,2 @@
fileFormatVersion: 2
guid: 9e8f7d6c5b4a3d2e1f0a9b8c7d6e5f4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
guid: 19b1385c0dcb77240b2cfb3c6b10f717

View File

@@ -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<FogOfWarVisibility>() == null)
{
var visibility = obj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = showInExploredAreas;
visibility.updateInterval = updateInterval;
}
}
public static void PreparePreviewForRendering(GameObject preview)
{
NetworkObject netObj = preview.GetComponent<NetworkObject>();
if (netObj != null)
{
Object.DestroyImmediate(netObj);
}
Building building = preview.GetComponent<Building>();
if (building != null)
{
Object.DestroyImmediate(building);
}
}
public static void ApplyMaterialToPreview(GameObject preview, Material material)
{
Renderer[] renderers = preview.GetComponentsInChildren<Renderer>();
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>())
{
collider.enabled = false;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9b6c0b0cbea70604fac8231195b6b7b4

View File

@@ -16,9 +16,9 @@ namespace Northbound
public List<Transform> spawnPoints = new List<Transform>();
public bool useRandomSpawn = false;
public bool findSpawnPointsAutomatically = true;
private Dictionary<ulong, int> _clientSpawnIndices = new Dictionary<ulong, int>();
private int _nextSpawnIndex = 0;
private NetworkVariable<int> networkNextSpawnIndex = new NetworkVariable<int>(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];
}

View File

@@ -5,10 +5,7 @@ using System.Collections.Generic;
namespace Northbound
{
/// <summary>
/// 상호작용 대상 없이 실행 가능한 액션들을 관리
/// </summary>
public class PlayerActionSystem : NetworkBehaviour
public class PlayerActionSystem : InputActionManager
{
[Header("Actions")]
public List<MonoBehaviour> actionComponents = new List<MonoBehaviour>();
@@ -16,7 +13,6 @@ namespace Northbound
[Header("Animation")]
public bool playAnimations = true;
private PlayerInputActions _inputActions;
private Dictionary<string, IAction> _actions = new Dictionary<string, IAction>();
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();
}
}
}
}

View File

@@ -1,11 +1,2 @@
fileFormatVersion: 2
guid: 7a3e5b8c4d2f1a9e6b0c3d7e8f1a2b3c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
guid: 7a3e5b8c4d2f1a9e6b0c3d7e8f1a2b3c

View File

@@ -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<Animator>();
_equipmentSocket = GetComponent<EquipmentSocket>();
}
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<IInteractable>();
if (interactable == null)
{
interactable = hit.collider.GetComponentInParent<IInteractable>();
}
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);
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
public class PlayerInventory : NetworkBehaviour
{
[Header("Inventory Settings")]
public int maxResourceCapacity = 100;
private NetworkVariable<int> resourceCount = new NetworkVariable<int>(
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);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 90231c209cbef84469511de397004be9

View File

@@ -1,76 +0,0 @@
using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
/// <summary>
/// 플레이어의 자원 인벤토리 관리
/// </summary>
public class PlayerResourceInventory : NetworkBehaviour
{
[Header("Inventory Settings")]
public int maxResourceCapacity = 100; // 최대 자원 보유량
private NetworkVariable<int> _currentResourceAmount = new NetworkVariable<int>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
public int CurrentResourceAmount => _currentResourceAmount.Value;
public int MaxResourceCapacity => maxResourceCapacity;
/// <summary>
/// 자원을 추가할 수 있는지 확인
/// </summary>
public bool CanAddResource(int amount)
{
return _currentResourceAmount.Value + amount <= maxResourceCapacity;
}
/// <summary>
/// 추가 가능한 최대 자원량 계산
/// </summary>
public int GetAvailableSpace()
{
return maxResourceCapacity - _currentResourceAmount.Value;
}
/// <summary>
/// 자원 추가 (서버에서만 호출)
/// </summary>
[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}");
}
/// <summary>
/// 자원 제거 (서버에서만 호출)
/// </summary>
[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}");
}
/// <summary>
/// 자원 설정 (서버에서만 호출)
/// </summary>
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
public void SetResourceServerRpc(int amount)
{
_currentResourceAmount.Value = Mathf.Clamp(amount, 0, maxResourceCapacity);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3c64072402b0a3f46a674eb73c5541ac

View File

@@ -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<Transform> spawnPoints = new List<Transform>();
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<PlayerSpawnPoint>(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($"<color=cyan>[SpawnPositionSetter] {spawnPoints.Count}개의 스폰 포인트를 찾았습니다.</color>");
}
public Vector3 GetSpawnPosition(ulong clientId)
{
if (spawnPoints.Count == 0)
{
Debug.LogWarning("[SpawnPositionSetter] 스폰 포인트가 없습니다. 기본 위치 반환.");
return Vector3.zero;
}
int spawnIndex = GetSpawnIndexForClient(clientId);
Debug.Log($"<color=yellow>[SpawnPositionSetter] 클라이언트 {clientId}에게 스폰 인덱스 {spawnIndex} 할당</color>");
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<ulong> connectedClientIds = new List<ulong>(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;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 49cd9c4e7c611b04c8740c9e049129b9

View File

@@ -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<PlayerResourceInventory>();
var playerInventory = client.PlayerObject.GetComponent<PlayerInventory>();
if (playerInventory != null)
{
// 플레이어가 받을 수 있는 공간이 없으면 상호작용 불가
@@ -120,10 +120,10 @@ namespace Northbound
if (playerObject == null)
return;
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
var playerInventory = playerObject.GetComponent<PlayerInventory>();
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}");

View File

@@ -42,7 +42,7 @@ namespace Northbound
{
if (client.PlayerObject != null)
{
var playerInventory = client.PlayerObject.GetComponent<PlayerResourceInventory>();
var playerInventory = client.PlayerObject.GetComponent<PlayerInventory>();
if (playerInventory != null)
{
// 플레이어가 받을 수 있는 공간이 없으면 상호작용 불가
@@ -80,10 +80,10 @@ namespace Northbound
if (playerObject == null)
return;
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
var playerInventory = playerObject.GetComponent<PlayerInventory>();
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}을(를) 획득했습니다.");

View File

@@ -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("<color=yellow><b>[SmartAutoHost]</b> MAIN EDITOR → Starting as HOST</color>");
}
else
{
NetworkManager.Singleton.StartClient();
Debug.Log("<color=blue><b>[SmartAutoHost]</b> SECONDARY EDITOR → Connecting as CLIENT</color>");
}
}
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("<color=blue>[SmartAutoHost] Build → Connecting as CLIENT</color>");
}
}
#endif
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 13b6e5c1ebfd6fb4d939d2e99b25fffc

View File

@@ -97,7 +97,7 @@ namespace Northbound
private void OnTriggerEnter(Collider other)
{
// 서버에서만 텔레포트 처리
if (!IsServer) return;
if (!IsOwner) return;
// 어느 트리거에 진입했는지 확인
Collider triggeredCollider = null;

View File

@@ -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<TeamType> _team = new NetworkVariable<TeamType>(
TeamType.Neutral,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
public event System.Action<TeamType> 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($"<color=cyan>[{GetType().Name}] {gameObject.name} team changed: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}</color>");
}
protected virtual void OnTeamValueChanged(TeamType previousValue, TeamType newValue)
{
OnTeamChanged?.Invoke(newValue);
UpdateTeamVisuals();
}
protected virtual void UpdateTeamVisuals()
{
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 25aae83d83e1df146ad1b22af79848bb

View File

@@ -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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 624b6b15c0f5b4c4d912ee2e880c8581
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -43,7 +43,6 @@
<UnityVersion>6000.3.5f2</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\Extensions\Microsoft\Visual Studio Tools for Unity\Analyzers\Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.SourceGenerators.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
@@ -631,6 +630,10 @@
<HintPath>C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="websocket-sharp">
<HintPath>Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualScripting.YamlDotNet">
<HintPath>Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll</HintPath>
<Private>False</Private>

View File

@@ -43,7 +43,6 @@
<UnityVersion>6000.3.5f2</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\Extensions\Microsoft\Visual Studio Tools for Unity\Analyzers\Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.SourceGenerators.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
@@ -614,6 +613,10 @@
<HintPath>C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="websocket-sharp">
<HintPath>Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualScripting.YamlDotNet">
<HintPath>Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll</HintPath>
<Private>False</Private>

44
Northbound.sln Normal file
View File

@@ -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

View File

@@ -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",

View File

@@ -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,

View File

@@ -0,0 +1,4 @@
{
"PlayerTags": [],
"version": "6000.3.5f2"
}

View File

@@ -43,15 +43,14 @@
<UnityVersion>6000.3.5f2</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\Extensions\Microsoft\Visual Studio Tools for Unity\Analyzers\Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.SourceGenerators.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Overrides\ColorAdjustments.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\ScreenSpaceAmbientOcclusionPass.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Decal\Entities\DecalEntityManager.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\ScreenSpaceAmbientOcclusionPass.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Overrides\ScreenSpaceLensFlare.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\History\RawColorHistory.cs" />
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\EditorAttributes\Core\DrawerAttributes\InfoBoxAttribute.cs" />
@@ -143,8 +142,8 @@
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\RenderFeatures\Outline\FlatKitOutline.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\AdditionalLightsShadowAtlasLayout.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Deprecated.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\ColorGradingLutPass.cs" />
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\RenderFeatures\Common\ScreenRenderPass.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\ColorGradingLutPass.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Tiling\TileRangeExpansionJob.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\TemporalAA.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\VFXGraph\Utility\PropertyBinders\URPCameraBinder.cs" />
@@ -247,8 +246,8 @@
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Data\UniversalRenderPipelineAsset.DefaultResources.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Data\RenderStateData.cs" />
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\EditorAttributes\Core\DrawerAttributes\DrawerAttribute.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\FrameData\UniversalLightData.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Debug\DebugHandler.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\FrameData\UniversalLightData.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Decal\Entities\DecalDrawSystem.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\PostProcessPass.cs" />
<Compile Include="Library\PackageCache\com.unity.render-pipelines.universal@1e87cf1dccb8\Runtime\Passes\CapturePass.cs" />
@@ -832,6 +831,10 @@
<HintPath>C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="websocket-sharp">
<HintPath>Library\PackageCache\com.unity.services.wire@9a73acde80cc\Plugins\websocket-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Unity.VisualScripting.YamlDotNet">
<HintPath>Library\PackageCache\com.unity.visualscripting@191c0d7e3b69\Editor\VisualScripting.Core\Dependencies\YamlDotNet\Unity.VisualScripting.YamlDotNet.dll</HintPath>
<Private>False</Private>