기본 배치 건물이 시야를 제공하도록 변경

This commit is contained in:
2026-02-26 00:37:38 +09:00
parent a4eae438de
commit 600f35ae8f
10 changed files with 289 additions and 31 deletions

View File

@@ -38,7 +38,7 @@ namespace Northbound.Data
public int length => sizeZ; // Z축 (깊이) public int length => sizeZ; // Z축 (깊이)
public float height => sizeY; // Y축 (높이) public float height => sizeY; // Y축 (높이)
public int maxHealth => maxHp; public int maxHealth => maxHp;
public float visionRange => atkRange; public float visionRange => sight;
public float requiredWorkAmount => manpower; public float requiredWorkAmount => manpower;
public Vector3 GetSize(int rotation) public Vector3 GetSize(int rotation)

47
Assets/Prefabs/Core.asset Normal file
View File

@@ -0,0 +1,47 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
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: 8c40fef5ebc37b743a3f225c1ca57c32, type: 3}
m_Name: Core
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.TowerData
id: 0
memo:
buildingName:
level: 0
upgradeTo: 0
towerType:
mana: 0
manpower: 0
sizeX: 0
sizeY: 0
sizeZ: 0
maxHp: 0
sight: 100
atkRange: 0
atkDamage: 0
atkIntervalSec: 0
modelPath:
prefab: {fileID: 0}
icon: {fileID: 0}
placementOffset: {x: 0, y: 0, z: 0}
allowRotation: 1
isIndestructible: 0
autoRegenerate: 0
regenPerSecond: 1
providesVision: 1
constructionEquipment:
socketName: RightHand
equipmentPrefab: {fileID: 0}
attachOnStart: 1
detachOnEnd: 1
keepEquipped: 0
attachDelay: 0
detachDelay: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8e9cb7f0c2209b543b171709534789aa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -49,7 +49,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
GlobalObjectIdHash: 3998537868 GlobalObjectIdHash: 615747208
InScenePlacedSourceGlobalObjectIdHash: 615747208 InScenePlacedSourceGlobalObjectIdHash: 615747208
DeferredDespawnTick: 0 DeferredDespawnTick: 0
Ownership: 0 Ownership: 0
@@ -106,7 +106,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Building m_EditorClassIdentifier: Assembly-CSharp::Northbound.Building
ShowTopMostFoldoutHeaderGroup: 1 ShowTopMostFoldoutHeaderGroup: 1
buildingData: {fileID: 0} buildingData: {fileID: 11400000, guid: 8e9cb7f0c2209b543b171709534789aa, type: 2}
gridPosition: {x: 0, y: 0, z: 0} gridPosition: {x: 0, y: 0, z: 0}
rotation: 0 rotation: 0
initialTeam: 1 initialTeam: 1
@@ -152,10 +152,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 760137a2fd0da7f458ac4b0ee7f485d6, type: 3} m_Script: {fileID: 11500000, guid: 760137a2fd0da7f458ac4b0ee7f485d6, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.FogOfWarVisibility m_EditorClassIdentifier: Assembly-CSharp::Northbound.FogOfWarVisibility
alwaysVisible: 1
showInExploredAreas: 1 showInExploredAreas: 1
updateInterval: 0.2 updateInterval: 0.2
renderers: [] renderers: []
enableDistantVisibility: 1 enableDistantVisibility: 1
baseVisibilityRange: 50
heightVisibilityMultiplier: 2 heightVisibilityMultiplier: 2
minHeightForDistantVisibility: 3 minHeightForDistantVisibility: 3
useExploredMaterial: 0 useExploredMaterial: 0

View File

@@ -6,8 +6,16 @@ namespace Northbound
/// <summary> /// <summary>
/// 블랙스미스 건물 - 업그레이드 구매 가능 /// 블랙스미스 건물 - 업그레이드 구매 가능
/// </summary> /// </summary>
public class Blacksmith : MonoBehaviour, IInteractable public class Blacksmith : NetworkBehaviour, IInteractable, IVisionProvider, ITeamMember
{ {
[Header("Vision Settings")]
[Tooltip("블랙스미스가 제공하는 시야 범위")]
public float visionRange = 10f;
[Header("Team")]
[Tooltip("건물의 팀")]
public TeamType initialTeam = TeamType.Player;
[Header("UI Reference")] [Header("UI Reference")]
[SerializeField] private GameObject _upgradePopupPrefab; [SerializeField] private GameObject _upgradePopupPrefab;
@@ -55,9 +63,6 @@ namespace Northbound
return transform; return transform;
} }
#endregion #endregion
private void OpenUpgradePopup(ulong playerId) private void OpenUpgradePopup(ulong playerId)
@@ -115,12 +120,63 @@ namespace Northbound
return null; return null;
} }
private void OnDestroy() public override void OnDestroy()
{ {
if (_popupInstance != null) if (_popupInstance != null)
{ {
Destroy(_popupInstance); Destroy(_popupInstance);
} }
base.OnDestroy();
} }
#region NetworkBehaviour Overrides
public override void OnNetworkSpawn()
{
if (IsServer)
{
// 시야 제공자로 등록
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
}
}
public override void OnNetworkDespawn()
{
if (IsServer)
{
// 시야 제공자 등록 해제
FogOfWarSystem.Instance?.UnregisterVisionProvider(this);
}
}
#endregion
#region IVisionProvider Implementation
public ulong GetOwnerId() => 0; // 블랙스미스는 모든 플레이어에게 시야 제공
public float GetVisionRange() => visionRange;
Transform IVisionProvider.GetTransform() => transform;
public bool IsActive() => IsSpawned;
TeamType IVisionProvider.GetTeam() => initialTeam;
#endregion
#region ITeamMember Implementation
public TeamType GetTeam() => initialTeam;
public bool IsDead() => false; // 블랙스미스는 파괴되지 않음
public void SetTeam(TeamType team)
{
// 블랙스미스의 팀은 변경할 수 없음
Debug.LogWarning("[Blacksmith] 블랙스미스의 팀은 변경할 수 없습니다.");
}
#endregion
} }
} }

