diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
index 76af8ee..4c237bb 100644
--- a/Assembly-CSharp.csproj
+++ b/Assembly-CSharp.csproj
@@ -68,6 +68,7 @@
+
diff --git a/Assets/Prefabs/Gate.prefab b/Assets/Prefabs/Gate.prefab
index c19a8da..5007636 100644
--- a/Assets/Prefabs/Gate.prefab
+++ b/Assets/Prefabs/Gate.prefab
@@ -1,5 +1,67 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &529351500009441178
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 9184257640407244267}
+ m_Layer: 0
+ m_Name: FrontExitPoint
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &9184257640407244267
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 529351500009441178}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 4}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 7180212943015590733}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &2607013536690627895
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1475778514072803484}
+ m_Layer: 0
+ m_Name: BackExitPoint
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1475778514072803484
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2607013536690627895}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: -4}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 7180212943015590733}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2998551506809628252
GameObject:
m_ObjectHideFlags: 0
@@ -11,6 +73,12 @@ GameObject:
- component: {fileID: 7180212943015590733}
- component: {fileID: 6249878823685746784}
- component: {fileID: 321081944195966357}
+ - component: {fileID: 2557322720114216881}
+ - component: {fileID: 6718554010216801635}
+ - component: {fileID: 6336968390548483685}
+ - component: {fileID: 1893682255695519596}
+ - component: {fileID: 2622677457934171119}
+ - component: {fileID: 2624539771375861302}
m_Layer: 0
m_Name: Gate
m_TagString: Untagged
@@ -32,6 +100,8 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1060510535957475976}
+ - {fileID: 9184257640407244267}
+ - {fileID: 1475778514072803484}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!65 &6249878823685746784
@@ -71,6 +141,150 @@ NavMeshObstacle:
m_CarveOnlyStationary: 1
m_Center: {x: 0, y: 0, z: 0}
m_TimeToStationary: 0.5
+--- !u!114 &2557322720114216881
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2998551506809628252}
+ 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: 2819059600
+ InScenePlacedSourceGlobalObjectIdHash: 1171432577
+ DeferredDespawnTick: 0
+ Ownership: 1
+ AlwaysReplicateAsRoot: 0
+ SynchronizeTransform: 1
+ ActiveSceneSynchronization: 0
+ SceneMigrationSynchronization: 0
+ SpawnWithObservers: 1
+ DontDestroyWithOwner: 0
+ AutoObjectParentSync: 1
+ SyncOwnerTransformWhenParented: 1
+ AllowOwnerToParent: 0
+--- !u!114 &6718554010216801635
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2998551506809628252}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 71da9742096858d4d97441d6b84f13dd, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::Northbound.TeamGate
+ ShowTopMostFoldoutHeaderGroup: 1
+ allowedTeam: 1
+ allowAllTeams: 0
+ frontTrigger: {fileID: 1893682255695519596}
+ backTrigger: {fileID: 2622677457934171119}
+ frontExitPoint: {fileID: 9184257640407244267}
+ backExitPoint: {fileID: 1475778514072803484}
+ teleportCooldown: 1
+ teleportEffectPrefab: {fileID: 0}
+ teleportSound: {fileID: 0}
+ blockedSound: {fileID: 0}
+ teamIndicatorRenderer: {fileID: 0}
+ allowedTeamMaterial: {fileID: 0}
+ blockedMaterial: {fileID: 0}
+--- !u!114 &6336968390548483685
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2998551506809628252}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 0ceedb9b012d848478813136b65738ae, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::Northbound.Building
+ ShowTopMostFoldoutHeaderGroup: 1
+ buildingData: {fileID: 0}
+ gridPosition: {x: 0, y: 0, z: 0}
+ rotation: 0
+ initialTeam: 1
+ initialOwnerId: 0
+ useInitialOwner: 0
+ 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!65 &1893682255695519596
+BoxCollider:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2998551506809628252}
+ 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: 3
+ m_Size: {x: 8, y: 8, z: 1}
+ m_Center: {x: 0, y: 0, z: -2}
+--- !u!65 &2622677457934171119
+BoxCollider:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2998551506809628252}
+ 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: 3
+ m_Size: {x: 8, y: 8, z: 1}
+ m_Center: {x: 0, y: 0, z: 2}
+--- !u!114 &2624539771375861302
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2998551506809628252}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.AI.Navigation::Unity.AI.Navigation.NavMeshLink
+ m_SerializedVersion: 1
+ m_AgentTypeID: 0
+ m_StartPoint: {x: 0, y: 0, z: -2.5}
+ m_EndPoint: {x: 0, y: 0, z: 2.5}
+ m_StartTransform: {fileID: 0}
+ m_EndTransform: {fileID: 0}
+ m_Activated: 1
+ m_Width: 0
+ m_CostModifier: 1
+ m_IsOverridingCost: 0
+ m_Bidirectional: 1
+ m_AutoUpdatePosition: 0
+ m_Area: 0
--- !u!1001 &665699090875585891
PrefabInstance:
m_ObjectHideFlags: 0
diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab
index b991c6c..d3778f9 100644
--- a/Assets/Prefabs/Player.prefab
+++ b/Assets/Prefabs/Player.prefab
@@ -129,12 +129,12 @@ CharacterController:
m_Enabled: 1
serializedVersion: 3
m_Height: 2
- m_Radius: 0.5
+ m_Radius: 0.3
m_SlopeLimit: 45
m_StepOffset: 0.3
m_SkinWidth: 0.08
m_MinMoveDistance: 0.001
- m_Center: {x: 0, y: 0, z: 0}
+ m_Center: {x: 0, y: 1, z: 0}
--- !u!114 &1883169379180791275
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -156,6 +156,8 @@ MonoBehaviour:
invalidMaterial: {fileID: 0}
selectedBuildingIndex: 0
showGridBounds: 1
+ enableDragBuilding: 1
+ maxDragBuildingCount: 50
--- !u!114 &8729870597719024730
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/Assets/Scenes/GameMain.unity b/Assets/Scenes/GameMain.unity
index b23c747..d24bb00 100644
--- a/Assets/Scenes/GameMain.unity
+++ b/Assets/Scenes/GameMain.unity
@@ -1872,6 +1872,11 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1354840086 stripped
+GameObject:
+ m_CorrespondingSourceObject: {fileID: 2998551506809628252, guid: 1be692ccde46d2a4baedc2ee75fbfbdb, type: 3}
+ m_PrefabInstance: {fileID: 8960047168891090775}
+ m_PrefabAsset: {fileID: 0}
--- !u!1 &1433142230
GameObject:
m_ObjectHideFlags: 0
@@ -3147,13 +3152,70 @@ PrefabInstance:
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
- m_AddedComponents: []
+ m_AddedComponents:
+ - targetCorrespondingSourceObject: {fileID: 2998551506809628252, guid: 1be692ccde46d2a4baedc2ee75fbfbdb, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 8960047168891090780}
+ - targetCorrespondingSourceObject: {fileID: 2998551506809628252, guid: 1be692ccde46d2a4baedc2ee75fbfbdb, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 8960047168891090779}
m_SourcePrefab: {fileID: 100100000, guid: 1be692ccde46d2a4baedc2ee75fbfbdb, type: 3}
--- !u!4 &8960047168891090776 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 7180212943015590733, guid: 1be692ccde46d2a4baedc2ee75fbfbdb, type: 3}
m_PrefabInstance: {fileID: 8960047168891090775}
m_PrefabAsset: {fileID: 0}
+--- !u!114 &8960047168891090779
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1354840086}
+ 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: 1203277090
+ 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 &8960047168891090780
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1354840086}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 71da9742096858d4d97441d6b84f13dd, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::Northbound.TeamGate
+ ShowTopMostFoldoutHeaderGroup: 1
+ allowedTeam: 1
+ allowAllTeams: 0
+ frontTrigger: {fileID: 0}
+ backTrigger: {fileID: 0}
+ frontExitPoint: {fileID: 0}
+ backExitPoint: {fileID: 0}
+ teleportCooldown: 1
+ teleportEffectPrefab: {fileID: 0}
+ teleportSound: {fileID: 0}
+ blockedSound: {fileID: 0}
+ teamIndicatorRenderer: {fileID: 0}
+ allowedTeamMaterial: {fileID: 0}
+ blockedMaterial: {fileID: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/TeamGate.cs b/Assets/Scripts/TeamGate.cs
new file mode 100644
index 0000000..551870e
--- /dev/null
+++ b/Assets/Scripts/TeamGate.cs
@@ -0,0 +1,386 @@
+using Unity.Netcode;
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Northbound
+{
+ ///
+ /// 특정 팀만 순간이동으로 통과할 수 있는 성문
+ /// 허용되지 않은 팀은 물리적으로 막힘
+ ///
+ public class TeamGate : NetworkBehaviour
+ {
+ [Header("Gate Settings")]
+ [Tooltip("통과를 허용할 팀")]
+ public TeamType allowedTeam = TeamType.Player;
+
+ [Tooltip("모든 팀 통과 허용 (공용 문)")]
+ public bool allowAllTeams = false;
+
+ [Header("Teleport Settings")]
+ [Tooltip("트리거 콜라이더 (문 앞면)")]
+ public Collider frontTrigger;
+
+ [Tooltip("트리거 콜라이더 (문 뒷면)")]
+ public Collider backTrigger;
+
+ [Tooltip("앞면 텔레포트 목적지 (문 뒤)")]
+ public Transform frontExitPoint;
+
+ [Tooltip("뒷면 텔레포트 목적지 (문 앞)")]
+ public Transform backExitPoint;
+
+ [Tooltip("텔레포트 쿨다운 (초) - 연속 텔레포트 방지")]
+ public float teleportCooldown = 1f;
+
+ [Header("Visual Feedback")]
+ public GameObject teleportEffectPrefab;
+ public AudioClip teleportSound;
+ public AudioClip blockedSound;
+
+ [Header("Team Indicator")]
+ [Tooltip("팀 표시용 렌더러 (깃발, 조명 등)")]
+ public Renderer teamIndicatorRenderer;
+
+ [Tooltip("허용 팀 머티리얼")]
+ public Material allowedTeamMaterial;
+
+ [Tooltip("차단 표시 머티리얼")]
+ public Material blockedMaterial;
+
+ // 쿨다운 추적
+ private Dictionary _lastTeleportTime = new Dictionary();
+
+ // 트리거별 진입 방향 추적
+ private Dictionary _triggerStates = new Dictionary();
+
+ private AudioSource _audioSource;
+
+ private void Awake()
+ {
+ _audioSource = GetComponent();
+ if (_audioSource == null && (teleportSound != null || blockedSound != null))
+ {
+ _audioSource = gameObject.AddComponent();
+ _audioSource.spatialBlend = 1f;
+ _audioSource.maxDistance = 20f;
+ }
+
+ // 트리거 설정 확인
+ if (frontTrigger != null)
+ {
+ frontTrigger.isTrigger = true;
+ }
+ else
+ {
+ Debug.LogError($"[TeamGate] {gameObject.name}: Front Trigger가 설정되지 않았습니다!");
+ }
+
+ if (backTrigger != null)
+ {
+ backTrigger.isTrigger = true;
+ }
+ else
+ {
+ Debug.LogError($"[TeamGate] {gameObject.name}: Back Trigger가 설정되지 않았습니다!");
+ }
+
+ // 출구 지점 확인
+ if (frontExitPoint == null || backExitPoint == null)
+ {
+ Debug.LogError($"[TeamGate] {gameObject.name}: Exit Points가 설정되지 않았습니다!");
+ }
+
+ UpdateVisual();
+ }
+
+ private void OnTriggerEnter(Collider other)
+ {
+ // 서버에서만 텔레포트 처리
+ if (!IsServer) return;
+
+ // 어느 트리거에 진입했는지 확인
+ Collider triggeredCollider = null;
+ Transform exitPoint = null;
+
+ if (other.bounds.Intersects(frontTrigger.bounds))
+ {
+ triggeredCollider = frontTrigger;
+ exitPoint = frontExitPoint;
+ }
+ else if (other.bounds.Intersects(backTrigger.bounds))
+ {
+ triggeredCollider = backTrigger;
+ exitPoint = backExitPoint;
+ }
+
+ if (triggeredCollider == null || exitPoint == null)
+ return;
+
+ // 이미 트리거 안에 있으면 무시 (연속 텔레포트 방지)
+ if (_triggerStates.ContainsKey(other) && _triggerStates[other])
+ return;
+
+ _triggerStates[other] = true;
+
+ // 팀 정보 가져오기
+ ITeamMember teamMember = other.GetComponent();
+ if (teamMember == null)
+ {
+ teamMember = other.GetComponentInParent();
+ }
+
+ if (teamMember == null)
+ {
+ _triggerStates[other] = false;
+ return;
+ }
+
+ // 쿨다운 체크
+ GameObject targetObject = teamMember as MonoBehaviour != null ? (teamMember as MonoBehaviour).gameObject : other.gameObject;
+
+ if (_lastTeleportTime.ContainsKey(targetObject))
+ {
+ float timeSinceLastTeleport = Time.time - _lastTeleportTime[targetObject];
+ if (timeSinceLastTeleport < teleportCooldown)
+ {
+ _triggerStates[other] = false;
+ return; // 쿨다운 중
+ }
+ }
+
+ // 통과 가능 여부 확인
+ bool canPass = CanPassThrough(teamMember);
+
+ if (canPass)
+ {
+ // 텔레포트 실행
+ ExecuteTeleport(other.gameObject, exitPoint.position);
+ _lastTeleportTime[targetObject] = Time.time;
+
+ // 이펙트 및 사운드
+ PlayTeleportEffectClientRpc(other.transform.position);
+ PlayTeleportEffectClientRpc(exitPoint.position);
+
+ if (teleportSound != null)
+ {
+ PlaySoundClientRpc(true);
+ }
+
+ Debug.Log($"[TeamGate] {other.name} 텔레포트: {triggeredCollider.name} -> {exitPoint.name}");
+ }
+ else
+ {
+ // 차단됨
+ if (blockedSound != null)
+ {
+ PlaySoundClientRpc(false);
+ }
+
+ Debug.Log($"[TeamGate] {other.name} 차단됨 - 팀: {teamMember.GetTeam()}");
+ }
+
+ _triggerStates[other] = false;
+ }
+
+ private void OnTriggerExit(Collider other)
+ {
+ // 트리거 상태 정리
+ if (_triggerStates.ContainsKey(other))
+ {
+ _triggerStates[other] = false;
+ }
+ }
+
+ ///
+ /// 팀원이 통과 가능한지 확인
+ ///
+ private bool CanPassThrough(ITeamMember teamMember)
+ {
+ if (allowAllTeams)
+ return true;
+
+ return teamMember.GetTeam() == allowedTeam;
+ }
+
+ ///
+ /// 오브젝트를 목적지로 텔레포트
+ ///
+ private void ExecuteTeleport(GameObject target, Vector3 destination)
+ {
+ // CharacterController를 사용하는 경우
+ CharacterController cc = target.GetComponent();
+ if (cc != null)
+ {
+ cc.enabled = false;
+ target.transform.position = destination;
+ cc.enabled = true;
+ return;
+ }
+
+ // Rigidbody를 사용하는 경우
+ Rigidbody rb = target.GetComponent();
+ if (rb != null)
+ {
+ rb.position = destination;
+ rb.linearVelocity = Vector3.zero; // 속도 초기화
+ return;
+ }
+
+ // NavMeshAgent를 사용하는 경우 (적 AI)
+ UnityEngine.AI.NavMeshAgent agent = target.GetComponent();
+ if (agent != null)
+ {
+ agent.Warp(destination);
+ return;
+ }
+
+ // 기본 Transform 이동
+ target.transform.position = destination;
+ }
+
+ ///
+ /// 텔레포트 이펙트 재생 (모든 클라이언트)
+ ///
+ [ClientRpc]
+ private void PlayTeleportEffectClientRpc(Vector3 position)
+ {
+ if (teleportEffectPrefab == null) return;
+
+ GameObject effect = Instantiate(teleportEffectPrefab, position, Quaternion.identity);
+ Destroy(effect, 3f);
+ }
+
+ ///
+ /// 사운드 재생 (모든 클라이언트)
+ ///
+ [ClientRpc]
+ private void PlaySoundClientRpc(bool isTeleport)
+ {
+ if (_audioSource == null) return;
+
+ AudioClip clip = isTeleport ? teleportSound : blockedSound;
+ if (clip != null)
+ {
+ _audioSource.PlayOneShot(clip);
+ }
+ }
+
+ ///
+ /// 허용 팀 변경 (서버에서 호출)
+ ///
+ [ServerRpc(RequireOwnership = false)]
+ public void ChangeAllowedTeamServerRpc(TeamType newTeam)
+ {
+ allowedTeam = newTeam;
+ UpdateVisualClientRpc(newTeam);
+
+ Debug.Log($"[TeamGate] 허용 팀 변경: {newTeam}");
+ }
+
+ ///
+ /// 비주얼 업데이트 (모든 클라이언트)
+ ///
+ [ClientRpc]
+ private void UpdateVisualClientRpc(TeamType team)
+ {
+ allowedTeam = team;
+ UpdateVisual();
+ }
+
+ private void UpdateVisual()
+ {
+ if (teamIndicatorRenderer == null)
+ return;
+
+ if (allowAllTeams)
+ {
+ if (allowedTeamMaterial != null)
+ teamIndicatorRenderer.material = allowedTeamMaterial;
+ }
+ else
+ {
+ // 팀별 색상 설정 (옵션)
+ switch (allowedTeam)
+ {
+ case TeamType.Player:
+ if (allowedTeamMaterial != null)
+ teamIndicatorRenderer.material = allowedTeamMaterial;
+ break;
+ default:
+ if (blockedMaterial != null)
+ teamIndicatorRenderer.material = blockedMaterial;
+ break;
+ }
+ }
+ }
+
+ private void OnDrawGizmos()
+ {
+ // 트리거 영역 시각화
+ Gizmos.color = new Color(0f, 1f, 1f, 0.3f);
+
+ if (frontTrigger != null)
+ {
+ DrawTriggerGizmo(frontTrigger, Color.cyan);
+ }
+
+ if (backTrigger != null)
+ {
+ DrawTriggerGizmo(backTrigger, Color.magenta);
+ }
+
+ // 출구 지점 시각화
+ if (frontExitPoint != null)
+ {
+ Gizmos.color = Color.green;
+ Gizmos.DrawWireSphere(frontExitPoint.position, 0.5f);
+ Gizmos.DrawLine(frontExitPoint.position, frontExitPoint.position + frontExitPoint.forward * 1f);
+ }
+
+ if (backExitPoint != null)
+ {
+ Gizmos.color = Color.yellow;
+ Gizmos.DrawWireSphere(backExitPoint.position, 0.5f);
+ Gizmos.DrawLine(backExitPoint.position, backExitPoint.position + backExitPoint.forward * 1f);
+ }
+
+ // 텔레포트 경로 표시
+ if (frontTrigger != null && frontExitPoint != null)
+ {
+ Gizmos.color = Color.cyan;
+ Gizmos.DrawLine(frontTrigger.bounds.center, frontExitPoint.position);
+ }
+
+ if (backTrigger != null && backExitPoint != null)
+ {
+ Gizmos.color = Color.magenta;
+ Gizmos.DrawLine(backTrigger.bounds.center, backExitPoint.position);
+ }
+ }
+
+ private void DrawTriggerGizmo(Collider col, Color color)
+ {
+ Gizmos.color = color;
+
+ if (col is BoxCollider box)
+ {
+ Matrix4x4 oldMatrix = Gizmos.matrix;
+ Gizmos.matrix = col.transform.localToWorldMatrix;
+ Gizmos.DrawWireCube(box.center, box.size);
+ Gizmos.matrix = oldMatrix;
+ }
+ else if (col is SphereCollider sphere)
+ {
+ Gizmos.DrawWireSphere(col.transform.position + sphere.center, sphere.radius);
+ }
+ }
+
+ private void OnDestroy()
+ {
+ // 정리
+ _lastTeleportTime.Clear();
+ _triggerStates.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/TeamGate.cs.meta b/Assets/Scripts/TeamGate.cs.meta
new file mode 100644
index 0000000..71a499c
--- /dev/null
+++ b/Assets/Scripts/TeamGate.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 71da9742096858d4d97441d6b84f13dd
\ No newline at end of file