0
\$\begingroup\$

I started a 2D Boardgame project and my partner decided to go with state pattern for the entire project so basically we sat down and thought about the states we need and we implemented like each scene in Unity has some states a state manager and state factory we are also using zenject. the problem is that i don't really like this approach because of the amount of code for a single scene and its kind of limited for example if i needed to add a new gamemode that needs more state or less whatever... i simply cant use this system and if the 2 modes are alike somehow the codes inside each state becomes huge so many nested if's for scene navigation or applying different logic for different modes. and i'm on a hunt for a good pattern to tackle this issue but i'm also afraid of over engineering. So i came here for help. I also thought about an event system for passing data between classes on the same scene and talking to each other via events but i don't know if it's good to for the enitre project or how i should handle scene navigation properly. so if events are the way to go tell me how can i use them.

Exmaple

so the game im working on is yahtzee and here are couple of the states some have controllers but others have the logic and states together and i know its a bad habit but i didnt have time :)

public class IdleState : IStateEntity
{        
    [Inject] private InGameHandler _inGameHandler;

    public void Initialize()
    {
        LogController.Log("+Initialize State: InGame.Idle");
        if (_inGameHandler.GameModel.gameType != GameType.TutorialGame)
        {
            _inGameHandler.RollDiceButtonActive.SetValueAndForceNotify(true);
        }
    }
    public void Dispose()
    {
        LogController.Log("-Dispose State: InGame.Idle");
    }
    public class Factory : PlaceholderFactory<IdleState>
    {
    }
}

and IStateEntity

public interface IStateEntity
{
    void Initialize();
    void Dispose();
}

so as you can see thats how my states look like and here is a state manager

public class InGameStateManager : IInitializable
{
    [Inject] private GameStateManager _gameStateManager;
    public InGameStateEntity _currentState = InGameStateEntity.WaitForStart;
    public InGameStateEntity _previousState;
    private readonly InGameStateFactory _inGameStateFactory;
    private IStateEntity _inGameStateEntity;

    public InGameStateManager(InGameStateFactory inGameStateFactory)
    {
        _inGameStateFactory = inGameStateFactory;
    }

    public void Initialize()
    {
        if(_gameStateManager.CurrentState.Value !=GameStateEntity.Tutorial )
            ChangeState(InGameStateEntity.Intro);

    }

    public void ChangeState(InGameStateEntity stateEntity)
    {
        //if we're in already in stateEntity dont change it
        if (stateEntity == _currentState)
        {
            LogController.Log("Already in State: " + _currentState);
            return;
        }

        //if we're in some state and this not the first time we're entering this phase then dispose current state and make it null
        if (_inGameStateEntity != null)
        {
            _inGameStateEntity.Dispose();
            _inGameStateEntity = null;
        }

        _previousState = _currentState;
        _currentState = stateEntity;

        //initializing stateEntity and assigning it to _InGameStateEntity which represents the current state.
        Debug.Log("from state " + _previousState + " to " + stateEntity);
        _inGameStateEntity = _inGameStateFactory.CreateState(stateEntity);
    }
}

and InGameStateEntity is an enum which is all the states for my gameplay

public enum InGameStateEntity
{
    WaitForStart,
    Intro,
    OpponentMove,
    Idle,
    RollDice,
    BonusRoll,
    Yahtzee,
    PrimarySubmission,
    CheckItem,
    FinalSubmission,
    CheckSectionBonus,
    SaveProgress,
    GameList,
    Outro,
    GetMoreDice,
    ResetTurn
}

I didn't want to send it but whatever i really need help i'm kind of a newbie so don't judge me this is my first real project and the code for intro is so messed up lemme show you...

public class IntroState : IStateEntity
{
    [Inject] private InGameIntroCanvasController.Factory _introCanvasControllerFactory;
    [Inject] private InGameStateManager _inGameStateManager;
    [Inject] private InGameHandler _inGameHandler;
    [Inject] private DicePlaceHolderManager _dicePlaceHolderManager;
    private InGameIntroCanvasController _controller;
    [Inject] private PlayerPrefsManager _playerPrefsManager;
    [Inject] private DiceRollController _diceRollController;
    [Inject] private GlobalVariables _globalVariables;
    [Inject] private SFXManager _sfxManager;
    [Inject] private InGameSprites _inGameSprites;

