using System; using System.IO; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using VS.Sim; // 진입점 — 데이터 로드 · 저장 · 게임 진행 · 입력 · 화면 갱신 namespace VS.View { public partial class GameApp : MonoBehaviour { public static GameApp Instance { get; private set; } public Tables T; public Game G; public Canvas Canvas; public RectTransform Root, PopupLayer, FxLayer; public Camera Cam; public static string SavePathOverride; // 렌더 검사용 임시 세이브 public static Func ClockOverride; public static RenderTexture RenderTarget; // 렌더 검사용 public bool Headless; // EditorRender: Update 진행 끔 float saveTimer, uiTimer; string SavePath => SavePathOverride ?? Path.Combine(Application.persistentDataPath, "vs_save.json"); public static string ReadData(string name) { var ta = Resources.Load("Data/" + name); return ta != null ? ta.text : null; } bool inited; void Awake() { if (Application.isPlaying) Init(); } public static void Kill(GameObject go) { if (go == null) return; if (Application.isPlaying) Destroy(go); else DestroyImmediate(go); } public void Init() { if (inited) return; inited = true; Instance = this; Application.targetFrameRate = 60; T = Tables.Load(ReadData); SaveData s = null; try { if (File.Exists(SavePath)) s = JsonUtility.FromJson(File.ReadAllText(SavePath)); } catch (Exception e) { Debug.LogWarning("[VS] save load failed: " + e.Message); } G = new Game(T, s, ClockOverride); BuildCanvas(); BuildHud(); G.OfflineCheck(); HookEvents(); RefreshAll(); } void BuildCanvas() { var camGo = new GameObject("UICamera"); camGo.transform.SetParent(transform, false); Cam = camGo.AddComponent(); Cam.clearFlags = CameraClearFlags.SolidColor; Cam.backgroundColor = new Color32(40, 28, 56, 255); Cam.orthographic = true; Cam.cullingMask = 1 << 5; Cam.depth = 0; Cam.nearClipPlane = -10; Cam.farClipPlane = 1000; if (RenderTarget != null) Cam.targetTexture = RenderTarget; var cgo = new GameObject("Canvas", typeof(RectTransform)); cgo.layer = 5; cgo.transform.SetParent(transform, false); Canvas = cgo.AddComponent(); Canvas.renderMode = RenderMode.ScreenSpaceCamera; Canvas.worldCamera = Cam; Canvas.planeDistance = 100; var sc = cgo.AddComponent(); sc.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; sc.referenceResolution = new Vector2(1080, 1920); sc.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight; sc.matchWidthOrHeight = 0.5f; cgo.AddComponent(); Root = UI.Rect("Root", cgo.transform).Fill(); if (FindFirstObjectByType() == null) { var es = new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule)); es.transform.SetParent(transform, false); } } void Update() { if (Headless) return; float dt = Mathf.Min(Time.unscaledDeltaTime, 0.25f); G.Tick(dt); TickBattle(dt); UpdateInput(dt); UpdateHudFx(dt); uiTimer -= dt; if (uiTimer <= 0f) { uiTimer = 0.1f; RefreshAll(); } saveTimer += dt; if (saveTimer > 10f) { saveTimer = 0; SaveNow(); } } /// 렌더 검사용 — 게임 진행 + 화면 연출을 dt 단위로 n 번 public void DebugTick(float dt, int n) { for (int i = 0; i < n; i++) { G.Tick(dt); TickBattle(dt); UpdateHudFx(dt); } RefreshAll(); } public void SaveNow() { try { G.Commit(); File.WriteAllText(SavePath, JsonUtility.ToJson(G.S)); } catch (Exception e) { Debug.LogWarning("[VS] save failed: " + e.Message); } } void OnApplicationPause(bool p) { if (G == null) return; if (p) { G.MarkAfkStamp(); SaveNow(); } else { G.OfflineCheck(); afkShownThisSession = false; } } void OnApplicationQuit() { if (G == null) return; G.MarkAfkStamp(); SaveNow(); } public void ResetSave() { try { if (File.Exists(SavePath)) File.Delete(SavePath); } catch { } G = new Game(T, new SaveData(), ClockOverride); HookEvents(); CloseAllPopups(); RebuildStage(); RefreshAll(); } // ---------------- 입력: 무대 탭 · 길게 누르기(원작 OnOffActiveHold) ---------------- bool pressing; float pressTime; float holdAcc; bool holdActive; public void OnStagePointerDown(Vector2 screenPos) { if (PopupOpen) return; pressing = true; pressTime = 0; holdAcc = 0; holdActive = false; DoTap(screenPos); } public void OnStagePointerUp() { pressing = false; holdActive = false; } void UpdateInput(float dt) { if (!pressing) return; pressTime += dt; var r = T.Cfg.remote; if (!r.OnOffActiveHold) return; if (!holdActive && pressTime >= r.HoldActivateSeconds) { holdActive = true; holdAcc = 0; } if (holdActive) { holdAcc += dt; float iv = 1f / Mathf.Max(1, r.HoldTapsPerSecond); while (holdAcc >= iv) { holdAcc -= iv; DoTap(Input.mousePosition); } } } void DoTap(Vector2 screenPos) { bool coin = G.Tap(); OnTapVisual(screenPos, coin); } } }