Files
ProjectMD/Assets/Scripts/MineableBlock.cs
Dal4segno 8369e4d42f Mineable 블록에 대해 전장의 안개 추가
그림자 안나오는 문제도 해결
2026-01-17 20:14:12 +09:00

207 lines
7.0 KiB
C#

using NUnit.Framework.Interfaces;
using System.Collections;
using Unity.Netcode;
using UnityEngine;
public class MineableBlock : NetworkBehaviour
{
[Header("Block Stats")]
[SerializeField] private int maxHp = 100;
// [동기화] 모든 플레이어가 동일한 블록 체력을 보게 함
private NetworkVariable<int> _currentHp = new NetworkVariable<int>();
[SerializeField] private ItemData dropItemData;
[SerializeField] private GameObject genericDropPrefab; // 여기에 위에서 만든 'GenericDroppedItem' 프리팹을 넣으세요.
[Header("Visuals")]
private Outline _outline;
private Vector3 _originalPos;
[Header("Shake Settings")]
[SerializeField] private float shakeDuration = 0.15f; // 흔들리는 시간
[SerializeField] private float shakeMagnitude = 0.1f; // 흔들리는 강도
private Coroutine _shakeCoroutine;
private Color _originalColor; // 본래의 색상을 저장할 변수
private float _lastVisibleTime;
private const float VisibilityThreshold = 0.25f;
[Header("Fog Settings")]
[Range(0f, 1f)]
[SerializeField] private float darkIntensity = 0.2f; // 안개 속에서 얼마나 어두워질지 (0: 완전 검정, 1: 원본)
private MaterialPropertyBlock _propBlock;
private NetworkVariable<bool> isDiscovered = new NetworkVariable<bool>(false);
private MeshRenderer _renderer;
void Awake()
{
_renderer = GetComponentInChildren<MeshRenderer>();
_propBlock = new MaterialPropertyBlock();
// 시작 시에는 보이지 않게 설정
if (_renderer != null) _renderer.enabled = false;
_originalColor = _renderer.sharedMaterial.HasProperty("_BaseColor")
? _renderer.sharedMaterial.GetColor("_BaseColor")
: _renderer.sharedMaterial.GetColor("_Color");
_renderer.enabled = false;
// 해당 오브젝트 혹은 자식에게서 Outline 컴포넌트를 찾습니다.
_outline = GetComponentInChildren<Outline>();
_originalPos = transform.localPosition; // 로컬 위치 저장
if (_outline != null)
{
// 게임 시작 시 하이라이트는 꺼둡니다.
_outline.enabled = false;
}
else
{
Debug.LogWarning($"{gameObject.name}: QuickOutline 에셋의 Outline 컴포넌트를 찾을 수 없습니다.");
}
}
public override void OnNetworkSpawn()
{
if (IsServer)
{
_currentHp.Value = maxHp;
}
// 데이터가 동기화될 때 비주얼 업데이트
UpdateVisuals(isDiscovered.Value);
isDiscovered.OnValueChanged += (oldVal, newVal) => {
if (newVal) UpdateState();
};
}
void Update()
{
// 1. 서버에서 한 번이라도 발견되었고
// 2. 최근(0.2초 이내)에 플레이어의 시야 범위 안에 있었다면 렌더러를 켭니다.
bool isCurrentlyVisible = (Time.time - _lastVisibleTime) < VisibilityThreshold;
if (_renderer != null)
{
// 실시간 시야: 발견되었더라도 지금 근처에 플레이어가 없으면 다시 숨깁니다.
_renderer.enabled = isDiscovered.Value && isCurrentlyVisible;
}
// 1. 이미 발견된 블록만 실시간 시야 연산을 수행합니다.
if (!isDiscovered.Value) return;
UpdateState();
}
private void UpdateState()
{
if (_renderer == null) return;
// 2. 현재 시야 안에 있는지 판단합니다.
bool isCurrentlyVisible = (Time.time - _lastVisibleTime) < VisibilityThreshold;
// 3. 상태에 따라 색상과 렌더러 상태를 결정합니다.
_renderer.enabled = true; // 발견된 상태이므로 항상 켭니다.
_renderer.GetPropertyBlock(_propBlock);
// 2. 시야 내에 있으면 원본 색상(_originalColor), 멀어지면 어둡게 만든 색상을 적용합니다.
Color targetColor = isCurrentlyVisible ? _originalColor : _originalColor * darkIntensity;
_propBlock.SetColor("_BaseColor", targetColor);
_renderer.SetPropertyBlock(_propBlock);
}
public void RevealBlock() // 서버에서 호출
{
if (IsServer && !isDiscovered.Value) isDiscovered.Value = true;
}
// 플레이어가 주변을 훑을 때 호출해줄 함수
public void UpdateLocalVisibility()
{
_lastVisibleTime = Time.time;
}
// 서버에서만 대미지를 처리하도록 제한
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
public void TakeDamageRpc(int damageAmount)
{
if (_currentHp.Value <= 0) return;
_currentHp.Value -= damageAmount;
if (_currentHp.Value <= 0)
{
DestroyBlock();
}
}
private void DestroyBlock()
{
DropItem();
// 2. 서버에서 네트워크 오브젝트 제거 (모든 클라이언트에서 사라짐)
GetComponent<NetworkObject>().Despawn();
}
// 하이라이트 상태를 설정하는 공개 메서드
public void SetHighlight(bool isOn)
{
if (_outline == null) return;
// 외곽선 컴포넌트 활성화/비활성화
_outline.enabled = isOn;
}
// 서버에서 호출하여 모든 클라이언트에게 흔들림 지시
[ClientRpc]
public void PlayHitEffectClientRpc()
{
if (_shakeCoroutine != null) StopCoroutine(_shakeCoroutine);
_shakeCoroutine = StartCoroutine(ShakeRoutine());
}
private IEnumerator ShakeRoutine()
{
float elapsed = 0.0f;
while (elapsed < shakeDuration)
{
Vector3 randomOffset = Random.insideUnitSphere * shakeMagnitude;
transform.localPosition = _originalPos + randomOffset;
// 좌표가 실제로 바뀌고 있는지 로그 출력
// Debug.Log($"현재 좌표: {transform.localPosition}");
elapsed += Time.deltaTime;
yield return null;
}
transform.localPosition = _originalPos;
}
private void DropItem()
{
if (!IsServer || dropItemData == null || genericDropPrefab == null) return;
// 원본 블록이 아니라 '범용 컨테이너'를 소환합니다.
GameObject dropObj = Instantiate(genericDropPrefab, transform.position + Vector3.up * 0.5f, Quaternion.identity);
NetworkObject netObj = dropObj.GetComponent<NetworkObject>();
netObj.Spawn();
// 소환된 컨테이너에 "너는 어떤 아이템의 모양을 따라해야 해"라고 알려줍니다.
if (dropObj.TryGetComponent<DroppedItem>(out var droppedItem))
{
droppedItem.Initialize(dropItemData.itemID);
}
}
private void UpdateVisuals(bool discovered)
{
if (_renderer != null) _renderer.enabled = discovered;
// 발견되지 않은 블록은 아웃라인도 표시되지 않아야 함
if (!discovered && _outline != null) _outline.enabled = false;
}
}