Merge branch 'develop' into feature/UI更新
This commit is contained in:
@@ -26,14 +26,14 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
[YarnCommand("load_scene")]
|
||||
public static IEnumerator LoadScene(string sceneName, float duration = 1)
|
||||
public static IEnumerator LoadScene(string sceneName)
|
||||
{
|
||||
HideDialog();
|
||||
yield return SceneLoader.Instance.LoadSceneAsync(sceneName);
|
||||
}
|
||||
|
||||
[YarnCommand("unload_scene")]
|
||||
public static IEnumerator UnloadScene(string sceneName)
|
||||
public static IEnumerator UnloadScene()
|
||||
{
|
||||
yield return SceneLoader.Instance.UnloadSceneAsync();
|
||||
}
|
||||
@@ -73,17 +73,17 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
[YarnCommand("show_obj")]
|
||||
public static IEnumerator ShowObj(string picName)
|
||||
public static IEnumerator ShowObj(string picName, float duration = 1)
|
||||
{
|
||||
var panel = UIManager.Instance.GetPanel<PlayToolPanel>();
|
||||
return panel.ShowObj(picName);
|
||||
return panel.ShowObj(picName, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("hide_obj")]
|
||||
public static IEnumerator HideObj()
|
||||
public static IEnumerator HideObj(float duration = 1)
|
||||
{
|
||||
var panel = UIManager.Instance.GetPanel<PlayToolPanel>();
|
||||
return panel.HideObj();
|
||||
return panel.HideObj(duration);
|
||||
}
|
||||
|
||||
[YarnCommand("show_sprite")]
|
||||
@@ -141,17 +141,17 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
[YarnCommand("show_full_screen")]
|
||||
public static IEnumerator ShowFullScreen(string picName)
|
||||
public static IEnumerator ShowFullScreen(string picName, float duration = 1)
|
||||
{
|
||||
var panel = UIManager.Instance.GetPanel<PlayToolPanel>();
|
||||
return panel.ShowFullScreen(picName);
|
||||
return panel.ShowFullScreen(picName, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("hide_full_screen")]
|
||||
public static IEnumerator HideFullScreen()
|
||||
public static IEnumerator HideFullScreen(float duration = 1)
|
||||
{
|
||||
var panel = UIManager.Instance.GetPanel<PlayToolPanel>();
|
||||
return panel.HideFullScreen();
|
||||
return panel.HideFullScreen(duration);
|
||||
}
|
||||
|
||||
[YarnCommand("open_progress_window")]
|
||||
|
||||
@@ -1,39 +1,61 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using AibisDream.Framework;
|
||||
using DG.Tweening;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class MouseFollowAndZoom : MonoBehaviour, IInteraction
|
||||
{
|
||||
public GameObject targetIndicator; // UI 目标框
|
||||
public TMP_Text targetNameText; // 显示目标名称的文本
|
||||
public Vector2 sceneBoundsMin; // 场景边界的左下角
|
||||
public Vector2 sceneBoundsMax; // 场景边界的右上角
|
||||
private EyeSystem eyemanager;
|
||||
private EyeTarget currentTarget; // 当前目标
|
||||
private bool isActive = false;
|
||||
private bool isLocked = false;
|
||||
|
||||
// 交互状态管理
|
||||
[Header("交互状态")]
|
||||
[SerializeField] private bool _isInteractionActive = true; // 是否激活交互
|
||||
[SerializeField] private bool _isInteractionAvailable = true; // 是否可用交互
|
||||
[SerializeField] private float _dialogEndCooldown = 0.1f; // 对话结束后的冷却时间
|
||||
|
||||
// 内部状态
|
||||
private float _lastDialogEndTime = -1f; // 上次对话结束的时间
|
||||
private bool _isDialogCooldownActive = false; // 是否在对话冷却期间
|
||||
public GameObject targetIndicator;
|
||||
public TMP_Text targetNameText;
|
||||
public Vector2 sceneBoundsMin;
|
||||
public Vector2 sceneBoundsMax;
|
||||
|
||||
[Header("Cinemachine Pan")]
|
||||
[Tooltip("平移目标;留空则平移本物体。Eye 模块应指向 Eye DeepCamera 虚拟相机 Transform。")]
|
||||
[SerializeField] private Transform panTransform;
|
||||
[Tooltip("虚拟相机可移动的世界区域(与 ViewCamera / EyeSystem 聚焦边界一致)。")]
|
||||
[SerializeField] private Vector2 worldRegionMin = new(30.2f, -5f);
|
||||
[SerializeField] private Vector2 worldRegionMax = new(50.2f, 5f);
|
||||
|
||||
[Header("Input")]
|
||||
[SerializeField] private float mouseScreenOffsetX;
|
||||
[FormerlySerializedAs("viewCamera")]
|
||||
[SerializeField] private Camera legacyInputCamera;
|
||||
|
||||
public Camera viewCamera;
|
||||
public float parallaxEffectMultiplier = 0.1f;
|
||||
public Vector2 maxOffset = new Vector2(2f, 2f);
|
||||
|
||||
[Header("Interaction")]
|
||||
[SerializeField] private bool _isInteractionActive = true;
|
||||
[SerializeField] private bool _isInteractionAvailable = true;
|
||||
[SerializeField] private float _dialogEndCooldown = 0.1f;
|
||||
|
||||
private EyeSystem eyemanager;
|
||||
private EyeTarget currentTarget;
|
||||
private bool isActive;
|
||||
private bool isLocked;
|
||||
private float _lastDialogEndTime = -1f;
|
||||
private bool _isDialogCooldownActive;
|
||||
private Vector3 initialPosition;
|
||||
private Vector3 currentOffset;
|
||||
private Tween focusTween;
|
||||
|
||||
private float viewOffset = 0;
|
||||
private Transform PanTransform => panTransform != null ? panTransform : transform;
|
||||
|
||||
private bool UsesCinemachinePan => panTransform != null;
|
||||
|
||||
public Vector3 ViewPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
Camera cameraForInput = GetInputCamera();
|
||||
return cameraForInput != null ? cameraForInput.transform.position : PanTransform.position;
|
||||
}
|
||||
}
|
||||
|
||||
// 读写访问
|
||||
public void SetIsActive(bool value)
|
||||
{
|
||||
isActive = value;
|
||||
@@ -43,152 +65,212 @@ namespace AibisDream
|
||||
{
|
||||
isLocked = value;
|
||||
}
|
||||
|
||||
// 交互状态控制方法
|
||||
|
||||
public void SetInteractionActive(bool value)
|
||||
{
|
||||
_isInteractionActive = value;
|
||||
}
|
||||
|
||||
|
||||
public void SetInteractionAvailable(bool value)
|
||||
{
|
||||
_isInteractionAvailable = value;
|
||||
}
|
||||
|
||||
|
||||
void Start()
|
||||
public void ResetPanOrigin()
|
||||
{
|
||||
initialPosition = transform.position;
|
||||
targetIndicator.SetActive(false); // 初始时隐藏目标框
|
||||
focusTween?.Kill();
|
||||
currentOffset = Vector3.zero;
|
||||
initialPosition = PanTransform.position;
|
||||
}
|
||||
|
||||
public void MoveFocusTo(Transform target, float duration, Vector2 minBounds, Vector2 maxBounds, Vector2 viewportSize)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 focusPosition = ClampFocusPosition(target.position, minBounds, maxBounds, viewportSize);
|
||||
Vector3 targetPanPosition;
|
||||
|
||||
if (UsesCinemachinePan)
|
||||
{
|
||||
targetPanPosition = ClampFocusPosition(
|
||||
new Vector3(focusPosition.x, focusPosition.y, PanTransform.position.z),
|
||||
minBounds,
|
||||
maxBounds,
|
||||
viewportSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 viewPosition = ViewPosition;
|
||||
viewPosition.z = focusPosition.z;
|
||||
targetPanPosition = PanTransform.position + viewPosition - focusPosition;
|
||||
targetPanPosition.z = PanTransform.position.z;
|
||||
}
|
||||
|
||||
initialPosition = targetPanPosition;
|
||||
focusTween?.Kill();
|
||||
|
||||
if (duration <= 0f)
|
||||
{
|
||||
PanTransform.position = initialPosition + currentOffset;
|
||||
return;
|
||||
}
|
||||
|
||||
focusTween = PanTransform.DOMove(initialPosition + currentOffset, duration).SetEase(Ease.InOutSine);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
initialPosition = PanTransform.position;
|
||||
targetIndicator.SetActive(false);
|
||||
eyemanager = FindObjectOfType<EyeSystem>();
|
||||
if (eyemanager == null)
|
||||
{
|
||||
Debug.LogError("EyeManager instance not found in the scene.");
|
||||
Debug.LogError("[MouseFollowAndZoom] EyeSystem 未找到。", this);
|
||||
}
|
||||
|
||||
// 注册对话事件以处理对话结束后的冷却
|
||||
|
||||
RegisterDialogEvents();
|
||||
}
|
||||
|
||||
void Update()
|
||||
private void Update()
|
||||
{
|
||||
if (!isActive)
|
||||
{
|
||||
targetIndicator.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查对话结束冷却
|
||||
UpdateDialogCooldown();
|
||||
|
||||
HandleTargetIndicator(); // 显示指示器
|
||||
if (isLocked) return;
|
||||
UpdateDialogCooldown();
|
||||
HandleTargetIndicator();
|
||||
|
||||
if (isLocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleMouseMovement();
|
||||
UpdateCurrentTarget(); // 更新当前目标
|
||||
UpdateCurrentTarget();
|
||||
}
|
||||
|
||||
private Vector3 currentOffset; // 当前的偏移量
|
||||
|
||||
void HandleMouseMovement()
|
||||
private void HandleMouseMovement()
|
||||
{
|
||||
// 获取鼠标位置
|
||||
Vector3 mousePosition = Input.mousePosition;
|
||||
mousePosition.x -= 40f; // 为x坐标加上40.3
|
||||
Vector3 worldMousePosition = viewCamera.ScreenToWorldPoint(mousePosition);
|
||||
worldMousePosition.z = 0; // 确保 z 为 0
|
||||
Camera cameraForInput = GetInputCamera();
|
||||
if (cameraForInput == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算目标偏移量并反向
|
||||
Vector3 targetOffset = (viewCamera.transform.position - worldMousePosition) * parallaxEffectMultiplier;
|
||||
Vector3 mousePosition = GetMouseScreenPosition();
|
||||
Vector3 worldMousePosition = cameraForInput.ScreenToWorldPoint(mousePosition);
|
||||
worldMousePosition.z = 0;
|
||||
|
||||
// 视差锚点用 initialPosition,避免相机跟随平移后 delta 被抵消
|
||||
Vector3 parallaxAnchor = UsesCinemachinePan ? initialPosition : ViewPosition;
|
||||
parallaxAnchor.z = 0;
|
||||
Vector3 delta = UsesCinemachinePan
|
||||
? worldMousePosition - parallaxAnchor
|
||||
: parallaxAnchor - worldMousePosition;
|
||||
Vector3 targetOffset = delta * parallaxEffectMultiplier;
|
||||
targetOffset.x = Mathf.Clamp(targetOffset.x, -maxOffset.x, maxOffset.x);
|
||||
targetOffset.y = Mathf.Clamp(targetOffset.y, -maxOffset.y, maxOffset.y);
|
||||
|
||||
// 平滑处理偏移量
|
||||
float smoothFactor = 0.01f; // 平滑系数,值越小平滑效果越强,响应越慢
|
||||
currentOffset = Vector3.Lerp(currentOffset, targetOffset, smoothFactor);
|
||||
currentOffset = Vector3.Lerp(currentOffset, targetOffset, 0.01f);
|
||||
|
||||
// 计算目标位置
|
||||
Vector3 targetPosition = initialPosition + currentOffset;
|
||||
if (UsesCinemachinePan)
|
||||
{
|
||||
Vector2 parallaxSlack = maxOffset * parallaxEffectMultiplier;
|
||||
targetPosition = ClampFocusPositionWithSlack(
|
||||
targetPosition,
|
||||
worldRegionMin,
|
||||
worldRegionMax,
|
||||
GetViewportSize(),
|
||||
parallaxSlack);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPosition.x = Mathf.Clamp(targetPosition.x, sceneBoundsMin.x, sceneBoundsMax.x);
|
||||
targetPosition.y = Mathf.Clamp(targetPosition.y, sceneBoundsMin.y, sceneBoundsMax.y);
|
||||
}
|
||||
|
||||
// 限制目标位置在场景边界范围内
|
||||
targetPosition.x = Mathf.Clamp(targetPosition.x, sceneBoundsMin.x, sceneBoundsMax.x);
|
||||
targetPosition.y = Mathf.Clamp(targetPosition.y, sceneBoundsMin.y, sceneBoundsMax.y);
|
||||
targetPosition.z = PanTransform.position.z;
|
||||
|
||||
// 使用插值(Lerp)平滑更新位置
|
||||
float followSpeed = 0.1f; // 跟随速度,值越小延迟越大
|
||||
Vector3 smoothedPosition = Vector3.Lerp(transform.position, targetPosition, followSpeed);
|
||||
|
||||
// 应用平滑后的位置
|
||||
transform.position = smoothedPosition;
|
||||
PanTransform.position = Vector3.Lerp(PanTransform.position, targetPosition, 0.1f);
|
||||
}
|
||||
|
||||
void UpdateCurrentTarget()
|
||||
private void UpdateCurrentTarget()
|
||||
{
|
||||
// 获取鼠标位置
|
||||
Vector3 mousePosition = Input.mousePosition;
|
||||
mousePosition.x -= 40f; // 为x坐标加上40.3
|
||||
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition);
|
||||
RaycastHit2D hit =
|
||||
Physics2D.Raycast(worldPosition + viewCamera.transform.position - Camera.main.transform.position,
|
||||
Vector2.zero);
|
||||
Camera cameraForInput = GetInputCamera();
|
||||
if (cameraForInput == null)
|
||||
{
|
||||
currentTarget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有目标
|
||||
Vector3 worldPosition = cameraForInput.ScreenToWorldPoint(GetMouseScreenPosition());
|
||||
worldPosition.z = 0;
|
||||
|
||||
RaycastHit2D hit = Physics2D.Raycast(worldPosition, Vector2.zero);
|
||||
if (hit.collider != null && hit.collider.CompareTag("EyeTarget"))
|
||||
{
|
||||
currentTarget = hit.collider.GetComponent<EyeTarget>(); // 更新当前目标
|
||||
|
||||
// 处理鼠标点击事件 - 只有在交互可用且不在对话冷却期时才处理
|
||||
currentTarget = hit.collider.GetComponent<EyeTarget>();
|
||||
if (Input.GetMouseButtonDown(0) && _isInteractionActive && _isInteractionAvailable && !_isDialogCooldownActive)
|
||||
{
|
||||
Debug.Log("点击目标触发了");
|
||||
if (currentTarget != null)
|
||||
{
|
||||
eyemanager.SetTarget(currentTarget); // 设置目标
|
||||
eyemanager.SetTarget(currentTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("The collider does not have an EyeTarget component.");
|
||||
Debug.LogWarning("[MouseFollowAndZoom] Collider 缺少 EyeTarget 组件。", hit.collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTarget = null; // 如果没有目标,设置为 null
|
||||
currentTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTargetIndicator()
|
||||
private Vector3 GetMouseScreenPosition()
|
||||
{
|
||||
Vector3 mousePosition = Input.mousePosition;
|
||||
mousePosition.x -= mouseScreenOffsetX;
|
||||
mousePosition.z = GetInputCamera() != null
|
||||
? GetInputCamera().nearClipPlane
|
||||
: mousePosition.z;
|
||||
return mousePosition;
|
||||
}
|
||||
|
||||
private void HandleTargetIndicator()
|
||||
{
|
||||
if (currentTarget == null || !_isInteractionActive)
|
||||
{
|
||||
targetIndicator.SetActive(false); // 隐藏指示器
|
||||
targetIndicator.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标的 BoxCollider2D
|
||||
BoxCollider2D boxCollider = currentTarget.GetComponent<BoxCollider2D>();
|
||||
if (boxCollider != null)
|
||||
if (boxCollider == null)
|
||||
{
|
||||
Vector3 boxCenter = boxCollider.bounds.center;
|
||||
Vector3 boxSize = boxCollider.bounds.size;
|
||||
|
||||
// 转换为世界空间坐标
|
||||
Vector3 worldCenter = boxCenter;
|
||||
Vector3 worldSize = boxSize;
|
||||
|
||||
// 设置目标框的位置
|
||||
RectTransform indicatorRectTransform = targetIndicator.GetComponent<RectTransform>();
|
||||
indicatorRectTransform.position = worldCenter;
|
||||
indicatorRectTransform.sizeDelta = new Vector2(worldSize.x, worldSize.y);
|
||||
|
||||
// 设置目标名称
|
||||
targetNameText.text = currentTarget.name + " " +
|
||||
"R" + currentTarget._color.r.ToString("F1") +
|
||||
" G" + currentTarget._color.g.ToString("F1") +
|
||||
" B" + currentTarget._color.b.ToString("F1");
|
||||
|
||||
// 显示指示器
|
||||
targetIndicator.SetActive(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 boxCenter = boxCollider.bounds.center;
|
||||
Vector3 boxSize = boxCollider.bounds.size;
|
||||
|
||||
RectTransform indicatorRectTransform = targetIndicator.GetComponent<RectTransform>();
|
||||
indicatorRectTransform.position = boxCenter;
|
||||
indicatorRectTransform.sizeDelta = new Vector2(boxSize.x, boxSize.y);
|
||||
|
||||
targetNameText.text = currentTarget.name + " " +
|
||||
"R" + currentTarget._color.r.ToString("F1") +
|
||||
" G" + currentTarget._color.g.ToString("F1") +
|
||||
" B" + currentTarget._color.b.ToString("F1");
|
||||
|
||||
targetIndicator.SetActive(true);
|
||||
}
|
||||
|
||||
public void SetIndicatorTarget(EyeTarget target)
|
||||
@@ -196,85 +278,123 @@ namespace AibisDream
|
||||
currentTarget = target;
|
||||
}
|
||||
|
||||
void OnDrawGizmos()
|
||||
private Camera GetInputCamera()
|
||||
{
|
||||
if (UsesCinemachinePan && CameraKit.Instance != null)
|
||||
{
|
||||
return CameraKit.Instance.GetCurrentCamera();
|
||||
}
|
||||
|
||||
if (legacyInputCamera != null)
|
||||
{
|
||||
return legacyInputCamera;
|
||||
}
|
||||
|
||||
return Camera.main;
|
||||
}
|
||||
|
||||
private Vector2 GetViewportSize()
|
||||
{
|
||||
Camera cam = GetInputCamera();
|
||||
if (cam == null || !cam.orthographic)
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
float height = cam.orthographicSize * 2f;
|
||||
return new Vector2(height * cam.aspect, height);
|
||||
}
|
||||
|
||||
private static Vector3 ClampFocusPositionWithSlack(
|
||||
Vector3 targetPosition,
|
||||
Vector2 minBounds,
|
||||
Vector2 maxBounds,
|
||||
Vector2 viewportSize,
|
||||
Vector2 slack)
|
||||
{
|
||||
if (maxBounds.x > minBounds.x)
|
||||
{
|
||||
float halfWidth = Mathf.Max(0f, viewportSize.x * 0.5f);
|
||||
targetPosition.x = Mathf.Clamp(
|
||||
targetPosition.x,
|
||||
minBounds.x + halfWidth - slack.x,
|
||||
maxBounds.x - halfWidth + slack.x);
|
||||
}
|
||||
|
||||
if (maxBounds.y > minBounds.y)
|
||||
{
|
||||
float halfHeight = Mathf.Max(0f, viewportSize.y * 0.5f);
|
||||
targetPosition.y = Mathf.Clamp(
|
||||
targetPosition.y,
|
||||
minBounds.y + halfHeight - slack.y,
|
||||
maxBounds.y - halfHeight + slack.y);
|
||||
}
|
||||
|
||||
return targetPosition;
|
||||
}
|
||||
|
||||
private static Vector3 ClampFocusPosition(Vector3 targetPosition, Vector2 minBounds, Vector2 maxBounds, Vector2 viewportSize)
|
||||
{
|
||||
if (maxBounds.x > minBounds.x)
|
||||
{
|
||||
float halfWidth = Mathf.Max(0f, viewportSize.x * 0.5f);
|
||||
targetPosition.x = Mathf.Clamp(targetPosition.x, minBounds.x + halfWidth, maxBounds.x - halfWidth);
|
||||
}
|
||||
|
||||
if (maxBounds.y > minBounds.y)
|
||||
{
|
||||
float halfHeight = Mathf.Max(0f, viewportSize.y * 0.5f);
|
||||
targetPosition.y = Mathf.Clamp(targetPosition.y, minBounds.y + halfHeight, maxBounds.y - halfHeight);
|
||||
}
|
||||
|
||||
return targetPosition;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// 设置 Gizmos 颜色
|
||||
Gizmos.color = Color.red;
|
||||
Vector2 min = UsesCinemachinePan ? worldRegionMin : sceneBoundsMin;
|
||||
Vector2 max = UsesCinemachinePan ? worldRegionMax : sceneBoundsMax;
|
||||
Vector3 bottomLeft = new Vector3(min.x, min.y, 0);
|
||||
Vector3 topRight = new Vector3(max.x, max.y, 0);
|
||||
|
||||
// 绘制场景边界
|
||||
Vector3 bottomLeft = new Vector3(sceneBoundsMin.x, sceneBoundsMin.y, 0);
|
||||
Vector3 topRight = new Vector3(sceneBoundsMax.x, sceneBoundsMax.y, 0);
|
||||
|
||||
// 绘制边界框
|
||||
Gizmos.DrawLine(bottomLeft, new Vector3(bottomLeft.x, topRight.y, 0));
|
||||
Gizmos.DrawLine(bottomLeft, new Vector3(topRight.x, bottomLeft.y, 0));
|
||||
Gizmos.DrawLine(topRight, new Vector3(bottomLeft.x, topRight.y, 0));
|
||||
Gizmos.DrawLine(topRight, new Vector3(topRight.x, bottomLeft.y, 0));
|
||||
}
|
||||
|
||||
#region 对话冷却处理
|
||||
|
||||
/// <summary>
|
||||
/// 注册对话事件
|
||||
/// </summary>
|
||||
|
||||
private void RegisterDialogEvents()
|
||||
{
|
||||
// 监听对话结束事件
|
||||
EnumEventSystem.Global.Register(InteractionEventEnum.DialogEnd, OnDialogueComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对话结束时的处理
|
||||
/// </summary>
|
||||
|
||||
private void OnDialogueComplete()
|
||||
{
|
||||
_lastDialogEndTime = Time.time;
|
||||
_isDialogCooldownActive = true;
|
||||
Debug.Log($"[交互冷却] 对话结束,开始 {_dialogEndCooldown} 秒冷却期");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新对话冷却状态
|
||||
/// </summary>
|
||||
|
||||
private void UpdateDialogCooldown()
|
||||
{
|
||||
if (_isDialogCooldownActive && Time.time - _lastDialogEndTime >= _dialogEndCooldown)
|
||||
{
|
||||
_isDialogCooldownActive = false;
|
||||
Debug.Log("[交互冷却] 冷却期结束,交互恢复正常");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁时取消事件注册
|
||||
/// </summary>
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
focusTween?.Kill();
|
||||
EnumEventSystem.Global.UnRegister(InteractionEventEnum.DialogEnd, OnDialogueComplete);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IInteraction 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否激活,激活就正常显示鼠标,否则就显示禁用标志
|
||||
/// </summary>
|
||||
|
||||
public bool IsActive => _isInteractionActive && !_isDialogCooldownActive;
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可用,可用就正常显示鼠标,否则就保持原有标志不变
|
||||
/// </summary>
|
||||
public bool IsAvailable => _isInteractionAvailable && !_isDialogCooldownActive;
|
||||
|
||||
/// <summary>
|
||||
/// 返回GameObject
|
||||
/// </summary>
|
||||
|
||||
public GameObject GetGameObject()
|
||||
{
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,140 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 714af643a5975bf4aacfd94e297e7ee6
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -5,7 +5,9 @@ using Yarn.Unity;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
@@ -16,11 +18,37 @@ namespace AibisDream.FixSystem
|
||||
private const float DefaultFadeDuration = 1f;
|
||||
private const float SearchStartDelay = 1f;
|
||||
private const float ProjectorMoveDuration = 2f;
|
||||
private const float MaxClarityDistortion = 0.58f;
|
||||
private const float MaxFrequencyBlur = 0.6f;
|
||||
private const float MinPixelDensity = 18f;
|
||||
private const float MaxPixelDensity = 96f;
|
||||
private const string DirectionalDistortionFadeProperty = "_DirectionalDistortionFade";
|
||||
|
||||
private static readonly int EnablePixelateId = Shader.PropertyToID("_EnablePixelate");
|
||||
private static readonly int FrequencyTuningId = Shader.PropertyToID("_FrequencyTuning");
|
||||
private static readonly int ClarityTuningId = Shader.PropertyToID("_ClarityTuning");
|
||||
private static readonly int DirectionalDistortionFadeId = Shader.PropertyToID(DirectionalDistortionFadeProperty);
|
||||
private static readonly int PixelateFadeId = Shader.PropertyToID("_PixelateFade");
|
||||
private static readonly int PixelatePixelDensityId = Shader.PropertyToID("_PixelatePixelDensity");
|
||||
private static readonly int EnableGaussianBlurId = Shader.PropertyToID("_EnableGaussianBlur");
|
||||
private static readonly int GaussianBlurFadeId = Shader.PropertyToID("_GaussianBlurFade");
|
||||
private static readonly int GaussianBlurOffsetId = Shader.PropertyToID("_GaussianBlurOffset");
|
||||
private static readonly int EnableUvDistortId = Shader.PropertyToID("_EnableUVDistort");
|
||||
private static readonly int UvDistortFadeId = Shader.PropertyToID("_UVDistortFade");
|
||||
private static readonly int MemoryGrayscaleId = Shader.PropertyToID("_MemoryGrayscale");
|
||||
private static readonly int MemoryContrastId = Shader.PropertyToID("_MemoryContrast");
|
||||
|
||||
[SerializeField] private GameObject memoryView;
|
||||
[SerializeField] private GameObject projector;
|
||||
[SerializeField] private SpriteRenderer memoryFinishedSpriteRenderer; // 用于显示记忆处理结束的表现
|
||||
[SerializeField] private SpriteRenderer memoryFinishedImageNoColor; // 用于显示记忆处理结束的表现
|
||||
[SerializeField] private Image memoryFinishedImage; // UI彩色记忆层
|
||||
[SerializeField] private Image memoryFinishedNoColorImage; // UI去色记忆层
|
||||
[FormerlySerializedAs("memoryFinishedspRender")]
|
||||
[FormerlySerializedAs("memoryFinishedSpriteRenderer")]
|
||||
[HideInInspector]
|
||||
[SerializeField] private SpriteRenderer legacyMemoryFinishedSpriteRenderer;
|
||||
[FormerlySerializedAs("memoryFinishedImageNoColor")]
|
||||
[HideInInspector]
|
||||
[SerializeField] private SpriteRenderer legacyMemoryFinishedImageNoColor;
|
||||
|
||||
[SerializeField] private Volume mainEffect;
|
||||
[SerializeField] private Volume memoryClarityVolume;
|
||||
@@ -28,8 +56,15 @@ namespace AibisDream.FixSystem
|
||||
[SerializeField] private Volume memoryProcessVolume;
|
||||
[SerializeField] private MemorySlider frequencySlider; // 频率调节滑动条
|
||||
[SerializeField] private MemorySlider claritySlider; // 清晰度调节滑动条
|
||||
[SerializeField] private bool useSpriteShaderTuning = true;
|
||||
|
||||
[SerializeField] private CanvasGroup playBackground;
|
||||
[SerializeField] private Graphic standbyOverlay;
|
||||
[SerializeField, Range(0f, 1f)] private float standbyOverlayMinAlpha = 0.04f;
|
||||
[SerializeField, Range(0f, 1f)] private float standbyOverlayMaxAlpha = 0.12f;
|
||||
[SerializeField, Range(0f, 1f)] private float searchOverlayAlpha = 0.85f;
|
||||
[SerializeField] private float standbyOverlayPulseDuration = 0.8f;
|
||||
[SerializeField] private float searchTargetMinDistanceFromStart = 2.5f;
|
||||
|
||||
[SerializeField] private GameObject memoryGroup;
|
||||
|
||||
@@ -41,12 +76,14 @@ namespace AibisDream.FixSystem
|
||||
[SerializeField] private MemoryPunchTapeSlot punchTapeSlot;
|
||||
|
||||
[SerializeField] private Material fadeMaterial;
|
||||
private Material runtimeMemoryMaterial;
|
||||
private Material runtimeNoColorMemoryMaterial;
|
||||
private Tween overlayTween;
|
||||
|
||||
public void OpenMemoryView()
|
||||
{
|
||||
memoryGroup.SetActive(true);
|
||||
memoryView.gameObject.SetActive(true);
|
||||
|
||||
}
|
||||
|
||||
public IEnumerator DropMemoryProjector()
|
||||
@@ -64,6 +101,8 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public void CloseMemoryView()
|
||||
{
|
||||
StopStandbyOverlay();
|
||||
SetOverlayAlpha(0f);
|
||||
memoryView.gameObject.SetActive(false);
|
||||
memoryGroup.SetActive(false);
|
||||
}
|
||||
@@ -86,6 +125,7 @@ namespace AibisDream.FixSystem
|
||||
PunchTapeManager.Instance.OpenFavoritesPanel();
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
StartStandbyOverlay();
|
||||
}
|
||||
|
||||
public void OnExit()
|
||||
@@ -134,6 +174,7 @@ namespace AibisDream.FixSystem
|
||||
private void InitSystem()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
DisableLegacyRenderTextureCameras();
|
||||
if (memoryGroup != null)
|
||||
memoryGroup.SetActive(false);
|
||||
}
|
||||
@@ -141,28 +182,57 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void Start()
|
||||
{
|
||||
PrepareRuntimeMemoryMaterial();
|
||||
frequencySlider.OnFound += CheckAllFound;
|
||||
claritySlider.OnFound += CheckAllFound;
|
||||
Init();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
overlayTween?.Kill();
|
||||
if (runtimeMemoryMaterial != null)
|
||||
Destroy(runtimeMemoryMaterial);
|
||||
if (runtimeNoColorMemoryMaterial != null)
|
||||
Destroy(runtimeNoColorMemoryMaterial);
|
||||
}
|
||||
|
||||
private void DisableLegacyRenderTextureCameras()
|
||||
{
|
||||
if (memoryGroup == null)
|
||||
return;
|
||||
|
||||
var cameras = memoryGroup.GetComponentsInChildren<Camera>(true);
|
||||
foreach (var renderCamera in cameras)
|
||||
{
|
||||
if (renderCamera != null && renderCamera.targetTexture != null)
|
||||
renderCamera.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAllFound()
|
||||
{
|
||||
if (frequencySlider.IsFound && claritySlider.IsFound)
|
||||
OnSearchComplete();
|
||||
}
|
||||
|
||||
private float GetRandomNumber()
|
||||
private float GetRandomSearchTarget(MemorySlider slider)
|
||||
{
|
||||
// 随机决定选择哪个区间
|
||||
if (UnityEngine.Random.value < 0.5f) // 50% 概率选择第一个区间
|
||||
{
|
||||
return UnityEngine.Random.Range(0.5f, 2f);
|
||||
}
|
||||
else // 50% 概率选择第二个区间
|
||||
{
|
||||
return UnityEngine.Random.Range(4f, 5.5f);
|
||||
}
|
||||
float start = slider != null ? slider.StartValue : 0f;
|
||||
float max = slider != null ? slider.MaxValue : 6f;
|
||||
float minDistance = Mathf.Max(0f, searchTargetMinDistanceFromStart);
|
||||
float nearBandMin = start + minDistance;
|
||||
float nearBandMax = start + (max - start) * 0.55f;
|
||||
float farBandMin = max - 1.5f;
|
||||
float farBandMax = max - 0.5f;
|
||||
|
||||
if (nearBandMin >= nearBandMax)
|
||||
return UnityEngine.Random.Range(farBandMin, farBandMax);
|
||||
|
||||
if (UnityEngine.Random.value < 0.5f)
|
||||
return UnityEngine.Random.Range(nearBandMin, nearBandMax);
|
||||
|
||||
return UnityEngine.Random.Range(farBandMin, farBandMax);
|
||||
}
|
||||
|
||||
private void OnSearchComplete()
|
||||
@@ -184,6 +254,9 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public void TweenWeight(Volume postProcessingVolume, float targetWeight, float duration)
|
||||
{
|
||||
if (postProcessingVolume == null)
|
||||
return;
|
||||
|
||||
// 动态调整权重
|
||||
DOTween.To(() => postProcessingVolume.weight, x => postProcessingVolume.weight = x, targetWeight, duration);
|
||||
}
|
||||
@@ -200,9 +273,19 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
|
||||
if (frequencySlider != null)
|
||||
{
|
||||
frequencySlider.OnTuningChanged -= ApplyFrequencyTuning;
|
||||
frequencySlider.Init(memoryFrequencyVolume);
|
||||
frequencySlider.SetVolumeOutputEnabled(!useSpriteShaderTuning);
|
||||
frequencySlider.OnTuningChanged += ApplyFrequencyTuning;
|
||||
}
|
||||
if (claritySlider != null)
|
||||
{
|
||||
claritySlider.OnTuningChanged -= ApplyClarityTuning;
|
||||
claritySlider.Init(memoryClarityVolume);
|
||||
claritySlider.SetVolumeOutputEnabled(!useSpriteShaderTuning);
|
||||
claritySlider.OnTuningChanged += ApplyClarityTuning;
|
||||
}
|
||||
|
||||
ResetPresentationState();
|
||||
}
|
||||
@@ -232,10 +315,12 @@ namespace AibisDream.FixSystem
|
||||
private void ResetPresentationState()
|
||||
{
|
||||
if (fadeMaterial != null)
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", FadeMaterialStartValue);
|
||||
memoryFinishedSpriteRenderer.color = Color.black;
|
||||
memoryProcessVolume.weight = 0;
|
||||
SetFloatIfExists(DirectionalDistortionFadeId, FadeMaterialStartValue);
|
||||
ResetSpriteTuning();
|
||||
SetMemoryColor(Color.black);
|
||||
SetVolumeWeight(memoryProcessVolume, 0f);
|
||||
_activePunchTape = null;
|
||||
StartStandbyOverlay();
|
||||
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
@@ -264,27 +349,30 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
if (fadeMaterial == null) yield break;
|
||||
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", FadeMaterialStartValue);
|
||||
Tween tween = fadeMaterial.DOFloat(0, "_DirectionalDistortionFade", 3);
|
||||
SetFloatIfExists(DirectionalDistortionFadeId, FadeMaterialStartValue);
|
||||
DG.Tweening.Sequence sequence = DOTween.Sequence();
|
||||
AppendMaterialFloatTween(sequence, fadeMaterial, 0f, DirectionalDistortionFadeProperty, 3f);
|
||||
if (runtimeNoColorMemoryMaterial != null && runtimeNoColorMemoryMaterial != fadeMaterial)
|
||||
AppendMaterialFloatTween(sequence, runtimeNoColorMemoryMaterial, 0f, DirectionalDistortionFadeProperty, 3f);
|
||||
Tween tween = sequence;
|
||||
yield return tween.WaitForCompletion();
|
||||
}
|
||||
|
||||
[YarnCommand("PlayMemory")]
|
||||
public IEnumerator PlayMemory(bool useYarn = false, string memName = "")
|
||||
{
|
||||
memoryFinishedSpriteRenderer.color = Color.white;
|
||||
SetMemoryColor(Color.white);
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
|
||||
if (useYarn)
|
||||
{
|
||||
if (fadeMaterial != null)
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", FadeMaterialStartValue);
|
||||
SetFloatIfExists(DirectionalDistortionFadeId, FadeMaterialStartValue);
|
||||
var memoryKey = string.Format(MemorySpritePath, memName);
|
||||
var memoryHandle = ResourceSystem.LoadAsync<Sprite>(memoryKey);
|
||||
yield return memoryHandle;
|
||||
Sprite memSprite = memoryHandle.Status == AsyncOperationStatus.Succeeded ? memoryHandle.Result : null;
|
||||
memoryFinishedSpriteRenderer.sprite = memSprite;
|
||||
memoryFinishedImageNoColor.sprite = memSprite;
|
||||
SetMemorySprite(memSprite);
|
||||
if (memSprite == null)
|
||||
{
|
||||
Debug.LogError("Memory not found: " + memoryKey);
|
||||
@@ -320,13 +408,14 @@ namespace AibisDream.FixSystem
|
||||
isPlayingFaderSound = false;
|
||||
}
|
||||
|
||||
frequencySlider.TargetValue = GetRandomNumber();
|
||||
claritySlider.TargetValue = GetRandomNumber();
|
||||
frequencySlider.TargetValue = GetRandomSearchTarget(frequencySlider);
|
||||
claritySlider.TargetValue = GetRandomSearchTarget(claritySlider);
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
|
||||
if (punchTape == null)
|
||||
{
|
||||
Debug.Log("no punchTape");
|
||||
StartStandbyOverlay();
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -338,30 +427,31 @@ namespace AibisDream.FixSystem
|
||||
yield break;
|
||||
}
|
||||
|
||||
StopStandbyOverlay();
|
||||
ShowSearchOverlay();
|
||||
|
||||
var searchMemoryKey = string.Format(MemorySpritePath, _activePunchTape.MemoryIndex);
|
||||
var searchMemoryHandle = ResourceSystem.LoadAsync<Sprite>(searchMemoryKey);
|
||||
yield return searchMemoryHandle;
|
||||
Sprite memorySprite = searchMemoryHandle.Status == AsyncOperationStatus.Succeeded
|
||||
? searchMemoryHandle.Result
|
||||
: null;
|
||||
memoryFinishedSpriteRenderer.sprite = memorySprite;
|
||||
memoryFinishedImageNoColor.sprite = memorySprite;
|
||||
SetMemorySprite(memorySprite);
|
||||
if (memorySprite == null)
|
||||
{
|
||||
Debug.LogError("Memory not found: " + searchMemoryKey);
|
||||
}
|
||||
|
||||
memoryProcessVolume.weight = 1;
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/mem_load");
|
||||
|
||||
yield return new WaitForSeconds(SearchStartDelay);
|
||||
AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 1);
|
||||
|
||||
memoryFinishedSpriteRenderer.color = Color.white;
|
||||
memoryProcessVolume.weight = 0;
|
||||
SetMemoryColor(Color.white);
|
||||
isProcessing = true;
|
||||
frequencySlider.PrepareForSearch();
|
||||
claritySlider.PrepareForSearch();
|
||||
ClearSearchOverlay();
|
||||
bool freqUnlocked = false;
|
||||
bool clarityUnlocked = false;
|
||||
frequencySlider.UnlockSlider(() => freqUnlocked = true);
|
||||
@@ -371,10 +461,227 @@ namespace AibisDream.FixSystem
|
||||
claritySlider.SetInteractable(true);
|
||||
}
|
||||
|
||||
private void ApplyFrequencyTuning(float strength)
|
||||
{
|
||||
if (!useSpriteShaderTuning || fadeMaterial == null)
|
||||
return;
|
||||
|
||||
bool active = strength > 0.001f;
|
||||
SetFloatIfExists(FrequencyTuningId, strength);
|
||||
SetFloatIfExists(EnablePixelateId, active ? 1f : 0f);
|
||||
SetFloatIfExists(PixelateFadeId, strength);
|
||||
SetFloatIfExists(PixelatePixelDensityId, Mathf.Lerp(MaxPixelDensity, MinPixelDensity, strength));
|
||||
SetFloatIfExists(EnableGaussianBlurId, active ? 1f : 0f);
|
||||
SetFloatIfExists(GaussianBlurFadeId, strength * MaxFrequencyBlur);
|
||||
SetFloatIfExists(GaussianBlurOffsetId, Mathf.Lerp(0.05f, 0.35f, strength));
|
||||
}
|
||||
|
||||
private void ApplyClarityTuning(float strength)
|
||||
{
|
||||
if (!useSpriteShaderTuning || fadeMaterial == null)
|
||||
return;
|
||||
|
||||
bool active = strength > 0.001f;
|
||||
SetFloatIfExists(ClarityTuningId, strength);
|
||||
SetFloatIfExists(EnableUvDistortId, active ? 1f : 0f);
|
||||
SetFloatIfExists(UvDistortFadeId, strength * MaxClarityDistortion);
|
||||
}
|
||||
|
||||
private void ResetSpriteTuning()
|
||||
{
|
||||
if (!useSpriteShaderTuning || fadeMaterial == null)
|
||||
return;
|
||||
|
||||
ApplyFrequencyTuning(0f);
|
||||
ApplyClarityTuning(0f);
|
||||
}
|
||||
|
||||
private void SetFloatIfExists(int propertyId, float value)
|
||||
{
|
||||
SetFloatIfExists(fadeMaterial, propertyId, value);
|
||||
if (runtimeNoColorMemoryMaterial != null && runtimeNoColorMemoryMaterial != fadeMaterial)
|
||||
SetFloatIfExists(runtimeNoColorMemoryMaterial, propertyId, value);
|
||||
}
|
||||
|
||||
private static void SetVolumeWeight(Volume volume, float weight)
|
||||
{
|
||||
if (volume != null)
|
||||
volume.weight = weight;
|
||||
}
|
||||
|
||||
private static void SetFloatIfExists(Material material, int propertyId, float value)
|
||||
{
|
||||
if (material != null && material.HasProperty(propertyId))
|
||||
material.SetFloat(propertyId, value);
|
||||
}
|
||||
|
||||
private static void AppendMaterialFloatTween(
|
||||
DG.Tweening.Sequence sequence,
|
||||
Material material,
|
||||
float target,
|
||||
string property,
|
||||
float duration)
|
||||
{
|
||||
if (sequence != null && material != null && material.HasProperty(property))
|
||||
sequence.Join(material.DOFloat(target, property, duration));
|
||||
}
|
||||
|
||||
private void PrepareRuntimeMemoryMaterial()
|
||||
{
|
||||
if (!useSpriteShaderTuning || !HasMemoryGraphic())
|
||||
return;
|
||||
|
||||
var baseMaterial = GetColorMemoryMaterial();
|
||||
if (baseMaterial == null)
|
||||
baseMaterial = fadeMaterial;
|
||||
|
||||
var shader = baseMaterial != null ? baseMaterial.shader : null;
|
||||
if (shader == null || shader.name != "AibisDream/MemoryTuningSprite")
|
||||
shader = Shader.Find("AibisDream/MemoryTuningSprite");
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogWarning("[MemoryProcess] 未找到 AibisDream/MemoryTuningSprite,调频将回退到当前材质表现。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeMemoryMaterial = CreateRuntimeMemoryMaterial(baseMaterial, shader, "Color", 0f, 1f);
|
||||
SetColorMemoryMaterial(runtimeMemoryMaterial);
|
||||
fadeMaterial = runtimeMemoryMaterial;
|
||||
|
||||
var noColorBaseMaterial = GetNoColorMemoryMaterial();
|
||||
if (noColorBaseMaterial != null)
|
||||
{
|
||||
runtimeNoColorMemoryMaterial = CreateRuntimeMemoryMaterial(
|
||||
noColorBaseMaterial,
|
||||
shader,
|
||||
"NoColor",
|
||||
1f,
|
||||
1.8f);
|
||||
SetNoColorMemoryMaterial(runtimeNoColorMemoryMaterial);
|
||||
}
|
||||
|
||||
ResetSpriteTuning();
|
||||
}
|
||||
|
||||
private static Material CreateRuntimeMemoryMaterial(
|
||||
Material source,
|
||||
Shader shader,
|
||||
string suffix,
|
||||
float grayscale,
|
||||
float contrast)
|
||||
{
|
||||
var material = source != null ? new Material(source) : new Material(shader);
|
||||
material.shader = shader;
|
||||
material.name = $"{(source != null ? source.name : shader.name)}_RuntimeMemoryTuning_{suffix}";
|
||||
SetFloatIfExists(material, MemoryGrayscaleId, grayscale);
|
||||
SetFloatIfExists(material, MemoryContrastId, contrast);
|
||||
return material;
|
||||
}
|
||||
|
||||
private bool HasMemoryGraphic()
|
||||
{
|
||||
return memoryFinishedImage != null || legacyMemoryFinishedSpriteRenderer != null;
|
||||
}
|
||||
|
||||
private void StartStandbyOverlay()
|
||||
{
|
||||
if (standbyOverlay == null || isProcessing)
|
||||
return;
|
||||
|
||||
overlayTween?.Kill();
|
||||
SetOverlayAlpha(standbyOverlayMinAlpha);
|
||||
overlayTween = standbyOverlay
|
||||
.DOFade(standbyOverlayMaxAlpha, Mathf.Max(0.01f, standbyOverlayPulseDuration))
|
||||
.SetLoops(-1, LoopType.Yoyo)
|
||||
.SetEase(Ease.InOutSine);
|
||||
}
|
||||
|
||||
private void StopStandbyOverlay()
|
||||
{
|
||||
overlayTween?.Kill();
|
||||
overlayTween = null;
|
||||
}
|
||||
|
||||
private void ShowSearchOverlay()
|
||||
{
|
||||
StopStandbyOverlay();
|
||||
SetOverlayAlpha(searchOverlayAlpha);
|
||||
}
|
||||
|
||||
private void ClearSearchOverlay()
|
||||
{
|
||||
StopStandbyOverlay();
|
||||
SetOverlayAlpha(0f);
|
||||
}
|
||||
|
||||
private void SetOverlayAlpha(float alpha)
|
||||
{
|
||||
if (standbyOverlay == null)
|
||||
return;
|
||||
|
||||
Color color = standbyOverlay.color;
|
||||
color.a = Mathf.Clamp01(alpha);
|
||||
standbyOverlay.color = color;
|
||||
}
|
||||
|
||||
private void SetMemorySprite(Sprite sprite)
|
||||
{
|
||||
if (memoryFinishedImage != null)
|
||||
memoryFinishedImage.sprite = sprite;
|
||||
if (memoryFinishedNoColorImage != null)
|
||||
memoryFinishedNoColorImage.sprite = sprite;
|
||||
if (legacyMemoryFinishedSpriteRenderer != null)
|
||||
legacyMemoryFinishedSpriteRenderer.sprite = sprite;
|
||||
if (legacyMemoryFinishedImageNoColor != null)
|
||||
legacyMemoryFinishedImageNoColor.sprite = sprite;
|
||||
}
|
||||
|
||||
private void SetMemoryColor(Color color)
|
||||
{
|
||||
if (memoryFinishedImage != null)
|
||||
memoryFinishedImage.color = color;
|
||||
if (legacyMemoryFinishedSpriteRenderer != null)
|
||||
legacyMemoryFinishedSpriteRenderer.color = color;
|
||||
}
|
||||
|
||||
private Material GetColorMemoryMaterial()
|
||||
{
|
||||
if (memoryFinishedImage != null)
|
||||
return memoryFinishedImage.material;
|
||||
return legacyMemoryFinishedSpriteRenderer != null
|
||||
? legacyMemoryFinishedSpriteRenderer.sharedMaterial
|
||||
: null;
|
||||
}
|
||||
|
||||
private Material GetNoColorMemoryMaterial()
|
||||
{
|
||||
if (memoryFinishedNoColorImage != null)
|
||||
return memoryFinishedNoColorImage.material;
|
||||
return legacyMemoryFinishedImageNoColor != null
|
||||
? legacyMemoryFinishedImageNoColor.sharedMaterial
|
||||
: null;
|
||||
}
|
||||
|
||||
private void SetColorMemoryMaterial(Material material)
|
||||
{
|
||||
if (memoryFinishedImage != null)
|
||||
memoryFinishedImage.material = material;
|
||||
if (legacyMemoryFinishedSpriteRenderer != null)
|
||||
legacyMemoryFinishedSpriteRenderer.sharedMaterial = material;
|
||||
}
|
||||
|
||||
private void SetNoColorMemoryMaterial(Material material)
|
||||
{
|
||||
if (memoryFinishedNoColorImage != null)
|
||||
memoryFinishedNoColorImage.material = material;
|
||||
if (legacyMemoryFinishedImageNoColor != null)
|
||||
legacyMemoryFinishedImageNoColor.sharedMaterial = material;
|
||||
}
|
||||
|
||||
|
||||
public bool IsProcessing()
|
||||
{
|
||||
return isProcessing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,19 @@ namespace AibisDream.FixSystem
|
||||
private Slider slider;
|
||||
private PlayableDirector playableDirector;
|
||||
private Volume _volume;
|
||||
private bool driveVolume = true;
|
||||
|
||||
/// <summary>视觉 Lock/Unlock(Timeline);true = 收起未开,false = 已打开。</summary>
|
||||
private bool isLocked;
|
||||
|
||||
public float TargetValue { get; set; }
|
||||
public float StartValue => defaultValue;
|
||||
public float MaxValue => sliderMaxValue;
|
||||
public bool IsFound { get; private set; }
|
||||
|
||||
public event Action OnFound;
|
||||
public event Action OnLost;
|
||||
public event Action<float> OnTuningChanged;
|
||||
|
||||
public void Init(Volume volume)
|
||||
{
|
||||
@@ -35,6 +39,7 @@ namespace AibisDream.FixSystem
|
||||
playableDirector = GetComponent<PlayableDirector>();
|
||||
|
||||
_volume = volume;
|
||||
driveVolume = true;
|
||||
IsFound = false;
|
||||
slider.onValueChanged.RemoveAllListeners();
|
||||
slider.onValueChanged.AddListener(HandleValueChanged);
|
||||
@@ -49,6 +54,13 @@ namespace AibisDream.FixSystem
|
||||
/// <summary>仅控制是否可拖动,与 <see cref="UnlockSlider"/> / <see cref="LockSlider"/> 无关。</summary>
|
||||
public void SetInteractable(bool interactable) => slider.interactable = interactable;
|
||||
|
||||
public void SetVolumeOutputEnabled(bool enabled)
|
||||
{
|
||||
driveVolume = enabled;
|
||||
if (!driveVolume && _volume != null)
|
||||
_volume.weight = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新一轮搜索前复位滑条与 IsFound(不播 Timeline、不改变视觉锁状态)。
|
||||
/// 需先设置好 <see cref="TargetValue"/>。
|
||||
@@ -116,9 +128,11 @@ namespace AibisDream.FixSystem
|
||||
);
|
||||
float t = Mathf.Clamp01(distance / maxDistance);
|
||||
|
||||
if (_volume != null)
|
||||
if (_volume != null && driveVolume)
|
||||
_volume.weight = Mathf.Lerp(0, 1, t);
|
||||
|
||||
OnTuningChanged?.Invoke(t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!84 &8400000
|
||||
RenderTexture:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: maskTexture
|
||||
m_ImageContentsHash:
|
||||
serializedVersion: 2
|
||||
Hash: 00000000000000000000000000000000
|
||||
m_ForcedFallbackFormat: 4
|
||||
m_DownscaleFallback: 0
|
||||
m_IsAlphaChannelOptional: 0
|
||||
serializedVersion: 5
|
||||
m_Width: 1920
|
||||
m_Height: 1080
|
||||
m_AntiAliasing: 1
|
||||
m_MipCount: -1
|
||||
m_DepthStencilFormat: 94
|
||||
m_ColorFormat: 8
|
||||
m_MipMap: 0
|
||||
m_GenerateMips: 1
|
||||
m_SRGB: 0
|
||||
m_UseDynamicScale: 0
|
||||
m_BindMS: 0
|
||||
m_EnableCompatibleFormat: 1
|
||||
m_EnableRandomWrite: 0
|
||||
m_TextureSettings:
|
||||
serializedVersion: 2
|
||||
m_FilterMode: 1
|
||||
m_Aniso: 0
|
||||
m_MipBias: 0
|
||||
m_WrapU: 1
|
||||
m_WrapV: 1
|
||||
m_WrapW: 1
|
||||
m_Dimension: 2
|
||||
m_VolumeDepth: 1
|
||||
m_ShadowSamplingMode: 2
|
||||
Binary file not shown.
@@ -1,140 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5661e676977c3c348b0f50c7f1737dd3
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -29,8 +29,8 @@ namespace AibisDream.FixSystem
|
||||
ufSystem.OpenUFView();
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep);
|
||||
eyeSystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Eye);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
}
|
||||
|
||||
public IEnumerator Exit(FixTransition transition)
|
||||
@@ -72,9 +72,8 @@ namespace AibisDream.FixSystem
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync(0.5f);
|
||||
yield return CameraKit.Instance.SwitchCamera(CameraEnum.EyeDeep);
|
||||
eyeSystem.OnEnter();
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
|
||||
BubbleSlotKit.Instance.LoadBubbles(BubbleSlotEnum.Eye);
|
||||
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.5f);
|
||||
}
|
||||
|
||||
public IEnumerator EnterImmediate(FixTransition transition)
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43cd57e530f22b840b58612b2a536e20
|
||||
NativeFormatImporter:
|
||||
guid: 8f92d49bad6ceaa4983976ea6bb47c46
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 8400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
[CustomEditor(typeof(EyeSystem))]
|
||||
public class EyeSystemEditor : Editor
|
||||
{
|
||||
private bool editorOverlayPreview;
|
||||
private float editorBlinkPreview = 1f;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawPropertiesExcluding(serializedObject, "m_Script", "visualSettings", "replayFocusedEffectOnSettingsChange");
|
||||
|
||||
SerializedProperty visualSettings = serializedObject.FindProperty("visualSettings");
|
||||
SerializedProperty replayOnChange = serializedObject.FindProperty("replayFocusedEffectOnSettingsChange");
|
||||
|
||||
if (visualSettings != null)
|
||||
{
|
||||
EditorGUILayout.Space(6f);
|
||||
EditorGUILayout.LabelField("Visual Tuning", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(visualSettings, includeChildren: true);
|
||||
bool visualChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.PropertyField(replayOnChange);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"常驻视线 = 椭圆(eyeWidth / eyeHeight)。\n" +
|
||||
"眨眼 = 同一椭圆纵向压扁(BlinkOpen 1→0),不是另一套眼皮形状。\n" +
|
||||
"用「BlinkOpen 预览」滑条看闭合;eyeWidth / eyeHeight 在 BlinkOpen=1 时调。",
|
||||
MessageType.Info);
|
||||
|
||||
DrawViewportOverlayControls(visualChanged);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("立即应用(Idle 预览)"))
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
GetEyeSystem().ApplyVisualSettingsNow(previewIdleState: true);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("重播当前目标效果"))
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
GetEyeSystem().ApplyVisualSettingsNow(replayFocusedEffect: true);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (visualChanged)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
var system = GetEyeSystem();
|
||||
system.ApplyVisualSettingsNow(
|
||||
previewIdleState: !Application.isPlaying,
|
||||
replayFocusedEffect: Application.isPlaying && replayOnChange.boolValue);
|
||||
|
||||
if (editorOverlayPreview)
|
||||
{
|
||||
RefreshEditorOverlayPreview(system);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"visualSettings 未找到。请确认 EyeSystem.cs 已编译通过。",
|
||||
MessageType.Warning);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawViewportOverlayControls(bool visualChanged)
|
||||
{
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.LabelField("Viewport Overlay 预览", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
editorOverlayPreview = EditorGUILayout.Toggle("Edit 模式预览遮罩", editorOverlayPreview);
|
||||
if (EditorGUI.EndChangeCheck() || visualChanged)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
var system = GetEyeSystem();
|
||||
if (editorOverlayPreview)
|
||||
{
|
||||
RefreshEditorOverlayPreview(system);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureOverlay(system).ExitEditorPreview();
|
||||
editorBlinkPreview = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = editorOverlayPreview;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
editorBlinkPreview = EditorGUILayout.Slider("BlinkOpen 预览(眨眼)", editorBlinkPreview, 0f, 1f);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var overlay = EnsureOverlay(GetEyeSystem());
|
||||
overlay.SetBlinkOpenPreview(editorBlinkPreview);
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("BlinkOpen = 睁开"))
|
||||
{
|
||||
editorBlinkPreview = 1f;
|
||||
EnsureOverlay(GetEyeSystem()).SetBlinkOpenPreview(1f);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("BlinkOpen = 闭合"))
|
||||
{
|
||||
editorBlinkPreview = 0f;
|
||||
EnsureOverlay(GetEyeSystem()).SetBlinkOpenPreview(0f);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUI.enabled = Application.isPlaying;
|
||||
if (GUILayout.Button("测试眨眼动画"))
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
var overlay = EnsureOverlay(GetEyeSystem());
|
||||
editorBlinkPreview = 1f;
|
||||
overlay.SetBlinkOpenPreview(1f);
|
||||
overlay.PlayBlink();
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
if (GUILayout.Button("刷新遮罩参数"))
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
RefreshEditorOverlayPreview(GetEyeSystem());
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void RefreshEditorOverlayPreview(EyeSystem system)
|
||||
{
|
||||
var overlay = EnsureOverlay(system);
|
||||
overlay.EnterEditorPreview(system.VisualSettings);
|
||||
overlay.SetBlinkOpenPreview(editorBlinkPreview);
|
||||
}
|
||||
|
||||
private static EyeViewportOverlay EnsureOverlay(EyeSystem system)
|
||||
{
|
||||
var overlay = system.GetComponentInChildren<EyeViewportOverlay>(true);
|
||||
if (overlay == null)
|
||||
{
|
||||
overlay = system.gameObject.AddComponent<EyeViewportOverlay>();
|
||||
}
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
private EyeSystem GetEyeSystem()
|
||||
{
|
||||
return (EyeSystem)target;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (!editorOverlayPreview || target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
editorOverlayPreview = false;
|
||||
editorBlinkPreview = 1f;
|
||||
var system = target as EyeSystem;
|
||||
system?.GetComponentInChildren<EyeViewportOverlay>(true)?.ExitEditorPreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 193d982b27c7dd34cb2aa48c07b1df09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
@@ -19,20 +19,93 @@ namespace AibisDream
|
||||
{
|
||||
private const string MemorySpritePath = "Sprite/Memory/{0}.png";
|
||||
|
||||
[SerializeField] private List<EyeTarget> targets = new(); // 保存目标列表
|
||||
public List<EyeTarget> Targets => targets; // 提供目标列表的公共访问
|
||||
private EyeTarget currentTarget; // 当前选中的目标
|
||||
public EyeTarget CurrentTarget => currentTarget; // 提供当前目标的公共访问
|
||||
public RawImage eyeImage;
|
||||
[SerializeField] private List<EyeTarget> targets = new();
|
||||
public List<EyeTarget> Targets => targets;
|
||||
|
||||
private EyeTarget currentTarget;
|
||||
public EyeTarget CurrentTarget => currentTarget;
|
||||
|
||||
[Header("Memory Overlay (UI)")]
|
||||
public Image memoryRender;
|
||||
public GameObject eyeView;
|
||||
public ViewCameraManager cameraManager;
|
||||
|
||||
[Header("Focus Pan Bounds")]
|
||||
[SerializeField] private Vector2 focusMinBounds = new(30.2f, -5f);
|
||||
[SerializeField] private Vector2 focusMaxBounds = new(50.2f, 5f);
|
||||
|
||||
[Header("Visual Tuning")]
|
||||
[SerializeField] private EyeVisualSettings visualSettings = new();
|
||||
|
||||
[SerializeField] private EyeViewportOverlay viewportOverlay;
|
||||
|
||||
[Tooltip("Play 模式下改 Visual Tuning 后,自动对当前目标重播一次 Focused 效果(如 ColorIn)。")]
|
||||
[SerializeField] private bool replayFocusedEffectOnSettingsChange = true;
|
||||
|
||||
public EyeVisualSettings VisualSettings => visualSettings;
|
||||
|
||||
private IEyeStateEffect eyeStateEffect;
|
||||
private EyeColorState _currentState = EyeColorState.CannotImagineColor;
|
||||
private MouseFollowAndZoom mouseFollowAndZoom;
|
||||
private Sequence glitchin;
|
||||
|
||||
/// <summary>获取当前 Eye 颜色阶段。</summary>
|
||||
public EyeColorState GetCurrentColorState() => _currentState;
|
||||
|
||||
public float GetEffectTransitionDuration() => visualSettings.GetDurationForState(_currentState);
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ApplyVisualSettingsNow(
|
||||
previewIdleState: !Application.isPlaying,
|
||||
replayFocusedEffect: Application.isPlaying && replayFocusedEffectOnSettingsChange,
|
||||
touchViewportOverlay: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Visual Tuning 推送到所有 EyeTarget。Edit 模式预览 idle;Play 模式可选重播当前目标效果。
|
||||
/// </summary>
|
||||
public void ApplyVisualSettingsNow(
|
||||
bool previewIdleState = false,
|
||||
bool replayFocusedEffect = false,
|
||||
bool touchViewportOverlay = true)
|
||||
{
|
||||
if (targets == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
target.BindVisualSettings(visualSettings);
|
||||
if (previewIdleState)
|
||||
{
|
||||
target.ApplyIdlePreviewState();
|
||||
}
|
||||
}
|
||||
|
||||
if (touchViewportOverlay && Application.isPlaying)
|
||||
{
|
||||
EnsureViewportOverlay()?.ApplySettings(visualSettings);
|
||||
}
|
||||
|
||||
if (!replayFocusedEffect || !Application.isPlaying || currentTarget == null || eyeStateEffect == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentTarget.KillVisualTweens();
|
||||
eyeStateEffect.ApplyFocusedEffect(currentTarget, this);
|
||||
}
|
||||
|
||||
private void ApplyVisualSettingsToTargets()
|
||||
{
|
||||
ApplyVisualSettingsNow();
|
||||
}
|
||||
|
||||
public enum EyeColorState
|
||||
{
|
||||
CanImagineColor,
|
||||
@@ -42,49 +115,85 @@ namespace AibisDream
|
||||
HaveChip
|
||||
}
|
||||
|
||||
private Tween hsvShiftTween;
|
||||
private MouseFollowAndZoom mouseFollowAndZoom;
|
||||
|
||||
/// <summary>
|
||||
/// 眼睛 RawImage 的故障/色相/饱和度等效果依赖 AllIn1SpriteShader(或兼容)材质上的属性。
|
||||
/// 若未在 Inspector 指定 Material,Unity 会使用 Default UI Material(UI/Default),不含 _HsvShift 等属性。
|
||||
/// </summary>
|
||||
private bool TryGetEyeImageMaterialForEffects(out Material material)
|
||||
private void Awake()
|
||||
{
|
||||
material = null;
|
||||
if (eyeImage == null)
|
||||
Init();
|
||||
}
|
||||
|
||||
private EyeViewportOverlay EnsureViewportOverlay()
|
||||
{
|
||||
if (viewportOverlay != null)
|
||||
{
|
||||
Debug.LogError("[EyeSystem] eyeImage 未赋值。", this);
|
||||
return false;
|
||||
return viewportOverlay;
|
||||
}
|
||||
|
||||
material = eyeImage.material;
|
||||
if (material == null || !material.HasProperty("_HsvShift"))
|
||||
viewportOverlay = GetComponentInChildren<EyeViewportOverlay>(true);
|
||||
if (viewportOverlay == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"[EyeSystem] eyeImage 需要指定 AllIn1SpriteShader(或兼容)材质;当前为 " +
|
||||
(material != null && material.shader != null ? material.shader.name : "null") +
|
||||
"。请在场景中选中该 RawImage,指定项目内 EyeMaterials / AllIn1 材质,勿留空(否则会使用 Default UI Material)。",
|
||||
this);
|
||||
viewportOverlay = gameObject.AddComponent<EyeViewportOverlay>();
|
||||
}
|
||||
|
||||
return viewportOverlay;
|
||||
}
|
||||
|
||||
private bool EnsureMouseFollowAndZoom()
|
||||
{
|
||||
if (mouseFollowAndZoom != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
mouseFollowAndZoom = FindObjectOfType<MouseFollowAndZoom>(true);
|
||||
if (mouseFollowAndZoom == null)
|
||||
{
|
||||
Debug.LogError("[EyeSystem] MouseFollowAndZoom 未找到,无法进入视觉模块。", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
private Vector2 GetFocusViewportSize()
|
||||
{
|
||||
Init();
|
||||
var focusCamera = Camera.main;
|
||||
if (focusCamera == null || !focusCamera.orthographic)
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
float height = focusCamera.orthographicSize * 2f;
|
||||
return new Vector2(height * focusCamera.aspect, height);
|
||||
}
|
||||
|
||||
private static Transform GetFocusTransform(EyeTarget target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return target.targetTransform ? target.targetTransform : target.transform;
|
||||
}
|
||||
|
||||
public void FocusOnTarget(EyeTarget target, float duration)
|
||||
{
|
||||
Transform focusTransform = GetFocusTransform(target);
|
||||
if (focusTransform == null || !EnsureMouseFollowAndZoom())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mouseFollowAndZoom.MoveFocusTo(
|
||||
focusTransform,
|
||||
duration,
|
||||
focusMinBounds,
|
||||
focusMaxBounds,
|
||||
GetFocusViewportSize());
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
|
||||
if (cameraManager != null)
|
||||
{
|
||||
cameraManager.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public EyeSnapshotDto CaptureEyeSnapshot()
|
||||
@@ -107,61 +216,58 @@ namespace AibisDream
|
||||
|
||||
public void OnEnter()
|
||||
{
|
||||
// 先激活 ViewCamera
|
||||
if (cameraManager != null)
|
||||
if (!EnsureMouseFollowAndZoom())
|
||||
{
|
||||
cameraManager.gameObject.SetActive(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
mouseFollowAndZoom.SetIsActive(true);
|
||||
mouseFollowAndZoom.SetIsLock(false);
|
||||
// 激活 ViewCamera 后再打开 EyeView
|
||||
OpenEyeView();
|
||||
// 进入视图时的逻辑
|
||||
Debug.Log("EyeView entered.");
|
||||
mouseFollowAndZoom.ResetPanOrigin();
|
||||
ApplyVisualSettingsToTargets();
|
||||
InitializeAllTargets();
|
||||
EnsureViewportOverlay()?.EnterModule(visualSettings);
|
||||
}
|
||||
|
||||
public void OpenEyeView()
|
||||
private void InitializeAllTargets()
|
||||
{
|
||||
eyeView.SetActive(true);
|
||||
}
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
target.Init();
|
||||
}
|
||||
|
||||
public void CloseEyeView()
|
||||
{
|
||||
eyeView.SetActive(false);
|
||||
if (currentTarget != null)
|
||||
{
|
||||
eyeStateEffect?.InitializeEffect(currentTarget, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnShow()
|
||||
{
|
||||
// 离开视图时的逻辑
|
||||
Debug.Log("EyeView showed.");
|
||||
}
|
||||
|
||||
public void OnExit()
|
||||
{
|
||||
EnsureViewportOverlay()?.ExitModule();
|
||||
|
||||
if (currentTarget != null)
|
||||
{
|
||||
eyeStateEffect?.CleanupEffect(CurrentTarget);
|
||||
}
|
||||
|
||||
mouseFollowAndZoom.SetIsActive(false);
|
||||
currentTarget = null;
|
||||
// 先关闭 EyeView
|
||||
CloseEyeView();
|
||||
// 再关闭 ViewCamera
|
||||
if (cameraManager != null)
|
||||
if (EnsureMouseFollowAndZoom())
|
||||
{
|
||||
cameraManager.gameObject.SetActive(false);
|
||||
mouseFollowAndZoom.SetIsActive(false);
|
||||
}
|
||||
// 离开视图时的逻辑
|
||||
Debug.Log("EyeView exited.");
|
||||
|
||||
currentTarget = null;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ApplyVisualSettingsToTargets();
|
||||
SetEyeColorState(_currentState);
|
||||
|
||||
mouseFollowAndZoom = FindObjectOfType<MouseFollowAndZoom>();
|
||||
EnsureMouseFollowAndZoom();
|
||||
}
|
||||
|
||||
public EyeTarget FindEyeTargetByName(string name)
|
||||
@@ -180,9 +286,20 @@ namespace AibisDream
|
||||
StartCoroutine(SetTargetCoroutine(newTarget.gameObject.name));
|
||||
}
|
||||
|
||||
[YarnCommand("EyeBlink")]
|
||||
public void EyeBlink(float duration = 0f)
|
||||
{
|
||||
EnsureViewportOverlay()?.PlayBlink(duration > 0f ? duration : null);
|
||||
}
|
||||
|
||||
[YarnCommand("set_Eyetarget")]
|
||||
public IEnumerator SetTargetCoroutine(string targetName, bool startDialogue = true)
|
||||
{
|
||||
if (!EnsureMouseFollowAndZoom())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
mouseFollowAndZoom.SetIsLock(true);
|
||||
EyeTarget target = FindEyeTargetByName(targetName);
|
||||
if (target == null)
|
||||
@@ -192,13 +309,13 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
YarnVariableStorage.Instance.SetValue("$currentEyeTarget", target.name);
|
||||
StartCoroutine(EyeEffectFadeOut());
|
||||
StartCoroutine(EyeEffect_FadeOut());
|
||||
|
||||
cameraManager.MoveCameraTo(target.targetTransform ? target.targetTransform : target.transform, 1f);
|
||||
FocusOnTarget(target, 1f);
|
||||
|
||||
if (currentTarget != null)
|
||||
{
|
||||
currentTarget.BlurIn(eyeStateEffect.TransitionDuration, false);
|
||||
currentTarget.BlurIn(visualSettings.targetSwitchDuration);
|
||||
}
|
||||
|
||||
if (currentTarget == target)
|
||||
@@ -206,16 +323,16 @@ namespace AibisDream
|
||||
DialogController.Instance.StartDialogNode("重复看同一个目标");
|
||||
currentTarget = target;
|
||||
mouseFollowAndZoom.SetIndicatorTarget(currentTarget);
|
||||
currentTarget.BlurOut(eyeStateEffect.TransitionDuration, false);
|
||||
yield return new WaitForSeconds(eyeStateEffect.TransitionDuration);
|
||||
currentTarget.BlurOut(visualSettings.targetSwitchDuration);
|
||||
yield return new WaitForSeconds(visualSettings.targetSwitchDuration);
|
||||
yield break;
|
||||
}
|
||||
|
||||
currentTarget = target;
|
||||
mouseFollowAndZoom.SetIndicatorTarget(currentTarget);
|
||||
currentTarget.BlurOut(eyeStateEffect.TransitionDuration, false);
|
||||
currentTarget.BlurOut(visualSettings.targetSwitchDuration);
|
||||
|
||||
yield return new WaitForSeconds(eyeStateEffect.TransitionDuration);
|
||||
yield return new WaitForSeconds(visualSettings.targetSwitchDuration);
|
||||
|
||||
if (startDialogue)
|
||||
{
|
||||
@@ -226,48 +343,43 @@ namespace AibisDream
|
||||
[YarnCommand("Set_MouseMovementLockState")]
|
||||
public void SetMouseMovementLockState(bool isActive)
|
||||
{
|
||||
if (!EnsureMouseFollowAndZoom())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mouseFollowAndZoom.SetIsLock(isActive);
|
||||
}
|
||||
|
||||
[YarnCommand("EyeEffect_FadeIn")]
|
||||
public IEnumerator EyeEffectFadeIn()
|
||||
public IEnumerator EyeEffect_FadeIn()
|
||||
{
|
||||
if (currentTarget != null)
|
||||
{
|
||||
// AudioManager.RandomPlayInteraction("colorimagine");
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
|
||||
eyeStateEffect?.ApplyFocusedEffect(currentTarget);
|
||||
eyeStateEffect?.ApplyFocusedEffect(currentTarget, this);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(eyeStateEffect.TransitionDuration);
|
||||
yield return new WaitForSeconds(GetEffectTransitionDuration());
|
||||
}
|
||||
|
||||
[YarnCommand("EyeEffect_FadeOut")]
|
||||
public IEnumerator EyeEffectFadeOut()
|
||||
public IEnumerator EyeEffect_FadeOut()
|
||||
{
|
||||
if (currentTarget != null)
|
||||
{
|
||||
// AudioManager.RandomPlayInteraction("colorimagine");
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
eyeStateEffect?.ApplyUnfocusedEffect(currentTarget);
|
||||
eyeStateEffect?.ApplyUnfocusedEffect(currentTarget, this);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(eyeStateEffect.TransitionDuration);
|
||||
yield return new WaitForSeconds(GetEffectTransitionDuration());
|
||||
}
|
||||
|
||||
[YarnCommand("EyeEffect_Init")]
|
||||
public void EyeEffectInit()
|
||||
{
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
target.Init();
|
||||
}
|
||||
|
||||
if (currentTarget != null)
|
||||
{
|
||||
eyeStateEffect?.InitializeEffect(currentTarget, cameraManager);
|
||||
}
|
||||
ApplyVisualSettingsToTargets();
|
||||
InitializeAllTargets();
|
||||
}
|
||||
|
||||
[YarnCommand("EyeEffect_Clean")]
|
||||
@@ -282,127 +394,71 @@ namespace AibisDream
|
||||
[YarnCommand("set_EyeColorState")]
|
||||
public void SetEyeColorStateForYarn(string state)
|
||||
{
|
||||
// Try to parse the string as an EyeColorState
|
||||
if (!Enum.TryParse(state, true, out EyeColorState eyeState))
|
||||
{
|
||||
throw new ArgumentException("Invalid eye state: " + state, nameof(state));
|
||||
}
|
||||
|
||||
// Start the coroutine for setting the eye state
|
||||
SetEyeColorState(eyeState);
|
||||
}
|
||||
|
||||
private void SetEyeColorState(EyeColorState state)
|
||||
{
|
||||
_currentState = state;
|
||||
switch (state)
|
||||
eyeStateEffect = state switch
|
||||
{
|
||||
case EyeColorState.CanImagineColor:
|
||||
eyeStateEffect = new CanImagineColorEffect();
|
||||
break;
|
||||
case EyeColorState.CannotImagineColor:
|
||||
eyeStateEffect = new CannotImagineColorEffect();
|
||||
break;
|
||||
case EyeColorState.EyeDisorder:
|
||||
eyeStateEffect = new EyeDisorderEffect();
|
||||
break;
|
||||
case EyeColorState.CanSeeColor:
|
||||
eyeStateEffect = new CanSeeColorEffect();
|
||||
break;
|
||||
case EyeColorState.HaveChip:
|
||||
eyeStateEffect = new HaveChip();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unknown eye state: " + state.ToString(), nameof(state));
|
||||
}
|
||||
EyeColorState.CanImagineColor => new CanImagineColorEffect(),
|
||||
EyeColorState.CannotImagineColor => new CannotImagineColorEffect(),
|
||||
EyeColorState.EyeDisorder => new EyeDisorderEffect(),
|
||||
EyeColorState.CanSeeColor => new CanSeeColorEffect(),
|
||||
EyeColorState.HaveChip => new HaveChip(),
|
||||
_ => throw new ArgumentException("Unknown eye state: " + state, nameof(state))
|
||||
};
|
||||
}
|
||||
|
||||
[YarnCommand("Set_Saturation")]
|
||||
public IEnumerator Set_Saturation(float targetSaturation, float duration)
|
||||
{
|
||||
if (!TryGetEyeImageMaterialForEffects(out Material material))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 确保 targetSaturation 在 0 到 1 之间
|
||||
targetSaturation = Mathf.Clamp01(targetSaturation);
|
||||
|
||||
Sequence saturationSequence = DOTween.Sequence();
|
||||
if (!TryJoinTargetSaturation(saturationSequence, targetSaturation, duration))
|
||||
{
|
||||
Debug.LogWarning("[EyeSystem] 无可用 EyeTarget 饱和度 tween。", this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 同时启动材质的饱和度变化和 Volume 的weight变化
|
||||
saturationSequence.Join(material.DOFloat(targetSaturation, "_HsvSaturation", duration));
|
||||
|
||||
// 获取 Tween 对象并添加到序列中
|
||||
Tween saturationTween = ScreenEffectManager.Instance.TweenSaturation(targetSaturation, duration);
|
||||
Tween saturationTween = ScreenEffectManager.Instance != null
|
||||
? ScreenEffectManager.Instance.TweenSaturation(targetSaturation, duration)
|
||||
: null;
|
||||
if (saturationTween != null)
|
||||
{
|
||||
saturationSequence.Join(saturationTween);
|
||||
}
|
||||
|
||||
// 启动 Sequence 并等待完成
|
||||
yield return saturationSequence.WaitForCompletion();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Sequence glitchin;
|
||||
|
||||
[YarnCommand("EyeGlitch_in")]
|
||||
public IEnumerator EyeGlitch_in()
|
||||
{
|
||||
if (!TryGetEyeImageMaterialForEffects(out Material material))
|
||||
if (!TryStartUnifiedGlitch())
|
||||
{
|
||||
yield break;
|
||||
Debug.LogWarning("[EyeSystem] 无可用 EyeTarget Glitch tween。", this);
|
||||
}
|
||||
|
||||
glitchin = DOTween.Sequence();
|
||||
// _HsvShift 从 0 ~ 360 来回变化
|
||||
hsvShiftTween = DOTween
|
||||
.To(() => material.GetFloat("_HsvShift"), x => material.SetFloat("_HsvShift", x), 360, 1f)
|
||||
.SetLoops(-1, LoopType.Yoyo);
|
||||
glitchin.Append(hsvShiftTween);
|
||||
|
||||
// _GlitchAmount 1 秒内从 0 ~ 8
|
||||
glitchin.Insert(0,
|
||||
DOTween.To(() => material.GetFloat("_GlitchAmount"), x => material.SetFloat("_GlitchAmount", x), 8,
|
||||
1f));
|
||||
|
||||
// _WarpStrength 1 秒内从 0 ~ 0.015
|
||||
glitchin.Insert(0,
|
||||
DOTween.To(() => material.GetFloat("_WarpStrength"), x => material.SetFloat("_WarpStrength", x), 0.015f,
|
||||
1f));
|
||||
|
||||
yield return new WaitForSeconds(1f);
|
||||
yield return new WaitForSeconds(visualSettings.glitchInDuration);
|
||||
}
|
||||
|
||||
[YarnCommand("EyeGlitch_out")]
|
||||
public void EyeGlitch_out(float duration)
|
||||
{
|
||||
if (!TryGetEyeImageMaterialForEffects(out Material material))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (glitchin != null && glitchin.IsActive())
|
||||
{
|
||||
glitchin.Kill();
|
||||
}
|
||||
|
||||
material.DOFloat(0, "_WarpStrength", duration);
|
||||
material.DOFloat(0, "_GlitchAmount", duration);
|
||||
material.DOFloat(0, "_HsvShift", duration);
|
||||
// 确保之前的动画被停止
|
||||
TryStopUnifiedGlitch(duration > 0f ? duration : visualSettings.glitchOutDuration);
|
||||
}
|
||||
|
||||
[YarnCommand("set_EyeColor")]
|
||||
public IEnumerator SetEyeColor(string color, string memoryName = null)
|
||||
{
|
||||
if (!TryGetEyeImageMaterialForEffects(out Material material))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (memoryRender == null)
|
||||
{
|
||||
Debug.LogError("[EyeSystem] memoryRender 未赋值。", this);
|
||||
@@ -426,63 +482,37 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
memoryRender.sprite = memoryHandle.Result;
|
||||
// 使 memoryRender 可见
|
||||
memoryRender.gameObject.SetActive(true);
|
||||
|
||||
// 创建一个新的 DOTween 序列
|
||||
Sequence memorySequence = DOTween.Sequence();
|
||||
|
||||
memorySequence.Append(memoryMaterial.DOFloat(0.85f, "_FullDistortionFade", 1.5f));
|
||||
memorySequence.AppendInterval(1.5f);
|
||||
memorySequence.Append(memoryMaterial.DOFloat(0f, "_SqueezePower", 2f));
|
||||
memorySequence.Insert(4.5f, memoryMaterial.DOFloat(0f, "_FullDistortionFade", 0.5f).OnComplete(() =>
|
||||
{
|
||||
memoryRender.gameObject.SetActive(false);
|
||||
// AudioManager.RandomPlayInteraction("water_drip");
|
||||
}));
|
||||
|
||||
// 等待序列完成
|
||||
yield return memorySequence.WaitForCompletion();
|
||||
}
|
||||
|
||||
Sequence sequence = DOTween.Sequence();
|
||||
// 添加一个 tween 来改变 _RoundWaveStrength 的值
|
||||
sequence.Append(material.DOFloat(1, "_RoundWaveStrength", 2)).OnComplete(() => { EyeGlitch_out(0.5f); });
|
||||
// 根据颜色改变 _ColorChangeTolerance 的值
|
||||
switch (color.ToLower())
|
||||
|
||||
Sequence waveIn = CreateTargetRoundWaveSequence(1f, visualSettings.roundWaveInDuration);
|
||||
if (waveIn != null)
|
||||
{
|
||||
case "blue":
|
||||
sequence.Join(material.DOFloat(0, "_ColorChangeTolerance", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance3", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance2", 2));
|
||||
break;
|
||||
case "red":
|
||||
sequence.Join(material.DOFloat(0, "_ColorChangeTolerance3", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance2", 2));
|
||||
break;
|
||||
case "green":
|
||||
sequence.Join(material.DOFloat(0, "_ColorChangeTolerance2", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance3", 2));
|
||||
break;
|
||||
case "All":
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance2", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance", 2));
|
||||
sequence.Join(material.DOFloat(1, "_ColorChangeTolerance3", 2));
|
||||
break;
|
||||
default:
|
||||
Debug.LogWarning("Unknown color: " + color);
|
||||
break;
|
||||
sequence.Append(waveIn);
|
||||
}
|
||||
|
||||
// 添加一个 tween 来将 _RoundWaveStrength 的值回复为 0
|
||||
sequence.Append(material.DOFloat(0, "_RoundWaveStrength", 0.5f));
|
||||
sequence.AppendCallback(() => { EyeGlitch_out(visualSettings.glitchOutDuration); });
|
||||
|
||||
Sequence waveOut = CreateTargetRoundWaveSequence(0f, visualSettings.roundWaveOutDuration);
|
||||
if (waveOut != null)
|
||||
{
|
||||
sequence.Append(waveOut);
|
||||
}
|
||||
|
||||
// 开始这个序列
|
||||
sequence.Play();
|
||||
|
||||
// 等待序列完成
|
||||
yield return sequence.WaitForCompletion();
|
||||
}
|
||||
|
||||
@@ -490,13 +520,118 @@ namespace AibisDream
|
||||
public void GetColor()
|
||||
{
|
||||
SetEyeColorState(EyeColorState.CanImagineColor);
|
||||
// 对目标列表中的每个目标执行 ColorBack 方法
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
target.ColorBack();
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到 CanImagineColorEffect 状态
|
||||
public void SetUnifiedUfWeights(float ascii, float dream, float memory)
|
||||
{
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
target.SetUfWeights(ascii, dream, memory);
|
||||
}
|
||||
}
|
||||
|
||||
public Tween TweenUnifiedUfWeights(float ascii, float dream, float memory, float duration)
|
||||
{
|
||||
Sequence sequence = DOTween.Sequence();
|
||||
bool hasTween = false;
|
||||
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
Tween tween = target.TweenUfWeights(ascii, dream, memory, duration);
|
||||
if (tween == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sequence.Join(tween);
|
||||
hasTween = true;
|
||||
}
|
||||
|
||||
return hasTween ? sequence : null;
|
||||
}
|
||||
|
||||
private bool TryJoinTargetSaturation(Sequence sequence, float saturation, float duration)
|
||||
{
|
||||
bool hasTween = false;
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
Tween tween = target.TweenSaturation(saturation, duration);
|
||||
if (tween == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sequence.Join(tween);
|
||||
hasTween = true;
|
||||
}
|
||||
|
||||
return hasTween;
|
||||
}
|
||||
|
||||
private bool TryStartUnifiedGlitch()
|
||||
{
|
||||
glitchin?.Kill();
|
||||
glitchin = DOTween.Sequence();
|
||||
bool hasTween = false;
|
||||
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
Sequence targetGlitch = target.StartUnifiedGlitch(visualSettings.glitchInDuration);
|
||||
if (targetGlitch == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
glitchin.Join(targetGlitch);
|
||||
hasTween = true;
|
||||
}
|
||||
|
||||
return hasTween;
|
||||
}
|
||||
|
||||
private bool TryStopUnifiedGlitch(float duration)
|
||||
{
|
||||
bool hasTarget = false;
|
||||
if (glitchin != null && glitchin.IsActive())
|
||||
{
|
||||
glitchin.Kill();
|
||||
hasTarget = true;
|
||||
}
|
||||
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
if (!target.TryGetUnifiedVisual(out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
target.StopUnifiedGlitch(duration);
|
||||
hasTarget = true;
|
||||
}
|
||||
|
||||
return hasTarget;
|
||||
}
|
||||
|
||||
private Sequence CreateTargetRoundWaveSequence(float value, float duration)
|
||||
{
|
||||
Sequence sequence = DOTween.Sequence();
|
||||
bool hasTween = false;
|
||||
|
||||
foreach (EyeTarget target in targets)
|
||||
{
|
||||
Tween tween = target.TweenRoundWave(value, duration);
|
||||
if (tween != null)
|
||||
{
|
||||
sequence.Join(tween);
|
||||
hasTween = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasTween ? sequence : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,149 +1,429 @@
|
||||
using UnityEngine;
|
||||
using DG.Tweening;
|
||||
using AibisDream.Utility;
|
||||
using System;
|
||||
using AibisDream;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
public class EyeTarget : MonoBehaviour
|
||||
{
|
||||
public Transform targetTransform; // EyeTarget的位置
|
||||
[SerializeField] private float burnRadiusMin = 0f; // 对应_BurnRadius的最小值
|
||||
private static readonly int AlphaId = Shader.PropertyToID("_Alpha");
|
||||
|
||||
[SerializeField] private float burnRadiusMax = 1f; // 对应_BurnRadius的最大值
|
||||
public Transform targetTransform;
|
||||
|
||||
private Material forwardMaterial;
|
||||
|
||||
[SerializeField] private SpriteRenderer backSpriteRenderer;
|
||||
|
||||
[SerializeField] private SpriteRenderer wrongColorSpriteRenderer;
|
||||
[Header("Optional overlay sprite for UF imagination")]
|
||||
[SerializeField] private SpriteRenderer imageSpriteRender;
|
||||
private Material backMaterial;
|
||||
private Material wrongColorMaterial;
|
||||
|
||||
[Header("Wrong-color overlay (e.g. plantError); auto-resolved from sibling if unset")]
|
||||
[SerializeField] private SpriteRenderer wrongColorSpriteRenderer;
|
||||
|
||||
[SerializeField] private Shader unifiedSpriteShader;
|
||||
[SerializeField] private EyeTargetVisual unifiedVisual;
|
||||
|
||||
public Color _color;
|
||||
|
||||
private EyeVisualSettings visualSettings = new();
|
||||
private Material wrongColorMaterial;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 获取此对象的材质
|
||||
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
forwardMaterial = spriteRenderer.material;
|
||||
forwardMaterial.SetFloat("_SourceGlowDissolveFade", burnRadiusMin);
|
||||
var spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
if (spriteRenderer == null)
|
||||
{
|
||||
Debug.LogError($"[EyeTarget] {name} 缺少 SpriteRenderer。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
backMaterial = backSpriteRenderer.material;
|
||||
wrongColorMaterial = wrongColorSpriteRenderer.material;
|
||||
;
|
||||
EnsureWrongColorOverlay();
|
||||
EnsureUnifiedVisual(spriteRenderer);
|
||||
DisableLegacyChildRenderers();
|
||||
DisableLegacySiblingRenderers();
|
||||
}
|
||||
|
||||
public void BindVisualSettings(EyeVisualSettings settings)
|
||||
{
|
||||
visualSettings = settings ?? new EyeVisualSettings();
|
||||
RefreshMaterialTuning();
|
||||
}
|
||||
|
||||
public void RefreshMaterialTuning()
|
||||
{
|
||||
EnsureVisualReady();
|
||||
unifiedVisual?.ApplyTuning(visualSettings);
|
||||
}
|
||||
|
||||
public void ApplyIdlePreviewState()
|
||||
{
|
||||
EnsureVisualReady();
|
||||
Init();
|
||||
}
|
||||
|
||||
public void KillVisualTweens()
|
||||
{
|
||||
unifiedVisual?.KillActiveTweens();
|
||||
wrongColorMaterial?.DOKill();
|
||||
}
|
||||
|
||||
private void EnsureVisualReady()
|
||||
{
|
||||
var spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
if (spriteRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (unifiedVisual == null || !unifiedVisual.IsReady)
|
||||
{
|
||||
EnsureUnifiedVisual(spriteRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
SetToMinBurnRadius();
|
||||
SetToMaxBlur(false);
|
||||
SetToMinColorReveal();
|
||||
SetToMaxBlur();
|
||||
SetToNoWrongColor();
|
||||
SetNoImage();
|
||||
}
|
||||
|
||||
public void ImageIn(float duration)
|
||||
{
|
||||
imageSpriteRender.DOFade(1, duration);
|
||||
unifiedVisual?.TweenImageAlpha(1f, duration);
|
||||
}
|
||||
|
||||
public void ImageOut(float duration)
|
||||
{
|
||||
imageSpriteRender.DOFade(0, duration);
|
||||
unifiedVisual?.TweenImageAlpha(0f, duration);
|
||||
}
|
||||
|
||||
public void SetNoImage()
|
||||
{
|
||||
imageSpriteRender.SetAlpha(0);
|
||||
unifiedVisual?.SetImageAlpha(0f);
|
||||
}
|
||||
|
||||
public void SetToMinBurnRadius()
|
||||
public void SetToMinColorReveal()
|
||||
{
|
||||
forwardMaterial.SetFloat("_SourceGlowDissolveFade", burnRadiusMin);
|
||||
unifiedVisual?.SetColorReveal(visualSettings.colorRevealMin);
|
||||
}
|
||||
|
||||
public void SetToMaxBurnRadius()
|
||||
public void SetToMaxColorReveal()
|
||||
{
|
||||
forwardMaterial.SetFloat("_SourceGlowDissolveFade", burnRadiusMax);
|
||||
unifiedVisual?.SetColorReveal(visualSettings.colorRevealMax);
|
||||
}
|
||||
|
||||
public void SetToMaxBlur(bool isForwardMaterial = true)
|
||||
public void SetToMaxBlur()
|
||||
{
|
||||
Material targetMaterial = isForwardMaterial ? forwardMaterial : backMaterial;
|
||||
targetMaterial.SetFloat("_GaussianBlurFade", 1f);
|
||||
unifiedVisual?.SetBlur(visualSettings.idleBlurAmount);
|
||||
}
|
||||
|
||||
public void SetToMinBlur(bool isForwardMaterial = true)
|
||||
public void SetToMinBlur()
|
||||
{
|
||||
Material targetMaterial = isForwardMaterial ? forwardMaterial : backMaterial;
|
||||
targetMaterial.SetFloat("_GaussianBlurFade", 0f);
|
||||
unifiedVisual?.SetBlur(visualSettings.focusedBlurAmount);
|
||||
}
|
||||
|
||||
public void SetToNoWrongColor()
|
||||
{
|
||||
wrongColorMaterial.SetFloat("_Alpha", 0f);
|
||||
unifiedVisual?.SetWrongColor(0f);
|
||||
SetWrongColorOverlayAlpha(0f);
|
||||
}
|
||||
|
||||
public void SetToWrongColor()
|
||||
{
|
||||
wrongColorMaterial.SetFloat("_Alpha", 1f);
|
||||
unifiedVisual?.SetWrongColor(1f);
|
||||
SetWrongColorOverlayAlpha(1f);
|
||||
}
|
||||
|
||||
public void FadeOut(float duration)
|
||||
{
|
||||
// 使用DoTween库来改变_BurnRadius,使目标褪色
|
||||
forwardMaterial.DOFloat(burnRadiusMin, "_SourceGlowDissolveFade", duration);
|
||||
forwardMaterial.DOFloat(1f, "_GaussianBlurFade", duration).SetEase(Ease.InSine);
|
||||
forwardMaterial.DOFloat(1f, "_UVDistortFade", duration / 2).SetEase(Ease.InSine).SetLoops(2, LoopType.Yoyo);
|
||||
;
|
||||
unifiedVisual?.TweenColorReveal(visualSettings.colorRevealMin, duration)?.SetEase(Ease.InSine);
|
||||
unifiedVisual?.TweenBlur(visualSettings.idleBlurAmount, duration)?.SetEase(Ease.InSine);
|
||||
unifiedVisual?.PulseDistortion(visualSettings.colorPulseDistortion, duration);
|
||||
}
|
||||
|
||||
public void ColorIn(float duration)
|
||||
{
|
||||
forwardMaterial.DOFloat(burnRadiusMax, "_SourceGlowDissolveFade", duration).SetEase(Ease.InOutSine);
|
||||
forwardMaterial.DOFloat(0f, "_GaussianBlurFade", duration).SetEase(Ease.InOutSine);
|
||||
forwardMaterial.DOFloat(1f, "_UVDistortFade", duration / 2).SetEase(Ease.InOutSine).SetLoops(2, LoopType.Yoyo);
|
||||
;
|
||||
unifiedVisual?.TweenColorReveal(visualSettings.colorRevealMax, duration)?.SetEase(Ease.InOutSine);
|
||||
unifiedVisual?.TweenBlur(visualSettings.focusedBlurAmount, duration)?.SetEase(Ease.InOutSine);
|
||||
unifiedVisual?.PulseDistortion(visualSettings.colorPulseDistortion, duration);
|
||||
}
|
||||
|
||||
public void WrongColorIn(float duration)
|
||||
{
|
||||
wrongColorMaterial.DOFloat(1f, "_Alpha", duration).SetEase(Ease.InOutSine);
|
||||
unifiedVisual?.TweenWrongColor(1f, duration)?.SetEase(Ease.InOutSine);
|
||||
TweenWrongColorOverlay(1f, duration);
|
||||
}
|
||||
|
||||
public void WrongColorOut(float duration)
|
||||
{
|
||||
wrongColorMaterial.DOFloat(0f, "_Alpha", duration).SetEase(Ease.InOutSine);
|
||||
unifiedVisual?.TweenWrongColor(0f, duration)?.SetEase(Ease.InOutSine);
|
||||
TweenWrongColorOverlay(0f, duration);
|
||||
}
|
||||
|
||||
public void BlurIn(float duration, bool isForwardMaterial = true)
|
||||
public void BlurIn(float duration)
|
||||
{
|
||||
// AudioManager.PlayLoopAudio("visualturn");
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/visualturn");
|
||||
Material targetMaterial = isForwardMaterial ? forwardMaterial : backMaterial;
|
||||
targetMaterial.DOFloat(1f, "_GaussianBlurFade", duration).SetEase(Ease.InOutSine)
|
||||
.OnComplete(() => { AudioManager.Instance.StopSfx("event:/ActionFB/visualturn"); });
|
||||
var tween = unifiedVisual?.TweenBlur(visualSettings.idleBlurAmount, duration);
|
||||
tween?.SetEase(Ease.InOutSine).OnComplete(StopVisualTurnSfx);
|
||||
}
|
||||
|
||||
public void BlurOut(float duration, bool isForwardMaterial = true)
|
||||
public void BlurOut(float duration)
|
||||
{
|
||||
// AudioManager.PlayLoopAudio("visualturn");
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/visualturn");
|
||||
Material targetMaterial = isForwardMaterial ? forwardMaterial : backMaterial;
|
||||
targetMaterial.DOFloat(0f, "_GaussianBlurFade", duration).SetEase(Ease.InOutSine)
|
||||
.OnComplete(() => { AudioManager.Instance.StopSfx("event:/ActionFB/visualturn"); });
|
||||
var tween = unifiedVisual?.TweenBlur(visualSettings.focusedBlurAmount, duration);
|
||||
tween?.SetEase(Ease.InOutSine).OnComplete(StopVisualTurnSfx);
|
||||
}
|
||||
|
||||
public void ColorBack()
|
||||
{
|
||||
SetToMaxBurnRadius();
|
||||
SetToMaxColorReveal();
|
||||
SetToNoWrongColor();
|
||||
SetToMinBlur();
|
||||
}
|
||||
|
||||
public void ColorLost()
|
||||
{
|
||||
SetToMinBurnRadius();
|
||||
//SetToNoWrongColor();
|
||||
SetToMinColorReveal();
|
||||
SetToMaxBlur();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetUnifiedVisual(out EyeTargetVisual visual)
|
||||
{
|
||||
visual = unifiedVisual;
|
||||
return visual != null && visual.IsReady;
|
||||
}
|
||||
|
||||
public void SetSaturation(float saturation)
|
||||
{
|
||||
unifiedVisual?.SetSaturation(saturation);
|
||||
}
|
||||
|
||||
public Tween TweenSaturation(float saturation, float duration)
|
||||
{
|
||||
return unifiedVisual != null ? unifiedVisual.TweenSaturation(saturation, duration) : null;
|
||||
}
|
||||
|
||||
public DG.Tweening.Sequence StartUnifiedGlitch(float duration = 1f)
|
||||
{
|
||||
return unifiedVisual != null ? unifiedVisual.StartGlitch(duration) : null;
|
||||
}
|
||||
|
||||
public void StopUnifiedGlitch(float duration)
|
||||
{
|
||||
unifiedVisual?.StopGlitch(duration);
|
||||
}
|
||||
|
||||
public Tween TweenRoundWave(float value, float duration)
|
||||
{
|
||||
return unifiedVisual != null ? unifiedVisual.TweenRoundWave(value, duration) : null;
|
||||
}
|
||||
|
||||
public void SetUfWeights(float ascii, float dream, float memory)
|
||||
{
|
||||
unifiedVisual?.SetUfWeights(ascii, dream, memory);
|
||||
}
|
||||
|
||||
public Tween TweenUfWeights(float ascii, float dream, float memory, float duration)
|
||||
{
|
||||
return unifiedVisual != null ? unifiedVisual.TweenUfWeights(ascii, dream, memory, duration) : null;
|
||||
}
|
||||
|
||||
private void EnsureUnifiedVisual(SpriteRenderer spriteRenderer)
|
||||
{
|
||||
Shader shader = unifiedSpriteShader != null
|
||||
? unifiedSpriteShader
|
||||
: Shader.Find("AIBIS/EyeUnifiedSprite");
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogError($"[EyeTarget] 找不到 AIBIS/EyeUnifiedSprite shader:{name}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
Material currentMaterial = Application.isPlaying
|
||||
? spriteRenderer.material
|
||||
: spriteRenderer.sharedMaterial;
|
||||
|
||||
bool usesUnifiedShader = currentMaterial != null
|
||||
&& currentMaterial.shader != null
|
||||
&& currentMaterial.shader.name == shader.name;
|
||||
if (currentMaterial == null || !usesUnifiedShader || !currentMaterial.HasProperty("_BlurAmount"))
|
||||
{
|
||||
Texture mainTex = currentMaterial != null && currentMaterial.HasProperty("_MainTex")
|
||||
? currentMaterial.GetTexture("_MainTex")
|
||||
: null;
|
||||
var runtimeMaterial = new Material(shader)
|
||||
{
|
||||
name = $"{gameObject.name}_EyeUnified_Runtime"
|
||||
};
|
||||
if (mainTex != null)
|
||||
{
|
||||
runtimeMaterial.SetTexture("_MainTex", mainTex);
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
spriteRenderer.material = runtimeMaterial;
|
||||
}
|
||||
else
|
||||
{
|
||||
spriteRenderer.sharedMaterial = runtimeMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
if (unifiedVisual == null)
|
||||
{
|
||||
unifiedVisual = GetComponent<EyeTargetVisual>();
|
||||
}
|
||||
|
||||
if (unifiedVisual == null)
|
||||
{
|
||||
unifiedVisual = gameObject.AddComponent<EyeTargetVisual>();
|
||||
}
|
||||
|
||||
unifiedVisual.Configure(spriteRenderer, imageSpriteRender);
|
||||
unifiedVisual.ApplyTuning(visualSettings);
|
||||
}
|
||||
|
||||
private void DisableLegacyChildRenderers()
|
||||
{
|
||||
foreach (var childRenderer in GetComponentsInChildren<SpriteRenderer>(true))
|
||||
{
|
||||
if (childRenderer.gameObject == gameObject)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (imageSpriteRender != null && childRenderer == imageSpriteRender)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
childRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用同组下旧版 overlay(如 posterNoColor / computernocolor),避免盖住 unified 效果。
|
||||
/// </summary>
|
||||
private void EnsureWrongColorOverlay()
|
||||
{
|
||||
if (wrongColorSpriteRenderer == null && transform.parent != null)
|
||||
{
|
||||
string errorName = $"{gameObject.name}Error";
|
||||
foreach (var renderer in transform.parent.GetComponentsInChildren<SpriteRenderer>(true))
|
||||
{
|
||||
if (string.Equals(renderer.gameObject.name, errorName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
wrongColorSpriteRenderer = renderer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wrongColorSpriteRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wrongColorMaterial = Application.isPlaying
|
||||
? wrongColorSpriteRenderer.material
|
||||
: wrongColorSpriteRenderer.sharedMaterial;
|
||||
wrongColorSpriteRenderer.enabled = true;
|
||||
SetWrongColorOverlayAlpha(0f);
|
||||
}
|
||||
|
||||
private void SetWrongColorOverlayAlpha(float alpha)
|
||||
{
|
||||
if (wrongColorSpriteRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
alpha = Mathf.Clamp01(alpha);
|
||||
if (alpha > 0f)
|
||||
{
|
||||
wrongColorSpriteRenderer.gameObject.SetActive(true);
|
||||
wrongColorSpriteRenderer.enabled = true;
|
||||
}
|
||||
|
||||
if (wrongColorMaterial != null && wrongColorMaterial.HasProperty(AlphaId))
|
||||
{
|
||||
wrongColorMaterial.SetFloat(AlphaId, alpha);
|
||||
}
|
||||
|
||||
if (alpha <= 0f)
|
||||
{
|
||||
wrongColorSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void TweenWrongColorOverlay(float alpha, float duration)
|
||||
{
|
||||
if (wrongColorSpriteRenderer == null || wrongColorMaterial == null || !wrongColorMaterial.HasProperty(AlphaId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (alpha > 0f)
|
||||
{
|
||||
wrongColorSpriteRenderer.gameObject.SetActive(true);
|
||||
wrongColorSpriteRenderer.enabled = true;
|
||||
}
|
||||
|
||||
wrongColorMaterial.DOKill();
|
||||
wrongColorMaterial
|
||||
.DOFloat(alpha, AlphaId, duration)
|
||||
.SetEase(Ease.InOutSine)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
if (alpha <= 0f && wrongColorSpriteRenderer != null)
|
||||
{
|
||||
wrongColorSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static bool IsWrongColorOverlayRenderer(SpriteRenderer renderer)
|
||||
{
|
||||
return renderer != null
|
||||
&& renderer.gameObject.name.EndsWith("Error", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void DisableLegacySiblingRenderers()
|
||||
{
|
||||
Transform groupRoot = transform.parent;
|
||||
if (groupRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var renderer in groupRoot.GetComponentsInChildren<SpriteRenderer>(true))
|
||||
{
|
||||
if (renderer.gameObject == gameObject)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.GetComponent<EyeTarget>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.GetComponentInParent<EyeTarget>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsWrongColorOverlayRenderer(renderer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
renderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void StopVisualTurnSfx()
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/visualturn");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using AibisDream.Utility;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// Drives the unified EyeUnifiedSprite path for Peipei eye targets.
|
||||
/// </summary>
|
||||
public class EyeTargetVisual : MonoBehaviour
|
||||
{
|
||||
private static readonly int ColorRevealId = Shader.PropertyToID("_ColorReveal");
|
||||
private static readonly int GrayAmountId = Shader.PropertyToID("_GrayAmount");
|
||||
private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount");
|
||||
private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize");
|
||||
private static readonly int DistortAmountId = Shader.PropertyToID("_DistortAmount");
|
||||
private static readonly int WrongColorAmountId = Shader.PropertyToID("_WrongColorAmount");
|
||||
private static readonly int SpriteFadeId = Shader.PropertyToID("_SpriteFade");
|
||||
private static readonly int AsciiAmountId = Shader.PropertyToID("_AsciiAmount");
|
||||
private static readonly int DreamAmountId = Shader.PropertyToID("_DreamAmount");
|
||||
private static readonly int MemoryAmountId = Shader.PropertyToID("_MemoryAmount");
|
||||
private static readonly int ScanlineAmountId = Shader.PropertyToID("_ScanlineAmount");
|
||||
private static readonly int GlitchAmountId = Shader.PropertyToID("_GlitchAmount");
|
||||
private static readonly int HsvShiftId = Shader.PropertyToID("_HsvShift");
|
||||
private static readonly int HsvSaturationId = Shader.PropertyToID("_HsvSaturation");
|
||||
private static readonly int RoundWaveStrengthId = Shader.PropertyToID("_RoundWaveStrength");
|
||||
|
||||
private static readonly int RevealWidthId = Shader.PropertyToID("_RevealWidth");
|
||||
private static readonly int RevealRotationId = Shader.PropertyToID("_RevealRotation");
|
||||
private static readonly int RevealNoiseId = Shader.PropertyToID("_RevealNoise");
|
||||
private static readonly int WrongColorId = Shader.PropertyToID("_WrongColor");
|
||||
|
||||
[SerializeField] private SpriteRenderer spriteRenderer;
|
||||
[SerializeField] private SpriteRenderer imageSpriteRenderer;
|
||||
[SerializeField] private bool driveImageLayer = true;
|
||||
|
||||
private Material material;
|
||||
private float activeBlurSize = 0.008f;
|
||||
|
||||
public bool IsReady => material != null && material.HasProperty(ColorRevealId);
|
||||
public bool CanDriveImageLayer => driveImageLayer && imageSpriteRenderer != null;
|
||||
public Material Material => material;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureMaterial();
|
||||
}
|
||||
|
||||
public bool EnsureMaterial()
|
||||
{
|
||||
if (spriteRenderer == null)
|
||||
{
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
}
|
||||
|
||||
if (spriteRenderer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
material = Application.isPlaying
|
||||
? spriteRenderer.material
|
||||
: spriteRenderer.sharedMaterial;
|
||||
return IsReady;
|
||||
}
|
||||
|
||||
public void Configure(SpriteRenderer mainRenderer, SpriteRenderer imageRenderer)
|
||||
{
|
||||
spriteRenderer = mainRenderer;
|
||||
imageSpriteRenderer = imageRenderer;
|
||||
EnsureMaterial();
|
||||
}
|
||||
|
||||
public void ApplyTuning(EyeVisualSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeBlurSize = settings.blurSize;
|
||||
|
||||
if (!EnsureMaterial())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
material.SetFloat(RevealWidthId, settings.revealWidth);
|
||||
material.SetFloat(RevealRotationId, settings.revealRotation);
|
||||
material.SetFloat(RevealNoiseId, settings.revealNoise);
|
||||
material.SetColor(WrongColorId, settings.wrongColor);
|
||||
SetFloatRaw(HsvSaturationId, settings.hsvSaturation);
|
||||
SyncBlurSizeToMaterial();
|
||||
}
|
||||
|
||||
public void SyncBlurSizeToMaterial()
|
||||
{
|
||||
if (!EnsureMaterial() || !material.HasProperty(BlurSizeId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float blur = material.GetFloat(BlurAmountId);
|
||||
material.SetFloat(BlurSizeId, blur > 0.01f ? activeBlurSize : 0f);
|
||||
}
|
||||
|
||||
public void KillActiveTweens()
|
||||
{
|
||||
if (material != null)
|
||||
{
|
||||
material.DOKill();
|
||||
}
|
||||
|
||||
if (driveImageLayer && imageSpriteRenderer != null)
|
||||
{
|
||||
imageSpriteRenderer.DOKill();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetVisual(float reveal)
|
||||
{
|
||||
if (!EnsureMaterial())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetFloat(ColorRevealId, reveal);
|
||||
SetFloat(GrayAmountId, 1f - reveal);
|
||||
SetFloat(BlurAmountId, 1f);
|
||||
if (material.HasProperty(BlurSizeId))
|
||||
{
|
||||
material.SetFloat(BlurSizeId, activeBlurSize);
|
||||
}
|
||||
SetFloat(DistortAmountId, 0f);
|
||||
SetFloat(WrongColorAmountId, 0f);
|
||||
SetFloat(SpriteFadeId, 1f);
|
||||
SetFloat(AsciiAmountId, 0f);
|
||||
SetFloat(DreamAmountId, 0f);
|
||||
SetFloat(MemoryAmountId, 0f);
|
||||
SetFloat(ScanlineAmountId, 0f);
|
||||
SetFloat(GlitchAmountId, 0f);
|
||||
SetFloat(HsvShiftId, 0f);
|
||||
SetFloat(HsvSaturationId, 1f);
|
||||
SetFloat(RoundWaveStrengthId, 0f);
|
||||
SetImageAlpha(0f);
|
||||
}
|
||||
|
||||
public void SetColorReveal(float value)
|
||||
{
|
||||
SetFloat(ColorRevealId, value);
|
||||
SetFloat(GrayAmountId, 1f - value);
|
||||
}
|
||||
|
||||
public Tween TweenColorReveal(float value, float duration)
|
||||
{
|
||||
if (!EnsureMaterial())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
value = Mathf.Clamp01(value);
|
||||
Sequence sequence = DOTween.Sequence();
|
||||
sequence.Join(material.DOFloat(value, ColorRevealId, duration));
|
||||
sequence.Join(material.DOFloat(1f - value, GrayAmountId, duration));
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void SetBlur(float value)
|
||||
{
|
||||
SetFloat(BlurAmountId, value);
|
||||
if (EnsureMaterial() && material.HasProperty(BlurSizeId))
|
||||
{
|
||||
material.SetFloat(BlurSizeId, value > 0.01f ? activeBlurSize : 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public Tween TweenBlur(float value, float duration)
|
||||
{
|
||||
return TweenFloat(BlurAmountId, value, duration);
|
||||
}
|
||||
|
||||
public void SetWrongColor(float value)
|
||||
{
|
||||
SetFloat(WrongColorAmountId, value);
|
||||
}
|
||||
|
||||
public Tween TweenWrongColor(float value, float duration)
|
||||
{
|
||||
return TweenFloat(WrongColorAmountId, value, duration);
|
||||
}
|
||||
|
||||
public Tween PulseDistortion(float amount, float duration)
|
||||
{
|
||||
if (!EnsureMaterial())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return material.DOFloat(amount, DistortAmountId, Mathf.Max(0.01f, duration * 0.5f))
|
||||
.SetEase(Ease.InOutSine)
|
||||
.SetLoops(2, LoopType.Yoyo);
|
||||
}
|
||||
|
||||
public void SetImageAlpha(float alpha)
|
||||
{
|
||||
if (!driveImageLayer || imageSpriteRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
alpha = Mathf.Clamp01(alpha);
|
||||
if (alpha > 0f)
|
||||
{
|
||||
imageSpriteRenderer.gameObject.SetActive(true);
|
||||
imageSpriteRenderer.enabled = true;
|
||||
}
|
||||
|
||||
imageSpriteRenderer.SetAlpha(alpha);
|
||||
|
||||
if (alpha <= 0f)
|
||||
{
|
||||
imageSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Tween TweenImageAlpha(float alpha, float duration)
|
||||
{
|
||||
if (!driveImageLayer || imageSpriteRenderer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
alpha = Mathf.Clamp01(alpha);
|
||||
if (alpha > 0f)
|
||||
{
|
||||
imageSpriteRenderer.gameObject.SetActive(true);
|
||||
imageSpriteRenderer.enabled = true;
|
||||
}
|
||||
|
||||
return imageSpriteRenderer.DOFade(alpha, duration)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
if (alpha <= 0f && imageSpriteRenderer != null)
|
||||
{
|
||||
imageSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SetUfWeights(float ascii, float dream, float memory)
|
||||
{
|
||||
SetFloat(AsciiAmountId, ascii);
|
||||
SetFloat(DreamAmountId, dream);
|
||||
SetFloat(MemoryAmountId, memory);
|
||||
SetFloat(ScanlineAmountId, Mathf.Max(ascii, memory));
|
||||
}
|
||||
|
||||
public Tween TweenUfWeights(float ascii, float dream, float memory, float duration)
|
||||
{
|
||||
if (!EnsureMaterial())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Sequence sequence = DOTween.Sequence();
|
||||
sequence.Join(material.DOFloat(Mathf.Clamp01(ascii), AsciiAmountId, duration));
|
||||
sequence.Join(material.DOFloat(Mathf.Clamp01(dream), DreamAmountId, duration));
|
||||
sequence.Join(material.DOFloat(Mathf.Clamp01(memory), MemoryAmountId, duration));
|
||||
sequence.Join(material.DOFloat(Mathf.Clamp01(Mathf.Max(ascii, memory)), ScanlineAmountId, duration));
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void SetSaturation(float saturation)
|
||||
{
|
||||
SetFloatRaw(HsvSaturationId, Mathf.Clamp(saturation, 0f, 2f));
|
||||
}
|
||||
|
||||
public Tween TweenSaturation(float saturation, float duration)
|
||||
{
|
||||
return TweenFloat(HsvSaturationId, Mathf.Clamp(saturation, 0f, 2f), duration);
|
||||
}
|
||||
|
||||
public Sequence StartGlitch(float duration = 1f)
|
||||
{
|
||||
if (!EnsureMaterial())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
duration = Mathf.Max(0.01f, duration);
|
||||
Sequence sequence = DOTween.Sequence();
|
||||
sequence.Join(material.DOFloat(1f, GlitchAmountId, duration));
|
||||
sequence.Join(material.DOFloat(0.02f, DistortAmountId, duration));
|
||||
sequence.Join(material.DOFloat(360f, HsvShiftId, duration).SetLoops(-1, LoopType.Yoyo));
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void StopGlitch(float duration)
|
||||
{
|
||||
TweenFloat(GlitchAmountId, 0f, duration);
|
||||
TweenFloat(DistortAmountId, 0f, duration);
|
||||
TweenFloat(HsvShiftId, 0f, duration);
|
||||
}
|
||||
|
||||
public Tween TweenRoundWave(float value, float duration)
|
||||
{
|
||||
return TweenFloat(RoundWaveStrengthId, value, duration);
|
||||
}
|
||||
|
||||
private Tween TweenFloat(int propertyId, float value, float duration)
|
||||
{
|
||||
if (!EnsureMaterial() || !material.HasProperty(propertyId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return material.DOFloat(value, propertyId, duration);
|
||||
}
|
||||
|
||||
private void SetFloat(int propertyId, float value)
|
||||
{
|
||||
if (!EnsureMaterial() || !material.HasProperty(propertyId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
material.SetFloat(propertyId, Mathf.Clamp01(value));
|
||||
}
|
||||
|
||||
private void SetFloatRaw(int propertyId, float value)
|
||||
{
|
||||
if (!EnsureMaterial() || !material.HasProperty(propertyId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
material.SetFloat(propertyId, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0c7e3c9e6a546b49887860b3f26cf91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,403 @@
|
||||
using System.Collections;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// Mobile-friendly fullscreen eye viewport: elliptical sight + vertical ellipse blink.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[ExecuteAlways]
|
||||
public class EyeViewportOverlay : MonoBehaviour
|
||||
{
|
||||
private const string OverlaySortingLayer = "StartUI";
|
||||
private const float OverlayPlaneDistance = 10f;
|
||||
|
||||
private static readonly int EyeWidthId = Shader.PropertyToID("_EyeWidth");
|
||||
private static readonly int EyeHeightId = Shader.PropertyToID("_EyeHeight");
|
||||
private static readonly int EdgeSoftnessId = Shader.PropertyToID("_EdgeSoftness");
|
||||
private static readonly int BlinkOpenId = Shader.PropertyToID("_BlinkOpen");
|
||||
|
||||
/// <summary>须低于 Persistence 中 UI Canvas(1) 与 Dialog Canvas(3)。</summary>
|
||||
[SerializeField] private int sortingOrder;
|
||||
|
||||
private Canvas overlayCanvas;
|
||||
private Image overlayImage;
|
||||
private Material instanceMaterial;
|
||||
private EyeVisualSettings activeSettings;
|
||||
private Coroutine autoBlinkRoutine;
|
||||
private Tween blinkTween;
|
||||
private float blinkOpen = 1f;
|
||||
private bool moduleActive;
|
||||
private bool editorPreviewActive;
|
||||
private bool hierarchyReady;
|
||||
private bool lifecycleReady;
|
||||
private Coroutine deferPrepareRoutine;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
lifecycleReady = true;
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareHierarchyIfNeeded();
|
||||
SetOverlayActive(false);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
StopEffects();
|
||||
if (instanceMaterial != null)
|
||||
{
|
||||
Destroy(instanceMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnterModule(EyeVisualSettings settings)
|
||||
{
|
||||
moduleActive = true;
|
||||
editorPreviewActive = false;
|
||||
ShowOverlay(settings);
|
||||
}
|
||||
|
||||
public void ExitModule()
|
||||
{
|
||||
moduleActive = false;
|
||||
if (editorPreviewActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopEffects();
|
||||
SetOverlayActive(false);
|
||||
activeSettings = null;
|
||||
}
|
||||
|
||||
public void EnterEditorPreview(EyeVisualSettings settings)
|
||||
{
|
||||
editorPreviewActive = true;
|
||||
ShowOverlay(settings);
|
||||
}
|
||||
|
||||
public void ExitEditorPreview()
|
||||
{
|
||||
editorPreviewActive = false;
|
||||
if (moduleActive)
|
||||
{
|
||||
ShowOverlay(activeSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
StopEffects();
|
||||
SetOverlayActive(false);
|
||||
activeSettings = null;
|
||||
}
|
||||
|
||||
public void ApplySettings(EyeVisualSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settings.enableViewportOverlay)
|
||||
{
|
||||
if (editorPreviewActive)
|
||||
{
|
||||
ExitEditorPreview();
|
||||
}
|
||||
else if (moduleActive)
|
||||
{
|
||||
ExitModule();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareHierarchyIfNeeded();
|
||||
if (instanceMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeSettings = settings;
|
||||
PushViewportParams(settings);
|
||||
SetBlinkOpen(blinkOpen);
|
||||
|
||||
if (!overlayCanvas.gameObject.activeInHierarchy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.autoBlink && Application.isPlaying && (moduleActive || editorPreviewActive))
|
||||
{
|
||||
StartAutoBlink(settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
StopAutoBlink();
|
||||
}
|
||||
}
|
||||
|
||||
public Tween PlayBlink(float? totalDuration = null)
|
||||
{
|
||||
if (instanceMaterial == null || activeSettings == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
blinkTween?.Kill();
|
||||
|
||||
float closeDuration = activeSettings.blinkCloseDuration;
|
||||
float openDuration = activeSettings.blinkOpenDuration;
|
||||
if (totalDuration.HasValue)
|
||||
{
|
||||
float total = Mathf.Max(0.05f, totalDuration.Value);
|
||||
closeDuration = total * 0.42f;
|
||||
openDuration = total * 0.58f;
|
||||
}
|
||||
|
||||
blinkTween = DOTween.Sequence()
|
||||
.Append(CreateBlinkTween(blinkOpen, 0f, closeDuration, Ease.InQuad))
|
||||
.Append(CreateBlinkTween(0f, 1f, openDuration, Ease.OutQuad))
|
||||
.OnComplete(() => SetBlinkOpen(1f));
|
||||
|
||||
return blinkTween;
|
||||
}
|
||||
|
||||
private void ShowOverlay(EyeVisualSettings settings)
|
||||
{
|
||||
if (settings == null || !settings.enableViewportOverlay)
|
||||
{
|
||||
ExitModule();
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareHierarchyIfNeeded();
|
||||
activeSettings = settings;
|
||||
ApplySettings(settings);
|
||||
if (!editorPreviewActive)
|
||||
{
|
||||
SetBlinkOpen(1f);
|
||||
}
|
||||
|
||||
SetOverlayActive(true);
|
||||
|
||||
if (settings.autoBlink && Application.isPlaying)
|
||||
{
|
||||
StartAutoBlink(settings);
|
||||
}
|
||||
}
|
||||
|
||||
private void PushViewportParams(EyeVisualSettings settings)
|
||||
{
|
||||
instanceMaterial.SetFloat(EyeWidthId, settings.eyeWidth);
|
||||
instanceMaterial.SetFloat(EyeHeightId, settings.eyeHeight);
|
||||
instanceMaterial.SetFloat(EdgeSoftnessId, settings.edgeSoftness);
|
||||
}
|
||||
|
||||
private Tween CreateBlinkTween(float from, float to, float duration, Ease ease)
|
||||
{
|
||||
blinkOpen = from;
|
||||
SetBlinkOpen(from);
|
||||
return DOTween.To(() => blinkOpen, value =>
|
||||
{
|
||||
blinkOpen = value;
|
||||
SetBlinkOpen(value);
|
||||
}, to, duration).SetEase(ease);
|
||||
}
|
||||
|
||||
private void StartAutoBlink(EyeVisualSettings settings)
|
||||
{
|
||||
StopAutoBlink();
|
||||
if (!settings.autoBlink)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
autoBlinkRoutine = StartCoroutine(AutoBlinkRoutine(settings));
|
||||
}
|
||||
|
||||
private IEnumerator AutoBlinkRoutine(EyeVisualSettings settings)
|
||||
{
|
||||
while (enabled && overlayCanvas != null && overlayCanvas.gameObject.activeInHierarchy)
|
||||
{
|
||||
float min = Mathf.Max(1f, settings.autoBlinkInterval.x);
|
||||
float max = Mathf.Max(min, settings.autoBlinkInterval.y);
|
||||
yield return new WaitForSeconds(Random.Range(min, max));
|
||||
|
||||
Tween blink = PlayBlink();
|
||||
if (blink != null)
|
||||
{
|
||||
yield return blink.WaitForCompletion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StopEffects()
|
||||
{
|
||||
blinkTween?.Kill();
|
||||
blinkTween = null;
|
||||
StopAutoBlink();
|
||||
}
|
||||
|
||||
private void StopAutoBlink()
|
||||
{
|
||||
if (autoBlinkRoutine == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopCoroutine(autoBlinkRoutine);
|
||||
autoBlinkRoutine = null;
|
||||
}
|
||||
|
||||
public void SetBlinkOpenPreview(float value)
|
||||
{
|
||||
SetBlinkOpen(value);
|
||||
}
|
||||
|
||||
public float GetBlinkOpenPreview() => blinkOpen;
|
||||
|
||||
private void SetBlinkOpen(float value)
|
||||
{
|
||||
blinkOpen = Mathf.Clamp01(value);
|
||||
instanceMaterial?.SetFloat(BlinkOpenId, blinkOpen);
|
||||
}
|
||||
|
||||
private void SetOverlayActive(bool active)
|
||||
{
|
||||
if (overlayCanvas != null)
|
||||
{
|
||||
overlayCanvas.gameObject.SetActive(active);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareHierarchyIfNeeded()
|
||||
{
|
||||
if (hierarchyReady && overlayCanvas != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying && !lifecycleReady)
|
||||
{
|
||||
ScheduleDeferredPrepare();
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayHierarchy();
|
||||
hierarchyReady = overlayCanvas != null;
|
||||
}
|
||||
|
||||
private void ScheduleDeferredPrepare()
|
||||
{
|
||||
if (deferPrepareRoutine != null || !isActiveAndEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
deferPrepareRoutine = StartCoroutine(DeferredPrepareHierarchy());
|
||||
}
|
||||
|
||||
private IEnumerator DeferredPrepareHierarchy()
|
||||
{
|
||||
yield return null;
|
||||
deferPrepareRoutine = null;
|
||||
EnsureOverlayHierarchy();
|
||||
hierarchyReady = overlayCanvas != null;
|
||||
|
||||
if (activeSettings != null && instanceMaterial != null)
|
||||
{
|
||||
PushViewportParams(activeSettings);
|
||||
SetBlinkOpen(blinkOpen);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureOverlayHierarchy()
|
||||
{
|
||||
if (overlayCanvas == null)
|
||||
{
|
||||
var canvasGo = new GameObject("EyeViewportOverlayCanvas");
|
||||
canvasGo.transform.SetParent(transform, false);
|
||||
|
||||
overlayCanvas = canvasGo.AddComponent<Canvas>();
|
||||
overlayCanvas.pixelPerfect = false;
|
||||
|
||||
var scaler = canvasGo.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
canvasGo.AddComponent<GraphicRaycaster>();
|
||||
|
||||
var imageGo = new GameObject("Overlay", typeof(RectTransform));
|
||||
imageGo.transform.SetParent(canvasGo.transform, false);
|
||||
|
||||
var rect = (RectTransform)imageGo.transform;
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
|
||||
overlayImage = imageGo.AddComponent<Image>();
|
||||
overlayImage.raycastTarget = false;
|
||||
overlayImage.color = Color.black;
|
||||
SetupMaterial();
|
||||
}
|
||||
|
||||
ApplyCanvasRenderSettings();
|
||||
}
|
||||
|
||||
private void SetupMaterial()
|
||||
{
|
||||
Shader shader = Shader.Find("UI/EyeViewportOverlay");
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogError("[EyeViewportOverlay] 找不到 UI/EyeViewportOverlay shader。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
instanceMaterial = new Material(shader)
|
||||
{
|
||||
name = "EyeViewportOverlay (Instance)",
|
||||
hideFlags = HideFlags.DontSave
|
||||
};
|
||||
overlayImage.material = instanceMaterial;
|
||||
}
|
||||
|
||||
private void ApplyCanvasRenderSettings()
|
||||
{
|
||||
if (overlayCanvas == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
overlayCanvas.renderMode = RenderMode.ScreenSpaceCamera;
|
||||
overlayCanvas.worldCamera = ResolveUICamera();
|
||||
overlayCanvas.planeDistance = OverlayPlaneDistance;
|
||||
overlayCanvas.sortingLayerName = OverlaySortingLayer;
|
||||
overlayCanvas.sortingOrder = sortingOrder;
|
||||
}
|
||||
|
||||
private static Camera ResolveUICamera()
|
||||
{
|
||||
if (UIManager.Instance != null && UIManager.Instance.UICamera != null)
|
||||
{
|
||||
return UIManager.Instance.UICamera;
|
||||
}
|
||||
|
||||
var uiManager = FindObjectOfType<UIManager>();
|
||||
if (uiManager != null && uiManager.UICamera != null)
|
||||
{
|
||||
return uiManager.UICamera;
|
||||
}
|
||||
|
||||
return Camera.main;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b95035b93d484ef4da0b45fcf2c2a380
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// Central tuning for Peipei eye-module visuals. Edit on EyeManager (EyeSystem).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class EyeVisualSettings
|
||||
{
|
||||
[Header("Color Reveal")]
|
||||
[Tooltip("Unfocused / gray state (0 = gray, 1 = full color).")]
|
||||
[Range(0f, 1f)] public float colorRevealMin;
|
||||
|
||||
[Tooltip("Focused / imagined color state.")]
|
||||
[Range(0f, 1f)] public float colorRevealMax = 1f;
|
||||
|
||||
[Header("Transition Duration (seconds)")]
|
||||
public float imagineColorDuration = 1f;
|
||||
public float cannotImagineDuration = 1f;
|
||||
public float eyeDisorderDuration = 2f;
|
||||
public float canSeeColorDuration = 1f;
|
||||
public float targetSwitchDuration = 1f;
|
||||
|
||||
[Header("Blur")]
|
||||
[Tooltip("Blur when unfocused or module idle.")]
|
||||
[Range(0f, 1f)] public float idleBlurAmount = 1f;
|
||||
|
||||
[Tooltip("Blur when focused and sharp.")]
|
||||
[Range(0f, 1f)] public float focusedBlurAmount;
|
||||
|
||||
[Tooltip("Shader _BlurSize when blur is active.")]
|
||||
[Range(0f, 0.02f)] public float blurSize = 0.008f;
|
||||
|
||||
[Header("Color Sweep (shader)")]
|
||||
[Tooltip("Width of the directional color sweep during ColorIn/FadeOut.")]
|
||||
[Range(0.001f, 1f)] public float revealWidth = 0.18f;
|
||||
|
||||
[Tooltip("Sweep direction in radians (0 = left-to-right).")]
|
||||
[Range(0f, 6.28318f)] public float revealRotation;
|
||||
|
||||
[Range(0f, 1f)] public float revealNoise = 0.08f;
|
||||
|
||||
[Header("Distortion Pulse")]
|
||||
[Tooltip("Peak _DistortAmount during ColorIn / FadeOut.")]
|
||||
[Range(0f, 0.08f)] public float colorPulseDistortion = 0.035f;
|
||||
|
||||
[Header("Wrong Color (Eye Disorder)")]
|
||||
public Color wrongColor = new(1f, 0.16f, 0.08f, 1f);
|
||||
|
||||
[Header("HSV")]
|
||||
[Range(0f, 2f)] public float hsvSaturation = 1f;
|
||||
|
||||
[Header("Glitch / Memory Wave")]
|
||||
public float glitchInDuration = 1f;
|
||||
public float roundWaveInDuration = 2f;
|
||||
public float roundWaveOutDuration = 0.5f;
|
||||
public float glitchOutDuration = 0.5f;
|
||||
|
||||
[Header("Viewport Overlay (no post-processing)")]
|
||||
public bool enableViewportOverlay = true;
|
||||
|
||||
[Tooltip("Horizontal half-size of the resting sight almond (left/right darkness).")]
|
||||
[Range(0.22f, 0.58f)] public float eyeWidth = 0.46f;
|
||||
|
||||
[Tooltip("Vertical half-size of the resting sight almond (top/bottom darkness).")]
|
||||
[Range(0.15f, 0.72f)] public float eyeHeight = 0.28f;
|
||||
|
||||
[Tooltip("Softness of the elliptical sight boundary.")]
|
||||
[Range(0.001f, 0.2f)] public float edgeSoftness = 0.022f;
|
||||
|
||||
[Tooltip("Off by default so you can tune the sight without blinking.")]
|
||||
public bool autoBlink;
|
||||
|
||||
public Vector2 autoBlinkInterval = new(8f, 15f);
|
||||
public float blinkCloseDuration = 0.09f;
|
||||
public float blinkOpenDuration = 0.14f;
|
||||
|
||||
public float GetDurationForState(EyeSystem.EyeColorState state) => state switch
|
||||
{
|
||||
EyeSystem.EyeColorState.CanImagineColor => imagineColorDuration,
|
||||
EyeSystem.EyeColorState.CannotImagineColor => cannotImagineDuration,
|
||||
EyeSystem.EyeColorState.EyeDisorder => eyeDisorderDuration,
|
||||
EyeSystem.EyeColorState.CanSeeColor => canSeeColorDuration,
|
||||
_ => imagineColorDuration
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2767162520353a0458684a571ce59532
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,179 +1,133 @@
|
||||
using AibisDream;
|
||||
|
||||
public interface IEyeStateEffect
|
||||
{
|
||||
float TransitionDuration { get; }
|
||||
void InitializeEffect(EyeTarget target, ViewCameraManager cameraManager);
|
||||
void InitializeEffect(EyeTarget target, EyeSystem eyeSystem);
|
||||
void CleanupEffect(EyeTarget target);
|
||||
void ApplyFocusedEffect(EyeTarget target);
|
||||
void ApplyUnfocusedEffect(EyeTarget target);
|
||||
void ApplyFocusedEffect(EyeTarget target, EyeSystem eyeSystem);
|
||||
void ApplyUnfocusedEffect(EyeTarget target, EyeSystem eyeSystem);
|
||||
}
|
||||
|
||||
public class CanImagineColorEffect : IEyeStateEffect
|
||||
{
|
||||
public float TransitionDuration => 1f;
|
||||
|
||||
public void InitializeEffect(EyeTarget target, ViewCameraManager cameraManager)
|
||||
public void InitializeEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.SetToNoWrongColor();
|
||||
target.SetToMaxBurnRadius();
|
||||
target.SetToMaxColorReveal();
|
||||
target.SetToMinBlur();
|
||||
if (target.targetTransform)
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.targetTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.transform);
|
||||
}
|
||||
eyeSystem?.FocusOnTarget(target, 0f);
|
||||
}
|
||||
|
||||
public void CleanupEffect(EyeTarget target)
|
||||
{
|
||||
target.SetToMinBurnRadius();
|
||||
target.SetToMinColorReveal();
|
||||
target.SetToMaxBlur();
|
||||
// Add cleanup logic here
|
||||
}
|
||||
|
||||
public void ApplyFocusedEffect(EyeTarget target)
|
||||
public void ApplyFocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.ColorIn(TransitionDuration);
|
||||
target.ColorIn(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
|
||||
public void ApplyUnfocusedEffect(EyeTarget target)
|
||||
public void ApplyUnfocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.FadeOut(TransitionDuration);
|
||||
target.FadeOut(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
}
|
||||
|
||||
public class CannotImagineColorEffect : IEyeStateEffect
|
||||
{
|
||||
public float TransitionDuration { get; } = 1f;
|
||||
|
||||
public void InitializeEffect(EyeTarget target, ViewCameraManager cameraManager)
|
||||
public void InitializeEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.SetToMinBlur(false);
|
||||
target.SetToMinBurnRadius();
|
||||
target.SetToMinBlur();
|
||||
target.SetToMinColorReveal();
|
||||
target.SetToNoWrongColor();
|
||||
if (target.targetTransform)
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.targetTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.transform);
|
||||
}
|
||||
eyeSystem?.FocusOnTarget(target, 0f);
|
||||
}
|
||||
|
||||
public void CleanupEffect(EyeTarget target)
|
||||
{
|
||||
// Add cleanup logic here
|
||||
target.SetToMaxBlur(false);
|
||||
target.SetToMaxBlur();
|
||||
}
|
||||
|
||||
public void ApplyFocusedEffect(EyeTarget target)
|
||||
public void ApplyFocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.BlurOut(TransitionDuration, false);
|
||||
target.BlurOut(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
|
||||
public void ApplyUnfocusedEffect(EyeTarget target)
|
||||
public void ApplyUnfocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.BlurIn(TransitionDuration, false);
|
||||
target.BlurIn(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
}
|
||||
|
||||
public class EyeDisorderEffect : IEyeStateEffect
|
||||
{
|
||||
public float TransitionDuration { get; } = 2f;
|
||||
|
||||
public void InitializeEffect(EyeTarget target, ViewCameraManager cameraManager)
|
||||
public void InitializeEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.SetToMinBurnRadius();
|
||||
// Add initialization logic here
|
||||
if (target.targetTransform)
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.targetTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.transform);
|
||||
}
|
||||
|
||||
target.SetToMinColorReveal();
|
||||
eyeSystem?.FocusOnTarget(target, 0f);
|
||||
target.SetToWrongColor();
|
||||
target.SetToMinBlur(false);
|
||||
}
|
||||
|
||||
public void CleanupEffect(EyeTarget target)
|
||||
{
|
||||
// Add cleanup logic here
|
||||
target.SetToNoWrongColor();
|
||||
}
|
||||
|
||||
public void ApplyFocusedEffect(EyeTarget target)
|
||||
{
|
||||
target.WrongColorIn(TransitionDuration);
|
||||
}
|
||||
|
||||
public void ApplyUnfocusedEffect(EyeTarget target)
|
||||
{
|
||||
target.WrongColorOut(TransitionDuration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CanSeeColorEffect : IEyeStateEffect
|
||||
{
|
||||
public float TransitionDuration { get; } = 1f;
|
||||
|
||||
public void InitializeEffect(EyeTarget target, ViewCameraManager cameraManager)
|
||||
{
|
||||
target.SetToNoWrongColor();
|
||||
target.SetToMaxBurnRadius();
|
||||
if (target.targetTransform)
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.targetTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraManager.SetCameraPosition(target.transform);
|
||||
}
|
||||
|
||||
target.SetToMinBlur();
|
||||
}
|
||||
|
||||
public void CleanupEffect(EyeTarget target)
|
||||
{
|
||||
// Add cleanup logic here
|
||||
target.SetToNoWrongColor();
|
||||
}
|
||||
|
||||
public void ApplyFocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.WrongColorIn(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
|
||||
public void ApplyUnfocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.WrongColorOut(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
}
|
||||
|
||||
public class CanSeeColorEffect : IEyeStateEffect
|
||||
{
|
||||
public void InitializeEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.SetToNoWrongColor();
|
||||
target.SetToMaxColorReveal();
|
||||
eyeSystem?.FocusOnTarget(target, 0f);
|
||||
target.SetToMinBlur();
|
||||
}
|
||||
|
||||
public void CleanupEffect(EyeTarget target)
|
||||
{
|
||||
target.SetToMaxBlur();
|
||||
}
|
||||
|
||||
public void ApplyFocusedEffect(EyeTarget target)
|
||||
public void ApplyFocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.BlurOut(TransitionDuration);
|
||||
target.BlurOut(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
|
||||
public void ApplyUnfocusedEffect(EyeTarget target)
|
||||
public void ApplyUnfocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
target.BlurIn(TransitionDuration);
|
||||
target.BlurIn(eyeSystem.GetEffectTransitionDuration());
|
||||
}
|
||||
}
|
||||
|
||||
public class HaveChip : IEyeStateEffect
|
||||
{
|
||||
public float TransitionDuration { get; } = 1f;
|
||||
|
||||
public void InitializeEffect(EyeTarget target, ViewCameraManager cameraManager)
|
||||
public void InitializeEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanupEffect(EyeTarget target)
|
||||
{
|
||||
// Add cleanup logic here
|
||||
}
|
||||
|
||||
public void ApplyFocusedEffect(EyeTarget target)
|
||||
public void ApplyFocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyUnfocusedEffect(EyeTarget target)
|
||||
public void ApplyUnfocusedEffect(EyeTarget target, EyeSystem eyeSystem)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using AibisDream;
|
||||
using AibisDream.FixSystem;
|
||||
@@ -7,12 +7,10 @@ using UnityEngine.Rendering;
|
||||
using Yarn.Unity;
|
||||
using DG.Tweening;
|
||||
using VolFx;
|
||||
using System;
|
||||
using AibisDream.Kit;
|
||||
|
||||
public class UFSystem : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
private GameObject _UFCover;
|
||||
private GameObject _UAudioule;
|
||||
|
||||
@@ -22,17 +20,18 @@ public class UFSystem : MonoBehaviour
|
||||
|
||||
public SpriteRenderer face;
|
||||
|
||||
private bool isLoopImage = false;
|
||||
private bool isLoopImage;
|
||||
|
||||
[Header("Global UF Cover")]
|
||||
[SerializeField] private Volume FullAscillVolume;
|
||||
|
||||
[Header("Target Post-Processing Volumes")]
|
||||
[SerializeField] private Volume imageAscillVolume;
|
||||
[SerializeField] private Volume imageVolume;
|
||||
[SerializeField] private Volume finalVolume;
|
||||
|
||||
|
||||
private AsciiVol ascii;
|
||||
|
||||
private Tween scaleTween; // 存储 Tween 动画
|
||||
private Tween scaleTween;
|
||||
|
||||
public enum UFState
|
||||
{
|
||||
@@ -45,21 +44,54 @@ public class UFSystem : MonoBehaviour
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
eyeSystem = FindObjectOfType<EyeSystem>();
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
}
|
||||
|
||||
public void TweenWeight(Volume postProcessingVolume, float targetWeight, float duration)
|
||||
{
|
||||
// 动态调整权重
|
||||
if (postProcessingVolume == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DOTween.To(() => postProcessingVolume.weight, x => postProcessingVolume.weight = x, targetWeight, duration);
|
||||
}
|
||||
|
||||
private void ClearUnifiedUfSpriteWeights()
|
||||
{
|
||||
eyeSystem?.SetUnifiedUfWeights(0f, 0f, 0f);
|
||||
}
|
||||
|
||||
private EyeTarget ResolveSpriteTarget()
|
||||
{
|
||||
if (eyeSystem == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (eyeSystem.CurrentTarget != null)
|
||||
{
|
||||
return eyeSystem.CurrentTarget;
|
||||
}
|
||||
|
||||
if (YarnVariableStorage.Instance != null
|
||||
&& YarnVariableStorage.Instance.TryGetValue("$currentEyeTarget", out string targetName)
|
||||
&& !string.IsNullOrEmpty(targetName))
|
||||
{
|
||||
return eyeSystem.FindEyeTargetByName(targetName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[YarnCommand("UF_spriteIn")]
|
||||
public IEnumerator UF_spriteIn(float duration = 1.5f)
|
||||
{
|
||||
if (eyeSystem.CurrentTarget != null)
|
||||
var target = ResolveSpriteTarget();
|
||||
if (target != null)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
eyeSystem.CurrentTarget.ImageIn(duration);
|
||||
target.ImageIn(duration);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
@@ -68,10 +100,11 @@ public class UFSystem : MonoBehaviour
|
||||
[YarnCommand("UF_spriteOut")]
|
||||
public IEnumerator UF_spriteOut(float duration = 1.5f)
|
||||
{
|
||||
if (eyeSystem.CurrentTarget != null)
|
||||
var target = ResolveSpriteTarget();
|
||||
if (target != null)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
eyeSystem.CurrentTarget.ImageOut(duration);
|
||||
target.ImageOut(duration);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
@@ -119,58 +152,72 @@ public class UFSystem : MonoBehaviour
|
||||
public IEnumerator UF_ImaginationEffectIn(float duration = 1.5f)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
TweenWeight(imageVolume, 1, duration);
|
||||
yield return new WaitForSeconds(0);
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
TweenWeight(imageVolume, 1f, duration);
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_ImaginationEffectOut")]
|
||||
public IEnumerator UF_ImaginationEffectOut(float duration = 1.5f)
|
||||
{
|
||||
TweenWeight(imageVolume, 0, duration);
|
||||
TweenWeight(imageVolume, 0f, duration);
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
yield return new WaitForSeconds(0);
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_MemoryEffectIn")]
|
||||
public IEnumerator UF_MemoryEffectIn(float duration = 1.5f)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
TweenWeight(finalVolume, 1, duration);
|
||||
yield return new WaitForSeconds(0);
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
TweenWeight(finalVolume, 1f, duration);
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_MemoryEffectOut")]
|
||||
public IEnumerator UF_MemoryEffectOut(float duration = 1.5f)
|
||||
{
|
||||
TweenWeight(finalVolume, 0, duration);
|
||||
TweenWeight(finalVolume, 0f, duration);
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
yield return new WaitForSeconds(0);
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_EyeTargetAscillIn")]
|
||||
public IEnumerator UF_EyeTargetAscillIn(float duration = 1.5f)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
TweenWeight(imageAscillVolume, 1, duration);
|
||||
yield return new WaitForSeconds(0);
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
TweenWeight(imageAscillVolume, 1f, duration);
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_EyeTargetAscillOut")]
|
||||
public IEnumerator UF_EyeTargetAscillOut(float duration = 1.5f)
|
||||
{
|
||||
TweenWeight(imageAscillVolume, 0, duration);
|
||||
TweenWeight(imageAscillVolume, 0f, duration);
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
yield return new WaitForSeconds(0);
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_SetBorkenValue")]
|
||||
public void UF_Borken(int value)
|
||||
{
|
||||
FullAscillVolume.profile.TryGet<AsciiVol>(out ascii);
|
||||
ascii.m_Depth.value = value;
|
||||
ascii.m_Ascii.value = Color.red;
|
||||
}
|
||||
if (FullAscillVolume == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FullAscillVolume.gameObject.activeInHierarchy)
|
||||
{
|
||||
FullAscillVolume.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
if (FullAscillVolume.profile.TryGet(out ascii))
|
||||
{
|
||||
ascii.m_Depth.value = value;
|
||||
ascii.m_Ascii.value = Color.red;
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("StartUFEffect_loop")]
|
||||
public void UFImageLoop()
|
||||
@@ -187,28 +234,39 @@ public class UFSystem : MonoBehaviour
|
||||
[YarnCommand("UF_fightEffect")]
|
||||
public void UF_fightEffect()
|
||||
{
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
scaleTween = DOTween.To(() => 0f, t =>
|
||||
{
|
||||
// 根据 t(0 到 1 的变化)更新多个值
|
||||
imageVolume.weight = Mathf.Lerp(0f, 1f, t); // 例如:imageVolume 的 weight
|
||||
imageAscillVolume.weight = Mathf.Lerp(0f, 1f, t); // 假设 saturation 是另一个值
|
||||
finalVolume.weight = Mathf.Lerp(1f, 0f, t); // 假设 contrast 是另一个值
|
||||
if (imageVolume != null)
|
||||
{
|
||||
imageVolume.weight = Mathf.Lerp(0f, 1f, t);
|
||||
}
|
||||
|
||||
if (imageAscillVolume != null)
|
||||
{
|
||||
imageAscillVolume.weight = Mathf.Lerp(0f, 1f, t);
|
||||
}
|
||||
|
||||
if (finalVolume != null)
|
||||
{
|
||||
finalVolume.weight = Mathf.Lerp(1f, 0f, t);
|
||||
}
|
||||
}, 1f, 1f)
|
||||
.SetEase(Ease.InOutSine) // 平滑缓动
|
||||
.SetLoops(-1, LoopType.Yoyo); // 无限循环
|
||||
.SetEase(Ease.InOutSine)
|
||||
.SetLoops(-1, LoopType.Yoyo);
|
||||
}
|
||||
|
||||
[YarnCommand("UF_StopfightEffect")]
|
||||
public void UF_StopfightEffect()
|
||||
{
|
||||
scaleTween.Kill();
|
||||
scaleTween?.Kill();
|
||||
}
|
||||
|
||||
public IEnumerator UFImageLoopProcess()
|
||||
{
|
||||
int index = 0;
|
||||
isLoopImage = true;
|
||||
while (isLoopImage) // 无限循环
|
||||
while (isLoopImage)
|
||||
{
|
||||
eyeSystem.StartCoroutine(eyeSystem.SetTargetCoroutine(eyeSystem.Targets[index].name, false));
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
@@ -222,36 +280,61 @@ public class UFSystem : MonoBehaviour
|
||||
eyeSystem.Targets[index].ImageOut(0.5f);
|
||||
StartCoroutine(UF_MemoryEffectOut(0.5f));
|
||||
index++;
|
||||
// 如果到达列表末尾,则回到开头
|
||||
if (index >= eyeSystem.Targets.Count - 2)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
//yield return new WaitForSeconds(1f); // 每隔1秒打印一次
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenUFView()
|
||||
{
|
||||
if (!FullAscillVolume.gameObject.activeInHierarchy)
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
|
||||
if (FullAscillVolume != null && !FullAscillVolume.gameObject.activeInHierarchy)
|
||||
{
|
||||
FullAscillVolume.gameObject.SetActive(true);
|
||||
imageAscillVolume.weight = 1;
|
||||
imageVolume.weight = 0;
|
||||
finalVolume.weight = 0;
|
||||
}
|
||||
|
||||
if (imageAscillVolume != null)
|
||||
{
|
||||
imageAscillVolume.weight = 1f;
|
||||
}
|
||||
|
||||
if (imageVolume != null)
|
||||
{
|
||||
imageVolume.weight = 0f;
|
||||
}
|
||||
|
||||
if (finalVolume != null)
|
||||
{
|
||||
finalVolume.weight = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseUFView()
|
||||
{
|
||||
if (FullAscillVolume.gameObject.activeInHierarchy)
|
||||
if (FullAscillVolume != null && FullAscillVolume.gameObject.activeInHierarchy)
|
||||
{
|
||||
FullAscillVolume.gameObject.SetActive(false);
|
||||
imageAscillVolume.weight = 1;
|
||||
imageVolume.weight = 0;
|
||||
finalVolume.weight = 0;
|
||||
//scaleTween.Kill();
|
||||
}
|
||||
|
||||
if (imageAscillVolume != null)
|
||||
{
|
||||
imageAscillVolume.weight = 1f;
|
||||
}
|
||||
|
||||
if (imageVolume != null)
|
||||
{
|
||||
imageVolume.weight = 0f;
|
||||
}
|
||||
|
||||
if (finalVolume != null)
|
||||
{
|
||||
finalVolume.weight = 0f;
|
||||
}
|
||||
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
}
|
||||
|
||||
public void OpenCover()
|
||||
@@ -277,36 +360,32 @@ public class UFSystem : MonoBehaviour
|
||||
[YarnCommand("UF_SwitchState")]
|
||||
public IEnumerator SwitchState(string stateName, float duration)
|
||||
{
|
||||
UFState state;
|
||||
if (!Enum.TryParse(stateName, true, out state))
|
||||
if (!Enum.TryParse(stateName, true, out UFState state))
|
||||
{
|
||||
Debug.LogError($"Invalid state name: {stateName}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
ClearUnifiedUfSpriteWeights();
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case UFState.UF:
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
TweenWeight(imageAscillVolume, 1, duration);
|
||||
TweenWeight(imageVolume, 0, duration);
|
||||
TweenWeight(finalVolume, 0, duration);
|
||||
TweenWeight(imageAscillVolume, 1f, duration);
|
||||
TweenWeight(imageVolume, 0f, duration);
|
||||
TweenWeight(finalVolume, 0f, duration);
|
||||
break;
|
||||
|
||||
case UFState.imagination:
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
TweenWeight(imageAscillVolume, 0, duration);
|
||||
TweenWeight(imageVolume, 1, duration);
|
||||
TweenWeight(finalVolume, 0, duration);
|
||||
TweenWeight(imageAscillVolume, 0f, duration);
|
||||
TweenWeight(imageVolume, 1f, duration);
|
||||
TweenWeight(finalVolume, 0f, duration);
|
||||
break;
|
||||
|
||||
case UFState.memory:
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
|
||||
TweenWeight(imageAscillVolume, 0, duration);
|
||||
TweenWeight(imageVolume, 0, duration);
|
||||
TweenWeight(finalVolume, 1, duration);
|
||||
TweenWeight(imageAscillVolume, 0f, duration);
|
||||
TweenWeight(imageVolume, 0f, duration);
|
||||
TweenWeight(finalVolume, 1f, duration);
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.LogError($"Unhandled state: {state}");
|
||||
yield break;
|
||||
@@ -314,4 +393,4 @@ public class UFSystem : MonoBehaviour
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace AibisDream.UI
|
||||
/// 显示物体
|
||||
/// </summary>
|
||||
/// <returns>协程</returns>
|
||||
public IEnumerator ShowObj(string picName)
|
||||
public IEnumerator ShowObj(string picName, float duration = 1)
|
||||
{
|
||||
_showObjPanel.gameObject.SetActive(true);
|
||||
|
||||
@@ -151,16 +151,16 @@ namespace AibisDream.UI
|
||||
|
||||
// 淡入
|
||||
_showObjImage.sprite = newSprite;
|
||||
yield return _showObjImage.FadeInAsync(1);
|
||||
yield return _showObjImage.FadeInAsync(duration);
|
||||
}
|
||||
|
||||
public IEnumerator HideObj()
|
||||
public IEnumerator HideObj(float duration = 1)
|
||||
{
|
||||
yield return _showObjImage.FadeOutAsync(1);
|
||||
yield return _showObjImage.FadeOutAsync(duration);
|
||||
_showObjPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public IEnumerator ShowFullScreen(string picName)
|
||||
public IEnumerator ShowFullScreen(string picName, float duration = 1)
|
||||
{
|
||||
var key = string.Format(ObjSpritePath, NormalizeObjPicName(picName));
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
@@ -174,12 +174,12 @@ namespace AibisDream.UI
|
||||
_fullScreenImage.sprite = handle.Result;
|
||||
|
||||
// 淡入
|
||||
yield return _fullScreenImage.FadeInAsync(1);
|
||||
yield return _fullScreenImage.FadeInAsync(duration);
|
||||
}
|
||||
|
||||
public IEnumerator HideFullScreen()
|
||||
public IEnumerator HideFullScreen(float duration = 1)
|
||||
{
|
||||
return _fullScreenImage.FadeOutAsync(1);
|
||||
yield return _fullScreenImage.FadeOutAsync(duration);
|
||||
}
|
||||
|
||||
public IEnumerator OpenProgressWindow(string title, string description, float duration)
|
||||
|
||||
Reference in New Issue
Block a user