EerieVillage/Assets/Scripts/Mechanics/EnemyController.cs

146 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections;
using System.Collections.Generic;
using Platformer.Gameplay;
using UnityEngine;
using static Platformer.Core.Simulation;
namespace Platformer.Mechanics
{
/// <summary>
/// A simple controller for enemies. Provides movement control over a patrol path.
/// </summary>
[RequireComponent(typeof(AnimationController), typeof(Collider2D))]
public class EnemyController : MonoBehaviour
{
public PatrolPath path;
public AudioClip ouch;
/// <summary>BT5-Dev #16 — Distance 기반 감지 영역 X 임계값 (전체 폭). 표준 platformer Enemy 옆 닿음 영역 0.6~0.8.</summary>
public float hitRangeX = 0.7f;
/// <summary>Y 임계값. 위/아래 둘 다 인정.</summary>
public float hitRangeY = 1.0f;
/// <summary>밟기 판정 — Player가 Enemy보다 위 거리. 발 닿는 느낌 영역(0.05~0.15).</summary>
public float stompMinDy = 0.1f;
internal PatrolPath.Mover mover;
internal AnimationController control;
internal Collider2D _collider;
internal AudioSource _audio;
SpriteRenderer spriteRenderer;
public Bounds Bounds => _collider.bounds;
/// <summary>
/// PD 지시 2026-05-07 — Enemy 시각(SpriteRenderer) 영역 기반 충돌 감지용. CapsuleCollider2D는 작은 ground sensor 영역(0.45×0.09)이라 감지에 부적합.
/// SpriteRenderer 부재 시 Collider Bounds로 fallback.
/// </summary>
public Bounds VisualBounds => spriteRenderer != null ? spriteRenderer.bounds : _collider.bounds;
PlayerController _cachedPlayer;
bool _ignoreCollisionApplied;
void Awake()
{
control = GetComponent<AnimationController>();
_collider = GetComponent<Collider2D>();
_audio = GetComponent<AudioSource>();
spriteRenderer = GetComponent<SpriteRenderer>();
// PD 지시 2026-05-07 — Player ↔ Enemy 물리 충돌 무시 (통과 가능). 감지는 Bounds.Intersects 별도.
// Enemy Collider 일반 유지 (지면 충돌 영역 보존) → 등장 정합.
var playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null && _collider != null)
{
var pc = playerObj.GetComponent<Collider2D>();
if (pc != null) Physics2D.IgnoreCollision(_collider, pc, true);
}
// BT5-Dev #17 marker — 본 영역 출력 시 새 코드 영역 적용 정합. 출력 X = Editor Asset Refresh 영역 미수행
Debug.Log($"[BT17-MARKER@{name}] sr={(spriteRenderer != null ? "OK" : "NULL")} hitX={hitRangeX} hitY={hitRangeY} stomp={stompMinDy} | colB={_collider?.bounds.size} vB={VisualBounds.size}");
}
void Update()
{
if (path != null)
{
if (mover == null) mover = path.CreateMover(control.maxSpeed * 0.5f);
control.move.x = Mathf.Clamp(mover.Position.x - transform.position.x, -1, 1);
}
// PD 지시 2026-05-07 — Player ↔ Enemy 통과 가능이지만 Bounds.Intersects로 매 프레임 감지
if (_cachedPlayer == null)
{
// 1차: tag 영역 발견
var pgo = GameObject.FindGameObjectWithTag("Player");
if (pgo != null) _cachedPlayer = pgo.GetComponent<PlayerController>();
// 2차 fallback: tag 영역 미설정 영역에 대비해 PlayerController 영역 직접 검색
if (_cachedPlayer == null)
{
_cachedPlayer = Object.FindFirstObjectByType<PlayerController>();
}
if (Time.frameCount % 60 == 0)
{
int allCount = Object.FindObjectsByType<PlayerController>(FindObjectsSortMode.None).Length;
Debug.Log($"[BT17-Update@{name}] f={Time.frameCount} cached={(_cachedPlayer != null ? _cachedPlayer.name : "NULL")} pgoTag={(pgo != null ? pgo.name : "NULL")} allPCcount={allCount}");
}
}
// BT20 — IgnoreCollision 영역 Awake 시점 Player 발견 X 영역 fallback. Update 영역에서 발견 직후 1회 영역 호출.
if (_cachedPlayer != null && !_ignoreCollisionApplied && _collider != null)
{
var pc = _cachedPlayer.GetComponent<Collider2D>();
if (pc != null)
{
Physics2D.IgnoreCollision(_collider, pc, true);
_ignoreCollisionApplied = true;
Debug.Log($"[BT20-Ignore@{name}] Player↔Enemy IgnoreCollision applied");
}
}
if (_cachedPlayer != null && _cachedPlayer.health != null && _cachedPlayer.health.IsAlive)
{
// BT5-Dev #16 — Distance 기반 단순 감지 (Bounds 영역 산수 영역 의존 X·항상 작동)
// dx 0.7·|dy| 1.0 = 표준 platformer Enemy/Player 닿음 영역. dy > 0.5 = Player가 Enemy 위에서 밟음.
Vector3 ePos = transform.position;
Vector3 pPos = _cachedPlayer.transform.position;
float dx = Mathf.Abs(pPos.x - ePos.x);
float dy = pPos.y - ePos.y;
bool inRange = dx < hitRangeX && Mathf.Abs(dy) < hitRangeY;
if (inRange != _diagWasIntersecting)
{
Debug.Log($"[EnemyDiag@{name}] inRange={inRange} dx={dx:F2} dy={dy:F2} (thresholds dx<{hitRangeX} |dy|<{hitRangeY}) | ePos={ePos} pPos={pPos}");
_diagWasIntersecting = inRange;
}
if (inRange)
{
var ev = Schedule<PlayerEnemyCollision>();
ev.player = _cachedPlayer;
ev.enemy = this;
ev.dyAtCollision = dy;
}
}
}
bool _diagWasIntersecting;
void OnDrawGizmos()
{
// BT5-Dev #15 진단 — Scene 영역 시각화 (Editor에서만 표시)
if (Application.isPlaying && spriteRenderer != null)
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(VisualBounds.center, VisualBounds.size);
}
if (Application.isPlaying && _collider != null)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireCube(_collider.bounds.center, _collider.bounds.size);
}
}
}
}