Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/unity3d/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
切换案例与事件:游戏状态更新(UNITY,C#)_C#_Unity3d_Events_Switch Statement_State - Fatal编程技术网

切换案例与事件:游戏状态更新(UNITY,C#)

切换案例与事件:游戏状态更新(UNITY,C#),c#,unity3d,events,switch-statement,state,C#,Unity3d,Events,Switch Statement,State,假设我有一个Unity游戏,有多个管理者(游戏、GUI、声音、相机、输入、地形等),通过Singleton模式实例化 当GameManager调用(或被调用)更改游戏状态(简介、教程、游戏玩法等)时,它是否应该通过事件通知其他经理新的游戏状态,或者更确切地说是处理它并在切换案例中对每个经理执行特定操作 使用开关箱: 或使用事件: 这里的最佳实践/可维护模式是什么?从您的游戏当前的结构听起来,我认为最好在GameManager中处理对游戏状态的更改(例如,使用switch语句),而不是通过事件通知

假设我有一个Unity游戏,有多个管理者
(游戏、GUI、声音、相机、输入、地形等)
,通过Singleton模式实例化

当GameManager调用(或被调用)更改游戏状态
(简介、教程、游戏玩法等)
时,它是否应该通过事件通知其他经理新的游戏状态,或者更确切地说是处理它并在切换案例中对每个经理执行特定操作

使用开关箱:

或使用事件:


这里的最佳实践/可维护模式是什么?

从您的游戏当前的结构听起来,我认为最好在
GameManager
中处理对游戏状态的更改(例如,使用switch语句),而不是通过事件通知其他管理者新的游戏状态。这是因为我认为其他经理不应该知道比赛状态是什么。例如,假设您希望对教程游戏状态进行更改,以涉及摄影机和声音管理器的操作:

public void ChangeGameState(GameState newGameState)
{
    switch(state)
    {
    case GameState.Intro:
        //do stuff
        break;

    case GameState.Tutorial:
        cameraManager.MoveToTutorialPosition();
        soundManager.PlayTutorialAudio();
        break;

    case GameState.Gameplay:
        //do stuff
        break;
    }

    previousGameState = currentGameState;
    currentGameState = newGameState;
}
我认为,与通过事件将游戏状态更改传达给其他每位经理相比,上述内容更可取,因为您的每位经理都必须知道如何处理每个
游戏状态。与以下其他方法相比:

public class CameraManager
{
    public void UpdateGameState(GameState gameState)
    {
        switch (gameState)
        {
            case GameState.Intro:
                // do stuff
                break;
            case GameState.Tutorial:
                MoveToTutorialPosition();
                break;
            case GameState.GamePlay:
                // do stuff
                break;
        }
    }
}

public class SoundManager
{
    public void UpdateGameState(GameState gameState)
    {
        switch (gameState)
        {
            case GameState.Intro:
                // do stuff
                break;
            case GameState.Tutorial:
                PlayTutorialAudio();
                break;
            case GameState.GamePlay:
                // do stuff
                break;
        }
    }
}
请注意,如果您的
GameManager
中有很多逻辑,那么开始重构以进一步分解类可能是有意义的。例如:

  • 您可以使用状态设计模式代替switch语句,将每个开关案例表示为其自己的类,而不是使用枚举值。(有关详细信息,请参阅。)
  • 虽然我在这里指出,除了
    GameManager
    之外的其他管理者不应该知道
    GameState
    ,但每个管理者也应该有自己的状态,这取决于管理者的复杂性。例如,
    CameraState
    可能很有用

(我希望我正确地理解了你的问题。我对此不是100%肯定,所以我也希望听到其他答案或反馈。)

你的第一种方法不太可取,因为它在类之间创建了一个硬依赖关系,它看起来无害,但往往导致以后很难打破的相互依赖关系。显然,你需要在某个地方建立链接,但是GameManager硬编码提到了处理声音或相机的类,这显然违反了关注点分离,理想情况下,游戏经理不应该关心其他类在做什么,让cameraManager和soundManager订阅gameManager事件要好得多-至少你正在创建两个点,其中一个类引用另一个类,这比创建一个点,其中一个类引用另两个类要好得多-想象一下以后你有10个管理者,如果您在代码中的某个地方有一个列表,这并不是一个很好的迹象,因为这将变得越来越复杂,难以管理

如果将其拆分,则可以将与要使用摄影机执行的操作相关的功能封装在摄影机脚本中的开关中,并将声音功能封装在声音脚本中的另一个开关中。这听起来像是用开关盒复制代码,但其他开关盒将比控制状态变化图的主开关盒简单得多,例如,当从状态C切换到状态D时,您可能只会播放一个声音,而您只在状态a和C下移动相机,尽量不要编写与少数关注点重叠的代码

在这个问题上(我花了大量时间思考各种解决方案,大多数方法都有好的一面和坏的一面),下面是我首选的方法(这是我个人的偏好:)

考虑到您仍然需要保留变量以了解上一个和下一个状态是什么,因为可能有其他脚本稍后会变为唤醒状态,并且错过事件的第一个x激发,它们需要能够订阅未来的状态更改事件,并知道当前和上一个状态是什么。这意味着您仍然需要静态变量,这可以理解为没有单一的真理来源。假设您以后犯了一个错误,您忘记触发一个事件,或者(如上所述)当您触发时目标脚本尚未订阅该事件,并且该脚本从变量获取状态信息,而其他脚本根据自己的状态缓存(由委托通知)获取状态信息。这可能会导致应用程序内的状态不一致(某个模块可能认为您与其他模块处于不同的状态)。这没有错,只是感觉有点危险

我通常解决这个问题的方法是在状态改变时触发一个无参数事件(System.Action)(您甚至可以将这两个事件绑定在一起,以便通过setter自动触发事件,即

    public static System.Action OnGameStateChange;
    protected static GameState _currentGameState;
    public static GameState previousGameState;
    public static GameState currentGameState
       { get { return _currentGameState;} 
         set { previousGameState=_currentGameState;
              _currentGameState=value;
              if (OnGameStateChange!=null) OnGameStateChange.Invoke(); } } 
通过这种方式,如果更改字段,您总是会得到事件,但处于不一致状态的风险较小(例如,相同的对象缺少更新) 脚本始终可以安全地引用公共字段(它将是该正确值的唯一真实来源),并且无论您如何更改该值,都会通知所有侦听器。您还可以保护setter,以便不能从game manager类外部调用它


最好是有一点不自信,假设你是个白痴,试着保护代码不让自己在六周后把它弄糟,因为你可能不完全记得你决定选择哪种方法以及为什么,最好是代码引导你以一致的方式编写新代码。

这取决于你的想法如果您不需要在发生事件时通知其他脚本,则无需在此处使用事件。如果您需要在发生事件时通知其他脚本,则
public class CameraManager
{
    public void UpdateGameState(GameState gameState)
    {
        switch (gameState)
        {
            case GameState.Intro:
                // do stuff
                break;
            case GameState.Tutorial:
                MoveToTutorialPosition();
                break;
            case GameState.GamePlay:
                // do stuff
                break;
        }
    }
}

public class SoundManager
{
    public void UpdateGameState(GameState gameState)
    {
        switch (gameState)
        {
            case GameState.Intro:
                // do stuff
                break;
            case GameState.Tutorial:
                PlayTutorialAudio();
                break;
            case GameState.GamePlay:
                // do stuff
                break;
        }
    }
}
    public static System.Action OnGameStateChange;
    protected static GameState _currentGameState;
    public static GameState previousGameState;
    public static GameState currentGameState
       { get { return _currentGameState;} 
         set { previousGameState=_currentGameState;
              _currentGameState=value;
              if (OnGameStateChange!=null) OnGameStateChange.Invoke(); } }