StorySystem

This commit is contained in:
SoulliesOfficial
2026-07-07 01:28:27 -04:00
parent d031afd075
commit 7b7d069b84
105 changed files with 4852 additions and 2734 deletions

View File

@@ -213,7 +213,7 @@ Material:
- _Dst: 10
- _DstBlend: 0
- _DstBlendAlpha: 0
- _EdgeValue: 0.9350733
- _EdgeValue: 0.7629054
- _EnvironmentReflections: 1
- _FNLfanxiangkaiguan: 0
- _Face: 1
@@ -258,7 +258,7 @@ Material:
- _Mask_scale: 1
- _Metallic: 0
- _OcclusionStrength: 1
- _Opacity: 0.064926684
- _Opacity: 0.23709458
- _Parallax: 0.005
- _Pass: 0
- _QueueOffset: 0

Binary file not shown.

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0486971babc04a1419e16413dfce980c
guid: d5e986d3a0508cf4aae911cac5aab212
folderAsset: yes
DefaultImporter:
externalObjects: {}

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6c799daafc19ce54a8ba2d09737bc2aa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88d982fd5c6bda04e8ea20350cd159aa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 61661a97b386b1b468a2f6eb6edbd219
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b436cb055982024d87c26e5dd006d98
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: de75c1b8ae3365341bbe21fc291dedba
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 02149fcde1352dd499e840aca3a762aa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3f8b371af239879499277d4b555cd69b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d7530d3dd169d2141a5207d0172e3a9b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d51a896a1514cde44bcff62055b619f0
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
fileFormatVersion: 2
guid: cf843fc3f4fd10e499a6cc1b60bc1861
folderAsset: yes
DefaultImporter:
guid: a8e9cecf40a73474d8a5ffc9bada8c20
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -0,0 +1,92 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &597743784740087054
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7917580385239275621}
- component: {fileID: 1310777969124757443}
- component: {fileID: 6320712627302899334}
- component: {fileID: 5305109034942869160}
m_Layer: 5
m_Name: Character
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7917580385239275621
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 597743784740087054}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 760, y: 1350}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1310777969124757443
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 597743784740087054}
m_CullTransparentMesh: 1
--- !u!114 &6320712627302899334
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 597743784740087054}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 2736c74fecf99944a9977f12bf51f01c, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &5305109034942869160
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 597743784740087054}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 331609805c7de66409dba07cce2f3a28, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.Story.Dialogue.SpritePortraitRenderer
_alphaActive: 1
_alphaInactive: 0.7

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 373499de58b10f34983c975fccad9f89
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -14,7 +14,7 @@ OcclusionCullingSettings:
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 1
m_Fog: 0
m_FogColor: {r: 0.09296818, g: 0.080870956, b: 0.29559743, a: 1}
m_FogMode: 1
m_FogDensity: 0.01

File diff suppressed because it is too large Load Diff

View File

@@ -21,15 +21,19 @@ namespace Ichni.Story
public class CharacterData : SerializedScriptableObject
{
[LabelText("Character ID")]
[InfoBox("此 ID 需与 .yarn 文件中对应台词的 speaker 名完全一致。")]
[Tooltip("此 ID 需与 .yarn 文件中对应台词的 speaker 名完全一致。")]
public string characterId;
[LabelText("Display Name Key")]
[InfoBox("Unity Localization String Table Key用于多语言角色名显示。")]
[Tooltip("Unity Localization String Table Key用于多语言角色名显示。")]
public string displayNameKey;
[LabelText("Portrait Style")]
public PortraitStyle portraitStyle = PortraitStyle.Sprite;
[LabelText("Position Offset")]
[Tooltip("立绘在舞台上的位置偏移(单位:映射百分比)")]
public Vector2 positionOffset = new Vector2(0f, 0f);
// ── Sprite ──────────────────────────────────────────────────────────────
@@ -46,7 +50,6 @@ namespace Ichni.Story
[InfoBox("Live2D / Spine 立绘预制体,需挂载实现 IPortraitRenderer 接口的组件(阶段 3 制作)。")]
public GameObject dynamicPortraitPrefab;
private bool IsDynamicPortrait =>
portraitStyle == PortraitStyle.Live2D || portraitStyle == PortraitStyle.Spine;
private bool IsDynamicPortrait => portraitStyle == PortraitStyle.Live2D || portraitStyle == PortraitStyle.Spine;
}
}

View File

