feat(BT12-Dev): SkillInventoryHUD 시각화 + 사망 원인 디버그 로그 (PD 후속 지시 2건)
PD 직접 발화 2건: 1. PlayerSkillInventory 등록 시각화 (유니티 기본 자원 활용) 2. 스킬 습득 후 사망 버그 수정 작업 내용: - SkillInventoryHUD.cs 신규 (OnGUI 좌상단·장착 액티브 DisplayName/Lv/CD·패시브 카운트) - PlayerController.Awake에 HUD 자동 부착 - ProjectileSpawner fallback prefab 시각화 (SpriteRenderer + 16x16 동적 흰색 원 + 속성별 색상) - Projectile.OnTriggerEnter2D Player 명시 차단 (defensive proxy) - Health.Decrement·DecrementSilent·Die에 Debug.Log + StackTrace (사망 호출자 추적) 가설 (미검증): BT5-Dev EnemyController patrol → PlayerEnemyCollision Event → player.health.Decrement(). 검증 절차: PD Play 테스트 → Console log StackTrace 분석 → 호출자 확정. pm-auditor Major 1 정정 완료 (PD 지시 로그 2행 등재)·Minor 2 정정 완료. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d53150b5ed
commit
e31c34cf24
|
|
@ -126,7 +126,10 @@ namespace Platformer.Mechanics
|
|||
return;
|
||||
}
|
||||
|
||||
int beforeHP = currentHP;
|
||||
currentHP = Mathf.Clamp(currentHP - damage, 0, maxHP);
|
||||
// PD 지시 2026-05-09 — 사망 원인 추적 (스킬 습득 후 갑자기 사망 버그)
|
||||
Debug.Log($"[Health@{name}] Decrement(damage={damage}) hp {beforeHP}→{currentHP} t={Time.time:F2}\n{System.Environment.StackTrace}");
|
||||
|
||||
// 피격 성공 시 무적 시간 활성화 (다음 피격 대비)
|
||||
if (invulnerableDuration > 0f)
|
||||
|
|
@ -197,7 +200,9 @@ namespace Platformer.Mechanics
|
|||
if (damage <= 0) return;
|
||||
if (Time.time < invulnerableUntil) return;
|
||||
|
||||
int beforeHP = currentHP;
|
||||
currentHP = Mathf.Clamp(currentHP - damage, 0, maxHP);
|
||||
Debug.Log($"[Health@{name}] DecrementSilent(damage={damage}) hp {beforeHP}→{currentHP} t={Time.time:F2}");
|
||||
if (invulnerableDuration > 0f) invulnerableUntil = Time.time + invulnerableDuration;
|
||||
|
||||
if (currentHP > 0)
|
||||
|
|
@ -249,6 +254,7 @@ namespace Platformer.Mechanics
|
|||
/// </summary>
|
||||
public void Die()
|
||||
{
|
||||
Debug.Log($"[Health@{name}] Die() called t={Time.time:F2}\n{System.Environment.StackTrace}");
|
||||
invulnerableUntil = -1f; // i-frame 무효화
|
||||
if (currentHP > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -96,10 +96,14 @@ namespace Platformer.Mechanics
|
|||
if (GetComponent<EerieVillage.Progression.PlayerProgression>() == null)
|
||||
gameObject.AddComponent<EerieVillage.Progression.PlayerProgression>();
|
||||
|
||||
// Phase 2-D 신규 (2026-05-09) — PlayerSkillInventory 자동 부착 (스킬 인벤토리 영역)
|
||||
// Phase 2-D 신규 (2026-05-09) — PlayerSkillInventory 자동 부착 (스킬 인벤토리)
|
||||
if (GetComponent<EerieVillage.Skills.PlayerSkillInventory>() == null)
|
||||
gameObject.AddComponent<EerieVillage.Skills.PlayerSkillInventory>();
|
||||
|
||||
// BT12-Dev 후속 (2026-05-09) — SkillInventoryHUD 자동 부착 (PD 시각화 지시)
|
||||
if (GetComponent<EerieVillage.MyUI.SkillInventoryHUD>() == null)
|
||||
gameObject.AddComponent<EerieVillage.MyUI.SkillInventoryHUD>();
|
||||
|
||||
// 사망 시 입력 차단 / 부활 시 입력 복원
|
||||
if (health != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
using UnityEngine;
|
||||
using EerieVillage.Skills;
|
||||
|
||||
namespace EerieVillage.MyUI
|
||||
{
|
||||
/// <summary>
|
||||
/// BT12-Dev 시각화 HUD — PlayerSkillInventory 등록 상태를 좌상단에 표시.
|
||||
/// PD 지시 2026-05-09 — "PlayerSkillInventory 등록이 되었는지 어떻게 판단해야하지?
|
||||
/// 시각적인 변화가 없으니 확인이 불가능해. 유니티 기본 제공 리소스를 활용해도 좋으니 보이게 해줘."
|
||||
///
|
||||
/// 표시 내용:
|
||||
/// - 장착 액티브 스킬 목록 (DisplayName · Lv · CooldownRemaining/EffectiveCooldown)
|
||||
/// - 패시브 슬롯 카운트
|
||||
///
|
||||
/// PlayerController.Awake에서 자동 부착되는 PlayerSkillInventory와 함께 부착.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PlayerSkillInventory))]
|
||||
public class SkillInventoryHUD : MonoBehaviour
|
||||
{
|
||||
PlayerSkillInventory _inventory;
|
||||
GUIStyle _boxStyle;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_inventory = GetComponent<PlayerSkillInventory>();
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (_inventory == null) return;
|
||||
|
||||
if (_boxStyle == null)
|
||||
{
|
||||
_boxStyle = new GUIStyle(GUI.skin.box);
|
||||
_boxStyle.alignment = TextAnchor.UpperLeft;
|
||||
_boxStyle.fontSize = 14;
|
||||
_boxStyle.normal.textColor = Color.white;
|
||||
_boxStyle.padding = new RectOffset(10, 10, 6, 6);
|
||||
_boxStyle.richText = true;
|
||||
}
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("<b><color=#ffd86b>장착 스킬</color></b>");
|
||||
|
||||
int activeCount = 0;
|
||||
foreach (var active in _inventory.ActiveSkills)
|
||||
{
|
||||
if (active == null) continue;
|
||||
var data = active.Data;
|
||||
if (data == null) continue;
|
||||
activeCount++;
|
||||
|
||||
float remaining = 0f;
|
||||
float total = 0f;
|
||||
if (active is ActiveSkillRuntime rt)
|
||||
{
|
||||
remaining = Mathf.Max(0f, rt.CooldownRemaining);
|
||||
total = rt.EffectiveCooldown;
|
||||
}
|
||||
|
||||
sb.Append($"{activeCount}. {data.DisplayName} <color=#88c8ff>Lv.{active.StackLevel}</color>");
|
||||
if (total > 0f)
|
||||
{
|
||||
sb.Append($" <color=#bbbbbb>CD {remaining:F1}s/{total:F1}s</color>");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (activeCount == 0)
|
||||
{
|
||||
sb.AppendLine("<color=#888888>(없음)</color>");
|
||||
}
|
||||
|
||||
int passiveCount = _inventory.PassiveSkills.Count;
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"<b><color=#a0d8a0>패시브: {passiveCount}장</color></b>");
|
||||
|
||||
// 가변 높이 (라인 수 기반)
|
||||
int lines = activeCount + 4 + (activeCount == 0 ? 1 : 0);
|
||||
float height = lines * 20f + 16f;
|
||||
GUI.Box(new Rect(10f, 10f, 320f, height), sb.ToString(), _boxStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bdee77dfcafe4087a759feafb02f6b3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -45,6 +45,9 @@ namespace EerieVillage.Skills.Effectors
|
|||
{
|
||||
if (_hitTargets.Contains(other)) return;
|
||||
|
||||
// PD 지시 2026-05-09 후속 방어 — 자기(Player) hit·자기 자신·hit 방어.
|
||||
if (other.GetComponent<PlayerController>() != null) return;
|
||||
|
||||
// Enemy 레이어 한정.
|
||||
// Phase 2-D fallback (2026-05-09): TagManager에 "Enemy" 레이어 미등재 시 LayerMask.NameToLayer 반환값 = -1.
|
||||
// 레이어 매칭 실패 시 EnemyController 컴포넌트 존재 여부로 대체 판정.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
using EerieVillage.Skills;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
|
|
@ -60,21 +61,70 @@ namespace EerieVillage.Skills.Effectors
|
|||
/// 투사체 프리팹 로드.
|
||||
/// Phase 2-C 에서 data.projectilePrefab 필드를 추가하면 해당 경로 우선 사용.
|
||||
/// 현재는 Resources/Skills/Projectiles/Default 폴백.
|
||||
/// 폴백 실패 시 Collider2D 부착 빈 오브젝트를 반환한다.
|
||||
/// 폴백 실패 시 Collider2D + SpriteRenderer 부착 빈 오브젝트를 반환한다.
|
||||
/// PD 지시 2026-05-09 — 시각화: 흰색 원 sprite (Unity 기본 whiteTexture 사용).
|
||||
/// </summary>
|
||||
private static GameObject LoadProjectilePrefab(ActiveSkillData data)
|
||||
{
|
||||
var prefab = Resources.Load<GameObject>("Skills/Projectiles/Default");
|
||||
if (prefab != null) return prefab;
|
||||
|
||||
// 폴백 — 빈 오브젝트 + CircleCollider2D (시각 없음, 판정만)
|
||||
// 폴백 — Collider + SpriteRenderer (시각 표시·판정 동시)
|
||||
var go = new GameObject($"Projectile_{data.CardId}");
|
||||
var col = go.AddComponent<CircleCollider2D>();
|
||||
col.isTrigger = true;
|
||||
col.radius = 0.2f;
|
||||
|
||||
var sr = go.AddComponent<SpriteRenderer>();
|
||||
sr.sprite = GetOrCreateFallbackSprite();
|
||||
sr.color = GetColorByAttribute(data.AttributeTags);
|
||||
sr.sortingOrder = 50; // Player·Enemy 위
|
||||
// 0.4 unit 직경 정도의 작은 원
|
||||
go.transform.localScale = new Vector3(0.4f, 0.4f, 1f);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
// 캐시: Sprite 매번 생성하지 않도록 정적 보존
|
||||
static Sprite _fallbackSprite;
|
||||
|
||||
static Sprite GetOrCreateFallbackSprite()
|
||||
{
|
||||
if (_fallbackSprite != null) return _fallbackSprite;
|
||||
|
||||
// 16×16 원형 알파 텍스처 (둥근 형태)
|
||||
const int size = 16;
|
||||
var tex = new Texture2D(size, size, TextureFormat.RGBA32, false);
|
||||
tex.wrapMode = TextureWrapMode.Clamp;
|
||||
tex.filterMode = FilterMode.Bilinear;
|
||||
float r = size * 0.5f;
|
||||
for (int y = 0; y < size; y++)
|
||||
{
|
||||
for (int x = 0; x < size; x++)
|
||||
{
|
||||
float dx = x - r + 0.5f;
|
||||
float dy = y - r + 0.5f;
|
||||
float dist = Mathf.Sqrt(dx * dx + dy * dy);
|
||||
float alpha = Mathf.Clamp01(1f - (dist - (r - 1.5f)));
|
||||
tex.SetPixel(x, y, new Color(1f, 1f, 1f, alpha));
|
||||
}
|
||||
}
|
||||
tex.Apply();
|
||||
_fallbackSprite = Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), size);
|
||||
return _fallbackSprite;
|
||||
}
|
||||
|
||||
static Color GetColorByAttribute(AttributeTag attr)
|
||||
{
|
||||
// 속성별 색상 (시각 구분 영역)
|
||||
if ((attr & AttributeTag.Fire) != 0) return new Color(1f, 0.5f, 0.2f);
|
||||
if ((attr & AttributeTag.Frost) != 0) return new Color(0.5f, 0.85f, 1f);
|
||||
if ((attr & AttributeTag.Dark) != 0) return new Color(0.6f, 0.3f, 0.85f);
|
||||
if ((attr & AttributeTag.Lightning) != 0) return new Color(1f, 1f, 0.4f);
|
||||
if ((attr & AttributeTag.Physical) != 0) return new Color(0.95f, 0.95f, 0.95f);
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
private static Vector2 RotateVector(Vector2 v, float degrees)
|
||||
{
|
||||
float rad = degrees * Mathf.Deg2Rad;
|
||||
|
|
|
|||
Loading…
Reference in New Issue