Project_WL/Assets/WL/Island/WLIslandCampDecor.cs

124 lines
6.1 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLIslandCampDecor.cs — WL-816y 「캠프파이어 설치」
//
// PD 원문(2026-09-15) 「게임 시작 후 처음 골드로 지역을 개방하는 위치에 "캠프파이어 설치"로 명명하고,
// "Fireplace_01" 에셋의 캠프파이어를 중앙에 배치하고, 가장자리에 "Tent_01" 텐트를 배치해
// 자연스럽게 캠프파이어를 차린 것처럼 꾸며줘」
//
// ■ 자리 = 첫 발판이 서 있던 자리(= 「열림 구역」 정중앙 · WLIslandGateFlow.PlatformPosFor(cfg, 0)).
// 값을 치르면 발판은 영영 사라지고(816g) 그 자리에 이 캠프가 남는다.
// ■ 세이브 복원 = 섬이 준비될 때 해금 수(WL_Island_DungeonsUnlocked) ≥ 1 이면 다시 놓는다.
// ■ 장식은 **콜라이더를 전부 끈다** — 이동 방해 0 · NavMesh 재베이크 없음(816g 더미와 같은 원칙).
// ■ 값(프리팹 경로·개수·거리·회전·스케일·바닥 스냅)은 전부 `WLIslandSettings` 에 있다(C45).
// 🔴 원본 에셋(Assets/FantasyForest/**)은 한 줄도 고치지 않는다 — 인스턴스만 만든다.
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다.
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace WL.Island
{
public static class WLIslandCampDecor
{
const string RootName = "~WL_CampDecor";
static readonly List<GameObject> s_made = new List<GameObject>(4);
/// <summary>진단 — 마지막으로 놓은 장식 수·중심.</summary>
public static int DecorSpawned;
public static Vector3 LastCenter;
public static string LastLog = "";
/// <summary>지금 해금 상태에 맞게 캠프 장식을 놓거나 지운다(섬 준비 직후 · 지불 직후 둘 다 여기로).</summary>
public static void Refresh(WLIslandSettings cfg, Scene scene)
{
Clear();
if (cfg == null || cfg.campDecorEnabled == 0) return;
if (WLIslandGateFlow.UnlockedCount(cfg) < 1) { LastLog = "캠프 없음 — 첫 자리 미개방"; return; }
Vector3 center = WLIslandGateFlow.PlatformPosFor(cfg, 0);
LastCenter = center;
var root = new GameObject(RootName);
root.transform.position = center;
if (scene.IsValid() && scene.isLoaded) SceneManager.MoveGameObjectToScene(root, scene);
s_made.Add(root);
int n = 0;
// ① 모닥불 = 구역 중앙
var fire = Spawn(cfg, cfg.campFirePrefabPath, root.transform, center, 0f, cfg.campFireScale, "Fire");
if (fire != null) n++;
// ② 텐트 = 구역 가장자리 · 불 쪽을 향하게
float r = TentRadius(cfg);
int tents = Mathf.Max(0, cfg.campTentCount);
for (int i = 0; i < tents; i++)
{
float yaw = cfg.campTentStartYaw + (tents > 0 ? 360f / tents * i : 0f);
float rad = yaw * Mathf.Deg2Rad;
Vector3 p = center + new Vector3(Mathf.Sin(rad) * r, 0f, Mathf.Cos(rad) * r);
// 텐트가 중앙(불)을 바라보도록 = 바깥 방향의 반대
float faceYaw = yaw + 180f + cfg.campTentFaceYawOffset;
if (Spawn(cfg, cfg.campTentPrefabPath, root.transform, p, faceYaw, cfg.campTentScale, "Tent_" + i) != null) n++;
}
DecorSpawned = n;
LastLog = "캠프 장식 " + n + "개 — 중심 " + center.ToString("F2") + " · 텐트 거리 " + r.ToString("F2");
WLIslandBridge.Log(cfg, LastLog);
}
/// <summary>텐트 거리 — 「열림 구역」이 있으면 그 짧은 쪽 반지름(= 가장자리), 없으면 설정값.</summary>
static float TentRadius(WLIslandSettings cfg)
{
float r = cfg.campTentRadius;
if (cfg.campTentUseZone != 0 && WLIslandGateFlow.ZoneBoundsValid)
{
var e = WLIslandGateFlow.ZoneBounds.extents;
float zr = Mathf.Min(e.x, e.z) * cfg.campTentZoneScale;
if (zr > 0.01f) r = zr;
}
return Mathf.Max(0.01f, r);
}
static GameObject Spawn(WLIslandSettings cfg, string path, Transform parent, Vector3 pos, float yaw, float scale, string name)
{
if (string.IsNullOrEmpty(path)) return null;
var prefab = WLIslandAssets.LoadPrefab(path);
if (prefab == null) { WLIslandBridge.Log(cfg, "캠프 장식 프리팹 없음 — " + path); return null; }
var go = Object.Instantiate(prefab);
go.name = "~WL_Camp_" + name;
go.transform.SetParent(parent, false);
go.transform.position = Snap(cfg, pos);
go.transform.rotation = Quaternion.Euler(0f, yaw, 0f);
float s = scale > 0.001f ? scale : 1f;
go.transform.localScale = prefab.transform.localScale * s;
var cols = go.GetComponentsInChildren<Collider>(true);
for (int i = 0; i < cols.Length; i++) if (cols[i] != null) cols[i].enabled = false;
return go;
}
static Vector3 Snap(WLIslandSettings cfg, Vector3 p)
{
if (cfg.campDecorSnapToGround == 0) return p;
RaycastHit hit;
if (Physics.Raycast(new Vector3(p.x, p.y + 30f, p.z), Vector3.down, out hit, 60f, ~0, QueryTriggerInteraction.Ignore))
return new Vector3(p.x, hit.point.y, p.z);
return p;
}
public static void Clear()
{
for (int i = 0; i < s_made.Count; i++)
if (s_made[i] != null) Object.Destroy(s_made[i]);
s_made.Clear();
DecorSpawned = 0;
}
}
}