From 1ba1d2c03cef9ba63907a580a38342f75a111c5c Mon Sep 17 00:00:00 2001 From: Dal4segno Date: Sat, 17 Jan 2026 00:13:38 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A7=80=ED=95=98=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=ED=84=B0=EB=84=90=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/DefaultNetworkPrefabs.asset | 10 +- Assets/Prefabs/Player.prefab | 4 +- Assets/Prefabs/ResourceBlock.prefab | 157 ++++++++++++ Assets/Prefabs/ResourceBlock.prefab.meta | 7 + Assets/Prefabs/Tunnel.prefab | 104 +++++++- Assets/Scenes/DefenceScene.unity | 241 +++++++----------- Assets/Scripts/Enemy/EnemyAttack.cs | 1 - Assets/Scripts/GameBase/BuildManager.cs | 208 +++++++-------- .../Scripts/GameBase/UndergroundGenerator.cs | 120 +++++++++ .../GameBase/UndergroundGenerator.cs.meta | 2 + Assets/Scripts/IInteractable.cs | 3 - Assets/Scripts/MineableBlock.cs | 1 - .../Scripts/Player/PlayerNetworkController.cs | 63 ++--- Assets/Scripts/Tower/ConstructionSite.cs | 6 +- Assets/Scripts/TunnelNode.cs | 92 +++++-- Assets/Scripts/TunnelTraveler.cs | 52 +--- ProjectSettings/TagManager.asset | 2 +- 17 files changed, 687 insertions(+), 386 deletions(-) create mode 100644 Assets/Prefabs/ResourceBlock.prefab create mode 100644 Assets/Prefabs/ResourceBlock.prefab.meta create mode 100644 Assets/Scripts/GameBase/UndergroundGenerator.cs create mode 100644 Assets/Scripts/GameBase/UndergroundGenerator.cs.meta diff --git a/Assets/DefaultNetworkPrefabs.asset b/Assets/DefaultNetworkPrefabs.asset index 54d1fb8..31db7b1 100644 --- a/Assets/DefaultNetworkPrefabs.asset +++ b/Assets/DefaultNetworkPrefabs.asset @@ -69,13 +69,13 @@ MonoBehaviour: SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} - - Override: 0 - Prefab: {fileID: 1126366023313988, guid: a4e5400b711cb57478dcf41916935a7e, type: 3} - SourcePrefabToOverride: {fileID: 0} - SourceHashToOverride: 0 - OverridingTargetPrefab: {fileID: 0} - Override: 0 Prefab: {fileID: 989066657509100432, guid: dbb1cfcb3d9e3844e8d9cdf09b0a1660, type: 3} SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 989066657509100432, guid: 17532917e1ada23469c573abf64905f0, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab index 6d8ef1a..c6d2334 100644 --- a/Assets/Prefabs/Player.prefab +++ b/Assets/Prefabs/Player.prefab @@ -139,7 +139,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d71ce06d133743140877345b807f33ad, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::TunnelTraveler - travelSpeed: 20 + travelSpeed: 2 --- !u!95 &5870045807328036684 Animator: serializedVersion: 7 @@ -207,7 +207,7 @@ MonoBehaviour: interactRange: 3 interactableLayer: serializedVersion: 2 - m_Bits: 1024 + m_Bits: 9216 constructionLayer: serializedVersion: 2 m_Bits: 256 diff --git a/Assets/Prefabs/ResourceBlock.prefab b/Assets/Prefabs/ResourceBlock.prefab new file mode 100644 index 0000000..002abc3 --- /dev/null +++ b/Assets/Prefabs/ResourceBlock.prefab @@ -0,0 +1,157 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &989066657509100432 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5902598773338262147} + - component: {fileID: 7167019900531103381} + - component: {fileID: 384943650775659982} + - component: {fileID: 3086293200253757409} + - component: {fileID: 7528764990365051674} + - component: {fileID: 3421159559893464927} + m_Layer: 12 + m_Name: ResourceBlock + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5902598773338262147 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989066657509100432} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.5, y: 0.5, z: 0.5} + 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!33 &7167019900531103381 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989066657509100432} + m_Mesh: {fileID: 4300000, guid: 09d8bdb2477b8c04f87c3f5d1d04a55f, type: 3} +--- !u!23 &384943650775659982 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989066657509100432} + 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: 4930c6ffa5b9b9f4098249a43abf8506, 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!64 &3086293200253757409 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989066657509100432} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 0 + m_CookingOptions: 30 + m_Mesh: {fileID: 4300000, guid: 10d475a0f15b31443aa032c25648465b, type: 3} +--- !u!114 &7528764990365051674 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989066657509100432} + 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: 1191681468 + InScenePlacedSourceGlobalObjectIdHash: 3862515551 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 0 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!114 &3421159559893464927 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989066657509100432} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e8e42f98781f84c42855fa4e989d3c1b, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::MineableBlock + ShowTopMostFoldoutHeaderGroup: 1 + maxHp: 100 + breakEffectPrefab: {fileID: 0} diff --git a/Assets/Prefabs/ResourceBlock.prefab.meta b/Assets/Prefabs/ResourceBlock.prefab.meta new file mode 100644 index 0000000..a98976f --- /dev/null +++ b/Assets/Prefabs/ResourceBlock.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 17532917e1ada23469c573abf64905f0 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Tunnel.prefab b/Assets/Prefabs/Tunnel.prefab index d6d9cb1..3262459 100644 --- a/Assets/Prefabs/Tunnel.prefab +++ b/Assets/Prefabs/Tunnel.prefab @@ -11,7 +11,7 @@ GameObject: - component: {fileID: 4703568612144557396} - component: {fileID: 2320767985316498782} - component: {fileID: 2292926820231089492} - m_Layer: 10 + m_Layer: 13 m_Name: TunnelNodeA m_TagString: Untagged m_Icon: {fileID: 0} @@ -45,9 +45,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 275306f282948c343bf20ee884ad22b8, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::TunnelNode - ShowTopMostFoldoutHeaderGroup: 1 - aboveNode: {fileID: 0} - belowNode: {fileID: 0} + isTop: 1 + partnerNode: {fileID: 3033105595038429359} --- !u!65 &2292926820231089492 BoxCollider: m_ObjectHideFlags: 0 @@ -100,8 +99,77 @@ Transform: m_Children: - {fileID: 4229549338725785498} - {fileID: 4703568612144557396} + - {fileID: 6728412950896342264} m_Father: {fileID: 7892781535212668078} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &6875506762777844854 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6728412950896342264} + - component: {fileID: 3033105595038429359} + - component: {fileID: 3152835292698073290} + m_Layer: 13 + m_Name: TunnelNodeB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6728412950896342264 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6875506762777844854} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5411003075815891614} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3033105595038429359 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6875506762777844854} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 275306f282948c343bf20ee884ad22b8, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::TunnelNode + isTop: 0 + partnerNode: {fileID: 2320767985316498782} +--- !u!65 &3152835292698073290 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6875506762777844854} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} --- !u!1 &7297238503126003997 GameObject: m_ObjectHideFlags: 0 @@ -225,6 +293,7 @@ GameObject: m_Component: - component: {fileID: 7892781535212668078} - component: {fileID: 5637751854208875435} + - component: {fileID: 5398902060671424421} m_Layer: 11 m_Name: Tunnel m_TagString: Untagged @@ -273,3 +342,30 @@ MonoBehaviour: AutoObjectParentSync: 1 SyncOwnerTransformWhenParented: 1 AllowOwnerToParent: 0 +--- !u!54 &5398902060671424421 +Rigidbody: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7817822323996939414} + serializedVersion: 5 + m_Mass: 1 + m_LinearDamping: 0 + m_AngularDamping: 0.05 + m_CenterOfMass: {x: 0, y: 0, z: 0} + m_InertiaTensor: {x: 1, y: 1, z: 1} + m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ImplicitCom: 1 + m_ImplicitTensor: 1 + m_UseGravity: 0 + m_IsKinematic: 1 + m_Interpolate: 0 + m_Constraints: 0 + m_CollisionDetection: 0 diff --git a/Assets/Scenes/DefenceScene.unity b/Assets/Scenes/DefenceScene.unity index 08033fe..c67ba53 100644 --- a/Assets/Scenes/DefenceScene.unity +++ b/Assets/Scenes/DefenceScene.unity @@ -678,152 +678,6 @@ MonoBehaviour: m_MinRegionArea: 2 m_NavMeshData: {fileID: 23800000, guid: 2d822e23a760f3943a3174a0d27b4254, type: 2} m_BuildHeightMesh: 0 ---- !u!1 &342345820 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 342345821} - - component: {fileID: 342345825} - - component: {fileID: 342345824} - - component: {fileID: 342345823} - - component: {fileID: 342345822} - m_Layer: 7 - m_Name: DungeonBase - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 2147483647 - m_IsActive: 1 ---- !u!4 &342345821 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 342345820} - serializedVersion: 2 - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -9, y: -5, z: 0} - m_LocalScale: {x: 10, y: 2, z: 6} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 743367988} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &342345822 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 342345820} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 7a5ac11cc976e418e8d13136b07e1f52, type: 3} - m_Name: - m_EditorClassIdentifier: Unity.AI.Navigation::Unity.AI.Navigation.NavMeshSurface - m_SerializedVersion: 0 - m_AgentTypeID: 0 - m_CollectObjects: 0 - m_Size: {x: 10, y: 10, z: 10} - m_Center: {x: 0, y: 2, z: 0} - m_LayerMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_UseGeometry: 0 - m_DefaultArea: 0 - m_GenerateLinks: 0 - m_IgnoreNavMeshAgent: 1 - m_IgnoreNavMeshObstacle: 1 - m_OverrideTileSize: 0 - m_TileSize: 256 - m_OverrideVoxelSize: 0 - m_VoxelSize: 0.016666668 - m_MinRegionArea: 2 - m_NavMeshData: {fileID: 0} - m_BuildHeightMesh: 0 ---- !u!64 &342345823 -MeshCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 342345820} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 5 - m_Convex: 0 - m_CookingOptions: 30 - m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} ---- !u!23 &342345824 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 342345820} - 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: a3cd22eceea5e8548b10cdb5d9b49a25, 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!33 &342345825 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 342345820} - m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} --- !u!4 &372588680 stripped Transform: m_CorrespondingSourceObject: {fileID: 6438027004378685690, guid: 2bb7e098e271eb44a873c856dbf59c7c, type: 3} @@ -838,8 +692,8 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 412220987} - - component: {fileID: 412220988} - component: {fileID: 412220989} + - component: {fileID: 412220988} m_Layer: 0 m_Name: BuildManager m_TagString: Untagged @@ -876,7 +730,8 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::BuildManager ShowTopMostFoldoutHeaderGroup: 1 cellSize: 1 - tunnelHeight: 3 + pivotOffset: 0.5 + tunnelLengthInBlocks: 3 constructionSitePrefab: {fileID: 7327242023390354019, guid: 7d362c5c1b34c2b4e901294618e6c3e8, type: 3} turretLibrary: - turretName: Arrow Tower @@ -895,6 +750,12 @@ MonoBehaviour: finalPrefab: {fileID: 7817822323996939414, guid: 48bf40d31e903d34f9469451d7de06dd, type: 3} ghostPrefab: {fileID: 7817822323996939414, guid: 532bea89bbc1d2245b65a9c22749711a, type: 3} buildTime: 10 + groundLayer: + serializedVersion: 2 + m_Bits: 128 + tunnelLayer: + serializedVersion: 2 + m_Bits: 2048 --- !u!114 &412220989 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1111,7 +972,7 @@ MonoBehaviour: m_DisconnectTimeoutMS: 30000 ConnectionData: Address: 127.0.0.1 - Port: 7777 + Port: 7774 ServerListenAddress: 127.0.0.1 ClientBindPort: 0 DebugSimulator: @@ -1564,7 +1425,6 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 290482132} - - {fileID: 342345821} - {fileID: 445606026} - {fileID: 372588680} - {fileID: 2084947170} @@ -2699,6 +2559,86 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1600458457} m_CullTransparentMesh: 1 +--- !u!1 &1634635642 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1634635645} + - component: {fileID: 1634635643} + - component: {fileID: 1634635644} + m_Layer: 0 + m_Name: UndergroundGenerator + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1634635643 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1634635642} + 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: 1220698054 + 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 &1634635644 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1634635642} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7fc80ebb61fb70d4fa694d3a1f81d2ab, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::UndergroundGenerator + ShowTopMostFoldoutHeaderGroup: 1 + generationRange: {x: 10, y: 3, z: 6} + noiseScale: 0.14 + hollowThreshold: 0.4 + baseResourceThreshold: 0.6 + increaseResourceWithDepth: 1 + depthFactor: 0.005 + normalBlockPrefab: {fileID: 989066657509100432, guid: dbb1cfcb3d9e3844e8d9cdf09b0a1660, type: 3} + resourceBlockPrefab: {fileID: 989066657509100432, guid: 17532917e1ada23469c573abf64905f0, type: 3} + containerName: UndergroundBlocks +--- !u!4 &1634635645 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1634635642} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -14, y: -8, z: -3} + 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!20 &1648146738 stripped Camera: m_CorrespondingSourceObject: {fileID: 5650099317679730308, guid: 2b08dd32e48ef5e4aa65a6122099152e, type: 3} @@ -3346,3 +3286,4 @@ SceneRoots: - {fileID: 1409253547} - {fileID: 14847856} - {fileID: 556982644} + - {fileID: 1634635645} diff --git a/Assets/Scripts/Enemy/EnemyAttack.cs b/Assets/Scripts/Enemy/EnemyAttack.cs index 787e31d..db8b430 100644 --- a/Assets/Scripts/Enemy/EnemyAttack.cs +++ b/Assets/Scripts/Enemy/EnemyAttack.cs @@ -78,7 +78,6 @@ public class EnemyAttack : MonoBehaviour _targetDamageable.TakeDamage(damage); _nextAttackTime = Time.time + attackCooldown; StartCoroutine(FlashEmissionRoutine()); - Debug.Log($"[공격!] 대상: {_target.name}"); } } diff --git a/Assets/Scripts/GameBase/BuildManager.cs b/Assets/Scripts/GameBase/BuildManager.cs index 6e95cec..dd10c3d 100644 --- a/Assets/Scripts/GameBase/BuildManager.cs +++ b/Assets/Scripts/GameBase/BuildManager.cs @@ -12,21 +12,23 @@ public class BuildManager : NetworkBehaviour public struct TurretData { public string turretName; - public GameObject finalPrefab; // NetworkObject 필수 - public GameObject ghostPrefab; // 로컬 프리뷰용 + public GameObject finalPrefab; + public GameObject ghostPrefab; public float buildTime; } - [Header("Grid Settings")] + [Header("Uniform Grid Settings")] public float cellSize = 1f; - public float tunnelHeight = 3f; - [SerializeField] private float yOffset = 0.5f; // 터널 중심점 오프셋 + [SerializeField] private float pivotOffset = 0.5f; // 블록/터널 에셋의 중심점 + + [Header("Tunnel Settings")] + public int tunnelLengthInBlocks = 3; [Header("Prefabs & Layers")] [SerializeField] private GameObject constructionSitePrefab; [SerializeField] private List turretLibrary = new List(); [SerializeField] private LayerMask groundLayer; - [SerializeField] private LayerMask playerLayer; + [SerializeField] private LayerMask tunnelLayer; private int _selectedTurretIndex = 0; private bool _isBuildMode = false; @@ -34,7 +36,6 @@ public class BuildManager : NetworkBehaviour private Vector3Int _currentGridPos; private PlayerInputActions _inputActions; - // 데이터 레지스트리 (다른 코드에서 참조) private Dictionary _tunnelRegistry = new Dictionary(); private HashSet _occupiedNodes = new HashSet(); @@ -42,26 +43,20 @@ public class BuildManager : NetworkBehaviour { if (Instance == null) Instance = this; else Destroy(gameObject); - _inputActions = new PlayerInputActions(); } public override void OnNetworkSpawn() { - // 최신 Action-based 이벤트 바인딩 _inputActions.Player.ToggleBuild.performed += ctx => ToggleBuildMode(); _inputActions.Player.Build.performed += ctx => OnBuildRequested(); _inputActions.Player.Cancel.performed += ctx => ExitBuildMode(); - // 숫자키 슬롯 선택 (Input Action 에셋 설정에 따라 Select1, Select2... 등 사용) _inputActions.Player.Select1.performed += ctx => SelectTurret(0); _inputActions.Player.Select2.performed += ctx => SelectTurret(1); _inputActions.Player.Select3.performed += ctx => SelectTurret(2); _inputActions.Enable(); - - // 시작 시 씬에 배치된 터널 자동 등록 - RegisterAllExistingTunnels(); } void Update() @@ -70,144 +65,115 @@ public class BuildManager : NetworkBehaviour UpdateGhostPosition(); } - #region Selection & Build Logic - - public void SelectTurret(int index) - { - if (index < 0 || index >= turretLibrary.Count) return; - - _selectedTurretIndex = index; - Debug.Log($"타워 선택: {turretLibrary[_selectedTurretIndex].turretName}"); - - if (_isBuildMode) - { - if (_ghostInstance != null) Destroy(_ghostInstance); - _ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab); - _ghostInstance.transform.position = GridToWorld(_currentGridPos); - } - } - private void UpdateGhostPosition() { Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()); - int tunnelMask = LayerMask.GetMask("Tunnel"); - if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer | tunnelMask)) + if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer | tunnelLayer)) { - Vector3Int gridPos = WorldToGrid3D(hit.point); + Vector3 targetPos; - // 터널 조준 시 해당 터널의 한 칸 아래로 스냅 - if (((1 << hit.collider.gameObject.layer) & tunnelMask) != 0) + // 터널 조준 시: 복잡한 계산 없이 X, Z는 유지, Y만 3 낮춤 + if (((1 << hit.collider.gameObject.layer) & tunnelLayer) != 0) { - gridPos = WorldToGrid3D(hit.collider.transform.position) + Vector3Int.down; + Vector3 parentPos = hit.collider.transform.position; + targetPos = new Vector3(parentPos.x, parentPos.y - (tunnelLengthInBlocks * cellSize), parentPos.z); + } + else + { + // 지형 조준 시: 격자에 맞춤 + _currentGridPos = WorldToGrid3D(hit.point + hit.normal * 0.01f); + targetPos = GridToWorld(_currentGridPos); } - _currentGridPos = gridPos; - _ghostInstance.transform.position = GridToWorld(gridPos); + _currentGridPos = WorldToGrid3D(targetPos); + _ghostInstance.transform.position = targetPos; } } + // 모든 좌표를 반올림하여 0.5 단위 오차를 제거 + public Vector3Int WorldToGrid3D(Vector3 worldPos) + { + return new Vector3Int( + Mathf.RoundToInt((worldPos.x - pivotOffset) / cellSize), + Mathf.RoundToInt((worldPos.y - pivotOffset) / cellSize), + Mathf.RoundToInt((worldPos.z - pivotOffset) / cellSize) + ); + } + + public Vector3 GridToWorld(Vector3Int gridPos) + { + return new Vector3( + (gridPos.x * cellSize) + pivotOffset, + (gridPos.y * cellSize) + pivotOffset, + (gridPos.z * cellSize) + pivotOffset + ); + } + + // [에러 해결] ConstructionSite.cs 참조용 + public TurretData GetTurretData(int index) + { + if (index < 0 || index >= turretLibrary.Count) return default; + return turretLibrary[index]; + } + + // BuildManager.cs 내 핵심 수정 부분 + // [수정] 서버와 클라이언트 모두에서 터널 위치를 기록하도록 함 + // BuildManager.cs 내부 + public void RegisterTunnel(Vector3Int topPos, TunnelNode node) + { + // 터널이 점유하는 Y, Y-1, Y-2 세 칸 모두에 노드를 등록합니다. + for (int i = 0; i < 3; i++) + { + Vector3Int cellPos = topPos + (Vector3Int.down * i); + if (!_tunnelRegistry.ContainsKey(cellPos)) + { + _tunnelRegistry.Add(cellPos, node); + } + } + } + + // [수정] 특정 좌표의 터널을 찾는 공용 함수 + public TunnelNode GetTunnelAt(Vector3Int pos) + { + return _tunnelRegistry.GetValueOrDefault(pos); + } + + // 1. 건설 요청 시 실제 계산된 worldPos를 넘겨줍니다. private void OnBuildRequested() { if (!_isBuildMode || EventSystem.current.IsPointerOverGameObject()) return; - // 서버에 건설 가능 여부 확인 및 생성 요청 - RequestBuildRpc(_selectedTurretIndex, _currentGridPos); - + // 고스트가 현재 위치한 '그 좌표'를 그대로 보냅니다. + RequestBuildRpc(_selectedTurretIndex, _currentGridPos, _ghostInstance.transform.position); ExitBuildMode(); } + // 2. RPC에서 넘겨받은 worldPos를 사용하여 토대를 생성합니다. [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] - private void RequestBuildRpc(int index, Vector3Int gridPos) + private void RequestBuildRpc(int index, Vector3Int gridPos, Vector3 worldPos) { - // 서버 측 중복 점유 확인 - if (_occupiedNodes.Contains(gridPos)) return; - - Vector3 spawnPos = GridToWorld(gridPos); - - // 1. 토대 생성 및 네트워크 스폰 - GameObject siteObj = Instantiate(constructionSitePrefab, spawnPos, Quaternion.identity); + // GridToWorld를 다시 계산하지 않고 전달받은 worldPos를 그대로 사용합니다. + GameObject siteObj = Instantiate(constructionSitePrefab, worldPos, Quaternion.identity); siteObj.GetComponent().Spawn(); - // 2. 토대 데이터 초기화 ConstructionSite site = siteObj.GetComponent(); - if (site != null) - { - site.Initialize(index, gridPos); - } - - _occupiedNodes.Add(gridPos); + if (site != null) site.Initialize(index, gridPos); } - #endregion - - #region Utility Functions (External References) - - // ConstructionSite에서 프리팹 정보를 가져갈 때 사용 - public TurretData GetTurretData(int index) => turretLibrary[index]; - - // TunnelNode가 스폰될 때 자신을 등록하기 위해 사용 - public void RegisterTunnel(Vector3Int pos, TunnelNode node) + public void SelectTurret(int index) { - if (!_tunnelRegistry.ContainsKey(pos)) + if (index < 0 || index >= turretLibrary.Count) return; + _selectedTurretIndex = index; + if (_isBuildMode && _ghostInstance != null) { - _tunnelRegistry.Add(pos, node); - _occupiedNodes.Add(pos); // 건설된 구역으로 마킹 + Destroy(_ghostInstance); + _ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab); } } - // TunnelNode가 위아래 노드를 찾기 위해 사용 - public TunnelNode GetTunnelAt(Vector3Int pos) => _tunnelRegistry.GetValueOrDefault(pos); - - // 좌표 변환: 월드 -> 격자 인덱스 (0.5 오프셋 반영) - public Vector3Int WorldToGrid3D(Vector3 worldPos) - { - return new Vector3Int( - Mathf.FloorToInt(worldPos.x / cellSize), - Mathf.RoundToInt((worldPos.y - yOffset) / tunnelHeight), - Mathf.FloorToInt(worldPos.z / cellSize) - ); - } - - // 좌표 변환: 격자 인덱스 -> 월드 (0.5 오프셋 반영) - public Vector3 GridToWorld(Vector3Int gridPos) - { - return new Vector3( - gridPos.x * cellSize, - (gridPos.y * tunnelHeight) + yOffset, - gridPos.z * cellSize - ); - } - - // 씬에 미리 배치된 터널들을 한꺼번에 등록 - public void RegisterAllExistingTunnels() - { - TunnelNode[] nodes = FindObjectsByType(FindObjectsSortMode.None); - foreach (var node in nodes) - { - Vector3Int pos = WorldToGrid3D(node.transform.position); - RegisterTunnel(pos, node); - } - } - - #endregion - - #region Mode Switching - - public void ToggleBuildMode() { if (_isBuildMode) ExitBuildMode(); else EnterBuildMode(); } - private void EnterBuildMode() - { - if (turretLibrary.Count == 0) return; - _isBuildMode = true; - _ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab); - } - private void ExitBuildMode() - { - _isBuildMode = false; - if (_ghostInstance) Destroy(_ghostInstance); - } - - #endregion - + private void ToggleBuildMode() { if (_isBuildMode) ExitBuildMode(); else EnterBuildMode(); } + private void EnterBuildMode() { _isBuildMode = true; if (turretLibrary.Count > 0) _ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab); } + private void ExitBuildMode() { _isBuildMode = false; if (_ghostInstance) Destroy(_ghostInstance); } public override void OnNetworkDespawn() => _inputActions.Disable(); } \ No newline at end of file diff --git a/Assets/Scripts/GameBase/UndergroundGenerator.cs b/Assets/Scripts/GameBase/UndergroundGenerator.cs new file mode 100644 index 0000000..c3bc7cd --- /dev/null +++ b/Assets/Scripts/GameBase/UndergroundGenerator.cs @@ -0,0 +1,120 @@ +using UnityEngine; +using Unity.Netcode; + +public class UndergroundGenerator : NetworkBehaviour +{ + [Header("Generation Range")] + [SerializeField] private Vector3Int generationRange = new Vector3Int(20, 30, 10); + [SerializeField] private float noiseScale = 0.12f; + + [Header("Thresholds (0 to 1)")] + [SerializeField, Range(0, 1)] private float hollowThreshold = 0.35f; + [SerializeField, Range(0, 1)] private float baseResourceThreshold = 0.8f; + + [Header("Depth Settings")] + [SerializeField] private bool increaseResourceWithDepth = true; + [SerializeField] private float depthFactor = 0.005f; // 깊어질수록 임계값 감소 (자원 증가) + + [Header("Prefabs")] + [SerializeField] private GameObject normalBlockPrefab; + [SerializeField] private GameObject resourceBlockPrefab; + + [Header("Organization")] + [SerializeField] private string containerName = "UndergroundBlocks"; + private Transform _blockContainer; // 블록들을 담을 부모 오브젝트 + + private float _seedX, _seedY, _seedZ; + + private void Awake() + { + _seedX = Random.Range(0f, 99999f); + _seedY = Random.Range(0f, 99999f); + _seedZ = Random.Range(0f, 99999f); + + // Hierarchy 정리를 위한 컨테이너 생성 + _blockContainer = new GameObject(containerName).transform; + } + + public override void OnNetworkSpawn() + { + if (IsServer) GenerateFromGeneratorPivot(); + } + + private void GenerateFromGeneratorPivot() + { + Vector3Int originGrid = BuildManager.Instance.WorldToGrid3D(transform.position); + + for (int x = 0; x < generationRange.x; x++) + { + for (int y = 0; y > -generationRange.y; y--) + { + for (int z = 0; z < generationRange.z; z++) + { + Vector3Int targetGridPos = originGrid + new Vector3Int(x, y, z); + float noise = Get3DNoise(targetGridPos.x, targetGridPos.y, targetGridPos.z); + + if (noise < hollowThreshold) continue; + + float currentThreshold = baseResourceThreshold + (y * depthFactor); + GameObject prefab = (noise > currentThreshold) ? resourceBlockPrefab : normalBlockPrefab; + + SpawnBlock(prefab, targetGridPos); + } + } + } + } + + private void SpawnBlock(GameObject prefab, Vector3Int gridPos) + { + if (prefab == null) return; + + Vector3 worldPos = BuildManager.Instance.GridToWorld(gridPos); + + // 1. 생성 시 부모(Container)를 지정하여 Hierarchy 정리 + GameObject block = Instantiate(prefab, worldPos, Quaternion.identity); + + // 2. 네트워크 스폰 (Netcode for GameObjects) + block.GetComponent().Spawn(); + } + + private float Get3DNoise(int x, int y, int z) + { + // 대칭 방지를 위한 10000 오프셋 및 시드 적용 + float xCoord = (x + _seedX + 10000f) * noiseScale; + float yCoord = (y + _seedY + 10000f) * noiseScale; + float zCoord = (z + _seedZ + 10000f) * noiseScale; + + float ab = Mathf.PerlinNoise(xCoord, yCoord); + float bc = Mathf.PerlinNoise(yCoord, zCoord); + float ac = Mathf.PerlinNoise(xCoord, zCoord); + + return (ab + bc + ac) / 3f; + } + + private void OnDrawGizmosSelected() + { + BuildManager bm = BuildManager.Instance; + if (bm == null) bm = FindFirstObjectByType(); // 에러 방지 안전장치 + if (bm == null) return; + + Vector3Int originGrid = bm.WorldToGrid3D(transform.position); + + for (int x = 0; x < generationRange.x; x += 2) + { + for (int y = -generationRange.y; y < 0; y += 2) + { + for (int z = 0; z < generationRange.z; z += 2) + { + Vector3Int targetGridPos = originGrid + new Vector3Int(x, y, z); + float noise = Get3DNoise(targetGridPos.x, targetGridPos.y, targetGridPos.z); + + if (noise < hollowThreshold) continue; + + Gizmos.color = (noise > baseResourceThreshold) ? Color.yellow : new Color(0.5f, 0.5f, 0.5f, 0.2f); + Vector3 pos = bm.GridToWorld(targetGridPos); + Gizmos.DrawSphere(pos, 0.2f); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/GameBase/UndergroundGenerator.cs.meta b/Assets/Scripts/GameBase/UndergroundGenerator.cs.meta new file mode 100644 index 0000000..487f92d --- /dev/null +++ b/Assets/Scripts/GameBase/UndergroundGenerator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7fc80ebb61fb70d4fa694d3a1f81d2ab \ No newline at end of file diff --git a/Assets/Scripts/IInteractable.cs b/Assets/Scripts/IInteractable.cs index 60573de..7df8d4b 100644 --- a/Assets/Scripts/IInteractable.cs +++ b/Assets/Scripts/IInteractable.cs @@ -4,7 +4,4 @@ public interface IInteractable { // 누가 상호작용을 시도했는지 알려주기 위해 GameObject를 인자로 받습니다. void Interact(GameObject user); - - // 선택 사항: 화면에 띄울 메시지를 반환하는 기능 (예: "통로 이용하기 [E]") - string GetInteractionText(); } \ No newline at end of file diff --git a/Assets/Scripts/MineableBlock.cs b/Assets/Scripts/MineableBlock.cs index e6d937b..4c38a67 100644 --- a/Assets/Scripts/MineableBlock.cs +++ b/Assets/Scripts/MineableBlock.cs @@ -26,7 +26,6 @@ public class MineableBlock : NetworkBehaviour if (_currentHp.Value <= 0) return; _currentHp.Value -= damageAmount; - Debug.Log($"블록 대미지! 남은 체력: {_currentHp.Value}"); if (_currentHp.Value <= 0) { diff --git a/Assets/Scripts/Player/PlayerNetworkController.cs b/Assets/Scripts/Player/PlayerNetworkController.cs index fc0bb7d..1960435 100644 --- a/Assets/Scripts/Player/PlayerNetworkController.cs +++ b/Assets/Scripts/Player/PlayerNetworkController.cs @@ -1,11 +1,12 @@ using UnityEngine; using UnityEngine.InputSystem; -using Unity.Netcode; // NGO 필수 네임스페이스 +using Unity.Netcode; using Unity.Netcode.Components; [RequireComponent(typeof(NetworkObject))] public class PlayerNetworkController : NetworkBehaviour { + // ... 기존 변수들 유지 ... [Header("Movement Settings")] public float moveSpeed = 5f; public float rotationSpeed = 10f; @@ -21,7 +22,7 @@ public class PlayerNetworkController : NetworkBehaviour [Header("Mining Settings")] [SerializeField] private float attackRange = 1.5f; [SerializeField] private int miningDamage = 25; - [SerializeField] private LayerMask mineableLayer; // 'Mineable' 레이어 설정 필요 + [SerializeField] private LayerMask mineableLayer; private CharacterController _controller; private PlayerInputActions _inputActions; @@ -34,16 +35,15 @@ public class PlayerNetworkController : NetworkBehaviour private bool _isGrounded; private bool _isHoldingInteract = false; - // NGO에서 Start 대신 사용하는 네트워크 초기화 메서드 + // NGO 초기화 public override void OnNetworkSpawn() { - // 내 캐릭터가 아니라면 입력을 활성화하지 않습니다. if (!IsOwner) return; _inputActions = new PlayerInputActions(); _inputActions.Player.Jump.performed += ctx => OnJump(); - _inputActions.Player.Attack.performed += ctx => OnAttackServerRpc(); // 서버에 공격 요청 - _inputActions.Player.Interact.performed += ctx => OnInteractTap(); + _inputActions.Player.Attack.performed += ctx => OnAttackServerRpc(); + _inputActions.Player.Interact.performed += ctx => OnInteractTap(); // 탭 상호작용 _inputActions.Player.Interact.started += ctx => _isHoldingInteract = true; _inputActions.Player.Interact.canceled += ctx => _isHoldingInteract = false; @@ -60,17 +60,16 @@ public class PlayerNetworkController : NetworkBehaviour void Update() { - // [중요] '나'의 캐릭터가 아니면 조종 로직을 아예 실행하지 않습니다. if (!IsOwner) return; if (_traveler != null && _traveler.IsTraveling) return; HandleGravity(); HandleMovement(); - // 건설 가속 로직 if (_isHoldingInteract) PerformConstructionSupport(); } + // --- 이동 관련 로직 (기존 유지) --- private void HandleMovement() { _isGrounded = _controller.isGrounded; @@ -80,7 +79,6 @@ public class PlayerNetworkController : NetworkBehaviour _moveInput = _inputActions.Player.Move.ReadValue(); Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y).normalized; - // 지상/공중 및 공격 중 관성 처리 if (isAttacking) move = _isGrounded ? Vector3.zero : _currentMoveDir; else if (move.magnitude > 0.1f) _currentMoveDir = move; @@ -93,7 +91,6 @@ public class PlayerNetworkController : NetworkBehaviour } _controller.Move(move * moveSpeed * Time.deltaTime); } - _animator.SetFloat("MoveSpeed", isAttacking && _isGrounded ? 0 : move.magnitude); } @@ -109,49 +106,56 @@ public class PlayerNetworkController : NetworkBehaviour if (_isGrounded) _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); } - // 기존 OnAttackServerRpc를 수정하거나 호출되는 시점에 아래 로직 포함 + // --- 채광 로직 (기존 유지) --- [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)] private void OnAttackServerRpc() { - // 모든 유저에게 애니메이션 재생 신호 전송 OnAttackClientRpc(); - - // [채광 판정] 서버에서 물리 연산을 통해 전방의 블록 탐색 Collider[] hitBlocks = Physics.OverlapSphere(transform.position + transform.forward, attackRange, mineableLayer); - foreach (var col in hitBlocks) { MineableBlock block = col.GetComponentInParent(); - if (block != null) - { - // 서버가 직접 블록의 대미지 함수 호출 - block.TakeDamageRpc(miningDamage); - } + if (block != null) block.TakeDamageRpc(miningDamage); } } [ClientRpc] - private void OnAttackClientRpc() - { - _animator.SetTrigger("Attack"); - } + private void OnAttackClientRpc() => _animator.SetTrigger("Attack"); private void OnInteractTap() { + if (!IsOwner) return; _animator.SetTrigger("Interact"); + // 1. 캐릭터 주변 반경 내의 모든 콜라이더 감지 Collider[] colliders = Physics.OverlapSphere(transform.position, interactRange, interactableLayer); + + IInteractable closestTarget = null; + float minDistance = float.MaxValue; + foreach (var col in colliders) { - IInteractable interactable = col.GetComponentInParent(); - if (interactable != null) + // 2. 방향 조건 없이 거리만 체크하여 가장 가까운 것 선택 + float dist = Vector3.Distance(transform.position, col.transform.position); + if (dist < minDistance) { - interactable.Interact(gameObject); - break; + IInteractable interactable = col.GetComponentInParent(); + if (interactable != null) + { + minDistance = dist; + closestTarget = interactable; + } } } + + // 3. 가장 가까운 대상이 있다면 상호작용 실행 + if (closestTarget != null) + { + closestTarget.Interact(gameObject); + } } + // 건설 지원 로직 (범위 내 지속 작업이므로 OverlapSphere 유지 또는 Raycast로 변경 가능) private void PerformConstructionSupport() { Collider[] targets = Physics.OverlapSphere(transform.position, interactRange, constructionLayer); @@ -160,7 +164,6 @@ public class PlayerNetworkController : NetworkBehaviour ConstructionSite site = col.GetComponentInParent(); if (site != null) { - // 건설 진행도는 서버에서 관리하는 것이 안전하므로 나중에 RPC로 전환 필요 site.AdvanceConstruction(Time.deltaTime * buildSpeedMultiplier); } } @@ -168,6 +171,6 @@ public class PlayerNetworkController : NetworkBehaviour public override void OnNetworkDespawn() { - if (IsOwner) _inputActions.Disable(); + if (IsOwner && _inputActions != null) _inputActions.Disable(); } } \ No newline at end of file diff --git a/Assets/Scripts/Tower/ConstructionSite.cs b/Assets/Scripts/Tower/ConstructionSite.cs index b5c7dac..73e6257 100644 --- a/Assets/Scripts/Tower/ConstructionSite.cs +++ b/Assets/Scripts/Tower/ConstructionSite.cs @@ -14,6 +14,8 @@ public class ConstructionSite : NetworkBehaviour if (!IsServer) return; _syncTurretIndex.Value = index; _gridPos = pos; + + // BuildManager로부터 데이터 참조 _targetBuildTime = BuildManager.Instance.GetTurretData(index).buildTime; } @@ -35,8 +37,8 @@ public class ConstructionSite : NetworkBehaviour _isCompleted = true; var data = BuildManager.Instance.GetTurretData(_syncTurretIndex.Value); - // [중요] 저장된 그리드 좌표로부터 정확한 월드 좌표 복원 - Vector3 finalPos = BuildManager.Instance.GridToWorld(_gridPos); + // 고스트가 있던 그 위치 그대로 생성 + Vector3 finalPos = transform.position; GameObject finalObj = Instantiate(data.finalPrefab, finalPos, Quaternion.identity); finalObj.GetComponent().Spawn(); diff --git a/Assets/Scripts/TunnelNode.cs b/Assets/Scripts/TunnelNode.cs index 492d679..d4d243c 100644 --- a/Assets/Scripts/TunnelNode.cs +++ b/Assets/Scripts/TunnelNode.cs @@ -1,41 +1,79 @@ using UnityEngine; -using Unity.Netcode; -public class TunnelNode : NetworkBehaviour, IInteractable +public class TunnelNode : MonoBehaviour, IInteractable { - public TunnelNode aboveNode; - public TunnelNode belowNode; + public bool isTop; + [SerializeField] private TunnelNode partnerNode; // 같은 프리팹 내 반대쪽 노드 - public override void OnNetworkSpawn() + // [자동 연결] 닿아있는 다른 터널의 노드 + private TunnelNode neighborNode; + + // 물리적으로 닿으면 자동으로 연결 고리 생성 + private void OnTriggerEnter(Collider other) { - // 모든 클라이언트에서 각자 장부에 등록 - Vector3Int myPos = BuildManager.Instance.WorldToGrid3D(transform.position); - BuildManager.Instance.RegisterTunnel(myPos, this); - - // 모든 노드가 등록될 시간을 벌기 위해 서버에서 약간 지연 후 연결 명령 - if (IsServer) Invoke(nameof(SyncLinks), 0.2f); + if (other.TryGetComponent(out var neighbor)) + { + // 상단 노드는 하단 노드와만, 하단은 상단과만 연결 (잘못된 조립 방지) + if (this.isTop != neighbor.isTop) + { + neighborNode = neighbor; + } + } } - private void SyncLinks() => LinkVerticalRpc(); - - [Rpc(SendTo.Everyone)] - private void LinkVerticalRpc() => LinkVertical(); - - public void LinkVertical() + private void OnTriggerExit(Collider other) { - Vector3Int myPos = BuildManager.Instance.WorldToGrid3D(transform.position); - aboveNode = BuildManager.Instance.GetTunnelAt(myPos + Vector3Int.up); - belowNode = BuildManager.Instance.GetTunnelAt(myPos + Vector3Int.down); - - if (aboveNode != null) aboveNode.belowNode = this; - if (belowNode != null) belowNode.aboveNode = this; + if (other.TryGetComponent(out var neighbor)) + { + if (neighborNode == neighbor) neighborNode = null; + } } - public void Interact(GameObject user) + public void Interact(GameObject player) { - var traveler = user.GetComponent(); - if (traveler && !traveler.IsTraveling) traveler.StartTravel(this); + // 내가 체인의 끝단(입구/출구)일 때만 작동 + if (neighborNode != null) return; + + // 반대쪽 끝 노드를 찾아서 이동 요청 + TunnelNode endNode = FindUltimateEndNode(); + if (player.TryGetComponent(out var traveler)) + { + traveler.StartTravel(endNode.transform.position, !endNode.isTop); + } } - public string GetInteractionText() => "터널 이용 [E]"; + private TunnelNode FindUltimateEndNode() + { + TunnelNode current = this; + + // [연결 고리 추적] 파트너 -> 이웃 -> 파트너 -> 이웃... + while (true) + { + // 1. 현재 노드의 프리팹 반대쪽(파트너)으로 이동 + TunnelNode partner = current.partnerNode; + + // 2. 파트너와 연결된 다음 터널의 노드가 있는지 확인 + if (partner.neighborNode != null) + { + current = partner.neighborNode; // 다음 터널로 점프 + } + else + { + return partner; // 더 이상 연결이 없으면 파트너가 최종 목적지 + } + } + } + + // 디버깅: 연결 상태를 씬 뷰에서 확인 + private void OnDrawGizmos() + { + Gizmos.color = isTop ? Color.cyan : Color.blue; + Gizmos.DrawWireSphere(transform.position, 0.2f); + + if (neighborNode != null) + { + Gizmos.color = Color.red; + Gizmos.DrawLine(transform.position, neighborNode.transform.position); + } + } } \ No newline at end of file diff --git a/Assets/Scripts/TunnelTraveler.cs b/Assets/Scripts/TunnelTraveler.cs index 02117fa..0922c60 100644 --- a/Assets/Scripts/TunnelTraveler.cs +++ b/Assets/Scripts/TunnelTraveler.cs @@ -1,6 +1,5 @@ using UnityEngine; using System.Collections; -using System.Collections.Generic; public class TunnelTraveler : MonoBehaviour { @@ -8,58 +7,33 @@ public class TunnelTraveler : MonoBehaviour private bool _isTraveling; public bool IsTraveling => _isTraveling; - public void StartTravel(TunnelNode start) + public void StartTravel(Vector3 targetPos, bool isBottom) { if (_isTraveling) return; - // [디버그 1] 상호작용한 노드의 연결 상태 확인 - Debug.Log($"[Travel] 시작 노드: {start.name} | 위: {(start.aboveNode != null)} | 아래: {(start.belowNode != null)}"); - - List path = new List(); - - // 이동 방향 결정 (아래가 있으면 아래로, 없으면 위로) - bool goDown = start.belowNode != null; - TunnelNode curr = goDown ? start.belowNode : start.aboveNode; - - while (curr != null) - { - path.Add(curr.transform.position); - curr = goDown ? curr.belowNode : curr.aboveNode; - } - - // [디버그 2] 최종 경로 개수 확인 - Debug.Log($"[Travel] 생성된 경로 포인트 개수: {path.Count}"); - - if (path.Count > 0) - { - StartCoroutine(Travel(path)); - } - else - { - Debug.LogWarning("[Travel] 이동할 경로가 없습니다! 노드 연결을 확인하세요."); - } + // 하단 노드가 목적지라면 캐릭터 중심 좌표를 고려해 약간 더 아래(바닥)로 설정 + Vector3 finalDestination = isBottom ? targetPos + Vector3.down * 0.5f : targetPos; + StartCoroutine(TravelRoutine(finalDestination)); } - private IEnumerator Travel(List path) + private IEnumerator TravelRoutine(Vector3 destination) { - Debug.Log("[Travel] 코루틴 이동 시작!"); _isTraveling = true; var cc = GetComponent(); if (cc) cc.enabled = false; - foreach (var point in path) + // X, Z는 유지한 채 Y축 중심으로 이동 + float halfHeight = cc ? cc.height / 2f : 0f; + Vector3 target = new Vector3(destination.x, destination.y - halfHeight, destination.z); + + while (Vector3.Distance(transform.position, target) > 0.05f) { - Vector3 target = new Vector3(point.x, point.y - (cc ? cc.height / 2 : 0), point.z); - while (Vector3.Distance(transform.position, target) > 0.1f) - { - transform.position = Vector3.MoveTowards(transform.position, target, travelSpeed * Time.deltaTime); - yield return null; - } - transform.position = target; + transform.position = Vector3.MoveTowards(transform.position, target, travelSpeed * Time.deltaTime); + yield return null; } + transform.position = target; if (cc) cc.enabled = true; _isTraveling = false; - Debug.Log("[Travel] 이동 완료!"); } } \ No newline at end of file diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset index 66c37ed..5d0a273 100644 --- a/ProjectSettings/TagManager.asset +++ b/ProjectSettings/TagManager.asset @@ -21,7 +21,7 @@ TagManager: - Interactable - Tunnel - Mineable - - + - TunnelNode - - -