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

137 lines
5.6 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 UnityEngine;
using Platformer.Mechanics;
using EerieVillage.Skills;
namespace EerieVillage.Skills.Effectors
{
/// <summary>
/// 투사체 생성기. IEffector 구현체.
/// SkillFireEvent.Execute 에서 ActiveCategory.Projectile 분기 시 호출.
/// BT12-Dev Phase 2-B §4-4.
///
/// 다중 발사: PlayerStats.ExtraProjectiles 반영 (P08 투사체증폭).
/// 궤적 분기: ActiveSkillData.Trajectory — Line → Projectile, Homing → HomingProjectile.
/// </summary>
public class ProjectileSpawner : IEffector
{
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
{
var data = runtime.ActiveData;
// 플레이어 위치·방향 취득
Transform playerTransform = inventory.transform;
Vector2 spawnPos = playerTransform.position;
// PlayerController.Facing 참조
Vector2 facing = Vector2.right;
var pc = inventory.GetComponent<PlayerController>();
if (pc != null) facing = pc.Facing;
// 프리팹 로드
GameObject prefab = LoadProjectilePrefab(data);
if (prefab == null)
{
Debug.LogWarning($"[ProjectileSpawner] 투사체 프리팹 로드 실패 (data.id={data.CardId})");
return;
}
// 다중 발사 수 (기본 1 + ExtraProjectiles)
int count = 1 + Mathf.Max(0, inventory.Stats.ExtraProjectiles);
float angleStep = count > 1 ? 10f : 0f;
float startAngle = -angleStep * (count - 1) * 0.5f;
for (int i = 0; i < count; i++)
{
float angle = startAngle + angleStep * i;
Vector2 dir = RotateVector(facing, angle);
var go = Object.Instantiate(prefab, (Vector3)spawnPos, Quaternion.identity);
Projectile proj;
if (data.Trajectory == ProjectileTrajectory.Homing)
proj = go.GetComponent<HomingProjectile>() ?? go.AddComponent<HomingProjectile>();
else
proj = go.GetComponent<Projectile>() ?? go.AddComponent<Projectile>();
proj.Initialize(runtime, inventory, dir);
}
}
/// <summary>
/// 투사체 프리팹 로드.
/// Phase 2-C 에서 data.projectilePrefab 필드를 추가하면 해당 경로 우선 사용.
/// 현재는 Resources/Skills/Projectiles/Default 폴백.
/// 폴백 실패 시 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;
// 폴백 — 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;
float cos = Mathf.Cos(rad);
float sin = Mathf.Sin(rad);
return new Vector2(v.x * cos - v.y * sin, v.x * sin + v.y * cos);
}
}
}