feat: 아군 타게팅 시스템 구현 — SingleAlly 투사체형 치유/보호막

- 치유/보호막 스킬을 즉발 자가시전에서 투사체형 아군 1인 타겟팅으로 전환

- TargetType.SingleAlly 추가, targetOverride 매개변수로 외부 타겟 주입 지원

- PlayerSkillInput: 카메라 레이캐스트 기반 아군 탐지, 서버 검증, RPC 타겟 ID 전달

- AllyTargetIndicator: 호버 아군 위에 디스크 인디케이터 표시, 사거리/초과 색상 변경

- SpawnEffect: 타겟 방향 회전 보정

- 투사체 스폰 이펙트 에셋 생성 (치유/보호막 각각)

- 인디케이터 프리팹 + URP/Unlit 머티리얼 생성

- Player 프리팹에 AllyTargetIndicator 컴포넌트 추가 및 설정

- Input.mousePosition → Mouse.current.position.ReadValue() 수정 (Input System 호환)
This commit is contained in:
2026-03-31 23:06:13 +09:00
parent 2c6a65d406
commit 106e53c9aa
22 changed files with 6779 additions and 112 deletions

View File

@@ -489,6 +489,60 @@ namespace Colosseum.Skills
return true;
}
/// <summary>
/// 이 로드아웃에 지정한 TargetType을 사용하는 효과가 있는지 확인합니다.
/// 기반 스킬과 장착된 젬의 모든 효과를 검사합니다.
/// </summary>
public bool HasEffectWithTargetType(TargetType type)
{
if (baseSkill != null)
{
if (baseSkill.CastStartEffects != null && CheckEffectsForTargetType(baseSkill.CastStartEffects, type))
return true;
if (baseSkill.Effects != null && CheckEffectsForTargetType(baseSkill.Effects, type))
return true;
}
if (socketedGems == null)
return false;
for (int i = 0; i < socketedGems.Length; i++)
{
SkillGemData gem = socketedGems[i];
if (gem == null) continue;
if (gem.CastStartEffects != null && CheckEffectsForTargetType(gem.CastStartEffects, type))
return true;
if (gem.TriggeredEffects != null)
{
for (int j = 0; j < gem.TriggeredEffects.Count; j++)
{
SkillGemTriggeredEffectEntry entry = gem.TriggeredEffects[j];
if (entry == null || entry.Effects == null) continue;
if (CheckEffectsForTargetType(entry.Effects, type))
return true;
}
}
}
return false;
}
private static bool CheckEffectsForTargetType(IReadOnlyList<SkillEffect> effects, TargetType type)
{
if (effects == null) return false;
for (int i = 0; i < effects.Count; i++)
{
if (effects[i] != null && effects[i].TargetType == type)
return true;
}
return false;
}
private float GetResolvedScalarMultiplier(System.Func<SkillGemData, float> selector)
{
if (baseSkill == null)