249 lines
17 KiB
C#
249 lines
17 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLGameplaySettings.cs — WL 게임플레이 전역 스위치 단일 출처(SOT)
|
||
//
|
||
// PD 지시 #763 (2026-09-06):
|
||
// "내가 지시할때까지 플레이어의 펫은 등장시키지 마."
|
||
//
|
||
// 목적:
|
||
// "펫을 등장시킬지"는 코드가 아니라 **데이터**로 둔다(C45). 코드에는 상수 값을 넣지 않고
|
||
// 이 ScriptableObject 를 읽는다. PD 가 다시 지시하면 에셋의 체크박스 하나만 켜면 된다
|
||
// — 코드 되돌리기·재빌드 없이 원복되는 롤백 경로(C8).
|
||
//
|
||
// 로드 관례:
|
||
// 같은 폴더의 SlashTrailSettings 와 동일하게 Resources 로 1회 로드 후 캐시한다.
|
||
// 에셋 경로 = Assets/WL/Settings/Resources/WL/WLGameplaySettings.asset
|
||
//
|
||
// 🔴 어셈블리 주의: 힘민지 Assets/Script/ 에는 .asmdef 가 없다(전부 Assembly-CSharp).
|
||
// Assets/WL/Settings/ 에도 .asmdef 를 만들지 말 것.
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using UnityEngine;
|
||
|
||
namespace WL.Settings
|
||
{
|
||
/// <summary>
|
||
/// WL 게임플레이 전역 스위치. 코드 상수 금지(C45) — 모든 값을 이 에셋에서 읽는다.
|
||
/// </summary>
|
||
[CreateAssetMenu(fileName = "WLGameplaySettings", menuName = "WL/Gameplay Settings")]
|
||
public sealed class WLGameplaySettings : ScriptableObject
|
||
{
|
||
// ── 로드 경로 (게임 데이터 값이 아니라 에셋 주소다 · SlashTrailSettings 와 동일 규약) ──
|
||
public const string ResourcesPath = "WL/WLGameplaySettings";
|
||
|
||
private static WLGameplaySettings s_cached;
|
||
private static bool s_lookupDone;
|
||
|
||
/// <summary>설정 에셋(1회 로드 후 캐시). 에셋이 없으면 null.</summary>
|
||
public static WLGameplaySettings Instance
|
||
{
|
||
get
|
||
{
|
||
if (s_lookupDone) return s_cached;
|
||
s_lookupDone = true;
|
||
s_cached = Resources.Load<WLGameplaySettings>(ResourcesPath);
|
||
return s_cached;
|
||
}
|
||
}
|
||
|
||
/// <summary>캐시를 비운다(에디터에서 에셋을 새로 만들었을 때 · 테스트용).</summary>
|
||
public static void ClearCache()
|
||
{
|
||
s_cached = null;
|
||
s_lookupDone = false;
|
||
}
|
||
|
||
// ── 펫 (#763) ────────────────────────────────────────────────────────────
|
||
[Header("■ 펫 (PD 지시 #763)")]
|
||
[Tooltip("플레이어 펫을 게임에 등장시킬지. PD 지시로 꺼 둔 상태다 — 다시 등장시키라는 지시가 오면 여기를 켠다. " +
|
||
"끄면 장비창에 펫이 장착되어 있어도 프리팹을 로드하지 않고 펫 카드를 Empty 로 둔다. " +
|
||
"적 펫(PvP·투견장 상대)에는 영향이 없다.")]
|
||
public bool spawnPlayerPet = false;
|
||
|
||
/// <summary>
|
||
/// 플레이어 펫을 등장시킬지. **에셋이 없으면 false**(= 등장 안 함)로 본다 —
|
||
/// PD 지시가 에셋 누락으로 무력화되지 않게 하기 위한 안전 기본값이며,
|
||
/// 게임 데이터 값의 코드 하드코딩이 아니라 SOT 부재 시의 실패 처리다.
|
||
/// </summary>
|
||
public static bool SpawnPlayerPet
|
||
{
|
||
get { var i = Instance; return i != null && i.spawnPlayerPet; }
|
||
}
|
||
|
||
// ── 무적 (#765 · 임시) ───────────────────────────────────────────────────
|
||
[Header("■ 플레이어 무적 (PD 지시 #765 · 임시)")]
|
||
[Tooltip("임시 · PD 지시 #765 · 끄면 원상. 테스트 편의를 위해 플레이어(PC)가 피해를 받지 않는다. " +
|
||
"MyActor.Get_Damage 진입에서 곧바로 반환하므로 HP 는 줄지 않는다. " +
|
||
"넉백 등 상태이상까지 막지는 않는다. 펫·적에게는 영향이 없다.")]
|
||
public bool playerInvincible = true;
|
||
|
||
/// <summary>
|
||
/// 플레이어가 무적인지. **에셋이 없으면 false**(= 무적 아님) — SOT 부재 시
|
||
/// 게임을 원래 규칙대로 두는 안전 처리이며, 게임 데이터 값의 하드코딩이 아니다.
|
||
/// </summary>
|
||
public static bool PlayerInvincible
|
||
{
|
||
get { var i = Instance; return i != null && i.playerInvincible; }
|
||
}
|
||
|
||
// ── 충돌(이동 차단) 범위 · NavMeshAgent (#768) ───────────────────────────
|
||
// PD 지시 #768 (2026-09-06): "플레이어와 몬스터의 충돌범위가 너무 넓은 것 같아.
|
||
// (좁게 만들어서 대부분 지나갈 수 있도록 해야 해. (단, 보스 몬스터는 몬스터 크기를 고려해 크게 잡아도 됨)"
|
||
// 실측(2026-09-06): 플레이어·몬스터가 서로를 밀어내는 것은 물리 콜라이더가 아니라 NavMeshAgent 다.
|
||
// - PC 프리팹 Ai01: CapsuleCollider 는 IsTrigger, Rigidbody 는 Constraints=126(위치·회전 전부 고정)
|
||
// - MobActor.Start 가 몹 Rigidbody 를 isKinematic 으로 강제 → 물리로는 서로를 밀 수 없다
|
||
// - 남는 것은 NavMeshAgent 의 radius + 상호 회피(RVO). 몹 프리팹의 avoidancePriority=10 이 PC(50)보다
|
||
// 우선순위가 높아, 실제로는 플레이어가 몹을 비켜 가도록 밀려난다.
|
||
// 적용 지점 = PCActor.Set / MobActor.Set(MonsterTableData…) → WLCollisionTuning (프리팹 개별 편집 없음).
|
||
[Header("■ 충돌 범위 · NavMeshAgent (PD 지시 #768)")]
|
||
[Tooltip("이 섹션 전체의 마스터 스위치. 끄면 프리팹 원본 값 그대로 동작한다(= 지시 이전 상태로 롤백).")]
|
||
public bool agentTuningEnabled = true;
|
||
|
||
[Tooltip("플레이어(PC)의 NavMeshAgent 반경(m). 0 이하면 프리팹 원본 유지. 프리팹 원본 = 0.4")]
|
||
public float pcAgentRadius = 0.15f;
|
||
|
||
[Tooltip("일반 몹(MonsterList e_MonsterType = None · 251종)의 원본 반경에 곱할 배율. 1 이면 원본 유지.")]
|
||
public float mobAgentRadiusScale = 0.5f;
|
||
|
||
[Tooltip("일반·정예 몹 반경의 하한(m). 배율을 곱한 결과가 이 값보다 작아지지 않는다. 0 이면 하한 없음.")]
|
||
public float mobAgentRadiusMin = 0.08f;
|
||
|
||
[Tooltip("정예 몹(Elite · 36종)의 반경 배율.")]
|
||
public float eliteAgentRadiusScale = 0.75f;
|
||
|
||
[Tooltip("보스(Boss · 12종)의 반경 배율. PD 지시대로 보스는 크게 유지 = 1.0(원본).")]
|
||
public float bossAgentRadiusScale = 1f;
|
||
|
||
[Tooltip("플레이어의 회피 우선순위(0 = 최우선 · 낮을수록 남이 비켜 준다 · Unity 기본 50). 음수면 원본 유지.")]
|
||
public int pcAvoidancePriority = 0;
|
||
|
||
[Tooltip("일반·정예 몹의 회피 우선순위(프리팹 원본 = 10 · Unity 기본 50). 음수면 원본 유지. " +
|
||
"보스는 tuneBossAgent 가 꺼져 있으면 손대지 않는다.")]
|
||
public int mobAvoidancePriority = 50;
|
||
|
||
[Tooltip("일반 몹(None)의 상호 회피 품질. Keep = 프리팹 원본(LowQuality). None 이면 서로 겹쳐 지나갈 수 있다.")]
|
||
public AvoidanceOverride mobAvoidance = AvoidanceOverride.Keep;
|
||
|
||
[Tooltip("플레이어의 상호 회피 품질. Keep = 프리팹 원본(LowQuality). None 이면 플레이어가 몹을 피해 밀려나지 않는다.")]
|
||
public AvoidanceOverride pcAvoidance = AvoidanceOverride.Keep;
|
||
|
||
[Tooltip("켜면 보스에게도 회피 우선순위·회피 품질 오버라이드를 적용한다. 기본은 꺼짐(보스는 원본 유지).")]
|
||
public bool tuneBossAgent = false;
|
||
|
||
/// <summary>NavMeshAgent.obstacleAvoidanceType 오버라이드. Keep(-1) = 프리팹 원본 유지.</summary>
|
||
public enum AvoidanceOverride
|
||
{
|
||
Keep = -1,
|
||
None = 0,
|
||
LowQuality = 1,
|
||
MedQuality = 2,
|
||
GoodQuality = 3,
|
||
HighQuality = 4,
|
||
}
|
||
|
||
// ── 몬스터 투사체 크기 (#769) ────────────────────────────────────────────
|
||
// PD 지시 #769 (2026-09-06): "몬스터 투사체 크기를 지금 크기에서 70% 줄여줘 (작게)" → 배율 0.3
|
||
// 적용 지점 = ProjectileBase.Set → WLCollisionTuning.ProjectileScale. 플레이어·펫 투사체는 무변경.
|
||
[Header("■ 몬스터 투사체 크기 (PD 지시 #769)")]
|
||
[Tooltip("일반 몹이 쏜 투사체의 크기 배율(원본 대비). 0.3 = 70% 축소. 1 이면 원본.")]
|
||
public float mobProjectileScale = 0.3f;
|
||
|
||
[Tooltip("정예 몹(Elite)이 쏜 투사체의 크기 배율.")]
|
||
public float eliteProjectileScale = 0.3f;
|
||
|
||
[Tooltip("보스(Boss)가 쏜 투사체의 크기 배율. PD 가 따로 조절할 수 있게 분리해 두었다.")]
|
||
public float bossProjectileScale = 0.3f;
|
||
|
||
[Tooltip("켜면 '렌더러가 있는(눈에 보이는)' 투사체만 축소한다. 렌더러가 없는 프리팹은 근접 공격 등의 " +
|
||
"보이지 않는 타격 판정 박스(예: P_*_Meele = 3x3x3 트리거 박스 · 수명 0.1초)라, 줄이면 화면상 변화 없이 " +
|
||
"몹의 공격 사거리만 줄어 빗나간다. 끄면 판정 박스까지 전부 축소한다(= PD 지시 문자 그대로).")]
|
||
public bool projectileScaleVisibleOnly = true;
|
||
|
||
// ── 몬스터 몸 크기 (#816x) ───────────────────────────────────────────────
|
||
// PD 지시 #816 (2026-09-15): "기본적인 몬스터의 크기도 50% 키워줘" → 일반 몹 배율 1.5
|
||
// 적용 지점 = MobActor.Set 이 이미 부르는 WLCollisionTuning.ApplyMob(원본 훅 재사용 · 새 훅 0줄).
|
||
// MobActor.cs:87 이 transform.localScale = f_DefaultScale 을 대입한 **직후**(:118)라
|
||
// 매 스폰·풀 재사용마다 테이블 기본값에서 다시 곱한다 = 누적 없음.
|
||
// 데이터 표(MonsterList.json f_DefaultScale)는 손대지 않는다 — 런타임 루트 스케일만.
|
||
[Header("■ 몬스터 몸 크기 (PD 지시 #816x)")]
|
||
[Tooltip("켜면 몬스터 루트 스케일에 아래 배율을 곱한다. 끄면 테이블 f_DefaultScale 그대로(C8 롤백).")]
|
||
public bool mobBodyScaleEnabled = true;
|
||
|
||
[Tooltip("일반 몹(None)의 몸 크기 배율. 1.5 = PD 지시 '50% 크게'. 1 이면 무변경.")]
|
||
public float mobBodyScale = 1.5f;
|
||
|
||
[Tooltip("정예 몹(Elite)의 몸 크기 배율. PD 지시는 '기본적인 몬스터'라 기본 1(무변경).")]
|
||
public float eliteBodyScale = 1f;
|
||
|
||
[Tooltip("보스(Boss)의 몸 크기 배율. PD 지시 범위 밖이라 기본 1(무변경).")]
|
||
public float bossBodyScale = 1f;
|
||
|
||
[Tooltip("켜면 배율 결과가 보스 판정 경계(bodyScaleBossThreshold)를 넘지 않도록 자른다.\n" +
|
||
"원본 ProjectileBase.Check_TargetBoss(:305/308) 와 Actor.Get_ScaleSizeDist(:950) 가 " +
|
||
"localScale.x > 2 를 '보스급' 판정에 쓰기 때문이다. 끄면 PD 지시대로 곧이곧대로 ×배율(기본 = 끔).")]
|
||
public bool mobBodyScaleKeepUnderBossThreshold = false;
|
||
|
||
[Tooltip("위 자르기의 경계값. 원본 코드가 쓰는 2 와 같아야 한다.")]
|
||
public float bodyScaleBossThreshold = 2f;
|
||
|
||
[Tooltip("경계에서 얼마나 아래로 자를지(여유). 0.02 = 1.98 로 자른다.")]
|
||
public float bodyScaleThresholdEpsilon = 0.02f;
|
||
|
||
// ── 인게임 HUD 오버라이드 (#775) ─────────────────────────────────────────
|
||
// PD 지시 #775 (2026-09-06): "우측 하단 공격 버튼 및 스킬 아이콘 버튼은 추후 변경해서 사용할
|
||
// 예정이므로 지금은 보이지 않게 숨기고, 포트레이트 뷰에 맞게 WL 프로젝트 UI를 배치해줘."
|
||
// 적용 지점 = NewGameUI.SetUI_byGameMode 끝 → WL.UI.WLIngameUiOverride.Apply (런타임 전용).
|
||
// 프리팹을 편집하는 것이 아니라 런타임에 SetActive 만 바꾼다 → 스위치를 끄면 즉시 원복(C8).
|
||
[Header("■ 인게임 HUD 오버라이드 (PD 지시 #775)")]
|
||
[Tooltip("PD #775 임시 · 추후 교체. 우측 하단 전투 패드(공격·회피·스킬 슬롯·자동 타겟 토글)를 인게임에서 숨긴다. " +
|
||
"끄면 다음 인게임 진입(또는 WLIngameUiOverride.ReapplyAll 호출) 시 즉시 원복된다. " +
|
||
"숨겨도 전투는 성립한다 — 플레이어 공격은 WLTargetingSettings.autoEngage(자동 교전)가 담당한다.")]
|
||
public bool hideBattlePad = true;
|
||
|
||
[Tooltip("숨길 전투 패드 루트의 경로 목록. NewGameUI 프리팹 루트 기준 상대 경로다(대소문자 구분). " +
|
||
"실측(2026-09-06): 인게임 전투 패드 = NewGameUI/IngameUIs/BattleUI (Attack·Avoid·Equip·btn_auto·SkillCard×6). " +
|
||
"같은 이름의 BattleUI 가 SkillEquipUI·SkillLayout 아래에도 있어 이름만으로 찾으면 스킬 화면까지 " +
|
||
"꺼지므로 반드시 경로로 지정한다.")]
|
||
public string[] battlePadPaths = new[] { "IngameUIs/BattleUI" };
|
||
|
||
[Tooltip("인게임(Stage·Dungeon)에서만 끌 힘민지 공용 UI(NewGameUI/Common 의 자식) 이름 목록. " +
|
||
"로비로 돌아오면 자동으로 다시 켠다 — 로비 화면은 손대지 않는다. " +
|
||
"기준 = WL HUD 와 '같은 기능'이라 중복되는 것만. " +
|
||
"TopRightMoney = WL HUD_Currency 와 같은 재화 표시 · Lv = WL HUD_Level 과 같은 레벨/경험치 표시(화면 하단 EXP 바). " +
|
||
"되돌리려면 해당 이름을 목록에서 지우면 된다.")]
|
||
public string[] ingameHideCommonObjectNames = new[] { "TopRightMoney", "Lv" };
|
||
|
||
/// <summary>우측 하단 전투 패드를 숨길지. 에셋이 없으면 false(= 원래대로 보임).</summary>
|
||
public static bool HideBattlePad
|
||
{
|
||
get { var i = Instance; return i != null && i.hideBattlePad; }
|
||
}
|
||
|
||
// ── 인게임 채팅창 숨김 (#796) ────────────────────────────────────────────
|
||
// PD 지시 #796 (2026-09-07): "하단 채팅창을 일단 숨기고 메뉴 버튼을 화면 하단에 가득 채워질 수 있도록 배치"
|
||
// 적용 지점 = NewGameUI.SetUI_byGameMode 끝 → WL.UI.WLIngameUiOverride.Apply (런타임 전용).
|
||
//
|
||
// 🔴 SetActive(false) 를 쓰지 않는 이유(실측 2026-09-07):
|
||
// ChatUI.Awake 가 밴 목록(m_ChatBan)을 로드하고 Start 가 m_BanListUI.Check() 를 부른다.
|
||
// Awake 전에 꺼 버리면 m_ChatBan 이 null 인 채로 남고, 채팅 서버 메시지가 오는 순간
|
||
// MessageInfo.Update → ChatUI.Add_Chat 의 m_ChatBan.m_List 에서 NullReferenceException 이 난다.
|
||
// 그래서 오브젝트는 켠 채로 두고 CanvasGroup 으로만 가린다 → 채팅 시스템 코드 경로는 그대로 산다.
|
||
[Header("■ 인게임 채팅창 숨김 (PD 지시 #796)")]
|
||
[Tooltip("PD #796 임시 · 인게임(Stage·Dungeon)에서 화면 최하단 채팅창(입력창·스크롤뷰·순위 표시)을 가린다. " +
|
||
"끄면 다음 인게임 진입(또는 WLIngameUiOverride.ReapplyAll 호출) 시 즉시 원복된다. " +
|
||
"오브젝트를 끄는 것이 아니라 CanvasGroup alpha 0 + blocksRaycasts false 로 가리므로 " +
|
||
"채팅 서버 수신·밴 목록 등 채팅 시스템 동작은 그대로 유지된다.")]
|
||
public bool hideIngameChat = true;
|
||
|
||
[Tooltip("가릴 대상의 경로 목록. NewGameUI 프리팹 루트 기준 상대 경로다(대소문자 구분). " +
|
||
"실측(2026-09-07): 채팅창 = NewGameUI/MessageInfo/Chat (ChatUI · bg · scrollview_chat · rank · " +
|
||
"inputfield_chat · btn_send · BanList). 되돌리려면 이 목록을 비우면 된다.")]
|
||
public string[] ingameHideCanvasGroupPaths = new[] { "MessageInfo/Chat" };
|
||
|
||
/// <summary>인게임에서 채팅창을 가릴지. 에셋이 없으면 false(= 원래대로 보임).</summary>
|
||
public static bool HideIngameChat
|
||
{
|
||
get { var i = Instance; return i != null && i.hideIngameChat; }
|
||
}
|
||
}
|
||
}
|