615 lines
27 KiB
C#
615 lines
27 KiB
C#
using System;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace HotUpdate.UI
|
|
{
|
|
/// <summary>
|
|
/// 营地主界面:绑定 JSON Prefab 入口、网络状态和断线重连。
|
|
/// </summary>
|
|
public sealed class MainHudView : MonoBehaviour
|
|
{
|
|
public const string PrefabName = "UI_MainHUD";
|
|
|
|
TextMeshProUGUI _status;
|
|
TextMeshProUGUI _pid;
|
|
GameObject _reconnectPanel;
|
|
TextMeshProUGUI _reconnectText;
|
|
Button _reconnectBtn;
|
|
HomeCampPresenter _campPresenter;
|
|
|
|
public event Action OnReconnectClicked;
|
|
public event Action<string> OnHomeActionClicked;
|
|
|
|
public void TriggerHomeAction(string key)
|
|
{
|
|
OnHomeActionClicked?.Invoke(key);
|
|
}
|
|
|
|
public void Bind()
|
|
{
|
|
var campWorld = transform.Find("CampWorld");
|
|
if (campWorld != null)
|
|
{
|
|
_campPresenter = campWorld.GetComponent<HomeCampPresenter>();
|
|
if (_campPresenter == null)
|
|
_campPresenter = campWorld.gameObject.AddComponent<HomeCampPresenter>();
|
|
_campPresenter.SetOwner(this);
|
|
}
|
|
_status = FindTmp("Txt_Status");
|
|
_pid = FindTmp("Txt_Pid");
|
|
var panel = transform.Find("Panel_Reconnect");
|
|
_reconnectPanel = panel != null ? panel.gameObject : null;
|
|
_reconnectText = FindTmp("Txt_Reconnect");
|
|
var btnTf = transform.Find("Panel_Reconnect/Btn_Reconnect");
|
|
_reconnectBtn = btnTf != null ? btnTf.GetComponent<Button>() : null;
|
|
if (_reconnectBtn != null)
|
|
{
|
|
_reconnectBtn.onClick.RemoveAllListeners();
|
|
_reconnectBtn.onClick.AddListener(() => OnReconnectClicked?.Invoke());
|
|
}
|
|
ShowReconnect(false);
|
|
foreach (var button in GetComponentsInChildren<Button>(true))
|
|
{
|
|
if (button == _reconnectBtn || button.name == "Btn_Explorer") continue;
|
|
string key = null;
|
|
string bRaw = null;
|
|
if (button.name.StartsWith("Btn_Nav", StringComparison.Ordinal))
|
|
key = button.name.Substring(7);
|
|
else if (button.name.StartsWith("Btn_Building_", StringComparison.Ordinal))
|
|
{
|
|
bRaw = button.name.Substring(13);
|
|
if (bRaw == "MainHouse") key = "Camp";
|
|
else if (bRaw == "Fountain") key = "Rewards";
|
|
else if (bRaw == "Stall") key = "Daily";
|
|
else key = bRaw;
|
|
}
|
|
else if (button.name.StartsWith("Btn_", StringComparison.Ordinal))
|
|
key = button.name.Substring(4);
|
|
if (key == null) continue;
|
|
string actionKey = key;
|
|
string buildingRaw = bRaw;
|
|
button.onClick.RemoveAllListeners();
|
|
button.onClick.AddListener(() =>
|
|
{
|
|
if (!string.IsNullOrEmpty(buildingRaw) && _campPresenter != null && _campPresenter.HasExplorer)
|
|
{
|
|
_campPresenter.NavigateToBuilding(buildingRaw, actionKey, () => OnHomeActionClicked?.Invoke(actionKey));
|
|
}
|
|
else
|
|
{
|
|
OnHomeActionClicked?.Invoke(actionKey);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void SetStatus(string text)
|
|
{
|
|
if (_status != null) _status.text = text ?? string.Empty;
|
|
}
|
|
|
|
public void SetPid(long pid)
|
|
{
|
|
if (_pid != null) _pid.text = pid > 0 ? $"pid={pid}" : "pid=-";
|
|
}
|
|
|
|
public void SetPrototypeState(Gameplay.PrototypeSave state, Gameplay.PrototypeConfig config = null, long serverNow = 0)
|
|
{
|
|
SetText("Txt_Level", "Lv. " + state.level);
|
|
SetText("Txt_Coin", state.coins.ToString("N0"));
|
|
SetText("Txt_Diamond", "0");
|
|
SetText("Txt_Pinballs", state.balls.ToString());
|
|
SetText("Txt_TaskCaption", "教学目标");
|
|
SetText("Txt_Task", GuideTask(state, config));
|
|
SetText("Txt_AdventureHint", state.cleared ? "首通已完成 · 可重复挑战" : "首通另获糖果 40 · 不消耗体力");
|
|
if (config != null && state.schema >= 2)
|
|
{
|
|
var stage = config.Stage(state.UnlockedStage); bool complete = state.clearedStages[stage.id - 1] == 1;
|
|
SetText("Txt_Chapter", config.chapter);
|
|
SetText("Txt_Stage", stage.code + " · " + stage.name);
|
|
SetText("Txt_Reward_coin", complete ? "已领取" : stage.reward.coins.ToString());
|
|
SetText("Txt_Reward_pinball", complete ? "已领取" : stage.reward.balls.ToString());
|
|
SetText("Txt_AdventureHint", complete ? "本轮五关已完成 · 可免费重试" : "首通另获糖果 " + stage.reward.candy + " · 不消耗体力");
|
|
}
|
|
foreach (var child in GetComponentsInChildren<Transform>(true))
|
|
if (child.name.StartsWith("Dot_", StringComparison.Ordinal)) child.gameObject.SetActive(false);
|
|
if (state.mails != null)
|
|
foreach (var child in GetComponentsInChildren<Transform>(true))
|
|
if (child.name == "Dot_Mail") child.gameObject.SetActive(Array.Exists(state.mails, m => !m.expired && serverNow < m.expiresAt && (!m.read || !m.claimed && m.HasAttachment)));
|
|
if (config?.rewards?.chests != null && state.chests != null)
|
|
foreach (var child in GetComponentsInChildren<Transform>(true))
|
|
if (child.name == "Dot_Rewards") child.gameObject.SetActive(state.chestDailyClaimed == 0 || Array.Exists(state.chests, n => n > 0));
|
|
if (state.schema >= 5 && config?.daily != null)
|
|
{
|
|
int ready = 0; bool signin = false;
|
|
for (int i = 0; i < 3; i++) if (state.dailyClaimed[i] == 0 && state.dailyProgress[i] >= config.daily.tasks[i].target) ready++;
|
|
for (int i = 0; i < state.signinDays; i++) if (state.signinClaimed[i] == 0) signin = true;
|
|
foreach (var child in GetComponentsInChildren<Transform>(true))
|
|
if (child.name == "Dot_Tasks") child.gameObject.SetActive(ready > 0 || state.cleared && !state.goalClaimed);
|
|
else if (child.name == "Dot_Daily") child.gameObject.SetActive(signin);
|
|
if (state.goalClaimed)
|
|
{
|
|
SetText("Txt_TaskCaption", "每日目标");
|
|
SetText("Txt_Task", ready > 0 ? ready + "项奖励可领取" : "查看今日任务进度");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static string GuideTask(Gameplay.PrototypeSave state, Gameplay.PrototypeConfig config = null)
|
|
{
|
|
if (state.goalClaimed) return "教学目标已完成";
|
|
if (state.cleared) return "领取首通目标奖励";
|
|
if (state.shots == 0) return "发射第一枚弹珠";
|
|
if (!state.captureTutorialDone && state.tickets > 0) return "用捕捉券获得芽芽";
|
|
if (state.level < 2)
|
|
{
|
|
int cost = (config?.economy?.trainCost ?? 30) * state.level;
|
|
if (state.candy < cost) return state.balls > 0 ? "发射弹珠,补足训练糖果" : "领取免费补给,再收集训练糖果";
|
|
return "用糖果做一次共享训练";
|
|
}
|
|
return "布阵并完成教学关 1-1";
|
|
}
|
|
|
|
void SetText(string name, string value)
|
|
{
|
|
var label = FindTmp(name);
|
|
if (label != null) label.text = value;
|
|
}
|
|
|
|
public void ShowReconnect(bool visible, string message = null)
|
|
{
|
|
if (_reconnectPanel != null) _reconnectPanel.SetActive(visible);
|
|
if (visible && _reconnectText != null && !string.IsNullOrEmpty(message))
|
|
_reconnectText.text = message;
|
|
}
|
|
|
|
TextMeshProUGUI FindTmp(string path)
|
|
{
|
|
var t = transform.Find(path);
|
|
if (t == null)
|
|
{
|
|
// 深层查找
|
|
var all = GetComponentsInChildren<TextMeshProUGUI>(true);
|
|
foreach (var c in all)
|
|
if (c.name == path) return c;
|
|
return null;
|
|
}
|
|
return t.GetComponent<TextMeshProUGUI>();
|
|
}
|
|
}
|
|
|
|
/// <summary>Displays the authored camp scene behind the existing HUD.</summary>
|
|
public sealed class HomeCampPresenter : MonoBehaviour
|
|
{
|
|
const string ScenePath = "Assets/Bundles/Scenes/Town/HomeCamp/HomeCamp.unity";
|
|
const float ReferenceAspect = 1080f / 1920f;
|
|
RawImage _image;
|
|
Camera _camera;
|
|
GameObject _sceneRoot;
|
|
Transform _backdrop;
|
|
RenderTexture _texture;
|
|
YooAsset.SceneHandle _handle;
|
|
bool _loading, _destroyed;
|
|
GameObject _explorerStage, _explorerButton;
|
|
|
|
// Building transform references and UI bubble anchors
|
|
Transform _mainHouse, _houseLeft, _houseRight, _fountain, _stall;
|
|
RectTransform _bubbleMainHouse, _bubbleShop, _bubblePets, _bubbleFountain, _bubbleStall;
|
|
|
|
// Protagonist Explorer references
|
|
Transform _explorer;
|
|
HotUpdate.Gameplay.CampExplorerController _explorerController;
|
|
|
|
MainHudView _owner;
|
|
public void SetOwner(MainHudView owner) => _owner = owner;
|
|
public bool HasExplorer => _explorerController != null;
|
|
|
|
struct BuildingEntrance
|
|
{
|
|
public Vector3 position;
|
|
public Vector3 facing;
|
|
}
|
|
|
|
static readonly System.Collections.Generic.Dictionary<string, BuildingEntrance> BuildingEntrances =
|
|
new System.Collections.Generic.Dictionary<string, BuildingEntrance>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
{ "MainHouse", new BuildingEntrance { position = new Vector3(0f, 0f, 3.5f), facing = Vector3.forward } },
|
|
{ "Shop", new BuildingEntrance { position = new Vector3(-3.8f, 0f, 1.8f), facing = new Vector3(-1f, 0f, 0.3f) } },
|
|
{ "HouseLeft", new BuildingEntrance { position = new Vector3(-3.8f, 0f, 1.8f), facing = new Vector3(-1f, 0f, 0.3f) } },
|
|
{ "Pets", new BuildingEntrance { position = new Vector3(3.8f, 0f, 1.8f), facing = new Vector3(1f, 0f, 0.3f) } },
|
|
{ "HouseRight", new BuildingEntrance { position = new Vector3(3.8f, 0f, 1.8f), facing = new Vector3(1f, 0f, 0.3f) } },
|
|
{ "Fountain", new BuildingEntrance { position = new Vector3(0f, 0f, -2.6f), facing = Vector3.forward } },
|
|
{ "Stall", new BuildingEntrance { position = new Vector3(-2.8f, 0f, -3.2f), facing = Vector3.left } },
|
|
};
|
|
|
|
string _currentNavBuilding;
|
|
|
|
public void NavigateToBuilding(string buildingKey, string actionKey, Action onArrive)
|
|
{
|
|
if (_explorerController == null)
|
|
{
|
|
onArrive?.Invoke();
|
|
return;
|
|
}
|
|
|
|
// 再次点击同一建筑则立即快速进入
|
|
if (_currentNavBuilding == buildingKey && _explorerController.IsMoving)
|
|
{
|
|
_currentNavBuilding = null;
|
|
_explorerController.Stop();
|
|
onArrive?.Invoke();
|
|
return;
|
|
}
|
|
|
|
if (BuildingEntrances.TryGetValue(buildingKey, out var entrance))
|
|
{
|
|
_currentNavBuilding = buildingKey;
|
|
_explorerController.MoveTo(entrance.position, () =>
|
|
{
|
|
_currentNavBuilding = null;
|
|
onArrive?.Invoke();
|
|
}, entrance.facing);
|
|
}
|
|
else
|
|
{
|
|
onArrive?.Invoke();
|
|
}
|
|
}
|
|
|
|
public bool Ready => _camera != null && _texture != null;
|
|
public bool Loading => _loading;
|
|
|
|
void Awake()
|
|
{
|
|
_image = GetComponent<RawImage>();
|
|
if (_image != null) _image.enabled = false;
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
if (_sceneRoot != null) { _sceneRoot.SetActive(true); Resize(); UpdateBuildingAnchors(); }
|
|
else if (!_loading) Load();
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
if (_sceneRoot != null) _sceneRoot.SetActive(false);
|
|
if (_image != null) _image.enabled = false;
|
|
}
|
|
|
|
void Load()
|
|
{
|
|
_loading = true;
|
|
if (YooAsset.YooAssets.IsInitialized && YooAsset.YooAssets.TryGetPackage("Art", out var package) &&
|
|
package.InitializeStatus == YooAsset.EOperationStatus.Succeeded && package.IsLocationValid("HomeCamp"))
|
|
{
|
|
_handle = package.LoadSceneAsync("HomeCamp", UnityEngine.SceneManagement.LoadSceneMode.Additive);
|
|
// The shipped October shell stripped SceneHandle.Completed and UnloadSceneAsync.
|
|
// Poll inherited HandleBase members on the persistent UI host so hiding the HUD
|
|
// during a download does not stop completion/cleanup.
|
|
MonoBehaviour host = UiManager.Instance != null ? (MonoBehaviour)UiManager.Instance : this;
|
|
host.StartCoroutine(WaitForScene(_handle));
|
|
return;
|
|
}
|
|
#if UNITY_EDITOR
|
|
var operation = UnityEditor.SceneManagement.EditorSceneManager.LoadSceneAsyncInPlayMode(
|
|
ScenePath, new UnityEngine.SceneManagement.LoadSceneParameters(UnityEngine.SceneManagement.LoadSceneMode.Additive));
|
|
if (operation != null)
|
|
{
|
|
operation.completed += _ => {
|
|
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByPath(ScenePath);
|
|
if (_destroyed) { if (scene.IsValid() && scene.isLoaded) UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(scene); return; }
|
|
Setup(scene);
|
|
};
|
|
return;
|
|
}
|
|
#endif
|
|
_loading = false;
|
|
Debug.LogWarning("[HomeCamp] Scene unavailable in Art; retaining the illustrated background.");
|
|
}
|
|
|
|
System.Collections.IEnumerator WaitForScene(YooAsset.SceneHandle handle)
|
|
{
|
|
while (YooAsset.YooAssets.IsInitialized && handle.IsValid && !handle.IsDone) yield return null;
|
|
if (YooAsset.YooAssets.IsInitialized && handle.IsValid) Loaded(handle);
|
|
}
|
|
|
|
static void ReleaseScene(YooAsset.SceneHandle handle)
|
|
{
|
|
if (!YooAsset.YooAssets.IsInitialized || !handle.IsValid) return;
|
|
if (handle.Status == YooAsset.EOperationStatus.Succeeded)
|
|
{
|
|
var scene = handle.SceneObject;
|
|
// YooAsset listens to sceneUnloaded and releases its scene handles there.
|
|
// Using Unity's retained API also works with the existing AOT shell.
|
|
if (scene.IsValid() && scene.isLoaded)
|
|
{
|
|
UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(scene);
|
|
return;
|
|
}
|
|
}
|
|
handle.Release();
|
|
}
|
|
|
|
void Loaded(YooAsset.SceneHandle handle)
|
|
{
|
|
if (_destroyed) { ReleaseScene(handle); return; }
|
|
if (handle.Status != YooAsset.EOperationStatus.Succeeded)
|
|
{
|
|
Debug.LogWarning("[HomeCamp] Scene load failed: " + handle.Error);
|
|
handle.Release(); _handle = null; _loading = false; return;
|
|
}
|
|
Setup(handle.SceneObject);
|
|
}
|
|
|
|
void Setup(UnityEngine.SceneManagement.Scene scene)
|
|
{
|
|
_loading = false;
|
|
if (scene.IsValid() && scene.isLoaded)
|
|
foreach (var root in scene.GetRootGameObjects())
|
|
if (root.name == "HomeCamp") { _sceneRoot = root; break; }
|
|
if (_sceneRoot != null)
|
|
{
|
|
_camera = _sceneRoot.GetComponentInChildren<Camera>(true);
|
|
_backdrop = _sceneRoot.transform.Find("DistantLakeWaterfallCastle");
|
|
_mainHouse = _sceneRoot.transform.Find("MainHouse");
|
|
_houseLeft = _sceneRoot.transform.Find("HouseLeft");
|
|
_houseRight = _sceneRoot.transform.Find("HouseRight");
|
|
_fountain = _sceneRoot.transform.Find("Fountain");
|
|
_stall = _sceneRoot.transform.Find("StallRight");
|
|
}
|
|
if (_camera == null || _image == null)
|
|
{
|
|
Debug.LogWarning("[HomeCamp] Scene camera missing; retaining the illustrated background.");
|
|
if (_sceneRoot != null) _sceneRoot.SetActive(false);
|
|
return;
|
|
}
|
|
_camera.enabled = false;
|
|
// The old guide is a separate 2D camera overlay and does not match the new scene's scale or ground.
|
|
_explorerStage = transform.parent.Find("ExplorerStage")?.gameObject;
|
|
_explorerButton = transform.parent.Find("Btn_Explorer")?.gameObject;
|
|
if (_explorerStage != null) _explorerStage.SetActive(false);
|
|
if (_explorerButton != null) _explorerButton.SetActive(false);
|
|
|
|
BindBuildingBubbles();
|
|
|
|
// Bind Explorer protagonist
|
|
if (_sceneRoot != null)
|
|
{
|
|
_explorer = _sceneRoot.transform.Find("Explorer");
|
|
if (_explorer == null)
|
|
{
|
|
var expPrefab = Resources.Load<GameObject>("Explorer")
|
|
#if UNITY_EDITOR
|
|
?? UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/Characters/Human/Explorer/Explorer.prefab")
|
|
#endif
|
|
;
|
|
if (expPrefab != null)
|
|
{
|
|
var expInst = Instantiate(expPrefab, _sceneRoot.transform);
|
|
expInst.name = "Explorer";
|
|
expInst.transform.position = new Vector3(0f, 0f, -3.5f);
|
|
expInst.transform.rotation = Quaternion.LookRotation(Vector3.forward, Vector3.up);
|
|
_explorer = expInst.transform;
|
|
}
|
|
}
|
|
if (_explorer != null)
|
|
{
|
|
_explorerController = _explorer.GetComponent<HotUpdate.Gameplay.CampExplorerController>();
|
|
if (_explorerController == null)
|
|
_explorerController = _explorer.gameObject.AddComponent<HotUpdate.Gameplay.CampExplorerController>();
|
|
}
|
|
}
|
|
|
|
_sceneRoot.SetActive(isActiveAndEnabled);
|
|
if (isActiveAndEnabled)
|
|
{
|
|
Resize();
|
|
UpdateBuildingAnchors();
|
|
}
|
|
}
|
|
|
|
void BindBuildingBubbles()
|
|
{
|
|
var hud = transform.parent;
|
|
if (hud == null) return;
|
|
var group = hud.Find("Group_CampBuildings");
|
|
if (group == null) return;
|
|
_bubbleMainHouse = group.Find("Btn_Building_MainHouse") as RectTransform;
|
|
_bubbleShop = group.Find("Btn_Building_Shop") as RectTransform;
|
|
_bubblePets = group.Find("Btn_Building_Pets") as RectTransform;
|
|
_bubbleFountain = group.Find("Btn_Building_Fountain") as RectTransform;
|
|
_bubbleStall = group.Find("Btn_Building_Stall") as RectTransform;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (_camera != null && _image != null && _image.enabled)
|
|
{
|
|
HandleGroundClick();
|
|
}
|
|
}
|
|
|
|
void HandleGroundClick()
|
|
{
|
|
if (_camera == null || _explorerController == null) return;
|
|
|
|
bool isClick = false;
|
|
Vector2 screenPos = Vector2.zero;
|
|
|
|
if (Input.GetMouseButtonDown(0))
|
|
{
|
|
if (UnityEngine.EventSystems.EventSystem.current == null || !UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
|
{
|
|
isClick = true;
|
|
screenPos = Input.mousePosition;
|
|
}
|
|
}
|
|
else if (Input.touchCount > 0)
|
|
{
|
|
var touch = Input.GetTouch(0);
|
|
if (touch.phase == TouchPhase.Began)
|
|
{
|
|
if (UnityEngine.EventSystems.EventSystem.current == null || !UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject(touch.fingerId))
|
|
{
|
|
isClick = true;
|
|
screenPos = touch.position;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isClick) return;
|
|
|
|
Ray ray = _camera.ScreenPointToRay(screenPos);
|
|
int layerMask = 1 << 29; // Layer 29 (HomeCamp)
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 150f, layerMask))
|
|
{
|
|
// 优先检查是否点击中 3D 建筑本体
|
|
Transform cur = hit.collider.transform;
|
|
string matchedBuilding = null;
|
|
while (cur != null && cur != _sceneRoot.transform)
|
|
{
|
|
if (BuildingEntrances.ContainsKey(cur.name))
|
|
{
|
|
matchedBuilding = cur.name;
|
|
break;
|
|
}
|
|
cur = cur.parent;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(matchedBuilding))
|
|
{
|
|
string actionKey = matchedBuilding;
|
|
if (matchedBuilding == "MainHouse") actionKey = "Camp";
|
|
else if (matchedBuilding == "Fountain") actionKey = "Rewards";
|
|
else if (matchedBuilding == "Stall") actionKey = "Daily";
|
|
else if (matchedBuilding == "HouseLeft") actionKey = "Shop";
|
|
else if (matchedBuilding == "HouseRight") actionKey = "Pets";
|
|
NavigateToBuilding(matchedBuilding, actionKey, () => _owner?.TriggerHomeAction(actionKey));
|
|
return;
|
|
}
|
|
|
|
// 点击普通地表或平整可走区域
|
|
if (hit.collider.name.IndexOf("Ground", StringComparison.OrdinalIgnoreCase) >= 0 || hit.point.y < 0.8f)
|
|
{
|
|
_currentNavBuilding = null;
|
|
_explorerController.MoveTo(hit.point);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
if (_camera != null && _image != null && _image.enabled)
|
|
{
|
|
Resize();
|
|
UpdateBuildingAnchors();
|
|
}
|
|
}
|
|
|
|
void Resize()
|
|
{
|
|
if (_camera == null || _image == null || Screen.width <= 0 || Screen.height <= 0) return;
|
|
float aspect = (float)Screen.width / Screen.height;
|
|
// Preserve the horizontal framing on long phones while showing more vertically.
|
|
_camera.fieldOfView = 2f * Mathf.Atan(Mathf.Tan(25f * Mathf.Deg2Rad) * Mathf.Max(1f, ReferenceAspect / aspect)) * Mathf.Rad2Deg;
|
|
|
|
// Dynamically scale and frame backdrop to avoid gaps on ultra-tall or ultra-wide displays
|
|
if (_backdrop != null)
|
|
{
|
|
const float dist = 75f;
|
|
float frustumHalfH = dist * Mathf.Tan(_camera.fieldOfView * Mathf.Deg2Rad * 0.5f);
|
|
float frustumHalfW = frustumHalfH * aspect;
|
|
float bdW = Mathf.Max(72f, frustumHalfW * 2f * 1.15f);
|
|
float bdH = Mathf.Max(56f, frustumHalfH * 1.35f);
|
|
float topHeight = frustumHalfH * 1.08f;
|
|
_backdrop.localScale = new Vector3(bdW, bdH, 1f);
|
|
_backdrop.position = _camera.transform.position + _camera.transform.forward * dist + _camera.transform.up * (topHeight - bdH * 0.5f);
|
|
_backdrop.rotation = _camera.transform.rotation;
|
|
}
|
|
|
|
int width = Mathf.Min(Screen.width, 720);
|
|
int height = Mathf.Max(1, Mathf.RoundToInt(width / aspect));
|
|
if (_texture == null || _texture.width != width || _texture.height != height)
|
|
{
|
|
_camera.targetTexture = null;
|
|
if (_texture != null) { _texture.Release(); Destroy(_texture); }
|
|
_texture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32) { name = "HomeCampPreview" };
|
|
_texture.Create();
|
|
_camera.targetTexture = _texture;
|
|
_image.texture = _texture;
|
|
}
|
|
_camera.enabled = true;
|
|
_image.enabled = true;
|
|
}
|
|
|
|
void UpdateBuildingAnchors()
|
|
{
|
|
if (_camera == null) return;
|
|
var hud = transform.parent as RectTransform;
|
|
if (hud == null) return;
|
|
Vector2 canvasSize = hud.rect.size;
|
|
if (canvasSize.x <= 0 || canvasSize.y <= 0) return;
|
|
|
|
UpdateAnchor(_bubbleMainHouse, _mainHouse, new Vector3(0, 6.7f, 7.2f), canvasSize, 0);
|
|
UpdateAnchor(_bubbleShop, _houseLeft, new Vector3(-6.8f, 4.0f, 3.6f), canvasSize, 1);
|
|
UpdateAnchor(_bubblePets, _houseRight, new Vector3(7.5f, 4.2f, 4.0f), canvasSize, 2);
|
|
UpdateAnchor(_bubbleFountain, _fountain, new Vector3(0, 2.5f, 0.8f), canvasSize, 3);
|
|
UpdateAnchor(_bubbleStall, _stall, new Vector3(4.5f, 2.6f, 2.8f), canvasSize, 4);
|
|
}
|
|
|
|
void UpdateAnchor(RectTransform bubble, Transform targetTf, Vector3 anchorPos, Vector2 canvasSize, int bobIndex)
|
|
{
|
|
if (bubble == null) return;
|
|
Vector3 worldPos = targetTf != null ? targetTf.position : anchorPos;
|
|
worldPos.y = anchorPos.y;
|
|
worldPos.x = anchorPos.x;
|
|
worldPos.z = anchorPos.z;
|
|
|
|
Vector3 vp = _camera.WorldToViewportPoint(worldPos);
|
|
if (vp.z <= 0 || vp.x < -0.2f || vp.x > 1.2f || vp.y < -0.2f || vp.y > 1.2f)
|
|
{
|
|
if (bubble.gameObject.activeSelf) bubble.gameObject.SetActive(false);
|
|
return;
|
|
}
|
|
if (!bubble.gameObject.activeSelf) bubble.gameObject.SetActive(true);
|
|
|
|
float x = (vp.x - 0.5f) * canvasSize.x;
|
|
float y = (vp.y - 0.5f) * canvasSize.y;
|
|
|
|
float bob = Mathf.Sin(Time.time * 2.5f + bobIndex * 1.2f) * 2.5f;
|
|
y += bob;
|
|
|
|
float halfW = canvasSize.x * 0.5f;
|
|
float halfH = canvasSize.y * 0.5f;
|
|
float bubbleHalfW = bubble.rect.width * 0.5f;
|
|
x = Mathf.Clamp(x, -halfW + bubbleHalfW + 15f, halfW - bubbleHalfW - 15f);
|
|
y = Mathf.Clamp(y, -halfH + 280f, halfH - 180f);
|
|
|
|
bubble.anchoredPosition = new Vector2(x, y);
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
_destroyed = true;
|
|
if (_image != null) _image.texture = null;
|
|
if (_camera != null) _camera.targetTexture = null;
|
|
if (_texture != null) { _texture.Release(); Destroy(_texture); }
|
|
if (_handle != null && !_loading && YooAsset.YooAssets.IsInitialized && _handle.IsValid && _handle.IsDone)
|
|
{
|
|
ReleaseScene(_handle);
|
|
_handle = null;
|
|
}
|
|
#if UNITY_EDITOR
|
|
else if (_handle == null && !_loading)
|
|
{
|
|
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByPath(ScenePath);
|
|
if (scene.IsValid() && scene.isLoaded) UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(scene);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|