Merge branch 'feature/帧动画编辑器重构' into 'develop'

Feature/帧动画编辑器重构

See merge request aibis-dream/aibis-dream!771
This commit is contained in:
2026-07-22 14:30:43 +00:00
15 changed files with 3025 additions and 282 deletions
@@ -0,0 +1,254 @@
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace AibisDream.FrameAnimation.Editor
{
internal enum FrameAnimationBrowserEntryKind
{
Header,
Clip,
Flow,
Source
}
internal enum FrameAnimationBrowserIssueState
{
None,
Warning,
Error
}
internal sealed class FrameAnimationBrowserEntry
{
public FrameAnimationBrowserEntryKind Kind;
public string Title = string.Empty;
public string Id = string.Empty;
public string TypeLabel = string.Empty;
public string Meta = string.Empty;
public string StatusLabel = string.Empty;
public Color? AccentColor;
public FrameAnimationBrowserIssueState IssueState;
public FrameAnimationEditorSelection Selection;
public bool IsHeader => Kind == FrameAnimationBrowserEntryKind.Header;
}
internal readonly struct FrameAnimationFlowColorPalette
{
public readonly Color Raw;
public readonly Color Stroke;
public readonly Color Header;
public readonly Color Muted;
public readonly Color Text;
public FrameAnimationFlowColorPalette(Color raw, Color stroke, Color header, Color muted, Color text)
{
Raw = raw;
Stroke = stroke;
Header = header;
Muted = muted;
Text = text;
}
}
internal static class FrameAnimationFlowColorUtility
{
private static readonly Color Panel = new Color32(43, 47, 52, 255);
private static readonly Color LightText = new Color32(244, 246, 248, 255);
private static readonly Color DarkText = new Color32(24, 27, 30, 255);
private static readonly Color Neutral = new Color32(100, 168, 216, 255);
internal static Color ResolveRaw(FrameAnimationGraph graph, AnimationFlow flow)
{
return Opaque(ResolveStored(graph, flow));
}
internal static Color ResolveStored(FrameAnimationGraph graph, AnimationFlow flow)
{
if (graph == null || flow == null)
{
return Neutral;
}
var data = graph.EditorData?.FlowEditorData == null
? null
: System.Linq.Enumerable.FirstOrDefault(graph.EditorData.FlowEditorData,
item => item != null && item.FlowId == flow.Id);
return data?.Color ?? Neutral;
}
internal static FrameAnimationFlowColorPalette CreatePalette(Color value)
{
var raw = Opaque(value);
var luminance = Luminance(raw);
var stroke = luminance < 0.30f
? Color.Lerp(raw, Color.white, Mathf.InverseLerp(0.30f, 0.02f, luminance) * 0.42f)
: luminance > 0.82f
? Color.Lerp(raw, Color.black, Mathf.InverseLerp(0.82f, 1f, luminance) * 0.22f)
: raw;
stroke.a = 1f;
var header = Color.Lerp(Panel, stroke, 0.68f);
header.a = 1f;
var muted = Color.Lerp(Panel, stroke, 0.42f);
muted.a = 1f;
var text = Luminance(header) > 0.22f ? DarkText : LightText;
return new FrameAnimationFlowColorPalette(raw, stroke, header, muted, text);
}
internal static Color Opaque(Color value) => new Color(
Mathf.Clamp01(value.r), Mathf.Clamp01(value.g), Mathf.Clamp01(value.b), 1f);
internal static float Luminance(Color value)
{
var linear = value.linear;
return linear.r * 0.2126f + linear.g * 0.7152f + linear.b * 0.0722f;
}
}
internal sealed class FrameAnimationResourceRowElement : VisualElement
{
private readonly VisualElement flowDot;
private readonly Label typeBadge;
private readonly Label titleLabel;
private readonly Label idLabel;
private readonly Label metaLabel;
private readonly Label statusBadge;
internal FrameAnimationResourceRowElement()
{
AddToClassList("fa-resource-row");
var main = new VisualElement();
main.AddToClassList("fa-resource-row__main");
flowDot = new VisualElement { pickingMode = PickingMode.Ignore };
flowDot.AddToClassList("fa-flow-dot");
typeBadge = new Label { pickingMode = PickingMode.Ignore };
typeBadge.AddToClassList("fa-badge");
titleLabel = new Label { pickingMode = PickingMode.Ignore };
titleLabel.AddToClassList("fa-resource-row__name");
idLabel = new Label { pickingMode = PickingMode.Ignore };
idLabel.AddToClassList("fa-resource-row__id");
statusBadge = new Label { pickingMode = PickingMode.Ignore };
statusBadge.AddToClassList("fa-badge");
main.Add(flowDot);
main.Add(typeBadge);
main.Add(titleLabel);
main.Add(idLabel);
main.Add(statusBadge);
metaLabel = new Label { pickingMode = PickingMode.Ignore };
metaLabel.AddToClassList("fa-resource-row__meta");
Add(main);
Add(metaLabel);
}
internal void Bind(FrameAnimationBrowserEntry entry, bool selected)
{
userData = entry;
var isHeader = entry?.IsHeader == true;
EnableInClassList("fa-resource-group", isHeader);
EnableInClassList("fa-resource-row--selected", selected);
EnableInClassList("fa-resource-row--error", entry?.IssueState == FrameAnimationBrowserIssueState.Error);
EnableInClassList("fa-resource-row--warning", entry?.IssueState == FrameAnimationBrowserIssueState.Warning);
if (entry == null)
{
style.display = DisplayStyle.None;
return;
}
style.display = DisplayStyle.Flex;
titleLabel.text = entry.Title;
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
idLabel.text = entry.Id;
metaLabel.text = entry.Meta;
typeBadge.text = entry.TypeLabel;
typeBadge.style.display = isHeader ? DisplayStyle.None : DisplayStyle.Flex;
idLabel.style.display = isHeader ? DisplayStyle.None : DisplayStyle.Flex;
metaLabel.style.display = isHeader ? DisplayStyle.None : DisplayStyle.Flex;
statusBadge.text = entry.StatusLabel;
statusBadge.style.display = isHeader || string.IsNullOrEmpty(entry.StatusLabel) ? DisplayStyle.None : DisplayStyle.Flex;
flowDot.style.display = !isHeader && entry.AccentColor.HasValue ? DisplayStyle.Flex : DisplayStyle.None;
if (entry.AccentColor.HasValue)
{
flowDot.style.backgroundColor = FrameAnimationFlowColorUtility.CreatePalette(entry.AccentColor.Value).Stroke;
}
typeBadge.EnableInClassList("fa-badge--blue", entry.Kind == FrameAnimationBrowserEntryKind.Clip);
typeBadge.EnableInClassList("fa-badge--purple", entry.Kind == FrameAnimationBrowserEntryKind.Flow);
typeBadge.EnableInClassList("fa-badge--green", entry.Kind == FrameAnimationBrowserEntryKind.Source);
statusBadge.EnableInClassList("fa-badge--red", entry.IssueState == FrameAnimationBrowserIssueState.Error);
statusBadge.EnableInClassList("fa-badge--yellow", entry.IssueState == FrameAnimationBrowserIssueState.Warning);
}
}
internal sealed class FrameAnimationFrameRowElement : VisualElement
{
private readonly ObjectField spriteField;
private readonly IntegerField durationField;
private readonly Label frameNameLabel;
private readonly Label sourceIndexLabel;
private FrameClip clip;
private int index;
private bool readOnly;
private Action changed;
internal FrameAnimationFrameRowElement()
{
AddToClassList("fa-frame-row");
spriteField = new ObjectField("Sprite") { objectType = typeof(Sprite), allowSceneObjects = false };
durationField = new IntegerField("Duration Ms");
var readOnlyRow = new VisualElement();
readOnlyRow.AddToClassList("fa-frame-row__readonly");
frameNameLabel = new Label();
sourceIndexLabel = new Label();
readOnlyRow.Add(frameNameLabel);
readOnlyRow.Add(sourceIndexLabel);
Add(spriteField);
Add(durationField);
Add(readOnlyRow);
spriteField.RegisterValueChangedCallback(OnSpriteChanged);
durationField.RegisterValueChangedCallback(OnDurationChanged);
}
internal void Bind(FrameClip value, int itemIndex, bool isReadOnly, Action onChanged)
{
clip = value;
index = itemIndex;
readOnly = isReadOnly;
changed = onChanged;
userData = itemIndex;
var frame = clip != null && index >= 0 && index < clip.Frames.Count ? clip.Frames[index] : null;
spriteField.SetValueWithoutNotify(frame?.Sprite);
durationField.SetValueWithoutNotify(frame?.DurationMs ?? 100);
frameNameLabel.text = "Frame " + (frame?.FrameName ?? string.Empty);
sourceIndexLabel.text = "Source " + (frame?.SourceIndex.ToString() ?? "-1");
spriteField.SetEnabled(!readOnly);
durationField.SetEnabled(!readOnly);
}
private void OnSpriteChanged(ChangeEvent<UnityEngine.Object> evt)
{
if (readOnly || clip == null || index < 0 || index >= clip.Frames.Count) return;
var serialized = new SerializedObject(clip);
serialized.Update();
var element = serialized.FindProperty("frames").GetArrayElementAtIndex(index);
element.FindPropertyRelative("sprite").objectReferenceValue = evt.newValue;
serialized.ApplyModifiedProperties();
EditorUtility.SetDirty(clip);
changed?.Invoke();
}
private void OnDurationChanged(ChangeEvent<int> evt)
{
if (readOnly || clip == null || index < 0 || index >= clip.Frames.Count) return;
var serialized = new SerializedObject(clip);
serialized.Update();
var element = serialized.FindProperty("frames").GetArrayElementAtIndex(index);
element.FindPropertyRelative("durationMs").intValue = Mathf.Max(1, evt.newValue);
serialized.ApplyModifiedProperties();
durationField.SetValueWithoutNotify(Mathf.Max(1, evt.newValue));
EditorUtility.SetDirty(clip);
changed?.Invoke();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 876191b33b10eef44b133cc291ebc912
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,3 +1,642 @@
:root {
--fa-bg: rgb(27, 29, 32);
--fa-canvas: rgb(30, 32, 35);
--fa-panel: rgb(36, 39, 43);
--fa-panel-raised: rgb(43, 46, 51);
--fa-panel-hover: rgb(48, 52, 58);
--fa-field: rgb(28, 31, 34);
--fa-line: rgb(17, 19, 21);
--fa-line-soft: rgb(62, 67, 74);
--fa-text: rgb(215, 217, 220);
--fa-muted: rgb(143, 150, 158);
--fa-accent: rgb(61, 145, 212);
--fa-accent-soft: rgb(36, 79, 112);
--fa-success: rgb(86, 185, 123);
--fa-warning: rgb(224, 184, 88);
--fa-error: rgb(223, 106, 106);
}
.fa-workbench {
flex-grow: 1;
flex-direction: column;
background-color: var(--fa-bg);
color: var(--fa-text);
font-size: 12px;
}
.fa-global-toolbar-host,
.fa-main-host,
.fa-bottom-host {
flex-shrink: 0;
}
.fa-main-host {
flex-grow: 1;
min-height: 0;
}
.fa-workbench .unity-toolbar {
min-height: 32px;
padding-left: 6px;
padding-right: 6px;
background-color: var(--fa-panel-raised);
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-workbench .unity-toolbar-button,
.fa-workbench .unity-button {
min-height: 24px;
margin-left: 2px;
margin-right: 2px;
padding-left: 8px;
padding-right: 8px;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-color: var(--fa-line);
border-right-color: var(--fa-line);
border-top-color: var(--fa-line);
border-bottom-color: var(--fa-line);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
background-color: rgb(54, 58, 63);
color: var(--fa-text);
}
.fa-workbench .unity-toolbar-button:hover,
.fa-workbench .unity-button:hover {
background-color: rgb(70, 75, 81);
}
.fa-button--primary {
background-color: rgb(42, 105, 150);
border-left-color: rgb(25, 74, 104);
border-right-color: rgb(25, 74, 104);
border-top-color: rgb(25, 74, 104);
border-bottom-color: rgb(25, 74, 104);
}
.fa-button--danger {
background-color: rgb(87, 54, 56);
color: rgb(244, 205, 207);
}
.fa-toolbar-group {
flex-direction: row;
align-items: center;
flex-shrink: 0;
}
.fa-toolbar-separator {
width: 1px;
height: 21px;
margin-left: 5px;
margin-right: 5px;
background-color: var(--fa-line);
}
.fa-panel {
flex-direction: column;
flex-shrink: 0;
min-height: 0;
background-color: var(--fa-panel);
}
.fa-panel-header {
height: 28px;
min-height: 28px;
padding-left: 9px;
padding-right: 9px;
-unity-text-align: middle-left;
-unity-font-style: bold;
background-color: rgb(44, 47, 51);
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-resizer--vertical {
width: 4px;
background-color: rgb(24, 26, 29);
}
.fa-resizer--horizontal {
height: 4px;
background-color: rgb(24, 26, 29);
}
.fa-resizer--vertical:hover,
.fa-resizer--horizontal:hover {
background-color: var(--fa-accent);
}
.fa-resource-tabs {
min-height: 34px;
padding-left: 4px;
padding-right: 4px;
padding-top: 3px;
background-color: rgb(31, 34, 38);
}
.fa-resource-tabs .unity-toolbar-button,
.fa-bottom-tabs .unity-toolbar-button {
flex-grow: 1;
flex-basis: 0;
min-height: 28px;
margin-left: 1px;
margin-right: 1px;
border-left-width: 0;
border-right-width: 0;
border-top-width: 0;
border-bottom-width: 2px;
border-bottom-color: rgba(0, 0, 0, 0);
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-color: rgba(0, 0, 0, 0);
color: rgb(169, 175, 182);
}
.fa-resource-tabs .unity-toolbar-button:hover,
.fa-bottom-tabs .unity-toolbar-button:hover {
color: var(--fa-text);
background-color: rgb(48, 52, 57);
}
.fa-resource-tabs .unity-toolbar-button.fa-tab--active,
.fa-bottom-tabs .unity-toolbar-button.fa-tab--active {
color: white;
background-color: var(--fa-panel-raised);
border-bottom-color: var(--fa-accent);
-unity-font-style: bold;
}
.fa-resource-tools {
padding-left: 7px;
padding-right: 7px;
padding-top: 7px;
padding-bottom: 7px;
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-resource-list {
flex-grow: 1;
min-height: 0;
background-color: var(--fa-panel);
}
.fa-resource-row {
height: 48px;
padding-left: 9px;
padding-right: 7px;
padding-top: 5px;
padding-bottom: 4px;
border-left-width: 3px;
border-left-color: rgba(0, 0, 0, 0);
border-bottom-width: 1px;
border-bottom-color: rgb(32, 35, 39);
}
.fa-resource-row:hover {
background-color: var(--fa-panel-hover);
}
.fa-resource-row--selected {
background-color: rgb(37, 75, 103);
border-left-color: rgb(98, 178, 234);
}
.fa-resource-row--error {
border-left-color: var(--fa-error);
}
.fa-resource-row--warning {
border-left-color: var(--fa-warning);
}
.fa-resource-row__main,
.fa-resource-row__meta,
.fa-badge-row {
flex-direction: row;
align-items: center;
}
.fa-resource-row__main {
height: 20px;
}
.fa-resource-row__name {
flex-grow: 1;
min-width: 0;
-unity-font-style: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fa-resource-row__id {
max-width: 115px;
margin-left: 5px;
color: rgb(156, 163, 171);
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fa-resource-row__meta {
height: 17px;
color: var(--fa-muted);
font-size: 10px;
}
.fa-resource-group {
height: 27px;
padding-left: 8px;
padding-right: 8px;
-unity-text-align: middle-left;
-unity-font-style: bold;
color: rgb(184, 189, 195);
background-color: rgb(41, 44, 49);
border-top-width: 1px;
border-top-color: rgb(52, 56, 61);
border-bottom-width: 1px;
border-bottom-color: rgb(26, 28, 31);
}
.fa-badge {
min-height: 16px;
margin-right: 4px;
padding-left: 5px;
padding-right: 5px;
-unity-text-align: middle-center;
font-size: 9px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
background-color: rgb(52, 56, 61);
color: rgb(174, 180, 187);
}
.fa-badge--blue { background-color: rgb(37, 63, 85); color: rgb(143, 202, 239); }
.fa-badge--green { background-color: rgb(35, 63, 49); color: rgb(131, 209, 160); }
.fa-badge--yellow { background-color: rgb(74, 62, 37); color: rgb(240, 200, 111); }
.fa-badge--red { background-color: rgb(75, 41, 44); color: rgb(239, 141, 147); }
.fa-badge--purple { background-color: rgb(59, 48, 75); color: rgb(195, 168, 232); }
.fa-flow-dot {
width: 8px;
height: 8px;
margin-right: 6px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.fa-canvas-panel {
flex-grow: 1;
min-width: 280px;
min-height: 0;
flex-direction: column;
background-color: var(--fa-canvas);
}
.fa-canvas-toolbar {
min-height: 32px;
}
.fa-toolbar-label {
margin-left: 2px;
margin-right: 4px;
color: var(--fa-muted);
-unity-text-align: middle-left;
}
.fa-canvas-toolbar #flow-focus-field {
flex-shrink: 1;
}
.fa-flow-focus {
min-width: 110px;
max-width: 240px;
padding-left: 7px;
padding-right: 7px;
-unity-font-style: bold;
-unity-text-align: middle-left;
border-left-width: 3px;
border-left-color: rgba(0, 0, 0, 0);
}
.fa-empty-state {
color: var(--fa-muted);
}
.fa-node {
min-width: 224px;
width: 224px;
max-width: 224px;
min-height: 164px;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-color: var(--fa-line);
border-right-color: var(--fa-line);
border-top-color: var(--fa-line);
border-bottom-color: var(--fa-line);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
background-color: rgb(43, 47, 52);
}
.fa-node .title {
min-height: 31px;
padding-left: 7px;
padding-right: 7px;
background-color: rgb(46, 111, 155);
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-node .title-label {
-unity-font-style: bold;
}
.fa-node .input,
.fa-node .output {
min-width: 12px;
}
.fa-node__meta {
flex-direction: row;
justify-content: space-between;
margin-left: 8px;
margin-right: 8px;
margin-top: 4px;
margin-bottom: 4px;
color: rgb(175, 181, 188);
font-size: 10px;
}
.fa-node__clip-id {
flex-grow: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.fa-node__behavior {
margin-left: 5px;
color: rgb(201, 205, 210);
}
.fa-node__badges {
min-height: 21px;
margin-left: 8px;
margin-right: 8px;
margin-top: 5px;
flex-direction: row;
align-items: center;
overflow: hidden;
}
.fa-node__entry {
min-width: 16px;
height: 16px;
margin-right: 5px;
padding-left: 3px;
padding-right: 3px;
-unity-text-align: middle-center;
-unity-font-style: bold;
font-size: 9px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.fa-node__preview {
height: 78px;
margin-left: 8px;
margin-right: 8px;
margin-top: 1px;
}
.fa-node--focused {
border-left-width: 2px;
}
.fa-node.selected {
border-left-width: 2px;
border-right-width: 2px;
border-top-width: 2px;
border-bottom-width: 2px;
border-left-color: rgb(105, 185, 237);
border-right-color: rgb(105, 185, 237);
border-top-color: rgb(105, 185, 237);
border-bottom-color: rgb(105, 185, 237);
}
.fa-node--preview-current {
border-left-color: rgb(55, 174, 240);
border-right-color: rgb(55, 174, 240);
border-top-color: rgb(55, 174, 240);
border-bottom-color: rgb(55, 174, 240);
}
.fa-node--preview-passed { border-left-color: rgb(83, 154, 113); }
.fa-node--preview-upcoming { opacity: 0.72; }
.fa-inspector-scroll,
.fa-bottom-scroll {
flex-grow: 1;
min-height: 0;
background-color: var(--fa-panel);
}
.fa-inspector-title {
padding-left: 11px;
padding-right: 11px;
padding-top: 10px;
padding-bottom: 8px;
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-inspector-title__name {
font-size: 15px;
-unity-font-style: bold;
}
.fa-inspector-title__id {
margin-top: 3px;
color: var(--fa-muted);
font-size: 10px;
}
.fa-section {
padding-bottom: 8px;
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-section__title {
height: 27px;
padding-left: 10px;
-unity-text-align: middle-left;
-unity-font-style: bold;
background-color: rgb(41, 44, 48);
}
.fa-section__body {
padding-left: 10px;
padding-right: 10px;
padding-top: 7px;
}
.fa-section__body > .unity-base-field,
.fa-section__body > .unity-button,
.fa-section__body > .fa-readonly-row,
.fa-section__body > .fa-callout {
margin-top: 2px;
margin-bottom: 2px;
}
.fa-readonly-row {
min-height: 22px;
flex-direction: row;
align-items: center;
}
.fa-readonly-row__label {
width: 118px;
color: rgb(173, 179, 186);
}
.fa-readonly-row__value {
flex-grow: 1;
min-width: 0;
color: rgb(197, 202, 208);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.fa-callout {
padding-left: 9px;
padding-right: 9px;
padding-top: 7px;
padding-bottom: 7px;
white-space: normal;
border-left-width: 3px;
border-left-color: var(--fa-accent);
background-color: rgb(34, 48, 58);
color: rgb(188, 209, 223);
}
.fa-callout--warning { border-left-color: var(--fa-warning); background-color: rgb(57, 51, 33); color: rgb(228, 208, 161); }
.fa-callout--error { border-left-color: var(--fa-error); background-color: rgb(63, 39, 42); color: rgb(239, 184, 188); }
.fa-frame-row {
min-height: 72px;
padding-left: 5px;
padding-right: 5px;
padding-top: 4px;
padding-bottom: 4px;
border-bottom-width: 1px;
border-bottom-color: rgb(48, 51, 55);
}
.fa-frame-row__readonly {
flex-direction: row;
}
.fa-frame-row__readonly > .unity-label {
flex-grow: 1;
color: var(--fa-muted);
font-size: 10px;
}
.fa-bottom-panel {
flex-direction: column;
min-height: 28px;
background-color: rgb(32, 35, 39);
border-top-width: 1px;
border-top-color: var(--fa-line);
}
.fa-bottom-toolbar {
min-height: 31px;
padding-left: 4px;
padding-right: 4px;
}
.fa-bottom-tabs {
min-width: 230px;
height: 30px;
flex-direction: row;
align-items: stretch;
}
.fa-bottom-tabs .unity-toolbar-button {
min-width: 108px;
padding-left: 14px;
padding-right: 14px;
}
.fa-bottom-toolbar .fa-bottom-collapse {
width: 28px;
min-width: 28px;
padding-left: 0;
padding-right: 0;
font-size: 14px;
}
.fa-result-controls {
min-height: 31px;
flex-direction: row;
align-items: center;
padding-left: 8px;
padding-right: 8px;
border-bottom-width: 1px;
border-bottom-color: var(--fa-line);
}
.fa-result-header,
.fa-result-row {
min-height: 29px;
flex-direction: row;
align-items: center;
padding-left: 10px;
padding-right: 10px;
border-bottom-width: 1px;
border-bottom-color: rgb(41, 44, 48);
}
.fa-result-header {
min-height: 26px;
color: var(--fa-muted);
background-color: rgb(40, 43, 48);
}
.fa-result-row:hover { background-color: var(--fa-panel-hover); }
.fa-result-severity { width: 82px; -unity-font-style: bold; }
.fa-result-kind { width: 135px; }
.fa-result-object { width: 180px; }
.fa-result-message { flex-grow: 1; min-width: 260px; white-space: normal; }
.fa-severity--error { color: var(--fa-error); }
.fa-severity--warning { color: var(--fa-warning); }
.fa-severity--info { color: rgb(114, 189, 232); }
.fa-preview {
position: relative;
overflow: hidden;
@@ -25,26 +664,6 @@
color: rgb(190, 190, 190);
}
.fa-node__preview {
height: 82px;
margin-top: 5px;
}
.fa-node--preview-current {
border-left-color: rgb(55, 174, 240);
border-right-color: rgb(55, 174, 240);
border-top-color: rgb(55, 174, 240);
border-bottom-color: rgb(55, 174, 240);
}
.fa-node--preview-passed {
border-left-color: rgb(83, 154, 113);
}
.fa-node--preview-upcoming {
opacity: 0.72;
}
.fa-clip-preview-panel {
flex-shrink: 0;
padding-left: 6px;
@@ -55,6 +674,26 @@
border-bottom-color: rgb(28, 28, 28);
}
.fa-selection-preview-header {
min-height: 34px;
flex-direction: column;
justify-content: center;
}
.fa-selection-preview-title {
color: var(--fa-text);
-unity-font-style: bold;
}
.fa-selection-preview-target {
margin-top: 1px;
color: var(--fa-muted);
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fa-clip-preview-panel .fa-preview {
height: 190px;
margin-bottom: 4px;
@@ -63,10 +702,82 @@
.fa-preview-controls {
flex-direction: row;
align-items: center;
flex-shrink: 0;
min-height: 27px;
padding-left: 0;
padding-right: 0;
}
.fa-selection-preview-panel .fa-preview-controls .fa-preview-button {
width: 24px;
min-width: 24px;
max-width: 24px;
height: 24px;
min-height: 24px;
max-height: 24px;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
margin-left: 1px;
margin-right: 1px;
flex-shrink: 0;
align-items: center;
justify-content: center;
}
.fa-preview-button__icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.fa-preview-timeline {
min-width: 28px;
flex-grow: 1;
flex-shrink: 1;
margin-left: 5px;
margin-right: 5px;
}
.fa-preview-time {
width: 82px;
min-width: 82px;
flex-shrink: 0;
white-space: nowrap;
-unity-text-align: middle-right;
}
.fa-preview-settings {
min-height: 27px;
flex-direction: row;
align-items: center;
}
.fa-preview-setting {
flex-grow: 1;
flex-basis: 0;
min-width: 0;
margin-right: 5px;
flex-direction: row;
align-items: center;
}
.fa-preview-setting > .unity-label {
width: 64px;
min-width: 64px;
color: var(--fa-muted);
font-size: 10px;
}
.fa-preview-setting__field {
flex-grow: 1;
min-width: 0;
}
.fa-preview-status {
font-size: 10px;
color: rgb(185, 185, 185);
margin-top: 2px;
white-space: normal;
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ui:UXML xmlns:ui="UnityEngine.UIElements" editor-extension-mode="True">
<ui:VisualElement name="fa-workbench" class="fa-workbench">
<ui:VisualElement name="global-toolbar-host" class="fa-global-toolbar-host" />
<ui:VisualElement name="main-host" class="fa-main-host" />
<ui:VisualElement name="bottom-host" class="fa-bottom-host" />
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3286d11c482fe8640a72c689bed6780c
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
File diff suppressed because it is too large Load Diff
@@ -12,10 +12,12 @@ namespace AibisDream.FrameAnimation.Editor
{
private readonly Label subtitle;
private readonly Label behavior;
private readonly Label flowBadge;
private readonly Label issueBadge;
private readonly Label entryBadge;
private readonly VisualElement badgeContainer;
private readonly FrameAnimationPreviewElement preview;
private readonly Action<FrameAnimationClipNodeView> selectedCallback;
private FrameAnimationFlowColorPalette? entryPalette;
public AnimationNode Data { get; }
public Port InputPort { get; }
@@ -29,30 +31,40 @@ namespace AibisDream.FrameAnimation.Editor
this.selectedCallback = selectedCallback;
viewDataKey = data.InternalId;
AddToClassList("fa-node");
style.minWidth = 245f;
style.maxWidth = 285f;
InputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi,
typeof(AnimationNode));
InputPort.portName = "In";
InputPort.portName = string.Empty;
inputContainer.Add(InputPort);
OutputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single,
typeof(AnimationNode));
OutputPort.portName = "Next";
OutputPort.portName = string.Empty;
outputContainer.Add(OutputPort);
subtitle = new Label();
subtitle.AddToClassList("fa-node__clip-id");
behavior = new Label();
flowBadge = new Label();
issueBadge = new Label();
issueBadge.style.unityFontStyleAndWeight = FontStyle.Bold;
extensionContainer.Add(subtitle);
extensionContainer.Add(behavior);
extensionContainer.Add(flowBadge);
extensionContainer.Add(issueBadge);
behavior.AddToClassList("fa-node__behavior");
var meta = new VisualElement();
meta.AddToClassList("fa-node__meta");
meta.Add(subtitle);
meta.Add(behavior);
extensionContainer.Add(meta);
preview = new FrameAnimationPreviewElement();
preview.AddToClassList("fa-node__preview");
extensionContainer.Add(preview);
badgeContainer = new VisualElement();
badgeContainer.AddToClassList("fa-node__badges");
entryBadge = new Label("E") { tooltip = "Flow Entry" };
entryBadge.AddToClassList("fa-node__entry");
badgeContainer.Add(entryBadge);
issueBadge = new Label();
issueBadge.AddToClassList("fa-badge");
issueBadge.style.unityFontStyleAndWeight = FontStyle.Bold;
badgeContainer.Add(issueBadge);
extensionContainer.Add(badgeContainer);
RefreshExpandedState();
}
@@ -65,28 +77,67 @@ namespace AibisDream.FrameAnimation.Editor
internal void Refresh(
FrameAnimationGraph graph,
IReadOnlyList<FrameAnimationEditorIssue> issues,
IReadOnlyList<AnimationFlow> flows,
IReadOnlyCollection<string> entryFlowIds)
IReadOnlyList<AnimationFlow> flows)
{
var clip = graph.Clips.FirstOrDefault(item => item != null && item.Id == Data.ClipId);
title = string.IsNullOrWhiteSpace(Data.DisplayName) ? Data.ClipId : Data.DisplayName;
subtitle.text = clip != null ? $"Clip: {Data.ClipId}" : $"Clip: <Missing {Data.ClipId}>";
subtitle.text = clip != null ? Data.ClipId : $"Missing: {Data.ClipId}";
var speed = Data.SpeedOverride.HasValue
? $"Speed {Data.SpeedOverride.Value:0.###} (Node)"
: clip != null ? $"Speed {clip.Speed:0.###} (Clip)" : "Speed ?";
var end = Data.EndBehaviorOverride?.ToString() ?? "Continue / terminal fallback";
behavior.text = $"{speed} · {end}";
var used = flows.Select(flow => flow.Id).ToArray();
var entries = used.Where(entryFlowIds.Contains).ToArray();
flowBadge.text = used.Length == 0
? "Unused"
: "Flows: " + string.Join(", ", used.Take(3)) + (used.Length > 3 ? $" +{used.Length - 3}" : string.Empty) +
(entries.Length > 0 ? " [ENTRY]" : string.Empty);
var used = flows.Where(flow => flow != null).ToArray();
var entryFlows = used.Where(flow => flow.EntryNodeId == Data.InternalId).ToArray();
entryPalette = entryFlows.Length > 0
? FrameAnimationFlowColorUtility.CreatePalette(
FrameAnimationFlowColorUtility.ResolveRaw(graph, entryFlows[0]))
: (FrameAnimationFlowColorPalette?)null;
entryBadge.style.display = entryPalette.HasValue ? DisplayStyle.Flex : DisplayStyle.None;
if (entryPalette.HasValue)
{
entryBadge.style.backgroundColor = entryPalette.Value.Stroke;
entryBadge.style.color = entryPalette.Value.Text;
entryBadge.tooltip = "Flow Entry: " + string.Join(", ", entryFlows.Select(flow => flow.Id));
}
foreach (var label in badgeContainer.Query<Label>(className: "fa-flow-badge").ToList())
{
label.RemoveFromHierarchy();
}
if (used.Length == 0)
{
var unused = new Label("UNUSED");
unused.AddToClassList("fa-badge");
unused.AddToClassList("fa-badge--yellow");
unused.AddToClassList("fa-flow-badge");
badgeContainer.Insert(entryPalette.HasValue ? 1 : 0, unused);
}
else
{
var insertIndex = entryPalette.HasValue ? 1 : 0;
foreach (var flow in used.Take(3))
{
var palette = FrameAnimationFlowColorUtility.CreatePalette(
FrameAnimationFlowColorUtility.ResolveRaw(graph, flow));
var badge = new Label(flow.Id) { tooltip = FlowTooltip(flow) };
badge.AddToClassList("fa-badge");
badge.AddToClassList("fa-flow-badge");
badge.style.backgroundColor = palette.Muted;
badge.style.color = palette.Text;
badgeContainer.Insert(insertIndex++, badge);
}
}
var matching = issues.Where(issue => issue.Selection.Kind == FrameAnimationEditorSelectionKind.Node &&
ReferenceEquals(issue.Selection.Value, Data)).ToArray();
issueBadge.text = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
? "ERROR"
: matching.Length > 0 ? "WARNING" : string.Empty;
issueBadge.style.display = matching.Length > 0 ? DisplayStyle.Flex : DisplayStyle.None;
issueBadge.EnableInClassList("fa-badge--red",
matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error));
issueBadge.EnableInClassList("fa-badge--yellow",
matching.Length > 0 && matching.All(issue => issue.Severity != FrameAnimationValidationSeverity.Error));
issueBadge.style.color = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
? new StyleColor(new Color(1f, 0.35f, 0.3f))
: new StyleColor(new Color(1f, 0.72f, 0.25f));
@@ -96,6 +147,30 @@ namespace AibisDream.FrameAnimation.Editor
: "创建唯一的顺序后继";
}
private static string FlowTooltip(AnimationFlow flow) =>
$"Flow: {flow.DisplayName} ({flow.Id})";
internal void SetFocusedFlow(FrameAnimationGraph graph, AnimationFlow flow, bool belongs)
{
EnableInClassList("fa-node--focused", flow != null && belongs);
if (flow != null && belongs)
{
var palette = FrameAnimationFlowColorUtility.CreatePalette(
FrameAnimationFlowColorUtility.ResolveRaw(graph, flow));
titleContainer.style.backgroundColor = palette.Header;
var titleLabel = titleContainer.Q<Label>(className: "title-label");
if (titleLabel != null) titleLabel.style.color = palette.Text;
style.borderLeftColor = palette.Stroke;
}
else
{
titleContainer.style.backgroundColor = StyleKeyword.Null;
var titleLabel = titleContainer.Q<Label>(className: "title-label");
if (titleLabel != null) titleLabel.style.color = StyleKeyword.Null;
style.borderLeftColor = StyleKeyword.Null;
}
}
internal void SetDimmed(bool dimmed)
{
style.opacity = dimmed ? 0.45f : 1f;
@@ -119,7 +194,10 @@ namespace AibisDream.FrameAnimation.Editor
internal sealed class FrameAnimationEdgeView : Edge
{
private readonly Action<FrameAnimationEdgeView> selectedCallback;
private Color baseColor = Color.white;
private Color baseColor = new Color32(119, 127, 135, 255);
private bool hasError;
private bool hasWarning;
private FrameAnimationFlowElementState previewState;
public AnimationEdge Data { get; }
internal FrameAnimationEdgeView(AnimationEdge data, Action<FrameAnimationEdgeView> selectedCallback)
@@ -128,6 +206,7 @@ namespace AibisDream.FrameAnimation.Editor
this.selectedCallback = selectedCallback;
viewDataKey = data.InternalId;
userData = data;
RegisterCallback<AttachToPanelEvent>(_ => ApplyVisualState());
}
public override void OnSelected()
@@ -146,12 +225,9 @@ namespace AibisDream.FrameAnimation.Editor
var matching = (issues ?? Array.Empty<FrameAnimationEditorIssue>()).Where(issue =>
issue.Selection.Kind == FrameAnimationEditorSelectionKind.Edge &&
ReferenceEquals(issue.Selection.Value, Data)).ToArray();
var color = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
? new Color(1f, 0.3f, 0.25f)
: matching.Length > 0 ? new Color(1f, 0.72f, 0.2f) : Color.white;
baseColor = color;
edgeControl.inputColor = color;
edgeControl.outputColor = color;
hasError = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error);
hasWarning = !hasError && matching.Length > 0;
ApplyVisualState();
tooltip = matching.Length == 0
? $"{Data.ExitName} / {Data.Condition}"
: string.Join("\n", matching.Select(issue => $"[{issue.Code}] {issue.Message}"));
@@ -159,25 +235,47 @@ namespace AibisDream.FrameAnimation.Editor
internal void SetPreviewState(FrameAnimationFlowElementState state)
{
if (state == FrameAnimationFlowElementState.Current)
previewState = state;
ApplyVisualState();
}
internal void SetFocusedFlow(Color? flowColor, bool belongs)
{
baseColor = flowColor.HasValue && belongs
? FrameAnimationFlowColorUtility.CreatePalette(flowColor.Value).Stroke
: new Color32(119, 127, 135, 255);
ApplyVisualState();
}
private void ApplyVisualState()
{
var color = hasError
? new Color32(223, 106, 106, 255)
: hasWarning ? new Color32(224, 184, 88, 255) : baseColor;
var width = 3f;
if (!hasError && !hasWarning)
{
edgeControl.inputColor = new Color(0.25f, 0.75f, 1f);
edgeControl.outputColor = new Color(0.25f, 0.75f, 1f);
if (previewState == FrameAnimationFlowElementState.Current)
{
color = Color.Lerp(color, Color.white, 0.28f);
width = 5f;
}
else if (previewState == FrameAnimationFlowElementState.Passed)
{
color = Color.Lerp(color, new Color32(86, 185, 123, 255), 0.22f);
width = 3.5f;
}
else if (previewState == FrameAnimationFlowElementState.Upcoming)
{
color = Color.Lerp(color, new Color32(43, 47, 52, 255), 0.42f);
width = 2f;
}
}
else if (state == FrameAnimationFlowElementState.Passed)
edgeControl.inputColor = color;
edgeControl.outputColor = color;
if (panel != null)
{
edgeControl.inputColor = new Color(0.35f, 0.68f, 0.46f);
edgeControl.outputColor = new Color(0.35f, 0.68f, 0.46f);
}
else if (state == FrameAnimationFlowElementState.Upcoming)
{
edgeControl.inputColor = new Color(0.55f, 0.55f, 0.55f);
edgeControl.outputColor = new Color(0.55f, 0.55f, 0.55f);
}
else
{
edgeControl.inputColor = baseColor;
edgeControl.outputColor = baseColor;
edgeControl.edgeWidth = Mathf.RoundToInt(width);
}
}
}
@@ -285,14 +383,12 @@ namespace AibisDream.FrameAnimation.Editor
if (graph != null)
{
var topology = new FrameAnimationGraphTopology(graph);
var entries = new HashSet<string>(graph.Flows.Where(flow => flow != null)
.Select(flow => flow.EntryNodeId));
foreach (var node in graph.Nodes.Where(node => node != null))
{
var view = new FrameAnimationClipNodeView(node, OnNodeSelected);
view.SetPosition(new Rect(FrameAnimationGraphMutationService.GetNodePosition(graph, node),
new Vector2(255f, 150f)));
view.Refresh(graph, issues, topology.FindFlowsUsingNode(node.InternalId), entries);
new Vector2(224f, 164f)));
view.Refresh(graph, issues, topology.FindFlowsUsingNode(node.InternalId));
nodeViews[node.InternalId] = view;
AddElement(view);
}
@@ -372,8 +468,16 @@ namespace AibisDream.FrameAnimation.Editor
focusedFlowId = flowId ?? string.Empty;
if (graph == null || string.IsNullOrEmpty(flowId))
{
foreach (var view in nodeViews.Values) view.SetDimmed(false);
foreach (var view in edgeViews.Values) view.SetDimmed(false);
foreach (var view in nodeViews.Values)
{
view.SetDimmed(false);
view.SetFocusedFlow(graph, null, false);
}
foreach (var view in edgeViews.Values)
{
view.SetDimmed(false);
view.SetFocusedFlow(null, false);
}
return;
}
var flow = graph.Flows.FirstOrDefault(item => item != null && item.Id == flowId);
@@ -383,8 +487,19 @@ namespace AibisDream.FrameAnimation.Editor
: new FrameAnimationReachability(Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>());
var nodeIds = new HashSet<string>(reachable.Nodes.Select(node => node.InternalId));
var edgeIds = new HashSet<string>(reachable.Edges.Select(edge => edge.InternalId));
foreach (var pair in nodeViews) pair.Value.SetDimmed(!nodeIds.Contains(pair.Key));
foreach (var pair in edgeViews) pair.Value.SetDimmed(!edgeIds.Contains(pair.Key));
var flowColor = flow != null ? FrameAnimationFlowColorUtility.ResolveRaw(graph, flow) : (Color?)null;
foreach (var pair in nodeViews)
{
var belongs = nodeIds.Contains(pair.Key);
pair.Value.SetDimmed(!belongs);
pair.Value.SetFocusedFlow(graph, flow, belongs);
}
foreach (var pair in edgeViews)
{
var belongs = edgeIds.Contains(pair.Key);
pair.Value.SetDimmed(!belongs);
pair.Value.SetFocusedFlow(flowColor, belongs);
}
}
internal void SelectAndFrame(FrameAnimationEditorSelection target)
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5ad5208284c226e4297926dd64539442
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,166 @@
fileFormatVersion: 2
guid: 0a870adb61eef3b4bb54dd6a93d043ef
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: iPhone
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: Android
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:
@@ -12,6 +12,7 @@ using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using UnityEngine.TestTools;
namespace AibisDream.EditorTools
{
@@ -479,6 +480,7 @@ namespace AibisDream.EditorTools
var context = new SnapshotRestoreContext(new SaveSnapshot(), strictMode: true);
context.SetPhase("Provider restore");
context.Warn("optional state missing");
LogAssert.Expect(LogType.Error, "[SnapshotRestore] required state missing");
context.Error("required state missing");
Assert.That(context.StrictMode, Is.True);