775 lines
24 KiB
C#
775 lines
24 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using AibisDream.FixSystem;
|
|
using AibisDream.Framework;
|
|
using AibisDream.Kit;
|
|
using AibisDream.SaveSystem;
|
|
using AibisDream.UI;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace AibisDream
|
|
{
|
|
public sealed class SessionFailure
|
|
{
|
|
public string Command { get; internal set; }
|
|
public string Stage { get; internal set; }
|
|
public IReadOnlyList<string> Errors { get; internal set; } = Array.Empty<string>();
|
|
}
|
|
|
|
public class GameManager : Singleton<GameManager>
|
|
{
|
|
private enum SessionCommandKind
|
|
{
|
|
None,
|
|
Start,
|
|
Transition,
|
|
Restore,
|
|
ReturnToMenu
|
|
}
|
|
|
|
private const float TransitionDuration = 0.5f;
|
|
private const float DialogueHandoffTimeout = 5f;
|
|
|
|
[Header("生产环境数据(一般不动)")]
|
|
[SerializeField] private TalkSceneSO firstTalkSo;
|
|
|
|
private GameSession _session;
|
|
private TalkSceneGraphIndex _talkSceneIndex;
|
|
private SessionCommandKind _activeCommand;
|
|
private bool _returnToMenuRequested;
|
|
|
|
public static GameSession Session =>
|
|
Instance != null && Instance._session != null
|
|
? Instance._session
|
|
: throw new InvalidOperationException(
|
|
"GameManager.Session accessed before GameManager initialization.");
|
|
|
|
public IReadOnlyList<TalkSceneSO> RuntimeChapters => _talkSceneIndex.RuntimeChapters;
|
|
|
|
public override void OnSingletonInit()
|
|
{
|
|
_session = new GameSession();
|
|
_talkSceneIndex = new TalkSceneGraphIndex(firstTalkSo);
|
|
|
|
foreach (var error in _talkSceneIndex.ValidationErrors)
|
|
{
|
|
Debug.LogError($"[GameManager] {error}");
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (DialogController.Instance != null)
|
|
{
|
|
DialogController.Instance.DialogueActivityChanged += OnDialogueActivityChanged;
|
|
}
|
|
}
|
|
|
|
public override void OnSingletonDestroy()
|
|
{
|
|
if (DialogController.Instance != null)
|
|
{
|
|
DialogController.Instance.DialogueActivityChanged -= OnDialogueActivityChanged;
|
|
}
|
|
}
|
|
|
|
public bool TryStartNewGame()
|
|
{
|
|
return TryStartChapter(firstTalkSo);
|
|
}
|
|
|
|
public bool TryStartChapter(TalkSceneSO chapter)
|
|
{
|
|
if (!ValidateChapter(chapter, out var error))
|
|
{
|
|
Debug.LogError($"[GameManager] Cannot start chapter: {error}");
|
|
return false;
|
|
}
|
|
|
|
if (!TryAcquireCommand(SessionCommandKind.Start))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StartCoroutine(StartChapterRoutine(chapter));
|
|
return true;
|
|
}
|
|
|
|
public bool TryRestoreSlot(int slotIndex, Action<RestoreResult> completed = null)
|
|
{
|
|
if (!CanBeginRestore() || !TryAcquireCommand(SessionCommandKind.Restore))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StartCoroutine(RestoreRoutine(
|
|
() => SaveRestoreOrchestrator.PrepareRestoreFromSlot(
|
|
slotIndex,
|
|
_talkSceneIndex,
|
|
RestoreOptions.Default),
|
|
completed));
|
|
return true;
|
|
}
|
|
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
public bool TryRestoreTestSave(TestSaveEntry entry, Action<RestoreResult> completed = null)
|
|
{
|
|
if (entry == null
|
|
|| !entry.IsValid
|
|
|| !CanBeginRestore()
|
|
|| !TryAcquireCommand(SessionCommandKind.Restore))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StartCoroutine(RestoreRoutine(
|
|
() => SaveRestoreOrchestrator.PrepareRestoreFromTestSave(
|
|
entry,
|
|
_talkSceneIndex,
|
|
RestoreOptions.Default),
|
|
completed));
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
public bool TryPause()
|
|
{
|
|
if (!_session.CanPause)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_session.SetPaused(true);
|
|
Time.timeScale = 0f;
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionPaused);
|
|
return true;
|
|
}
|
|
|
|
public bool TryResume()
|
|
{
|
|
if (!_session.CanResume)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ResumeSessionInternal(sendEvent: true);
|
|
return true;
|
|
}
|
|
|
|
public bool TryReturnToMainMenu()
|
|
{
|
|
if (_session.Phase is GameSessionPhase.MainMenu or GameSessionPhase.ReturningToMenu)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_activeCommand != SessionCommandKind.None)
|
|
{
|
|
_returnToMenuRequested = true;
|
|
return true;
|
|
}
|
|
|
|
if (!TryAcquireCommand(SessionCommandKind.ReturnToMenu))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StartCoroutine(ReturnToMainMenuRoutine());
|
|
return true;
|
|
}
|
|
|
|
public void QuitApplication()
|
|
{
|
|
Application.Quit();
|
|
}
|
|
|
|
internal bool TryAdvanceChapterFromYarn(string exitName)
|
|
{
|
|
var target = _session.CurrentTalkScene?.GetNextScene(exitName);
|
|
if (!ValidateChapter(target, out var error))
|
|
{
|
|
Debug.LogError($"[GameManager] Invalid chapter exit '{exitName ?? "default"}': {error}");
|
|
return false;
|
|
}
|
|
|
|
if (_session.Phase != GameSessionPhase.Playing
|
|
|| !TryAcquireCommand(SessionCommandKind.Transition))
|
|
{
|
|
Debug.LogWarning("[GameManager] Chapter transition rejected because another command is active.");
|
|
return false;
|
|
}
|
|
|
|
StartCoroutine(AdvanceChapterCommandRoutine(target));
|
|
return true;
|
|
}
|
|
|
|
private IEnumerator AdvanceChapterCommandRoutine(TalkSceneSO target)
|
|
{
|
|
try
|
|
{
|
|
yield return TransitionChapterRoutine(target);
|
|
}
|
|
finally
|
|
{
|
|
FinishCommand(SessionCommandKind.Transition);
|
|
}
|
|
}
|
|
|
|
internal IEnumerator LoadSceneFromYarn(string sceneName)
|
|
{
|
|
if (_session.Phase != GameSessionPhase.Playing
|
|
|| !TryAcquireCommand(SessionCommandKind.Transition))
|
|
{
|
|
Debug.LogWarning("[GameManager] Yarn scene load rejected because another command is active.");
|
|
yield break;
|
|
}
|
|
|
|
try
|
|
{
|
|
DialogUIManager.Instance?.HideDialog();
|
|
|
|
if (!TrySetPhase(GameSessionPhase.Transitioning))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
SceneOperationResult sceneResult = null;
|
|
yield return SceneLoader.Instance.LoadSceneAsync(sceneName, value => sceneResult = value);
|
|
if (!EnsureSceneSucceeded(sceneResult, "Yarn scene load"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
SceneReadinessResult readiness = null;
|
|
yield return SceneReadiness.WaitUntilReady(
|
|
SceneLoader.Instance.CurrentScene,
|
|
IsReturnRequested,
|
|
value => readiness = value);
|
|
if (!EnsureReadinessSucceeded(readiness, "Yarn scene readiness"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
TrySetPhase(GameSessionPhase.Playing);
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionReady);
|
|
}
|
|
finally
|
|
{
|
|
FinishCommand(SessionCommandKind.Transition);
|
|
}
|
|
}
|
|
|
|
internal IEnumerator UnloadSceneFromYarn()
|
|
{
|
|
if (_session.Phase != GameSessionPhase.Playing
|
|
|| !TryAcquireCommand(SessionCommandKind.Transition))
|
|
{
|
|
Debug.LogWarning("[GameManager] Yarn scene unload rejected because another command is active.");
|
|
yield break;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!TrySetPhase(GameSessionPhase.Transitioning))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
SceneOperationResult result = null;
|
|
yield return SceneLoader.Instance.UnloadSceneAsync(value => result = value);
|
|
if (!EnsureSceneSucceeded(result, "Yarn scene unload"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
TrySetPhase(GameSessionPhase.Playing);
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionReady);
|
|
}
|
|
finally
|
|
{
|
|
FinishCommand(SessionCommandKind.Transition);
|
|
}
|
|
}
|
|
|
|
private IEnumerator StartChapterRoutine(TalkSceneSO chapter)
|
|
{
|
|
try
|
|
{
|
|
// 设置当前状态
|
|
_session.ResetRuntimeState();
|
|
if (!TrySetPhase(GameSessionPhase.Starting))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionStarted);
|
|
yield return FadeIn();
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
SceneOperationResult sceneResult = null;
|
|
yield return SceneLoader.Instance.LoadSceneAsync(
|
|
chapter.firstSceneName,
|
|
value => sceneResult = value);
|
|
if (!EnsureSceneSucceeded(sceneResult, "Start scene"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
SceneReadinessResult readiness = null;
|
|
yield return SceneReadiness.WaitUntilReady(
|
|
SceneLoader.Instance.CurrentScene,
|
|
IsReturnRequested,
|
|
value => readiness = value);
|
|
if (!EnsureReadinessSucceeded(readiness, "Start readiness"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
CommitChapter(chapter);
|
|
DialogController.Instance.StartDialog(chapter.yarnProject);
|
|
TrySetPhase(GameSessionPhase.Playing);
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionReady);
|
|
// 让Yarn自己处理淡出
|
|
// yield return FadeOut();
|
|
}
|
|
finally
|
|
{
|
|
FinishCommand(SessionCommandKind.Start);
|
|
}
|
|
}
|
|
|
|
private IEnumerator TransitionChapterRoutine(TalkSceneSO target)
|
|
{
|
|
if (!TrySetPhase(GameSessionPhase.Transitioning))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
yield return FadeIn();
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
var sourceDialogueCompleted = false;
|
|
yield return WaitForSourceDialogueCompletion(
|
|
value => sourceDialogueCompleted = value);
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
if (!sourceDialogueCompleted)
|
|
{
|
|
SendFailure(
|
|
"Transition",
|
|
"SourceDialogueCompletion",
|
|
new[]
|
|
{
|
|
$"Source dialogue did not complete within {DialogueHandoffTimeout:0.#} seconds. "
|
|
+ "NextYarn must be the final executable command on its branch."
|
|
});
|
|
_returnToMenuRequested = true;
|
|
yield break;
|
|
}
|
|
|
|
SceneOperationResult sceneResult = null;
|
|
yield return SceneLoader.Instance.LoadSceneAsync(
|
|
target.firstSceneName,
|
|
value => sceneResult = value);
|
|
if (!EnsureSceneSucceeded(sceneResult, "Chapter scene"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
SceneReadinessResult readiness = null;
|
|
yield return SceneReadiness.WaitUntilReady(
|
|
SceneLoader.Instance.CurrentScene,
|
|
IsReturnRequested,
|
|
value => readiness = value);
|
|
if (!EnsureReadinessSucceeded(readiness, "Chapter readiness"))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
CommitChapter(target);
|
|
DialogController.Instance.StartDialog(target.yarnProject);
|
|
TrySetPhase(GameSessionPhase.Playing);
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionReady);
|
|
// 让Yarn自己处理淡出
|
|
// yield return FadeOut();
|
|
}
|
|
|
|
private IEnumerator WaitForSourceDialogueCompletion(Action<bool> completed)
|
|
{
|
|
var runner = DialogController.Instance?.DialogueRunner;
|
|
if (runner == null)
|
|
{
|
|
completed?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
var deadline = Time.unscaledTime + DialogueHandoffTimeout;
|
|
while (runner.IsDialogueRunning && Time.unscaledTime < deadline)
|
|
{
|
|
if (_returnToMenuRequested)
|
|
{
|
|
completed?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
|
|
completed?.Invoke(!runner.IsDialogueRunning);
|
|
}
|
|
|
|
private IEnumerator RestoreRoutine(
|
|
Func<RestorePreparation> prepare,
|
|
Action<RestoreResult> completed)
|
|
{
|
|
var originPhase = _session.Phase;
|
|
RestoreResult result = null;
|
|
|
|
try
|
|
{
|
|
if (!TrySetPhase(GameSessionPhase.Restoring))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
var preparation = prepare();
|
|
result = preparation.Result;
|
|
if (!preparation.Success)
|
|
{
|
|
SendFailure("Restore", result.FailedPhase, result.Errors);
|
|
TrySetPhase(originPhase);
|
|
yield break;
|
|
}
|
|
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionStarted);
|
|
ResumeSessionInternal(sendEvent: _session.IsPaused);
|
|
yield return FadeIn();
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
result.Cancelled = true;
|
|
yield break;
|
|
}
|
|
|
|
if (DialogController.Instance != null)
|
|
{
|
|
yield return DialogController.Instance.StopDialogRoutine();
|
|
}
|
|
|
|
if (_returnToMenuRequested)
|
|
{
|
|
result.Cancelled = true;
|
|
yield break;
|
|
}
|
|
|
|
ResetPresentationEffects();
|
|
yield return SaveRestoreOrchestrator.ExecutePreparedRestore(
|
|
preparation,
|
|
IsReturnRequested);
|
|
|
|
result = preparation.Result;
|
|
if (result.Cancelled || _returnToMenuRequested)
|
|
{
|
|
_returnToMenuRequested = true;
|
|
yield break;
|
|
}
|
|
|
|
if (!result.Success)
|
|
{
|
|
SendFailure("Restore", result.FailedPhase, result.Errors);
|
|
_returnToMenuRequested = true;
|
|
yield break;
|
|
}
|
|
|
|
CommitChapter(result.RestoredTalkScene);
|
|
TrySetPhase(GameSessionPhase.Playing);
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionReady);
|
|
ScreenSnapshotHelper.ApplyDeferredFadeScreenIfNeeded();
|
|
// yield return FadeOut();
|
|
}
|
|
finally
|
|
{
|
|
completed?.Invoke(result ?? RestoreResult.CreateFailure(
|
|
"Restore",
|
|
"Restore did not produce a result."));
|
|
FinishCommand(SessionCommandKind.Restore);
|
|
}
|
|
}
|
|
|
|
private IEnumerator ReturnToMainMenuRoutine()
|
|
{
|
|
try
|
|
{
|
|
_returnToMenuRequested = false;
|
|
if (!TrySetPhase(GameSessionPhase.ReturningToMenu))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionEnding);
|
|
|
|
if (DialogController.Instance != null)
|
|
{
|
|
yield return DialogController.Instance.StopDialogRoutine();
|
|
}
|
|
|
|
// The in-game terminal remains visible while this transition
|
|
// covers the gameplay behind it.
|
|
yield return FadeIn();
|
|
UIManager.Instance.GetPanel<InGameTerminalPanel>()?.CloseForSessionEnd();
|
|
|
|
// The showcase belongs to the loaded gameplay scene, so clear it
|
|
// while that scene is still alive and hidden behind the fade.
|
|
ResetPresentationEffects();
|
|
|
|
SceneOperationResult unloadResult = null;
|
|
const int maxUnloadAttempts = 2;
|
|
for (var attempt = 1; attempt <= maxUnloadAttempts; attempt++)
|
|
{
|
|
yield return SceneLoader.Instance.UnloadSceneAsync(value => unloadResult = value);
|
|
if (unloadResult?.Success == true)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (attempt < maxUnloadAttempts)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
if (unloadResult?.Success != true)
|
|
{
|
|
SendFailure(
|
|
"ReturnToMenu",
|
|
"UnloadScene",
|
|
new[] { unloadResult?.Error ?? "Scene unload did not return a result." });
|
|
yield break;
|
|
}
|
|
|
|
UIManager.Instance.HidePanel<EndPanel>();
|
|
yield return LocalizationKit.WaitUntilReady();
|
|
|
|
ResumeSessionInternal(sendEvent: _session.IsPaused);
|
|
_session.ResetRuntimeState();
|
|
TrySetPhase(GameSessionPhase.MainMenu);
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionEnded);
|
|
|
|
yield return LocalizationKit.WaitForUIRefresh();
|
|
yield return FadeOut();
|
|
EventSystem.current?.SetSelectedGameObject(null);
|
|
}
|
|
finally
|
|
{
|
|
FinishCommand(SessionCommandKind.ReturnToMenu);
|
|
}
|
|
}
|
|
|
|
private bool CanBeginRestore()
|
|
{
|
|
return _activeCommand == SessionCommandKind.None
|
|
&& _session.Phase is GameSessionPhase.MainMenu or GameSessionPhase.Playing;
|
|
}
|
|
|
|
private bool TryAcquireCommand(SessionCommandKind command)
|
|
{
|
|
if (_activeCommand != SessionCommandKind.None)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_activeCommand = command;
|
|
return true;
|
|
}
|
|
|
|
private void FinishCommand(SessionCommandKind command)
|
|
{
|
|
if (_activeCommand == command)
|
|
{
|
|
_activeCommand = SessionCommandKind.None;
|
|
}
|
|
|
|
if (!_returnToMenuRequested
|
|
|| _activeCommand != SessionCommandKind.None
|
|
|| _session.Phase is GameSessionPhase.MainMenu or GameSessionPhase.ReturningToMenu)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (TryAcquireCommand(SessionCommandKind.ReturnToMenu))
|
|
{
|
|
StartCoroutine(ReturnToMainMenuRoutine());
|
|
}
|
|
}
|
|
|
|
private bool TrySetPhase(GameSessionPhase phase)
|
|
{
|
|
if (_session.TryTransitionTo(phase, out var error))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Debug.LogError($"[GameManager] {error}");
|
|
return false;
|
|
}
|
|
|
|
private void CommitChapter(TalkSceneSO chapter)
|
|
{
|
|
_session.SetCurrentTalkScene(chapter);
|
|
ChapterController.Instance?.UnlockChapter(chapter);
|
|
}
|
|
|
|
private bool EnsureSceneSucceeded(SceneOperationResult result, string stage)
|
|
{
|
|
if (result?.Success == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
SendFailure("Scene", stage, new[] { result?.Error ?? "Scene operation did not return a result." });
|
|
_returnToMenuRequested = true;
|
|
return false;
|
|
}
|
|
|
|
private bool EnsureReadinessSucceeded(SceneReadinessResult result, string stage)
|
|
{
|
|
if (result?.Success == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (result?.Cancelled == true)
|
|
{
|
|
_returnToMenuRequested = true;
|
|
return false;
|
|
}
|
|
|
|
SendFailure("SceneReadiness", stage, new[]
|
|
{
|
|
result?.Error ?? "Scene readiness did not return a result."
|
|
});
|
|
_returnToMenuRequested = true;
|
|
return false;
|
|
}
|
|
|
|
private void SendFailure(string command, string stage, IReadOnlyList<string> errors)
|
|
{
|
|
var failure = new SessionFailure
|
|
{
|
|
Command = command,
|
|
Stage = stage,
|
|
Errors = errors ?? Array.Empty<string>()
|
|
};
|
|
|
|
Debug.LogError($"[GameManager] {command} failed at {stage}: {string.Join("; ", failure.Errors)}");
|
|
EnumEventSystem.Global.Send<GameLifecycleEvent, SessionFailure>(
|
|
GameLifecycleEvent.SessionFailed,
|
|
failure);
|
|
}
|
|
|
|
private void ResumeSessionInternal(bool sendEvent)
|
|
{
|
|
var wasPaused = _session.IsPaused;
|
|
Time.timeScale = 1f;
|
|
_session.SetPaused(false);
|
|
if (sendEvent && wasPaused)
|
|
{
|
|
EnumEventSystem.Global.Send(GameLifecycleEvent.SessionResumed);
|
|
}
|
|
}
|
|
|
|
private void ResetPresentationEffects()
|
|
{
|
|
ScreenEffectManager.Instance?.ResetSaturation();
|
|
UIManager.Instance.GetPanel<PlayToolPanel>()?.ClearPresentationEffects();
|
|
Day2SleepPresentationController.Instance?.ClearPresentationEffects();
|
|
SpriteShowcase.Instance?.ClearPresentationEffects();
|
|
CameraKit.Instance?.ResetCamera();
|
|
}
|
|
|
|
private IEnumerator FadeIn()
|
|
{
|
|
var transitionPanel = UIManager.Instance.GetPanel<ScreenTransitionPanel>();
|
|
if (transitionPanel != null)
|
|
{
|
|
yield return transitionPanel.FadeInAsync(TransitionDuration, useUnscaledTime: true);
|
|
}
|
|
}
|
|
|
|
private IEnumerator FadeOut()
|
|
{
|
|
var transitionPanel = UIManager.Instance.GetPanel<ScreenTransitionPanel>();
|
|
if (transitionPanel != null)
|
|
{
|
|
yield return transitionPanel.FadeOutAsync(TransitionDuration, useUnscaledTime: true);
|
|
}
|
|
}
|
|
|
|
private void OnDialogueActivityChanged(bool active)
|
|
{
|
|
_session.SetDialogueActive(active);
|
|
}
|
|
|
|
private bool IsReturnRequested()
|
|
{
|
|
return _returnToMenuRequested;
|
|
}
|
|
|
|
private static bool ValidateChapter(TalkSceneSO chapter, out string error)
|
|
{
|
|
if (chapter == null)
|
|
{
|
|
error = "TalkSceneSO is null.";
|
|
return false;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(chapter.firstSceneName))
|
|
{
|
|
error = $"TalkSceneSO {chapter.name} has an empty scene key.";
|
|
return false;
|
|
}
|
|
|
|
if (chapter.yarnProject == null)
|
|
{
|
|
error = $"TalkSceneSO {chapter.name} has no YarnProject.";
|
|
return false;
|
|
}
|
|
|
|
error = null;
|
|
return true;
|
|
}
|
|
}
|
|
}
|