    public void Initialize()
    {
        LogController.Log("+Initialize State: InGame.Intro");
        _dicePlaceHolderManager.Initialize();

        if (_inGameHandler.IsDiceWorld.Value)
        {
            if (!_inGameHandler.GameModel.hasIntroShown)
            {
                ShowIntro();
                Observable.FromCoroutine(() => IntroCountDown(3.5f)).Subscribe();
            }
            else
                DiceWorldIntroWithoutEnumerator();
            return;
        }
        if (_inGameHandler.GameModel.gameType == GameType.TutorialGame)
        {
            if (!_inGameHandler.GameModel.hasIntroShown)
            {
                ShowIntro();
                Observable.FromCoroutine(() => IntroCountDown(3.5f)).Subscribe();
            }
            else
                DiceWorldIntroWithoutEnumerator();
            return;
        }
        if (!_inGameHandler.GameModel.hasIntroShown && _inGameHandler.IsPlayerTurn.Value)
        {
           ShowIntro();
            Observable.FromCoroutine(() => IntroCountDown(4)).Subscribe();
        }
        else
            IntroWithoutEnumerator();
    }

    private void ShowIntro()
    {
        _inGameHandler.GameModel.hasIntroShown = true;
        _playerPrefsManager.GameModelData.GameModel = _inGameHandler.GameModel;
        _inGameHandler.RollDiceButtonActive.SetValueAndForceNotify(false);
        _controller = _introCanvasControllerFactory.Create();
        string nickname = _globalVariables.UserProfile.Nickname;
        int level = _globalVariables.UserProfile.Level;
        _controller.playerProfile.nickName.text = nickname;
        _controller.playerProfile.level.text = level.ToString();

        if (_inGameHandler.IsDiceWorld.Value)
        {
            SetBossAvatar(_globalVariables.CurrentRegion);
        }
        else if (_inGameHandler.GameModel.gameType == GameType.TutorialGame)
        {
            SetPlayerAvatar(_inGameSprites.DogAvatar);
            SetOpponentAvatar(_inGameSprites.CatAvatar);
        }
        nickname = _inGameHandler.GameModel.opponent.nickname;
        level = _inGameHandler.GameModel.opponent.level;
        _controller.opponentProfile.nickName.text = nickname == null || nickname.Equals("") ? "در حال جستجو" : nickname;
        _controller.opponentProfile.level.text = (level == 0  ? 1 : level).ToString();

        _controller.playerProfile.SelectedDice
            .DoIntroMoveIn()
            .Join(_controller.opponentProfile.SelectedDice
                .DoIntroMoveIn())
            .OnStart(() =>
            {
                Observable.FromCoroutine(() => _controller.PlayParticle(.8f)).Subscribe();
                _controller.PrizePlaceholderUp(.5f);
                _sfxManager.PlayInGameIntro(.5f);
            });

        // _controller.playerProfile.SelectedDice.ProfileMoveIn()
        //     .Join(_controller.opponentProfile.ProfileMoveIn())
        //     .OnStart(() =>
        //     {
        //         
        //     });
    }




    private void SetBossAvatar(RegionData region)
    {

        if(region.boss.avatar == null)
            return;
        _controller.opponentProfile.avatar.sprite = region.boss.avatar;
    }

    private void SetOpponentAvatar(Sprite sprite)
    {
        _controller.opponentProfile.avatar.sprite = sprite;
    }
    private void SetPlayerAvatar(Sprite sprite)
    {
        _controller.playerProfile.avatar.sprite = sprite;
    }

    void DiceWorldIntroWithoutEnumerator()
    {
        _diceRollController.ShowLastRolledDice();
        _inGameStateManager.ChangeState(_inGameHandler.PlayerTurn() == false
            ? InGameStateEntity.OpponentMove
            : InGameStateEntity.Idle);
    }

