[WL] 다리 생성 에러(원본 활성화 경쟁 → 해금·비활성) 3겹 방어 · 확장 가격판 0.5배·판 크기 트리거·항상 위에(WL/SpriteOverlay) · EventSystem 경고 홍수 차단 · WLErrorTrace (#816)
- WLDemoTerrain.Update: 활성화 첫 프레임에 IsUnlocked 선반영 · 활성화 중 꺼지면 재활성 · 해금·비활성 칸 5 s 스캔 복구(InitializeProps 리플렉션) - WLIslandFlowV2: PlacePurchasers 배율 expansionPadScale 0.5 · 트리거 expansionPadTriggerSize 3.4 m(십자 겹침 제거) · ApplyOverlay(스프라이트 WL/SpriteOverlay · TMP Overlay) — 우리 발판에도 - WLIslandUi: ReconcileEventSystems(원본 EventSystem 있으면 우리 것 삭제) · WLErrorTrace: 에러·예외·Coroutine 경고 최근 40건 보관 - 실측: 같은 프레임 땅+다리 활성화 오류 0 · 가격판 scale 0.50 · 상자 3.4 m · overlayApplied 9 · 콘솔 에러 0 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
3e6b94db45
commit
28be65ada8
|
|
@ -97,6 +97,9 @@ MonoBehaviour:
|
|||
expansionPadPlace: 1
|
||||
expansionPadInset: 2.2
|
||||
expansionPadLift: 0.06
|
||||
expansionPadTriggerSize: 3.4
|
||||
expansionPadScale: 0.5
|
||||
expansionPadOverlay: 1
|
||||
campDecorSnapToGround: 0
|
||||
applyStartCoin: 1
|
||||
startCoin: 10
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 90beba3ec3bc4f0469656aa21524963a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// WLSpriteOverlay.shader — 스프라이트를 **항상 위에**(깊이 무시) 그리는 언릿 · 알파 블렌드 셰이더
|
||||
// 2026-09-17 PD 「가격판이 풀·오브젝트에 가려지지 않게 상단에 보이게」 — 확장 가격판(FI Purchaser 의 Frame/Coin 스프라이트)에 런타임 복사본 재질로 물린다.
|
||||
// SpriteRenderer 가 _MainTex 에 스프라이트 텍스처를 넣어 주고 정점색을 곱한다. 모바일: 텍스처 1장 · 조명 없음.
|
||||
Shader "WL/SpriteOverlay"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
}
|
||||
SubShader
|
||||
{
|
||||
Tags { "Queue"="Transparent+100" "RenderType"="Transparent" "IgnoreProjector"="True" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest Always
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float4 _MainTex_ST;
|
||||
half4 _Color;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; };
|
||||
struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; };
|
||||
|
||||
Varyings vert(Attributes v)
|
||||
{
|
||||
Varyings o;
|
||||
o.positionHCS = TransformObjectToHClip(v.positionOS.xyz);
|
||||
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
|
||||
o.color = v.color * _Color;
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag(Varyings i) : SV_Target
|
||||
{
|
||||
half4 c = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv) * i.color;
|
||||
c.rgb *= c.a;
|
||||
return half4(c.rgb / max(c.a, 1e-4), c.a);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
Fallback Off
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d6fd8c10b72144e4980dd1090d915bec
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLErrorTrace.cs — 런타임 에러·예외·「Coroutine couldn't be started」 경고를 **메모리에 보관**한다(최근 40건 · 스택 포함).
|
||||
//
|
||||
// 2026-09-17 PD 「다리가 생성될 때 에러」 실측: 화면 팝업은 보였는데 에디터 콘솔(파이프라인 조회)은 비어 있어 원문·스택을 못 잡았다
|
||||
// (Play 재시작·Clear 로 사라짐). 이후로는 Lead 프로브가 `WLErrorTrace.Dump()` 로 언제든 읽는다. 원본 코드 0줄 · 화면 출력 없음.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WL.Island
|
||||
{
|
||||
public static class WLErrorTrace
|
||||
{
|
||||
public struct Entry { public float time; public LogType type; public string message; public string stack; }
|
||||
|
||||
static readonly List<Entry> s_entries = new List<Entry>(64);
|
||||
static bool s_hooked;
|
||||
public static int Total;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void Hook()
|
||||
{
|
||||
if (s_hooked) return;
|
||||
s_hooked = true;
|
||||
Application.logMessageReceived += OnLog;
|
||||
}
|
||||
|
||||
static void OnLog(string message, string stack, LogType type)
|
||||
{
|
||||
bool keep = type == LogType.Error || type == LogType.Exception || type == LogType.Assert
|
||||
|| (type == LogType.Warning && message != null && message.IndexOf("Coroutine couldn't be started", System.StringComparison.Ordinal) >= 0);
|
||||
if (!keep) return;
|
||||
Total++;
|
||||
if (s_entries.Count >= 40) s_entries.RemoveAt(0);
|
||||
s_entries.Add(new Entry { time = Time.realtimeSinceStartup, type = type, message = message, stack = stack });
|
||||
}
|
||||
|
||||
/// <summary>최근 항목을 한 문자열로(프로브용). 같은 메시지는 횟수만 센다.</summary>
|
||||
public static string Dump(int maxEach = 400)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.Append("total=").Append(Total).Append(" kept=").Append(s_entries.Count);
|
||||
var counts = new Dictionary<string, int>();
|
||||
for (int i = 0; i < s_entries.Count; i++)
|
||||
{
|
||||
var e = s_entries[i];
|
||||
string key = e.message.Length > 100 ? e.message.Substring(0, 100) : e.message;
|
||||
int c; counts.TryGetValue(key, out c);
|
||||
counts[key] = c + 1;
|
||||
if (c > 0) continue;
|
||||
sb.Append("\n[").Append(e.time.ToString("F1")).Append("s ").Append(e.type).Append("] ")
|
||||
.Append(e.message.Length > maxEach ? e.message.Substring(0, maxEach) : e.message);
|
||||
if (!string.IsNullOrEmpty(e.stack)) sb.Append("\n ").Append(e.stack.Length > maxEach ? e.stack.Substring(0, maxEach) : e.stack);
|
||||
}
|
||||
foreach (var kv in counts) if (kv.Value > 1) sb.Append("\n x").Append(kv.Value).Append(" ").Append(kv.Key);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static void Clear() { s_entries.Clear(); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d5cabada8fb5b024a965629f583e026e
|
||||
|
|
@ -501,8 +501,25 @@ namespace WL.Island
|
|||
dir = Mathf.Abs(dir.x) >= Mathf.Abs(dir.z) ? new Vector3(Mathf.Sign(dir.x), 0f, 0f) : new Vector3(0f, 0f, Mathf.Sign(dir.z));
|
||||
var target = c + dir * (half - Mathf.Clamp(cfg.expansionPadInset, 0.5f, half));
|
||||
float gy = PadGroundY(target, c.y);
|
||||
float plateDrop = 0f; var frame = p.transform.Find("Frame"); if (frame != null) plateDrop = -frame.localPosition.y; // 판 자식들이 루트보다 아래(−0.49)
|
||||
p.transform.position = new Vector3(target.x, gy + plateDrop + cfg.expansionPadLift, target.z);
|
||||
float plateDropLocal = 0f; var frame = p.transform.Find("Frame"); if (frame != null) plateDropLocal = -frame.localPosition.y; // 판 자식들이 루트보다 아래(로컬 −0.49)
|
||||
// 🔴 2026-09-17 PD 「확장 가격판이 너무 크다 → 캠프파이어 발판과 같은 크기」 = 우리 발판과 같은 배율(platformScale 0.5 = expansionPadScale)
|
||||
float ps = cfg.expansionPadScale > 0.05f ? cfg.expansionPadScale : 1f;
|
||||
p.transform.localScale = Vector3.one * ps;
|
||||
p.transform.position = new Vector3(target.x, gy + plateDropLocal * ps + cfg.expansionPadLift, target.z);
|
||||
// 🔴 2026-09-17 — 원본 트리거는 십자(10×5×5 두 개 · ±5 m)라 가장자리로 옮긴 판끼리 겹쳐 두 구매가 동시에 시작됐다(원본 활성화 경쟁 → 「Bridge (0) inactive」).
|
||||
// 판 크기(expansionPadTriggerSize · 월드 m)만큼의 상자 하나로 줄인다 = 판을 밟아야 산다(우리 발판과 같은 규칙). 로컬 치수는 배율로 역보정.
|
||||
if (cfg.expansionPadTriggerSize > 0.1f)
|
||||
{
|
||||
var boxes = p.GetComponents<BoxCollider>();
|
||||
float ts = cfg.expansionPadTriggerSize / ps;
|
||||
for (int b = 0; b < boxes.Length; b++)
|
||||
{
|
||||
if (boxes[b] == null) continue;
|
||||
boxes[b].size = new Vector3(ts, 4f / ps, ts);
|
||||
boxes[b].center = new Vector3(0f, -plateDropLocal + 2f / ps, 0f); // 판 높이에서 위로 4 m(월드)
|
||||
}
|
||||
}
|
||||
if (cfg.expansionPadOverlay != 0) ApplyOverlay(p.gameObject); // 풀·소품에 가려지지 않게 항상 위에
|
||||
var mk = p.gameObject.AddComponent<WLPlacedPurchaser>(); mk.original = pos;
|
||||
PurchasersPlaced++;
|
||||
LastPlaceLog = "확장 가격판 " + pos.ToString("F0") + " → " + p.transform.position.ToString("F2") + "(이웃 " + c.ToString("F0") + " 가장자리 안쪽 " + cfg.expansionPadInset.ToString("F1") + " m · 지면 " + gy.ToString("F2") + ")";
|
||||
|
|
@ -510,6 +527,46 @@ namespace WL.Island
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 🔴 2026-09-17 PD 「가격판이 풀·오브젝트에 가려지지 않게 상단에 보이게」 — 스프라이트(Frame·Coin)는 깊이 무시 셰이더 `WL/SpriteOverlay`,
|
||||
// 가격 글자(TMP)는 TMP 의 Overlay 변형 셰이더로 바꾼 **런타임 복사본** 재질을 물린다(에셋 0줄 · 큐 3100 = 물·거품 뒤).
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
static Material s_spriteOverlay;
|
||||
static readonly Dictionary<Material, Material> s_tmpOverlay = new Dictionary<Material, Material>();
|
||||
public static int OverlayApplied;
|
||||
|
||||
public static void ApplyOverlay(GameObject root)
|
||||
{
|
||||
if (root == null) return;
|
||||
var srs = root.GetComponentsInChildren<SpriteRenderer>(true);
|
||||
if (srs.Length > 0)
|
||||
{
|
||||
if (s_spriteOverlay == null)
|
||||
{
|
||||
var sh = Shader.Find("WL/SpriteOverlay");
|
||||
if (sh != null) s_spriteOverlay = new Material(sh) { name = "WL_SpriteOverlay(runtime)", hideFlags = HideFlags.DontSave };
|
||||
}
|
||||
if (s_spriteOverlay != null)
|
||||
for (int i = 0; i < srs.Length; i++) if (srs[i] != null && srs[i].sharedMaterial != s_spriteOverlay) { srs[i].sharedMaterial = s_spriteOverlay; OverlayApplied++; }
|
||||
}
|
||||
var texts = root.GetComponentsInChildren<TMPro.TMP_Text>(true);
|
||||
for (int i = 0; i < texts.Length; i++)
|
||||
{
|
||||
var t = texts[i]; if (t == null) continue;
|
||||
var src = t.fontSharedMaterial; if (src == null || src.shader == null) continue;
|
||||
if (src.shader.name.IndexOf("Overlay", System.StringComparison.OrdinalIgnoreCase) >= 0) continue;
|
||||
Material dst;
|
||||
if (!s_tmpOverlay.TryGetValue(src, out dst) || dst == null)
|
||||
{
|
||||
var sh = Shader.Find("TextMeshPro/Mobile/Distance Field Overlay");
|
||||
if (sh == null) return;
|
||||
dst = new Material(src) { name = src.name + "(WL overlay)", hideFlags = HideFlags.DontSave, shader = sh };
|
||||
s_tmpOverlay[src] = dst;
|
||||
}
|
||||
t.fontSharedMaterial = dst; OverlayApplied++;
|
||||
}
|
||||
}
|
||||
|
||||
static float PadGroundY(Vector3 p, float fallbackY)
|
||||
{
|
||||
var hits = Physics.RaycastAll(new Vector3(p.x, fallbackY + 5f, p.z), Vector3.down, 20f, ~0, QueryTriggerInteraction.Ignore);
|
||||
|
|
|
|||
|
|
@ -554,6 +554,7 @@ namespace WL.Island
|
|||
WLIslandTileFenceStrip.Apply(cfg, scene); // 확장·재구성으로 타일 메시가 바뀌면 다시 잘라낸다(멱등)
|
||||
if (cfg.hideFarms != 0) { Vector3 _c; HideFarms(cfg, scene, out _c); } // 늦게 생긴 밭의 울타리·소품도
|
||||
WLIslandExpansionGate.PlacePurchasers(cfg, scene); // 2026-09-16 — 확장 뒤 새로 생긴 FI 가격판도 이웃 칸 가장자리로(표식 있으면 비용 0)
|
||||
WLIslandPrompt.ReconcileEventSystems(); // 2026-09-17 — 원본 EventSystem 이 올라오면 우리 것을 지운다(경고 홍수 차단)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -796,6 +797,7 @@ namespace WL.Island
|
|||
float ps = cfg.platformScale > 0.001f ? cfg.platformScale : 1f;
|
||||
AppliedScale = ps;
|
||||
if (ps != 1f) p.transform.localScale = p.transform.localScale * ps;
|
||||
if (cfg.expansionPadOverlay != 0) WLIslandExpansionGate.ApplyOverlay(p.gameObject); // 2026-09-17 PD — 우리 발판도 풀·소품 위에 보이게(같은 규칙)
|
||||
|
||||
// 🔴 FI 가격판은 프리팹 안에서 **루트보다 앞·아래**에 놓여 있다(실측 2026-09-13:
|
||||
// Frame/Price/Coin 이 전부 y = -0.49 · z = 루트+(-1/0/+1) — 잠긴 섬 자리의 **물 위**에 눕는 판이다).
|
||||
|
|
|
|||
|
|
@ -570,6 +570,15 @@ namespace WL.Island
|
|||
[Tooltip("판을 지면보다 이만큼(m) 띄운다(z-fighting 방지).")]
|
||||
public float expansionPadLift = 0.06f;
|
||||
|
||||
[Tooltip("옮긴 가격판의 트리거 한 변(m). 원본 십자(±5 m)는 가장자리로 옮긴 판끼리 겹쳐 두 구매가 동시에 시작돼 원본 활성화 경쟁 오류가 났다(2026-09-17). 0 = 원본 트리거 그대로.")]
|
||||
public float expansionPadTriggerSize = 3.4f;
|
||||
|
||||
[Tooltip("옮긴 확장 가격판의 크기 배율 — 2026-09-17 PD 「캠프파이어 발판과 같은 크기」 = platformScale 과 같은 0.5.")]
|
||||
public float expansionPadScale = 0.5f;
|
||||
|
||||
[Tooltip("1 = 가격판(프레임·코인 스프라이트·가격 글자)을 깊이 무시 셰이더로 그려 풀·소품에 가려지지 않게 한다(2026-09-17 PD). 0 = 원본 재질.")]
|
||||
public int expansionPadOverlay = 1;
|
||||
|
||||
[Tooltip("한 줄 = 한 단계. 위에서부터 차례로 발판이 선다.")]
|
||||
public WLFlowStepDef[] flowSteps = new WLFlowStepDef[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -157,8 +157,26 @@ namespace WL.Island
|
|||
if (s_root != null) s_root.SetActive(false);
|
||||
}
|
||||
|
||||
static GameObject s_ownEventSystem;
|
||||
public static int EventSystemsRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// 🔴 2026-09-17 실측: 우리가 「EventSystem 없음」일 때 만든 `~WL_EventSystem` 이 나중에 올라온 원본 씬의 EventSystem 과 겹쳐
|
||||
/// 매 프레임 「There are 2 event systems」 경고가 콘솔을 가득 채웠다(에러 원문이 밀려남). 원본 것이 있으면 우리 것을 지운다.
|
||||
/// </summary>
|
||||
public static void ReconcileEventSystems()
|
||||
{
|
||||
if (s_ownEventSystem == null) return;
|
||||
var all = Object.FindObjectsByType<EventSystem>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
if (all.Length <= 1) return;
|
||||
Object.Destroy(s_ownEventSystem);
|
||||
s_ownEventSystem = null;
|
||||
EventSystemsRemoved++;
|
||||
}
|
||||
|
||||
static void Ensure(WLIslandSettings cfg)
|
||||
{
|
||||
ReconcileEventSystems();
|
||||
if (s_root != null) return;
|
||||
|
||||
s_root = new GameObject("~WL_IslandPrompt");
|
||||
|
|
@ -179,6 +197,7 @@ namespace WL.Island
|
|||
Object.DontDestroyOnLoad(es);
|
||||
es.AddComponent<EventSystem>();
|
||||
es.AddComponent<StandaloneInputModule>();
|
||||
s_ownEventSystem = es;
|
||||
}
|
||||
|
||||
// 버튼
|
||||
|
|
|
|||
|
|
@ -754,17 +754,20 @@ namespace WL.Look.Farm
|
|||
// 새 칸 둘레만 이전 높이 → 새 높이로 `expandRiseSeconds` 동안 보간(SetHeightsDelayLOD · 부분 사각형). 풀 인스턴서·거품은 다 솟은 뒤.
|
||||
// FI 의 OnActivated(1 s 뒤) → 우리 훅의 재굽기는 내용이 같아 생략된다(§6 비교).
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
public static int Rises, RiseFrames;
|
||||
public static string LastRiseLog = "";
|
||||
public static int Rises, RiseFrames, Reactivated, Recovered, EarlyUnlocked;
|
||||
public static string LastRiseLog = "", LastGuardLog = "";
|
||||
readonly List<FIIsland> _rising = new List<FIIsland>(4);
|
||||
readonly List<FIIsland> _activating = new List<FIIsland>(4); // 활성화 애니 중인 칸(다리 포함) — 경쟁으로 꺼지면 다시 켠다
|
||||
FIIsland[] _watch; float _watchNext;
|
||||
bool _deferInstancer, _riseCancel;
|
||||
Coroutine _riseCo;
|
||||
static System.Reflection.MethodInfo s_initProps;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cfg == null || cfg.enabled_ == 0 || cfg.demoTerrainEnabled == 0 || cfg.expandHideOriginal == 0) return;
|
||||
if (_watch == null || Time.unscaledTime >= _watchNext)
|
||||
if (cfg == null || cfg.enabled_ == 0 || cfg.demoTerrainEnabled == 0) return;
|
||||
bool rescan = _watch == null || Time.unscaledTime >= _watchNext;
|
||||
if (rescan)
|
||||
{
|
||||
_watch = Object.FindObjectsByType<FIIsland>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
_watchNext = Time.unscaledTime + 5f;
|
||||
|
|
@ -772,16 +775,52 @@ namespace WL.Look.Farm
|
|||
for (int i = 0; i < _watch.Length; i++)
|
||||
{
|
||||
var isl = _watch[i];
|
||||
if (isl == null || isl.IsUnlocked || isl.IsBridge || !isl.gameObject.activeInHierarchy) continue;
|
||||
if (isl.transform.localScale.x >= 0.999f) continue; // 잠긴 채 아직 비활성화 전(로드 직후)은 scale 1 → 해당 없음
|
||||
if (_rising.Contains(isl)) continue;
|
||||
if (isl == null) continue;
|
||||
var mf = isl.GetComponent<MeshFilter>(); if (mf == null || mf.sharedMesh == null) continue; // 더미 제외
|
||||
if (gameObject.scene.IsValid() && isl.gameObject.scene != gameObject.scene) continue;
|
||||
// 🔴 2026-09-17 PD 화면 실측 「Coroutine couldn't be started because the game object 'Bridge (0)' is inactive!」
|
||||
// = 원본 경쟁: 칸 A 의 해금 갱신(OnIslandUnlocked → 잠긴 칸 전부 RefreshIsland → SetActive(false))이
|
||||
// 활성화 애니(1 s) 중인 칸 B 를 「잠김」으로 보고 꺼 버림 → B 의 애니 끝에 IsUnlocked·InitializeProps 코루틴이 꺼진 오브젝트에서 실패
|
||||
// → B 는 해금인데 비활성(안 보이고 못 걷는 다리). 가격판을 가장자리로 옮긴 뒤 두 판을 잇달아 밟기 쉬워져 드러났다.
|
||||
// ① 이미 깨진 칸(해금인데 비활성) = 5 s 스캔 때 복구 ② 활성화 중인 칸을 기억해 두었다가 꺼지면 그 프레임에 다시 켠다
|
||||
if (rescan && isl.IsUnlocked && !isl.gameObject.activeSelf && isl.transform.localScale.x >= 0.999f) { Recover(isl, "스캔"); continue; }
|
||||
if (_activating.Contains(isl) || isl.IsUnlocked) continue;
|
||||
if (!isl.gameObject.activeInHierarchy || isl.transform.localScale.x >= 0.999f) continue; // 잠긴 채 아직 비활성화 전(로드 직후)은 scale 1 → 해당 없음
|
||||
_activating.Add(isl);
|
||||
// ③ 🔴 해금 표식을 **애니 첫 프레임에 미리** 세운다(원본은 1 s 뒤 OnComplete 에서 세운다) → 그 사이 다른 칸의 해금 갱신이 이 칸을
|
||||
// 「잠김」으로 보고 끄는 일이 아예 없다(같은 프레임에 두 칸이 끝나는 경우도 방어). 원본이 1 s 뒤 같은 값을 다시 쓰고 OnActivated 를 낸다.
|
||||
isl.IsUnlocked = true; EarlyUnlocked++;
|
||||
if (cfg.expandHideOriginal == 0 || isl.IsBridge || _rising.Contains(isl)) continue;
|
||||
StartRise(isl);
|
||||
}
|
||||
for (int i = _activating.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var a = _activating[i];
|
||||
if (a == null) { _activating.RemoveAt(i); continue; }
|
||||
bool done = a.transform.localScale.x >= 0.999f; // 원본 DOScale 완료
|
||||
if (!a.gameObject.activeSelf) { a.gameObject.SetActive(true); Reactivated++; LastGuardLog = "활성화 중 꺼진 칸 다시 켬 — " + a.name + "@" + a.transform.position.ToString("F0"); cfg.Log(LastGuardLog); }
|
||||
if (done) _activating.RemoveAt(i);
|
||||
}
|
||||
for (int i = _rising.Count - 1; i >= 0; i--) if (_rising[i] == null || _rising[i].IsUnlocked) _rising.RemoveAt(i);
|
||||
}
|
||||
|
||||
/// <summary>해금인데 비활성으로 남은 칸을 살린다 — 켜고 원본 `InitializeProps(true)`(private · 리플렉션)를 돌려 소품·낚시터를 초기화한다.</summary>
|
||||
void Recover(FIIsland isl, string why)
|
||||
{
|
||||
isl.gameObject.SetActive(true);
|
||||
isl.transform.localScale = Vector3.one;
|
||||
if (s_initProps == null) s_initProps = typeof(FIIsland).GetMethod("InitializeProps", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||||
if (s_initProps != null)
|
||||
{
|
||||
var co = s_initProps.Invoke(isl, new object[] { true }) as IEnumerator;
|
||||
if (co != null) isl.StartCoroutine(co);
|
||||
}
|
||||
Recovered++;
|
||||
LastGuardLog = "해금·비활성 칸 복구(" + why + ") — " + isl.name + "@" + isl.transform.position.ToString("F0") + (isl.IsBridge ? " 다리" : "");
|
||||
cfg.Log(LastGuardLog);
|
||||
_dirty = true; Rebuild();
|
||||
}
|
||||
|
||||
void StartRise(FIIsland isl)
|
||||
{
|
||||
_rising.Add(isl);
|
||||
|
|
|
|||
Loading…
Reference in New Issue