1

I have a basic GameController with a finite state machine to handle game logic.

I'm adding game modes and coming across problems cleanly implementing them. The core functionality of the game stays the same--Physics, input handling, level creation--but I have many places where I need an if/else block because functionality is slightly different. At two game modes it was manageable but not the nicest implementation. I'm adding an extra game mode and want to clean it up.

Here's some simplified example code (in C#):

void OnBlocksCleared(int count) {
    score += getScoreFromClearedBlocks(count);
    scoreController.setScore(score);

    if (currentMode == GameMode.Endless) {
        DataManager.Instance.AddTotalBlocksCleared(count);
    } else if (currentMode == GameMode.Rush) {
        // Do nothing
    } else if (currentMode == GameMode.TimeAttack) {
        if (allBlocks.Count <= 0) {
            currentState = GameState.Win;
        }
    }
}

There are methods that have much more logic and only a single line if/else for a specific game mode (if TimeAttack time is up, if Rush level is completed, etc), and some methods like above where the if/else blocks take a lot of space. I was thinking about implementing an interface for each game mode but it wasn't easy to cleanly implement. Here is what I was planning:

public interface IGameBehavior {
    void SetHighscore();
    void OnBlocksCleared(int count);
    void SetRemainingTime(float remainingTime);
}

Then I would have a EndlessBehavior, TimeAttackBehavior, RushBehavior and at the bottom of corresponding methods call the specific method like so.

void OnBlocksCleared(int count) {
    // Do everything that's shared

    gameBehavior.OnBlocksCleared(count);
}

This doesn't seem to work well because if the behavior needs to modify specifics of the gameController it will become tightly coupled. Also in many cases only one or two of the game behaviors actually do anything.

1 Answer 1

0

There are certain aspects of design that have to be tightly coupled. The challenge of course is to do it in such a way that if you change something in one place, it doesn't break something in another place. When you are dealing with choices like this, sometimes you need to take a step back. You can ask yourself questions like this:

  • Would it break my game design if I put the currentState in DataManager.Instance? If I just bound the current state from DataManager to a property on my game controller, that might satisfy my needs.
  • Should I add an interface to my game controller? It allows you to separate the interface of what your IGameBehavior can do to a controller from the controller itself.
  • Is there a way I can tweak what I have so the roles and responsibilities are more clearly defined without a complete rewrite?

I'm sure there are more questions you can ask in this scenario. There is nothing inherently wrong with making the IGameBehavior tied to your game controller, assuming it's the only one. However, if you want to control how IGameBehavior has influence on the controller, then it might make sense to introduce an IGameController interface of your own.

With the example you had, you could have something like this:

public interface IGameController
{
    GameState CurrentState { get; set; }
    ICollection AllBlocks { get; }
}

If we take the IGameBehavior you defined in the OP you could have a TimeAttackBehavior implemented like this:

public class TimeAttackBehavior : IGameBehavior
{
    private readonly IGameController controller;

    public TimeAttachBehavior(IGameController controllerIn)
    {
        Debug.Assert(controllerIn != null);
        controller = controllerIn;
    }

    // Skip implementations for things that are outside of what was shown.

    public void OnBlocksCleared(int count)
    {
        if (controller.AllBlocks.Count <= 0)
        {
            controller.CurrentState = GameState.Win;
        }
    }
}

That of course simplifies your OnBlocksCleared method to this:

public void OnBlocksCleared(int count)
{
    score += getScoreFromClearedBlocks(count);
    scoreController.setScore(score);

    behavior.OnBlocksCleared(count);
}

There's more than one way to do this, so experiment a little. Hopefully this gives you a good idea of a potential solution.

3
  • +1 You seemed to have picked up a lot better on the nuance of the question than I did. I got a bit confused specifically about the distinction between 'modes' and 'behaviors' when the difficulty of adding new game modes lead to a temptation to abstract a 'behavior' interface. If you have some time, could you possibly tell me how you managed to comprehend these distinctions so well? Somehow I feel embarrassed as a former gamedev unaware of the terminology here. I might have gotten a bit stupid and thought it was a basic one about refactoring conditionals towards polymorphism.
    – user321630
    Commented Dec 4, 2018 at 14:23
  • 1
    @DragonEnergy I'll try to explain on my side why I chose those names, but I'm particularly bad at naming classes and interfaces. I consider the mode the actual game mode--Rush, TimeAttack, Endless. If I were to name an interface IGameMode, I would expect the interface to actually run the entire game. That is, instead of a GameController I would just have an IGameMode or IGameModeController. I wanted to distinguish that I want to keep a GameController and inside the GameController have a Behavior that differentiated from each game mode. If you have a better naming ideas please let me know!
    – GameDev
    Commented Dec 5, 2018 at 3:38
  • 1
    @DragonEnergy, I merely used the terminology that GameDev used in his OP. It's easier to learn the terminology that someone is already using and merely refine what they are asking. I've been developing software for more than a couple decades, and so that experience helps with honing in on what might be behind what someone is asking about. One thing I've learned is to introduce as little change as necessary to fix a design. The challenge of a redesign really needs good reasons for it, such as the current design not being able to satisfy the needs of the software. Commented Dec 5, 2018 at 13:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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