Initial Unity project commit

This commit is contained in:
2026-07-08 19:53:15 +08:00
commit 75f254212e
15657 changed files with 11633879 additions and 0 deletions
@@ -0,0 +1,158 @@
using System.Collections;
using UnityEngine;
#if DOTWEEN
using DG.Tweening;
#endif
namespace EndingSystem
{
public class EndingDoorController : MonoBehaviour
{
[Header("Door References")]
public Transform leftDoor;
public Transform rightDoor;
[Header("Animation Settings")]
[Tooltip("The initial small angle to open the door (e.g., crack it open slightly)")]
public float openLittleAngle = 10f;
public float openLittleDuration = 1.5f;
[Tooltip("How long to pause before fully opening the door")]
public float pauseDuration = 0.5f;
[Tooltip("The final angle to fully open the door")]
public float fullOpenAngle = 90f;
public float fullOpenDuration = 3f;
[Tooltip("The axis of rotation for the doors (usually Vector3.up)")]
public Vector3 rotationAxis = Vector3.up;
[Tooltip("If true, the right door rotates in the opposite direction of the left door")]
public bool mirrorRightDoor = true;
#if DOTWEEN
[Header("DOTween")]
public Ease openLittleEase = Ease.InOutSine;
public Ease fullOpenEase = Ease.InOutSine;
#endif
private Quaternion leftClosedRotation;
private Quaternion rightClosedRotation;
#if DOTWEEN
private Sequence currentSequence;
#endif
public void OpenDoor(bool useUnscaledTime = false)
{
#if DOTWEEN
PlayOpenDoor(useUnscaledTime);
#else
StartCoroutine(DoorAnimationCoroutine());
#endif
}
#if DOTWEEN
private void OnDisable()
{
if (currentSequence != null)
{
currentSequence.Kill(false);
currentSequence = null;
}
}
private void CacheClosedRotations()
{
if (leftDoor != null) leftClosedRotation = leftDoor.localRotation;
if (rightDoor != null) rightClosedRotation = rightDoor.localRotation;
}
private void PlayOpenDoor(bool useUnscaledTime)
{
CacheClosedRotations();
if (leftDoor != null) leftDoor.DOKill();
if (rightDoor != null) rightDoor.DOKill();
if (currentSequence != null)
{
currentSequence.Kill(false);
currentSequence = null;
}
currentSequence = DOTween.Sequence();
currentSequence.SetUpdate(useUnscaledTime);
currentSequence.Append(CreateRotateTween(openLittleAngle, Mathf.Max(0.0001f, openLittleDuration), openLittleEase, useUnscaledTime));
if (pauseDuration > 0f) currentSequence.AppendInterval(pauseDuration);
currentSequence.Append(CreateRotateTween(fullOpenAngle, Mathf.Max(0.0001f, fullOpenDuration), fullOpenEase, useUnscaledTime));
}
private Tween CreateRotateTween(float angle, float duration, Ease ease, bool useUnscaledTime)
{
Sequence seq = DOTween.Sequence();
seq.SetUpdate(useUnscaledTime);
Vector3 axis = rotationAxis.sqrMagnitude > 0f ? rotationAxis.normalized : Vector3.up;
if (leftDoor != null)
{
Quaternion target = leftClosedRotation * Quaternion.AngleAxis(angle, axis);
Tween t = leftDoor.DOLocalRotateQuaternion(target, duration).SetEase(ease).SetUpdate(useUnscaledTime);
seq.Join(t);
}
if (rightDoor != null)
{
float rightAngle = mirrorRightDoor ? -angle : angle;
Quaternion target = rightClosedRotation * Quaternion.AngleAxis(rightAngle, axis);
Tween t = rightDoor.DOLocalRotateQuaternion(target, duration).SetEase(ease).SetUpdate(useUnscaledTime);
seq.Join(t);
}
return seq;
}
#endif
private IEnumerator DoorAnimationCoroutine()
{
yield return StartCoroutine(RotateDoors(0f, openLittleAngle, openLittleDuration));
yield return new WaitForSeconds(pauseDuration);
yield return StartCoroutine(RotateDoors(openLittleAngle, fullOpenAngle, fullOpenDuration));
}
private IEnumerator RotateDoors(float startAngle, float endAngle, float duration)
{
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime / duration;
float currentAngle = Mathf.Lerp(startAngle, endAngle, t);
if (leftDoor != null)
{
leftDoor.localRotation = Quaternion.AngleAxis(currentAngle, rotationAxis);
}
if (rightDoor != null)
{
float rightAngle = mirrorRightDoor ? -currentAngle : currentAngle;
rightDoor.localRotation = Quaternion.AngleAxis(rightAngle, rotationAxis);
}
yield return null;
}
if (leftDoor != null)
leftDoor.localRotation = Quaternion.AngleAxis(endAngle, rotationAxis);
if (rightDoor != null)
{
float rightAngleFinal = mirrorRightDoor ? -endAngle : endAngle;
rightDoor.localRotation = Quaternion.AngleAxis(rightAngleFinal, rotationAxis);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b39d9923eb612140b7809b1cad268e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,88 @@
using System.Collections;
using UnityEngine;
#if DOTWEEN
using DG.Tweening;
#endif
namespace EndingSystem
{
public class EndingLightController : MonoBehaviour
{
[Header("Light Reference")]
public Light targetLight;
[Header("Animation Settings")]
[Tooltip("The final intensity of the light when fully turned on")]
public float targetIntensity = 5f;
[Tooltip("How long it takes for the light to reach target intensity")]
public float fadeDuration = 3f;
#if DOTWEEN
[Header("DOTween")]
public Ease fadeEase = Ease.InOutSine;
private Tween currentTween;
#endif
private void Awake()
{
if (targetLight != null)
{
targetLight.intensity = 0f;
targetLight.enabled = true;
}
}
public void TurnOnLight(bool useUnscaledTime = false)
{
if (targetLight != null)
{
#if DOTWEEN
PlayFade(useUnscaledTime);
#else
StartCoroutine(FadeLightCoroutine());
#endif
}
}
#if DOTWEEN
private void OnDisable()
{
if (currentTween != null)
{
currentTween.Kill(false);
currentTween = null;
}
}
private void PlayFade(bool useUnscaledTime)
{
if (currentTween != null)
{
currentTween.Kill(false);
currentTween = null;
}
float duration = Mathf.Max(0.0001f, fadeDuration);
currentTween = DOTween.To(() => targetLight.intensity, v => targetLight.intensity = v, targetIntensity, duration)
.SetEase(fadeEase)
.SetTarget(targetLight)
.SetUpdate(useUnscaledTime);
}
#endif
private IEnumerator FadeLightCoroutine()
{
float t = 0f;
float startIntensity = targetLight.intensity;
while (t < 1f)
{
t += Time.deltaTime / fadeDuration;
targetLight.intensity = Mathf.Lerp(startIntensity, targetIntensity, t);
yield return null;
}
targetLight.intensity = targetIntensity;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cdac34e0f9cd2f4cbd68a80a309d726
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,382 @@
using System.Collections;
using UnityEngine;
using Core.InputLock;
#if DOTWEEN
using DG.Tweening;
#endif
namespace EndingSystem
{
public class EndingSequenceController : MonoBehaviour
{
[Header("Overlay Safety")]
public bool forceScreenObjectsActive = true;
public bool bringScreenToFront = true;
public bool overrideCanvasSorting = true;
public int overlaySortingOrder = 5000;
[Header("Sequence Settings")]
public float delayBeforeFadeFromBlack = 1f;
[Header("UI Fader")]
[Tooltip("Assign a CanvasGroup attached to a black Image that covers the whole screen")]
public CanvasGroup blackScreenCanvasGroup;
public float fadeToBlackDuration = 1.5f;
public float fadeFromBlackDuration = 2f;
[Tooltip("Assign a CanvasGroup attached to a white Image that covers the whole screen")]
public CanvasGroup whiteScreenCanvasGroup;
public float fadeToWhiteDuration = 2f;
[Tooltip("How long to wait after the door opens before fading to white")]
public float delayBeforeFadeToWhite = 2f;
[Header("Ending UI")]
[Tooltip("The UI Panel to activate at the very end (e.g. Return to Menu / Credits)")]
public GameObject endingUIPanel;
[Header("Player & Camera References")]
[Tooltip("The parent object of the player (the one with CharacterController)")]
public Transform playerBody;
[Tooltip("The camera pivot (usually the parent of the camera that handles up/down look)")]
public Transform playerCameraPivot;
public Camera playerCamera;
[Header("Target Positions & Angles")]
[Tooltip("Where should the player be teleported to when the screen goes black?")]
public Transform targetPlayerTransform;
[Tooltip("The angle to which the camera will look up (negative values usually mean looking up)")]
public float cameraLookUpAngle = -30f;
public float cameraLookDuration = 3f;
[Tooltip("Target FOV to focus on the door")]
public float targetFOV = 30f;
public float fovShrinkDuration = 2f;
[Header("Scene Object Controllers")]
public EndingDoorController doorController;
public EndingLightController lightController;
#if DOTWEEN
[Header("DOTween")]
public bool useUnscaledTime = false;
public Ease fadeToBlackEase = Ease.InOutSine;
public Ease fadeFromBlackEase = Ease.InOutSine;
public Ease fadeToWhiteEase = Ease.InOutSine;
public Ease lookEase = Ease.InOutSine;
public Ease fovEase = Ease.InOutSine;
#endif
private bool sequenceStarted = false;
#if DOTWEEN
private Sequence sequence;
#endif
public void StartEndingSequence()
{
if (sequenceStarted) return;
sequenceStarted = true;
#if DOTWEEN
PlaySequenceWithDOTween();
#else
StartCoroutine(EndingCoroutine());
#endif
}
#if DOTWEEN
private void OnDisable()
{
if (sequence != null)
{
sequence.Kill(false);
sequence = null;
}
}
private void PlaySequenceWithDOTween()
{
if (sequence != null)
{
sequence.Kill(false);
sequence = null;
}
if (PlayerControlLockService.Instance != null)
{
PlayerControlLockService.Instance.Lock(this);
}
if (blackScreenCanvasGroup == null)
{
Debug.LogWarning("[EndingSequenceController] Black Screen CanvasGroup is not assigned!");
return;
}
if (endingUIPanel != null)
{
endingUIPanel.SetActive(false);
}
PrepareOverlay(blackScreenCanvasGroup, setAlpha: true, alpha: 0f);
if (whiteScreenCanvasGroup != null)
{
PrepareOverlay(whiteScreenCanvasGroup, setAlpha: true, alpha: 0f);
}
blackScreenCanvasGroup.DOKill();
if (playerCameraPivot != null) playerCameraPivot.DOKill();
if (playerCamera != null) DOTween.Kill(playerCamera);
sequence = DOTween.Sequence();
sequence.SetUpdate(useUnscaledTime);
sequence.Append(blackScreenCanvasGroup.DOFade(1f, Mathf.Max(0.0001f, fadeToBlackDuration)).SetEase(fadeToBlackEase));
sequence.AppendCallback(ApplyBlackScreenPlacementAndReset);
if (delayBeforeFadeFromBlack > 0f)
{
sequence.AppendInterval(delayBeforeFadeFromBlack);
}
Tween fadeFromTween = blackScreenCanvasGroup.DOFade(0f, Mathf.Max(0.0001f, fadeFromBlackDuration)).SetEase(fadeFromBlackEase);
sequence.Append(fadeFromTween);
if (playerCameraPivot != null)
{
Vector3 targetEuler = new Vector3(cameraLookUpAngle, 0f, 0f);
Tween lookTween = playerCameraPivot.DOLocalRotate(targetEuler, Mathf.Max(0.0001f, cameraLookDuration), RotateMode.Fast).SetEase(lookEase);
lookTween.SetUpdate(useUnscaledTime);
sequence.Join(lookTween);
}
if (playerCamera != null)
{
float duration = Mathf.Max(0.0001f, fovShrinkDuration);
Tween fovTween = DOTween.To(() => playerCamera.fieldOfView, v => playerCamera.fieldOfView = v, targetFOV, duration)
.SetEase(fovEase)
.SetTarget(playerCamera)
.SetUpdate(useUnscaledTime);
sequence.Append(fovTween);
}
sequence.AppendCallback(() =>
{
if (doorController != null) doorController.OpenDoor(useUnscaledTime);
if (lightController != null) lightController.TurnOnLight(useUnscaledTime);
});
// 等待开门动画和光照完成,再等一个自定义时间
float doorWaitTime = (doorController != null) ? (doorController.openLittleDuration + doorController.pauseDuration + doorController.fullOpenDuration) : 0f;
sequence.AppendInterval(doorWaitTime + delayBeforeFadeToWhite);
// 淡入白屏
if (whiteScreenCanvasGroup != null)
{
Tween whiteFadeTween = whiteScreenCanvasGroup.DOFade(1f, Mathf.Max(0.0001f, fadeToWhiteDuration)).SetEase(fadeToWhiteEase);
sequence.Append(whiteFadeTween);
}
// 最后激活UI并解锁鼠标
sequence.AppendCallback(FinishSequence);
}
#endif
private IEnumerator EndingCoroutine()
{
if (PlayerControlLockService.Instance != null)
{
PlayerControlLockService.Instance.Lock(this);
}
if (endingUIPanel != null)
{
endingUIPanel.SetActive(false);
}
PrepareOverlay(blackScreenCanvasGroup, setAlpha: true, alpha: 0f);
if (whiteScreenCanvasGroup != null) PrepareOverlay(whiteScreenCanvasGroup, setAlpha: true, alpha: 0f);
yield return StartCoroutine(FadeScreen(0f, 1f, fadeToBlackDuration, blackScreenCanvasGroup));
ApplyBlackScreenPlacementAndReset();
yield return new WaitForSeconds(delayBeforeFadeFromBlack);
Coroutine fadeRoutine = StartCoroutine(FadeScreen(1f, 0f, fadeFromBlackDuration, blackScreenCanvasGroup));
Coroutine lookRoutine = StartCoroutine(LookUpCoroutine());
yield return fadeRoutine;
yield return lookRoutine;
if (playerCamera != null)
{
float startFOV = playerCamera.fieldOfView;
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime / fovShrinkDuration;
playerCamera.fieldOfView = Mathf.Lerp(startFOV, targetFOV, t);
yield return null;
}
playerCamera.fieldOfView = targetFOV;
}
if (doorController != null) doorController.OpenDoor();
if (lightController != null) lightController.TurnOnLight();
// 等待开门时间
float doorWaitTime = (doorController != null) ? (doorController.openLittleDuration + doorController.pauseDuration + doorController.fullOpenDuration) : 0f;
yield return new WaitForSeconds(doorWaitTime + delayBeforeFadeToWhite);
if (whiteScreenCanvasGroup != null)
{
yield return StartCoroutine(FadeScreen(0f, 1f, fadeToWhiteDuration, whiteScreenCanvasGroup));
}
FinishSequence();
}
private void FinishSequence()
{
if (endingUIPanel != null)
{
endingUIPanel.SetActive(true);
BringEndingUIToFront();
}
// 解锁玩家输入,以显示鼠标点击 UI,但是玩家在结局时应该只看UI,通常我们可以保持移动锁定
// 但你的要求是"解锁鼠标",我们可以解锁 LockService,由于游戏已经结束,你可能需要在UI层面自己拦截按键
// 另外一种做法是调用 Unlock,然后让 UI 自己去 Lock(this),这取决于你的 UI 设计
if (PlayerControlLockService.Instance != null)
{
PlayerControlLockService.Instance.Unlock(this);
}
// 强制显示鼠标
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
private void BringEndingUIToFront()
{
if (endingUIPanel == null) { return; }
endingUIPanel.transform.SetAsLastSibling();
Canvas uiCanvas = endingUIPanel.GetComponentInParent<Canvas>(true);
if (uiCanvas == null) { return; }
int maxOverlayOrder = -1;
Canvas overlayCanvas = null;
if (blackScreenCanvasGroup != null) overlayCanvas = blackScreenCanvasGroup.GetComponentInParent<Canvas>(true);
if (overlayCanvas == null && whiteScreenCanvasGroup != null) overlayCanvas = whiteScreenCanvasGroup.GetComponentInParent<Canvas>(true);
if (overlayCanvas != null) maxOverlayOrder = overlayCanvas.sortingOrder;
if (uiCanvas == overlayCanvas)
{
if (blackScreenCanvasGroup != null) blackScreenCanvasGroup.transform.SetSiblingIndex(0);
if (whiteScreenCanvasGroup != null) whiteScreenCanvasGroup.transform.SetSiblingIndex(0);
endingUIPanel.transform.SetAsLastSibling();
return;
}
uiCanvas.overrideSorting = true;
uiCanvas.sortingOrder = Mathf.Max(uiCanvas.sortingOrder, maxOverlayOrder + 1, overlaySortingOrder + 1);
}
private void ApplyBlackScreenPlacementAndReset()
{
if (playerBody != null && targetPlayerTransform != null)
{
var cc = playerBody.GetComponent<CharacterController>();
if (cc != null) cc.enabled = false;
playerBody.position = targetPlayerTransform.position;
playerBody.rotation = targetPlayerTransform.rotation;
if (cc != null) cc.enabled = true;
}
if (playerCameraPivot != null)
{
playerCameraPivot.localRotation = Quaternion.identity;
}
}
private IEnumerator LookUpCoroutine()
{
if (playerCameraPivot == null) yield break;
Quaternion startRot = playerCameraPivot.localRotation;
Quaternion endRot = Quaternion.Euler(cameraLookUpAngle, 0f, 0f);
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime / cameraLookDuration;
playerCameraPivot.localRotation = Quaternion.Lerp(startRot, endRot, t);
yield return null;
}
playerCameraPivot.localRotation = endRot;
}
private IEnumerator FadeScreen(float startAlpha, float endAlpha, float duration, CanvasGroup cg)
{
if (cg == null)
{
Debug.LogWarning("[EndingSequenceController] A CanvasGroup is not assigned for fade!");
yield break;
}
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime / duration;
cg.alpha = Mathf.Lerp(startAlpha, endAlpha, t);
yield return null;
}
cg.alpha = endAlpha;
}
private void PrepareOverlay(CanvasGroup cg, bool setAlpha, float alpha)
{
if (cg == null) { return; }
if (forceScreenObjectsActive)
{
if (!cg.gameObject.activeSelf) cg.gameObject.SetActive(true);
if (cg.transform.parent != null && !cg.transform.parent.gameObject.activeSelf) cg.transform.parent.gameObject.SetActive(true);
}
var animator = cg.GetComponent<Animator>();
if (animator != null) animator.enabled = false;
var reachCanvasGroupAnimator = cg.GetComponent<Michsky.UI.Reach.CanvasGroupAnimator>();
if (reachCanvasGroupAnimator != null) reachCanvasGroupAnimator.enabled = false;
var reachImageFading = cg.GetComponent<Michsky.UI.Reach.ImageFading>();
if (reachImageFading != null) reachImageFading.enabled = false;
#if DOTWEEN
cg.DOKill();
#endif
cg.interactable = false;
cg.blocksRaycasts = false;
if (setAlpha) cg.alpha = alpha;
if (bringScreenToFront)
{
cg.transform.SetAsLastSibling();
}
if (overrideCanvasSorting)
{
Canvas canvas = cg.GetComponentInParent<Canvas>(true);
if (canvas != null)
{
canvas.overrideSorting = true;
canvas.sortingOrder = overlaySortingOrder;
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e117aa7f36336b4cbb9ac8c6bf7afc8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,121 @@
using UnityEngine;
namespace EndingSystem
{
[RequireComponent(typeof(BoxCollider))]
public class EndingTrigger : MonoBehaviour
{
[Header("Controller Reference")]
public EndingSequenceController sequenceController;
[Header("Trigger Settings")]
[Tooltip("If true, the sequence will start when the player enters the trigger. If false, you can call TriggerSequence() manually (e.g., from a UI Button).")]
public bool triggerOnCollision = true;
[Tooltip("Tag of the player object to detect collision")]
public string playerTag = "Player";
[Header("Win Condition")]
[Tooltip("If true, collision trigger requires EndingWinConditionService.CanTriggerEnding == true")]
public bool requireWinConditionToTrigger = true;
[Tooltip("If true, TriggerSequence() will ignore win condition (useful for debug buttons)")]
public bool allowManualTriggerWhenLocked = true;
private bool hasTriggered = false;
private void OnEnable()
{
enabled = true;
triggerOnCollision = true;
requireWinConditionToTrigger = false;
hasTriggered = false;
var coll = GetComponent<BoxCollider>();
if (coll != null) coll.isTrigger = true;
}
private void Awake()
{
enabled = true;
triggerOnCollision = true;
requireWinConditionToTrigger = false;
var coll = GetComponent<BoxCollider>();
coll.isTrigger = true;
if (sequenceController == null)
{
sequenceController = GetComponentInParent<EndingSequenceController>(true);
if (sequenceController == null)
{
sequenceController = FindObjectOfType<EndingSequenceController>(true);
}
}
}
private void OnValidate()
{
enabled = true;
triggerOnCollision = true;
requireWinConditionToTrigger = false;
var coll = GetComponent<BoxCollider>();
if (coll != null) coll.isTrigger = true;
if (sequenceController == null)
{
sequenceController = GetComponentInParent<EndingSequenceController>(true);
if (sequenceController == null)
{
sequenceController = FindObjectOfType<EndingSequenceController>(true);
}
}
}
private void OnTriggerEnter(Collider other)
{
if (hasTriggered) return;
if (other.CompareTag(playerTag))
{
TriggerSequence();
}
}
/// <summary>
/// 提供给外部调用的接口,比如 Button OnClick 事件,方便在开发阶段不检查碰撞直接触发
/// </summary>
public void TriggerSequence()
{
if (hasTriggered) return;
hasTriggered = true;
if (!allowManualTriggerWhenLocked && requireWinConditionToTrigger)
{
var wc = EndingWinConditionService.EnsureInstance();
if (wc != null && !wc.CanTriggerEnding)
{
hasTriggered = false;
return;
}
}
if (sequenceController == null)
{
sequenceController = GetComponentInParent<EndingSequenceController>(true);
if (sequenceController == null)
{
sequenceController = FindObjectOfType<EndingSequenceController>(true);
}
}
if (sequenceController != null)
{
sequenceController.StartEndingSequence();
}
else
{
Debug.LogError("[EndingTrigger] EndingSequenceController is not assigned!");
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c05e5ca4a67e0d48b7efd0317624066
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
using UnityEngine;
namespace EndingSystem
{
public class EndingWinConditionDebugButton : MonoBehaviour
{
public void UnlockEnding()
{
EndingWinConditionService.EnsureInstance().UnlockEnding();
}
public void LockEnding()
{
EndingWinConditionService.EnsureInstance().LockEnding();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 966a245d19c45bf408394b84389f4631
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,76 @@
using System;
using UnityEngine;
namespace EndingSystem
{
public class EndingWinConditionService : MonoBehaviour
{
public static EndingWinConditionService Instance { get; private set; }
[Header("Win Condition")]
[SerializeField] private bool canTriggerEnding = false;
[Header("Lifetime")]
[SerializeField] private bool dontDestroyOnLoad = true;
public event Action<bool> CanTriggerEndingChanged;
public bool CanTriggerEnding => canTriggerEnding;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
if (dontDestroyOnLoad) { DontDestroyOnLoad(gameObject); }
}
public void SetCanTriggerEnding(bool value)
{
if (canTriggerEnding == value) { return; }
canTriggerEnding = value;
CanTriggerEndingChanged?.Invoke(canTriggerEnding);
}
public void UnlockEnding()
{
SetCanTriggerEnding(true);
}
public void DebugUnlockEnding()
{
EnsureInstance().UnlockEnding();
}
public void LockEnding()
{
SetCanTriggerEnding(false);
}
public void DebugLockEnding()
{
EnsureInstance().LockEnding();
}
public static EndingWinConditionService EnsureInstance()
{
if (Instance != null) { return Instance; }
EndingWinConditionService existing = FindObjectOfType<EndingWinConditionService>(true);
if (existing != null)
{
Instance = existing;
return Instance;
}
var go = new GameObject("EndingWinConditionService");
var service = go.AddComponent<EndingWinConditionService>();
Instance = service;
return service;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a419e3badae0d7a4ea5d3b584d74148e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,211 @@
using UnityEngine;
using Core.InputLock;
#if DOTWEEN
using DG.Tweening;
#endif
namespace EndingSystem
{
public class FaintEndingSequenceController : MonoBehaviour
{
[Header("References")]
public Player.PlayerController playerController;
public Transform cameraPivot;
public Transform cameraTransform;
public CanvasGroup blackScreenCanvasGroup;
public GameObject endingUIPanel;
[Header("Input")]
public bool keepCursorLockedUntilEnd = true;
public bool switchToUIActionMapAtEnd = true;
#if DOTWEEN
[Header("DOTween")]
public bool useUnscaledTime = false;
public Ease step1Ease = Ease.InOutSine;
public Ease step3Ease = Ease.InOutSine;
public Ease step4Ease = Ease.InOutSine;
public Ease alphaEase = Ease.InOutSine;
#endif
[Header("Step 1")]
public float step1Duration = 1.2f;
public Vector3 step1CameraLocalOffset = new Vector3(0f, -0.4f, 0.3f);
public float step1Pitch = 35f;
[Range(0f, 1f)] public float step1Alpha = 0.65f;
[Header("Step 2")]
public float step2HoldDuration = 0.4f;
[Header("Step 3")]
public float step3Duration = 0.4f;
public Vector3 step3CameraLocalOffset = new Vector3(0f, -0.32f, 0.24f);
public float step3Pitch = 25f;
[Range(0f, 1f)] public float step3Alpha = 0.45f;
[Header("Step 4")]
public float step4Duration = 1.1f;
public Vector3 step4CameraLocalOffset = new Vector3(0f, -0.9f, 0.45f);
public float step4Pitch = 90f;
[Range(0f, 1f)] public float step4Alpha = 1f;
private bool sequenceStarted;
private Vector3 initialCameraLocalPos;
private Quaternion initialPivotLocalRot;
private float initialPivotPitch;
#if DOTWEEN
private Sequence sequence;
#endif
public void StartFaintSequence()
{
if (sequenceStarted) return;
sequenceStarted = true;
if (endingUIPanel != null) endingUIPanel.SetActive(false);
if (playerController == null) playerController = FindObjectOfType<Player.PlayerController>(true);
if (playerController != null) playerController.enabled = false;
if (cameraTransform == null)
{
var cam = Camera.main;
if (cam != null) cameraTransform = cam.transform;
}
if (cameraPivot == null && cameraTransform != null) cameraPivot = cameraTransform.parent;
if (cameraTransform != null) initialCameraLocalPos = cameraTransform.localPosition;
if (cameraPivot != null)
{
initialPivotLocalRot = cameraPivot.localRotation;
float x = cameraPivot.localEulerAngles.x;
if (x > 180f) x -= 360f;
initialPivotPitch = x;
}
PrepareOverlay(blackScreenCanvasGroup, setAlpha: true, alpha: 0f);
#if DOTWEEN
PlayWithDOTween();
#else
StartCoroutine(PlayWithCoroutine());
#endif
}
#if DOTWEEN
private void OnDisable()
{
if (sequence != null)
{
sequence.Kill(false);
sequence = null;
}
}
private void PlayWithDOTween()
{
if (sequence != null)
{
sequence.Kill(false);
sequence = null;
}
if (cameraTransform != null) cameraTransform.DOKill();
if (cameraPivot != null) cameraPivot.DOKill();
sequence = DOTween.Sequence();
sequence.SetUpdate(useUnscaledTime);
Vector3 step1Pos = initialCameraLocalPos + step1CameraLocalOffset;
Vector3 step3Pos = initialCameraLocalPos + step3CameraLocalOffset;
Vector3 step4Pos = initialCameraLocalPos + step4CameraLocalOffset;
if (blackScreenCanvasGroup != null)
sequence.Join(blackScreenCanvasGroup.DOFade(step1Alpha, Mathf.Max(0.0001f, step1Duration)).SetEase(alphaEase));
if (cameraTransform != null)
sequence.Join(cameraTransform.DOLocalMove(step1Pos, Mathf.Max(0.0001f, step1Duration)).SetEase(step1Ease).SetUpdate(useUnscaledTime));
if (cameraPivot != null)
sequence.Join(cameraPivot.DOLocalRotate(new Vector3(initialPivotPitch + step1Pitch, 0f, 0f), Mathf.Max(0.0001f, step1Duration), RotateMode.Fast).SetEase(step1Ease).SetUpdate(useUnscaledTime));
if (step2HoldDuration > 0f) sequence.AppendInterval(step2HoldDuration);
if (blackScreenCanvasGroup != null)
sequence.Append(blackScreenCanvasGroup.DOFade(step3Alpha, Mathf.Max(0.0001f, step3Duration)).SetEase(alphaEase));
if (cameraTransform != null)
sequence.Join(cameraTransform.DOLocalMove(step3Pos, Mathf.Max(0.0001f, step3Duration)).SetEase(step3Ease).SetUpdate(useUnscaledTime));
if (cameraPivot != null)
sequence.Join(cameraPivot.DOLocalRotate(new Vector3(initialPivotPitch + step3Pitch, 0f, 0f), Mathf.Max(0.0001f, step3Duration), RotateMode.Fast).SetEase(step3Ease).SetUpdate(useUnscaledTime));
if (blackScreenCanvasGroup != null)
sequence.Append(blackScreenCanvasGroup.DOFade(step4Alpha, Mathf.Max(0.0001f, step4Duration)).SetEase(alphaEase));
if (cameraTransform != null)
sequence.Join(cameraTransform.DOLocalMove(step4Pos, Mathf.Max(0.0001f, step4Duration)).SetEase(step4Ease).SetUpdate(useUnscaledTime));
if (cameraPivot != null)
sequence.Join(cameraPivot.DOLocalRotate(new Vector3(initialPivotPitch + step4Pitch, 0f, 0f), Mathf.Max(0.0001f, step4Duration), RotateMode.Fast).SetEase(step4Ease).SetUpdate(useUnscaledTime));
sequence.AppendCallback(FinishSequence);
}
#else
private System.Collections.IEnumerator PlayWithCoroutine()
{
yield return null;
}
#endif
private void FinishSequence()
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
if (switchToUIActionMapAtEnd && PlayerControlLockService.Instance != null)
{
PlayerControlLockService.Instance.Lock(this);
}
if (endingUIPanel != null) endingUIPanel.SetActive(true);
}
private void PrepareOverlay(CanvasGroup cg, bool setAlpha, float alpha)
{
if (cg == null) { return; }
if (!cg.gameObject.activeSelf) cg.gameObject.SetActive(true);
if (cg.transform.parent != null && !cg.transform.parent.gameObject.activeSelf) cg.transform.parent.gameObject.SetActive(true);
var animator = cg.GetComponent<Animator>();
if (animator != null) animator.enabled = false;
var reachCanvasGroupAnimator = cg.GetComponent<Michsky.UI.Reach.CanvasGroupAnimator>();
if (reachCanvasGroupAnimator != null) reachCanvasGroupAnimator.enabled = false;
var reachImageFading = cg.GetComponent<Michsky.UI.Reach.ImageFading>();
if (reachImageFading != null) reachImageFading.enabled = false;
#if DOTWEEN
cg.DOKill();
#endif
cg.interactable = false;
cg.blocksRaycasts = false;
if (setAlpha) cg.alpha = alpha;
cg.transform.SetAsLastSibling();
Canvas canvas = cg.GetComponentInParent<Canvas>(true);
if (canvas != null)
{
canvas.overrideSorting = true;
canvas.sortingOrder = 5000;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 837e4b6269a003744b0bfbddb6bed35e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: