feat(Cycle):实现循环提前检测,修复交互提示消失,按键绑定的事件在重启的时候没能正确,触发循环的提前检测有错误

This commit is contained in:
2025-10-27 10:31:01 +08:00
parent e8b9f47067
commit 9646483fa6
36 changed files with 6743 additions and 5221 deletions

View File

@@ -12,6 +12,16 @@ namespace Script.Gameplay.Connect
private IConnectable _pointA;
private IConnectable _pointB;
public IConnectable PointA
{
get => _pointA;
}
public IConnectable PointB
{
get => _pointB;
}
private LineRenderer line;
private void Awake()
@@ -31,11 +41,13 @@ namespace Script.Gameplay.Connect
Debug.Log("ConnectionLine requires two valid IConnectable points.");
return;
}
if (pointA == pointB)
{
Debug.Log("ConnectionLine cannot connect the same point.");
return;
}
_pointA = pointA;
_pointB = pointB;
pointA.ConnectionLines.Add(this);
@@ -81,10 +93,12 @@ namespace Script.Gameplay.Connect
public void OnSignalReceived(bool active, GameObject sender)
{
SendSignal(active,this.gameObject);
SendSignal(active, this.gameObject);
}
public void SendSignal(bool active,GameObject sender)
public bool IsEnableSendSignal { get; set; } = true;
public void SendSignal(bool active, GameObject sender)
{
var a = _pointA as ISignalReceiver;
var b = _pointB as ISignalReceiver;
@@ -93,6 +107,7 @@ namespace Script.Gameplay.Connect
{
a.OnSignalReceived(active, this.gameObject);
}
if (b != senderR)
{
b.OnSignalReceived(active, this.gameObject);

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Script.Gameplay.Connect;
using Script.Gameplay.Global;
using Script.Gameplay.Interface;
using UnityEngine;
@@ -15,6 +16,7 @@ namespace Script.Gameplay.Facility
[SerializeField] protected int needSignalCount = 1;
[SerializeField] protected int currentSignalCount = 0;
[SerializeField] protected string componentName;
[SerializeField] protected bool isEnableSendSignal = false;
public virtual bool IsEnableInteract
{
@@ -44,12 +46,13 @@ namespace Script.Gameplay.Facility
get => isEnableConnect;
set => isEnableConnect = value;
}
public virtual int NeedSignalCount
{
get => needSignalCount;
set => needSignalCount = value;
}
public virtual int CurrentNeedSignalCount
{
get => currentSignalCount;
@@ -111,7 +114,7 @@ namespace Script.Gameplay.Facility
}
else
{
CurrentNeedSignalCount = Mathf.Max(0, CurrentNeedSignalCount- 1);
CurrentNeedSignalCount = Mathf.Max(0, CurrentNeedSignalCount - 1);
}
if (CurrentNeedSignalCount < NeedSignalCount)
@@ -121,10 +124,24 @@ namespace Script.Gameplay.Facility
// Implement signal received logic here
}
public bool IsEnableSendSignal
{
get => isEnableSendSignal;
set => isEnableSendSignal = value;
}
public virtual void SendSignal(bool active, GameObject sender)
{
if(!IsEnableSendSignal) return;
if (ConnectionLines != null)
{
if (ISignalSender.IsSendToSignalSender(sender))
{
// 防止信号回传给发送者自己
BUGManager.Instance.LogStackOverflowBUG(this.transform);
return;
}
foreach (var line in ConnectionLines)
{
line.OnSignalReceived(active, this.gameObject);

View File

@@ -17,6 +17,17 @@ namespace Script.Gameplay.Facility
public override void Interact(GameObject interactor)
{
if (!IsEnableInteract) return;
PullLever();
}
public override void OnSignalReceived(bool active, GameObject sender)
{
base.OnSignalReceived(active, sender);
PullLever();
}
private void PullLever()
{
isPulled = !isPulled;
SendSignal(isPulled, this.gameObject);
// 可选:拉杆动画
@@ -30,17 +41,7 @@ namespace Script.Gameplay.Facility
// 旋转拉杆回到初始位置
transform.rotation = Quaternion.Euler(0f, 0f, 0f);
}
//Debug.Log(isPulled ? "Lever pulled down" : "Lever reset");
}
public override void OnGazeEnter(GameObject go)
{
// 可选:高亮拉杆
}
public override void OnGazeExit(GameObject go)
{
// 可选:取消高亮
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using Core;
using UnityEngine;
using UnityEngine.Events;
namespace Script.Gameplay.Global
{
public struct StackOverflowBUGLog
{
public Transform TargetTransform;
public int BeTriggerCycle; // 触发时的游戏循环次数
public StackOverflowBUGLog(Transform targetTransform, int beTriggerCycle)
{
TargetTransform = targetTransform;
BeTriggerCycle = beTriggerCycle;
}
}
public class BUGManager : MonoSingleton<BUGManager>
{
[SerializeField] private GameObject bugCubePrefab;
[Header("天核区域检测设置")] [SerializeField] private Vector3 AreaCenter = Vector3.zero;
[SerializeField] private Vector3 AreaSize = new Vector3(50, 50, 50);
public UnityEvent OnBUGHappenedInArea;
private List<StackOverflowBUGLog> stackOverflowBugLogs = new List<StackOverflowBUGLog>();
private List<GameObject> bugCubes = new List<GameObject>();
private void Start()
{
OnBUGHappenedInArea.AddListener(() =>
{
Debug.LogWarning("天核区域内检测到BUG立方体");
});
GameManager.Instance.OnGameStart += GenerateBUGCubes;
}
public void LogStackOverflowBUG(Transform targetTransform)
{
int beTriggerCycle = GameDataManager.Instance.TotalLoopCount;
stackOverflowBugLogs.Add(new StackOverflowBUGLog(targetTransform, beTriggerCycle));
GameManager.Instance.ReStartGame();
}
public void GenerateBUGCubes()
{
bugCubes.Clear();
int currentCycle = GameDataManager.Instance.TotalLoopCount;
// 记录的BUG位置生成BUG立方体
foreach (var log in stackOverflowBugLogs)
{
if (bugCubePrefab != null)
{
// 仅在下一次循环生成BUG立方体
if (log.BeTriggerCycle + 1 != currentCycle) continue;
GameObject go = Instantiate(bugCubePrefab, log.TargetTransform.position, Quaternion.identity);
bugCubes.Add(go);
}
}
// 检测天核区域内是否有BUG立方体
Collider[] colliders = Physics.OverlapBox(AreaCenter, AreaSize * 0.5f);
foreach (var collider in colliders)
{
if (bugCubes.Contains(collider.gameObject))
{
OnBUGHappenedInArea?.Invoke();
break;
}
}
}
// 可视化天核区域
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1, 0, 0, 0.3f);
Gizmos.DrawCube(AreaCenter, AreaSize);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7600480a20114f90a3cf7fea47d0b897
timeCreated: 1761492538

View File

@@ -9,13 +9,18 @@ namespace Script.Gameplay.Global
private const string LoopCountKey = "TotalLoopCount";
public int TotalLoopCount { get; private set; }
private void Start()
protected override void Awake()
{
GameManager.Instance.OnGameStart += AddLoop;
base.Awake();
// 读取已保存的循环次数
TotalLoopCount = PlayerPrefs.GetInt(LoopCountKey, 0);
Debug.Log("目前循环次数:" + TotalLoopCount);
}
private void Start()
{
GameManager.Instance.OnGameStart += AddLoop;
}
public void AddLoop()
{

View File

@@ -41,7 +41,7 @@ namespace Script.Gameplay.Global
{
StartCoroutine(ScreenGlitchManager.Instance.TriggerGlitchEffect());
yield return new WaitForSeconds(1.0f);
GameManager.Instance.StartGameplay();
GameManager.Instance.ReStartGame();
}
}
}

View File

@@ -6,6 +6,7 @@ namespace Script.Gameplay.Global
public class GameManager : MonoSingleton<GameManager>
{
public event Action OnGameStart;
private string currentStartGameMode = "Level1";
private void Start()
{
ScenesManager.Instance.LoadMainMenu();
@@ -14,12 +15,20 @@ namespace Script.Gameplay.Global
public void StartGameplay()
{
ScenesManager.Instance.LoadGameplay("Level1");
currentStartGameMode = "Level1";
OnGameStart?.Invoke();
}
public void StartTest()
{
ScenesManager.Instance.LoadGameplay("Test");
currentStartGameMode = "Test";
OnGameStart?.Invoke();
}
public void ReStartGame()
{
ScenesManager.Instance.LoadGameplay(currentStartGameMode);
OnGameStart?.Invoke();
}
}

View File

@@ -0,0 +1,130 @@
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using Core;
using Script.Gameplay.Input;
namespace Script.Gameplay.Global
{
public class ScenesManager : MonoSingleton<ScenesManager>
{
private string currentGameplayScene;
private bool isLoadGameplayUI = false;
// 修正:直接根据当前激活场景判断
public bool IsInMainMenu =>
currentGameplayScene == "StartMenu";
// 修正:只有当已有记录的 gameplay 场景且激活场景与之匹配时才返回 true
public bool IsInGameplay =>
currentGameplayScene == "Level1" || currentGameplayScene == "Test";
public bool IsActiveScene(string sceneName)
{
return SceneManager.GetActiveScene().name == sceneName;
}
public bool IsSceneLoaded(string sceneName)
{
return SceneManager.GetSceneByName(sceneName).isLoaded;
}
public void LoadMainMenu()
{
if (isLoadGameplayUI)
{
StartCoroutine(UnloadScene("UIScene"));
isLoadGameplayUI = false;
}
StartCoroutine(SwitchGameplay("StartMenu"));
}
public void LoadGameplay(string sceneName)
{
StartCoroutine(SwitchGameplay(sceneName));
ReLoadUIScene();
}
public void ReLoadUIScene()
{
if (isLoadGameplayUI)
{
StartCoroutine(UnloadScene("UIScene"));
}
StartCoroutine(LoadSceneAdditive("UIScene"));
isLoadGameplayUI = true;
}
private IEnumerator SwitchGameplay(string newScene)
{
if (!string.IsNullOrEmpty(currentGameplayScene))
{
yield return UnloadScene(currentGameplayScene);
}
yield return LoadSceneAdditive(newScene);
currentGameplayScene = newScene;
// 可选:将新场景设置为激活场景
SceneManager.SetActiveScene(SceneManager.GetSceneByName(newScene));
}
private IEnumerator LoadSceneAdditive(string sceneName)
{
var async = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
while (!async.isDone)
{
yield return null;
}
}
private IEnumerator UnloadScene(string sceneName)
{
var async = SceneManager.UnloadSceneAsync(sceneName);
while (!async.isDone)
{
yield return null;
}
}
public IEnumerator UnloadScenesInOrder(List<string> sceneNames)
{
foreach (var sceneName in sceneNames)
{
if (IsSceneLoaded(sceneName))
{
var async = SceneManager.UnloadSceneAsync(sceneName);
while (!async.isDone)
{
yield return null;
}
}
}
}
public void QuitGame()
{
var scenesToUnload = new List<string> { "UIScene", currentGameplayScene };
StartCoroutine(QuitAndUnload(scenesToUnload));
}
private IEnumerator QuitAndUnload(List<string> scenes)
{
yield return UnloadScenesInOrder(scenes);
// 清理 InputManager
if (InputManager.Instance != null)
{
Destroy(InputManager.Instance.gameObject);
}
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9f0b4ab0cd384d91bfb99fd97c34ac5d
timeCreated: 1760401308

View File

@@ -2,6 +2,7 @@ using System;
using UnityEngine;
using UnityEngine.InputSystem;
using Core;
using Script.Gameplay.Global;
namespace Script.Gameplay.Input
{

View File

@@ -11,5 +11,31 @@ namespace Script.Gameplay.Interface
GameObject GetGameObject(); // 获取连接点物体
string GetConnectableName(); // 获取连接点名称
public List<ConnectionLine> ConnectionLines { get; set; }
// public static List<GameObject> GetConnectedGameObjects(GameObject sender)
// {
// IConnectable connectable = sender.GetComponent<IConnectable>();
// List<GameObject> connectedObjects = new List<GameObject>();
// if (connectable != null)
// {
// foreach (var target in connectable.ConnectionLines)
// {
// if (target != null && target.PointA != null && target.PointB != null)
// {
// // 排除掉AB点中与sender相同的点
// if (target.PointA.GetGameObject() != sender)
// {
// connectedObjects.Add(target.PointA.GetGameObject());
// }
//
// if (target.PointB.GetGameObject() != sender)
// {
// connectedObjects.Add(target.PointB.GetGameObject());
// }
// }
// }
// }
// return connectedObjects;
// }
}
}

View File

@@ -1,8 +1,41 @@
using System.Collections.Generic;
using UnityEngine;
namespace Script.Gameplay.Interface
{
public interface ISignalSender
{
public bool IsEnableSendSignal { get; set; }
public void SendSignal(bool active, GameObject sender);
public static bool IsSendToSignalSender(GameObject sender)
{
IConnectable connectable = sender.GetComponent<IConnectable>();
// 从连接线中查找是否有连接到ISignalSender的对象
if (connectable != null)
{
foreach (var connectionLine in connectable.ConnectionLines)
{
if (connectionLine != null)
{
GameObject targetObject = null;
if (connectionLine.PointA.GetGameObject() == sender)
{
targetObject = connectionLine.PointB.GetGameObject();
}
else if (connectionLine.PointB.GetGameObject() == sender)
{
targetObject = connectionLine.PointA.GetGameObject();
}
if (targetObject != null && targetObject.GetComponent<ISignalSender>().IsEnableSendSignal)
{
return true;
}
}
}
}
return false;
}
}
}

View File

@@ -5,6 +5,7 @@ using Script.Gameplay.Connect;
using Script.Gameplay.Interface;
using Script.Gameplay.Input;
using System;
using UnityEngine.InputSystem;
namespace Script.Gameplay.Player
{
@@ -66,10 +67,10 @@ namespace Script.Gameplay.Player
void Start()
{
inputManager = InputManager.Instance;
inputManager.Input.Player.SetOutput.performed += ctx => SetPointA(CurrentTarget);
inputManager.Input.Player.SetInput.performed += ctx => SetPointB(CurrentTarget);
inputManager.Input.Player.Connect.performed += ctx => CreateConnection();
inputManager.Input.Player.CutLine.performed += ctx => CutConnectLine(CurrentTarget);
inputManager.Input.Player.SetOutput.performed += OnSetOutputOnperformed;
inputManager.Input.Player.SetInput.performed += OnSetInputOnperformed;
inputManager.Input.Player.Connect.performed += OnConnectOnperformed;
inputManager.Input.Player.CutLine.performed += OnCutLineOnperformed;
if (raycaster == null)
raycaster = GetComponent<FirstPersonRaycaster>() ?? GetComponentInChildren<FirstPersonRaycaster>();
@@ -81,6 +82,26 @@ namespace Script.Gameplay.Player
ControllerLocator.Instance.Register(this);
}
private void OnCutLineOnperformed(InputAction.CallbackContext ctx)
{
CutConnectLine(CurrentTarget);
}
private void OnConnectOnperformed(InputAction.CallbackContext ctx)
{
CreateConnection();
}
private void OnSetInputOnperformed(InputAction.CallbackContext ctx)
{
SetPointB(CurrentTarget);
}
private void OnSetOutputOnperformed(InputAction.CallbackContext ctx)
{
SetPointA(CurrentTarget);
}
void Update()
{
DetectConnectable();
@@ -147,7 +168,17 @@ namespace Script.Gameplay.Player
Debug.Log("Cannot create connection: invalid targets.");
}
}
private void OnDestroy()
{
ControllerLocator.Instance.Unregister(this);
inputManager.Input.Player.SetOutput.performed -= OnSetOutputOnperformed;
inputManager.Input.Player.SetInput.performed -= OnSetInputOnperformed;
inputManager.Input.Player.Connect.performed -= OnConnectOnperformed;
inputManager.Input.Player.CutLine.performed -= OnCutLineOnperformed;
}
void OnDrawGizmos()
{
// 射线可视化交由 FirstPersonRaycaster 处理

View File

@@ -7,6 +7,7 @@ using UnityEngine.Events;
using Script.Gameplay.Interface;
using Script.Gameplay.Input;
using UI;
using UnityEngine.InputSystem;
namespace Script.Gameplay.Player
{
@@ -60,24 +61,28 @@ namespace Script.Gameplay.Player
inputManager = InputManager.Instance;
var input = inputManager.Input;
input.Player.Read.performed += ctx =>
{
if (!isEnablePlayerDialogue) return;
if (CurrentDialogueTarget == null) return;
if (isReadingDialogue) return;
BeginDialogue();
};
input.Player.ShowNextDialogue.performed += ctx =>
{
if (!isEnablePlayerDialogue) return;
if (CurrentDialogueTarget == null) return;
if (!isReadingDialogue) return;
PassNextDialogue();
};
input.Player.Read.performed += OnReadOnperformed;
input.Player.ShowNextDialogue.performed += OnShowNextDialogueOnperformed;
ControllerLocator.Instance.Register(this);
}
private void OnShowNextDialogueOnperformed(InputAction.CallbackContext ctx)
{
if (!isEnablePlayerDialogue) return;
if (CurrentDialogueTarget == null) return;
if (!isReadingDialogue) return;
PassNextDialogue();
}
private void OnReadOnperformed(InputAction.CallbackContext ctx)
{
if (!isEnablePlayerDialogue) return;
if (CurrentDialogueTarget == null) return;
if (isReadingDialogue) return;
BeginDialogue();
}
void Update()
{
DetectDialogue();
@@ -159,5 +164,17 @@ namespace Script.Gameplay.Player
CurrentDialogueTarget = null;
}
}
private void OnDestroy()
{
ControllerLocator.Instance.Unregister<PlayerDialogueController>(this);
if (inputManager != null)
{
var input = inputManager.Input;
input.Player.Read.performed -= OnReadOnperformed;
input.Player.ShowNextDialogue.performed -= OnShowNextDialogueOnperformed;
}
}
}
}

View File

@@ -5,6 +5,7 @@ using Script.Gameplay.Input;
using System;
using System.Collections.Generic;
using Script.Gameplay.Global;
using UnityEngine.InputSystem;
namespace Script.Gameplay.Player
{
@@ -48,7 +49,7 @@ namespace Script.Gameplay.Player
void Start()
{
inputManager = InputManager.Instance;
inputManager.Input.Player.Edit.performed += context => EditTarget();
inputManager.Input.Player.Edit.performed += OnEditOnperformed;
timePauseManager = TimePauseManager.Instance;
if (raycaster == null)
raycaster = GetComponent<FirstPersonRaycaster>() ?? GetComponentInChildren<FirstPersonRaycaster>();
@@ -59,6 +60,11 @@ namespace Script.Gameplay.Player
ControllerLocator.Instance.Register(this);
}
private void OnEditOnperformed(InputAction.CallbackContext context)
{
EditTarget();
}
void Update()
{
DetectInteractable();
@@ -110,6 +116,15 @@ namespace Script.Gameplay.Player
}
}
private void OnDestroy()
{
if (inputManager != null)
{
inputManager.Input.Player.Edit.performed -= OnEditOnperformed;
}
ControllerLocator.Instance.Unregister(this);
}
void OnDrawGizmos()
{
// 交由 FirstPersonRaycaster 绘制射线

View File

@@ -3,6 +3,7 @@ using Script.Gameplay.Interface;
using System;
using Core;
using Script.Gameplay.Input;
using UnityEngine.InputSystem;
namespace Script.Gameplay.Player
{
@@ -46,16 +47,19 @@ namespace Script.Gameplay.Player
Debug.LogWarning("FirstPersonRaycaster not found! Please assign or add it to the player.");
var input = InputManager.Instance.Input;
input.Player.Interact.performed += ctx =>
{
if(CurrentTarget == null) return;
if (!CurrentTarget.IsEnableInteract) return;
CurrentTarget.Interact(this.gameObject);
};
input.Player.Interact.performed += OnInteractOnperformed;
ControllerLocator.Instance.Register(this);
}
private void OnInteractOnperformed(InputAction.CallbackContext ctx)
{
if (CurrentTarget == null) return;
if (!CurrentTarget.IsEnableInteract) return;
CurrentTarget.Interact(this.gameObject);
}
void Update()
{
DetectInteractable();
@@ -81,6 +85,14 @@ namespace Script.Gameplay.Player
}
}
private void OnDestroy()
{
ControllerLocator.Instance.Unregister<PlayerInteractorController>(this);
var input = InputManager.Instance.Input;
input.Player.Interact.performed -= OnInteractOnperformed;
}
void OnDrawGizmos()
{
// 交由 FirstPersonRaycaster 绘制射线

View File

@@ -122,8 +122,11 @@ namespace Script.Gameplay.Player
{
ControllerLocator.Instance.Unregister<PlayerWatchModeController>(this);
var input = inputManager.Input;
input.Player.SwitchWatchMode.performed -= RegisterInput;
if (inputManager != null)
{
var input = inputManager.Input;
input.Player.SwitchWatchMode.performed -= RegisterInput;
}
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
@@ -138,5 +139,11 @@ namespace UI
//go.SendMessage("SetComponent", components[i], SendMessageOptions.DontRequireReceiver);
}
}
private void OnDestroy()
{
editController.OnBeginEditTarget -= OnBeginEdit;
editController.OnEndEditTarget -= OnEndEdit;
}
}
}

View File

@@ -5,6 +5,7 @@ using TMPro;
using UnityEngine;
using Script.Gameplay.Input;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace UI
{
@@ -12,6 +13,7 @@ namespace UI
{
[SerializeField] private TMP_Text TitleText;
[SerializeField] private TMP_Text ContentText;
[SerializeField] private Button reloadButton;
private InputManager inputManager;
protected override void Awake()
@@ -19,6 +21,16 @@ namespace UI
base.Awake();
inputManager = InputManager.Instance;
inputManager.Input.Player.Setting.performed += RegisterInput;
reloadButton.onClick.AddListener(OnReloadButtonClicked);
}
private void OnReloadButtonClicked()
{
GameManager.Instance.ReStartGame();
ContentText.text = "循环次数:" + GameDataManager.Instance.TotalLoopCount.ToString();
inputManager.SetCursorState(false, CursorLockMode.Locked);
inputManager.SetInputForLook(true);
inputManager.SetInputForMove(true);
}
private void RegisterInput(InputAction.CallbackContext ctx)
@@ -26,12 +38,14 @@ namespace UI
if (!isActiveAndEnabled)
{
Show();
inputManager.SetCursorState(true, CursorLockMode.Confined);
inputManager.SetInputForLook(false);
inputManager.SetInputForMove(false);
}
else
{
Hide();
inputManager.SetCursorState(false, CursorLockMode.Locked);
inputManager.SetInputForLook(true);
inputManager.SetInputForMove(true);
}