View File

@@ -7,12 +7,15 @@ namespace Northbound
/// <summary> /// <summary>
/// 플레이어가 자원을 건내받아 게임의 전역 자원으로 관리하는 중앙 허브 /// 플레이어가 자원을 건내받아 게임의 전역 자원으로 관리하는 중앙 허브
/// </summary> /// </summary>
public class Core : NetworkBehaviour, IInteractable, IDamageable, ITeamMember public class Core : NetworkBehaviour, IInteractable, IDamageable, ITeamMember, IVisionProvider
{ {
[Header("Core Settings")] [Header("Core Settings")]
public int maxStorageCapacity = 1000; // 코어의 최대 저장 용량 public int maxStorageCapacity = 1000; // 코어의 최대 저장 용량
public bool unlimitedStorage = false; // 무제한 저장소 public bool unlimitedStorage = false; // 무제한 저장소
[Header("Vision")]
public float visionRange = 15f; // 코어가 제공하는 시야 범위
[Header("Health")] [Header("Health")]
public int maxHealth = 1000; public int maxHealth = 1000;
public GameObject damageEffectPrefab; public GameObject damageEffectPrefab;
@@ -55,6 +58,9 @@ namespace Northbound
{ {
_totalResources.Value = 0; _totalResources.Value = 0;
_currentHealth.Value = maxHealth; _currentHealth.Value = maxHealth;
// 시야 제공자로 등록
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
} }
_currentHealth.OnValueChanged += OnHealthChanged; _currentHealth.OnValueChanged += OnHealthChanged;
@@ -62,6 +68,12 @@ namespace Northbound
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
{ {
if (IsServer)
{
// 시야 제공자 등록 해제
FogOfWarSystem.Instance?.UnregisterVisionProvider(this);
}
_currentHealth.OnValueChanged -= OnHealthChanged; _currentHealth.OnValueChanged -= OnHealthChanged;
} }
@@ -372,5 +384,19 @@ namespace Northbound
} }
#endregion #endregion
#region IVisionProvider Implementation
public ulong GetOwnerId() => 0; // 코어는 모든 플레이어에게 시야 제공
public float GetVisionRange() => visionRange;
Transform IVisionProvider.GetTransform() => transform;
public bool IsActive() => IsSpawned && !IsDead();
TeamType IVisionProvider.GetTeam() => TeamType.Player;
#endregion
} }
} }

View File

@@ -182,17 +182,39 @@ namespace Northbound
{ {
if (NetworkManager.Singleton == null) return ulong.MaxValue; if (NetworkManager.Singleton == null) return ulong.MaxValue;
var localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); // 방법 1: SpawnManager에서 찾기
if (localPlayer != null) NetworkObject localPlayer = null;
if (NetworkManager.Singleton.SpawnManager != null)
{ {
var playerController = localPlayer.GetComponent<NetworkPlayerController>(); localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject();
if (playerController != null) }
// 방법 2: LocalClient에서 찾기
if (localPlayer == null && NetworkManager.Singleton.LocalClient != null)
{
localPlayer = NetworkManager.Singleton.LocalClient.PlayerObject;
}
// 방법 3: 직접 검색 (IsLocalPlayer인 플레이어 찾기)
if (localPlayer == null)
{
var allPlayers = FindObjectsByType<NetworkPlayerController>(FindObjectsSortMode.None);
foreach (var player in allPlayers)
{ {
return playerController.OwnerPlayerId; if (player.IsLocalPlayer)
{
localPlayer = player.GetComponent<NetworkObject>();
break;
}
} }
} }
return ulong.MaxValue; if (localPlayer == null) return ulong.MaxValue;
var playerController = localPlayer.GetComponent<NetworkPlayerController>();
if (playerController == null) return ulong.MaxValue;
return playerController.OwnerPlayerId;
} }
/// <summary> /// <summary>