@@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
namespace Ichni.Story
{
/// <summary>
/// 全局剧情变量的统一数据枢纽。
/// 所有外部系统(包括 Yarn、UI、小游戏读写剧情变量的唯一官方入口。
/// 自动桥接至 GameSaveManager 的 StorySaveModule 中持久化。
/// </summary>
public static class StoryVariables
{
// 应对游戏尚未初始化(或者单独测试场景)的内存兜底
private static readonly StoryVariablesSave _fallback = new StoryVariablesSave();
private static StoryVariablesSave Variables
{
get
{
if (GameSaveManager.instance != null && GameSaveManager.instance.StorySaveModule != null)
return GameSaveManager.instance.StorySaveModule.variables;
return _fallback;
}
}
// ── 读取 ────────────────────────────────────────────────────────────────
public static float GetFloat(string key, float defaultValue = 0f)
{
StoryVariablesSave vars = Variables;
if (vars.floatVariables.TryGetValue(key, out float f)) return f;
if (vars.boolVariables.TryGetValue(key, out bool b)) return b ? 1f : 0f;
if (vars.stringVariables.TryGetValue(key, out string s) && float.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out float p)) return p;
return defaultValue;
}
public static int GetInt(string key, int defaultValue = 0)
{
StoryVariablesSave vars = Variables;
if (vars.floatVariables.TryGetValue(key, out float f)) return Mathf.RoundToInt(f);
if (vars.boolVariables.TryGetValue(key, out bool b)) return b ? 1 : 0;
if (vars.stringVariables.TryGetValue(key, out string s) && float.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out float p)) return Mathf.RoundToInt(p);
return defaultValue;
}
public static bool GetBool(string key, bool defaultValue = false)
{
StoryVariablesSave vars = Variables;
if (vars.boolVariables.TryGetValue(key, out bool b)) return b;
if (vars.floatVariables.TryGetValue(key, out float f)) return f != 0f;
if (vars.stringVariables.TryGetValue(key, out string s) && bool.TryParse(s, out bool p)) return p;
return defaultValue;
}
public static string GetString(string key, string defaultValue = "")
{
StoryVariablesSave vars = Variables;
if (vars.stringVariables.TryGetValue(key, out string s)) return s;
if (vars.floatVariables.TryGetValue(key, out float f)) return f.ToString(CultureInfo.InvariantCulture);
if (vars.boolVariables.TryGetValue(key, out bool b)) return b.ToString();
return defaultValue;
}
public static bool HasVariable(string key)
{
StoryVariablesSave vars = Variables;
return vars.floatVariables.ContainsKey(key) || vars.boolVariables.ContainsKey(key) || vars.stringVariables.ContainsKey(key);
}
// ── 写入 ────────────────────────────────────────────────────────────────
public static void SetFloat(string key, float value)
{
RemoveStaleEntries(key, keepFloat: true, keepString: false, keepBool: false);
Variables.floatVariables[key] = value;
}
public static void SetInt(string key, int value)
{
// 在 Yarn/存档底层中数字统一存为 float避免双字典冗余
SetFloat(key, value);
}
public static void SetBool(string key, bool value)
{
RemoveStaleEntries(key, keepFloat: false, keepString: false, keepBool: true);
Variables.boolVariables[key] = value;
}
public static void SetString(string key, string value)
{
RemoveStaleEntries(key, keepFloat: false, keepString: true, keepBool: false);
Variables.stringVariables[key] = value;
}
public static void ClearAll()
{
StoryVariablesSave vars = Variables;
vars.floatVariables.Clear();
vars.stringVariables.Clear();
vars.boolVariables.Clear();
}
private static void RemoveStaleEntries(string name, bool keepFloat, bool keepString, bool keepBool)
{
StoryVariablesSave vars = Variables;
if (!keepFloat) vars.floatVariables.Remove(name);
if (!keepString) vars.stringVariables.Remove(name);
if (!keepBool) vars.boolVariables.Remove(name);
}
// ── 字典批量操作(内部供 Yarn Adapter 使用) ─────────────────────────────
internal static void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools, bool clear = true)
{
StoryVariablesSave vars = Variables;
if (clear)
{
vars.floatVariables.Clear();
vars.stringVariables.Clear();
vars.boolVariables.Clear();
}
if (floats != null) foreach (var kv in floats) vars.floatVariables[kv.Key] = kv.Value;
if (strings != null) foreach (var kv in strings) vars.stringVariables[kv.Key] = kv.Value;
if (bools != null) foreach (var kv in bools) vars.boolVariables[kv.Key] = kv.Value;
}
internal static (Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools) GetAllVariables()
{
StoryVariablesSave vars = Variables;
return (
new Dictionary<string, float>(vars.floatVariables),
new Dictionary<string, string>(vars.stringVariables),
new Dictionary<string, bool>(vars.boolVariables)
);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c69dde49931abdd40aeabc0dc35582f5

View File

@@ -0,0 +1,93 @@
using Sirenix.OdinInspector;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Yarn.Unity;
namespace Ichni.Story.Dialogue
{
/// <summary>
/// 单个对话选项按钮的显示与交互控制器。
/// <para>
/// <list type="bullet">
/// <item><b>可用选项</b>:显示 <see cref="availableSprite"/>,点击时触发 <c>onSelected</c> 回调。</item>
/// <item><b>不可用选项</b>:显示 <see cref="unavailableSprite"/>,点击无效(但 Button 的
/// <c>interactable</c> 保持开启,以备未来扩展悬停提示等功能)。</item>
/// </list>
/// </para>
/// <para>
/// 由 <see cref="VNDialoguePresenter"/> 统一调用 <see cref="Setup"/> / <see cref="Cleanup"/>。
/// </para>
/// </summary>
public class ChoiceButton : MonoBehaviour
{
[Tooltip("按钮组件引用(挂载在同 GameObject 上的 Button。")]
public Button button;
[Tooltip("按钮背景 Image用于在 available / unavailable 状态间切换 Sprite。")]
public Image buttonBackground;
[Tooltip("选项文本 TMP。")]
public TMP_Text buttonText;
[SerializeField, HideInPlayMode]
[Tooltip("选项可用时的背景 Sprite。")]
private Sprite availableSprite;
[SerializeField, HideInPlayMode]
[Tooltip("选项不可用时的背景 Sprite。")]
private Sprite unavailableSprite;
// ── 运行时状态 ────────────────────────────────────────────────────
/// <summary>当前绑定的选项是否可供玩家选择。</summary>
private bool _isAvailable;
// ── 公共 API ─────────────────────────────────────────────────────
/// <summary>
/// 根据 <paramref name="option"/> 配置按钮并激活显示。
/// </summary>
/// <param name="option">Yarn 对话选项数据。<c>option.IsAvailable</c> 决定外观与交互行为。</param>
/// <param name="onSelected">
/// 玩家点击且选项可用时触发的回调。
/// 不可用选项点击不会触发此回调,但按钮保持 <c>interactable=true</c>。
/// </param>
public void Setup(DialogueOption option, System.Action<DialogueOption> onSelected)
{
gameObject.SetActive(true);
_isAvailable = option.IsAvailable;
// 设置选项文本
if (buttonText != null)
buttonText.text = option.Line.TextWithoutCharacterName.Text;
// 根据可用性切换背景图
if (buttonBackground != null)
buttonBackground.sprite = _isAvailable ? availableSprite : unavailableSprite;
// 注册点击监听:不可用选项点击静默忽略
if (button != null)
{
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() =>
{
if (_isAvailable)
onSelected?.Invoke(option);
});
}
}
/// <summary>
/// 清除按钮监听并隐藏此按钮。
/// 在 <see cref="VNDialoguePresenter.RunOptionsAsync"/> 结束后统一调用。
/// </summary>
public void Cleanup()
{
if (button != null)
button.onClick.RemoveAllListeners();
gameObject.SetActive(false);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 63c53d8a6dc173a4db3d6a8a1499b7e6

View File

@@ -0,0 +1,59 @@
using UnityEngine;
namespace Ichni.Story.Dialogue
{
/// <summary>
/// 立绘渲染器抽象接口。所有立绘渲染实现Sprite / Live2D / Spine均须实现此接口
/// 使 <see cref="VNPortraitStage"/> 和 Yarn 命令层无需感知底层渲染细节。
/// </summary>
public interface IPortraitRenderer
{
/// <summary>此渲染器在舞台上占用的 RectTransform用于位置操控。</summary>
RectTransform RectTransform { get; }
/// <summary>
/// 以指定角色数据和情绪名显示立绘。若角色已在台上则更新情绪。
/// </summary>
void Show(CharacterData character, string emotion);
/// <summary>隐藏立绘(不销毁对象,留存以便复用)。</summary>
void Hide();
/// <summary>切换当前情绪(角色数据不变)。</summary>
void SetEmotion(string emotion);
/// <summary>
/// 将立绘的 anchoredPosition 设为指定坐标(即时,无动画)。
/// </summary>
/// <param name="anchoredPos">目标 anchoredPosition画布空间像素单位。</param>
void SetPosition(Vector2 anchoredPos);
/// <summary>
/// 控制说话者高亮状态:说话者 alpha = 1.0,旁观者 alpha = 0.7(可由实现自行定义)。
/// </summary>
void SetActiveVisual(bool isSpeaking);
/// <summary>
/// 平滑移动到目标位置。
/// </summary>
/// <param name="anchoredPos">目标像素坐标</param>
/// <param name="duration">持续时间</param>
void MoveTo(Vector2 anchoredPos, float duration);
/// <summary>
/// 原地跳跃动画。
/// </summary>
/// <param name="power">跳跃力度(高度)</param>
/// <param name="numJumps">跳跃次数</param>
/// <param name="duration">持续时间</param>
void Jump(float power, int numJumps, float duration);
/// <summary>
/// 左右震动动画。
/// </summary>
/// <param name="duration">持续时间</param>
/// <param name="strength">震动力度(像素范围)</param>
/// <param name="vibrato">震动频率</param>
void Shake(float duration, float strength, int vibrato);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3dbfb11aa34a55942b604a15cbd65d50

View File

@@ -0,0 +1,175 @@
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
namespace Ichni.Story.Dialogue
{
/// <summary>
/// 基于 <see cref="UnityEngine.UI.Image"/> 的 Sprite 立绘渲染器。
/// <para>
/// 挂载在 VN 对话 UI 内的立绘槽位 GameObject 上Left / Center / Right 等),
/// 每个槽位对应一个 <see cref="SpritePortraitRenderer"/> 实例。
/// 当前阶段仅实现静态显示Show / Hide / SetEmotion / SetPosition / SetActiveVisual
/// 后续阶段再叠加 DOTween 动画。
/// </para>
/// </summary>
[RequireComponent(typeof(Image))]
public class SpritePortraitRenderer : MonoBehaviour, IPortraitRenderer
{
// ── 设置 ────────────────────────────────────────────────────────────────
/// <summary>说话者的不透明度(满值)。</summary>
[SerializeField] private float _alphaActive = 1.0f;
/// <summary>非说话者的压暗不透明度。</summary>
[SerializeField] private float _alphaInactive = 0.7f;
// ── 组件缓存 ─────────────────────────────────────────────────────────────
private Image _image;
private RectTransform _rectTransform;
// ── 状态 ─────────────────────────────────────────────────────────────────
private CharacterData _currentCharacter;
// ── IPortraitRenderer 实现 ───────────────────────────────────────────────
/// <inheritdoc/>
public RectTransform RectTransform => _rectTransform;
// ── 生命周期 ──────────────────────────────────────────────────────────────
private void Awake()
{
_image = GetComponent<Image>();
_rectTransform = GetComponent<RectTransform>();
// 初始隐藏
SetGroupAlpha(0f);
}
// ── 接口方法 ─────────────────────────────────────────────────────────────
/// <inheritdoc/>
public void Show(CharacterData character, string emotion)
{
if (character == null)
{
Debug.LogWarning("[SpritePortraitRenderer] Show 传入了 null CharacterData。");
return;
}
_currentCharacter = character;
ApplySprite(emotion);
SetGroupAlpha(_alphaActive);
gameObject.SetActive(true);
}
/// <inheritdoc/>
public void Hide()
{
SetGroupAlpha(0f);
}
/// <inheritdoc/>
public void SetEmotion(string emotion)
{
if (_currentCharacter == null)
{
Debug.LogWarning("[SpritePortraitRenderer] SetEmotion 时尚未绑定角色数据。");
return;
}
ApplySprite(emotion);
}
/// <inheritdoc/>
public void SetPosition(Vector2 anchoredPos)
{
_rectTransform.anchoredPosition = anchoredPos;
}
/// <inheritdoc/>
public void SetActiveVisual(bool isSpeaking)
{
SetGroupAlpha(isSpeaking ? _alphaActive : _alphaInactive);
}
/// <inheritdoc/>
public void MoveTo(Vector2 anchoredPos, float duration)
{
_rectTransform.DOKill(complete: true);
_rectTransform.DOAnchorPos(anchoredPos, duration).SetEase(Ease.InOutQuad).Play();
}
/// <inheritdoc/>
public void Jump(float power, int numJumps, float duration)
{
// 对于跳跃,先停止并完成先前的动画,避免错乱(特别是被快进时)
_rectTransform.DOKill(complete: true);
// UI 的 DOJumpAnchorPos 非常适合这种表现,项目中要求手动调用 Play()
_rectTransform.DOJumpAnchorPos(_rectTransform.anchoredPosition, power, numJumps, duration).Play();
}
/// <inheritdoc/>
public void Shake(float duration, float strength, int vibrato)
{
_rectTransform.DOKill(complete: true);
// 左右震动(限制在 X 轴或以特定强度震动),项目中要求手动调用 Play()
_rectTransform.DOShakeAnchorPos(duration, new Vector2(strength, 0f), vibrato, 90, false, true).Play();
}
// ── 内部辅助 ─────────────────────────────────────────────────────────────
/// <summary>
/// 从 <see cref="CharacterData.emotionSprites"/> 中查找并赋值 Sprite。
/// 找不到指定情绪时回退到"normal",再找不到则使用字典中第一个条目,
/// 并在 Console 输出警告。
/// </summary>
private void ApplySprite(string emotion)
{
if (_currentCharacter.emotionSprites == null || _currentCharacter.emotionSprites.Count == 0)
{
Debug.LogWarning($"[SpritePortraitRenderer] 角色 '{_currentCharacter.characterId}' 没有配置任何情绪 Sprite。");
return;
}
// 1. 精确匹配
if (!string.IsNullOrEmpty(emotion) &&
_currentCharacter.emotionSprites.TryGetValue(emotion, out Sprite target))
{
_image.sprite = target;
return;
}
// 2. 回退 normal
if (_currentCharacter.emotionSprites.TryGetValue("normal", out Sprite fallback))
{
Debug.LogWarning($"[SpritePortraitRenderer] 角色 '{_currentCharacter.characterId}' 没有情绪 '{emotion}',回退到 'normal'。");
_image.sprite = fallback;
return;
}
// 3. 使用第一个条目
foreach (Sprite first in _currentCharacter.emotionSprites.Values)
{
Debug.LogWarning($"[SpritePortraitRenderer] 角色 '{_currentCharacter.characterId}' 也没有 'normal',使用第一张 Sprite。");
_image.sprite = first;
return;
}
}
private void SetGroupAlpha(float alpha)
{
if (_image != null)
{
Color c = _image.color;
c.a = alpha;
_image.color = c;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 331609805c7de66409dba07cce2f3a28

View File

@@ -153,11 +153,17 @@ namespace Ichni.Story.Dialogue
List<UnityAction> pending = new List<UnityAction>(dialogueEndActions);
dialogueEndActions.Clear();
foreach (UnityAction action in pending)
{
action?.Invoke();
}
StoryMessageBoxUIPage.instance.FadeIn();
}
if (storyUIPage != null)
{
storyUIPage.FadeIn();
}
}
}
}

View File

@@ -1,207 +0,0 @@
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using Yarn.Unity;
namespace Ichni.Story.Dialogue
{
/// <summary>
/// 自定义 Yarn 变量存储。直接读写 <see cref="StorySaveModule.variables"/>(全局剧情变量),
/// 让 Yarn 对话中的 <c>&lt;&lt;set $var to ...&gt;&gt;</c> 与故事树解锁条件共享同一份数据来源,
/// 并借助存档模块的 ES3 序列化持久化。
/// <para>变量名沿用 Yarn 约定(带 <c>$</c> 前缀);解锁条件中的变量名也应保持一致。</para>
/// </summary>
public class StoryVariableStorage : VariableStorageBehaviour
{
// GameSaveManager 尚未就绪(编辑器早期 / 场景未初始化)时的兜底容器
private readonly StoryVariablesSave _fallback = new StoryVariablesSave();
/// <summary>当前生效的变量容器:优先取存档模块,缺失时用本地兜底容器。</summary>
private StoryVariablesSave Variables
{
get
{
if (GameSaveManager.instance != null && GameSaveManager.instance.StorySaveModule != null)
return GameSaveManager.instance.StorySaveModule.variables;
return _fallback;
}
}
// ── IVariableStorage 实现 ────────────────────────────────────────────────
public override bool TryGetValue<T>(string variableName, out T result)
{
StoryVariablesSave vars = Variables;
if (vars.floatVariables.TryGetValue(variableName, out float f))
return TryConvert(f, out result);
if (vars.boolVariables.TryGetValue(variableName, out bool b))
return TryConvert(b, out result);
if (vars.stringVariables.TryGetValue(variableName, out string s))
return TryConvert(s, out result);
result = default;
return false;
}
public override void SetValue(string variableName, string stringValue)
{
RemoveStaleEntries(variableName, keepFloat: false, keepString: true, keepBool: false);
Variables.stringVariables[variableName] = stringValue;
NotifyVariableChanged(variableName, stringValue);
}
public override void SetValue(string variableName, float floatValue)
{
RemoveStaleEntries(variableName, keepFloat: true, keepString: false, keepBool: false);
Variables.floatVariables[variableName] = floatValue;
NotifyVariableChanged(variableName, floatValue);
}
public override void SetValue(string variableName, bool boolValue)
{
RemoveStaleEntries(variableName, keepFloat: false, keepString: false, keepBool: true);
Variables.boolVariables[variableName] = boolValue;
NotifyVariableChanged(variableName, boolValue);
}
public override bool Contains(string variableName)
{
StoryVariablesSave vars = Variables;
return vars.floatVariables.ContainsKey(variableName)
|| vars.boolVariables.ContainsKey(variableName)
|| vars.stringVariables.ContainsKey(variableName);
}
public override void Clear()
{
StoryVariablesSave vars = Variables;
vars.floatVariables.Clear();
vars.stringVariables.Clear();
vars.boolVariables.Clear();
}
public override void SetAllVariables(
Dictionary<string, float> floats,
Dictionary<string, string> strings,
Dictionary<string, bool> bools,
bool clear = true)
{
StoryVariablesSave vars = Variables;
if (clear)
{
vars.floatVariables.Clear();
vars.stringVariables.Clear();
vars.boolVariables.Clear();
}
if (floats != null)
foreach (KeyValuePair<string, float> kv in floats) vars.floatVariables[kv.Key] = kv.Value;
if (strings != null)
foreach (KeyValuePair<string, string> kv in strings) vars.stringVariables[kv.Key] = kv.Value;
if (bools != null)
foreach (KeyValuePair<string, bool> kv in bools) vars.boolVariables[kv.Key] = kv.Value;
}
public override (Dictionary<string, float> FloatVariables,
Dictionary<string, string> StringVariables,
Dictionary<string, bool> BoolVariables) GetAllVariables()
{
StoryVariablesSave vars = Variables;
return (
new Dictionary<string, float>(vars.floatVariables),
new Dictionary<string, string>(vars.stringVariables),
new Dictionary<string, bool>(vars.boolVariables)
);
}
// ── 供故事树解锁条件复用的便捷读取 ────────────────────────────────────────
/// <summary>读取变量的整数近似值float 四舍五入、bool 取 0/1不存在返回 0。</summary>
public int GetIntVariable(string variableName)
{
StoryVariablesSave vars = Variables;
if (vars.floatVariables.TryGetValue(variableName, out float f))
return Mathf.RoundToInt(f);
if (vars.boolVariables.TryGetValue(variableName, out bool b))
return b ? 1 : 0;
return 0;
}
/// <summary>该变量是否存在于任一类型的存储中。</summary>
public bool HasVariable(string variableName) => Contains(variableName);
// ── 内部 ────────────────────────────────────────────────────────────────
// 当变量被重新赋值为其它类型时,从旧类型字典移除,避免同名变量残留多份
private void RemoveStaleEntries(string name, bool keepFloat, bool keepString, bool keepBool)
{
StoryVariablesSave vars = Variables;
if (!keepFloat) vars.floatVariables.Remove(name);
if (!keepString) vars.stringVariables.Remove(name);
if (!keepBool) vars.boolVariables.Remove(name);
}
// 将存储中的原始值转换为 Yarn 请求的类型float / bool / string / int 之间互转)
private static bool TryConvert<T>(object value, out T result)
{
if (value is T typed)
{
result = typed;
return true;
}
try
{
if (typeof(T) == typeof(float) || typeof(T) == typeof(double) || typeof(T) == typeof(int))
{
float f = value switch
{
float fv => fv,
bool bv => bv ? 1f : 0f,
string sv => float.TryParse(sv, NumberStyles.Any, CultureInfo.InvariantCulture, out float p) ? p : 0f,
_ => 0f
};
result = (T)System.Convert.ChangeType(f, typeof(T), CultureInfo.InvariantCulture);
return true;
}
if (typeof(T) == typeof(bool))
{
bool b = value switch
{
bool bv => bv,
float fv => fv != 0f,
string sv => bool.TryParse(sv, out bool p) && p,
_ => false
};
result = (T)(object)b;
return true;
}
if (typeof(T) == typeof(string))
{
string s = value switch
{
string sv => sv,
float fv => fv.ToString(CultureInfo.InvariantCulture),
bool bv => bv.ToString(),
_ => value != null ? value.ToString() : string.Empty
};
result = (T)(object)s;
return true;
}
}
catch
{
// 转换失败时落到下方失败分支
}
result = default;
return false;
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 7dc7dc14d9f97c043bf6fb67c3a70877

View File

@@ -3,6 +3,7 @@ using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Yarn.Unity;
using Ichni.Story.UI;
namespace Ichni.Story.Dialogue
{
@@ -11,61 +12,21 @@ namespace Ichni.Story.Dialogue
/// <para>
/// 职责:
/// <list type="bullet">
/// <item>对话开始/结束时淡入/淡出 <see cref="panelGroup"/>。</item>
/// <item><see cref="RunLineAsync"/>:逐字打字效果 + 点击推进。</item>
/// <item><see cref="RunOptionsAsync"/>:显示选项按钮 + 等待玩家选择。</item>
/// <item>对话流控制:打字机效果、点击推进、等待选项。</item>
/// <item>将所有的 UI 引用委托给 <see cref="DialogUIPage"/> 进行管理。</item>
/// </list>
/// </para>
/// <para>
/// 挂载位置:与 <see cref="DialogueRunner"/> 同一 GameObjectStoryDialogueRoot
/// UI 引用字段需在 Inspector 中连接到 DialogPage 下的对应节点。
/// </para>
/// </summary>
public class VNDialoguePresenter : DialoguePresenterBase
{
// ── UI 引用 ─────────────────────────────────────────────────────────────
[Header("面板控制")]
[Tooltip("对话面板整体的 CanvasGroupDialogPage/DialogCanvas。用于淡入/淡出及交互开关。")]
[SerializeField] private CanvasGroup panelGroup;
[Header("台词区")]
[Tooltip("台词主体文本 TMPDialogContentFrame/Background/Text。")]
[SerializeField] private TextMeshProUGUI dialogueText;
[Tooltip("说话者名称的父容器 GameObjectDialogContentFrame/Speaker。无说话者时整体隐藏。")]
[SerializeField] private GameObject speakerContainer;
[Tooltip("说话者名称 TMPDialogContentFrame/Speaker/Text。")]
[SerializeField] private TextMeshProUGUI speakerText;
[Header("推进按钮")]
[Tooltip("点击此按钮推进对话。\n" +
" • 打字效果进行中 → 加速RequestHurryUpLine显示全文。\n" +
" • 打字已完成 → 进入下一行RequestNextLine。\n" +
"推荐:将场景中 DialogContentFrame/Background 或 BackButton 的 Button 拖入此处。")]
[SerializeField] private Button advanceButton;
[Header("选项区")]
[Tooltip("选项按钮容器根节点ChoiceFrame。有选项时显示无选项时隐藏。")]
[SerializeField] private GameObject choiceFrame;
[Tooltip("选项按钮数组(最多 4 个,对应 ChoiceFrame 下的四个 GameObject。")]
[SerializeField] private Button[] choiceButtons = new Button[4];
[Tooltip("每个选项按钮上的文本 TMP与 choiceButtons 按索引一一对应。")]
[SerializeField] private TextMeshProUGUI[] choiceTexts = new TextMeshProUGUI[4];
// ── 打字 / 淡入淡出设置 ──────────────────────────────────────────────────
[Header("UI 管理")]
[Tooltip("负责当前对话界面的 UI Page 容器。如果为空,将尝试获取全局单例。")]
[SerializeField] private DialogUIPage uiPage;
[Header("打字效果")]
[Tooltip("每秒显示的字符数。设为 0 则瞬间显示全文。")]
[SerializeField] private float lettersPerSecond = 40f;
[Header("淡入淡出")]
[SerializeField] private float fadeInDuration = 0.2f;
[SerializeField] private float fadeOutDuration = 0.15f;
// ── 私有状态 ─────────────────────────────────────────────────────────────
/// <summary>
@@ -74,6 +35,11 @@ namespace Ichni.Story.Dialogue
/// </summary>
private bool _typewriterComplete;
/// <summary>
/// 是否处于快进状态(遇到选项或玩家再次点击时会打断)。
/// </summary>
private bool _isFastForwarding;
/// <summary>
/// 选项选择的异步结果来源。RunOptionsAsync 等待其 Task 完成,
/// 点击按钮时 TrySetResult 触发完成。
@@ -81,179 +47,180 @@ namespace Ichni.Story.Dialogue
private YarnTaskCompletionSource<DialogueOption?> _optionTcs;
/// <summary>
/// 对应 StoryDialogueRoot 上的 DialogueRunner,用于调用 RequestNextLine / RequestHurryUpLine
/// 在 Awake 中通过 GetComponent 取得。
/// 对应 StoryDialogueRoot 上的 DialogueRunner。
/// </summary>
private DialogueRunner _runner;
private DialogUIPage Page => uiPage != null ? uiPage : DialogUIPage.instance;
// ── 生命周期 ─────────────────────────────────────────────────────────────
private void Awake()
{
// VNDialoguePresenter 需要与 DialogueRunner 同 GameObject通过 GetComponent 获取引用。
_runner = GetComponent<DialogueRunner>();
if (_runner == null)
Debug.LogWarning("[VNDialoguePresenter] 未在同 GameObject 上找到 DialogueRunner。" +
"请确认 VNDialoguePresenter 挂载在 StoryDialogueRoot 上。");
}
// 启动时隐藏面板alpha=0, 不响应交互)
SetPanelState(visible: false, instant: true);
private void Start()
{
if (Page == null) return;
// 启动时隐藏选项容器
if (choiceFrame != null) choiceFrame.SetActive(false);
if (Page.choiceFrame != null) Page.choiceFrame.SetActive(false);
// 注册推进按钮回调
if (advanceButton != null)
advanceButton.onClick.AddListener(OnAdvanceButtonClicked);
if (Page.advanceButton != null)
Page.advanceButton.onClick.AddListener(OnAdvanceButtonClicked);
// 注册快进按钮回调
if (Page.fastForwardButton != null)
Page.fastForwardButton.onClick.AddListener(OnFastForwardButtonClicked);
}
private void OnDestroy()
{
if (advanceButton != null)
advanceButton.onClick.RemoveListener(OnAdvanceButtonClicked);
if (Page != null)
{
if (Page.advanceButton != null)
Page.advanceButton.onClick.RemoveListener(OnAdvanceButtonClicked);
if (Page.fastForwardButton != null)
Page.fastForwardButton.onClick.RemoveListener(OnFastForwardButtonClicked);
}
}
// ── DialoguePresenterBase 实现 ───────────────────────────────────────────
/// <summary>
/// 对话开始:将面板淡入到可见状态。
/// 在 DialogueRunner 开始第一行前由 Yarn 调用。
/// </summary>
public override async YarnTask OnDialogueStartedAsync()
{
// 先令 CanvasGroup 可交互但 alpha=0为淡入动画做准备
SetPanelState(visible: true, instant: false);
if (panelGroup != null)
await Effects.FadeAlphaAsync(panelGroup, 0f, 1f, fadeInDuration, CancellationToken.None);
if (Page != null)
{
var tcs = new YarnTaskCompletionSource<bool>();
Page.PlayFadeIn(() => tcs.TrySetResult(true));
await tcs.Task;
}
}
/// <summary>
/// 呈现一行台词。
/// <list type="number">
/// <item>隐藏选项区,显示说话者名称(若有)。</item>
/// <item>逐字打字效果,期间可被 <c>HurryUpToken</c> 中断(显示全文)。</item>
/// <item>等待玩家点击推进(<c>NextContentToken</c> 由 <see cref="DialogueRunner.RequestNextLine"/> 取消)。</item>
/// </list>
/// </summary>
public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
if (Page == null) return;
// 确保选项区不可见;台词区始终可见
if (choiceFrame != null) choiceFrame.SetActive(false);
if (Page.choiceFrame != null) Page.choiceFrame.SetActive(false);
// ── 说话者名称 ──
bool hasSpeaker = !string.IsNullOrEmpty(line.CharacterName);
if (speakerContainer != null) speakerContainer.SetActive(hasSpeaker);
if (speakerText != null) speakerText.text = hasSpeaker ? line.CharacterName : string.Empty;
if (Page.speakerContainer != null) Page.speakerContainer.SetActive(hasSpeaker);
if (Page.speakerText != null) Page.speakerText.text = hasSpeaker ? line.CharacterName : string.Empty;
// ── 立绘高亮 ──
Page.portraitStage?.HighlightSpeaker(line.CharacterName);
// ── 打字效果 ──
string fullText = line.TextWithoutCharacterName.Text;
_typewriterComplete = false;
if (dialogueText != null)
if (Page.dialogueText != null)
{
// 先将全文赋给 TMP再通过 maxVisibleCharacters 逐步显示
// (避免每帧 Substring 产生大量 GC 分配)
dialogueText.text = fullText;
dialogueText.maxVisibleCharacters = 0;
Page.dialogueText.text = fullText;
Page.dialogueText.maxVisibleCharacters = 0;
}
// 逐字播放;若 HurryUpToken 被取消(玩家第一次点击)则立即跳出循环
await TypewriterAsync(fullText, token.HurryUpToken);
if (_isFastForwarding)
{
// 瞬间显示全文
if (Page.dialogueText != null) Page.dialogueText.maxVisibleCharacters = int.MaxValue;
_typewriterComplete = true;
// 确保全文可见(无论正常完成还是被加速中断)
if (dialogueText != null)
dialogueText.maxVisibleCharacters = int.MaxValue;
// 极短的延迟,防止瞬间刷过几百句话造成卡顿,也给玩家微小的视觉感知
await YarnTask.Delay(50).SuppressCancellationThrow();
_typewriterComplete = true;
if (_isFastForwarding && _runner != null)
{
_runner.RequestNextLine();
}
}
else
{
// 正常逐字播放
await TypewriterAsync(fullText, token.HurryUpToken);
// 确保全文可见
if (Page.dialogueText != null)
Page.dialogueText.maxVisibleCharacters = int.MaxValue;
_typewriterComplete = true;
}
// ── 等待玩家推进 ──
// token.NextContentToken 由外部 DialogueRunner.RequestNextLine() 取消,
// SuppressCancellationThrow 确保不抛出 OperationCanceledException。
await YarnTask.WaitUntilCanceled(token.NextContentToken).SuppressCancellationThrow();
}
/// <summary>
/// 呈现选项列表,等待玩家选择。
/// <list type="number">
/// <item>显示选项容器,配置每个按钮的文本与点击监听。</item>
/// <item>通过 <see cref="YarnTaskCompletionSource{T}"/> 异步等待玩家选择。</item>
/// <item>返回玩家选择的 <see cref="DialogueOption"/>(对话被外部取消则返回 null。</item>
/// </list>
/// </summary>
public override async YarnTask<DialogueOption?> RunOptionsAsync(
DialogueOption[] options,
LineCancellationToken token)
{
if (Page == null) return null;
// 遇到选项强制打断快进
_isFastForwarding = false;
// 清空台词区,准备显示选项
if (speakerContainer != null) speakerContainer.SetActive(false);
if (dialogueText != null) dialogueText.text = string.Empty;
if (Page.speakerContainer != null) Page.speakerContainer.SetActive(false);
if (Page.dialogueText != null) Page.dialogueText.text = string.Empty;
// 显示选项容器
if (choiceFrame != null) choiceFrame.SetActive(true);
if (Page.choiceFrame != null) Page.choiceFrame.SetActive(true);
// 创建选项选择的异步结果来源
_optionTcs = new YarnTaskCompletionSource<DialogueOption?>();
// 当 NextContentToken 被外部取消(对话被强制结束),提前将 TCS 置为 null 结果,
// 使 await _optionTcs.Task 能正常退出而不死锁。
WaitForExternalCancelAsync(token.NextContentToken).Forget();
// ── 配置每个选项按钮 ──
for (int i = 0; i < choiceButtons.Length; i++)
for (int i = 0; i < Page.choiceButtons.Length; i++)
{
// 只显示当前实际有效且可用的选项
bool active = i < options.Length && options[i].IsAvailable;
if (choiceButtons[i] != null)
choiceButtons[i].gameObject.SetActive(active);
if (Page.choiceButtons[i] == null) continue;
if (!active) continue;
// 设置按钮文本
if (i < choiceTexts.Length && choiceTexts[i] != null)
choiceTexts[i].text = options[i].Line.TextWithoutCharacterName.Text;
// 捕获循环变量,避免闭包捕获错误索引
DialogueOption captured = options[i];
choiceButtons[i].onClick.RemoveAllListeners();
choiceButtons[i].onClick.AddListener(() => _optionTcs.TrySetResult(captured));
if (i < options.Length)
Page.choiceButtons[i].Setup(options[i], opt => _optionTcs.TrySetResult(opt));
else
Page.choiceButtons[i].Cleanup();
}
// ── 等待玩家选择 ──
DialogueOption? selected = await _optionTcs.Task;
// 清理按钮监听,隐藏选项容器
foreach (Button btn in choiceButtons)
if (btn != null) btn.onClick.RemoveAllListeners();
// 清理所有按钮并隐藏选项容器
foreach (ChoiceButton btn in Page.choiceButtons)
btn?.Cleanup();
if (choiceFrame != null) choiceFrame.SetActive(false);
if (Page.choiceFrame != null) Page.choiceFrame.SetActive(false);
return selected;
}
/// <summary>
/// 对话结束:淡出并隐藏对话面板。
/// 在 DialogueRunner 处理完最后一行/选项后由 Yarn 调用。
/// </summary>
public override async YarnTask OnDialogueCompleteAsync()
{
if (panelGroup != null)
await Effects.FadeAlphaAsync(panelGroup, 1f, 0f, fadeOutDuration, CancellationToken.None);
SetPanelState(visible: false, instant: true);
if (Page != null)
{
var tcs = new YarnTaskCompletionSource<bool>();
Page.PlayFadeOut(() => tcs.TrySetResult(true));
await tcs.Task;
}
}
// ── 内部方法 ─────────────────────────────────────────────────────────────
/// <summary>
/// 推进按钮点击回调。
/// <list type="bullet">
/// <item>打字中 → RequestHurryUpLine取消 HurryUpToken立即显示全文。</item>
/// <item>打字完毕 → RequestNextLine取消 NextContentToken进入下一行。</item>
/// </list>
/// </summary>
private void OnAdvanceButtonClicked()
{
// 如果玩家在快进时点击屏幕/推进键,则打断快进状态并停止当前动作
if (_isFastForwarding)
{
_isFastForwarding = false;
return;
}
if (_runner == null) return;
if (!_typewriterComplete)
@@ -262,55 +229,40 @@ namespace Ichni.Story.Dialogue
_runner.RequestNextLine(); // 第二次点击:推进到下一行
}
/// <summary>
/// 控制对话面板 <see cref="panelGroup"/> 的显示状态。
/// </summary>
/// <param name="visible">是否可见且可交互。</param>
/// <param name="instant">为 true 时立即设置 alpha为 false 时仅设置 interactable/blocksRaycasts
/// alpha 留给外部的 <c>FadeAlphaAsync</c> 动画处理。</param>
private void SetPanelState(bool visible, bool instant)
private void OnFastForwardButtonClicked()
{
if (panelGroup == null) return;
panelGroup.interactable = visible;
panelGroup.blocksRaycasts = visible;
if (instant) panelGroup.alpha = visible ? 1f : 0f;
_isFastForwarding = true;
if (_runner == null) return;
// 立即跳过当前的等待状态
if (!_typewriterComplete)
_runner.RequestHurryUpLine();
else
_runner.RequestNextLine();
}
/// <summary>
/// 逐字打字效果。
/// 通过 <c>maxVisibleCharacters</c> 逐步显示文本(无 GC 分配)。
/// 被 <paramref name="hurryUpToken"/> 取消时立即返回,调用方负责将 maxVisibleCharacters 设为 MaxValue。
/// </summary>
/// <param name="text">要逐字展示的完整文本(已预先赋值给 dialogueText.text。</param>
/// <param name="hurryUpToken">取消此 token 可跳过打字效果。</param>
private async YarnTask TypewriterAsync(string text, CancellationToken hurryUpToken)
{
if (dialogueText == null) return;
if (Page.dialogueText == null) return;
// lettersPerSecond <= 0 时瞬间显示全文
if (lettersPerSecond <= 0f)
{
dialogueText.maxVisibleCharacters = int.MaxValue;
Page.dialogueText.maxVisibleCharacters = int.MaxValue;
return;
}
int delayMs = Mathf.Max(1, Mathf.RoundToInt(1000f / lettersPerSecond));
// TMP 的 maxVisibleCharacters 控制可见字符数(不含 rich-text 标签)
// 逐帧递增直到全文显示或被 hurryUpToken 中断
for (int i = 1; i <= text.Length; i++)
{
if (hurryUpToken.IsCancellationRequested) return;
dialogueText.maxVisibleCharacters = i;
Page.dialogueText.maxVisibleCharacters = i;
await YarnTask.Delay(delayMs, hurryUpToken).SuppressCancellationThrow();
if (hurryUpToken.IsCancellationRequested) return;
}
}
/// <summary>
/// 监听外部取消信号(对话被强制终止),将 <see cref="_optionTcs"/> 设为 null 结果,
/// 确保 <see cref="RunOptionsAsync"/> 的 await 能正常退出而不会死锁。
/// </summary>
private async YarnTask WaitForExternalCancelAsync(CancellationToken externalToken)
{
await YarnTask.WaitUntilCanceled(externalToken).SuppressCancellationThrow();

View File

@@ -0,0 +1,334 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Ichni.Story.Dialogue
{
/// <summary>
/// VN 立绘舞台。管理当前对话中所有已上台角色的 <see cref="IPortraitRenderer"/> 实例。
/// 依据用户的合理建议,为了支持未知数量的角色同时出现,本系统改为【动态生成与移除】机制:
/// <list type="bullet">
/// <item>当角色需要上台时,依据其 <see cref="CharacterData.portraitStyle"/> 动态实例化 Prefab。</item>
/// <item>当角色下台时直接销毁Destroy对应的 GameObject 释放内存。</item>
/// </list>
/// </summary>
public class VNPortraitStage : MonoBehaviour
{
public static VNPortraitStage Instance { get; private set; }
// ── 配置 ────────────────────────────────────────────────────────────────
[Header("Prefab Settings")]
[Tooltip("默认的 Sprite 立绘预制体。当角色的 Portrait Style 为 Sprite 时使用此预制体进行实例化。" +
"该预制体上必须挂载了实现 IPortraitRenderer 的组件(如 SpritePortraitRenderer。")]
[SerializeField]
private GameObject _spritePortraitPrefab;
[Header("Canvas Reference")]
[Tooltip("立绘容器的 RectTransform作为所有生成立绘的父节点。也是坐标映射的基准通常指向 DialogPage/PortraitContainer。")]
[SerializeField]
private RectTransform _portraitContainer;
// ── 运行时状态 ───────────────────────────────────────────────────────────
/// <summary>当前已上台的活跃角色characterId → 其对应的渲染器实例</summary>
private readonly Dictionary<string, IPortraitRenderer> _activePortraits =
new Dictionary<string, IPortraitRenderer>();
// ── 生命周期 ─────────────────────────────────────────────────────────────
private void Awake()
{
Instance = this;
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
// ── 公共 API ─────────────────────────────────────────────────────────────
/// <summary>
/// 当前说话者角色 Id由 Yarn 行数据驱动)。
/// </summary>
public string CurrentSpeakerId { get; private set; }
/// <summary>
/// 显示角色立绘。如果角色已在台上则更新情绪和位置;
/// 如果不在台上,则根据其渲染类型动态生成,并置于容器内。
/// 这里的坐标是归一化坐标 (-100, 100),内部会自动加上该角色的 positionOffset。
/// </summary>
/// <param name="characterId">角色 ID需在 CharacterRegistry 中存在)。</param>
/// <param name="emotion">初始情绪名。</param>
/// <param name="normalizedPosition">目标归一化坐标 [-100, 100]。</param>
/// <returns>成功生成或更新返回 true失败返回 false。</returns>
public bool ShowPortrait(string characterId, string emotion, Vector2 normalizedPosition)
{
CharacterData data = GetCharacterData(characterId);
if (data == null) return false;
// 1. 计算最终的像素坐标(应用 CharacterData 中配置的 positionOffset
Vector2 finalNormalizedPos = normalizedPosition + data.positionOffset;
Vector2 pixelPos = NormalizedToAnchoredPosition(finalNormalizedPos.x, finalNormalizedPos.y);
// 2. 如果角色已在台上,直接更新情绪与位置
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer existing))
{
existing.SetEmotion(emotion);
existing.SetPosition(pixelPos);
return true;
}
// 2. 如果不在台上,决定使用哪一个预制体进行动态生成
GameObject prefabToInstantiate = null;
if (data.portraitStyle == PortraitStyle.Sprite)
{
prefabToInstantiate = _spritePortraitPrefab;
if (prefabToInstantiate == null)
{
Debug.LogError("[VNPortraitStage] 未配置 _spritePortraitPrefab无法动态生成 Sprite 立绘!");
return false;
}
}
else // Live2D / Spine
{
prefabToInstantiate = data.dynamicPortraitPrefab;
if (prefabToInstantiate == null)
{
Debug.LogError($"[VNPortraitStage] 角色 '{characterId}' 为动态立绘,但 CharacterData 中未配置 dynamicPortraitPrefab");
return false;
}
}
// 3. 动态实例化
GameObject go = Instantiate(prefabToInstantiate, _portraitContainer);
go.name = $"Portrait_{characterId}";
// 确保坐标系和缩放正确
RectTransform rect = go.GetComponent<RectTransform>();
if (rect != null)
{
rect.localScale = Vector3.one;
rect.localRotation = Quaternion.identity;
}
IPortraitRenderer renderer = go.GetComponent<IPortraitRenderer>();
if (renderer == null)
{
Debug.LogError($"[VNPortraitStage] 生成的预制体 '{go.name}' 缺少实现 IPortraitRenderer 接口的组件!");
Destroy(go);
return false;
}
// 4. 显示并定位
renderer.Show(data, emotion);
renderer.SetPosition(pixelPos);
// 5. 登记到活跃立绘列表
_activePortraits[characterId] = renderer;
// 6. 保持当前说话者的高亮状态一致
renderer.SetActiveVisual(characterId == CurrentSpeakerId);
return true;
}
/// <summary>
/// 移除并销毁指定角色的立绘。
/// </summary>
public void HidePortrait(string characterId)
{
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer renderer))
{
if (renderer is MonoBehaviour mb && mb != null)
{
Destroy(mb.gameObject);
}
_activePortraits.Remove(characterId);
}
}
/// <summary>
/// 清空并销毁所有当前在台上的立绘。
/// </summary>
public void HideAll()
{
foreach (IPortraitRenderer renderer in _activePortraits.Values)
{
if (renderer is MonoBehaviour mb && mb != null)
{
Destroy(mb.gameObject);
}
}
_activePortraits.Clear();
}
/// <summary>
/// 设置或移动立绘位置,并可选择性地更新表情。如果角色不在台上,则会动态生成它。
/// </summary>
/// <param name="characterId">角色 ID。</param>
/// <param name="normalizedPosition">目标归一化坐标 [-100, 100],会自动应用角色的 positionOffset。</param>
/// <param name="emotion">可选表情。如果为 null 且角色已在台上,则保持当前表情;若角色不在台上,则默认使用 "normal"。</param>
public void SetPortraitPosition(string characterId, Vector2 normalizedPosition, string emotion = null)
{
CharacterData data = GetCharacterData(characterId);
if (data == null) return;
// 计算包含角色特定偏移量的最终像素坐标
Vector2 finalNormalizedPos = normalizedPosition + data.positionOffset;
Vector2 pixelPos = NormalizedToAnchoredPosition(finalNormalizedPos.x, finalNormalizedPos.y);
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer renderer))
{
renderer.SetPosition(pixelPos);
if (!string.IsNullOrEmpty(emotion))
{
renderer.SetEmotion(emotion);
}
}
else
{
// 不在台上,则用指定表情(空时默认 "normal")在目标位置生成它
string targetEmotion = string.IsNullOrEmpty(emotion) ? "normal" : emotion;
ShowPortrait(characterId, targetEmotion, normalizedPosition);
}
}
/// <summary>
/// 说话者高亮压暗处理:当前说话者高亮,其余压暗。
/// </summary>
public void HighlightSpeaker(string characterId)
{
CurrentSpeakerId = characterId;
foreach (KeyValuePair<string, IPortraitRenderer> kv in _activePortraits)
{
kv.Value.SetActiveVisual(kv.Key == characterId);
}
}
/// <summary>
/// 仅改变角色的表情(角色必须已在台上)。
/// </summary>
public void SetPortraitEmotion(string characterId, string emotion)
{
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer renderer))
{
renderer.SetEmotion(emotion);
}
else
{
Debug.LogWarning($"[VNPortraitStage] 无法仅设置表情:角色 '{characterId}' 目前不在台上。");
}
}
/// <summary>
/// 平滑移动立绘到目标归一化坐标。
/// </summary>
public void MovePortrait(string characterId, Vector2 normalizedPosition, float duration)
{
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer renderer))
{
CharacterData data = GetCharacterData(characterId);
if (data != null)
{
Vector2 finalNormalizedPos = normalizedPosition + data.positionOffset;
Vector2 pixelPos = NormalizedToAnchoredPosition(finalNormalizedPos.x, finalNormalizedPos.y);
renderer.MoveTo(pixelPos, duration);
}
}
else
{
Debug.LogWarning($"[VNPortraitStage] 无法移动:角色 '{characterId}' 目前不在台上。");
}
}
/// <summary>
/// 让指定角色的立绘跳跃。
/// </summary>
/// <param name="normalizedPower">跳跃高度(归一化坐标尺度)</param>
public void JumpPortrait(string characterId, float normalizedPower, int numJumps, float duration)
{
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer renderer))
{
float pixelPower = NormalizedToPixelDistance(normalizedPower, false); // 跳跃基于 Y 轴高度
renderer.Jump(pixelPower, numJumps, duration);
}
else
{
Debug.LogWarning($"[VNPortraitStage] 无法跳跃:角色 '{characterId}' 目前不在台上。");
}
}
/// <summary>
/// 让指定角色的立绘震动。
/// </summary>
/// <param name="normalizedStrength">震动幅度(归一化坐标尺度)</param>
public void ShakePortrait(string characterId, float duration, float normalizedStrength, int vibrato)
{
if (_activePortraits.TryGetValue(characterId, out IPortraitRenderer renderer))
{
float pixelStrength = NormalizedToPixelDistance(normalizedStrength, true); // 左右震动基于 X 轴宽度
renderer.Shake(duration, pixelStrength, vibrato);
}
else
{
Debug.LogWarning($"[VNPortraitStage] 无法震动:角色 '{characterId}' 目前不在台上。");
}
}
/// <summary>
/// 将归一化坐标 (-100, 100) 映射为 anchoredPosition 像素坐标。
/// </summary>
public Vector2 NormalizedToAnchoredPosition(float normalizedX, float normalizedY)
{
if (_portraitContainer == null)
{
Debug.LogWarning("[VNPortraitStage] _portraitContainer 未配置,坐标映射使用 1920x1080 默认尺寸映射。");
return new Vector2(normalizedX / 100f * 960f, normalizedY / 100f * 540f);
}
Vector2 halfSize = _portraitContainer.rect.size * 0.5f;
return new Vector2(normalizedX / 100f * halfSize.x, normalizedY / 100f * halfSize.y);
}
/// <summary>
/// 将归一化距离映射为像素距离(用于动画幅度换算)。
/// </summary>
/// <param name="normalizedDistance">归一化长度</param>
/// <param name="useXAxis">是否基于 X 轴进行映射(否则基于 Y 轴)</param>
public float NormalizedToPixelDistance(float normalizedDistance, bool useXAxis = false)
{
if (_portraitContainer == null)
{
return normalizedDistance / 100f * (useXAxis ? 960f : 540f);
}
Vector2 halfSize = _portraitContainer.rect.size * 0.5f;
return normalizedDistance / 100f * (useXAxis ? halfSize.x : halfSize.y);
}
// ── 内部辅助 ─────────────────────────────────────────────────────────────
private CharacterData GetCharacterData(string characterId)
{
if (StoryManager.instance == null || StoryManager.instance.characterRegistry == null)
{
Debug.LogError("[VNPortraitStage] StoryManager 或 CharacterRegistry 未初始化!");
return null;
}
if (!StoryManager.instance.characterRegistry.TryGet(characterId, out CharacterData data))
{
Debug.LogWarning($"[VNPortraitStage] 在 CharacterRegistry 中找不到 ID 为 '{characterId}' 的 CharacterData 资产。");
return null;
}
return data;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ec4abf93d4e3d1d448630a311fe7c236

View File

@@ -9,7 +9,6 @@ namespace Ichni.Story.UI
{
public class StoryUIPage : UIPageBase
{
public MessageBox messageBox;
public Button backButton;
protected override void Awake()

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3ec0b23c77de2764d9425add8f28ea00
guid: 981c044487823304aade761b1adaf914
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2de1e277bcf059c42826353d1c086ccc
guid: 0d5fd17f91f4f8049953f00fe242b32c
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,71 @@
using Ichni.Story.Dialogue;
using Ichni.UI;
using UnityEngine;
using UnityEngine.Events;
namespace Ichni.Story.UI
{
/// <summary>
/// 对话系统专用的 UI 页面容器。
/// 接管整个对话面板的淡入淡出及交互状态控制,
/// 将显示生命周期与对话流逻辑 (VNDialoguePresenter) 彻底解耦。
/// </summary>
public class DialogUIPage : UIPageBase
{
public static DialogUIPage instance;
[Header("立绘舞台")]
public VNPortraitStage portraitStage;
[Header("台词区")]
public TMPro.TextMeshProUGUI dialogueText;
public GameObject speakerContainer;
public TMPro.TextMeshProUGUI speakerText;
[Header("推进按钮")]
public UnityEngine.UI.Button advanceButton;
[Tooltip("点击后自动快进,直到遇到选项")]
public UnityEngine.UI.Button fastForwardButton;
[Header("选项区")]
public GameObject choiceFrame;
public ChoiceButton[] choiceButtons = new ChoiceButton[4];
[Header("动态组件")]
public DialogWheel dialogWheel;
protected override void Awake()
{
base.Awake();
if (instance == null)
{
instance = this;
}
else
{
Destroy(gameObject);
return;
}
// 默认启动时强制隐藏
if (mainCanvasGroup != null)
{
mainCanvasGroup.alpha = 0f;
mainCanvasGroup.interactable = false;
mainCanvasGroup.blocksRaycasts = false;
mainCanvasGroup.gameObject.SetActive(false);
}
}
public void PlayFadeIn(UnityAction onComplete = null)
{
FadeIn(0.2f, false, onComplete);
}
public void PlayFadeOut(UnityAction onComplete = null)
{
FadeOut(0.2f, false, onComplete);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0e1e4a2c0a479784e839bdf448215e19

View File

@@ -0,0 +1,72 @@
using DG.Tweening;
using UnityEngine;
namespace Ichni.Story.UI
{
/// <summary>
/// 对话时常驻转动的齿轮/轮盘,以及伴随浮动的指针。
/// </summary>
public class DialogWheel : MonoBehaviour
{
[Header("轮盘旋转")]
[Tooltip("要旋转的 RectTransform。如果不填默认获取自身。")]
public RectTransform wheelRect;
[Tooltip("顺时针旋转一整圈360度所需的时间")]
public float rotationCycleDuration = 5f;
[Header("指针浮动")]
[Tooltip("常驻上下浮动的指针对象 (RectTransform)")]
public RectTransform pointerRect;
[Tooltip("指针单向浮动的距离(像素)")]
public float pointerFloatDistance = 5f;
[Tooltip("指针完成单向浮动所需的时间")]
public float pointerFloatDuration = 0.3f;
private Tween _rotateTween;
private Tween _pointerTween;
private float _pointerStartY;
private void Awake()
{
if (wheelRect == null) wheelRect = GetComponent<RectTransform>();
if (pointerRect != null)
{
_pointerStartY = pointerRect.anchoredPosition.y;
}
}
private void Start()
{
// 轮盘常驻匀速旋转
if (wheelRect != null && rotationCycleDuration > 0f)
{
// SetRelative(true) 保证无论当前角度是多少,都在其基础上转 -360 度
// LoopType.Restart 配合 Linear 缓动,实现无限丝滑旋转
_rotateTween = wheelRect.DORotate(new Vector3(0, 0, -360f), rotationCycleDuration, RotateMode.FastBeyond360)
.SetRelative(true)
.SetLoops(-1, LoopType.Restart)
.SetEase(Ease.Linear);
_rotateTween.Play();
}
// 指针常驻浮动,启动后永不停止
if (pointerRect != null)
{
_pointerTween = pointerRect.DOAnchorPosY(_pointerStartY + pointerFloatDistance, pointerFloatDuration)
.SetLoops(-1, LoopType.Yoyo)
.SetEase(Ease.InOutSine);
_pointerTween.Play();
}
}
private void OnDestroy()
{
_rotateTween?.Kill();
_pointerTween?.Kill();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 027bcd2c2d137f5458f8b12002234663

View File

@@ -0,0 +1,143 @@
using System.Collections.Generic;
using Ichni.Menu.UI;
using Ichni.UI;
using UnityEngine;
namespace Ichni.Story.UI
{
/// <summary>
/// 用于在剧情系统中管理并顺序展示 MessageBox 的独立 UI 页面。
/// 支持动态实例化给定的 MessageBox Prefab并在所有消息展示完毕后进行大页面的 FadeOut。
/// </summary>
public class StoryMessageBoxUIPage : UIPageBase
{
public static StoryMessageBoxUIPage instance;
[Header("Prefabs & Containers")]
[Tooltip("默认使用的 MessageBox 预制体(请从 Project 中拖拽,而非场景中的实例)")]
public MessageBox defaultMessageBoxPrefab;
[Tooltip("生成的 MessageBox 挂载在哪?如果不填,默认挂载在自己身上")]
public Transform messageContainer;
// 用于排队的内部消息数据结构
private struct PendingMessage
{
public string title;
public string content;
public MessageBox customPrefab; // 支持未来传入不同种类的 Prefab
}
private Queue<PendingMessage> _messageQueue = new Queue<PendingMessage>();
private MessageBox _currentActiveBox;
private bool _isPageActive = false;
protected override void Awake()
{
base.Awake();
if (instance == null)
{
instance = this;
}
else
{
Debug.LogWarning("[StoryMessageBoxUIPage] 场景中存在多个实例,正在销毁多余的实例。");
Destroy(gameObject);
return;
}
if (messageContainer == null)
{
messageContainer = this.transform;
}
}
/// <summary>
/// 将歌曲解锁提示加入弹窗队列。
/// </summary>
public void ShowUnlockMessage(string songUnlockKey)
{
string title = "New Song Unlocked!";
string content = $"You have unlocked the song: {songUnlockKey}!";
EnqueueMessage(title, content, defaultMessageBoxPrefab);
}
/// <summary>
/// 将自定义文本提示加入弹窗队列。
/// </summary>
public void ShowCustomMessage(string title, string content)
{
EnqueueMessage(title, content, defaultMessageBoxPrefab);
}
/// <summary>
/// 将指定消息入队。如果是空闲状态,则唤醒大页面并开始处理队列。
/// </summary>
private void EnqueueMessage(string title, string content, MessageBox prefab)
{
if (prefab == null)
{
Debug.LogError("[StoryMessageBoxUIPage] 没有可用的 MessageBox 预制体!");
return;
}
_messageQueue.Enqueue(new PendingMessage
{
title = title,
content = content,
customPrefab = prefab
});
// 如果当前页面没有在显示中,则开始整体流程
if (!_isPageActive)
{
_isPageActive = true;
// 唤醒大页面(执行 UIPageBase 的渐入并拦截底层射线)
this.FadeIn(0.5f, false, ShowNextInQueue);
}
}
/// <summary>
/// 从队列中取出一个并实例化显示。
/// </summary>
private void ShowNextInQueue()
{
if (_messageQueue.Count == 0)
{
// 队列处理完毕,大页面整体退场
_currentActiveBox = null;
_isPageActive = false;
this.FadeOut();
return;
}
PendingMessage msg = _messageQueue.Dequeue();
// 实例化预制体
_currentActiveBox = Instantiate(msg.customPrefab, messageContainer);
// 订阅“当这个消息框彻底结束关闭时”的事件
_currentActiveBox.onAllMessagesClosed.AddListener(() =>
{
// 销毁旧实例
if (_currentActiveBox != null)
{
Destroy(_currentActiveBox.gameObject);
}
// 检查并显示下一个
ShowNextInQueue();
});
// 配置并启动 MessageBox
_currentActiveBox.Clear();
_currentActiveBox.AddInfo(msg.title, msg.content, null);
// MessageBox.SetUp() 内部会激活自己并执行自己的 FadeIn
_currentActiveBox.gameObject.SetActive(true);
_currentActiveBox.SetUp();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 07f2a7ffd4b513d4e90d60a6587914cc

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 31a6a9bf0919951489cacfb86fb715c9
guid: 0700ab9bd015c9e4f8e5dcdc6f5f59ce
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,30 @@
using UnityEngine;
using Yarn.Unity;
namespace Ichni.Story.YarnFunctions
{
public static class GeneralFunctions
{
[YarnCommand("log")]
public static void Log(string message, string logType)
{
logType = logType.ToLower();
switch (logType)
{
case "info":
Debug.Log(message);
break;
case "warning":
Debug.LogWarning(message);
break;
case "error":
Debug.LogError(message);
break;
default:
Debug.Log(message);
break;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1af4095ad0bedbe41b834a162253a0e8

View File

@@ -0,0 +1,69 @@
using UnityEngine;
using Yarn.Unity;
namespace Ichni.Story.YarnFunctions
{
/// <summary>
/// 立绘动画控制相关的 Yarn 自定义命令静态注册器。
/// 包含平移、跳跃、震动等 DOTween 动画表现。
/// </summary>
public static class PortraitAnimationCommands
{
// ── Yarn Commands ────────────────────────────────────────────────────────
/// <summary>
/// 平滑移动角色立绘到目标位置。
/// 格式: &lt;&lt;move_portrait characterId x y duration&gt;&gt;
/// </summary>
/// <param name="characterId">角色标识 ID</param>
/// <param name="x">目标 X 坐标 [-100, 100]</param>
/// <param name="y">目标 Y 坐标 [-100, 100]</param>
/// <param name="duration">动画持续时间(秒)</param>
[YarnCommand("move_portrait")]
public static void MovePortrait(string characterId, float x, float y, float duration)
{
if (!EnsureStage()) return;
Dialogue.VNPortraitStage.Instance.MovePortrait(characterId, new Vector2(x, y), duration);
}
/// <summary>
/// 让角色立绘原地跳跃。
/// 格式: &lt;&lt;jump_portrait characterId power numJumps duration&gt;&gt;
/// </summary>
/// <param name="characterId">角色标识 ID</param>
/// <param name="power">跳跃高度(使用与坐标域相同的归一化尺度,如 10 表示 10% 高度)</param>
/// <param name="numJumps">跳跃次数</param>
/// <param name="duration">动画总持续时间(秒)</param>
[YarnCommand("jump_portrait")]
public static void JumpPortrait(string characterId, float power, int numJumps, float duration)
{
if (!EnsureStage()) return;
Dialogue.VNPortraitStage.Instance.JumpPortrait(characterId, power, numJumps, duration);
}
/// <summary>
/// 让角色立绘产生左右震动效果。
/// 格式: &lt;&lt;shake_portrait characterId duration strength vibrato&gt;&gt;
/// </summary>
/// <param name="characterId">角色标识 ID</param>
/// <param name="duration">震动持续时间(秒)</param>
/// <param name="strength">震动幅度(使用与坐标域相同的归一化尺度)</param>
/// <param name="vibrato">震动频率</param>
[YarnCommand("shake_portrait")]
public static void ShakePortrait(string characterId, float duration, float strength, int vibrato)
{
if (!EnsureStage()) return;
Dialogue.VNPortraitStage.Instance.ShakePortrait(characterId, duration, strength, vibrato);
}
// ── 内部辅助 ─────────────────────────────────────────────────────────────
private static bool EnsureStage()
{
if (Dialogue.VNPortraitStage.Instance != null) return true;
Debug.LogError("[PortraitAnimationCommands] 未找到 VNPortraitStage 实例!请确保在当前场景中存在挂载了 VNPortraitStage 的游戏物体。");
return false;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 50b4898984f9bfc4da527dcd444f87b3

View File

@@ -0,0 +1,112 @@
using UnityEngine;
using Yarn.Unity;
namespace Ichni.Story.YarnFunctions
{
/// <summary>
/// 立绘控制相关的 Yarn 自定义命令静态注册器。
/// <para>
/// 全局静态命令,不依赖于 GameObject 的挂载。所有方法都使用强类型的参数,
/// 以更明确、简洁的指令名称区分不同应用场景,从而抛弃复杂的内部字符串解析判断。
/// </para>
/// </summary>
public static class PortraitCommands
{
// ── Yarn Commands ────────────────────────────────────────────────────────
/// <summary>
/// 最基础、最全面的立绘指令:设置指定角色的坐标和表情。
/// 若角色不在台上,会自动以指定的表情和位置实例化。
/// 格式: &lt;&lt;set_portrait characterId x y [emotion]&gt;&gt;
/// </summary>
/// <param name="characterId">角色标识 ID。</param>
/// <param name="x">归一化 X 坐标 [-100, 100]</param>
/// <param name="y">归一化 Y 坐标 [-100, 100]</param>
/// <param name="emotion">表情名称。如果不填,默认保持不变或初始化为 "normal"</param>
[YarnCommand("set_portrait")]
public static void SetPortrait(string characterId, float x, float y, string emotion = null)
{
if (!EnsureStage()) return;
// 传入归一化坐标,由 VNPortraitStage 内部自动应用 positionOffset 并转换为像素坐标
Vector2 normalizedPos = new Vector2(x, y);
Dialogue.VNPortraitStage.Instance.SetPortraitPosition(characterId, normalizedPos, emotion);
}
/// <summary>
/// 设置当前说话角色的坐标和表情。
/// 格式: &lt;&lt;set_portrait_current x y [emotion]&gt;&gt;
/// </summary>
[YarnCommand("set_portrait_current")]
public static void SetPortraitCurrent(float x, float y, string emotion = null)
{
if (!EnsureStage()) return;
string characterId = Dialogue.VNPortraitStage.Instance.CurrentSpeakerId;
if (string.IsNullOrEmpty(characterId))
{
Debug.LogWarning("[PortraitCommands] set_portrait_current: 当前没有活动的说话者,无法执行此命令!");
return;
}
SetPortrait(characterId, x, y, emotion);
}
/// <summary>
/// 仅设置或移动指定角色的坐标(不改变当前表情)。
/// 格式: &lt;&lt;set_portrait_position characterId x y&gt;&gt;
/// </summary>
[YarnCommand("set_portrait_position")]
public static void SetPortraitPosition(string characterId, float x, float y)
{
// 通过传入 emotion = nullVNPortraitStage 在更新时将保持角色原有表情
SetPortrait(characterId, x, y, null);
}
/// <summary>
/// 仅改变指定角色的表情(角色必须已经上台)。
/// 格式: &lt;&lt;set_portrait_emotion characterId emotion&gt;&gt;
/// </summary>
[YarnCommand("set_portrait_emotion")]
public static void SetPortraitEmotion(string characterId, string emotion)
{
if (!EnsureStage()) return;
Dialogue.VNPortraitStage.Instance.SetPortraitEmotion(characterId, emotion);
}
/// <summary>
/// 隐藏指定角色的立绘。
/// 格式: &lt;&lt;hide_portrait characterId&gt;&gt;
/// </summary>
[YarnCommand("hide_portrait")]
public static void HidePortrait(string characterId)
{
if (!EnsureStage()) return;
Dialogue.VNPortraitStage.Instance.HidePortrait(characterId);
}
/// <summary>
/// 隐藏当前所有在台上的立绘。
/// 格式: &lt;&lt;hide_all_portraits&gt;&gt;
/// </summary>
[YarnCommand("hide_all_portraits")]
public static void HideAllPortraits()
{
if (!EnsureStage()) return;
Dialogue.VNPortraitStage.Instance.HideAll();
}
// ── 内部辅助 ─────────────────────────────────────────────────────────────
private static bool EnsureStage()
{
if (Dialogue.VNPortraitStage.Instance != null) return true;
Debug.LogError("[PortraitCommands] 未找到 VNPortraitStage 实例!请确保在当前场景中存在挂载了 VNPortraitStage 的游戏物体。");
return false;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d56251fa130b1334eab42a8d3069932f

View File

@@ -0,0 +1,103 @@
using Ichni.Story.Dialogue;
using Ichni.Story.UI;
using UnityEngine;
using UnityEngine.Localization.Settings;
using Yarn.Unity;
namespace Ichni.Story.YarnFunctions
{
/// <summary>
/// 包含与故事树节点控制、全局进程、存档交互相关的 Yarn 自定义命令注册器。
/// </summary>
public static class StoryTreeCommands
{
// ── Yarn Commands ────────────────────────────────────────────────────────
/// <summary>
/// 解锁一首新歌曲,并在当前这段对话结束(对话 UI 淡出)后,弹出 MessageBox 提示。
/// 格式: &lt;&lt;unlock_song songUnlockKey&gt;&gt;
/// </summary>
/// <param name="songUnlockKey">要解锁的歌曲密钥/ID</param>
[YarnCommand("unlock_song")]
public static void UnlockSong(string songUnlockKey)
{
if (GameSaveManager.instance == null || GameSaveManager.instance.SongSaveModule == null)
{
Debug.LogError("[StoryTreeCommands] 无法解锁歌曲GameSaveManager 或其 SongSaveModule 未初始化!");
return;
}
// 1. 检查是否已经解锁
if (GameSaveManager.instance.SongSaveModule.CheckStoryKey(songUnlockKey))
{
Debug.Log($"[StoryTreeCommands] 歌曲 {songUnlockKey} 已处于解锁状态。");
return;
}
// 2. 写入解锁状态并保存存档
GameSaveManager.instance.SongSaveModule.storyUnlockKeys.Add(songUnlockKey);
GameSaveManager.instance.SongSaveModule.SaveStoryUnlockKeys();
Debug.Log($"[StoryTreeCommands] 已成功写入并保存歌曲解锁密钥:{songUnlockKey}");
// 3. 将 UI 弹窗提示加入到对话结束队列中
if (StoryDialogueController.instance != null)
{
StoryDialogueController.instance.dialogueEndActions.Add(() =>
{
if (StoryMessageBoxUIPage.instance != null)
{
StoryMessageBoxUIPage.instance.ShowUnlockMessage(songUnlockKey);
}
else
{
Debug.LogError("[StoryTreeCommands] 弹窗失败:场景中未找到 StoryMessageBoxUIPage 实例!");
}
});
}
else
{
Debug.LogError("[StoryTreeCommands] 弹窗队列注册失败:未找到 StoryDialogueController 实例!");
}
}
/// <summary>
/// 在对话结束后,弹出一个自定义 MessageBox 提示。
/// 会尝试使用 Unity Localization 进行翻译如果找不到对应条目则回退显示原文本Key
/// 格式: &lt;&lt;show_message titleKey contentKey [tableName]&gt;&gt;
/// </summary>
/// <param name="titleKey">标题文本或本地化键</param>
/// <param name="contentKey">正文文本或本地化键</param>
/// <param name="tableName">Unity Localization 中的 String Table 名称</param>
[YarnCommand("show_message")]
public static void ShowMessage(string titleKey, string contentKey, string tableName = "Message")
{
string translatedTitle = GetTranslatedText(tableName, titleKey);
string translatedContent = GetTranslatedText(tableName, contentKey);
Debug.Log($"[StoryTreeCommands] 准备在对话结束后显示自定义弹窗:标题='{translatedTitle}',内容='{translatedContent}'");
StoryDialogueController.instance.dialogueEndActions.Add(() =>
{
StoryMessageBoxUIPage.instance.ShowCustomMessage(translatedTitle, translatedContent);
});
}
// ── 内部辅助 ─────────────────────────────────────────────────────────────
private static string GetTranslatedText(string tableName, string key)
{
if (string.IsNullOrEmpty(key)) return string.Empty;
try
{
// 尝试从 Unity Localization 中获取翻译文本
string translated = LocalizationSettings.StringDatabase.GetLocalizedString(tableName, key);
// 若获取失败或为空,则直接原样返回 key 作为兜底文本
return string.IsNullOrEmpty(translated) ? key : translated;
}
catch
{
// 如果表不存在或其他异常,返回原 key
return key;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7a048ef81fee2164b8c7ae9748690cfd

View File

@@ -0,0 +1,76 @@
using UnityEngine;
using Yarn.Unity;
using Ichni.Story;
namespace Ichni.Story.YarnFunctions
{
/// <summary>
/// 提供可以直接在 Yarn 脚本中调用的全局变量读写指令。
/// 完全兼容旧系统中的自定义函数(避免旧的 .yarn 报错),
/// 底层统一指向全新的 StoryVariables 枢纽。
/// </summary>
public static class VariableCommands
{
[YarnCommand("set_bool")]
public static void SetBool(string key, bool value)
{
StoryVariables.SetBool(key, value);
}
[YarnFunction("get_bool")]
public static bool GetBool(string key)
{
return StoryVariables.GetBool(key);
}
[YarnCommand("set_int")]
public static void SetInt(string key, int value)
{
StoryVariables.SetInt(key, value);
}
[YarnCommand("modify_int")]
public static void ModifyInt(string key, int modification)
{
int currentValue = StoryVariables.GetInt(key);
StoryVariables.SetInt(key, currentValue + modification);
}
[YarnFunction("get_int")]
public static int GetInt(string key)
{
return StoryVariables.GetInt(key);
}
[YarnCommand("set_float")]
public static void SetFloat(string key, float value)
{
StoryVariables.SetFloat(key, value);
}
[YarnCommand("modify_float")]
public static void ModifyFloat(string key, float modification)
{
float currentValue = StoryVariables.GetFloat(key);
StoryVariables.SetFloat(key, currentValue + modification);
}
[YarnFunction("get_float")]
public static float GetFloat(string key)
{
return StoryVariables.GetFloat(key);
}
[YarnCommand("set_string")]
public static void SetString(string key, string value)
{
StoryVariables.SetString(key, value);
}
[YarnFunction("get_string")]
public static string GetString(string key)
{
return StoryVariables.GetString(key);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b9094628fd04644439b4afd553c7a6aa

View File

@@ -1,95 +0,0 @@
using System.Collections.Generic;
using Yarn.Unity;
namespace SLSUtilities.Narrative
{
public static partial class YarnFunctions
{
[YarnCommand("set_bool")]
public static void Yarn_SetBool(string key, bool value)
{
StorySystem.Variables.boolVariables[key] = value;
}
[YarnFunction("get_bool")]
public static bool Yarn_GetBool(string key)
{
return StorySystem.Variables.boolVariables.GetValueOrDefault(key, false);
}
[YarnCommand("set_int")]
public static void Yarn_SetInt(string key, int value)
{
StorySystem.Variables.intVariables[key] = value;
}
[YarnCommand("modify_int")]
public static void Yarn_ModifyInt(string key, int modification)
{
int currentValue = StorySystem.Variables.intVariables.GetValueOrDefault(key, 0);
StorySystem.Variables.intVariables[key] = currentValue + modification;
}
[YarnFunction("get_int")]
public static int Yarn_GetInt(string key)
{
return StorySystem.Variables.intVariables.GetValueOrDefault(key, 0);
}
[YarnCommand("set_float")]
public static void Yarn_SetFloat(string key, float value)
{
StorySystem.Variables.floatVariables[key] = value;
}
[YarnCommand("modify_float")]
public static void Yarn_ModifyFloat(string key, float modification)
{
float currentValue = StorySystem.Variables.floatVariables.GetValueOrDefault(key, 0f);
StorySystem.Variables.floatVariables[key] = currentValue + modification;
}
[YarnFunction("get_float")]
public static float Yarn_GetFloat(string key)
{
return StorySystem.Variables.floatVariables.GetValueOrDefault(key, 0f);
}
[YarnCommand("set_string")]
public static void Yarn_SetString(string key, string value)
{
StorySystem.Variables.stringVariables[key] = value;
}
[YarnFunction("get_string")]
public static string Yarn_GetString(string key)
{
return StorySystem.Variables.stringVariables.GetValueOrDefault(key, "");
}
}
public static partial class YarnFunctions
{
[YarnCommand("log")]
public static void Log(string message, string logType)
{
logType = logType.ToLower();
switch (logType)
{
case "info":
UnityEngine.Debug.Log(message);
break;
case "warning":
UnityEngine.Debug.LogWarning(message);
break;
case "error":
UnityEngine.Debug.LogError(message);
break;
default:
UnityEngine.Debug.Log(message);
break;
}
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: fd615694e3d8cc842a6c75bc3956a926

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b2c23056fb0ab4b45a8db11866018794
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,18 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeatmapStatusMark : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: b547d2cc398393a46a2a4c503f128cac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8c234abdb2207c8459280e59152ba1c9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using I2.Loc;
using Sirenix.OdinInspector;
using SLSUtilities.WwiseAssistance;
using TMPro;
@@ -17,16 +16,16 @@ namespace Ichni.Menu.UI
{
public CanvasGroup canvasGroup;
[Tooltip("当该消息框结束所有消息并彻底淡出消失时触发。")]
public UnityEvent onAllMessagesClosed = new UnityEvent();
public int infoIndex;
public List<MessageBoxInfo> infos;
public Sprite defaultIcon;
public Image iconImage;
public Localize titleLocalize;
public TMP_Text titleText;
public Localize contentLocalize;
public TMP_Text contentText;
public LocalizationParamsManager parametersManager;
public Button receiveButton;
@@ -76,8 +75,8 @@ namespace Ichni.Menu.UI
infos[infoIndex].action?.Invoke();
titleLocalize.SetTerm(infos[infoIndex].title);
contentLocalize.SetTerm(infos[infoIndex].content);
if (titleText != null) titleText.text = infos[infoIndex].title;
if (contentText != null) contentText.text = infos[infoIndex].content;
this.iconImage.sprite = infos[infoIndex].icon != null ? infos[infoIndex].icon : defaultIcon;
infoIndex++;
@@ -95,17 +94,10 @@ namespace Ichni.Menu.UI
public partial class MessageBox
{
public void SetParameter(string paramName, string paramValue, bool localize = false)
{
if (localize)
{
parametersManager.SetParameterValue(paramName, paramValue);
}
else
{
parametersManager.SetParameterValue(paramName, paramValue, false);
}
}
// 废弃对 I2.Loc 参数化功能的支持
// public void SetParameter(string paramName, string paramValue, bool localize = false)
// {
// }
}
public partial class MessageBox
@@ -136,6 +128,7 @@ namespace Ichni.Menu.UI
fadeTweener = canvasGroup.DOFade(0f, duration).OnComplete(() =>
{
canvasGroup.gameObject.SetActive(false);
onAllMessagesClosed?.Invoke();
});
if (ignoreTimeScale)

View File

@@ -1,4 +1,4 @@
// ============================================================
// ============================================================
// Chapter 0 · Block A0
// 命名约定C{章节}_{BlockId}_{分段}
// 入口节点C0_A0
@@ -12,26 +12,22 @@ position: 0,0
// 变量声明默认值StoryVariableStorage 存有存档值时会自动覆盖)
<<declare $asahi_knows_the_truth = false>>
<<declare $asahi_remembers_origin = false>>
SLS: 勇士,你终于来了。我们等待你很久了。 #line:0c2e7ae
<<set_portrait "Asahi" 33 0>>
Asahi: ……这就是那个传说中的地方吗? #line:0871a99
// 条件分支:朝日是否已知晓真相(再访时触发)
<<if $asahi_knows_the_truth>>
SLS: 朝日,你已经了解实情了。那就省去许多解释。 #line:036f930
<<jump C0_A0_truth_known>>
<<else>>
<<jump C0_A0_first_meeting>>
<<endif>>
<<jump C0_A0_first_meeting>>
===
title: C0_A0_first_meeting
position: -150,250
---
<<move_portrait "Asahi" -33 0 1.5>>
SLS: 这里曾是一场被时间掩埋的战争的终结之地。 #line:0cdce7d
SLS: 我们称之为"遗忘之地"。 #line:0fe1e57
<<jump_portrait "Asahi" 5 2 0.8>>
Asahi: 为什么叫这个名字? #line:068875f
SLS: 因为死去的人希望被遗忘—— #line:04a17d7
<<shake_portrait "Asahi" 1.0 2 10>>
SLS: ——而活下来的人,却永远无法忘记。 #line:03e5d89
Asahi: …… #line:0a4b2fc
@@ -41,6 +37,9 @@ Asahi: …… #line:0a4b2fc
<<jump C0_A0_reveal>>
-> 暂时不告诉朝日 #line:02b0524
<<jump C0_A0_conceal>>
-> 聊聊朝日的过去 <<if $asahi_remembers_origin>> #line:0e8d4c1
Asahi: 那段记忆……我终于能开口谈了。 #line:0a3f9b7
<<jump C0_A0_end>>
===
title: C0_A0_reveal
@@ -77,6 +76,7 @@ Asahi: 我已经准备好了。 #line:04617a4
title: C0_A0_end
position: 0,750
---
<<show_message "Test Title" "这是一条测试自定义信息的留言!">>
SLS: 无论你作何选择,这段旅途都只属于你。 #line:00bf206
SLS: 去吧,勇士。前方还有很长的路。 #line:03c5112
Asahi: 我会一直陪着你的,不管去哪里。 #line:06d2a12

View File

@@ -1,6 +1,6 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 2623424500
CRC: 429033785
HashAppended: 0
AssetBundleManifest:
AssetBundleInfos:

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 3927326319
CRC: 1054575820
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 512dd2d788d66c7fe4fad82c9898ac5a
Hash: 007f20ee8efa9449fd672739395d8b8b
TypeTreeHash:
serializedVersion: 2
Hash: dfed41e3b38758e5f34df30221d719dc
IncrementalBuildHash:
serializedVersion: 2
Hash: 11597e67617d09816dd570d9aea10af1
Hash: d5b71e5a50fcc975f61f5696454edfd0
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 3130613331
CRC: 3864940768
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 0782fed7ca84aeedfdeb1c740adc5b26
Hash: 8b97cbc629946fe6dd535e8a6f698e0c
TypeTreeHash:
serializedVersion: 2
Hash: 8cc3f86bb5dbc7fe3d0bf9f241837627
Hash: 6dc25ede816b5f1e26e55c131592f792
IncrementalBuildHash:
serializedVersion: 2
Hash: d8a4fa251b182c13d591514f02a84c6d
Hash: 54ab974a222b103c89f3e0fe238ac426
HashAppended: 0
ClassTypes:
- Class: 1
@@ -37,6 +37,8 @@ ClassTypes:
Script: {fileID: 11500000, guid: 0def5156137c6d14082064fa7b5d5247, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 77ab80dc0820f9d4a8cd9fddbbc89057, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 2cc178b2441bca14eb1321c83761ab6f, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 8ba3055fc57abcb41811796e6a0ed3aa, type: 3}
- Class: 114
@@ -83,6 +85,7 @@ SerializeReferenceClassIdentifiers:
- AssemblyName: Sirenix.Serialization
ClassName: Sirenix.Serialization.SerializationData
Assets:
- Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Environment/DTM_CircleSpinTrails.prefab
- Assets/ThemeBundles/DepartureToMultiverse/Prefabs/DTM_NoteVisualTap.prefab
- Assets/ThemeBundles/DepartureToMultiverse/Materials/Skybox/DTM_Skybox_Starry.mat
- Assets/ThemeBundles/DepartureToMultiverse/Prefabs/Environment/DTM_RandomGridTube.prefab

View File

@@ -10,7 +10,7 @@ Hashes:
Hash: 8ab77e54d824a64c30bc20496c5f5926
IncrementalBuildHash:
serializedVersion: 2
Hash: 7593ca8fd4ac3c250191162c1a3f3194
Hash: d713e609a88c186608032508763d02c3
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -1,6 +1,6 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 4273076075
CRC: 3383810862
HashAppended: 0
AssetBundleManifest:
AssetBundleInfos:

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 874269373
CRC: 4039318435
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 8ecfb5459953077e6f509c9dc150eec6
Hash: e59b40a27d3c2a868588952827b1934d
TypeTreeHash:
serializedVersion: 2
Hash: 6dc25ede816b5f1e26e55c131592f792
IncrementalBuildHash:
serializedVersion: 2
Hash: 5c3ae75a4cdbf2e6e5fc462d0226b21b
Hash: 19e5914687b1150ef00de5f4923cced6
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -108,7 +108,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -4816,7 +4816,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &6436623435409707789
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -4832,6 +4832,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -4853,9 +4858,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -4887,7 +4894,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &393908652242839112
GameObject:
m_ObjectHideFlags: 0
@@ -4996,7 +5002,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -9740,7 +9746,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &5469123982439044343
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -9756,6 +9762,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -9777,9 +9788,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 1
m_MeshDistribution: 0
m_SortMode: 0
@@ -9811,7 +9824,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &987757201259740425
GameObject:
m_ObjectHideFlags: 0
@@ -9920,7 +9932,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -14664,7 +14676,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &7698517573709736333
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -14680,6 +14692,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -14701,9 +14718,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 4
m_MeshDistribution: 0
m_SortMode: 0
@@ -14735,7 +14754,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &2649872400038691757
GameObject:
m_ObjectHideFlags: 0
@@ -14844,7 +14862,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -19561,7 +19579,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3804338172303829356
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -19577,6 +19595,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -19598,9 +19621,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -19632,7 +19657,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &4315719500094685406
GameObject:
m_ObjectHideFlags: 0
@@ -19741,7 +19765,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -24574,7 +24598,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3349403536436963533
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -24590,6 +24614,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -24611,9 +24640,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 1
m_MeshDistribution: 0
m_SortMode: 0
@@ -24645,7 +24676,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &6152325990999234384
GameObject:
m_ObjectHideFlags: 0
@@ -24754,7 +24784,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -29498,7 +29528,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &6804594070940386649
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -29514,6 +29544,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -29535,9 +29570,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 1
m_MeshDistribution: 0
m_SortMode: 0
@@ -29569,7 +29606,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &7653901243468862591
GameObject:
m_ObjectHideFlags: 0
@@ -29678,7 +29714,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -34511,7 +34547,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3494879711755687896
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -34527,6 +34563,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -34548,9 +34589,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 1
m_MeshDistribution: 0
m_SortMode: 0
@@ -34582,7 +34625,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &7916584198410465453
GameObject:
m_ObjectHideFlags: 0
@@ -34698,7 +34740,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -39348,7 +39390,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &6411650891525449585
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -39364,6 +39406,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -39385,9 +39432,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -39419,4 +39468,3 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0

View File

@@ -4758,7 +4758,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &9090909857404846383
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -4774,6 +4774,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -4795,9 +4800,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 1
m_MeshDistribution: 0
m_SortMode: 0
@@ -4829,7 +4836,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &2070592205952932412
GameObject:
m_ObjectHideFlags: 0
@@ -9588,7 +9594,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &9081262536151624848
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -9604,6 +9610,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -9625,9 +9636,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -9659,7 +9672,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &2261133155526879230
GameObject:
m_ObjectHideFlags: 0
@@ -14485,7 +14497,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &6151027602389049168
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -14501,6 +14513,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -14522,9 +14539,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -14556,7 +14575,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &2376255500188663908
GameObject:
m_ObjectHideFlags: 0
@@ -19315,7 +19333,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &1348402425390080252
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -19331,6 +19349,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -19352,9 +19375,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -19386,7 +19411,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &2943020494385716766
GameObject:
m_ObjectHideFlags: 0
@@ -19501,7 +19525,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -24151,7 +24175,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &4188768693027638627
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -24167,6 +24191,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -24188,9 +24217,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -24222,7 +24253,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &6573924625455929684
GameObject:
m_ObjectHideFlags: 0
@@ -28981,7 +29011,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3563924814195315285
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -28997,6 +29027,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -29018,9 +29053,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 1
m_MeshDistribution: 0
m_SortMode: 0
@@ -29052,7 +29089,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &6651680105352939543
GameObject:
m_ObjectHideFlags: 0
@@ -33869,7 +33905,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3958041096823129390
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -33885,6 +33921,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -33906,9 +33947,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -33940,4 +33983,3 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0

View File

@@ -4950,7 +4950,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3

View File

@@ -29255,7 +29255,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3

View File

@@ -108,7 +108,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -4816,7 +4816,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &5640736116712977215
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -4832,6 +4832,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -4853,9 +4858,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -4887,7 +4894,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &5249990677744981946
GameObject:
m_ObjectHideFlags: 0
@@ -4999,7 +5005,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -9649,7 +9655,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &4138720391616017682
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -9665,6 +9671,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -9686,9 +9697,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -9720,7 +9733,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &5931878544904699336
GameObject:
m_ObjectHideFlags: 0
@@ -9829,7 +9841,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -14546,7 +14558,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &5870414974609834262
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -14562,6 +14574,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -14583,9 +14600,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -14617,7 +14636,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &8633588085551914740
GameObject:
m_ObjectHideFlags: 0
@@ -14726,7 +14744,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -19452,7 +19470,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &1602668134873900334
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -19468,6 +19486,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -19489,9 +19512,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 4
m_MeshDistribution: 0
m_SortMode: 0
@@ -19523,4 +19548,3 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0

View File

@@ -108,7 +108,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -4825,7 +4825,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &4109529955070703249
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -4841,6 +4841,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -4862,9 +4867,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -4896,7 +4903,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &5480151176008203866
GameObject:
m_ObjectHideFlags: 0
@@ -5005,7 +5011,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -9731,7 +9737,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &5640841965484054353
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -9747,6 +9753,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -9768,9 +9779,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 4
m_MeshDistribution: 0
m_SortMode: 0
@@ -9802,7 +9815,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &8754511166712621810
GameObject:
m_ObjectHideFlags: 0
@@ -9913,7 +9925,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -14563,7 +14575,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &1014060429300395170
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -14579,6 +14591,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -14600,9 +14617,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -14634,4 +14653,3 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0

View File

@@ -108,7 +108,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -4825,7 +4825,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3594631519425132185
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -4841,6 +4841,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -4862,9 +4867,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 1
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -4896,7 +4903,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &3240066047640847786
GameObject:
m_ObjectHideFlags: 0
@@ -5005,7 +5011,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -9749,7 +9755,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &3141689248629385762
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -9765,6 +9771,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -9786,9 +9797,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 4
m_MeshDistribution: 0
m_SortMode: 0
@@ -9820,7 +9833,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &7671123070809976812
GameObject:
m_ObjectHideFlags: 0
@@ -9932,7 +9944,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -14582,7 +14594,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &8700396741641032723
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -14598,6 +14610,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -14619,9 +14636,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -14653,7 +14672,6 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!1 &7986537471941685033
GameObject:
m_ObjectHideFlags: 0
@@ -14762,7 +14780,7 @@ ParticleSystem:
m_RotationOrder: 4
moveWithTransform: 0
moveWithCustomTransform: {fileID: 0}
scalingMode: 1
scalingMode: 0
randomSeed: 0
InitialModule:
serializedVersion: 3
@@ -19470,7 +19488,7 @@ ParticleSystem:
vectorLabel1_3: W
--- !u!199 &1832114136647296338
ParticleSystemRenderer:
serializedVersion: 6
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@@ -19486,6 +19504,11 @@ ParticleSystemRenderer:
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -19507,9 +19530,11 @@ ParticleSystemRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_RenderMode: 0
m_MeshDistribution: 0
m_SortMode: 0
@@ -19541,4 +19566,3 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0

Some files were not shown because too many files have changed in this diff Show More