    void IntroWithoutEnumerator()
    {
        if (_inGameHandler.IsPlayerTurn.Value)
        {
            if (_inGameHandler.GameModel.lastUpdatedCell != null && !_inGameHandler.GameModel.hasOpponentMoveShown)
                _inGameStateManager.ChangeState(InGameStateEntity.OpponentMove);
            else
            {
                _diceRollController.ShowLastRolledDice();
                _inGameStateManager.ChangeState(_inGameHandler.GameModel.isWaiting
                    ? InGameStateEntity.GameList
                    : InGameStateEntity.Idle);
            }
        }
        else
        {
            if (!_inGameHandler.HasOpponentMoved())
            {
                _inGameHandler.RollDiceButtonActive.SetValueAndForceNotify(false);
                _inGameHandler.PlayButtonActive.SetValueAndForceNotify(false);
                // _inGameHandler.OpponentAvailablePlaceHolders[_inGameHandler.GameModelData.lastUpdatedCell.cellId].opponentPlaceHolder.GetComponentInChildren<RTLTextMeshPro>().text = _inGameHandler.GameModelData.lastUpdatedCell.score + "";
                _inGameStateManager.ChangeState(InGameStateEntity.GameList);    
            }
            else if(_inGameHandler.GameModel.hasOpponentMoveShown)
            {
                _inGameHandler.RollDiceButtonActive.SetValueAndForceNotify(false);
                _inGameHandler.PlayButtonActive.SetValueAndForceNotify(false);
                _inGameStateManager.ChangeState(InGameStateEntity.OpponentMove);
            }
        }
    }
    IEnumerator IntroCountDown(float delay)
    {
        yield return new WaitForSeconds(delay);
        _diceRollController.ShowLastRolledDice();
        if (_inGameHandler.IsDiceWorld.Value)
        {
            _inGameStateManager.ChangeState(_inGameHandler.IsPlayerTurn.Value
                ? InGameStateEntity.Idle
                : InGameStateEntity.OpponentMove);
        }
        else if (_inGameHandler.IsPlayerTurn.Value)
        {
            _inGameStateManager.ChangeState(_inGameHandler.GameModel.lastUpdatedCell != null
                ? InGameStateEntity.OpponentMove
                : InGameStateEntity.Idle);
        }
        else
        {
            if (!_inGameHandler.HasOpponentMoved())
            {
                _inGameHandler.RollDiceButtonActive.SetValueAndForceNotify(false);
                _inGameHandler.PlayButtonActive.SetValueAndForceNotify(false);
                _inGameHandler.OpponentAvailablePlaceHolders[_inGameHandler.GameModel.lastUpdatedCell.cellId].opponentPlaceHolder.GetComponentInChildren<RTLTextMeshPro>().text = _inGameHandler.GameModel.lastUpdatedCell.score + "";
                _inGameStateManager.ChangeState(InGameStateEntity.GameList);
            }
            else
            {
                _inGameHandler.RollDiceButtonActive.SetValueAndForceNotify(false);
                _inGameHandler.PlayButtonActive.SetValueAndForceNotify(false);
                _inGameStateManager.ChangeState(InGameStateEntity.OpponentMove);
            }

        }
    }

    public void Dispose()
    {
        if(_controller != null)
            _controller.Destroy();
        LogController.Log("-Dispose State: InGame.Intro");
    }
    public class Factory : PlaceholderFactory<IntroState>
    {
    }
}

I know that part of it is my fault for not separating the controllers from states but i really dont like states or if this is ok what is the best approach for making it reusable. just think about adding new intro i need to add bunch of if statements if based on the game type

BTW InGameHandler is a gameobject in the ingame scene and all the data i need for the game is inside that kinda silly and if you know a better way lemme know and dont be hard on me ok ? :))))

InGameStateFactory:

public class InGameStateFactory
{
    [Inject] private IntroState.Factory _introStateFactory;
    [Inject] private OpponentMoveState.Factory _opponentMoveStateFactory;
    [Inject] private IdleState.Factory _idleStateFactory;
    [Inject] private RollDiceState.Factory _rollDiceStateFactory;
    [Inject] private BonusRollState.Factory _bonusRollStateFactory;
    [Inject] private DicerState.Factory _yahtzeeStateFactory;
    [Inject] private PrimarySubmissionState.Factory _primarySubmissionStateFactory;
    [Inject] private CheckItemState.Factory _checkItemStateFactory;
    [Inject] private FinalSubmissionState.Factory _finalSubmissionStateFactory;
    [Inject] private CheckSectionBonusState.Factory _checkSectionBonusStateFactory;
    [Inject] private SaveProgressState.Factory _saveProgressStateFactory;
    [Inject] private GameListState.Factory _gameListStateFactory;
    [Inject] private OutroState.Factory _outroStateFactory;
    [Inject] private ResetTurnState.Factory _resetTurnFactory;
    [Inject] private GetMoreDiceState.Factory _getMoreDiceFactory;

