画报编辑和播放添加动画支持
parent
7e7a2f4c37
commit
c2f5ca10d0
|
|
@ -3,6 +3,7 @@ using UnityEngine.UI;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PhxhSDK;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
|
|
@ -464,6 +465,19 @@ namespace Framework.UI.Pictorial
|
|||
return;
|
||||
}
|
||||
|
||||
// ---- 退出动画:在卸载旧画报前播放 ----
|
||||
if (items.Count > 0 && pictorialData?.animData != null)
|
||||
{
|
||||
var oldAnimData = pictorialData.animData;
|
||||
bool hasExitAnim = (oldAnimData.bgExitAnim != null && oldAnimData.bgExitAnim.enabled) ||
|
||||
(oldAnimData.itemExitAnims != null && oldAnimData.itemExitAnims.Any(a => a != null && a.enabled));
|
||||
if (hasExitAnim)
|
||||
{
|
||||
Debug.Log("[画报系统] 播放旧画报退出动画");
|
||||
await PlayExitAnimAsync(oldAnimData);
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏CharatorItem模板
|
||||
Transform templateTransform = transform.Find("CharatorItem");
|
||||
if (templateTransform != null)
|
||||
|
|
@ -553,12 +567,13 @@ namespace Framework.UI.Pictorial
|
|||
}
|
||||
|
||||
// 恢复背景图片到Item_ATemp_Bg_Second节点
|
||||
Image bgImage = null;
|
||||
if (!string.IsNullOrEmpty(pictorialData.backgroundPath))
|
||||
{
|
||||
Transform bgImageTransform = transform.Find("Item_ATemp_Bg_Second");
|
||||
if (bgImageTransform != null)
|
||||
{
|
||||
Image bgImage = bgImageTransform.GetComponent<Image>();
|
||||
bgImage = bgImageTransform.GetComponent<Image>();
|
||||
if (bgImage != null)
|
||||
{
|
||||
// 使用LoadSpriteFromPath加载背景图片(支持子资源)
|
||||
|
|
@ -619,6 +634,214 @@ namespace Framework.UI.Pictorial
|
|||
Debug.Log($"<color=yellow>[画报系统] 当前背景尺寸 {bgWidth}x{bgHeight} 没有精确匹配配置,使用插值计算位置</color>");
|
||||
UpdateAllItemsPosition();
|
||||
}
|
||||
|
||||
// ---- 进入动画:画报元素创建并定位完成后播放 ----
|
||||
if (pictorialData.animData != null)
|
||||
{
|
||||
var animData = pictorialData.animData;
|
||||
bool hasEnterAnim = (animData.bgEnterAnim != null && animData.bgEnterAnim.enabled) ||
|
||||
(animData.itemEnterAnims != null && animData.itemEnterAnims.Any(a => a != null && a.enabled));
|
||||
if (hasEnterAnim)
|
||||
{
|
||||
Debug.Log("[画报系统] 播放新画报进入动画");
|
||||
await PlayEnterAnimAsync(animData, bgImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 动画方法 =====================
|
||||
|
||||
/// <summary>
|
||||
/// 播放进入动画(异步)
|
||||
/// 进入动画:元素从(实际位置 + offset)移动回实际位置,alpha 从 alphaStart 到 alphaEnd
|
||||
/// </summary>
|
||||
public async UniTask PlayEnterAnimAsync(PictorialAnimData animData, Image bgImageOverride = null)
|
||||
{
|
||||
if (animData == null) return;
|
||||
|
||||
List<PictorialItem> sceneItems = GetSortedSceneItems();
|
||||
var targets = PrepareAnimTargets(sceneItems, animData, bgImageOverride, isEnter: true);
|
||||
|
||||
if (targets.Count == 0) return;
|
||||
|
||||
await RunAnimTween(targets, animData.enterDuration, isEnter: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放退出动画(异步)
|
||||
/// 退出动画:元素从实际位置移动到(实际位置 + offset),alpha 从 alphaStart 到 alphaEnd
|
||||
/// </summary>
|
||||
public async UniTask PlayExitAnimAsync(PictorialAnimData animData)
|
||||
{
|
||||
if (animData == null) return;
|
||||
|
||||
// 退出动画使用当前场景中的小背景图片
|
||||
Image bgImg = null;
|
||||
Transform bgT = transform.Find("Item_ATemp_Bg_Second");
|
||||
if (bgT != null) bgImg = bgT.GetComponent<Image>();
|
||||
|
||||
List<PictorialItem> sceneItems = GetSortedSceneItems();
|
||||
var targets = PrepareAnimTargets(sceneItems, animData, bgImg, isEnter: false);
|
||||
|
||||
if (targets.Count == 0) return;
|
||||
|
||||
await RunAnimTween(targets, animData.exitDuration, isEnter: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画目标数据
|
||||
/// </summary>
|
||||
private struct AnimTarget
|
||||
{
|
||||
public RectTransform rectTransform;
|
||||
public Image image;
|
||||
public Vector3 basePosition; // 实际计算位置(anchoredPosition3D)
|
||||
public PictorialItemAnimData animData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建动画目标列表
|
||||
/// </summary>
|
||||
private List<AnimTarget> PrepareAnimTargets(
|
||||
List<PictorialItem> sceneItems,
|
||||
PictorialAnimData animData,
|
||||
Image bgImageRef,
|
||||
bool isEnter)
|
||||
{
|
||||
var targets = new List<AnimTarget>();
|
||||
|
||||
// 处理小背景
|
||||
var bgAnim = isEnter ? animData.bgEnterAnim : animData.bgExitAnim;
|
||||
if (bgAnim != null && bgAnim.enabled && bgImageRef != null)
|
||||
{
|
||||
var bgRect = bgImageRef.GetComponent<RectTransform>();
|
||||
if (bgRect != null)
|
||||
{
|
||||
targets.Add(new AnimTarget
|
||||
{
|
||||
rectTransform = bgRect,
|
||||
image = bgImageRef,
|
||||
basePosition = bgRect.anchoredPosition3D,
|
||||
animData = bgAnim
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理各画报元素
|
||||
var itemAnimList = isEnter ? animData.itemEnterAnims : animData.itemExitAnims;
|
||||
if (itemAnimList == null) return targets;
|
||||
|
||||
for (int i = 0; i < sceneItems.Count; i++)
|
||||
{
|
||||
if (i >= itemAnimList.Count) break;
|
||||
|
||||
var ia = itemAnimList[i];
|
||||
if (ia == null || !ia.enabled) continue;
|
||||
|
||||
var item = sceneItems[i];
|
||||
var itemRect = item.GetComponent<RectTransform>();
|
||||
var itemImage = item.GetComponentInChildren<Image>(true);
|
||||
|
||||
if (itemRect == null) continue;
|
||||
|
||||
targets.Add(new AnimTarget
|
||||
{
|
||||
rectTransform = itemRect,
|
||||
image = itemImage,
|
||||
basePosition = itemRect.anchoredPosition3D,
|
||||
animData = ia
|
||||
});
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行动画补间(SmoothStep 曲线)
|
||||
/// isEnter=true: 从 (basePos + offset, alphaStart) 到 (basePos, alphaEnd)
|
||||
/// isEnter=false: 从 (basePos, alphaStart) 到 (basePos + offset, alphaEnd)
|
||||
/// </summary>
|
||||
private async UniTask RunAnimTween(List<AnimTarget> targets, float duration, bool isEnter)
|
||||
{
|
||||
if (duration <= 0f || targets.Count == 0) return;
|
||||
|
||||
// 设置初始状态
|
||||
foreach (var t in targets)
|
||||
{
|
||||
if (t.rectTransform == null) continue;
|
||||
if (isEnter)
|
||||
{
|
||||
t.rectTransform.anchoredPosition3D = t.basePosition + t.animData.offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
t.rectTransform.anchoredPosition3D = t.basePosition;
|
||||
}
|
||||
if (t.image != null)
|
||||
SetImageAlpha(t.image, t.animData.alphaStart);
|
||||
}
|
||||
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
await UniTask.Yield();
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float rawT = Mathf.Clamp01(elapsed / duration);
|
||||
float smoothT = Mathf.SmoothStep(0f, 1f, rawT);
|
||||
|
||||
foreach (var t in targets)
|
||||
{
|
||||
if (t.rectTransform == null) continue;
|
||||
|
||||
Vector3 fromPos = isEnter ? (t.basePosition + t.animData.offset) : t.basePosition;
|
||||
Vector3 toPos = isEnter ? t.basePosition : (t.basePosition + t.animData.offset);
|
||||
t.rectTransform.anchoredPosition3D = Vector3.Lerp(fromPos, toPos, smoothT);
|
||||
|
||||
if (t.image != null)
|
||||
{
|
||||
float alpha = Mathf.Lerp(t.animData.alphaStart, t.animData.alphaEnd, smoothT);
|
||||
SetImageAlpha(t.image, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保最终状态精确
|
||||
foreach (var t in targets)
|
||||
{
|
||||
if (t.rectTransform == null) continue;
|
||||
t.rectTransform.anchoredPosition3D = isEnter ? t.basePosition : (t.basePosition + t.animData.offset);
|
||||
if (t.image != null)
|
||||
SetImageAlpha(t.image, t.animData.alphaEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取场景中按层次排序的 PictorialItem 列表(过滤特殊节点)
|
||||
/// </summary>
|
||||
private List<PictorialItem> GetSortedSceneItems()
|
||||
{
|
||||
var result = new List<PictorialItem>();
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
Transform child = transform.GetChild(i);
|
||||
if (child.name.Contains("Item_ATemp_Bg_Second") || child.name.Contains("CharatorItem"))
|
||||
continue;
|
||||
var item = child.GetComponent<PictorialItem>();
|
||||
if (item != null)
|
||||
result.Add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Image 组件的 alpha 值
|
||||
/// </summary>
|
||||
private static void SetImageAlpha(Image image, float alpha)
|
||||
{
|
||||
if (image == null) return;
|
||||
Color c = image.color;
|
||||
c.a = Mathf.Clamp01(alpha);
|
||||
image.color = c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ namespace Framework.UI.Pictorial
|
|||
/// 保存的分辨率配置列表
|
||||
/// </summary>
|
||||
public List<ResolutionConfig> resolutionConfigs = new List<ResolutionConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// 画报动画配置(可选)
|
||||
/// </summary>
|
||||
public PictorialAnimData animData = new PictorialAnimData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -180,4 +185,70 @@ namespace Framework.UI.Pictorial
|
|||
/// </summary>
|
||||
public Vector3 scale = Vector3.one;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画报整体动画配置
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PictorialAnimData
|
||||
{
|
||||
/// <summary>
|
||||
/// 进入动画时长(秒)
|
||||
/// </summary>
|
||||
public float enterDuration = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// 退出动画时长(秒)
|
||||
/// </summary>
|
||||
public float exitDuration = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// 各元素的进入动画数据(与 PictorialData.items 索引对应)
|
||||
/// </summary>
|
||||
public List<PictorialItemAnimData> itemEnterAnims = new List<PictorialItemAnimData>();
|
||||
|
||||
/// <summary>
|
||||
/// 各元素的退出动画数据(与 PictorialData.items 索引对应)
|
||||
/// </summary>
|
||||
public List<PictorialItemAnimData> itemExitAnims = new List<PictorialItemAnimData>();
|
||||
|
||||
/// <summary>
|
||||
/// 小背景(Item_ATemp_Bg_Second)的进入动画
|
||||
/// </summary>
|
||||
public PictorialItemAnimData bgEnterAnim = new PictorialItemAnimData();
|
||||
|
||||
/// <summary>
|
||||
/// 小背景(Item_ATemp_Bg_Second)的退出动画
|
||||
/// </summary>
|
||||
public PictorialItemAnimData bgExitAnim = new PictorialItemAnimData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个元素的动画属性
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PictorialItemAnimData
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否启用动画
|
||||
/// </summary>
|
||||
public bool enabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// 位移偏移量。
|
||||
/// 进入动画:元素从(实际位置 + offset)移动回实际位置。
|
||||
/// 退出动画:元素从实际位置移动到(实际位置 + offset)。
|
||||
/// </summary>
|
||||
public Vector3 offset = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 起始 Alpha(动画开始时)
|
||||
/// </summary>
|
||||
public float alphaStart = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// 结束 Alpha(动画结束时)
|
||||
/// </summary>
|
||||
public float alphaEnd = 1f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6944,5 +6944,153 @@
|
|||
"backgroundHeight": 756.0,
|
||||
"timestamp": "2026-03-04 12:05:16"
|
||||
}
|
||||
]
|
||||
],
|
||||
"animData": {
|
||||
"enterDuration": 0.5,
|
||||
"exitDuration": 0.30000001192092898,
|
||||
"itemEnterAnims": [
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 50.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.43799999356269839,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 50.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.31299999356269839,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 60.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.20800000429153443,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 50.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": -50.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.3109999895095825,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
}
|
||||
],
|
||||
"itemExitAnims": [
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 60.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 50.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
}
|
||||
],
|
||||
"bgEnterAnim": {
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 30.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.5,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
"bgExitAnim": {
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3116,5 +3116,153 @@
|
|||
"backgroundHeight": 756.0,
|
||||
"timestamp": "2026-02-27 14:45:36"
|
||||
}
|
||||
]
|
||||
],
|
||||
"animData": {
|
||||
"enterDuration": 0.5,
|
||||
"exitDuration": 0.30000001192092898,
|
||||
"itemEnterAnims": [
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 30.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
}
|
||||
],
|
||||
"itemExitAnims": [
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 30.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
}
|
||||
],
|
||||
"bgEnterAnim": {
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 0.0,
|
||||
"alphaEnd": 1.0
|
||||
},
|
||||
"bgExitAnim": {
|
||||
"enabled": true,
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"alphaStart": 1.0,
|
||||
"alphaEnd": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,9 @@ namespace PictorialEditor
|
|||
{
|
||||
// 取消注册
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
// 确保动画预览清理
|
||||
if (isAnimPreviewPlaying)
|
||||
StopAnimPreview();
|
||||
Debug.Log("[画报编辑器] 已取消EditorApplication.update");
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +63,7 @@ namespace PictorialEditor
|
|||
|
||||
private Vector2 scrollPos;
|
||||
private string pictorialName = "NewPictorial";
|
||||
private string savePath = "Assets/Resources/Pictorial/";
|
||||
private string savePath = "Assets/Config/Data/ActivityPicturialData/";
|
||||
private Sprite selectedMainBackgroundSprite; // 大背景图片(Sprite)
|
||||
private Texture2D selectedBackgroundTexture;
|
||||
private Texture2D selectedItemTexture;
|
||||
|
|
@ -74,6 +77,20 @@ namespace PictorialEditor
|
|||
private double lastCheckTime;
|
||||
private bool forceCheck = false; // 强制检查标志
|
||||
|
||||
// 动画编辑面板
|
||||
private bool animSectionFoldout = false;
|
||||
private bool animItemsFoldout = false;
|
||||
|
||||
// 动画预览状态
|
||||
private bool isAnimPreviewPlaying = false;
|
||||
private double animPreviewStartTime;
|
||||
private float animPreviewDuration;
|
||||
private bool animPreviewIsEnter;
|
||||
// 预览前保存的元素状态:(RectTransform, origPos, origAlpha, Image)
|
||||
private System.Collections.Generic.List<(UnityEngine.RectTransform rt, Vector3 origPos, float origAlpha, Image img)> animPreviewSnapshot;
|
||||
// 预览动画目标
|
||||
private System.Collections.Generic.List<(UnityEngine.RectTransform rt, Image img, Vector3 basePos, PictorialItemAnimData anim)> animPreviewTargets;
|
||||
|
||||
private const string EDITOR_SCENE_PATH = "Assets/Editor/Pictorial/PictorialEditor.unity";
|
||||
private const string CANVAS_PATH = "Canvas";
|
||||
// 注意:大背景路径是相对路径,在编辑器场景中可能不存在
|
||||
|
|
@ -426,35 +443,333 @@ namespace PictorialEditor
|
|||
private void DrawPictorialManagement()
|
||||
{
|
||||
EditorGUILayout.LabelField("2. 画报元素管理", EditorStyles.boldLabel);
|
||||
|
||||
|
||||
EditorGUILayout.LabelField($"当前画报: {pictorialData.pictorialName}");
|
||||
EditorGUILayout.LabelField($"元素数量: {pictorialData.items.Count}");
|
||||
EditorGUILayout.LabelField($"分辨率配置: {pictorialData.resolutionConfigs.Count}");
|
||||
|
||||
|
||||
// 显示提示信息
|
||||
EditorGUILayout.HelpBox(
|
||||
"层次顺序说明:\n" +
|
||||
"- 在Hierarchy视图中,可以直接拖拽元素调整渲染顺序\n" +
|
||||
"- 调整后,点击‘保存当前分辨率数据’即可保存层次关系\n" +
|
||||
"- 调整后,点击'保存当前分辨率数据'即可保存层次关系\n" +
|
||||
"- 层次关系会同步应用到所有分辨率配置",
|
||||
MessageType.Info
|
||||
);
|
||||
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
|
||||
// 添加画报元素
|
||||
DrawAddItemSection();
|
||||
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
|
||||
// 动画配置
|
||||
DrawAnimSection();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 分辨率配置
|
||||
DrawResolutionSection();
|
||||
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
|
||||
// 保存/导出
|
||||
DrawSaveSection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画配置区域
|
||||
/// </summary>
|
||||
private void DrawAnimSection()
|
||||
{
|
||||
// 确保 animData 不为 null
|
||||
if (pictorialData.animData == null)
|
||||
pictorialData.animData = new PictorialAnimData();
|
||||
|
||||
var anim = pictorialData.animData;
|
||||
|
||||
animSectionFoldout = EditorGUILayout.Foldout(animSectionFoldout, "动画配置", true, EditorStyles.foldoutHeader);
|
||||
if (!animSectionFoldout) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// --- 全局时长 ---
|
||||
EditorGUILayout.LabelField("全局动画时长", EditorStyles.boldLabel);
|
||||
anim.enterDuration = EditorGUILayout.FloatField("进入动画时长(秒)", anim.enterDuration);
|
||||
anim.exitDuration = EditorGUILayout.FloatField("退出动画时长(秒)", anim.exitDuration);
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// --- 预览按钮 ---
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(isAnimPreviewPlaying);
|
||||
|
||||
if (GUILayout.Button("进入动画预览", GUILayout.Height(28)))
|
||||
{
|
||||
StartAnimPreview(isEnter: true);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("退出动画预览", GUILayout.Height(28)))
|
||||
{
|
||||
StartAnimPreview(isEnter: false);
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (isAnimPreviewPlaying)
|
||||
{
|
||||
if (GUILayout.Button("停止预览", GUILayout.Width(70), GUILayout.Height(28)))
|
||||
{
|
||||
StopAnimPreview();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (isAnimPreviewPlaying)
|
||||
{
|
||||
double elapsed = EditorApplication.timeSinceStartup - animPreviewStartTime;
|
||||
float t = Mathf.Clamp01((float)(elapsed / animPreviewDuration));
|
||||
EditorGUILayout.HelpBox($"预览播放中... {t * 100f:F0}%", MessageType.None);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// --- 小背景动画 ---
|
||||
EditorGUILayout.LabelField("小背景 (Item_ATemp_Bg_Second) 动画", EditorStyles.boldLabel);
|
||||
DrawItemAnimDataFields("进入动画", ref anim.bgEnterAnim);
|
||||
DrawItemAnimDataFields("退出动画", ref anim.bgExitAnim);
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// --- 各元素动画 ---
|
||||
animItemsFoldout = EditorGUILayout.Foldout(animItemsFoldout, $"画报元素动画 (共 {pictorialData.items.Count} 个)", true);
|
||||
if (animItemsFoldout)
|
||||
{
|
||||
// 确保列表长度与元素数量一致
|
||||
EnsureAnimListSize(anim.itemEnterAnims, pictorialData.items.Count);
|
||||
EnsureAnimListSize(anim.itemExitAnims, pictorialData.items.Count);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
for (int i = 0; i < pictorialData.items.Count; i++)
|
||||
{
|
||||
string itemName = pictorialData.items[i].itemName;
|
||||
EditorGUILayout.LabelField($"[{i}] {itemName}", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
DrawItemAnimDataFields("进入动画", ref anim.itemEnterAnims, i);
|
||||
DrawItemAnimDataFields("退出动画", ref anim.itemExitAnims, i);
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space(3);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制单个 PictorialItemAnimData 字段(列表版本,按索引)
|
||||
/// </summary>
|
||||
private void DrawItemAnimDataFields(string label, ref System.Collections.Generic.List<PictorialItemAnimData> list, int index)
|
||||
{
|
||||
var data = list[index];
|
||||
DrawItemAnimDataFields(label, ref data);
|
||||
list[index] = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制单个 PictorialItemAnimData 字段(引用版本)
|
||||
/// </summary>
|
||||
private void DrawItemAnimDataFields(string label, ref PictorialItemAnimData data)
|
||||
{
|
||||
if (data == null) data = new PictorialItemAnimData();
|
||||
|
||||
data.enabled = EditorGUILayout.Toggle(label + " 启用", data.enabled);
|
||||
if (data.enabled)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
data.offset = EditorGUILayout.Vector3Field("位移偏移量", data.offset);
|
||||
data.alphaStart = EditorGUILayout.Slider("Alpha 起始", data.alphaStart, 0f, 1f);
|
||||
data.alphaEnd = EditorGUILayout.Slider("Alpha 结束", data.alphaEnd, 0f, 1f);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保动画列表长度至少有 targetCount 个,只添加不截断
|
||||
/// (截断逻辑由 SyncItemDataWithScene/AddPictorialItem 精确管理)
|
||||
/// </summary>
|
||||
private void EnsureAnimListSize(System.Collections.Generic.List<PictorialItemAnimData> list, int targetCount)
|
||||
{
|
||||
while (list.Count < targetCount)
|
||||
list.Add(new PictorialItemAnimData());
|
||||
}
|
||||
|
||||
// ===================== 动画预览 =====================
|
||||
|
||||
/// <summary>
|
||||
/// 开始动画预览(编辑器模式下使用 EditorApplication.update 驱动)
|
||||
/// </summary>
|
||||
private void StartAnimPreview(bool isEnter)
|
||||
{
|
||||
if (pictorialData?.animData == null) return;
|
||||
|
||||
var anim = pictorialData.animData;
|
||||
animPreviewIsEnter = isEnter;
|
||||
animPreviewDuration = isEnter ? anim.enterDuration : anim.exitDuration;
|
||||
if (animPreviewDuration <= 0f) animPreviewDuration = 0.01f;
|
||||
|
||||
// 收集场景中的元素(编辑器里用 PictorialItemIdentifier 标识,按 pictorialData.items 顺序匹配)
|
||||
var bgT = backgroundObject?.transform.Find("Item_ATemp_Bg_Second");
|
||||
Image bgImg = bgT?.GetComponent<Image>();
|
||||
|
||||
// 按 pictorialData.items 顺序,通过 itemId 找到对应 GameObject
|
||||
var sceneItems = new System.Collections.Generic.List<(UnityEngine.RectTransform, Image)>();
|
||||
foreach (var itemData in pictorialData.items)
|
||||
{
|
||||
GameObject itemObj = itemObjects.FirstOrDefault(obj =>
|
||||
{
|
||||
if (obj == null) return false;
|
||||
var id = obj.GetComponent<PictorialItemIdentifier>();
|
||||
return id != null && id.itemId == itemData.itemId;
|
||||
});
|
||||
if (itemObj != null)
|
||||
{
|
||||
var rt = itemObj.GetComponent<UnityEngine.RectTransform>();
|
||||
var img = itemObj.GetComponentInChildren<Image>(true);
|
||||
sceneItems.Add((rt, img));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 占位,保持与 itemEnterAnims/itemExitAnims 的索引对齐
|
||||
sceneItems.Add((null, null));
|
||||
}
|
||||
}
|
||||
|
||||
// 保存初始状态快照
|
||||
animPreviewSnapshot = new System.Collections.Generic.List<(UnityEngine.RectTransform, Vector3, float, Image)>();
|
||||
|
||||
if (bgImg != null)
|
||||
{
|
||||
var bgRt = bgImg.GetComponent<UnityEngine.RectTransform>();
|
||||
animPreviewSnapshot.Add((bgRt, bgRt.anchoredPosition3D, bgImg.color.a, bgImg));
|
||||
}
|
||||
|
||||
foreach (var (rt, img) in sceneItems)
|
||||
{
|
||||
if (rt == null) continue; // 跳过占位
|
||||
animPreviewSnapshot.Add((rt, rt.anchoredPosition3D, img != null ? img.color.a : 1f, img));
|
||||
}
|
||||
|
||||
// 构建动画目标
|
||||
animPreviewTargets = new System.Collections.Generic.List<(UnityEngine.RectTransform, Image, Vector3, PictorialItemAnimData)>();
|
||||
|
||||
var bgAnimData = isEnter ? anim.bgEnterAnim : anim.bgExitAnim;
|
||||
if (bgAnimData != null && bgAnimData.enabled && bgImg != null)
|
||||
{
|
||||
var rt = bgImg.GetComponent<UnityEngine.RectTransform>();
|
||||
animPreviewTargets.Add((rt, bgImg, rt.anchoredPosition3D, bgAnimData));
|
||||
}
|
||||
|
||||
var itemAnimList = isEnter ? anim.itemEnterAnims : anim.itemExitAnims;
|
||||
EnsureAnimListSize(itemAnimList, sceneItems.Count);
|
||||
for (int i = 0; i < sceneItems.Count; i++)
|
||||
{
|
||||
if (i >= itemAnimList.Count) break;
|
||||
var ia = itemAnimList[i];
|
||||
if (ia == null || !ia.enabled) continue;
|
||||
var (rt, img) = sceneItems[i];
|
||||
if (rt == null) continue; // 跳过占位(元素不在场景中)
|
||||
animPreviewTargets.Add((rt, img, rt.anchoredPosition3D, ia));
|
||||
}
|
||||
|
||||
if (animPreviewTargets.Count == 0)
|
||||
{
|
||||
Debug.Log("[画报编辑器] 动画预览:没有启用动画的元素");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置初始状态
|
||||
foreach (var (rt, img, basePos, ia) in animPreviewTargets)
|
||||
{
|
||||
rt.anchoredPosition3D = isEnter ? (basePos + ia.offset) : basePos;
|
||||
if (img != null) SetPreviewImageAlpha(img, ia.alphaStart);
|
||||
}
|
||||
|
||||
animPreviewStartTime = EditorApplication.timeSinceStartup;
|
||||
isAnimPreviewPlaying = true;
|
||||
|
||||
// 注册帧更新
|
||||
EditorApplication.update += OnAnimPreviewUpdate;
|
||||
|
||||
Debug.Log($"[画报编辑器] 开始{(isEnter ? "进入" : "退出")}动画预览,时长={animPreviewDuration}s,目标数={animPreviewTargets.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画预览逐帧更新
|
||||
/// </summary>
|
||||
private void OnAnimPreviewUpdate()
|
||||
{
|
||||
if (!isAnimPreviewPlaying) return;
|
||||
|
||||
double elapsed = EditorApplication.timeSinceStartup - animPreviewStartTime;
|
||||
float rawT = Mathf.Clamp01((float)(elapsed / animPreviewDuration));
|
||||
float smoothT = Mathf.SmoothStep(0f, 1f, rawT);
|
||||
|
||||
bool isEnter = animPreviewIsEnter;
|
||||
|
||||
foreach (var (rt, img, basePos, ia) in animPreviewTargets)
|
||||
{
|
||||
if (rt == null) continue;
|
||||
Vector3 fromPos = isEnter ? (basePos + ia.offset) : basePos;
|
||||
Vector3 toPos = isEnter ? basePos : (basePos + ia.offset);
|
||||
rt.anchoredPosition3D = Vector3.Lerp(fromPos, toPos, smoothT);
|
||||
if (img != null)
|
||||
SetPreviewImageAlpha(img, Mathf.Lerp(ia.alphaStart, ia.alphaEnd, smoothT));
|
||||
}
|
||||
|
||||
SceneView.RepaintAll();
|
||||
Repaint();
|
||||
|
||||
if (rawT >= 1f)
|
||||
{
|
||||
// 播放结束,恢复原始状态
|
||||
StopAnimPreview();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止并恢复动画预览
|
||||
/// </summary>
|
||||
private void StopAnimPreview()
|
||||
{
|
||||
isAnimPreviewPlaying = false;
|
||||
EditorApplication.update -= OnAnimPreviewUpdate;
|
||||
|
||||
// 恢复所有元素到初始状态
|
||||
if (animPreviewSnapshot != null)
|
||||
{
|
||||
foreach (var (rt, origPos, origAlpha, img) in animPreviewSnapshot)
|
||||
{
|
||||
if (rt != null) rt.anchoredPosition3D = origPos;
|
||||
if (img != null) SetPreviewImageAlpha(img, origAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
SceneView.RepaintAll();
|
||||
Repaint();
|
||||
Debug.Log("[画报编辑器] 动画预览结束,已恢复元素状态");
|
||||
}
|
||||
|
||||
private static void SetPreviewImageAlpha(Image image, float alpha)
|
||||
{
|
||||
if (image == null) return;
|
||||
Color c = image.color;
|
||||
c.a = Mathf.Clamp01(alpha);
|
||||
image.color = c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新建画报(清空当前状态,进入新画报编辑流程)
|
||||
|
|
@ -747,12 +1062,33 @@ namespace PictorialEditor
|
|||
}
|
||||
|
||||
// 删除已不存在于场景中的元素数据
|
||||
// 先收集所有要删除的原始索引,然后从大到小倒序删除,避免顺序删除时索引偏移
|
||||
if (itemsToRemove.Count > 0)
|
||||
{
|
||||
// 收集原始索引(在任何删除发生前)
|
||||
var indexesToRemove = itemsToRemove
|
||||
.Select(itemData => pictorialData.items.IndexOf(itemData))
|
||||
.Where(idx => idx >= 0)
|
||||
.OrderByDescending(idx => idx)
|
||||
.ToList();
|
||||
|
||||
// 先从 items 列表中移除
|
||||
foreach (var itemData in itemsToRemove)
|
||||
{
|
||||
pictorialData.items.Remove(itemData);
|
||||
Debug.Log($"[画报编辑器] 检测到元素已被删除,同步移除数据: {itemData.itemName}");
|
||||
pictorialData.items.Remove(itemData);
|
||||
}
|
||||
|
||||
// 从大到小倒序删除动画数据,保证索引不偏移
|
||||
if (pictorialData.animData != null)
|
||||
{
|
||||
foreach (int idx in indexesToRemove)
|
||||
{
|
||||
if (idx < pictorialData.animData.itemEnterAnims.Count)
|
||||
pictorialData.animData.itemEnterAnims.RemoveAt(idx);
|
||||
if (idx < pictorialData.animData.itemExitAnims.Count)
|
||||
pictorialData.animData.itemExitAnims.RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1265,7 +1601,7 @@ namespace PictorialEditor
|
|||
}
|
||||
|
||||
// 打开文件选择对话框
|
||||
string jsonPath = EditorUtility.OpenFilePanel("选择画报JSON文件", "Assets/Resources/Pictorial", "json");
|
||||
string jsonPath = EditorUtility.OpenFilePanel("选择画报JSON文件", "Assets/Config/Data/ActivityPicturialData", "json");
|
||||
|
||||
if (string.IsNullOrEmpty(jsonPath))
|
||||
{
|
||||
|
|
@ -1592,6 +1928,13 @@ namespace PictorialEditor
|
|||
|
||||
pictorialData.items.Add(itemData);
|
||||
|
||||
// 同步添加动画数据占位(保持与 items 索引对齐)
|
||||
if (pictorialData.animData != null)
|
||||
{
|
||||
pictorialData.animData.itemEnterAnims.Add(new PictorialItemAnimData());
|
||||
pictorialData.animData.itemExitAnims.Add(new PictorialItemAnimData());
|
||||
}
|
||||
|
||||
// 克隆模板元素
|
||||
GameObject itemObj = Instantiate(templateItemObject, backgroundObject.transform);
|
||||
itemObj.name = itemName;
|
||||
|
|
|
|||
Loading…
Reference in New Issue