feat(fix-system): 重构打孔带流程并接入新收藏与插槽系统
Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using DG.Tweening;
|
||||
@@ -13,448 +12,459 @@ using AibisDream.Kit;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
public class MemoryProcess : MonoBehaviour
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
private const string MemorySpritePath = "Sprite/Memory/{0}.png";
|
||||
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
public GameObject memoryView;
|
||||
public GameObject projector;
|
||||
public SpriteRenderer memoryFinishedspRender; // 用于显示记忆处理结束的表现
|
||||
public SpriteRenderer memoryFinishedImageNoColor; // 用于显示记忆处理结束的表现
|
||||
|
||||
[SerializeField] private Volume MainEffect;
|
||||
[SerializeField] private Volume MemoryClarityVloume;
|
||||
[SerializeField] private Volume Memoryfrequencyloume;
|
||||
[SerializeField] private Volume MemoryProcessVloume;
|
||||
public Slider frequencySlider; // 频率调节滑动条
|
||||
|
||||
public Slider claritySlider; // 频率调节滑动条
|
||||
|
||||
public CanvasGroup playBackground;
|
||||
public float targetFrequency = 1.25f; // 目标频率
|
||||
|
||||
public float targetClarity = 5.7f; // 目标频率
|
||||
|
||||
private GameObject memoryGroup;
|
||||
private float lockThreshold = 0.06f; // 锁定阈值
|
||||
|
||||
private bool isProcessing = false; // 新增变量来追踪是否正在处理记忆
|
||||
private FMOD.Studio.EventInstance faderSoundInstance; // 添加音效实例变量
|
||||
private bool isPlayingFaderSound = false; // 追踪音效是否正在播放
|
||||
private float lastFrequencyValue; // 记录上一帧的频率值
|
||||
private float lastClarityValue; // 记录上一帧的清晰度值
|
||||
|
||||
private bool isColorful = false; //
|
||||
private bool isFrequencyFound = false; // 追踪是否找到目标频率
|
||||
private bool isClarityFound = false; // 追踪是否找到目标清晰度
|
||||
|
||||
private Material memoryMaterial; // 噪波材质
|
||||
|
||||
private PunchTape PunchTape;
|
||||
|
||||
private MemoryClueSlot clueSlot;
|
||||
|
||||
public Material fadeMaterial;
|
||||
|
||||
private Dictionary<PunchTape, bool> processedPunchTapes = new Dictionary<PunchTape, bool>();
|
||||
|
||||
public void OpenMemoryView()
|
||||
public class MemoryProcess : MonoBehaviour
|
||||
{
|
||||
memoryGroup.SetActive(true);
|
||||
memoryView.gameObject.SetActive(true);
|
||||
|
||||
}
|
||||
private const string MemorySpritePath = "Sprite/Memory/{0}.png";
|
||||
|
||||
public IEnumerator DropMemoryProjector()
|
||||
{
|
||||
DG.Tweening.Sequence zoomInSequence = DOTween.Sequence();
|
||||
zoomInSequence.Append(projector.transform.DOLocalMoveY(-5, 2f).SetEase(Ease.OutElastic, 0.5f));
|
||||
yield return zoomInSequence.WaitForCompletion();
|
||||
}
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
public GameObject memoryView;
|
||||
public GameObject projector;
|
||||
public SpriteRenderer memoryFinishedspRender; // 用于显示记忆处理结束的表现
|
||||
public SpriteRenderer memoryFinishedImageNoColor; // 用于显示记忆处理结束的表现
|
||||
|
||||
public void PullMemoryProjector()
|
||||
{
|
||||
DG.Tweening.Sequence zoomOutSequence = DOTween.Sequence();
|
||||
zoomOutSequence.Append(projector.transform.DOLocalMoveY(0, 2f).SetEase(Ease.OutElastic, 0.5f));
|
||||
//yield return zoomOutSequence.WaitForCompletion();
|
||||
}
|
||||
[SerializeField] private Volume MainEffect;
|
||||
[SerializeField] private Volume MemoryClarityVloume;
|
||||
[SerializeField] private Volume Memoryfrequencyloume;
|
||||
[SerializeField] private Volume MemoryProcessVloume;
|
||||
public Slider frequencySlider; // 频率调节滑动条
|
||||
|
||||
public void CloseMemoryView()
|
||||
{
|
||||
memoryView.gameObject.SetActive(false);
|
||||
memoryGroup.SetActive(false);
|
||||
}
|
||||
public Slider claritySlider; // 频率调节滑动条
|
||||
|
||||
public void OnEnter()
|
||||
{
|
||||
OpenMemoryView();
|
||||
clueSlot = FindObjectOfType<MemoryClueSlot>();
|
||||
FavoritesManager.Instance.OpenFavorites();
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
}
|
||||
public CanvasGroup playBackground;
|
||||
public float targetFrequency = 1.25f; // 目标频率
|
||||
|
||||
public void OnShow()
|
||||
{
|
||||
Debug.Log("memoryView showed.");
|
||||
clueSlot = FindObjectOfType<MemoryClueSlot>();
|
||||
FavoritesManager.Instance.OpenFavorites();
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
}
|
||||
public float targetClarity = 5.7f; // 目标频率
|
||||
|
||||
public void OnExit()
|
||||
{
|
||||
// 停止拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
private GameObject memoryGroup;
|
||||
private float lockThreshold = 0.06f; // 锁定阈值
|
||||
|
||||
private bool isProcessing = false; // 新增变量来追踪是否正在处理记忆
|
||||
private FMOD.Studio.EventInstance faderSoundInstance; // 添加音效实例变量
|
||||
private bool isPlayingFaderSound = false; // 追踪音效是否正在播放
|
||||
private float lastFrequencyValue; // 记录上一帧的频率值
|
||||
private float lastClarityValue; // 记录上一帧的清晰度值
|
||||
|
||||
private bool isColorful = false; //
|
||||
private bool isFrequencyFound = false; // 追踪是否找到目标频率
|
||||
private bool isClarityFound = false; // 追踪是否找到目标清晰度
|
||||
|
||||
private Material memoryMaterial; // 噪波材质
|
||||
|
||||
private PunchTape _activePunchTape;
|
||||
|
||||
[SerializeField] private MemoryPunchTapeSlot punchTapeSlot;
|
||||
|
||||
public Material fadeMaterial;
|
||||
|
||||
public void OpenMemoryView()
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
memoryGroup.SetActive(true);
|
||||
memoryView.gameObject.SetActive(true);
|
||||
|
||||
}
|
||||
|
||||
FavoritesManager.Instance.CloseFavorites();
|
||||
CloseMemoryView();
|
||||
// 离开视图时的逻辑
|
||||
Debug.Log("MemoryView exited.");
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitSystem();
|
||||
}
|
||||
|
||||
private void InitSystem()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
memoryGroup = transform.Find("Memory Group").gameObject;
|
||||
memoryGroup.SetActive(false);
|
||||
var virtualCam = transform.Find("Memory Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Memory, virtualCam);
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
float GetRandomNumber()
|
||||
{
|
||||
// 随机决定选择哪个区间
|
||||
if (UnityEngine.Random.value < 0.5f) // 50% 概率选择第一个区间
|
||||
public IEnumerator DropMemoryProjector()
|
||||
{
|
||||
return UnityEngine.Random.Range(0.5f, 2f);
|
||||
DG.Tweening.Sequence zoomInSequence = DOTween.Sequence();
|
||||
zoomInSequence.Append(projector.transform.DOLocalMoveY(-5, 2f).SetEase(Ease.OutElastic, 0.5f));
|
||||
yield return zoomInSequence.WaitForCompletion();
|
||||
}
|
||||
else // 50% 概率选择第二个区间
|
||||
|
||||
public void PullMemoryProjector()
|
||||
{
|
||||
return UnityEngine.Random.Range(4f, 5.5f);
|
||||
DG.Tweening.Sequence zoomOutSequence = DOTween.Sequence();
|
||||
zoomOutSequence.Append(projector.transform.DOLocalMoveY(0, 2f).SetEase(Ease.OutElastic, 0.5f));
|
||||
//yield return zoomOutSequence.WaitForCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
void OnFrequencyChanged(float value)
|
||||
{
|
||||
float distance = Mathf.Abs(value - targetFrequency);
|
||||
float maxDistance = Mathf.Max(
|
||||
Mathf.Abs(frequencySlider.maxValue - targetFrequency),
|
||||
Mathf.Abs(frequencySlider.minValue - targetFrequency)
|
||||
);
|
||||
float t = Mathf.Clamp01(distance / maxDistance);
|
||||
UpdateFrequencyShaderParameters(t);
|
||||
CheckIfFound(t, ref isFrequencyFound);
|
||||
|
||||
// 控制音效 - 使用更平滑的速度计算
|
||||
float currentSpeed = Mathf.Abs(value - lastFrequencyValue) / Time.deltaTime;
|
||||
lastFrequencyValue = value;
|
||||
// UpdateFaderSound(currentSpeed);
|
||||
}
|
||||
|
||||
void OnClarityChanged(float value)
|
||||
{
|
||||
float distance = Mathf.Abs(value - targetClarity);
|
||||
float maxDistance = Mathf.Max(
|
||||
Mathf.Abs(claritySlider.maxValue - targetClarity),
|
||||
Mathf.Abs(claritySlider.minValue - targetClarity)
|
||||
);
|
||||
float t = Mathf.Clamp01(distance / maxDistance);
|
||||
|
||||
UpdateClarityShaderParameters(t);
|
||||
CheckIfFound(t, ref isClarityFound);
|
||||
|
||||
// 控制音效 - 使用更平滑的速度计算
|
||||
float currentSpeed = Mathf.Abs(value - lastClarityValue) / Time.deltaTime;
|
||||
lastClarityValue = value;
|
||||
// UpdateFaderSound(currentSpeed);
|
||||
}
|
||||
|
||||
void OnSearchComplete()
|
||||
{
|
||||
Debug.Log("Memory search completed.");
|
||||
frequencySlider.interactable = false;
|
||||
claritySlider.interactable = false;
|
||||
|
||||
// 停止拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
public void CloseMemoryView()
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
memoryView.gameObject.SetActive(false);
|
||||
memoryGroup.SetActive(false);
|
||||
}
|
||||
|
||||
AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 0);
|
||||
StartCoroutine(PlayMemory());
|
||||
}
|
||||
|
||||
void CheckIfFound(float distance, ref bool isFound)
|
||||
{
|
||||
if (distance < lockThreshold)
|
||||
public void OnEnter()
|
||||
{
|
||||
isFound = true;
|
||||
// 检查是否两个条件都满足
|
||||
if (isFrequencyFound && isClarityFound)
|
||||
OpenMemoryView();
|
||||
EnsurePunchTapeSlotReference();
|
||||
PunchTapeManager.Instance.OpenFavoritesPanel();
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
}
|
||||
|
||||
public void OnShow()
|
||||
{
|
||||
Debug.Log("memoryView showed.");
|
||||
EnsurePunchTapeSlotReference();
|
||||
PunchTapeManager.Instance.OpenFavoritesPanel();
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
}
|
||||
|
||||
public void OnExit()
|
||||
{
|
||||
// 停止拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
{
|
||||
OnSearchComplete(); // 一旦条件满足,调用完成处理
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateFrequencyShaderParameters(float distance)
|
||||
{
|
||||
Memoryfrequencyloume.weight = Mathf.Lerp(0, 1, distance);
|
||||
}
|
||||
|
||||
void UpdateClarityShaderParameters(float t)
|
||||
{
|
||||
MemoryClarityVloume.weight = Mathf.Lerp(0, 1, t);
|
||||
}
|
||||
|
||||
public void TweenWeight(Volume postProcessingVolume, float targetWeight, float duration)
|
||||
{
|
||||
// 动态调整权重
|
||||
DOTween.To(() => postProcessingVolume.weight, x => postProcessingVolume.weight = x, targetWeight, duration);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
isFrequencyFound = false;
|
||||
isClarityFound = false;
|
||||
isProcessing = false;
|
||||
|
||||
// 停止拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
}
|
||||
|
||||
if (frequencySlider != null)
|
||||
{
|
||||
frequencySlider.onValueChanged.AddListener(OnFrequencyChanged);
|
||||
frequencySlider.gameObject.SetActive(false);
|
||||
frequencySlider.value = 3;
|
||||
lastFrequencyValue = frequencySlider.value;
|
||||
frequencySlider.interactable = true;
|
||||
// 添加EventTrigger监听
|
||||
var trigger = frequencySlider.gameObject.GetComponent<EventTrigger>() ?? frequencySlider.gameObject.AddComponent<EventTrigger>();
|
||||
var entryDown = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
|
||||
entryDown.callback.AddListener((data) => OnFaderPointerDown());
|
||||
trigger.triggers.Add(entryDown);
|
||||
var entryUp = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
|
||||
entryUp.callback.AddListener((data) => OnFaderPointerUp());
|
||||
trigger.triggers.Add(entryUp);
|
||||
}
|
||||
|
||||
if (claritySlider != null)
|
||||
{
|
||||
claritySlider.onValueChanged.AddListener(OnClarityChanged);
|
||||
claritySlider.gameObject.SetActive(false);
|
||||
claritySlider.value = 3;
|
||||
lastClarityValue = claritySlider.value;
|
||||
claritySlider.interactable = true;
|
||||
// 添加EventTrigger监听
|
||||
var trigger = claritySlider.gameObject.GetComponent<EventTrigger>() ?? claritySlider.gameObject.AddComponent<EventTrigger>();
|
||||
var entryDown = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
|
||||
entryDown.callback.AddListener((data) => OnFaderPointerDown());
|
||||
trigger.triggers.Add(entryDown);
|
||||
var entryUp = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
|
||||
entryUp.callback.AddListener((data) => OnFaderPointerUp());
|
||||
trigger.triggers.Add(entryUp);
|
||||
}
|
||||
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", -15f);
|
||||
memoryFinishedspRender.color = Color.black;
|
||||
MemoryProcessVloume.weight = 0;
|
||||
MemoryClarityVloume.weight = 0;
|
||||
Memoryfrequencyloume.weight = 0;
|
||||
PunchTape = null;
|
||||
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
}
|
||||
|
||||
[YarnCommand("MemoryFinished")]
|
||||
public IEnumerator MemoryFinished()
|
||||
{
|
||||
// AudioManager.PauseMemoryAudio();
|
||||
// Fade out alpha from 1 to 0
|
||||
yield return playBackground.DOFade(0f, 1f).WaitForCompletion(); // Fade to fully transparent over 1 second
|
||||
|
||||
// Deactivate playBackground after fade out
|
||||
playBackground.gameObject.SetActive(false);
|
||||
|
||||
FavoritesManager.Instance.OpenFavorites();
|
||||
|
||||
// Initialize and release slot
|
||||
Init();
|
||||
clueSlot.punchTape.used = true;
|
||||
clueSlot.ReleaseSlot();
|
||||
}
|
||||
|
||||
[YarnCommand("ColorFadeOut")]
|
||||
public IEnumerator ColorFadeOut()
|
||||
{
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", -15f);
|
||||
// AudioManager.PauseMemoryAudio();
|
||||
Tween tween = fadeMaterial.DOFloat(0, "_DirectionalDistortionFade", 3);
|
||||
yield return tween.WaitForCompletion();
|
||||
}
|
||||
|
||||
[YarnCommand("PlayMemory")]
|
||||
public IEnumerator PlayMemory(bool useYarn = false, string memName = "")
|
||||
{
|
||||
memoryFinishedspRender.color = Color.white;
|
||||
if (useYarn)
|
||||
{
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", -15);
|
||||
FavoritesManager.Instance.CloseFavorites();
|
||||
var memoryKey = string.Format(MemorySpritePath, memName);
|
||||
var memoryHandle = ResourceSystem.LoadAsync<Sprite>(memoryKey);
|
||||
yield return memoryHandle;
|
||||
Sprite memSprite = memoryHandle.Status == AsyncOperationStatus.Succeeded ? memoryHandle.Result : null;
|
||||
memoryFinishedspRender.sprite = memSprite;
|
||||
memoryFinishedImageNoColor.sprite = memSprite;
|
||||
if (memSprite == null)
|
||||
{
|
||||
Debug.LogError("Memory not found: " + memoryKey);
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
}
|
||||
|
||||
// AudioManager.PlayMemoryAudio("mem_" + memName);
|
||||
playBackground.gameObject.SetActive(true);
|
||||
//FavoritesManager.Instance.CloseFavorites();
|
||||
playBackground.alpha = 0;
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mem_play");
|
||||
yield return playBackground.DOFade(1f, 1f).WaitForCompletion(); // Fade to fully opaque over 1 second
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
CloseMemoryView();
|
||||
// 离开视图时的逻辑
|
||||
Debug.Log("MemoryView exited.");
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitSystem();
|
||||
}
|
||||
|
||||
private void EnsurePunchTapeSlotReference()
|
||||
{
|
||||
if (punchTapeSlot != null) return;
|
||||
punchTapeSlot = MemoryPunchTapeSlot.ActiveInstance;
|
||||
if (punchTapeSlot == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"[MemoryProcess] 未配置 MemoryPunchTapeSlot:请在 Inspector 将 punchTapeSlot 赋值为场景中的插槽,或确保存在 MemoryPunchTapeSlot。",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitSystem()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
memoryGroup = transform.Find("Memory Group").gameObject;
|
||||
memoryGroup.SetActive(false);
|
||||
var virtualCam = transform.Find("Memory Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.Memory, virtualCam);
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
float GetRandomNumber()
|
||||
{
|
||||
// 随机决定选择哪个区间
|
||||
if (UnityEngine.Random.value < 0.5f) // 50% 概率选择第一个区间
|
||||
{
|
||||
return UnityEngine.Random.Range(0.5f, 2f);
|
||||
}
|
||||
else // 50% 概率选择第二个区间
|
||||
{
|
||||
return UnityEngine.Random.Range(4f, 5.5f);
|
||||
}
|
||||
}
|
||||
|
||||
void OnFrequencyChanged(float value)
|
||||
{
|
||||
float distance = Mathf.Abs(value - targetFrequency);
|
||||
float maxDistance = Mathf.Max(
|
||||
Mathf.Abs(frequencySlider.maxValue - targetFrequency),
|
||||
Mathf.Abs(frequencySlider.minValue - targetFrequency)
|
||||
);
|
||||
float t = Mathf.Clamp01(distance / maxDistance);
|
||||
UpdateFrequencyShaderParameters(t);
|
||||
CheckIfFound(t, ref isFrequencyFound);
|
||||
|
||||
// 控制音效 - 使用更平滑的速度计算
|
||||
float currentSpeed = Mathf.Abs(value - lastFrequencyValue) / Time.deltaTime;
|
||||
lastFrequencyValue = value;
|
||||
// UpdateFaderSound(currentSpeed);
|
||||
}
|
||||
|
||||
void OnClarityChanged(float value)
|
||||
{
|
||||
float distance = Mathf.Abs(value - targetClarity);
|
||||
float maxDistance = Mathf.Max(
|
||||
Mathf.Abs(claritySlider.maxValue - targetClarity),
|
||||
Mathf.Abs(claritySlider.minValue - targetClarity)
|
||||
);
|
||||
float t = Mathf.Clamp01(distance / maxDistance);
|
||||
|
||||
UpdateClarityShaderParameters(t);
|
||||
CheckIfFound(t, ref isClarityFound);
|
||||
|
||||
// 控制音效 - 使用更平滑的速度计算
|
||||
float currentSpeed = Mathf.Abs(value - lastClarityValue) / Time.deltaTime;
|
||||
lastClarityValue = value;
|
||||
// UpdateFaderSound(currentSpeed);
|
||||
}
|
||||
|
||||
void OnSearchComplete()
|
||||
{
|
||||
Debug.Log("Memory search completed.");
|
||||
frequencySlider.interactable = false;
|
||||
claritySlider.interactable = false;
|
||||
|
||||
// 停止拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
}
|
||||
|
||||
AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 0);
|
||||
StartCoroutine(PlayMemory());
|
||||
}
|
||||
|
||||
void CheckIfFound(float distance, ref bool isFound)
|
||||
{
|
||||
if (distance < lockThreshold)
|
||||
{
|
||||
isFound = true;
|
||||
// 检查是否两个条件都满足
|
||||
if (isFrequencyFound && isClarityFound)
|
||||
{
|
||||
OnSearchComplete(); // 一旦条件满足,调用完成处理
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateFrequencyShaderParameters(float distance)
|
||||
{
|
||||
Memoryfrequencyloume.weight = Mathf.Lerp(0, 1, distance);
|
||||
}
|
||||
|
||||
void UpdateClarityShaderParameters(float t)
|
||||
{
|
||||
MemoryClarityVloume.weight = Mathf.Lerp(0, 1, t);
|
||||
}
|
||||
|
||||
public void TweenWeight(Volume postProcessingVolume, float targetWeight, float duration)
|
||||
{
|
||||
// 动态调整权重
|
||||
DOTween.To(() => postProcessingVolume.weight, x => postProcessingVolume.weight = x, targetWeight, duration);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
isFrequencyFound = false;
|
||||
isClarityFound = false;
|
||||
isProcessing = false;
|
||||
|
||||
// 停止拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
}
|
||||
|
||||
if (frequencySlider != null)
|
||||
{
|
||||
frequencySlider.onValueChanged.RemoveAllListeners();
|
||||
frequencySlider.onValueChanged.AddListener(OnFrequencyChanged);
|
||||
frequencySlider.gameObject.SetActive(false);
|
||||
frequencySlider.value = 3;
|
||||
lastFrequencyValue = frequencySlider.value;
|
||||
frequencySlider.interactable = true;
|
||||
// 添加EventTrigger监听(避免 Init 重复调用时叠加)
|
||||
var trigger = frequencySlider.gameObject.GetComponent<EventTrigger>() ?? frequencySlider.gameObject.AddComponent<EventTrigger>();
|
||||
trigger.triggers.Clear();
|
||||
var entryDown = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
|
||||
entryDown.callback.AddListener((data) => OnFaderPointerDown());
|
||||
trigger.triggers.Add(entryDown);
|
||||
var entryUp = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
|
||||
entryUp.callback.AddListener((data) => OnFaderPointerUp());
|
||||
trigger.triggers.Add(entryUp);
|
||||
}
|
||||
|
||||
if (claritySlider != null)
|
||||
{
|
||||
claritySlider.onValueChanged.RemoveAllListeners();
|
||||
claritySlider.onValueChanged.AddListener(OnClarityChanged);
|
||||
claritySlider.gameObject.SetActive(false);
|
||||
claritySlider.value = 3;
|
||||
lastClarityValue = claritySlider.value;
|
||||
claritySlider.interactable = true;
|
||||
var trigger = claritySlider.gameObject.GetComponent<EventTrigger>() ?? claritySlider.gameObject.AddComponent<EventTrigger>();
|
||||
trigger.triggers.Clear();
|
||||
var entryDown = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
|
||||
entryDown.callback.AddListener((data) => OnFaderPointerDown());
|
||||
trigger.triggers.Add(entryDown);
|
||||
var entryUp = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
|
||||
entryUp.callback.AddListener((data) => OnFaderPointerUp());
|
||||
trigger.triggers.Add(entryUp);
|
||||
}
|
||||
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", -15f);
|
||||
memoryFinishedspRender.color = Color.black;
|
||||
MemoryProcessVloume.weight = 0;
|
||||
MemoryClarityVloume.weight = 0;
|
||||
Memoryfrequencyloume.weight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
FavoritesManager.Instance.CloseFavorites();
|
||||
// Activate playBackground and fade in alpha from 0 to 1
|
||||
// AudioManager.PlayMemoryAudio("mem_" + clueSlot.punchTape.MemoryIndex);
|
||||
playBackground.gameObject.SetActive(true);
|
||||
//FavoritesManager.Instance.CloseFavorites();
|
||||
_activePunchTape = null;
|
||||
|
||||
playBackground.gameObject.SetActive(false);
|
||||
playBackground.alpha = 0;
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mem_play");
|
||||
yield return playBackground.DOFade(1f, 1f).WaitForCompletion(); // Fade to fully opaque over 1 second
|
||||
// Set material properties
|
||||
MemoryClarityVloume.weight = 0;
|
||||
Memoryfrequencyloume.weight = 0;
|
||||
DialogController.Instance.StartDialogNode(clueSlot.punchTape.YarnNodeName);
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("search_memory")]
|
||||
public IEnumerator SearchMemory(PunchTape punchTape, bool isSkipDialogue = false)
|
||||
{
|
||||
// 停止之前的拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
[YarnCommand("MemoryFinished")]
|
||||
public IEnumerator MemoryFinished()
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
// AudioManager.PauseMemoryAudio();
|
||||
// Fade out alpha from 1 to 0
|
||||
yield return playBackground.DOFade(0f, 1f).WaitForCompletion(); // Fade to fully transparent over 1 second
|
||||
|
||||
// Deactivate playBackground after fade out
|
||||
playBackground.gameObject.SetActive(false);
|
||||
|
||||
PunchTapeManager.Instance.OpenFavoritesPanel();
|
||||
|
||||
// Initialize and release slot
|
||||
Init();
|
||||
EnsurePunchTapeSlotReference();
|
||||
if (punchTapeSlot != null && punchTapeSlot.punchTape != null)
|
||||
{
|
||||
punchTapeSlot.punchTape.used = true;
|
||||
punchTapeSlot.ReleaseSlot();
|
||||
}
|
||||
}
|
||||
|
||||
targetClarity = GetRandomNumber();
|
||||
targetFrequency = GetRandomNumber();
|
||||
FavoritesManager.Instance.CloseFavorites();
|
||||
//FavoritesManager.Instance.CloseFavorites();
|
||||
if (punchTape == null)
|
||||
|
||||
[YarnCommand("ColorFadeOut")]
|
||||
public IEnumerator ColorFadeOut()
|
||||
{
|
||||
Debug.Log("no punchTape");
|
||||
yield break;
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", -15f);
|
||||
// AudioManager.PauseMemoryAudio();
|
||||
Tween tween = fadeMaterial.DOFloat(0, "_DirectionalDistortionFade", 3);
|
||||
yield return tween.WaitForCompletion();
|
||||
}
|
||||
|
||||
PunchTape = punchTape;
|
||||
|
||||
// AudioManager.PauseMemoryAudio();
|
||||
if (isProcessing)
|
||||
[YarnCommand("PlayMemory")]
|
||||
public IEnumerator PlayMemory(bool useYarn = false, string memName = "")
|
||||
{
|
||||
Debug.LogWarning("Already processing a memory. Please wait until the process is finished.");
|
||||
yield break;
|
||||
memoryFinishedspRender.color = Color.white;
|
||||
if (useYarn)
|
||||
{
|
||||
fadeMaterial.SetFloat("_DirectionalDistortionFade", -15);
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
var memoryKey = string.Format(MemorySpritePath, memName);
|
||||
var memoryHandle = ResourceSystem.LoadAsync<Sprite>(memoryKey);
|
||||
yield return memoryHandle;
|
||||
Sprite memSprite = memoryHandle.Status == AsyncOperationStatus.Succeeded ? memoryHandle.Result : null;
|
||||
memoryFinishedspRender.sprite = memSprite;
|
||||
memoryFinishedImageNoColor.sprite = memSprite;
|
||||
if (memSprite == null)
|
||||
{
|
||||
Debug.LogError("Memory not found: " + memoryKey);
|
||||
}
|
||||
|
||||
// AudioManager.PlayMemoryAudio("mem_" + memName);
|
||||
playBackground.gameObject.SetActive(true);
|
||||
//PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
playBackground.alpha = 0;
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mem_play");
|
||||
yield return playBackground.DOFade(1f, 1f).WaitForCompletion(); // Fade to fully opaque over 1 second
|
||||
MemoryClarityVloume.weight = 0;
|
||||
Memoryfrequencyloume.weight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
// Activate playBackground and fade in alpha from 0 to 1
|
||||
// AudioManager.PlayMemoryAudio("mem_" + punchTapeSlot.punchTape.MemoryIndex);
|
||||
playBackground.gameObject.SetActive(true);
|
||||
//PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
playBackground.alpha = 0;
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mem_play");
|
||||
yield return playBackground.DOFade(1f, 1f).WaitForCompletion(); // Fade to fully opaque over 1 second
|
||||
// Set material properties
|
||||
MemoryClarityVloume.weight = 0;
|
||||
Memoryfrequencyloume.weight = 0;
|
||||
EnsurePunchTapeSlotReference();
|
||||
if (punchTapeSlot == null || punchTapeSlot.punchTape == null)
|
||||
{
|
||||
Debug.LogError("[MemoryProcess] PlayMemory:未配置插槽或打孔带数据缺失。", this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
DialogController.Instance.StartDialogNode(
|
||||
LocalizationKit.GetL10NParamKey(punchTapeSlot.punchTape.ClueName));
|
||||
}
|
||||
}
|
||||
|
||||
var searchMemoryKey = string.Format(MemorySpritePath, PunchTape.MemoryIndex);
|
||||
var searchMemoryHandle = ResourceSystem.LoadAsync<Sprite>(searchMemoryKey);
|
||||
yield return searchMemoryHandle;
|
||||
Sprite memorySprite = searchMemoryHandle.Status == AsyncOperationStatus.Succeeded
|
||||
? searchMemoryHandle.Result
|
||||
: null;
|
||||
memoryFinishedspRender.sprite = memorySprite;
|
||||
memoryFinishedImageNoColor.sprite = memorySprite;
|
||||
if (memorySprite == null)
|
||||
[YarnCommand("search_memory")]
|
||||
public IEnumerator SearchMemory(PunchTape punchTape, bool isSkipDialogue = false)
|
||||
{
|
||||
Debug.LogError("Memory not found: " + searchMemoryKey);
|
||||
// 停止之前的拉条音频
|
||||
if (isPlayingFaderSound)
|
||||
{
|
||||
AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
isPlayingFaderSound = false;
|
||||
}
|
||||
|
||||
targetClarity = GetRandomNumber();
|
||||
targetFrequency = GetRandomNumber();
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
//PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
if (punchTape == null)
|
||||
{
|
||||
Debug.Log("no punchTape");
|
||||
yield break;
|
||||
}
|
||||
|
||||
_activePunchTape = punchTape;
|
||||
|
||||
// AudioManager.PauseMemoryAudio();
|
||||
if (isProcessing)
|
||||
{
|
||||
Debug.LogWarning("Already processing a memory. Please wait until the process is finished.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var searchMemoryKey = string.Format(MemorySpritePath, _activePunchTape.MemoryIndex);
|
||||
var searchMemoryHandle = ResourceSystem.LoadAsync<Sprite>(searchMemoryKey);
|
||||
yield return searchMemoryHandle;
|
||||
Sprite memorySprite = searchMemoryHandle.Status == AsyncOperationStatus.Succeeded
|
||||
? searchMemoryHandle.Result
|
||||
: null;
|
||||
memoryFinishedspRender.sprite = memorySprite;
|
||||
memoryFinishedImageNoColor.sprite = memorySprite;
|
||||
if (memorySprite == null)
|
||||
{
|
||||
Debug.LogError("Memory not found: " + searchMemoryKey);
|
||||
}
|
||||
|
||||
MemoryProcessVloume.weight = 1;
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/mem_load");
|
||||
|
||||
yield return new WaitForSeconds(1f);
|
||||
AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 1);
|
||||
|
||||
|
||||
memoryFinishedspRender.color = Color.white;
|
||||
OnFrequencyChanged(frequencySlider.value);
|
||||
OnClarityChanged(claritySlider.value);
|
||||
MemoryProcessVloume.weight = 0;
|
||||
isProcessing = true; // 开始处理,设置为true
|
||||
frequencySlider.gameObject.SetActive(true);
|
||||
claritySlider.gameObject.SetActive(true);
|
||||
DG.Tweening.Sequence memorySequence = DOTween.Sequence();
|
||||
yield return memorySequence.WaitForCompletion();
|
||||
}
|
||||
|
||||
MemoryProcessVloume.weight = 1;
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/mem_load");
|
||||
|
||||
yield return new WaitForSeconds(1f);
|
||||
AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 1);
|
||||
public bool IsProcessing()
|
||||
{
|
||||
return isProcessing;
|
||||
}
|
||||
|
||||
private void OnFaderPointerDown()
|
||||
{
|
||||
}
|
||||
|
||||
memoryFinishedspRender.color = Color.white;
|
||||
OnFrequencyChanged(frequencySlider.value);
|
||||
OnClarityChanged(claritySlider.value);
|
||||
MemoryProcessVloume.weight = 0;
|
||||
isProcessing = true; // 开始处理,设置为true
|
||||
frequencySlider.gameObject.SetActive(true);
|
||||
claritySlider.gameObject.SetActive(true);
|
||||
DG.Tweening.Sequence memorySequence = DOTween.Sequence();
|
||||
yield return memorySequence.WaitForCompletion();
|
||||
private void OnFaderPointerUp()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsProcessing()
|
||||
{
|
||||
return isProcessing;
|
||||
}
|
||||
|
||||
private void OnFaderPointerDown()
|
||||
{
|
||||
// if (!isPlayingFaderSound)
|
||||
// {
|
||||
// AudioManager.Instance.PlaySfx("event:/ActionFB/mem_fader");
|
||||
// isPlayingFaderSound = true;
|
||||
// }
|
||||
}
|
||||
|
||||
private void OnFaderPointerUp()
|
||||
{
|
||||
// if (isPlayingFaderSound)
|
||||
// {
|
||||
// AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader");
|
||||
// isPlayingFaderSound = false;
|
||||
// }
|
||||
}
|
||||
|
||||
// private void UpdateFaderSound(float speed)
|
||||
// {
|
||||
// // 速度归一化,最大速度可根据实际情况调整
|
||||
// float normalizedSpeed = Mathf.Clamp01(speed / 10f);
|
||||
// AudioManager.Instance.SetSfxParam("event:/ActionFB/mem_fader", "mem_fader_speed", normalizedSpeed);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using AibisDream;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Kit;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 记忆流程中的打孔带投放槽:胶片动画、投放逻辑;插入后触发 <see cref="MemoryProcess.SearchMemory"/>。
|
||||
/// </summary>
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class MemoryPunchTapeSlot : MonoBehaviour
|
||||
{
|
||||
/// <summary>当前场景中的记忆插槽(用于 MemoryProcess 等避免 FindObjectOfType)。</summary>
|
||||
public static MemoryPunchTapeSlot ActiveInstance { get; private set; }
|
||||
|
||||
public Image filmImage;
|
||||
public Image RoateImage;
|
||||
|
||||
private Vector3 filmStartLocalPosition;
|
||||
private PunchTapeInteraction interaction;
|
||||
|
||||
[HideInInspector]
|
||||
public PunchTapeItem currentPunchTapeItem;
|
||||
public Image punchTapeImage;
|
||||
public Image coverClose;
|
||||
public Image coverOpen;
|
||||
|
||||
[HideInInspector]
|
||||
public bool isOccupied = false;
|
||||
|
||||
[HideInInspector]
|
||||
public bool isLocked = false;
|
||||
|
||||
private MemoryProcess memoryManager;
|
||||
[HideInInspector] public PunchTape punchTape;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (ActiveInstance != null && ActiveInstance != this)
|
||||
{
|
||||
Debug.LogWarning("[MemoryPunchTapeSlot] 场景中存在多个 MemoryPunchTapeSlot,ActiveInstance 将保留最先 Awake 的实例。", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveInstance = this;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (ActiveInstance == this)
|
||||
ActiveInstance = null;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
PunchTapeFavoritesDragEvents.DragStarted += OnFavoriteDragStarted;
|
||||
PunchTapeFavoritesDragEvents.DragEnded += OnFavoriteDragEnded;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
PunchTapeFavoritesDragEvents.DragStarted -= OnFavoriteDragStarted;
|
||||
PunchTapeFavoritesDragEvents.DragEnded -= OnFavoriteDragEnded;
|
||||
}
|
||||
|
||||
private void OnFavoriteDragStarted()
|
||||
{
|
||||
if (isOccupied) return;
|
||||
coverOpen.enabled = true;
|
||||
coverClose.enabled = false;
|
||||
}
|
||||
|
||||
private void OnFavoriteDragEnded()
|
||||
{
|
||||
if (isOccupied) return;
|
||||
coverOpen.enabled = false;
|
||||
coverClose.enabled = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
currentPunchTapeItem = null;
|
||||
coverClose.enabled = true;
|
||||
coverOpen.enabled = false;
|
||||
punchTapeImage.enabled = false;
|
||||
filmStartLocalPosition = filmImage.rectTransform.localPosition;
|
||||
|
||||
memoryManager = FixSystemCenter.SystemDic.Get<MemoryProcess>();
|
||||
if (memoryManager == null)
|
||||
Debug.LogWarning("[MemoryPunchTapeSlot] 未在 FixSystemCenter.SystemDic 中注册 MemoryProcess。", this);
|
||||
}
|
||||
|
||||
public void SetSlotCorrect()
|
||||
{
|
||||
SetSlotLockState(true);
|
||||
}
|
||||
|
||||
public bool TryAcceptDrop(PunchTapeItem item)
|
||||
{
|
||||
if (isOccupied || item == null) return false;
|
||||
|
||||
PunchTape droppedTape = item.GetPunchTapeData();
|
||||
if (droppedTape == null) return false;
|
||||
|
||||
interaction = item.GetComponent<PunchTapeInteraction>();
|
||||
currentPunchTapeItem = item;
|
||||
isOccupied = true;
|
||||
punchTapeImage.enabled = true;
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mem_insert");
|
||||
StartFilmAnimation();
|
||||
PunchTapeManager.Instance.CloseFavoritesPanel();
|
||||
punchTapeImage.rectTransform.DOAnchorPosY(-473f, 4f)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
punchTapeImage.enabled = false;
|
||||
punchTapeImage.rectTransform.DOAnchorPosY(-3f, 0.1f);
|
||||
OnPunchTapeInsertedAfterAnimation(droppedTape);
|
||||
StopFilmAnimation();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReleaseSlot()
|
||||
{
|
||||
Debug.Log("释放记忆插槽");
|
||||
coverClose.enabled = false;
|
||||
coverOpen.enabled = true;
|
||||
isOccupied = false;
|
||||
currentPunchTapeItem = null;
|
||||
interaction = null;
|
||||
filmImage.rectTransform.localPosition = filmStartLocalPosition;
|
||||
}
|
||||
|
||||
private void OnPunchTapeInsertedAfterAnimation(PunchTape tape)
|
||||
{
|
||||
punchTape = tape;
|
||||
coverClose.enabled = true;
|
||||
coverOpen.enabled = false;
|
||||
PlayMemory(punchTape);
|
||||
PunchTapeManager.Instance.RemovePunchTape(tape);
|
||||
}
|
||||
|
||||
private void PlayMemory(PunchTape tape)
|
||||
{
|
||||
if (tape != null)
|
||||
{
|
||||
Debug.Log("播放记忆:" + tape.MemoryIndex);
|
||||
if (memoryManager != null)
|
||||
memoryManager.StartCoroutine(memoryManager.SearchMemory(tape));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSlotLockState(bool lockState)
|
||||
{
|
||||
isLocked = lockState;
|
||||
}
|
||||
|
||||
private void StartFilmAnimation()
|
||||
{
|
||||
filmImage.rectTransform.DOLocalMoveX(-494f, 1f)
|
||||
.SetLoops(-1, LoopType.Restart)
|
||||
.SetEase(Ease.Linear);
|
||||
RoateImage.rectTransform.DORotate(new Vector3(0, 0, 360), 1f, RotateMode.FastBeyond360)
|
||||
.SetLoops(-1, LoopType.Restart)
|
||||
.SetEase(Ease.Linear);
|
||||
}
|
||||
|
||||
private void StopFilmAnimation()
|
||||
{
|
||||
filmImage.rectTransform.DOKill();
|
||||
RoateImage.rectTransform.DOKill();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收藏夹内打孔带拖拽生命周期事件,供插槽等监听以切换封面表现,避免 UI Item 直接引用插槽。
|
||||
/// </summary>
|
||||
public static class PunchTapeFavoritesDragEvents
|
||||
{
|
||||
public static event System.Action DragStarted;
|
||||
public static event System.Action DragEnded;
|
||||
|
||||
public static void NotifyDragStarted() => DragStarted?.Invoke();
|
||||
|
||||
public static void NotifyDragEnded() => DragEnded?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08ace7d1cbb872b49a73a914f1f93b10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// 打孔带数据:用于记忆系统的资源键与对话节点,不依赖已废弃的 Clue 体系。
|
||||
/// 仅保留可序列化字段;本地化与 Yarn 键由 UI / 流程层调用 LocalizationKit 处理。
|
||||
/// </summary>
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
[Serializable]
|
||||
public class PunchTape : IEquatable<PunchTape>
|
||||
{
|
||||
public string Category;
|
||||
public string ClueName;
|
||||
public string MemoryIndex;
|
||||
public bool used;
|
||||
|
||||
public PunchTape()
|
||||
{
|
||||
}
|
||||
|
||||
public PunchTape(string category, string name, string memoryIndex)
|
||||
{
|
||||
Category = category;
|
||||
ClueName = name;
|
||||
MemoryIndex = memoryIndex;
|
||||
}
|
||||
|
||||
/// <summary>用于收藏列表去重与 UI 行映射(与 Equals 一致)。</summary>
|
||||
public string GetRowKey()
|
||||
{
|
||||
return $"{Category ?? string.Empty}\u001f{ClueName ?? string.Empty}\u001f{MemoryIndex ?? string.Empty}";
|
||||
}
|
||||
|
||||
public bool Equals(PunchTape other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return string.Equals(Category, other.Category, StringComparison.Ordinal) &&
|
||||
string.Equals(ClueName, other.ClueName, StringComparison.Ordinal) &&
|
||||
string.Equals(MemoryIndex, other.MemoryIndex, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as PunchTape);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hc = (Category != null ? StringComparer.Ordinal.GetHashCode(Category) : 0);
|
||||
hc = (hc * 397) ^ (ClueName != null ? StringComparer.Ordinal.GetHashCode(ClueName) : 0);
|
||||
hc = (hc * 397) ^ (MemoryIndex != null ? StringComparer.Ordinal.GetHashCode(MemoryIndex) : 0);
|
||||
return hc;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(PunchTape left, PunchTape right)
|
||||
{
|
||||
if (left is null) return right is null;
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PunchTape left, PunchTape right) => !(left == right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4e3d2c1b0a9f8e7d6c5b4a3210fed98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,191 @@
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Kit;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 打孔带收藏夹视图。侧栏(<see cref="favoritesUI"/> 上的 RectTransform)做滑入滑出;
|
||||
/// 可选的 <see cref="punchTapePanel"/> 仅显隐,与侧栏同开同关。数据来自 <see cref="PunchTapeManager"/>。
|
||||
/// </summary>
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class PunchTapeFavoritesPanel : MonoBehaviour
|
||||
{
|
||||
private const float PanelSlideDuration = 0.5f;
|
||||
|
||||
[Header("收藏夹 UI")]
|
||||
[Tooltip("列表项父节点(Vertical Layout 等)")]
|
||||
[SerializeField] private Transform punchTapeContent;
|
||||
|
||||
[SerializeField] private GameObject punchTapeItemPrefab;
|
||||
|
||||
[Tooltip("配套列表面板")]
|
||||
[SerializeField] private RectTransform punchTapePanel;
|
||||
|
||||
[Header("定位信息")]
|
||||
[SerializeField] private Vector2 panelHiddenAnchoredPos;
|
||||
[SerializeField] private Vector2 panelVisibleAnchoredPos;
|
||||
|
||||
private SimpleObjectPool<GameObject> _rowPool;
|
||||
private readonly Dictionary<string, GameObject> _rowByTapeKey = new();
|
||||
|
||||
private PunchTapeManager PunchTapeManager => PunchTapeManager.Instance;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (punchTapeContent == null)
|
||||
{
|
||||
Debug.LogError("[PunchTapeFavoritesPanel] 未指定 punchTapeContent。", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
if (punchTapeItemPrefab == null)
|
||||
{
|
||||
Debug.LogError("[PunchTapeFavoritesPanel] 未指定 punchTapeItemPrefab。", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PunchTapeManager.OnCollectionChanged += HandleCollectionChanged;
|
||||
ApplyClosedLayout();
|
||||
SyncListToData();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (PunchTapeManager != null)
|
||||
PunchTapeManager.OnCollectionChanged -= HandleCollectionChanged;
|
||||
|
||||
punchTapePanel?.DOKill();
|
||||
|
||||
DisposeRowPool();
|
||||
_rowByTapeKey.Clear();
|
||||
}
|
||||
|
||||
private void HandleCollectionChanged() => SyncListToData();
|
||||
|
||||
private void DisposeRowPool()
|
||||
{
|
||||
if (_rowPool == null)
|
||||
return;
|
||||
|
||||
_rowPool.Clear(Destroy);
|
||||
_rowPool = null;
|
||||
}
|
||||
|
||||
private void EnsureRowPool()
|
||||
{
|
||||
if (_rowPool != null)
|
||||
return;
|
||||
|
||||
_rowPool = new SimpleObjectPool<GameObject>(
|
||||
() =>
|
||||
{
|
||||
var go = Instantiate(punchTapeItemPrefab, punchTapeContent);
|
||||
go.SetActive(false);
|
||||
return go;
|
||||
},
|
||||
go =>
|
||||
{
|
||||
go.SetActive(false);
|
||||
go.transform.SetParent(punchTapeContent, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void SyncListToData()
|
||||
{
|
||||
EnsureRowPool();
|
||||
|
||||
var favorites = PunchTapeManager.GetFavorites();
|
||||
RemoveRowsNotInFavorites(favorites);
|
||||
EnsureRowsForFavorites(favorites);
|
||||
}
|
||||
|
||||
private void RemoveRowsNotInFavorites(IReadOnlyList<PunchTape> favorites)
|
||||
{
|
||||
var keepKeys = new HashSet<string>();
|
||||
for (var i = 0; i < favorites.Count; i++)
|
||||
keepKeys.Add(favorites[i].GetRowKey());
|
||||
|
||||
var staleKeys = new List<string>();
|
||||
foreach (var key in _rowByTapeKey.Keys)
|
||||
{
|
||||
if (!keepKeys.Contains(key))
|
||||
staleKeys.Add(key);
|
||||
}
|
||||
|
||||
foreach (var key in staleKeys)
|
||||
{
|
||||
if (_rowByTapeKey.TryGetValue(key, out var go) && go != null)
|
||||
_rowPool.Recycle(go);
|
||||
_rowByTapeKey.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureRowsForFavorites(IReadOnlyList<PunchTape> favorites)
|
||||
{
|
||||
for (var i = 0; i < favorites.Count; i++)
|
||||
{
|
||||
var tape = favorites[i];
|
||||
var key = tape.GetRowKey();
|
||||
|
||||
if (!_rowByTapeKey.TryGetValue(key, out var rowGo) || rowGo == null)
|
||||
{
|
||||
rowGo = _rowPool.Allocate();
|
||||
rowGo.transform.SetParent(punchTapeContent, false);
|
||||
_rowByTapeKey[key] = rowGo;
|
||||
}
|
||||
|
||||
BindRow(rowGo, tape);
|
||||
rowGo.SetActive(true);
|
||||
rowGo.transform.SetSiblingIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BindRow(GameObject rowGo, PunchTape tape)
|
||||
{
|
||||
var interaction = rowGo.GetComponent<PunchTapeInteraction>();
|
||||
if (interaction != null)
|
||||
{
|
||||
interaction.SetIsLock(false);
|
||||
interaction.SetIsFavorited(true);
|
||||
}
|
||||
|
||||
var item = rowGo.GetComponent<PunchTapeItem>();
|
||||
if (item != null)
|
||||
item.Setup(tape);
|
||||
}
|
||||
|
||||
private void ApplyClosedLayout()
|
||||
{
|
||||
punchTapePanel.anchoredPosition = panelHiddenAnchoredPos;
|
||||
|
||||
punchTapePanel.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>侧栏滑入并显示;<see cref="punchTapePanel"/> 若有则一并显示。</summary>
|
||||
public void OpenPanel()
|
||||
{
|
||||
SyncListToData();
|
||||
|
||||
punchTapePanel.gameObject.SetActive(true);
|
||||
|
||||
punchTapePanel.DOKill();
|
||||
punchTapePanel.DOAnchorPos(panelVisibleAnchoredPos, PanelSlideDuration);
|
||||
}
|
||||
|
||||
/// <summary>侧栏滑出,结束后侧栏与 <see cref="punchTapePanel"/> 一并隐藏。</summary>
|
||||
public void ClosePanel()
|
||||
{
|
||||
punchTapePanel.DOKill();
|
||||
punchTapePanel
|
||||
.DOAnchorPos(panelHiddenAnchoredPos, PanelSlideDuration)
|
||||
.OnComplete(OnSidebarClosed);
|
||||
}
|
||||
|
||||
private void OnSidebarClosed()
|
||||
{
|
||||
punchTapePanel.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a84298a2467e2934fa5e8c60405a2834
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
/// <summary>
|
||||
/// 打孔带在 UI 中的拖拽与点击交互。
|
||||
/// </summary>
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class PunchTapeInteraction : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler,
|
||||
IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
private Transform originalParent;
|
||||
public Transform OriginalParent => originalParent;
|
||||
private Canvas canvas;
|
||||
[HideInInspector]
|
||||
public bool isFavorited = false;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Vector2 originalAnchorMin;
|
||||
private Vector2 originalAnchorMax;
|
||||
private Vector2 originalOffsetMin;
|
||||
private Vector2 originalOffsetMax;
|
||||
private Vector2 originalPivot;
|
||||
|
||||
private CursorMode cursorMode = CursorMode.Auto;
|
||||
|
||||
public event Action OnInteraction;
|
||||
|
||||
private bool isLocked = false;
|
||||
private bool _favoriteDragActive;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
canvas = GetComponentInParent<Canvas>();
|
||||
StoreOriginalTransform();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_favoriteDragActive)
|
||||
{
|
||||
_favoriteDragActive = false;
|
||||
PunchTapeFavoritesDragEvents.NotifyDragEnded();
|
||||
}
|
||||
}
|
||||
|
||||
private void StoreOriginalTransform()
|
||||
{
|
||||
originalParent = transform.parent;
|
||||
originalAnchorMin = rectTransform.anchorMin;
|
||||
originalAnchorMax = rectTransform.anchorMax;
|
||||
originalOffsetMin = rectTransform.offsetMin;
|
||||
originalOffsetMax = rectTransform.offsetMax;
|
||||
originalPivot = rectTransform.pivot;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
Cursor.SetCursor(null, Vector2.zero, cursorMode);
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!isFavorited || isLocked) return;
|
||||
|
||||
transform.SetParent(canvas.transform, true);
|
||||
transform.SetAsLastSibling();
|
||||
var cg = GetComponent<CanvasGroup>();
|
||||
if (cg != null) cg.blocksRaycasts = false;
|
||||
_favoriteDragActive = true;
|
||||
PunchTapeFavoritesDragEvents.NotifyDragStarted();
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!isFavorited || isLocked) return;
|
||||
|
||||
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint))
|
||||
{
|
||||
rectTransform.localPosition = new Vector3(localPoint.x, localPoint.y + 200f, 0f);
|
||||
}
|
||||
transform.rotation = Quaternion.Euler(0, 0, -90f);
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!isFavorited || isLocked) return;
|
||||
|
||||
var cg = GetComponent<CanvasGroup>();
|
||||
if (cg != null) cg.blocksRaycasts = true;
|
||||
|
||||
var slot = eventData.pointerEnter?
|
||||
.GetComponentInParent<MemoryPunchTapeSlot>();
|
||||
var item = GetComponent<PunchTapeItem>();
|
||||
|
||||
bool accepted = slot != null && slot.TryAcceptDrop(item);
|
||||
|
||||
if (_favoriteDragActive)
|
||||
{
|
||||
_favoriteDragActive = false;
|
||||
PunchTapeFavoritesDragEvents.NotifyDragEnded();
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
Destroy(gameObject);
|
||||
else
|
||||
ReturnToOriginalPosition();
|
||||
}
|
||||
|
||||
public void ReturnToOriginalPosition()
|
||||
{
|
||||
isLocked = false;
|
||||
transform.rotation = Quaternion.Euler(0, 0, 0);
|
||||
transform.SetParent(originalParent, false);
|
||||
rectTransform.anchorMin = originalAnchorMin;
|
||||
rectTransform.anchorMax = originalAnchorMax;
|
||||
rectTransform.offsetMin = originalOffsetMin;
|
||||
rectTransform.offsetMax = originalOffsetMax;
|
||||
rectTransform.pivot = originalPivot;
|
||||
}
|
||||
|
||||
public void SetIsFavorited(bool favorited)
|
||||
{
|
||||
isFavorited = favorited;
|
||||
}
|
||||
|
||||
public void SetIsLock(bool locked)
|
||||
{
|
||||
isLocked = locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d792e0af9bafc174d8a2566cf81b2c6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Localization.Components;
|
||||
using AibisDream.Framework;
|
||||
|
||||
/// <summary>
|
||||
/// 收藏夹 / 生成窗口中的打孔带 UI 项。
|
||||
/// </summary>
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class PunchTapeItem : MonoBehaviour
|
||||
{
|
||||
public Image clueImageUI;
|
||||
public TMP_Text categoryText;
|
||||
public LocalizeStringEvent nameText;
|
||||
public GameObject stateText;
|
||||
|
||||
[HideInInspector]
|
||||
public bool used = false;
|
||||
|
||||
private PunchTape punchTapeData;
|
||||
|
||||
public GameObject finished;
|
||||
|
||||
public void Setup(PunchTape punchTape)
|
||||
{
|
||||
punchTapeData = punchTape;
|
||||
used = punchTape != null && punchTape.used;
|
||||
ApplyDisplay(punchTapeData);
|
||||
}
|
||||
|
||||
private void ApplyDisplay(PunchTape pt)
|
||||
{
|
||||
if (pt == null)
|
||||
{
|
||||
Debug.LogWarning("PunchTape is null. Display aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (clueImageUI != null && pt.used)
|
||||
clueImageUI.color = Color.grey;
|
||||
|
||||
if (stateText != null)
|
||||
stateText.SetActive(pt.used);
|
||||
|
||||
if (categoryText != null)
|
||||
categoryText.text = "分类:" + pt.Category;
|
||||
if (nameText != null)
|
||||
{
|
||||
nameText.StringReference.Arguments = new object[] { LocalizationKit.LocalizeParam(pt.ClueName) };
|
||||
nameText.RefreshString();
|
||||
}
|
||||
}
|
||||
|
||||
public PunchTape GetPunchTapeData()
|
||||
{
|
||||
return punchTapeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d5f46e915536ee40a8c605a443e9584
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
using AibisDream.Framework;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 打孔带系统入口:收藏数据、存档注册、Yarn 命令、弹窗与收藏夹面板代理。
|
||||
/// </summary>
|
||||
public class PunchTapeManager : Singleton<PunchTapeManager>
|
||||
{
|
||||
public GameObject punchTapecluePrefab;
|
||||
|
||||
[SerializeField] private PunchTapeFavoritesPanel favoritesPanel;
|
||||
[SerializeField] private PunchTapePrinter punchTapePrinter;
|
||||
public PunchTapePrinter PunchTapePrinter => punchTapePrinter;
|
||||
|
||||
private readonly PunchTapeCollectionData _collectionData = new();
|
||||
|
||||
private GameObject currentPunchTapeGO;
|
||||
|
||||
/// <summary>收藏列表变更时通知 <see cref="PunchTapeFavoritesPanel"/> 等订阅方。</summary>
|
||||
public event Action OnCollectionChanged;
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
StorageSystem.Instance.RegisterData(_collectionData);
|
||||
_collectionData.LoadEvent += OnCollectionDataLoaded;
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
_collectionData.LoadEvent -= OnCollectionDataLoaded;
|
||||
}
|
||||
|
||||
private void OnCollectionDataLoaded(PunchTapeCollectionData data)
|
||||
{
|
||||
OnCollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public IReadOnlyList<PunchTape> GetFavorites()
|
||||
{
|
||||
return _collectionData.punchTapeFavorites;
|
||||
}
|
||||
|
||||
public void AddPunchTape(PunchTape punchTape)
|
||||
{
|
||||
if (punchTape == null) return;
|
||||
if (_collectionData.punchTapeFavorites.Contains(punchTape))
|
||||
return;
|
||||
|
||||
_collectionData.punchTapeFavorites.Add(punchTape);
|
||||
OnCollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void RemovePunchTape(PunchTape punchTape)
|
||||
{
|
||||
if (punchTape == null) return;
|
||||
if (!_collectionData.punchTapeFavorites.Remove(punchTape))
|
||||
return;
|
||||
|
||||
OnCollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void OpenFavoritesPanel()
|
||||
{
|
||||
if (favoritesPanel == null)
|
||||
{
|
||||
Debug.LogWarning("[PunchTapeManager] 未指定 _favoritesPanel,无法打开收藏夹面板。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
favoritesPanel.OpenPanel();
|
||||
}
|
||||
|
||||
public void CloseFavoritesPanel()
|
||||
{
|
||||
if (favoritesPanel == null)
|
||||
{
|
||||
Debug.LogWarning("[PunchTapeManager] 未指定 _favoritesPanel,无法关闭收藏夹面板。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
favoritesPanel.ClosePanel();
|
||||
}
|
||||
|
||||
public void GeneratePunchTapeWindow(string clueName, string category, string memoryIndex,
|
||||
bool directlyAddToFavorites = false)
|
||||
{
|
||||
PunchTape punchTape = new PunchTape(category, clueName, memoryIndex);
|
||||
|
||||
if (directlyAddToFavorites)
|
||||
{
|
||||
AddPunchTape(punchTape);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject go = Instantiate(punchTapecluePrefab, favoritesPanel.transform);
|
||||
|
||||
PunchTapeItem item = go.GetComponent<PunchTapeItem>();
|
||||
currentPunchTapeGO = go;
|
||||
item.Setup(punchTape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前弹窗中的打孔带收入收藏。兼容旧 Yarn:<<CollectionClue ClueManager>> 会传入参数,仍应执行收藏。
|
||||
/// </summary>
|
||||
public void CollectPunchTapeFromWindow()
|
||||
{
|
||||
if (currentPunchTapeGO == null)
|
||||
{
|
||||
Debug.LogWarning("PunchTapeManager: 没有当前打孔带窗口,无法收藏。");
|
||||
return;
|
||||
}
|
||||
|
||||
PunchTapeItem item = currentPunchTapeGO.GetComponent<PunchTapeItem>();
|
||||
if (item == null)
|
||||
{
|
||||
Debug.LogWarning("PunchTapeManager: 当前物体上缺少 PunchTapeItem。");
|
||||
return;
|
||||
}
|
||||
|
||||
AddPunchTape(item.GetPunchTapeData());
|
||||
Destroy(currentPunchTapeGO.gameObject);
|
||||
currentPunchTapeGO = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打孔带收藏列表的存档载体;由 <see cref="PunchTapeManager"/> 注册到 <see cref="StorageSystem"/>。
|
||||
/// </summary>
|
||||
public class PunchTapeCollectionData : IData
|
||||
{
|
||||
public List<PunchTape> punchTapeFavorites = new();
|
||||
|
||||
public event Action<PunchTapeCollectionData> LoadEvent;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dd927405fcdda54fbc837069abc360d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using AibisDream.Kit;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 躯体维修界面中的「打孔纸带打印机」纯 UI 动画(与叙事侧 <see cref="PunchTapeManager"/> 无关)。
|
||||
/// 注册到 <see cref="FixSystemCenter.SystemDic"/>,由 Yarn <<PrintPunchTapes>> 经 <see cref="AibisDream.BodyModuleYarnCommand"/> 驱动。
|
||||
/// </summary>
|
||||
public class PunchTapePrinter : MonoBehaviour
|
||||
{
|
||||
public RectTransform punchTapeMachine;
|
||||
public RectTransform punchTape;
|
||||
public GameObject blackPanel;
|
||||
|
||||
/// <summary>打印机位移动画序列,可重复调用次数由 <paramref name="tapeCount"/> 决定。</summary>
|
||||
public IEnumerator RunPrintAnimation(int tapeCount)
|
||||
{
|
||||
blackPanel.SetActive(true);
|
||||
Vector3 tapeStartPos = punchTape.anchoredPosition;
|
||||
Color tapeStartColor = punchTape.GetComponent<Image>().color;
|
||||
|
||||
while (tapeCount > 0)
|
||||
{
|
||||
// 0-0.7秒:打印机移动到指定位置
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/typing");
|
||||
yield return punchTapeMachine.DOAnchorPos(new Vector2(-1252f, punchTapeMachine.anchoredPosition.y), 0.7f)
|
||||
.WaitForCompletion();
|
||||
|
||||
// 0.7-1秒:停顿
|
||||
yield return new WaitForSeconds(0.3f);
|
||||
|
||||
// 1-1.6秒:纸带匀速弹出
|
||||
Vector2 targetPosition = new Vector2(796f, punchTape.anchoredPosition.y);
|
||||
yield return punchTape.DOAnchorPos(targetPosition, 0.6f).WaitForCompletion();
|
||||
|
||||
// 1.6-2.3秒:停顿
|
||||
yield return new WaitForSeconds(0.7f);
|
||||
|
||||
// 2.3-2.7秒:纸带淡出
|
||||
yield return punchTape.GetComponent<Image>().DOFade(0f, 0.4f).WaitForCompletion();
|
||||
|
||||
// 重置纸带位置和透明度
|
||||
punchTape.anchoredPosition = tapeStartPos;
|
||||
punchTape.GetComponent<Image>().color = tapeStartColor;
|
||||
|
||||
tapeCount--;
|
||||
}
|
||||
|
||||
// 打印机返回原位
|
||||
yield return punchTapeMachine.DOAnchorPos(new Vector2(-1428.186f, punchTapeMachine.anchoredPosition.y), 1f)
|
||||
.WaitForCompletion();
|
||||
blackPanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4075ea95a82355544862690e0de983bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections;
|
||||
using AibisDream.FixSystem;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public static class PunchTypeYarnCommand
|
||||
{
|
||||
private static PunchTapeManager PunchTapeManager => PunchTapeManager.Instance;
|
||||
private static PunchTapePrinter PunchTapePrinter => PunchTapeManager.Instance.PunchTapePrinter;
|
||||
|
||||
[YarnCommand("generate_PunchTape")]
|
||||
public static void GeneratePunchTapeWindow(string clueName, string category, string memoryIndex,
|
||||
bool directlyAddToFavorites = false)
|
||||
{
|
||||
PunchTapeManager.GeneratePunchTapeWindow(clueName, category, memoryIndex, directlyAddToFavorites);
|
||||
}
|
||||
|
||||
[YarnCommand("CollectionClue")]
|
||||
public static void CollectPunchTapeFromWindow()
|
||||
{
|
||||
PunchTapeManager.CollectPunchTapeFromWindow();
|
||||
}
|
||||
|
||||
[YarnCommand("PrintPunchTapes")]
|
||||
public static IEnumerator PrintPunchTapes(int count)
|
||||
{
|
||||
yield return PunchTapePrinter.RunPrintAnimation(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b67dac9cdf73ba3458611e5d424ac1df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user