    public IStateEntity CreateState(InGameStateEntity inGameStateEntity)
    {
        IStateEntity stateEntity = null;
        switch (inGameStateEntity)
        {
            case InGameStateEntity.Intro:
                stateEntity = _introStateFactory.Create();
                break;
            case InGameStateEntity.OpponentMove:
                stateEntity = _opponentMoveStateFactory.Create();
                break;
            case InGameStateEntity.Idle:
                stateEntity = _idleStateFactory.Create();
                break;
            case InGameStateEntity.RollDice:
                stateEntity = _rollDiceStateFactory.Create();
                break;
            case InGameStateEntity.BonusRoll:
                stateEntity = _bonusRollStateFactory.Create();
                break;
            case InGameStateEntity.Dicer:
                stateEntity = _yahtzeeStateFactory.Create();
                break;
            case InGameStateEntity.PrimarySubmission:
                stateEntity = _primarySubmissionStateFactory.Create();
                break;
            case InGameStateEntity.CheckItem:
                stateEntity = _checkItemStateFactory.Create();
                break;
            case InGameStateEntity.FinalSubmission:
                stateEntity = _finalSubmissionStateFactory.Create();
                break;
            case InGameStateEntity.CheckSectionBonus:
                stateEntity = _checkSectionBonusStateFactory.Create();
                break;
            case InGameStateEntity.SaveProgress:
                stateEntity = _saveProgressStateFactory.Create();
                break;
            case InGameStateEntity.GameList:
                stateEntity = _gameListStateFactory.Create();
                break;
            case InGameStateEntity.Outro:
                stateEntity = _outroStateFactory.Create();
                break;
            case InGameStateEntity.ResetTurn:
                stateEntity = _resetTurnFactory.Create();
                break;
            case InGameStateEntity.GetMoreDice:
                stateEntity = _getMoreDiceFactory.Create();
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(inGameStateEntity), inGameStateEntity, null);
        }
        if (stateEntity == null)
                throw Assert.CreateException("InGameState Not Found And This Code Should Not Be Reached There :/ !");
        stateEntity.Initialize();
        return stateEntity;
    }

PS: i'm using zenject , unirx and the game is connected to a server.

\$\endgroup\$
  • 3
    \$\begingroup\$ A state machine would also be my first approach to most kinds of board game. Maybe the problem is not in the idea of using state machines but rather in your execution? Can you give a concrete example of code which looks ugly in this architecture? Perhaps we can show you a way to do this in a better way. \$\endgroup\$ – Philipp Mar 17 '20 at 12:47
  • 1
    \$\begingroup\$ "somehow the codes inside each state becomes huge so many nested if's for scene navigation or applying different logic for different modes", It might be a stab in the dark, however, if I were you, I would try to fit an strategy pattern. You may also be interested in a blackboard pattern, and yes an event system. However, without more details I have no clue to what extend they are useful in your case. Please note that I am not saying to get rid of the state machine, yet extending it should be easy, and the code for each state should not be complex. \$\endgroup\$ – Theraot Mar 17 '20 at 13:13
  • \$\begingroup\$ so lets say i want to have different modes or even a tutorial we all know tutorials are hard i dont want make a brand new FSM for just that i have the scene i have the logic only im using them differently and maybe even a mode that has different logic but on the same scene and UI i dont want to create another FSM for this mode too and if i want make it happen with one FSM which im doing right now its a pain so many if statement if this game mode is tutorial do this if its offline mode do this if its online do this its a pain really. \$\endgroup\$ – Sina Shabani Mar 17 '20 at 13:44
  • 2
    \$\begingroup\$ It is really difficult to help you when you don't show us any actual code. Maybe hierarchical state machines could help you? Or maybe different state machines for different aspects of the game? Maybe you could avoid code duplication and if/else sections by using polymorphy and inheritance (states inheriting from other states)? Or maybe this is actually an interpersonal problem between you and your partner when it comes to articulating your requirements and architecture ideas? \$\endgroup\$ – Philipp Mar 17 '20 at 14:56
  • \$\begingroup\$ Ok i will show some samples as soon as i get home \$\endgroup\$ – Sina Shabani Mar 17 '20 at 15:37
1
\$\begingroup\$

I think the problem could be that your IStateEntity classes are god objects for their respective states. They know too much, do too much and get too far into details about how these things happen. You could make them a lot more lightweight and modular by delegating most of their responsibilities to other MonoBehaviours.

