Files
AIGame/AIClient/Assets/HotUpdate/Scripts/UI/HomeExplorerPresenter.cs
T

191 lines
7.6 KiB
C#

using System.Collections;
using HotUpdate.Gameplay;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace HotUpdate.UI
{
/// <summary>Walks the camp guide over the camp painting and speaks only after a tap.</summary>
public sealed class HomeExplorerPresenter : MonoBehaviour
{
const int Layer = 31;
static readonly string[] Lines =
{
"喷泉今天的水声很好听。",
"芽芽他们在营地里等你。",
"想再去弹珠台转转吗?",
"这条石路是我每天散步的地方。",
"花开得正好,要不要一起看看?",
"下一关的路上好像有新朋友。",
"训练之后,大家都会更有精神。",
"我在这儿看着营地,你去冒险吧。"
};
UiPrefabLoader _loader;
GameObject _stage;
Camera _camera;
RenderTexture _texture;
RawImage _image;
bool _booting;
Transform _actor;
RectTransform _button;
RectTransform _bubble;
TextMeshProUGUI _line;
float _hideAt;
int _last = -1;
bool _hooked;
Vector3 _wander;
public bool Ready => _actor != null;
public bool Talking => _bubble != null && _bubble.gameObject.activeSelf;
public string Line => _line != null ? _line.text : "";
void OnEnable()
{
_image = GetComponent<RawImage>();
if (_image != null) _image.enabled = _camera != null;
if (_camera != null) _camera.enabled = true;
else if (!_booting) StartCoroutine(Boot());
}
void OnDisable()
{
if (_camera != null) _camera.enabled = false;
if (_image != null) _image.enabled = false;
if (_bubble != null) _bubble.gameObject.SetActive(false);
}
void OnDestroy()
{
if (_button != null) _button.GetComponent<Button>().onClick.RemoveListener(Speak);
if (_image != null) _image.texture = null;
if (_camera != null) _camera.targetTexture = null;
if (_texture != null)
{
_texture.Release();
Destroy(_texture);
}
if (_stage != null) Destroy(_stage);
_loader?.Dispose();
}
IEnumerator Boot()
{
_booting = true;
try { yield return BootCore(); }
finally { _booting = false; }
}
IEnumerator BootCore()
{
// The serialized material makes the shader a real AssetBundle dependency.
// Never display the opaque camera clear color when that dependency is unavailable.
if (_image == null || _image.material == null || _image.material.shader == null ||
_image.material.shader.name != "AIGame/HomeExplorerCutout" || !_image.material.shader.isSupported)
{
Debug.LogWarning("[HomeExplorer] Preview material unavailable; keeping the camp background visible.");
yield break;
}
var root = transform.parent;
_button = root.Find("Btn_Explorer") as RectTransform;
_bubble = root.Find("Panel_ExplorerTalk") as RectTransform;
_line = root.Find("Panel_ExplorerTalk/Txt_ExplorerTalk").GetComponent<TextMeshProUGUI>();
_bubble.gameObject.SetActive(false);
yield return null;
GameObject prefab = null;
_loader = new UiPrefabLoader(null, "Assets/Bundles/Characters/Human/CampGuideStudio");
yield return _loader.LoadAsync("CampGuideStudio_Rigged", loaded => prefab = loaded);
if (prefab == null || _stage != null || !isActiveAndEnabled) yield break;
_stage = new GameObject("HomeExplorerStage");
_stage.transform.position = new Vector3(40f, 0f, 0f);
_actor = Instantiate(prefab, _stage.transform).transform;
_actor.localPosition = Vector3.zero;
_actor.localRotation = Quaternion.Euler(0f, 180f, 0f);
SetLayer(_actor);
var lightGo = new GameObject("Light");
lightGo.transform.SetParent(_stage.transform, false);
var light = lightGo.AddComponent<Light>();
light.type = LightType.Directional;
light.intensity = 1.15f;
light.cullingMask = 1 << Layer;
light.transform.rotation = Quaternion.Euler(38f, -28f, 0f);
SetLayer(lightGo.transform);
var camGo = new GameObject("Camera");
camGo.transform.SetParent(_stage.transform, false);
_camera = camGo.AddComponent<Camera>();
_camera.clearFlags = CameraClearFlags.SolidColor;
// Read the shipped material: a hot-update DLL may run with an older Art bundle.
_camera.backgroundColor = _image.material.GetColor("_Key");
_camera.cullingMask = 1 << Layer;
_camera.orthographic = true;
_camera.orthographicSize = 1.45f;
_camera.nearClipPlane = 0.05f;
_camera.farClipPlane = 30f;
_camera.allowHDR = false;
_camera.depth = -50;
_camera.transform.localPosition = new Vector3(0.15f, 1.7f, -3.6f);
_camera.transform.LookAt(_stage.transform.TransformPoint(0f, 0.7f, 0f));
_texture = new RenderTexture(512, 512, 16, RenderTextureFormat.ARGB32);
_texture.Create();
_camera.targetTexture = _texture;
_image.texture = _texture;
_image.enabled = true;
_bubble.gameObject.SetActive(false);
if (!_hooked && _button != null)
{
_button.GetComponent<Button>().onClick.AddListener(Speak);
_hooked = true;
}
}
void Update()
{
if (_actor == null) return;
float t = Time.time * 0.45f;
Vector3 next = new Vector3(Mathf.Sin(t) * 1.05f, 0f, Mathf.Sin(t * 0.5f) * 0.28f);
Vector3 dir = next - _wander;
_wander = next;
_actor.localPosition = next;
if (dir.sqrMagnitude > 0.000001f)
_actor.localRotation = Quaternion.LookRotation(new Vector3(dir.x, 0f, dir.z)) * Quaternion.Euler(0f, 180f, 0f);
}
void LateUpdate()
{
if (_actor == null || _camera == null || _button == null) return;
var stage = (RectTransform)transform;
Vector3 view = _camera.WorldToViewportPoint(_actor.position + Vector3.up * 0.75f);
if (view.z < 0f) return;
Vector3 world = stage.TransformPoint(new Vector3((view.x - 0.5f) * stage.rect.width, (view.y - 0.5f) * stage.rect.height, 0f));
_button.position = world;
if (_bubble.gameObject.activeSelf)
{
_bubble.position = _button.position + new Vector3(0f, 150f * _button.lossyScale.y, 0f);
if (Time.unscaledTime >= _hideAt) _bubble.gameObject.SetActive(false);
}
}
public void Speak()
{
if (_line == null || _bubble == null) return;
int index = Random.Range(0, Lines.Length);
if (Lines.Length > 1 && index == _last) index = (index + 1) % Lines.Length;
_last = index;
_line.text = Lines[index];
_bubble.gameObject.SetActive(true);
_hideAt = Time.unscaledTime + 3.5f;
}
static void SetLayer(Transform root)
{
root.gameObject.layer = Layer;
foreach (Transform child in root) SetLayer(child);
}
}
}