feat: 보스 가림 시 플레이어 외곽선 표시
- 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>
This commit is contained in:
@@ -68,7 +68,9 @@ namespace Colosseum.Player
|
||||
private HashSet<Renderer> CollectHits(Vector3 origin, Vector3 direction, float maxDistance)
|
||||
{
|
||||
var hits = new HashSet<Renderer>();
|
||||
RaycastHit[] rayHits = Physics.SphereCastAll(origin, checkRadius, direction, maxDistance, occlusionMask, QueryTriggerInteraction.Ignore);
|
||||
int enemyLayer = LayerMask.GetMask("Enemy");
|
||||
int mask = occlusionMask & ~enemyLayer;
|
||||
RaycastHit[] rayHits = Physics.SphereCastAll(origin, checkRadius, direction, maxDistance, mask, QueryTriggerInteraction.Ignore);
|
||||
|
||||
float targetY = targetTransform.position.y;
|
||||
|
||||
|
||||
@@ -91,6 +91,12 @@ namespace Colosseum.Player
|
||||
transparencyController = cameraInstance.gameObject.AddComponent<ObstacleTransparencyController>();
|
||||
transparencyController.Initialize(target, collisionMask);
|
||||
|
||||
// 플레이어 외곽선 컨트롤러 연동 (보스 가림 시)
|
||||
var outlineController = cameraInstance.gameObject.GetComponent<PlayerOcclusionOutline>();
|
||||
if (outlineController == null)
|
||||
outlineController = cameraInstance.gameObject.AddComponent<PlayerOcclusionOutline>();
|
||||
outlineController.Initialize(cameraInstance.transform, target);
|
||||
|
||||
// 카메라 위치를 즉시 타겟 위치로 초기화
|
||||
SnapToTarget();
|
||||
}
|
||||
|
||||
109
Assets/_Game/Scripts/Player/PlayerOcclusionOutline.cs
Normal file
109
Assets/_Game/Scripts/Player/PlayerOcclusionOutline.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f13a11c6eb7427efa4bfdac57d1458c
|
||||
8
Assets/_Game/Shaders.meta
Normal file
8
Assets/_Game/Shaders.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2031d8db9b9eec15ac21ab219326734
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
74
Assets/_Game/Shaders/Outline.shader
Normal file
74
Assets/_Game/Shaders/Outline.shader
Normal file
@@ -0,0 +1,74 @@
|
||||
Shader "Hidden/Colosseum/Outline"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_OutlineWidth("Width", Range(0.001, 0.1)) = 0.02
|
||||
_OutlineColor("Color", Color) = (1, 1, 1, 1)
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"RenderType" = "Transparent"
|
||||
"Queue" = "Overlay"
|
||||
"RenderPipeline" = "UniversalPipeline"
|
||||
}
|
||||
|
||||
ZWrite Off
|
||||
ZTest Always
|
||||
Cull Front
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float _OutlineWidth;
|
||||
float4 _OutlineColor;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float3 positionOS : POSITION;
|
||||
float3 normalOS : NORMAL;
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
float outlineAlpha : TEXCOORD0;
|
||||
};
|
||||
|
||||
Varyings vert(Attributes input)
|
||||
{
|
||||
Varyings output;
|
||||
|
||||
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
|
||||
float3 positionWS = TransformObjectToWorld(input.positionOS);
|
||||
float3 viewDir = normalize(GetWorldSpaceViewDir(positionWS));
|
||||
|
||||
float NdotV = dot(normalWS, viewDir);
|
||||
output.outlineAlpha = saturate(1.0 + NdotV);
|
||||
|
||||
positionWS += normalWS * _OutlineWidth * output.outlineAlpha;
|
||||
|
||||
output.positionCS = TransformWorldToHClip(positionWS);
|
||||
return output;
|
||||
}
|
||||
|
||||
half4 frag(Varyings input) : SV_Target
|
||||
{
|
||||
float alpha = _OutlineColor.a * pow(input.outlineAlpha, 3.0);
|
||||
return half4(_OutlineColor.rgb, alpha);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
FallBack Off
|
||||
}
|
||||
9
Assets/_Game/Shaders/Outline.shader.meta
Normal file
9
Assets/_Game/Shaders/Outline.shader.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38b277215185d2a71b39fc85259575aa
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user