When I use the state-machine pattern in Unity, then my states are not so much concerned with what happens in the states, but rather with what that state does and does not have compared to other states. So my states usually don't do much more than call .SetActive(true) and .SetActive(false) on a couple game objects. These objects then take care of what actually happens in the states while they are active.

Taking the intro scene, for example: The "boss avatar" logic should really be an own script. The state would just enable the "boss avatar" gameObject. That gameObject would then have a script with an OnEnable function which decides what sprite to pick. This has the advantage that you can easily reuse this gameObject for another state. It contains its own logic, so you don't need to duplicate its logic over all the states which use it.

Same with the separate sub-state-machine in IntroWithoutEnumerator which handles the logic for the intro. I would really put that in a separate script. The intro-state would enable the "introController" gameObject and the IntroController MonoBehaviour on that object would then manage the intro logic. That script would also decide when to transition to the next state. So no IntroCountDown. That logic belongs in the IntroController.

It also appears to me that this intro logic duplicates a lot of the logic which might also be used by the actual game. In that case it might make sense to move the duplicate logic into an own gameObject which exists in both states. The logic which is specific to the intro and the logic which is specific to the actual game would go into two separate objects.

And why do you have a InGameIntroCanvasController which apparently doesn't have control over anything?

    string nickname = _globalVariables.UserProfile.Nickname;
    int level = _globalVariables.UserProfile.Level;
    _controller.playerProfile.nickName.text = nickname;
    _controller.playerProfile.level.text = level.ToString();
    _controller.opponentProfile.nickName.text = nickname == null || nickname.Equals("") ? "در حال جستجو" : nickname;
    _controller.opponentProfile.level.text = (level == 0  ? 1 : level).ToString();

These all appear to be very tightly coupled calls which require that the state knows a lot of details about the internal workings of the canvas controller. The purpose of the controller pattern is that you only need to tell the controller object very high-level information about what's going on and then the controller decides on its own what to do with that information. So these lines should be moved into the controller. All this controller seems to need to execute these lines is _globalVariables.UserProfile. You could just pass to it when you create it and then let the controller decide internally what to do with that information. Or considering that it's a dependency which you can inject anywhere, you could also have the controller obtain that dependency itself.

\$\endgroup\$
  • \$\begingroup\$ You are right my problem with state will be solved with extracting the heavy code from logic but what about diffrent logic based on diffrent game types ? \$\endgroup\$ – Sina Shabani Mar 18 '20 at 10:02
  • \$\begingroup\$ @SinaShabani You might not have read my most recent edit yet. In that case you would put all the logic these game types have in common in one script and all the mode-specific logic into a separate script for each mode. You would then always have two scripts active: the common script and one of the specific scripts. An alternative approach would be through inheritance. Create an abstract base-class for all the game modes which implements the common logic and derived classes for the modes which override a couple methods of the base class with the mode-specific logic. \$\endgroup\$ – Philipp Mar 18 '20 at 10:04
  • \$\begingroup\$ Well i think you might be right i will try it thank you. \$\endgroup\$ – Sina Shabani Mar 18 '20 at 11:40
  • \$\begingroup\$ @SinaShabani When this answer answered your question, please accept it with the checkmark-icon next to it. \$\endgroup\$ – Philipp Mar 19 '20 at 16:19
0
\$\begingroup\$

Here is an idea: 1. extract methods to reduce code duplication (instead of having the same code in two places, make a method and call it in two places). 2. configure your game state factory to consider the game type. Then you can have a base game state entity for each game state, and derive (read: inherit) from it to make specialized game state entities for each game type. I'm assuming game type changes less often than game state. 3. Those methods you extracted? Yeah, make them virtual, so the derived types can override them. I hope that is enough to get rid of most nested if statements.

Theraot had a good point the first part helped me to figure out how make the code cleaner by extracting different logic into new files and adding a controller for the state so state should only calls the controller and nothing more. then the controller decides what to do with view, models and most importantly the FSM. so based on this states cant change FSM's current. and after that i creted.

Intro:

So i ended up changing the state factory and add more files that derive from IntroState as the base class and made the Initialize and Dispose methods virtual so my Factory looks something like this now

public class InGameStateFactory
{
    [Inject] private GlobalVariables _globalVariables;

