Files
aibis-dream/Assets/Scripts/FixSystem/HeatMap/HeatMapController.cs
T

423 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using System.Collections;
using AibisDream;
using AibisDream.FixSystem;
using AibisDream.Kit;
public class HeatMapController : MonoBehaviour
{
public Material heatMaterial; // 需要传递数据的材质
public Transform[] Pos;
public Button coolingButton;
private BodyModuleSystem bodyModuleSystem;
public GameObject heatmapGameobject;
public RectTransform imageToMove; // 需要移动的Image
public Transform imageToRotate; // 需要旋转的Image
[Header("拖拽")]
[Tooltip("在不改变视觉和布局的前提下,向左右/上下扩展拖拽命中范围。")]
[SerializeField] private Vector2 dragHitAreaExpansion = new Vector2(5.2f, 0.3f);
[SerializeField, Min(0f)] private float fanStopDuration = 0.15f;
[Tooltip("相对 Sprite 矩形中心的额外旋转轴偏移(本地空间,单位与 Transform 一致)。用于校准扇毂不在图心时的偏心。")]
[SerializeField] private Vector2 fanPivotLocalOffset;
[Header("低温背景")]
[SerializeField] private Color coldBaseColor = new Color(0.005f, 0.02f, 0.55f, 1f);
[SerializeField] private Color coldHighlightColor = new Color(0f, 0.3f, 1f, 1f);
[SerializeField, Min(0.01f)] private float coldNoiseScale = 1.8f;
[SerializeField, Range(0f, 1f)] private float coldNoiseStrength = 0.45f;
public Vector3 inPos; // Heatmap进入位置
public Vector3 outPos; // Heatmap退出位置
private Vector3 imageInitialPos; // Image的初始位置
[HideInInspector] private Vector4[] points = new Vector4[7]; // 存储6个点的数组,(x, y, z) = center, w = temperature
[HideInInspector] private Vector4[] properties = new Vector4[7]; // 存储6个盒子大小的数组, (x,y) = boxsize
private float uftempture = 1;
private float backtemture = 1;
private float modulesTempture = 1;
private float tempture = 1;
private Tween _temptureTween;
private Tween _fanSpeedTween;
private float _fanAngularSpeed;
private SpriteRenderer _fanRenderer;
private Vector3 _fanLocalPivot; // 相对 imageToRotate 的本地旋转轴
private bool _fanPivotReady;
private const float FanAngularSpeed = 1800f;
/// <summary>热力图面板当前是否在显示(用于 SetHeat 决定硬切还是平滑)。</summary>
public bool IsHeatMapVisible =>
heatmapGameobject != null && heatmapGameobject.activeInHierarchy;
public float Tempture
{
get => tempture;
set
{
if (tempture != value)
{
tempture = value;
SetDataToMaterial(); // 调用方法更新材质数据
}
}
}
public void ShowHeatMap()
{
StartCoroutine(ShowHeatMapCoroutine());
}
private IEnumerator ShowHeatMapCoroutine()
{
// 初始化Heatmap位置
heatmapGameobject.transform.localPosition = outPos;
heatmapGameobject.SetActive(true);
SetDataToMaterial();
heatmapGameobject.transform.DOLocalMove(inPos, 1f);
yield return new WaitForSeconds(1f); // 等待动画完成
heatmapGameobject.GetComponent<DraggableUI>().UnlockDrag();
coolingButton.interactable = false;
}
public void HideHeatMap()
{
var draggableUI = heatmapGameobject.GetComponent<DraggableUI>();
heatmapGameobject.transform.DOLocalMove(outPos, 1f).OnComplete(() => { draggableUI.UnlockDrag(); heatmapGameobject.SetActive(false);});
}
public void StartCooling()
{
coolingButton.interactable = false;
StartCoroutine(ReduceValueCoroutine());
}
IEnumerator ReduceValueCoroutine()
{
var draggableUI = heatmapGameobject.GetComponent<DraggableUI>();
draggableUI.LockDrag();
// 存储初始位置
imageInitialPos = imageToMove.localPosition;
// 移动和旋转动画
imageToMove.DOLocalMoveX(imageInitialPos.x + 3.5f, 1f);
StartFan();
bool coolingSucceeded = Tempture < 2;
if (coolingSucceeded)
{
Debug.Log("降温成功");
float duration = 3.5f;
SetTemptureSmooth(0.5f, duration);
AudioManager.Instance.PlaySfx("event:/FollowInput/cooler_cooling");
yield return new WaitForSeconds(duration);
}
else
{
Debug.Log("降温失败");
yield return new WaitForSeconds(3f);
}
yield return StopFanSmoothly();
// 恢复初始状态
imageToMove.DOLocalMove(imageInitialPos, 1f);
if (coolingSucceeded)
{
yield return new WaitForSeconds(1.5f);
DialogController.Instance.StartDialogNode("降温成功");
bodyModuleSystem.OnCooling();
}
else
{
DialogController.Instance.StartDialogNode("降温失败");
}
}
private void Update()
{
if (imageToRotate == null || Mathf.Approximately(_fanAngularSpeed, 0f))
return;
EnsureFanPivot();
float angle = _fanAngularSpeed * Time.deltaTime;
// 绕 Sprite 几何中心转,避免 pivot 不在扇叶中心时整圈公转(看起来像散架)。
Vector3 worldPivot = imageToRotate.TransformPoint(_fanLocalPivot);
imageToRotate.RotateAround(worldPivot, imageToRotate.forward, angle);
}
private void EnsureFanPivot()
{
if (_fanPivotReady)
return;
_fanLocalPivot = Vector3.zero;
if (imageToRotate == null)
{
_fanPivotReady = true;
return;
}
_fanRenderer = imageToRotate.GetComponent<SpriteRenderer>();
if (_fanRenderer != null && _fanRenderer.sprite != null)
{
var sprite = _fanRenderer.sprite;
// transform 在 sprite.pivot;矩形几何中心相对 pivot 的本地偏移。
// Aseprite Canvas 空间 pivot 会远超出 [0,1],直接 Rotate 会整圈公转。
Vector2 size = sprite.rect.size;
Vector2 pivotPx = sprite.pivot;
float ppu = sprite.pixelsPerUnit;
if (ppu > 0f)
{
_fanLocalPivot = new Vector3(
(size.x * 0.5f - pivotPx.x) / ppu,
(size.y * 0.5f - pivotPx.y) / ppu,
0f);
}
}
_fanLocalPivot += (Vector3)fanPivotLocalOffset;
_fanPivotReady = true;
}
private void StartFan()
{
_fanSpeedTween?.Kill();
_fanSpeedTween = null;
_fanPivotReady = false;
EnsureFanPivot();
_fanAngularSpeed = FanAngularSpeed;
}
private IEnumerator StopFanSmoothly()
{
_fanSpeedTween?.Kill();
if (fanStopDuration <= 0f)
{
_fanAngularSpeed = 0f;
_fanSpeedTween = null;
yield break;
}
_fanSpeedTween = DOTween.To(
() => _fanAngularSpeed,
value => _fanAngularSpeed = value,
0f,
fanStopDuration)
.SetEase(Ease.OutQuad)
.SetTarget(this);
yield return _fanSpeedTween.WaitForCompletion();
_fanAngularSpeed = 0f;
_fanSpeedTween = null;
}
void Start()
{
bodyModuleSystem = FindObjectOfType<BodyModuleSystem>();
ApplyDragHitArea();
EnsureFanPivot();
SetDataToMaterial();
}
private void ApplyDragHitArea()
{
if (heatmapGameobject == null)
return;
var dragGraphic = heatmapGameobject.GetComponent<Graphic>();
if (dragGraphic == null)
{
Debug.LogWarning("[HeatMapController] 拖拽对象缺少 Graphic,无法扩展拖拽命中范围。", heatmapGameobject);
return;
}
float horizontal = Mathf.Max(0f, dragHitAreaExpansion.x);
float vertical = Mathf.Max(0f, dragHitAreaExpansion.y);
// Graphic 使用负 raycastPadding 扩展命中区;不会改变 RectTransform
// 因而不会移动右上角按钮或拉伸热力图内容。
dragGraphic.raycastPadding = new Vector4(-horizontal, -vertical, -horizontal, -vertical);
}
void OnDestroy()
{
_temptureTween?.Kill();
_fanSpeedTween?.Kill();
_fanAngularSpeed = 0f;
}
/// <summary>
/// 热点尺寸(世界单位):自身 Sprite/Image → 父级 Sprite → RectTransform。
/// UFheatmap 等空 Sprite 锚点会回退到父级 Module Pic,避免静默变成 (1,1) 导致热区偏小。
/// </summary>
private static bool TryGetHotspotSize(Transform t, out Vector2 size)
{
size = default;
if (t == null) return false;
if (TryGetSpriteLocalSize(t, out size))
return true;
// 空 SpriteRenderer / 纯锚点:沿父链找有 sprite 的模块图
for (var p = t.parent; p != null; p = p.parent)
{
if (TryGetSpriteLocalSize(p, out size))
return true;
}
var image = t.GetComponent<Image>();
if (image != null && image.sprite != null)
{
var b = image.sprite.bounds.size;
size = new Vector2(b.x, b.y);
return true;
}
if (t is RectTransform rt)
{
size = new Vector2(rt.rect.width, rt.rect.height);
return size.x > 0f || size.y > 0f;
}
return false;
}
private static bool TryGetSpriteLocalSize(Transform t, out Vector2 size)
{
size = default;
var sr = t.GetComponent<SpriteRenderer>();
if (sr == null || sr.sprite == null)
return false;
var b = sr.sprite.bounds.size;
var ls = t.lossyScale;
size = new Vector2(b.x * Mathf.Abs(ls.x), b.y * Mathf.Abs(ls.y));
return size.x > 0.001f || size.y > 0.001f;
}
public void SetDataToMaterial()
{
// 逻辑温度 0~2 → shader 侧发热强度;各段端点连续,避免跨档跳变。
// 保持原有温度映射,冷色替换只由 shader 处理。
if (tempture <= 1f)
{
float t = Mathf.Clamp01(tempture);
uftempture = Mathf.Lerp(0.85f, 2.8f, t);
modulesTempture = Mathf.Lerp(0.55f, 1.6f, t);
backtemture = Mathf.Lerp(0.7f, 2.25f, t);
}
else
{
float t = Mathf.Clamp01(tempture - 1f);
uftempture = Mathf.Lerp(2.8f, 3.2f, t);
modulesTempture = Mathf.Lerp(1.6f, 2.3f, t);
backtemture = Mathf.Lerp(2.25f, 3.4f, t);
}
if (Pos == null || heatMaterial == null)
return;
for (int i = 0; i < 7; i++)
{
if (Pos[i] == null)
{
points[i] = Vector4.zero;
properties[i] = Vector4.zero;
continue;
}
Transform currentTransform = Pos[i].GetComponent<Transform>();
if (i == 0)
{
points[i] = new Vector4(Pos[i].position.x, Pos[i].position.y, uftempture, 0);
}
else if (i == 6)
{
points[i] = new Vector4(Pos[i].position.x, Pos[i].position.y, backtemture, 0);
}
else
{
points[i] = new Vector4(Pos[i].position.x, Pos[i].position.y, modulesTempture,
0); // (x, y, z, temperature)
}
if (!TryGetHotspotSize(currentTransform, out var boxSize))
{
Debug.LogWarning($"[HeatMapController] Pos[{i}] '{Pos[i].name}' 无法取得热点尺寸,回退为 (1,1)。请检查 Sprite 或父级 Module Pic。", Pos[i]);
boxSize = Vector2.one;
}
properties[i] = new Vector4(boxSize.x, boxSize.y, 0.0f, 0.0f);
}
// 将数据传递给 Shader
heatMaterial.SetVectorArray("_Points", points);
heatMaterial.SetVectorArray("_Properties", properties);
heatMaterial.SetColor("_ColdBaseColor", coldBaseColor);
heatMaterial.SetColor("_ColdHighlightColor", coldHighlightColor);
heatMaterial.SetFloat("_ColdNoiseScale", coldNoiseScale);
heatMaterial.SetFloat("_ColdNoiseStrength", coldNoiseStrength);
}
public void SetTemptureDirect(float value)
{
_temptureTween?.Kill();
Tempture = value;
}
public void SetTemptureSmooth(float targetValue, float duration)
{
_temptureTween?.Kill();
_temptureTween = DOTween.To(() => tempture, x => Tempture = x, targetValue, duration)
.SetEase(Ease.InOutQuad)
.SetTarget(this);
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (imageToRotate == null)
return;
// 编辑器下也能预览旋转轴(不依赖 Play 时缓存)。
var sr = imageToRotate.GetComponent<SpriteRenderer>();
Vector3 localPivot = (Vector3)fanPivotLocalOffset;
if (sr != null && sr.sprite != null && sr.sprite.pixelsPerUnit > 0f)
{
var sprite = sr.sprite;
Vector2 size = sprite.rect.size;
Vector2 pivotPx = sprite.pivot;
float ppu = sprite.pixelsPerUnit;
localPivot += new Vector3(
(size.x * 0.5f - pivotPx.x) / ppu,
(size.y * 0.5f - pivotPx.y) / ppu,
0f);
}
Vector3 worldPivot = imageToRotate.TransformPoint(localPivot);
Gizmos.color = new Color(1f, 0.45f, 0.1f, 0.9f);
Gizmos.DrawWireSphere(worldPivot, 0.08f);
Gizmos.color = new Color(0.3f, 0.9f, 0.55f, 0.7f);
Gizmos.DrawLine(worldPivot + Vector3.left * 0.2f, worldPivot + Vector3.right * 0.2f);
Gizmos.DrawLine(worldPivot + Vector3.down * 0.2f, worldPivot + Vector3.up * 0.2f);
}
#endif
}