지하 최적화
블록 프리팹 단위 -> 블록 청크 단위 스폰 기타 건설, 조준 관련 사이드이펙트 버그 수정
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// Mining behavior for pickaxes and similar tools.
|
||||
/// Supports both legacy MineableBlock and new chunk-based MineableChunk.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Items/Behaviors/Mining Behavior")]
|
||||
public class MiningBehavior : ItemBehavior
|
||||
@@ -27,7 +28,25 @@ public class MiningBehavior : ItemBehavior
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
// Use IDamageable interface for all damageable objects
|
||||
// Try chunk-based mining first (new system)
|
||||
if (target.TryGetComponent<MineableChunk>(out var chunk))
|
||||
{
|
||||
// Get the specific block index from PlayerNetworkController
|
||||
var playerController = user.GetComponent<PlayerNetworkController>();
|
||||
if (playerController != null)
|
||||
{
|
||||
var chunkTarget = playerController.GetCurrentChunkTarget();
|
||||
if (chunkTarget.hasHit && chunkTarget.chunk == chunk)
|
||||
{
|
||||
// Damage the specific block within the chunk
|
||||
chunk.DamageBlockServerRpc(chunkTarget.blockIndex, (byte)Mathf.Min(255, damage));
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to legacy IDamageable interface
|
||||
if (target.TryGetComponent<IDamageable>(out var damageable))
|
||||
{
|
||||
damageable.TakeDamage(new DamageInfo(damage, DamageType.Mining, user));
|
||||
@@ -37,8 +56,15 @@ public class MiningBehavior : ItemBehavior
|
||||
public override string GetBlockedReason(GameObject user, GameObject target)
|
||||
{
|
||||
if (target == null) return "No target";
|
||||
|
||||
// Check for chunk
|
||||
if (target.TryGetComponent<MineableChunk>(out _))
|
||||
return null; // Chunks are always mineable
|
||||
|
||||
// Check for legacy damageable
|
||||
if (!target.TryGetComponent<IDamageable>(out _))
|
||||
return "Cannot mine this object";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,13 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
private PlayerActionHandler _actionHandler;
|
||||
|
||||
private RectTransform _crosshairRect;
|
||||
private MineableBlock _currentTargetBlock; // 현재 강조 중인 블록 저장
|
||||
private MineableBlock _lastHighlightedBlock;
|
||||
private MineableBlock _currentTargetBlock; // 현재 강조 중인 블록 저장 (legacy)
|
||||
private MineableBlock _lastHighlightedBlock; // legacy block targeting
|
||||
|
||||
// Chunk-based targeting (new system)
|
||||
private MineableChunk _lastHighlightedChunk;
|
||||
private int _lastHighlightedChunkBlockIndex = -1;
|
||||
private ChunkInteractionHandler.ChunkHitResult _currentChunkTarget;
|
||||
|
||||
private CharacterController _controller;
|
||||
private PlayerInputActions _inputActions;
|
||||
@@ -209,13 +214,17 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
{
|
||||
if (!IsOwner || _actionHandler.IsBusy) return;
|
||||
|
||||
// Don't perform actions when in build mode
|
||||
if (BuildManager.Instance != null && BuildManager.Instance.IsBuildMode) return;
|
||||
|
||||
ItemData selectedItem = _inventory.GetSelectedItemData();
|
||||
if (selectedItem == null) return;
|
||||
|
||||
// Check if item has behavior (new system)
|
||||
if (selectedItem.behavior != null)
|
||||
{
|
||||
GameObject target = _lastHighlightedBlock != null ? _lastHighlightedBlock.gameObject : null;
|
||||
// Get target - prioritize chunk system over legacy blocks
|
||||
GameObject target = GetCurrentMiningTarget();
|
||||
|
||||
// Use the new behavior system
|
||||
if (selectedItem.CanUse(gameObject, target))
|
||||
@@ -334,51 +343,87 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
{
|
||||
if (!IsOwner || _crosshairRect == null) return;
|
||||
|
||||
// 1. 카메라 레이로 조준점 계산 (플레이어 몸통 무시)
|
||||
// Use direct raycast from camera through crosshair position
|
||||
// Use longer range (100m) from camera to catch all distances
|
||||
Ray aimRay = Camera.main.ScreenPointToRay(_crosshairRect.position);
|
||||
Vector3 worldAimPoint;
|
||||
if (Physics.Raycast(aimRay, out RaycastHit mouseHit, 100f, ~ignoreDuringAim))
|
||||
worldAimPoint = mouseHit.point;
|
||||
else
|
||||
worldAimPoint = aimRay.GetPoint(50f);
|
||||
|
||||
// 2. 캐릭터 가슴에서 조준점을 향하는 방향 계산
|
||||
Vector3 origin = transform.position + Vector3.up * 1.2f;
|
||||
Vector3 direction = (worldAimPoint - origin).normalized;
|
||||
|
||||
// 자기 자신 충돌 방지용 오프셋
|
||||
Vector3 rayStart = origin + direction * 0.4f;
|
||||
|
||||
// 3. [중요] 실제 공격과 동일한 SphereCast 실행
|
||||
RaycastHit blockHit;
|
||||
bool hasTarget = Physics.SphereCast(rayStart, aimRadius, direction, out blockHit, attackRange - 0.4f, mineableLayer);
|
||||
bool hasTarget = Physics.SphereCast(aimRay, aimRadius, out blockHit, 100f, mineableLayer);
|
||||
|
||||
// 4. 하이라이트 대상 업데이트
|
||||
MineableBlock currentTarget = null;
|
||||
// Filter by actual attack range from player
|
||||
if (hasTarget)
|
||||
{
|
||||
currentTarget = blockHit.collider.GetComponentInParent<MineableBlock>();
|
||||
Vector3 playerPos = transform.position + Vector3.up * 1.2f;
|
||||
float distanceFromPlayer = Vector3.Distance(playerPos, blockHit.point);
|
||||
|
||||
if (distanceFromPlayer > attackRange)
|
||||
{
|
||||
hasTarget = false; // Too far from player to interact with
|
||||
}
|
||||
}
|
||||
|
||||
// 대상이 바뀌었을 때만 아웃라인 갱신 (최적화)
|
||||
if (_lastHighlightedBlock != currentTarget)
|
||||
// 4. 하이라이트 대상 업데이트 - 청크 시스템과 레거시 블록 모두 지원
|
||||
MineableBlock currentLegacyTarget = null;
|
||||
MineableChunk currentChunk = null;
|
||||
int currentChunkBlockIndex = -1;
|
||||
|
||||
if (hasTarget)
|
||||
{
|
||||
// Try chunk first (new system)
|
||||
var chunkHit = ChunkInteractionHandler.GetChunkHit(blockHit);
|
||||
if (chunkHit.hasHit)
|
||||
{
|
||||
currentChunk = chunkHit.chunk;
|
||||
currentChunkBlockIndex = chunkHit.blockIndex;
|
||||
_currentChunkTarget = chunkHit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to legacy MineableBlock
|
||||
currentLegacyTarget = blockHit.collider.GetComponentInParent<MineableBlock>();
|
||||
_currentChunkTarget = ChunkInteractionHandler.ChunkHitResult.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentChunkTarget = ChunkInteractionHandler.ChunkHitResult.None;
|
||||
}
|
||||
|
||||
// Update chunk highlight
|
||||
bool chunkTargetChanged = (currentChunk != _lastHighlightedChunk) ||
|
||||
(currentChunkBlockIndex != _lastHighlightedChunkBlockIndex);
|
||||
if (chunkTargetChanged)
|
||||
{
|
||||
if (_lastHighlightedChunk != null)
|
||||
_lastHighlightedChunk.SetHighlight(false);
|
||||
if (currentChunk != null)
|
||||
currentChunk.SetHighlight(true, currentChunkBlockIndex);
|
||||
|
||||
_lastHighlightedChunk = currentChunk;
|
||||
_lastHighlightedChunkBlockIndex = currentChunkBlockIndex;
|
||||
}
|
||||
|
||||
// Update legacy block highlight
|
||||
if (_lastHighlightedBlock != currentLegacyTarget)
|
||||
{
|
||||
if (_lastHighlightedBlock != null) _lastHighlightedBlock.SetHighlight(false);
|
||||
if (currentTarget != null) currentTarget.SetHighlight(true);
|
||||
_lastHighlightedBlock = currentTarget;
|
||||
if (currentLegacyTarget != null) currentLegacyTarget.SetHighlight(true);
|
||||
_lastHighlightedBlock = currentLegacyTarget;
|
||||
}
|
||||
|
||||
// 기즈모 디버그 데이터 동기화
|
||||
_debugOrigin = rayStart;
|
||||
_debugDir = direction;
|
||||
_debugHit = hasTarget;
|
||||
_debugDist = hasTarget ? blockHit.distance : (attackRange - 0.4f);
|
||||
Ray debugRay = Camera.main.ScreenPointToRay(_crosshairRect.position);
|
||||
_debugOrigin = debugRay.origin;
|
||||
_debugDir = debugRay.direction;
|
||||
_debugHit = hasTarget && (currentChunk != null || currentLegacyTarget != null);
|
||||
_debugDist = hasTarget ? blockHit.distance : attackRange;
|
||||
|
||||
// 크로스헤어 이미지 교체
|
||||
bool hasValidTarget = currentChunk != null || currentLegacyTarget != null;
|
||||
if (crosshairUI != null)
|
||||
{
|
||||
crosshairUI.sprite = hasTarget ? targetCrosshair : idleCrosshair;
|
||||
crosshairUI.color = hasTarget ? Color.green : Color.white;
|
||||
crosshairUI.sprite = hasValidTarget ? targetCrosshair : idleCrosshair;
|
||||
crosshairUI.color = hasValidTarget ? Color.green : Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,11 +465,33 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
|
||||
private void RevealSurroundings()
|
||||
{
|
||||
// Use FogOfWarManager's revealRadius if available, fallback to visionRadius
|
||||
float currentRevealRadius = visionRadius;
|
||||
if (FogOfWarManager.Instance != null)
|
||||
{
|
||||
currentRevealRadius = FogOfWarManager.Instance.revealRadius;
|
||||
}
|
||||
|
||||
// 시야 반경 내의 블록 감지
|
||||
Collider[] hitBlocks = Physics.OverlapSphere(transform.position, visionRadius, mineableLayer);
|
||||
Collider[] hitBlocks = Physics.OverlapSphere(transform.position, currentRevealRadius, mineableLayer);
|
||||
|
||||
foreach (var col in hitBlocks)
|
||||
{
|
||||
// Try chunk-based reveal first (new system)
|
||||
if (col.TryGetComponent<MineableChunk>(out var chunk))
|
||||
{
|
||||
// Update local visibility (for fog of war visual states)
|
||||
chunk.UpdateLocalVisibility(transform.position, currentRevealRadius);
|
||||
|
||||
// Request server to mark blocks as discovered (permanent)
|
||||
if (IsOwner)
|
||||
{
|
||||
RequestChunkRevealServerRpc(chunk.GetComponent<NetworkObject>().NetworkObjectId, transform.position, currentRevealRadius);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback to legacy MineableBlock
|
||||
if (col.TryGetComponent<MineableBlock>(out var block))
|
||||
{
|
||||
// 1. [로컬] 내 화면에서 이 블록을 보이게 함 (실시간 시야)
|
||||
@@ -451,6 +518,18 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc]
|
||||
private void RequestChunkRevealServerRpc(ulong chunkNetId, Vector3 playerPos, float radius)
|
||||
{
|
||||
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(chunkNetId, out var netObj))
|
||||
{
|
||||
if (netObj.TryGetComponent<MineableChunk>(out var chunk))
|
||||
{
|
||||
chunk.RevealBlocksInRadius(playerPos, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ActionRoutine(float duration, string animTrigger, Action actionLogic)
|
||||
{
|
||||
// 1. 상태 잠금
|
||||
@@ -516,6 +595,9 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
{
|
||||
if (_actionHandler.IsBusy) return;
|
||||
|
||||
// Don't perform actions when in build mode
|
||||
if (BuildManager.Instance != null && BuildManager.Instance.IsBuildMode) return;
|
||||
|
||||
ItemData selectedItem = _inventory.GetSelectedItemData();
|
||||
if (selectedItem == null || selectedItem.behavior == null) return;
|
||||
|
||||
@@ -525,7 +607,8 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
// Skip if non-repeatable action already executed once
|
||||
if (!actionDesc.CanRepeat && _hasExecutedOnce) return;
|
||||
|
||||
GameObject target = _lastHighlightedBlock != null ? _lastHighlightedBlock.gameObject : null;
|
||||
// Get target - prioritize chunk system over legacy blocks
|
||||
GameObject target = GetCurrentMiningTarget();
|
||||
|
||||
if (selectedItem.CanUse(gameObject, target))
|
||||
{
|
||||
@@ -537,6 +620,29 @@ public class PlayerNetworkController : NetworkBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current mining target (chunk or legacy block)
|
||||
/// </summary>
|
||||
private GameObject GetCurrentMiningTarget()
|
||||
{
|
||||
// Prioritize chunk target
|
||||
if (_currentChunkTarget.hasHit && _currentChunkTarget.chunk != null)
|
||||
{
|
||||
return _currentChunkTarget.chunk.gameObject;
|
||||
}
|
||||
|
||||
// Fallback to legacy block
|
||||
return _lastHighlightedBlock != null ? _lastHighlightedBlock.gameObject : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current chunk target info (for MiningBehavior)
|
||||
/// </summary>
|
||||
public ChunkInteractionHandler.ChunkHitResult GetCurrentChunkTarget()
|
||||
{
|
||||
return _currentChunkTarget;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!Application.isPlaying || !IsOwner) return;
|
||||
|
||||
Reference in New Issue
Block a user