    //intro
    [Inject] private IntroState.Factory _introStateFactory;
    [Inject] private NoIntroState.Factory _noIntroStateFactory;
    [Inject] private CampaignIntroState.Factory _campaignIntroStateFactory;
    [Inject] private TutorialIntroState.Factory _tutorialStateFactory;


    [Inject] private OpponentMoveState.Factory _opponentMoveStateFactory;
    [Inject] private IdleState.Factory _idleStateFactory;
    [Inject] private RollDiceState.Factory _rollDiceStateFactory;
    [Inject] private BonusRollState.Factory _bonusRollStateFactory;
    [Inject] private DicerState.Factory _yahtzeeStateFactory;
    [Inject] private PrimarySubmissionState.Factory _primarySubmissionStateFactory;
    [Inject] private CheckItemState.Factory _checkItemStateFactory;
    [Inject] private FinalSubmissionState.Factory _finalSubmissionStateFactory;
    [Inject] private CheckSectionBonusState.Factory _checkSectionBonusStateFactory;
    [Inject] private SaveProgressState.Factory _saveProgressStateFactory;
    [Inject] private GameListState.Factory _gameListStateFactory;
    [Inject] private OutroState.Factory _outroStateFactory;
    [Inject] private ResetTurnState.Factory _resetTurnFactory;
    [Inject] private GetMoreDiceState.Factory _getMoreDiceFactory;

    public IStateEntity CreateState(InGameStateEntity inGameStateEntity)
    {
        IStateEntity stateEntity = null;
        switch (inGameStateEntity)
        {
            case InGameStateEntity.Intro:
                stateEntity = ChooseIntroState();
                break;
            case InGameStateEntity.OpponentMove:
                stateEntity = _opponentMoveStateFactory.Create();
                break;
            case InGameStateEntity.Idle:
                stateEntity = _idleStateFactory.Create();
                break;
            case InGameStateEntity.RollDice:
                stateEntity = _rollDiceStateFactory.Create();
                break;
            case InGameStateEntity.BonusRoll:
                stateEntity = _bonusRollStateFactory.Create();
                break;
            case InGameStateEntity.Dicer:
                stateEntity = _yahtzeeStateFactory.Create();
                break;
            case InGameStateEntity.PrimarySubmission:
                stateEntity = _primarySubmissionStateFactory.Create();
                break;
            case InGameStateEntity.CheckItem:
                stateEntity = _checkItemStateFactory.Create();
                break;
            case InGameStateEntity.FinalSubmission:
                stateEntity = _finalSubmissionStateFactory.Create();
                break;
            case InGameStateEntity.CheckSectionBonus:
                stateEntity = _checkSectionBonusStateFactory.Create();
                break;
            case InGameStateEntity.SaveProgress:
                stateEntity = _saveProgressStateFactory.Create();
                break;
            case InGameStateEntity.GameList:
                stateEntity = _gameListStateFactory.Create();
                break;
            case InGameStateEntity.Outro:
                stateEntity = _outroStateFactory.Create();
                break;
            case InGameStateEntity.ResetTurn:
                stateEntity = _resetTurnFactory.Create();
                break;
            case InGameStateEntity.GetMoreDice:
                stateEntity = _getMoreDiceFactory.Create();
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(inGameStateEntity), inGameStateEntity, null);
        }
        if (stateEntity == null)
                throw Assert.CreateException("InGameState Not Found And This Code Should Not Be Reached There :/ !");
        stateEntity.Initialize();
        return stateEntity;
    }

    private IntroState ChooseIntroState()
    {
        var type = _globalVariables.CurrentPvPGameData.gameType;

        var hasShownIntro = _globalVariables.CurrentPvPGameData.hasIntroShown;
        if (hasShownIntro)
            return _noIntroStateFactory.Create();

        switch (type)
        {
            case GameType.Campaing:
                return _diceWorldIntroStateFactory.Create();
            case GameType.PvP:
                return _introStateFactory.Create();
            case GameType.Tutorial:
                return _tutorialStateFactory.Create();
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

This with extracting code and move them to different files cleaned states like a boss. My next problem however was complicated models and static. i had only one model for all of the modes so for example i didn't have a model for creating a one-player-only game like tutorial and my approach was to set the opponent as null and preform null checks left and right. First of all don't do that! Create a base model that contains the minimum data you need the create classes deriving this model with additional data. This however works if your models have some similarity if every thing is different you need a new model for sure.

Thank You.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.