- PlayerOcclusionOutline: 보스(Enemy 레이어)가 플레이어를 가릴 때 외곽선 활성화 - Outline.shader: URP 법선 확장 외곽선 셰이더 (Fresnel 기반 알파) - 외곽선용 별도 SkinnedMeshRenderer를 자식 GameObject에 생성 - ObstacleTransparencyController: Enemy 레이어 장애물 숨김 제외 - PlayerCamera: PlayerOcclusionOutline 초기화 연동 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
110 lines
3.9 KiB
C#
110 lines
3.9 KiB
C#
using UnityEngine;
|
|
|
|
namespace Colosseum.Player
|
|
{
|
|
/// <summary>
|
|
/// 보스가 플레이어를 가릴 때 플레이어 외곽선을 표시하는 컨트롤러
|
|
/// </summary>
|
|
public class PlayerOcclusionOutline : MonoBehaviour
|
|
{
|
|
[Header("Settings")]
|
|
[Min(0.001f)] [SerializeField] private float outlineWidth = 0.02f;
|
|
[SerializeField] private Color outlineColor = Color.white;
|
|
[Min(0.01f)] [SerializeField] private float checkRadius = 0.3f;
|
|
|
|
private Transform cameraTransform;
|
|
private Transform playerTransform;
|
|
private SkinnedMeshRenderer originalRenderer;
|
|
private GameObject outlineHost;
|
|
private SkinnedMeshRenderer outlineRenderer;
|
|
private Material outlineMaterial;
|
|
private bool isShowingOutline;
|
|
|
|
public void Initialize(Transform camera, Transform player)
|
|
{
|
|
cameraTransform = camera;
|
|
playerTransform = player;
|
|
|
|
originalRenderer = player.GetComponentInChildren<SkinnedMeshRenderer>();
|
|
if (originalRenderer == null) return;
|
|
|
|
var shader = Shader.Find("Hidden/Colosseum/Outline");
|
|
if (shader == null)
|
|
{
|
|
Debug.LogError("[PlayerOcclusionOutline] Shader 'Hidden/Colosseum/Outline' not found");
|
|
return;
|
|
}
|
|
|
|
outlineMaterial = new Material(shader);
|
|
outlineMaterial.SetFloat("_OutlineWidth", outlineWidth);
|
|
outlineMaterial.SetColor("_OutlineColor", outlineColor);
|
|
|
|
if (originalRenderer.sharedMesh == null)
|
|
{
|
|
Debug.LogError("[PlayerOcclusionOutline] originalRenderer has no sharedMesh");
|
|
return;
|
|
}
|
|
|
|
outlineHost = new GameObject("OutlineRenderer");
|
|
outlineHost.transform.SetParent(originalRenderer.transform, false);
|
|
outlineRenderer = outlineHost.AddComponent<SkinnedMeshRenderer>();
|
|
outlineRenderer.sharedMesh = originalRenderer.sharedMesh;
|
|
outlineRenderer.bones = originalRenderer.bones;
|
|
outlineRenderer.rootBone = originalRenderer.rootBone;
|
|
outlineRenderer.sharedMaterials = new[] { outlineMaterial };
|
|
outlineRenderer.updateWhenOffscreen = true;
|
|
outlineRenderer.enabled = false;
|
|
|
|
Debug.Log($"[PlayerOcclusionOutline] Initialize: outlineRenderer on child {outlineHost.name}");
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (cameraTransform == null || playerTransform == null || outlineRenderer == null)
|
|
return;
|
|
|
|
outlineMaterial.SetFloat("_OutlineWidth", outlineWidth);
|
|
outlineMaterial.SetColor("_OutlineColor", outlineColor);
|
|
|
|
bool isOccluded = CheckBossOcclusion();
|
|
|
|
if (isOccluded && !isShowingOutline)
|
|
{
|
|
outlineRenderer.enabled = true;
|
|
isShowingOutline = true;
|
|
}
|
|
else if (!isOccluded && isShowingOutline)
|
|
{
|
|
outlineRenderer.enabled = false;
|
|
isShowingOutline = false;
|
|
}
|
|
}
|
|
|
|
private bool CheckBossOcclusion()
|
|
{
|
|
Vector3 origin = cameraTransform.position;
|
|
Vector3 direction = playerTransform.position - origin;
|
|
float distance = direction.magnitude;
|
|
|
|
if (distance < 0.01f) return false;
|
|
|
|
int enemyLayer = LayerMask.GetMask("Enemy");
|
|
|
|
if (Physics.SphereCast(origin, checkRadius, direction.normalized, out RaycastHit hit, distance, enemyLayer, QueryTriggerInteraction.Ignore))
|
|
{
|
|
return hit.collider.transform.IsChildOf(playerTransform) == false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (outlineHost != null)
|
|
Destroy(outlineHost);
|
|
if (outlineMaterial != null)
|
|
Destroy(outlineMaterial);
|
|
}
|
|
}
|
|
}
|