2026-05-09 10:00:27 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using Platformer.Mechanics;
|
2026-05-09 16:25:12 +00:00
|
|
|
using Platformer.Gameplay;
|
|
|
|
|
using static Platformer.Core.Simulation;
|
2026-05-09 10:00:27 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
// BT12-Dev 2026-05-10 (PD #1·#2) — 거리 제한·벽 충돌 영역
|
|
|
|
|
protected Vector2 _spawnPosition;
|
|
|
|
|
protected float _maxRange;
|
2026-05-10 07:50:08 +00:00
|
|
|
protected float _spawnTime;
|
2026-05-10 07:23:34 +00:00
|
|
|
|
2026-05-09 10:00:27 +00:00
|
|
|
// 동일 투사체로 동일 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();
|
|
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
// BT12-Dev 2026-05-10 (PD #1) — 거리 제한 영역 영역 spawn 위치 저장
|
|
|
|
|
_spawnPosition = transform.position;
|
2026-05-10 07:50:08 +00:00
|
|
|
_spawnTime = Time.time;
|
2026-05-10 07:23:34 +00:00
|
|
|
|
2026-05-10 07:56:20 +00:00
|
|
|
// BT12-Dev 2026-05-10 PD — 사정거리 5단계 (Camera 가로 배수)
|
|
|
|
|
float camWidth = 12.44f; // fallback (ortho size 3.5·16:9)
|
2026-05-10 07:23:34 +00:00
|
|
|
var cam = Camera.main;
|
|
|
|
|
if (cam != null && cam.orthographic)
|
|
|
|
|
{
|
2026-05-10 07:56:20 +00:00
|
|
|
float aspect = (cam.aspect > 0.01f) ? cam.aspect : (16f / 9f);
|
|
|
|
|
camWidth = cam.orthographicSize * 2f * aspect;
|
2026-05-10 07:23:34 +00:00
|
|
|
}
|
2026-05-10 07:56:20 +00:00
|
|
|
float[] mults = { 0.2f, 0.5f, 0.667f, 1.0f, 1.5f };
|
|
|
|
|
int idx = Mathf.Clamp((int)_data.Range, 0, mults.Length - 1);
|
|
|
|
|
_maxRange = camWidth * mults[idx];
|
2026-05-10 07:23:34 +00:00
|
|
|
|
|
|
|
|
// Phase 2-B: 풀링 미도입 — Invoke 기반 자동 소멸 (거리 제한 영역 영역 영역 영역 영역 안전망)
|
2026-05-09 10:00:27 +00:00
|
|
|
Invoke(nameof(SelfDestruct), _lifetime);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 07:46:04 +00:00
|
|
|
// BT12-Dev 2026-05-10 (PD #2 fix) — Wall Layer Mask (Layer 0 Default·Ground + Layer 16 Foreground·발판)
|
|
|
|
|
// Tilemap = Static collider·Projectile = Trigger·둘 다 Rigidbody2D 부재 → OnTriggerEnter2D 발화 X 영역 영역.
|
|
|
|
|
// → Update 영역 Physics2D.OverlapPoint 영역 Wall Layer 영역 영역 영역 SelfDestruct (영역 영역 영역 영역).
|
|
|
|
|
protected static readonly int WallLayerMask = (1 << 0) | (1 << 16);
|
|
|
|
|
|
2026-05-09 10:00:27 +00:00
|
|
|
protected virtual void Update()
|
|
|
|
|
{
|
|
|
|
|
transform.position += (Vector3)(_direction * _speed * Time.deltaTime);
|
2026-05-10 07:23:34 +00:00
|
|
|
|
|
|
|
|
// BT12-Dev 2026-05-10 (PD #1) — 거리 제한 영역 영역 SelfDestruct
|
|
|
|
|
if (Vector2.Distance(transform.position, _spawnPosition) >= _maxRange)
|
2026-05-10 07:46:04 +00:00
|
|
|
{
|
|
|
|
|
SelfDestruct();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 07:50:08 +00:00
|
|
|
// BT12-Dev 2026-05-10 (PD #2 fix·재발 정정) — Wall OverlapPoint·grace period 0.05s 영역 spawn 시점 즉시 SelfDestruct 회피.
|
|
|
|
|
// Player.position 영역 spawn 영역 — Player 영역 ground tile 영역 영역 영역 → 첫 frame OverlapPoint hit → 회귀.
|
|
|
|
|
if (Time.time - _spawnTime > 0.05f)
|
2026-05-10 07:23:34 +00:00
|
|
|
{
|
2026-05-10 07:50:08 +00:00
|
|
|
var wallHit = Physics2D.OverlapPoint(transform.position, WallLayerMask);
|
|
|
|
|
if (wallHit != null)
|
|
|
|
|
{
|
|
|
|
|
SelfDestruct();
|
|
|
|
|
}
|
2026-05-10 07:23:34 +00:00
|
|
|
}
|
2026-05-09 10:00:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void OnTriggerEnter2D(Collider2D other)
|
|
|
|
|
{
|
2026-05-09 16:09:17 +00:00
|
|
|
if (_hitTargets.Contains(other)) return;
|
2026-05-09 10:00:27 +00:00
|
|
|
|
2026-05-09 12:23:25 +00:00
|
|
|
// PD 지시 2026-05-09 후속 방어 — 자기(Player) hit·자기 자신·hit 방어.
|
2026-05-09 16:09:17 +00:00
|
|
|
if (other.GetComponent<PlayerController>() != null) return;
|
2026-05-09 12:23:25 +00:00
|
|
|
|
2026-05-09 11:57:28 +00:00
|
|
|
// Enemy 레이어 한정.
|
|
|
|
|
int enemyLayer = LayerMask.NameToLayer("Enemy");
|
2026-05-09 16:09:17 +00:00
|
|
|
bool isEnemy = (enemyLayer != -1 && other.gameObject.layer == enemyLayer)
|
|
|
|
|
|| other.GetComponent<EnemyController>() != null;
|
2026-05-09 10:00:27 +00:00
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
if (isEnemy)
|
|
|
|
|
{
|
|
|
|
|
var health = other.GetComponent<Health>();
|
|
|
|
|
if (health == null || !health.IsAlive) return;
|
2026-05-09 10:00:27 +00:00
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
_hitTargets.Add(other);
|
2026-05-09 10:00:27 +00:00
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
// 유효 대미지 산출 — BT12-Dev 2026-05-10 임시 (PD 지시): 기본 공격력 5 하한 강제.
|
|
|
|
|
int damage = Mathf.Max(_runtime.CalculateEffectiveDamage(), 5);
|
2026-05-09 10:00:27 +00:00
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
// 피해 적용
|
|
|
|
|
health.Decrement(damage);
|
2026-05-09 10:00:27 +00:00
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
// 부가 효과 (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;
|
2026-05-09 10:00:27 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-10 07:23:34 +00:00
|
|
|
// 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)
|
2026-05-09 16:25:12 +00:00
|
|
|
{
|
2026-05-10 07:23:34 +00:00
|
|
|
SelfDestruct();
|
2026-05-09 16:25:12 +00:00
|
|
|
}
|
2026-05-09 10:00:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void SelfDestruct()
|
|
|
|
|
{
|
|
|
|
|
CancelInvoke(nameof(SelfDestruct));
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|