- 치유/보호막 스킬을 즉발 자가시전에서 투사체형 아군 1인 타겟팅으로 전환 - TargetType.SingleAlly 추가, targetOverride 매개변수로 외부 타겟 주입 지원 - PlayerSkillInput: 카메라 레이캐스트 기반 아군 탐지, 서버 검증, RPC 타겟 ID 전달 - AllyTargetIndicator: 호버 아군 위에 디스크 인디케이터 표시, 사거리/초과 색상 변경 - SpawnEffect: 타겟 방향 회전 보정 - 투사체 스폰 이펙트 에셋 생성 (치유/보호막 각각) - 인디케이터 프리팹 + URP/Unlit 머티리얼 생성 - Player 프리팹에 AllyTargetIndicator 컴포넌트 추가 및 설정 - Input.mousePosition → Mouse.current.position.ReadValue() 수정 (Input System 호환)
288 lines
11 KiB
C#
288 lines
11 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Reflection;
|
|
|
|
using UnityEngine;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Colosseum.Player
|
|
{
|
|
/// <summary>
|
|
/// 아군 타게팅 시스템의 시각적 데모 + 설정 검증.
|
|
/// BalanceDummy 씬에서 TrainingDummy를 임시 아군으로 만들고,
|
|
/// 인디케이터 프리팹 표시 → 치유 스킬 투사체 발사를 실제로 보여줍니다.
|
|
/// </summary>
|
|
public class AllyTargetingDebugTest : MonoBehaviour
|
|
{
|
|
[Header("테스트 설정")]
|
|
[Tooltip("가짜 아군으로 사용할 GameObject 이름")]
|
|
[SerializeField] private string allyTargetName = "TrainingDummy";
|
|
[Tooltip("치유 스킬 에셋 경로")]
|
|
[SerializeField] private string healSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_치유.asset";
|
|
[Tooltip("보호막 스킬 에셋 경로")]
|
|
[SerializeField] private string shieldSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Player_보호막.asset";
|
|
[Tooltip("인디케이터 높이 오프셋")]
|
|
[Min(0f)] [SerializeField] private float indicatorHeight = 2.2f;
|
|
[Tooltip("시작 지연 (초)")]
|
|
[Min(0.5f)] [SerializeField] private float startDelay = 2f;
|
|
|
|
// 복원용
|
|
private TeamType originalTeamType;
|
|
private GameObject spawnedIndicator;
|
|
private GameObject allyObject;
|
|
|
|
private void Start()
|
|
{
|
|
Debug.Log("═══════════════════════════════════════════════════");
|
|
Debug.Log("[AllyTargetDemo] 아군 타게팅 시각적 데모 시작");
|
|
Debug.Log("═══════════════════════════════════════════════════");
|
|
|
|
StartCoroutine(RunDemo());
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
// 플레이 종료 시 복원
|
|
if (allyObject != null)
|
|
{
|
|
RestoreTeam();
|
|
DestroyIndicator();
|
|
}
|
|
}
|
|
|
|
private IEnumerator RunDemo()
|
|
{
|
|
yield return new WaitForSeconds(startDelay);
|
|
|
|
var player = FindPlayer();
|
|
if (player == null)
|
|
{
|
|
Debug.LogError("[AllyTargetDemo] 플레이어를 찾을 수 없습니다.");
|
|
EndDemo();
|
|
yield break;
|
|
}
|
|
|
|
allyObject = GameObject.Find(allyTargetName);
|
|
if (allyObject == null)
|
|
{
|
|
Debug.LogError($"[AllyTargetDemo] '{allyTargetName}'을(를) 찾을 수 없습니다.");
|
|
EndDemo();
|
|
yield break;
|
|
}
|
|
|
|
// ── 1. TrainingDummy 팀을 임시 Player로 변경 ──
|
|
var allyTeam = allyObject.GetComponent<Team>();
|
|
if (allyTeam == null)
|
|
{
|
|
Debug.LogError($"[AllyTargetDemo] {allyTargetName}에 Team 컴포넌트가 없습니다.");
|
|
EndDemo();
|
|
yield break;
|
|
}
|
|
|
|
originalTeamType = allyTeam.TeamType;
|
|
SetTeamType(allyTeam, TeamType.Player);
|
|
Debug.Log($"[AllyTargetDemo] {allyTargetName} 팀 변경: {originalTeamType} → Player");
|
|
|
|
// 거리 로그
|
|
float dist = Vector3.Distance(player.transform.position, allyObject.transform.position);
|
|
Debug.Log($"[AllyTargetDemo] 플레이어 ↔ 아군 거리: {dist:F1}m");
|
|
|
|
// ── 2. 인디케이터 프리팹 표시 (초록색) ──
|
|
yield return ShowIndicator(player, allyObject);
|
|
|
|
// ── 3. 2초 대기 (인디케이터 관찰 시간) ──
|
|
Debug.Log("[AllyTargetDemo] ▶ 인디케이터 표시 중... 2초 대기");
|
|
yield return new WaitForSeconds(2f);
|
|
|
|
// ── 4. 인디케이터 색상 빨간색으로 변경 (거리 초과 시뮬레이션) ──
|
|
yield return ChangeIndicatorColor(new Color(1f, 0.3f, 0.3f, 0.8f));
|
|
Debug.Log("[AllyTargetDemo] ▶ 인디케이터 빨간색으로 변경 (거리 초과 시뮬) — 2초 대기");
|
|
yield return new WaitForSeconds(2f);
|
|
|
|
// ── 5. 인디케이터 제거 후 잠시 대기 ──
|
|
DestroyIndicator();
|
|
Debug.Log("[AllyTargetDemo] ▶ 인디케이터 제거");
|
|
yield return new WaitForSeconds(1f);
|
|
|
|
// ── 6. 치유 스킬 실행 (투사체 발사) ──
|
|
Debug.Log("[AllyTargetDemo] ▶ 치유 스킬 투사체 발사...");
|
|
yield return ExecuteSkillVisual(player, allyObject, healSkillPath, "치유");
|
|
yield return new WaitForSeconds(3f);
|
|
|
|
// ── 7. 보호막 스킬 실행 (투사체 발사) ──
|
|
Debug.Log("[AllyTargetDemo] ▶ 보호막 스킬 투사체 발사...");
|
|
yield return ExecuteSkillVisual(player, allyObject, shieldSkillPath, "보호막");
|
|
yield return new WaitForSeconds(3f);
|
|
|
|
// ── 8. 인디케이터 다시 표시 (복원 데모) ──
|
|
yield return ShowIndicator(player, allyObject);
|
|
Debug.Log("[AllyTargetDemo] ▶ 인디케이터 최종 표시 — 3초 후 종료");
|
|
yield return new WaitForSeconds(3f);
|
|
|
|
// ── 9. 정리 ──
|
|
DestroyIndicator();
|
|
RestoreTeam();
|
|
Debug.Log("[AllyTargetDemo] 팀 복원 완료");
|
|
|
|
Debug.Log("═══════════════════════════════════════════════════");
|
|
Debug.Log("<color=green>[AllyTargetDemo] 데모 완료</color>");
|
|
Debug.Log("═══════════════════════════════════════════════════");
|
|
|
|
EndDemo();
|
|
}
|
|
|
|
#region 시각적 데모 단계
|
|
|
|
private IEnumerator ShowIndicator(GameObject player, GameObject ally)
|
|
{
|
|
// 플레이어의 AllyTargetIndicator에서 프리팹 참조 가져오기
|
|
var indicator = player.GetComponent<UI.AllyTargetIndicator>();
|
|
if (indicator == null)
|
|
{
|
|
Debug.LogError("[AllyTargetDemo] AllyTargetIndicator 컴포넌트가 없습니다.");
|
|
yield break;
|
|
}
|
|
|
|
var prefabField = typeof(UI.AllyTargetIndicator).GetField("indicatorPrefab",
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (prefabField == null)
|
|
{
|
|
Debug.LogError("[AllyTargetDemo] indicatorPrefab 필드 접근 실패.");
|
|
yield break;
|
|
}
|
|
|
|
var prefab = prefabField.GetValue(indicator) as GameObject;
|
|
if (prefab == null)
|
|
{
|
|
Debug.LogError("[AllyTargetDemo] indicatorPrefab이 null입니다.");
|
|
yield break;
|
|
}
|
|
|
|
// 아군 머리 위에 인스턴스화
|
|
Vector3 pos = ally.transform.position;
|
|
pos.y += indicatorHeight;
|
|
spawnedIndicator = Instantiate(prefab, pos, Quaternion.Euler(90f, 0f, 0f));
|
|
|
|
// 초록색으로 설정
|
|
var renderer = spawnedIndicator.GetComponent<Renderer>();
|
|
if (renderer != null)
|
|
{
|
|
renderer.material.color = new Color(0.2f, 1f, 0.2f, 0.8f);
|
|
}
|
|
|
|
Debug.Log($"[AllyTargetDemo] 인디케이터 표시: {prefab.name} @ {ally.name} (y+{indicatorHeight})");
|
|
yield return null;
|
|
}
|
|
|
|
private IEnumerator ChangeIndicatorColor(Color color)
|
|
{
|
|
if (spawnedIndicator == null) yield break;
|
|
|
|
var renderer = spawnedIndicator.GetComponent<Renderer>();
|
|
if (renderer != null)
|
|
{
|
|
renderer.material.color = color;
|
|
Debug.Log($"[AllyTargetDemo] 인디케이터 색상 변경: ({color.r:F1}, {color.g:F1}, {color.b:F1}, {color.a:F1})");
|
|
}
|
|
yield return null;
|
|
}
|
|
|
|
private IEnumerator ExecuteSkillVisual(GameObject player, GameObject ally, string skillPath, string skillLabel)
|
|
{
|
|
#if UNITY_EDITOR
|
|
var skillData = AssetDatabase.LoadAssetAtPath<Skills.SkillData>(skillPath);
|
|
if (skillData == null)
|
|
{
|
|
Debug.LogError($"[AllyTargetDemo] 스킬 로드 실패: {skillPath}");
|
|
yield break;
|
|
}
|
|
|
|
var entry = Skills.SkillLoadoutEntry.CreateTemporary(skillData);
|
|
bool hasSingleAlly = entry.HasEffectWithTargetType(Skills.TargetType.SingleAlly);
|
|
Debug.Log($"[AllyTargetDemo] {skillLabel}: SingleAlly 효과 있음={hasSingleAlly}");
|
|
|
|
var skillController = player.GetComponent<Skills.SkillController>();
|
|
if (skillController == null)
|
|
{
|
|
Debug.LogError("[AllyTargetDemo] SkillController가 없습니다.");
|
|
yield break;
|
|
}
|
|
|
|
if (skillController.IsExecutingSkill)
|
|
{
|
|
Debug.LogWarning($"[AllyTargetDemo] 스킬 실행 중 — 대기");
|
|
yield return new WaitUntil(() => !skillController.IsExecutingSkill);
|
|
}
|
|
|
|
// 플레이어를 아군 방향으로 회전
|
|
Vector3 direction = (ally.transform.position - player.transform.position);
|
|
direction.y = 0;
|
|
if (direction.sqrMagnitude > 0.001f)
|
|
{
|
|
player.transform.rotation = Quaternion.LookRotation(direction);
|
|
}
|
|
|
|
Debug.Log($"[AllyTargetDemo] {skillLabel} 실행 → 타겟: {ally.name}");
|
|
bool success = skillController.ExecuteSkill(entry, ally);
|
|
|
|
if (success)
|
|
Debug.Log($"<color=green>[AllyTargetDemo] {skillLabel} 스킬 발동 성공 — 투사체 확인!</color>");
|
|
else
|
|
Debug.LogError($"<AllyTargetDemo] {skillLabel} 스킬 발동 실패");
|
|
#else
|
|
Debug.LogError("[AllyTargetDemo] 에디터 전용");
|
|
#endif
|
|
yield return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 유틸리티
|
|
|
|
private static GameObject FindPlayer()
|
|
{
|
|
var controllers = FindObjectsOfType<PlayerNetworkController>();
|
|
return controllers.Length > 0 ? controllers[0].gameObject : null;
|
|
}
|
|
|
|
private static void SetTeamType(Team team, TeamType type)
|
|
{
|
|
var field = typeof(Team).GetField("teamType",
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
field?.SetValue(team, type);
|
|
}
|
|
|
|
private void RestoreTeam()
|
|
{
|
|
if (allyObject == null) return;
|
|
var allyTeam = allyObject.GetComponent<Team>();
|
|
if (allyTeam != null)
|
|
{
|
|
SetTeamType(allyTeam, originalTeamType);
|
|
Debug.Log($"[AllyTargetDemo] 팀 복원: {allyObject.name} → {originalTeamType}");
|
|
}
|
|
}
|
|
|
|
private void DestroyIndicator()
|
|
{
|
|
if (spawnedIndicator != null)
|
|
{
|
|
Destroy(spawnedIndicator);
|
|
spawnedIndicator = null;
|
|
}
|
|
}
|
|
|
|
private static void EndDemo()
|
|
{
|
|
#if UNITY_EDITOR
|
|
EditorApplication.isPlaying = false;
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|