验证码功能

This commit is contained in:
2025-08-08 12:36:25 +08:00
parent c07242fb6a
commit f1aeeb6bf3
32 changed files with 6901 additions and 9136 deletions
@@ -47,9 +47,6 @@ MonoBehaviour:
- {fileID: 11400000, guid: ac71ee7888fd1754bb3280725ad0076b, type: 2}
- {fileID: 11400000, guid: b9026802c7966cf4799a8df997d603e3, type: 2}
- {fileID: 11400000, guid: d582f9080605ab34cbcde3249c980030, type: 2}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 11400000, guid: 46e5fd2db66266e4698be9ad1b299913, type: 2}
- {fileID: 11400000, guid: 6ea418572850f5a4496b8bf2ea95ea43, type: 2}
- {fileID: 11400000, guid: 2bd43310d2c9f7147948458ae24b7969, type: 2}
@@ -114,7 +114,7 @@ MonoBehaviour:
- Locale-zh-Hans
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 72f4481345eb6c24594911deb872e0be
m_Address: Fiction_Day1_night_zh-Hans
m_Address: Fiction_Huoshan1_zh-Hans
m_ReadOnly: 1
m_SerializedLabels:
- Locale-zh-Hans
@@ -114,7 +114,7 @@ MonoBehaviour:
- Locale-en
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: d9da3ee8cbe1f3f40a93ee2c3042330d
m_Address: Fiction_Day1_night_en
m_Address: Fiction_Huoshan1_en
m_ReadOnly: 1
m_SerializedLabels:
- Locale-en
File diff suppressed because it is too large Load Diff
+9 -13
View File
@@ -477,19 +477,15 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
_filter:
- 0.0048309183
- 0.016401261
- 0.04532712
- 0.08293076
- 0.12053438
- 0.14946026
- 0.1610306
- 0.14946026
- 0.12053438
- 0.08293076
- 0.04532712
- 0.016401261
- 0.0048309183
- 0.007228916
- 0.043749984
- 0.12409639
- 0.20444278
- 0.24096388
- 0.20444278
- 0.12409639
- 0.043749984
- 0.007228916
--- !u!114 &-6101378415725230565
MonoBehaviour:
m_ObjectHideFlags: 3
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 7943305f9752fa74eb451f477f323e21
guid: 2fa76ffaa57f62244b77be9b25ca14c2
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,5 @@
fileFormatVersion: 2
guid: c00f5e40d3b7f9a48a72591616c67de4
folderAsset: yes
guid: 2bf3639539862924083bdd785c5fa5e7
DefaultImporter:
externalObjects: {}
userData:
File diff suppressed because it is too large Load Diff
-12
View File
@@ -1,12 +0,0 @@
fileFormatVersion: 2
<<<<<<<< HEAD:Assets/Scenes/OPTest.unity.meta
guid: 7cb899bcb936c224387f4de55defaac9
DefaultImporter:
========
guid: 8215a14f930072e46bc8756e84ced2ca
PrefabImporter:
>>>>>>>> develop:Assets/Prefabs/Huoshan/PlugCableGroup.prefab.meta
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -10451,7 +10451,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: feee73ba51d6e1a4da81ed7f94ab4694, type: 3}
m_Name:
m_EditorClassIdentifier:
runMode: 1
runMode: 0
firstTalkSo: {fileID: 11400000, guid: 2f763a2c04ecafb4981379b7363f9791, type: 2}
testTalkSo: {fileID: 11400000, guid: fe71f935f1f9a52458c994362567a211, type: 2}
saveFileName:
File diff suppressed because it is too large Load Diff
@@ -13,7 +13,7 @@ MonoBehaviour:
m_Name: Prologue
m_EditorClassIdentifier:
nextScene: {fileID: 11400000, guid: ac5ca9a66ff22ab4b9cd5adbdade6b8c, type: 2}
yarnProject: {fileID: 7545569452688597077, guid: 7943305f9752fa74eb451f477f323e21, type: 3}
yarnProject: {fileID: 7545569452688597077, guid: 2fa76ffaa57f62244b77be9b25ca14c2, type: 3}
firstSceneName: OpenFixScene
isEndOfDay: 0
nameToPlayer: "\u5E8F\u7AE0"
@@ -1,91 +0,0 @@
using System;
using AibisDream.Framework;
using AibisDream.Kit;
using DG.Tweening;
using UnityEngine;
namespace AibisDream
{
public class DeepInClock : MonoBehaviour
{
#region
private MinHand _minHand;
private Transform _secondHand;
private Transform _hourHand;
private SpriteLooper _cityLooper;
public event Action OnClockReached;
public event Action OnClockEnd;
#endregion
#region
[Header("背景切换时长")] public int cityLoopDuration;
[Header("开头全程所需时间")] public float rotateSpeed;
private bool _isReached;
#endregion
// 按分钟计算,全程1440分钟
private BindProperty<float> _curTime;
public void Enable()
{
gameObject.SetActive(true);
_minHand = transform.Find("fenzhen").GetComponent<MinHand>();
_secondHand = transform.Find("miaozhen");
_hourHand = transform.Find("shizhen");
_cityLooper = transform.Find("City").GetComponent<SpriteLooper>();
_curTime = new BindProperty<float>();
_curTime.Register(UpdateCityLooper);
_minHand.OnMinHandReached += StartAutoRotate;
_minHand.OnMinHandChanged += UpdateCurTime;
_minHand.IsActive = true;
}
private void UpdateCityLooper(float curTime)
{
// 时间转为背景循环值
var loopValue = curTime % cityLoopDuration / cityLoopDuration;
_cityLooper.SetLoopProgress(loopValue);
// 处理时针
var hourNum = curTime / 1440;
_hourHand.eulerAngles = new Vector3(0, 0, hourNum * 360);
// 处理分针
var minNum = curTime % 60 / 60;
_minHand.transform.eulerAngles = new Vector3(0, 0, minNum * 360);
if (_curTime.Value > 1200 && !_isReached)
{
_isReached = true;
OnClockReached?.Invoke();
}
}
private void StartAutoRotate()
{
DOTween.To(() => _curTime.Value, value => _curTime.Value = value, 1440f, rotateSpeed)
.SetEase(Ease.InQuad).OnComplete(OnRotateEnd);
}
private void UpdateCurTime(float minHandAngle)
{
_curTime.Value = minHandAngle / 360 * 60;
}
private void OnRotateEnd()
{
gameObject.SetActive(false);
OnClockEnd?.Invoke();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 753608042500c23428ad33810592d3a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,40 +0,0 @@
using AibisDream.Kit;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AibisDream
{
public class LightRoom : MonoBehaviour
{
#region
public SpriteRenderer room;
public SpriteRenderer onA;
private bool _isClicked;
#endregion
public void OnBtnClick(BaseEventData eventData)
{
if (_isClicked)
{
return;
}
_isClicked = true;
room.gameObject.SetActive(false);
ActionKit.Sequence()
.Delay(0.5f)
.Callback(() => onA.DOFade(0, 1f))
.Delay(1f, ZoomEnd)
.Start(this);
}
private void ZoomEnd()
{
DialogController.Instance.StartDialogNode("LightOn");
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 46dd8691ca2e18e46bfe764ce14ea847
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,89 +0,0 @@
using System;
using AibisDream.Framework;
using AibisDream.Utility;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AibisDream
{
public class MinHand : MonoBehaviour, IInteraction
{
#region
private EventTriggerEx _trigger;
public event Action<float> OnMinHandChanged;
public event Action OnMinHandReached;
#endregion
#region
public float rotationSpeedLimit = 10f; // 旋转速度限制(度/秒)
public float rotationThreshold = 30f; // 触发事件的角度阈值(度)
#endregion
private void Awake()
{
_trigger = GetComponent<EventTriggerEx>();
_trigger.Register(EventTriggerType.Drag, OnDrag);
EventSystemEx.Instance.isLocked = false;
}
private void OnDrag(BaseEventData data)
{
// 这里是一些阻碍拖动的情况
if (!IsActive) return;
if (data is not PointerEventData pointerData) return;
// 计算鼠标位置与分针中心点的向量
Vector2 worldPoint = CameraKit.GetMouseWorldPos(pointerData.position);
Vector2 direction = worldPoint - new Vector2(transform.position.x, transform.position.y);
// 计算当前角度
float mouseAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
float currentAngle = CommonUtil.Angle360(mouseAngle - 90);
// 计算角度差
float deltaAngle = Mathf.DeltaAngle(transform.eulerAngles.z, currentAngle);
// 限制旋转方向(仅允许逆时针旋转)
if (deltaAngle < 0)
{
deltaAngle = 0;
}
// 限制旋转速度
if (Mathf.Abs(deltaAngle) > rotationSpeedLimit * Time.deltaTime)
{
deltaAngle = rotationSpeedLimit * Time.deltaTime;
}
// 更新分针的旋转
transform.Rotate(0, 0, deltaAngle);
OnMinHandChanged?.Invoke(transform.eulerAngles.z);
// 触发事件
if (transform.eulerAngles.z >= rotationThreshold)
{
// 触发事件逻辑
Debug.Log("Rotation threshold reached!");
IsActive = false;
OnMinHandReached?.Invoke();
}
}
#region
public bool IsActive { get; set;}
public bool IsAvailable => true;
public GameObject GetGameObject()
{
return gameObject;
}
#endregion
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a04d4c06c6864b9cbaeb9d798ef6908d
timeCreated: 1746508524
@@ -5,22 +5,16 @@ using UnityEngine;
using UnityEngine.Playables;
using Yarn.Unity;
using System.Collections;
using UnityEngine.Timeline;
using System.Collections.Generic;
using DG.Tweening;
using AibisDream.Utility;
using System.Linq;
namespace AibisDream
{
public class OpenSystem : MonoBehaviour
{
public DeepInClock deepInClock;
public PlayableDirector openPlayable;
public PlayableDirector breakPlayable;
public PlayableDirector bubblePlayable;
public LightRoom lightRoom;
public GameObject screen;
public SpriteRenderer dreamSprite;
@@ -1,6 +1,7 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;
namespace AibisDream.FixSystem
{
@@ -10,13 +11,66 @@ namespace AibisDream.FixSystem
/// </summary>
public class VerificationCodeSystem : MonoBehaviour
{
private Image _target;
private Image _slot;
private Slider _slider;
private void Awake()
#region
[SerializeField] private Image background;
[SerializeField] private Image target;
[SerializeField] private Image slot;
[SerializeField] private SliderEx slider;
#endregion
private float _normalizePosX;
public event Action<bool> OnVerificationEnd;
private void Start()
{
StartVerification();
}
public void StartVerification()
{
_normalizePosX = Random.Range(0.4f, 0.9f);
// 随机生成数据,包括Slot位置,有资源之后增加背景图
var slotWidth = target.rectTransform.rect.width;
var backgroundWidth = background.rectTransform.rect.width - target.rectTransform.rect.width;
var slotPosX = _normalizePosX * backgroundWidth + slotWidth / 2;
slot.rectTransform.anchoredPosition = new Vector2(slotPosX, slot.rectTransform.anchoredPosition.y);
// 初始化
target.rectTransform.anchoredPosition = new Vector2(slotWidth / 2, target.rectTransform.anchoredPosition.y);
slider.value = 0;
// 绑定事件
slider.onEndDrag.AddListener(OnRelease);
slider.onValueChanged.AddListener(OnValueChange);
}
private void OnRelease(float value)
{
var isSuccess = Mathf.Abs(value - _normalizePosX) < 0.03f;
OnVerificationEnd?.Invoke(isSuccess);
slider.onValueChanged.RemoveAllListeners();
slider.onEndDrag.RemoveAllListeners();
// 如果失败就再来一次
if (!isSuccess)
{
StartVerification();
}
else
{
gameObject.SetActive(false);
}
}
private void OnValueChange(float value)
{
var slotWidth = target.rectTransform.rect.width;
var backgroundWidth = background.rectTransform.rect.width - target.rectTransform.rect.width;
var targetPosX = value * backgroundWidth + slotWidth / 2;
target.rectTransform.anchoredPosition = new Vector2(targetPosX, target.rectTransform.anchoredPosition.y);
}
}
}
@@ -1,61 +0,0 @@
using UnityEngine;
namespace AibisDream.Kit
{
public class RepeatTrack : MonoBehaviour
{
public Sprite sprite;
public float width;
public float moveSpeed;
private SpriteRenderer _renderer1;
private SpriteRenderer _renderer2;
private void Start()
{
_renderer1 = GeneNewRenderer();
_renderer1.transform.localPosition = Vector3.zero;
}
private SpriteRenderer GeneNewRenderer()
{
var newRenderer = new GameObject().AddComponent<SpriteRenderer>();
newRenderer.sprite = sprite;
newRenderer.transform.SetParent(transform);
newRenderer.sortingOrder = 5;
newRenderer.sortingLayerName = "FixMiddle";
newRenderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
return newRenderer;
}
public void Update()
{
// Debug.Log("render" + _renderer1);
// Debug.Log("bounds" + _renderer1.bounds);
// Debug.Log("size" + _renderer1.bounds.size);
var spriteWidth = _renderer1.bounds.size.x;
if (-_renderer1.transform.localPosition.x + width > spriteWidth && !_renderer2)
{
// 可以新建下一个了
_renderer2 = GeneNewRenderer();
_renderer2.transform.localPosition = new Vector3(width, 0, 0);
}
if (-_renderer1.transform.localPosition.x > spriteWidth)
{
Destroy(_renderer1.gameObject);
_renderer1 = _renderer2;
_renderer2 = null;
}
_renderer1.transform.localPosition =
new Vector3(_renderer1.transform.localPosition.x + moveSpeed * Time.deltaTime, 0, 0);
if (_renderer2)
{
_renderer2.transform.localPosition =
new Vector3(_renderer2.transform.localPosition.x + moveSpeed * Time.deltaTime, 0, 0);
}
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 1f3b807c39d1418293491f15d9f6326b
timeCreated: 1747141530
@@ -1,88 +0,0 @@
using System;
using AibisDream.Kit;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
namespace AibisDream.Kit
{
public class SpriteDeepIn : MonoBehaviour
{
private const int TopLayer = 20;
private const int BottomLayer = 19;
private const string LayerName = "FixMiddle";
public Sprite[] spriteArr;
public float duration;
public event Action OnFinished;
private SpriteRenderer _renderer1;
private SpriteRenderer _renderer2;
private float _curProgress;
private int _curIndex;
private void Update()
{
if (Input.GetKeyDown(KeyCode.K))
{
Play();
}
}
public void Play()
{
_renderer2 = transform.Find("Renderer1").GetComponent<SpriteRenderer>();
PlayNext();
}
private void PlayNext()
{
_curIndex++;
// 所有图片播放完毕则退出
if (_curIndex >= spriteArr.Length)
{
OnFinished?.Invoke();
return;
}
// 播放下一个图片
InitNewSprite(_curIndex);
// 缩放
_curProgress = 0.01f;
DOTween.To(() => _curProgress, value => _curProgress = value, 1, duration)
.SetEase(Ease.InQuad)
.OnUpdate(UpdateSprite)
.OnComplete(PlayNext);
}
private void InitNewSprite(int index)
{
if (_renderer1)
{
Destroy(_renderer1.gameObject);
}
_renderer1 = _renderer2;
_renderer2 = new GameObject().AddComponent<SpriteRenderer>();
_renderer2.transform.SetParent(transform);
// 处理层级
_renderer1.sortingLayerName = LayerName;
_renderer1.sortingOrder = BottomLayer;
_renderer2.sortingLayerName = LayerName;
_renderer2.sortingOrder = TopLayer;
_renderer2.transform.localScale = Vector3.one * 0.01f;
// 添加图片
_renderer2.sprite = spriteArr[index];
}
private void UpdateSprite()
{
// 大图片缩放
_renderer1.transform.localScale = Vector3.one * (_curProgress * 100);
// 小图片缩放
_renderer2.transform.localScale = Vector3.one * _curProgress;
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 885cefce8d27b9c44b433278584f254d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,74 +0,0 @@
using UnityEngine;
namespace AibisDream.Kit
{
public class SpriteLooper : MonoBehaviour
{
private SpriteRenderer _fadeOutRenderer;
private SpriteRenderer _fadeInRenderer;
public Sprite[] sprites;
[Range(0, 1)]public float fadeRadio;
public float CurProgress { get; private set; }
private void Awake()
{
InitRefs();
}
private void InitRefs()
{
_fadeOutRenderer = transform.Find("Sprite1").GetComponent<SpriteRenderer>();
_fadeInRenderer = transform.Find("Sprite2").GetComponent<SpriteRenderer>();
}
public void Init()
{
CurProgress = 0;
// 激活初始状态
_fadeOutRenderer.sprite = sprites[0];
_fadeOutRenderer.color = Color.white;
_fadeInRenderer.sprite = sprites[1];
_fadeInRenderer.color = Color.clear;
}
public void SetLoopProgress(float progress)
{
CurProgress = progress;
// 将进度条转为图片序号和淡出进度
var calculator = new LoopDataCalculator(progress, sprites.Length, fadeRadio);
_fadeOutRenderer.sprite = sprites[calculator.CurIdx];
_fadeOutRenderer.color = calculator.FadeOutColor;
_fadeInRenderer.sprite = sprites[calculator.NextIdx];
_fadeInRenderer.color = calculator.FadeInColor;
}
}
public readonly struct LoopDataCalculator
{
private readonly float _progress;
private readonly float _localProgress;
private readonly float _fadeRadio;
public int CurIdx { get; }
public int NextIdx { get; }
public LoopDataCalculator(float progress, int spriteCount, float fadeRadio)
{
_progress = progress;
_localProgress = 1 / (float)spriteCount;
_fadeRadio = fadeRadio;
CurIdx = Mathf.FloorToInt(progress / _localProgress);
NextIdx = CurIdx + 1 >= spriteCount ? 0 : CurIdx + 1;
}
private float LeftProgress =>
Mathf.Clamp01((_progress - (CurIdx + 1 - _fadeRadio) * _localProgress) / (_localProgress * _fadeRadio));
public Color FadeOutColor => new(1f, 1f, 1f, 1 - LeftProgress);
public Color FadeInColor => new(1f, 1f, 1f, LeftProgress);
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 46b06a7e067e04142a7715ae3571abde
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+853
View File
@@ -0,0 +1,853 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AibisDream
{
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
public class SliderEx : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
/// <summary>
/// Setting that indicates one of four directions.
/// </summary>
public enum Direction
{
/// <summary>
/// From the left to the right
/// </summary>
LeftToRight,
/// <summary>
/// From the right to the left
/// </summary>
RightToLeft,
/// <summary>
/// From the bottom to the top.
/// </summary>
BottomToTop,
/// <summary>
/// From the top to the bottom.
/// </summary>
TopToBottom,
}
private bool _isHolding;
[SerializeField] private RectTransform m_FillRect;
/// <summary>
/// Optional RectTransform to use as fill for the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
/// //Reference to new "RectTransform"(Child of FillArea).
/// public RectTransform newFillRect;
///
/// //Deactivates the old FillRect and assigns a new one.
/// void Start()
/// {
/// mainSlider.fillRect.gameObject.SetActive(false);
/// mainSlider.fillRect = newFillRect;
/// }
/// }
/// ]]>
///</code>
/// </example>
public RectTransform fillRect
{
get => m_FillRect;
set
{
if (SetPropertyUtility.SetClass(ref m_FillRect, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[SerializeField] private RectTransform m_HandleRect;
/// <summary>
/// Optional RectTransform to use as a handle for the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
/// //Reference to new "RectTransform" (Child of "Handle Slide Area").
/// public RectTransform handleHighlighted;
///
/// //Deactivates the old Handle, then assigns and enables the new one.
/// void Start()
/// {
/// mainSlider.handleRect.gameObject.SetActive(false);
/// mainSlider.handleRect = handleHighlighted;
/// mainSlider.handleRect.gameObject.SetActive(true);
/// }
/// }
/// ]]>
///</code>
/// </example>
public RectTransform handleRect
{
get => m_HandleRect;
set
{
if (SetPropertyUtility.SetClass(ref m_HandleRect, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[Space] [SerializeField] private Direction m_Direction = Direction.LeftToRight;
/// <summary>
/// The direction of the slider, from minimum to maximum value.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// //Changes the direction of the slider.
/// if (mainDirection == Direction.BottomToTop)
/// {
/// mainDirection = Direction.TopToBottom;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public Direction direction
{
get => m_Direction;
set
{
if (SetPropertyUtility.SetStruct(ref m_Direction, value)) UpdateVisuals();
}
}
[SerializeField] private float m_MinValue = 0;
/// <summary>
/// The minimum allowed value of the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// void Start()
/// {
/// // Changes the minimum value of the slider to 10;
/// mainSlider.minValue = 10;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float minValue
{
get => m_MinValue;
set
{
if (SetPropertyUtility.SetStruct(ref m_MinValue, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[SerializeField] private float m_MaxValue = 1;
/// <summary>
/// The maximum allowed value of the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// void Start()
/// {
/// // Changes the max value of the slider to 20;
/// mainSlider.maxValue = 20;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float maxValue
{
get => m_MaxValue;
set
{
if (SetPropertyUtility.SetStruct(ref m_MaxValue, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[SerializeField] private bool m_WholeNumbers = false;
/// <summary>
/// Should the value only be allowed to be whole numbers?
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// //sets the slider's value to accept whole numbers only.
/// mainSlider.wholeNumbers = true;
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool wholeNumbers
{
get => m_WholeNumbers;
set
{
if (SetPropertyUtility.SetStruct(ref m_WholeNumbers, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[SerializeField] protected float m_Value;
/// <summary>
/// The current value of the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// //Invoked when a submit button is clicked.
/// public void SubmitSliderSetting()
/// {
/// //Displays the value of the slider in the console.
/// Debug.Log(mainSlider.value);
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual float value
{
get => wholeNumbers ? Mathf.Round(m_Value) : m_Value;
set => Set(value);
}
/// <summary>
/// Set the value of the slider without invoking onValueChanged callback.
/// </summary>
/// <param name="input">The new value for the slider.</param>
public virtual void SetValueWithoutNotify(float input)
{
Set(input, false);
}
/// <summary>
/// The current value of the slider normalized into a value between 0 and 1.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// //Set to invoke when "OnValueChanged" method is called.
/// void CheckNormalisedValue()
/// {
/// //Displays the normalised value of the slider everytime the value changes.
/// Debug.Log(mainSlider.normalizedValue);
/// }
/// }
/// ]]>
///</code>
/// </example>
public float NormalizedValue
{
get
{
if (Mathf.Approximately(minValue, maxValue))
return 0;
return Mathf.InverseLerp(minValue, maxValue, value);
}
set => this.value = Mathf.Lerp(minValue, maxValue, value);
}
[Space] [SerializeField] private UnityEvent<float> m_OnValueChanged = new();
/// <summary>
/// Callback executed when the value of the slider is changed.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// //Adds a listener to the main slider and invokes a method when the value changes.
/// mainSlider.onValueChanged.AddListener(delegate {ValueChangeCheck(); });
/// }
///
/// // Invoked when the value of the slider changes.
/// public void ValueChangeCheck()
/// {
/// Debug.Log(mainSlider.value);
/// }
/// }
/// ]]>
///</code>
/// </example>
public UnityEvent<float> onValueChanged
{
get => m_OnValueChanged;
set => m_OnValueChanged = value;
}
[SerializeField] private UnityEvent m_OnStartDrag = new();
public UnityEvent onStartDrag
{
get => m_OnStartDrag;
set => m_OnStartDrag = value;
}
[SerializeField] private UnityEvent<float> m_OnEndDrag = new();
public UnityEvent<float> onEndDrag
{
get => m_OnEndDrag;
set => m_OnEndDrag = value;
}
// Private fields
private Image m_FillImage;
private Transform m_FillTransform;
private RectTransform m_FillContainerRect;
private Transform m_HandleTransform;
private RectTransform m_HandleContainerRect;
// The offset from handle position to mouse down position
private Vector2 m_Offset = Vector2.zero;
// field is never assigned warning
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
// This "delayed" mechanism is required for case 1037681.
private bool m_DelayedUpdateVisuals = false;
// Size of each step.
float stepSize => wholeNumbers ? 1 : (maxValue - minValue) * 0.1f;
protected SliderEx()
{
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (wholeNumbers)
{
m_MinValue = Mathf.Round(m_MinValue);
m_MaxValue = Mathf.Round(m_MaxValue);
}
//Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run.
if (IsActive())
{
UpdateCachedReferences();
// Update rects in next update since other things might affect them even if value didn't change.
m_DelayedUpdateVisuals = true;
}
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
#endif // if UNITY_EDITOR
public virtual void Rebuild(CanvasUpdate executing)
{
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
onValueChanged.Invoke(value);
#endif
}
/// <summary>
/// See ICanvasElement.LayoutComplete
/// </summary>
public virtual void LayoutComplete()
{
}
/// <summary>
/// See ICanvasElement.GraphicUpdateComplete
/// </summary>
public virtual void GraphicUpdateComplete()
{
}
protected override void OnEnable()
{
base.OnEnable();
UpdateCachedReferences();
Set(m_Value, false);
// Update rects since they need to be initialized correctly.
UpdateVisuals();
}
protected override void OnDisable()
{
m_Tracker.Clear();
base.OnDisable();
}
/// <summary>
/// Update the rect based on the delayed update visuals.
/// Got around issue of calling sendMessage from onValidate.
/// </summary>
protected virtual void Update()
{
if (m_DelayedUpdateVisuals)
{
m_DelayedUpdateVisuals = false;
Set(m_Value, false);
UpdateVisuals();
}
}
protected override void OnDidApplyAnimationProperties()
{
// Has value changed? Various elements of the slider have the old normalisedValue assigned, we can use this to perform a comparison.
// We also need to ensure the value stays within min/max.
m_Value = ClampValue(m_Value);
float oldNormalizedValue = NormalizedValue;
if (m_FillContainerRect != null)
{
if (m_FillImage != null && m_FillImage.type == Image.Type.Filled)
oldNormalizedValue = m_FillImage.fillAmount;
else
oldNormalizedValue =
(ReverseValue ? 1 - m_FillRect.anchorMin[(int)axis] : m_FillRect.anchorMax[(int)axis]);
}
else if (m_HandleContainerRect != null)
oldNormalizedValue =
(ReverseValue ? 1 - m_HandleRect.anchorMin[(int)axis] : m_HandleRect.anchorMin[(int)axis]);
UpdateVisuals();
if (!Mathf.Approximately(oldNormalizedValue, NormalizedValue))
{
UISystemProfilerApi.AddMarker("Slider.value", this);
onValueChanged.Invoke(m_Value);
}
// UUM-34170 Apparently, some properties on slider such as IsInteractable and Normalcolor Animation is broken.
// We need to call base here to render the animation on Scene
base.OnDidApplyAnimationProperties();
}
void UpdateCachedReferences()
{
if (m_FillRect && m_FillRect != (RectTransform)transform)
{
m_FillTransform = m_FillRect.transform;
m_FillImage = m_FillRect.GetComponent<Image>();
if (m_FillTransform.parent != null)
m_FillContainerRect = m_FillTransform.parent.GetComponent<RectTransform>();
}
else
{
m_FillRect = null;
m_FillContainerRect = null;
m_FillImage = null;
}
if (m_HandleRect && m_HandleRect != (RectTransform)transform)
{
m_HandleTransform = m_HandleRect.transform;
if (m_HandleTransform.parent != null)
m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
}
else
{
m_HandleRect = null;
m_HandleContainerRect = null;
}
}
float ClampValue(float input)
{
float newValue = Mathf.Clamp(input, minValue, maxValue);
if (wholeNumbers)
newValue = Mathf.Round(newValue);
return newValue;
}
/// <summary>
/// Set the value of the slider.
/// </summary>
/// <param name="input">The new value for the slider.</param>
/// <param name="sendCallback">If the OnValueChanged callback should be invoked.</param>
/// <remarks>
/// Process the input to ensure the value is between min and max value. If the input is different set the value and send the callback is required.
/// </remarks>
protected virtual void Set(float input, bool sendCallback = true)
{
// Clamp the input
float newValue = ClampValue(input);
// If the stepped value doesn't match the last one, it's time to update
if (Mathf.Approximately(m_Value, newValue))
return;
m_Value = newValue;
UpdateVisuals();
if (sendCallback)
{
UISystemProfilerApi.AddMarker("Slider.value", this);
m_OnValueChanged.Invoke(newValue);
}
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
//This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called.
if (!IsActive())
return;
UpdateVisuals();
}
enum Axis
{
Horizontal = 0,
Vertical = 1
}
Axis axis =>
m_Direction is Direction.LeftToRight or Direction.RightToLeft
? Axis.Horizontal
: Axis.Vertical;
private bool ReverseValue => m_Direction is Direction.RightToLeft or Direction.TopToBottom;
// Force-update the slider. Useful if you've changed the properties and want it to update visually.
private void UpdateVisuals()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
UpdateCachedReferences();
#endif
m_Tracker.Clear();
if (m_FillContainerRect != null)
{
m_Tracker.Add(this, m_FillRect, DrivenTransformProperties.Anchors);
Vector2 anchorMin = Vector2.zero;
Vector2 anchorMax = Vector2.one;
if (m_FillImage != null && m_FillImage.type == Image.Type.Filled)
{
m_FillImage.fillAmount = NormalizedValue;
}
else
{
if (ReverseValue)
anchorMin[(int)axis] = 1 - NormalizedValue;
else
anchorMax[(int)axis] = NormalizedValue;
}
m_FillRect.anchorMin = anchorMin;
m_FillRect.anchorMax = anchorMax;
}
if (m_HandleContainerRect != null)
{
m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
Vector2 anchorMin = Vector2.zero;
Vector2 anchorMax = Vector2.one;
anchorMin[(int)axis] = anchorMax[(int)axis] = (ReverseValue ? (1 - NormalizedValue) : NormalizedValue);
m_HandleRect.anchorMin = anchorMin;
m_HandleRect.anchorMax = anchorMax;
}
}
// Update the slider's position based on the mouse.
void UpdateDrag(PointerEventData eventData, Camera cam)
{
RectTransform clickRect = m_HandleContainerRect ?? m_FillContainerRect;
if (clickRect != null && clickRect.rect.size[(int)axis] > 0)
{
Vector2 position = Vector2.zero;
if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
return;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, position, cam,
out var localCursor))
return;
localCursor -= clickRect.rect.position;
float val = Mathf.Clamp01((localCursor - m_Offset)[(int)axis] / clickRect.rect.size[(int)axis]);
NormalizedValue = ReverseValue ? 1f - val : val;
}
}
private bool MayDrag(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
public override void OnPointerDown(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
base.OnPointerDown(eventData);
m_Offset = Vector2.zero;
if (m_HandleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect,
eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera))
{
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect,
eventData.pointerPressRaycast.screenPosition, eventData.pressEventCamera,
out var localMousePos))
m_Offset = localMousePos;
_isHolding = true;
m_OnStartDrag?.Invoke();
}
}
public override void OnPointerUp(PointerEventData eventData)
{
if (_isHolding)
{
_isHolding = false;
m_OnEndDrag?.Invoke(value);
}
if (!MayDrag(eventData)) return;
base.OnPointerUp(eventData);
}
public virtual void OnDrag(PointerEventData eventData)
{
if (!_isHolding || !MayDrag(eventData))
return;
UpdateDrag(eventData, eventData.pressEventCamera);
}
public override void OnMove(AxisEventData eventData)
{
if (!IsActive() || !IsInteractable())
{
base.OnMove(eventData);
return;
}
switch (eventData.moveDir)
{
case MoveDirection.Left:
if (axis == Axis.Horizontal && FindSelectableOnLeft() == null)
Set(ReverseValue ? value + stepSize : value - stepSize);
else
base.OnMove(eventData);
break;
case MoveDirection.Right:
if (axis == Axis.Horizontal && FindSelectableOnRight() == null)
Set(ReverseValue ? value - stepSize : value + stepSize);
else
base.OnMove(eventData);
break;
case MoveDirection.Up:
if (axis == Axis.Vertical && FindSelectableOnUp() == null)
Set(ReverseValue ? value - stepSize : value + stepSize);
else
base.OnMove(eventData);
break;
case MoveDirection.Down:
if (axis == Axis.Vertical && FindSelectableOnDown() == null)
Set(ReverseValue ? value + stepSize : value - stepSize);
else
base.OnMove(eventData);
break;
}
}
/// <summary>
/// See Selectable.FindSelectableOnLeft
/// </summary>
public override Selectable FindSelectableOnLeft()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnLeft();
}
/// <summary>
/// See Selectable.FindSelectableOnRight
/// </summary>
public override Selectable FindSelectableOnRight()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnRight();
}
/// <summary>
/// See Selectable.FindSelectableOnUp
/// </summary>
public override Selectable FindSelectableOnUp()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnUp();
}
/// <summary>
/// See Selectable.FindSelectableOnDown
/// </summary>
public override Selectable FindSelectableOnDown()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnDown();
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
eventData.useDragThreshold = false;
}
/// <summary>
/// Sets the direction of this slider, optionally changing the layout as well.
/// </summary>
/// <param name="vDirection">The direction of the slider</param>
/// <param name="includeRectLayouts">Should the layout be flipped together with the slider direction</param>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// mainSlider.SetDirection(Direction.LeftToRight, false);
/// }
/// }
/// ]]>
/// </code>
/// </example>
public void SetDirection(Direction vDirection, bool includeRectLayouts)
{
Axis oldAxis = axis;
bool oldReverse = ReverseValue;
this.direction = vDirection;
if (!includeRectLayouts)
return;
if (axis != oldAxis)
RectTransformUtility.FlipLayoutAxes(transform as RectTransform, true, true);
if (ReverseValue != oldReverse)
RectTransformUtility.FlipLayoutOnAxis(transform as RectTransform, (int)axis, true, true);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cb0e4398af6e4967956eef0e64893e87
timeCreated: 1754543039
+138
View File
@@ -0,0 +1,138 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AibisDream
{
public static class SetPropertyUtility
{
public static bool SetColor(ref Color currentValue, Color newValue)
{
if (Mathf.Approximately(currentValue.r, newValue.r) && Mathf.Approximately(currentValue.g, newValue.g) &&
Mathf.Approximately(currentValue.b, newValue.b) && Mathf.Approximately(currentValue.a, newValue.a))
return false;
currentValue = newValue;
return true;
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
return false;
currentValue = newValue;
return true;
}
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
{
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
return false;
currentValue = newValue;
return true;
}
}
public static class MultipleDisplayUtilities
{
/// <summary>
/// Converts the current drag position into a relative position for the display.
/// </summary>
/// <param name="eventData"></param>
/// <param name="position"></param>
/// <returns>Returns true except when the drag operation is not on the same display as it originated</returns>
public static bool GetRelativeMousePositionForDrag(PointerEventData eventData, ref Vector2 position)
{
#if UNITY_EDITOR
position = eventData.position;
#else
int pressDisplayIndex = eventData.pointerPressRaycast.displayIndex;
var relativePosition = RelativeMouseAtScaled(eventData.position);
int currentDisplayIndex = (int)relativePosition.z;
// Discard events on a different display.
if (currentDisplayIndex != pressDisplayIndex)
return false;
// If we are not on the main display then we must use the relative position.
position = pressDisplayIndex != 0 ? (Vector2)relativePosition : eventData.position;
#endif
return true;
}
/// <summary>
/// A version of Display.RelativeMouseAt that scales the position when the main display has a different rendering resolution to the system resolution.
/// By default, the mouse position is relative to the main render area, we need to adjust this so it is relative to the system resolution
/// in order to correctly determine the position on other displays.
/// </summary>
/// <returns></returns>
public static Vector3 RelativeMouseAtScaled(Vector2 position)
{
#if !UNITY_EDITOR && !UNITY_WSA
// If the main display is now the same resolution as the system then we need to scale the mouse position. (case 1141732)
if (Display.main.renderingWidth != Display.main.systemWidth || Display.main.renderingHeight != Display.main.systemHeight)
{
// The system will add padding when in full-screen and using a non-native aspect ratio. (case UUM-7893)
// For example Rendering 1920x1080 with a systeem resolution of 3440x1440 would create black bars on each side that are 330 pixels wide.
// we need to account for this or it will offset our coordinates when we are not on the main display.
var systemAspectRatio = Display.main.systemWidth / (float)Display.main.systemHeight;
var sizePlusPadding = new Vector2(Display.main.renderingWidth, Display.main.renderingHeight);
var padding = Vector2.zero;
if (Screen.fullScreen)
{
var aspectRatio = Screen.width / (float)Screen.height;
if (Display.main.systemHeight * aspectRatio < Display.main.systemWidth)
{
// Horizontal padding
sizePlusPadding.x = Display.main.renderingHeight * systemAspectRatio;
padding.x = (sizePlusPadding.x - Display.main.renderingWidth) * 0.5f;
}
else
{
// Vertical padding
sizePlusPadding.y = Display.main.renderingWidth / systemAspectRatio;
padding.y = (sizePlusPadding.y - Display.main.renderingHeight) * 0.5f;
}
}
var sizePlusPositivePadding = sizePlusPadding - padding;
// If we are not inside of the main display then we must adjust the mouse position so it is scaled by
// the main display and adjusted for any padding that may have been added due to different aspect ratios.
if (position.y < -padding.y || position.y > sizePlusPositivePadding.y ||
position.x < -padding.x || position.x > sizePlusPositivePadding.x)
{
var adjustedPosition = position;
if (!Screen.fullScreen)
{
// When in windowed mode, the window will be centered with the 0,0 coordinate at the top left, we need to adjust so it is relative to the screen instead.
adjustedPosition.x -= (Display.main.renderingWidth - Display.main.systemWidth) * 0.5f;
adjustedPosition.y -= (Display.main.renderingHeight - Display.main.systemHeight) * 0.5f;
}
else
{
// Scale the mouse position to account for the black bars when in a non-native aspect ratio.
adjustedPosition += padding;
adjustedPosition.x *= Display.main.systemWidth / sizePlusPadding.x;
adjustedPosition.y *= Display.main.systemHeight / sizePlusPadding.y;
}
var relativePos = Display.RelativeMouseAt(adjustedPosition);
// If we are not on the main display then return the adjusted position.
if (relativePos.z != 0)
return relativePos;
}
// We are using the main display.
return new Vector3(position.x, position.y, 0);
}
#endif
return Display.RelativeMouseAt(position);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 152c2cb0ff764bf995fc346ff3a490f8
timeCreated: 1754552564