EerieVillage/Assets/Scripts/Skills/Effectors/Projectile.cs

133 lines
5.0 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.Generic;
using UnityEngine;
using Platformer.Mechanics;
using Platformer.Gameplay;
using static Platformer.Core.Simulation;
namespace EerieVillage.Skills.Effectors
{
/// <summary>
/// 투사체 기본 컴포넌트. Line 궤적 직선 이동·단일 적 타격 후 소멸.
/// BT12-Dev Phase 2-B §4-2.
/// 파생: <see cref="HomingProjectile"/> (A15 추적 화염구).
/// </summary>
public class Projectile : MonoBehaviour
{
protected ActiveSkillData _data;
protected ActiveSkillRuntime _runtime;
protected PlayerSkillInventory _inventory;
protected Vector2 _direction;
protected float _speed = 12f;
protected float _lifetime = 3f;
// BT12-Dev 2026-05-10 (PD #1·#2) — 거리 제한·벽 충돌 영역
protected Vector2 _spawnPosition;
protected float _maxRange;
// 동일 투사체로 동일 Collider 중복 타격 방지
protected readonly HashSet<Collider2D> _hitTargets = new HashSet<Collider2D>();
/// <summary>
/// ProjectileSpawner.Trigger 에서 Instantiate 직후 호출.
/// </summary>
public virtual void Initialize(ActiveSkillRuntime runtime, PlayerSkillInventory inventory, Vector2 direction)
{
_runtime = runtime;
_data = runtime.ActiveData;
_inventory = inventory;
_direction = direction.normalized;
_hitTargets.Clear();
// BT12-Dev 2026-05-10 (PD #1) — 거리 제한 영역 영역 spawn 위치 저장
_spawnPosition = transform.position;
// 최대 거리 = Camera 영역 영역 영역 × 1.5 (PD #1 명시)
// Camera ortho size 3.5·aspect 16:9 → height 7·width ~12.44 → maxRange ~18.66 unit
var cam = Camera.main;
if (cam != null && cam.orthographic)
{
float camWidth = cam.orthographicSize * 2f * cam.aspect;
_maxRange = camWidth * 1.5f;
}
else
{
_maxRange = 20f; // fallback
}
// Phase 2-B: 풀링 미도입 — Invoke 기반 자동 소멸 (거리 제한 영역 영역 영역 영역 영역 안전망)
Invoke(nameof(SelfDestruct), _lifetime);
}
protected virtual void Update()
{
transform.position += (Vector3)(_direction * _speed * Time.deltaTime);
// BT12-Dev 2026-05-10 (PD #1) — 거리 제한 영역 영역 SelfDestruct
if (Vector2.Distance(transform.position, _spawnPosition) >= _maxRange)
{
SelfDestruct();
}
}
protected virtual void OnTriggerEnter2D(Collider2D other)
{
if (_hitTargets.Contains(other)) return;
// PD 지시 2026-05-09 후속 방어 — 자기(Player) hit·자기 자신·hit 방어.
if (other.GetComponent<PlayerController>() != null) return;
// Enemy 레이어 한정.
int enemyLayer = LayerMask.NameToLayer("Enemy");
bool isEnemy = (enemyLayer != -1 && other.gameObject.layer == enemyLayer)
|| other.GetComponent<EnemyController>() != null;
if (isEnemy)
{
var health = other.GetComponent<Health>();
if (health == null || !health.IsAlive) return;
_hitTargets.Add(other);
// 유효 대미지 산출 — BT12-Dev 2026-05-10 임시 (PD 지시): 기본 공격력 5 하한 강제.
int damage = Mathf.Max(_runtime.CalculateEffectiveDamage(), 5);
// 피해 적용
health.Decrement(damage);
// 부가 효과 (DoT·Stun·Slow·DebuffStack) — StatusApplier 위임
var enemy = other.GetComponent<EnemyController>();
if (enemy != null)
{
StatusApplier.Apply(_data, enemy);
}
// Enemy 즉사 시 EnemyDeath 체인 발동
if (!health.IsAlive && enemy != null)
{
Schedule<EnemyDeath>().enemy = enemy;
}
// 단일 적 타격 후 소멸 (관통 미지원 — Phase 2 범위 내)
SelfDestruct();
return;
}
// BT12-Dev 2026-05-10 (PD #2) — 벽 충돌 시 SelfDestruct.
// Layer 0 (Default·Ground) · Layer 16 (Foreground·발판) 영역 영역 Tilemap·Composite·Box collider 영역 정합.
// 레이저 영역 영역 영역 영역 영역 X — 본 Projectile 영역 영역 (영역 영역 영역 영역 X) — 모든 Projectile 영역 SelfDestruct.
int otherLayer = other.gameObject.layer;
bool isWall = (otherLayer == 0 || otherLayer == 16);
if (isWall)
{
SelfDestruct();
}
}
protected void SelfDestruct()
{
CancelInvoke(nameof(SelfDestruct));
Destroy(gameObject);
}
}
}