Merge branch 'feature/石头完善' into 'develop'
Feature/石头完善 See merge request aibis-dream/aibis-dream!629
This commit is contained in:
@@ -79,9 +79,40 @@ namespace AibisDream.Kit
|
||||
|
||||
private readonly List<IActionQueueCallback> _actionQueueCallbacks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 确保存在可用的 <see cref="ActionKitMonoBehaviourEvents"/> 实例。
|
||||
/// 由于 <see cref="Singleton{T}"/> 不会自动创建对象,当场景里没有该组件时,某些 Action 的 Deinit 回收入队会触发 NRE。
|
||||
/// </summary>
|
||||
private static ActionKitMonoBehaviourEvents EnsureInstance()
|
||||
{
|
||||
if (Instance != null) return Instance;
|
||||
if (!Application.isPlaying) return null;
|
||||
|
||||
var go = new GameObject(nameof(ActionKitMonoBehaviourEvents));
|
||||
DontDestroyOnLoad(go);
|
||||
return go.AddComponent<ActionKitMonoBehaviourEvents>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建全局事件载体(用于 StartGlobal、回收队列等)。
|
||||
/// </summary>
|
||||
public static ActionKitMonoBehaviourEvents GetOrCreate()
|
||||
{
|
||||
return EnsureInstance();
|
||||
}
|
||||
|
||||
public static void AddCallback(IActionQueueCallback actionQueueCallback)
|
||||
{
|
||||
Instance._actionQueueCallbacks.Add(actionQueueCallback);
|
||||
var inst = EnsureInstance();
|
||||
|
||||
// 在退出/非运行态等情况下无法创建实例时,直接执行回调避免 NRE(回收/清理类回调通常是幂等的)。
|
||||
if (inst == null)
|
||||
{
|
||||
actionQueueCallback?.Call();
|
||||
return;
|
||||
}
|
||||
|
||||
inst._actionQueueCallbacks.Add(actionQueueCallback);
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.Kit
|
||||
@@ -135,12 +135,12 @@ namespace AibisDream.Kit
|
||||
|
||||
public static IActionController StartGlobal(this IAction self, Action<IActionController> onFinish = null)
|
||||
{
|
||||
return self.Start(ActionKitMonoBehaviourEvents.Instance, onFinish);
|
||||
return self.Start(ActionKitMonoBehaviourEvents.GetOrCreate(), onFinish);
|
||||
}
|
||||
|
||||
public static IActionController StartGlobal(this IAction self, Action onFinish)
|
||||
{
|
||||
return self.Start(ActionKitMonoBehaviourEvents.Instance, onFinish);
|
||||
return self.Start(ActionKitMonoBehaviourEvents.GetOrCreate(), onFinish);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩形区域组件
|
||||
/// 用于划定一个矩形区域,并判断点是否在区域内
|
||||
/// </summary>
|
||||
public class BoxArea : MonoBehaviour
|
||||
{
|
||||
[Header("Box尺寸设置")]
|
||||
[SerializeField] private float width = 10f;
|
||||
[SerializeField] private float height = 10f;
|
||||
|
||||
[Header("原点位置设置")]
|
||||
[Tooltip("设置(0,0)点在Box中的位置")]
|
||||
[SerializeField] private AnchorPoint anchorPoint = AnchorPoint.Center;
|
||||
|
||||
/// <summary>
|
||||
/// Box的宽度
|
||||
/// </summary>
|
||||
public float Width
|
||||
{
|
||||
get => width;
|
||||
set => width = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Box的高度
|
||||
/// </summary>
|
||||
public float Height
|
||||
{
|
||||
get => height;
|
||||
set => height = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锚点位置
|
||||
/// </summary>
|
||||
public AnchorPoint AnchorPoint
|
||||
{
|
||||
get => anchorPoint;
|
||||
set => anchorPoint = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Box的世界坐标边界
|
||||
/// </summary>
|
||||
public Bounds WorldBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector3 center = GetWorldCenter();
|
||||
Vector3 size = new Vector3(width, height, 0f);
|
||||
return new Bounds(center, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Box的本地坐标边界
|
||||
/// </summary>
|
||||
public Rect LocalRect
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 offset = GetAnchorOffset();
|
||||
return new Rect(offset.x, offset.y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断给定的世界坐标位置是否在Box内
|
||||
/// </summary>
|
||||
/// <param name="worldPosition">世界坐标位置</param>
|
||||
/// <returns>如果位置在Box内返回true,否则返回false</returns>
|
||||
public bool Contains(Vector3 worldPosition)
|
||||
{
|
||||
// 将世界坐标转换为本地坐标
|
||||
Vector3 localPosition = transform.InverseTransformPoint(worldPosition);
|
||||
|
||||
// 获取本地坐标的矩形区域
|
||||
Rect rect = LocalRect;
|
||||
|
||||
// 判断点是否在矩形内(只考虑X和Y轴,忽略Z轴)
|
||||
return rect.Contains(new Vector2(localPosition.x, localPosition.y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断给定的本地坐标位置是否在Box内
|
||||
/// </summary>
|
||||
/// <param name="localPosition">本地坐标位置</param>
|
||||
/// <returns>如果位置在Box内返回true,否则返回false</returns>
|
||||
public bool ContainsLocal(Vector2 localPosition)
|
||||
{
|
||||
Rect rect = LocalRect;
|
||||
return rect.Contains(localPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Box的世界坐标中心点
|
||||
/// </summary>
|
||||
private Vector3 GetWorldCenter()
|
||||
{
|
||||
Vector2 anchorOffset = GetAnchorOffset();
|
||||
Vector3 localCenter = new Vector3(anchorOffset.x + width * 0.5f, anchorOffset.y + height * 0.5f, 0f);
|
||||
return transform.TransformPoint(localCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据锚点位置获取偏移量
|
||||
/// </summary>
|
||||
private Vector2 GetAnchorOffset()
|
||||
{
|
||||
return anchorPoint switch
|
||||
{
|
||||
AnchorPoint.BottomLeft => new Vector2(0f, 0f),
|
||||
AnchorPoint.BottomCenter => new Vector2(-width * 0.5f, 0f),
|
||||
AnchorPoint.BottomRight => new Vector2(-width, 0f),
|
||||
AnchorPoint.CenterLeft => new Vector2(0f, -height * 0.5f),
|
||||
AnchorPoint.Center => new Vector2(-width * 0.5f, -height * 0.5f),
|
||||
AnchorPoint.CenterRight => new Vector2(-width, -height * 0.5f),
|
||||
AnchorPoint.TopLeft => new Vector2(0f, -height),
|
||||
AnchorPoint.TopCenter => new Vector2(-width * 0.5f, -height),
|
||||
AnchorPoint.TopRight => new Vector2(-width, -height),
|
||||
_ => new Vector2(-width * 0.5f, -height * 0.5f)
|
||||
};
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 在Scene视图中绘制Box边界(选中时)
|
||||
/// </summary>
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// 保存原始颜色
|
||||
Color originalColor = Gizmos.color;
|
||||
|
||||
// 设置Box边界颜色
|
||||
Gizmos.color = Color.cyan;
|
||||
|
||||
// 获取Box的四个角点(世界坐标)
|
||||
Vector2 offset = GetAnchorOffset();
|
||||
Vector3[] corners = new Vector3[4]
|
||||
{
|
||||
transform.TransformPoint(new Vector3(offset.x, offset.y, 0f)), // 左下角
|
||||
transform.TransformPoint(new Vector3(offset.x + width, offset.y, 0f)), // 右下角
|
||||
transform.TransformPoint(new Vector3(offset.x + width, offset.y + height, 0f)), // 右上角
|
||||
transform.TransformPoint(new Vector3(offset.x, offset.y + height, 0f)) // 左上角
|
||||
};
|
||||
|
||||
// 绘制Box边界线
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int next = (i + 1) % 4;
|
||||
Gizmos.DrawLine(corners[i], corners[next]);
|
||||
}
|
||||
|
||||
// 绘制中心点标记
|
||||
Gizmos.color = Color.green;
|
||||
Vector3 center = GetWorldCenter();
|
||||
Gizmos.DrawWireSphere(center, 0.15f);
|
||||
|
||||
// 恢复原始颜色
|
||||
Gizmos.color = originalColor;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锚点位置枚举
|
||||
/// 定义(0,0)点在Box中的位置
|
||||
/// </summary>
|
||||
public enum AnchorPoint
|
||||
{
|
||||
[Tooltip("左下角")]
|
||||
BottomLeft,
|
||||
[Tooltip("底部中心")]
|
||||
BottomCenter,
|
||||
[Tooltip("右下角")]
|
||||
BottomRight,
|
||||
[Tooltip("左侧中心")]
|
||||
CenterLeft,
|
||||
[Tooltip("中心")]
|
||||
Center,
|
||||
[Tooltip("右侧中心")]
|
||||
CenterRight,
|
||||
[Tooltip("左上角")]
|
||||
TopLeft,
|
||||
[Tooltip("顶部中心")]
|
||||
TopCenter,
|
||||
[Tooltip("右上角")]
|
||||
TopRight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9831cebfabb09f4993b755f03fcc5eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AibisDream.Framework
|
||||
{
|
||||
public class CodeTimer : IDisposable
|
||||
{
|
||||
private readonly Stopwatch _stopwatch;
|
||||
private readonly string _name;
|
||||
|
||||
public CodeTimer(string name = "")
|
||||
{
|
||||
_name = name;
|
||||
_stopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
UnityEngine.Debug.Log($"[{_name}] 耗时: {_stopwatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4637aacee2dd3146acb68ee955cc70c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,218 @@
|
||||
using UnityEngine;
|
||||
using DG.Tweening;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// 三状态位置移动器
|
||||
/// 提供Open、Close、Half三个目标位置,使用DOTween进行平滑移动
|
||||
/// 移动过程不可被打断,并提供移动状态标记
|
||||
/// </summary>
|
||||
public class ThreeStatePositionMover : MonoBehaviour
|
||||
{
|
||||
[Header("目标位置设置")]
|
||||
[SerializeField] private Transform openPosition;
|
||||
[SerializeField] private Transform closePosition;
|
||||
[SerializeField] private Transform halfPosition;
|
||||
|
||||
[Header("移动设置")]
|
||||
[SerializeField] private float moveDuration = 0.5f;
|
||||
[SerializeField] private Ease moveEase = Ease.OutQuad;
|
||||
|
||||
// 当前状态
|
||||
private PositionState _currentState = PositionState.Close;
|
||||
|
||||
// 当前正在执行的Tween
|
||||
private Tween _currentTween;
|
||||
|
||||
// 是否正在移动中
|
||||
public bool IsMoving { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 初始化时设置到Close位置
|
||||
if (closePosition != null)
|
||||
{
|
||||
transform.position = closePosition.position;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 清理Tween
|
||||
if (_currentTween != null)
|
||||
{
|
||||
_currentTween.Kill();
|
||||
_currentTween = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置到Open状态
|
||||
/// </summary>
|
||||
public void SetToOpen()
|
||||
{
|
||||
MoveToState(PositionState.Open);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置到Close状态
|
||||
/// </summary>
|
||||
public void SetToClose()
|
||||
{
|
||||
MoveToState(PositionState.Close);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置到Half状态
|
||||
/// </summary>
|
||||
public void SetToHalf()
|
||||
{
|
||||
MoveToState(PositionState.Half);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动到指定状态
|
||||
/// </summary>
|
||||
/// <param name="targetState">目标状态</param>
|
||||
public void MoveToState(PositionState targetState)
|
||||
{
|
||||
// 如果正在移动中,忽略新的移动请求
|
||||
if (IsMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经是目标状态,直接返回
|
||||
if (_currentState == targetState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标Transform
|
||||
Transform targetTransform = GetTransformForState(targetState);
|
||||
if (targetTransform == null)
|
||||
{
|
||||
Debug.LogWarning($"ThreeStatePositionMover: {targetState} 状态的Transform未设置!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标位置
|
||||
Vector3 targetPosition = targetTransform.position;
|
||||
|
||||
// 如果当前有正在执行的Tween,先停止它
|
||||
if (_currentTween != null && _currentTween.IsActive())
|
||||
{
|
||||
_currentTween.Kill();
|
||||
}
|
||||
|
||||
// 设置移动状态
|
||||
IsMoving = true;
|
||||
|
||||
// 创建新的Tween
|
||||
_currentTween = transform.DOMove(targetPosition, moveDuration)
|
||||
.SetEase(moveEase)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
IsMoving = false;
|
||||
_currentState = targetState;
|
||||
_currentTween = null;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即设置到指定状态(不播放动画)
|
||||
/// </summary>
|
||||
/// <param name="targetState">目标状态</param>
|
||||
public void SetStateImmediate(PositionState targetState)
|
||||
{
|
||||
// 如果正在移动,先停止移动
|
||||
if (_currentTween != null && _currentTween.IsActive())
|
||||
{
|
||||
_currentTween.Kill();
|
||||
_currentTween = null;
|
||||
}
|
||||
|
||||
IsMoving = false;
|
||||
_currentState = targetState;
|
||||
|
||||
Transform targetTransform = GetTransformForState(targetState);
|
||||
if (targetTransform != null)
|
||||
{
|
||||
transform.position = targetTransform.position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定状态对应的Transform
|
||||
/// </summary>
|
||||
private Transform GetTransformForState(PositionState state)
|
||||
{
|
||||
return state switch
|
||||
{
|
||||
PositionState.Open => openPosition,
|
||||
PositionState.Close => closePosition,
|
||||
PositionState.Half => halfPosition,
|
||||
_ => closePosition
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态
|
||||
/// </summary>
|
||||
public PositionState GetCurrentState()
|
||||
{
|
||||
return _currentState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置移动持续时间(运行时)
|
||||
/// </summary>
|
||||
public void SetMoveDuration(float duration)
|
||||
{
|
||||
moveDuration = duration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置缓动类型(运行时)
|
||||
/// </summary>
|
||||
public void SetMoveEase(Ease ease)
|
||||
{
|
||||
moveEase = ease;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// 编辑器辅助:在Scene视图中显示目标位置
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (openPosition != null)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireSphere(openPosition.position, 0.1f);
|
||||
}
|
||||
|
||||
if (closePosition != null)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(closePosition.position, 0.1f);
|
||||
}
|
||||
|
||||
if (halfPosition != null)
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(halfPosition.position, 0.1f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置状态枚举
|
||||
/// </summary>
|
||||
public enum PositionState
|
||||
{
|
||||
Open,
|
||||
Close,
|
||||
Half
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 033a66b82d5f349458216810d638e1f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using AibisDream.Utility;
|
||||
using UnityEditor.U2D.PSD;
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@@ -92,6 +94,31 @@ namespace AibisDream.Framework
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载 Addressable 资源,callback 接收完整 handle。调用方需在适当时机调用 Release 释放。
|
||||
/// </summary>
|
||||
public static void LoadAssetAsyncWithHandle<T>(string key, Action<AsyncOperationHandle<T>> callback) where T : UnityEngine.Object
|
||||
{
|
||||
var handle = Addressables.LoadAssetAsync<T>(key);
|
||||
handle.Completed += operation =>
|
||||
{
|
||||
if (operation.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Debug.Log($"资源{key}不存在");
|
||||
}
|
||||
callback(operation);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 Addressable 加载的 handle
|
||||
/// </summary>
|
||||
public static void Release<T>(AsyncOperationHandle<T> handle)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static bool GetAssetAddressableKey<T>(T asset, out string addressableKey) where T : UnityEngine.Object
|
||||
{
|
||||
@@ -114,6 +141,19 @@ namespace AibisDream.Framework
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (asset is Sprite texture && AssetImporter.GetAtPath(path) is PSDImporter psdImporter)
|
||||
{
|
||||
if (TryGetAddressableKey(path, out var addressableKeyPrefix))
|
||||
{
|
||||
addressableKey = $"{addressableKeyPrefix}[{texture.name}]";
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
addressableKey = addressableKeyPrefix;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 其他情况正常输出
|
||||
return TryGetAddressableKey(path, out addressableKey);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace AibisDream.Kit
|
||||
@@ -26,6 +26,6 @@ namespace AibisDream.Kit
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void OnSingletonInit();
|
||||
public virtual void OnSingletonInit() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user