View File

@@ -307,7 +307,8 @@ namespace Northbound
if (!_visionProviders.Contains(provider)) if (!_visionProviders.Contains(provider))
{ {
_visionProviders.Add(provider); _visionProviders.Add(provider);
// Debug.Log($"<color=cyan>[FogOfWar] Vision Provider 등록: {provider.GetTransform().name} (Owner: {provider.GetOwnerId()})</color>"); // 즉시 시야 업데이트 트리거
_updateTimer = updateInterval;
} }
} }

View File

@@ -10,6 +10,9 @@ namespace Northbound
public class FogOfWarVisibility : MonoBehaviour public class FogOfWarVisibility : MonoBehaviour
{ {
[Header("Visibility Settings")] [Header("Visibility Settings")]
[Tooltip("Always show this object regardless of fog state (for core, important structures)")]
public bool alwaysVisible = false;
[Tooltip("Show this object in explored areas (greyed out) or only when visible")] [Tooltip("Show this object in explored areas (greyed out) or only when visible")]
public bool showInExploredAreas = false; public bool showInExploredAreas = false;
@@ -19,10 +22,13 @@ namespace Northbound
[Tooltip("Renderers to show/hide (auto-detected if empty)")] [Tooltip("Renderers to show/hide (auto-detected if empty)")]
public Renderer[] renderers; public Renderer[] renderers;
[Header("Height-Based Distant Visibility")] [Header("Extended Visibility")]
[Tooltip("Enable visibility from farther away based on object height")] [Tooltip("Enable visibility from farther away based on object height")]
public bool enableDistantVisibility = true; public bool enableDistantVisibility = true;
[Tooltip("Base visibility range (additional range beyond player vision)")]
public float baseVisibilityRange = 10f;
[Tooltip("Visibility range multiplier per unit of height (default: 2x vision per 1m height)")] [Tooltip("Visibility range multiplier per unit of height (default: 2x vision per 1m height)")]
public float heightVisibilityMultiplier = 2.0f; public float heightVisibilityMultiplier = 2.0f;
@@ -76,8 +82,17 @@ namespace Northbound
// CRITICAL: Start hidden and stay hidden until fog system confirms visibility // CRITICAL: Start hidden and stay hidden until fog system confirms visibility
// Force initial hide - don't use SetVisible because _isVisible defaults to false // Force initial hide - don't use SetVisible because _isVisible defaults to false
// which would cause early return // which would cause early return
_isVisible = true; // Set to true first so SetVisible(false) actually runs // 단, alwaysVisible이면 항상 보임
SetVisible(false); if (alwaysVisible)
{
_isVisible = false;
SetVisible(true);
}
else
{
_isVisible = true; // Set to true first so SetVisible(false) actually runs
SetVisible(false);
}
} }
/// <summary> /// <summary>
@@ -165,21 +180,50 @@ namespace Northbound
{ {
if (NetworkManager.Singleton == null) return ulong.MaxValue; if (NetworkManager.Singleton == null) return ulong.MaxValue;
var localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); // 방법 1: SpawnManager에서 찾기
if (localPlayer != null) NetworkObject localPlayer = null;
if (NetworkManager.Singleton.SpawnManager != null)
{ {
var playerController = localPlayer.GetComponent<NetworkPlayerController>(); localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject();
if (playerController != null) }
// 방법 2: LocalClient에서 찾기
if (localPlayer == null && NetworkManager.Singleton.LocalClient != null)
{
localPlayer = NetworkManager.Singleton.LocalClient.PlayerObject;
}
// 방법 3: 직접 검색 (IsLocalPlayer인 플레이어 찾기)
if (localPlayer == null)
{
var allPlayers = FindObjectsByType<NetworkPlayerController>(FindObjectsSortMode.None);
foreach (var player in allPlayers)
{ {
return playerController.OwnerPlayerId; if (player.IsLocalPlayer)
{
localPlayer = player.GetComponent<NetworkObject>();
break;
}
} }
} }
return ulong.MaxValue; if (localPlayer == null) return ulong.MaxValue;
var playerController = localPlayer.GetComponent<NetworkPlayerController>();
if (playerController == null) return ulong.MaxValue;
return playerController.OwnerPlayerId;
} }
private void UpdateVisibility() private void UpdateVisibility()
{ {
// 항상 보이는 객체는 fog 체크 안함
if (alwaysVisible)
{
SetVisible(true);
return;
}
var fogSystem = FogOfWarSystem.Instance; var fogSystem = FogOfWarSystem.Instance;
if (fogSystem == null) if (fogSystem == null)
{ {
@@ -268,11 +312,14 @@ namespace Northbound
// Calculate extended visibility range based on height // Calculate extended visibility range based on height
// Taller objects can be seen from farther away // Taller objects can be seen from farther away
// Formula: Base range + (height - minHeight) * multiplier // Formula: Base range + (height - minHeight) * multiplier
float extendedRange = (_objectHeight - minHeightForDistantVisibility) * heightVisibilityMultiplier; float extendedRange = 0f;
if (_objectHeight >= minHeightForDistantVisibility)
{
extendedRange = (_objectHeight - minHeightForDistantVisibility) * heightVisibilityMultiplier;
}
// Get player's vision range (assume average vision provider has ~15 unit range) // Total range = player vision + base visibility + height bonus
float baseVisionRange = 15f; // You can make this configurable float totalRange = baseVisibilityRange + extendedRange;
float totalRange = baseVisionRange + extendedRange;
return distanceToPlayer <= totalRange; return distanceToPlayer <= totalRange;
} }

View File

@@ -3,8 +3,16 @@ using UnityEngine;
namespace Northbound namespace Northbound
{ {
public class WorkerSpawner : NetworkBehaviour, IInteractable public class WorkerSpawner : NetworkBehaviour, IInteractable, IVisionProvider, ITeamMember
{ {
[Header("Vision Settings")]
[Tooltip("워커 홀이 제공하는 시야 범위")]
public float visionRange = 10f;
[Header("Team")]
[Tooltip("건물의 팀")]
public TeamType initialTeam = TeamType.Player;
[Header("Spawner Settings")] [Header("Spawner Settings")]
public GameObject workerPrefab; public GameObject workerPrefab;
public Transform spawnPoint; public Transform spawnPoint;
@@ -34,11 +42,24 @@ namespace Northbound
{ {
base.OnNetworkSpawn(); base.OnNetworkSpawn();
_workerCount.OnValueChanged += OnWorkerCountChanged; _workerCount.OnValueChanged += OnWorkerCountChanged;
if (IsServer)
{
// 시야 제공자로 등록
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
}
} }
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
{ {
_workerCount.OnValueChanged -= OnWorkerCountChanged; _workerCount.OnValueChanged -= OnWorkerCountChanged;
if (IsServer)
{
// 시야 제공자 등록 해제
FogOfWarSystem.Instance?.UnregisterVisionProvider(this);
}
base.OnNetworkDespawn(); base.OnNetworkDespawn();
} }
@@ -240,9 +261,37 @@ namespace Northbound
Vector3 spawnCenter = spawnPoint != null ? spawnPoint.position : transform.position; Vector3 spawnCenter = spawnPoint != null ? spawnPoint.position : transform.position;
Gizmos.DrawWireSphere(spawnCenter, spawnRadius); Gizmos.DrawWireSphere(spawnCenter, spawnRadius);
UnityEditor.Handles.Label(spawnCenter + Vector3.up * 2f, UnityEditor.Handles.Label(spawnCenter + Vector3.up * 2f,
$"Worker Spawner\nWorkers: {_workerCount.Value}/{maxWorkers}"); $"Worker Spawner\nWorkers: {_workerCount.Value}/{maxWorkers}");
#endif #endif
} }
#region IVisionProvider Implementation
public ulong GetOwnerId() => 0; // 워커 홀은 모든 플레이어에게 시야 제공
public float GetVisionRange() => visionRange;
Transform IVisionProvider.GetTransform() => transform;
public bool IsActive() => IsSpawned;
TeamType IVisionProvider.GetTeam() => initialTeam;
#endregion
#region ITeamMember Implementation
public TeamType GetTeam() => initialTeam;
public bool IsDead() => false; // 워커 홀은 파괴되지 않음
public void SetTeam(TeamType team)
{
// 워커 홀의 팀은 변경할 수 없음
Debug.LogWarning("[WorkerSpawner] 워커 홀의 팀은 변경할 수 없습니다.");
}
#endregion
} }
} }