How good is the official Platformer 2D sample for learning Monogame?

I’m trying to improve my game programming abilities by going through this Platformer sample advertised on here
Screenshot_2021-01-07 Samples Demos MonoGame Documentation
(code seen here)
I’m trying to rebuild it from scratch starting with it’s most simple elements. In a lot of cases I get what it’s trying to do but it feels like confusing spaghetti code sometimes trying to figure out what leads to what. Is it intentionally set up like that or is that not an accurate assessment and I’m just not skilled enough to grasp it at the moment?

I’ve spent the past year (on and off) learning MonoGame and have made one original (very simple) game as well as a pong, Flappy Bird and memory match clone. So I’m in a limbo territory between beginner and intermediate skill. But I’ve used the “Engine” (or helper files) as shown to me in this book and I just got the hang of Nez so maybe I’m not used to seeing the code to games beyond a certain level of complexity that doesn’t use those extended frameworks.

With that in mind how should I approach this Platformer sample if it’s worth approaching at all? If not then what else would you recommend?

I have to start by telling you that I don’t have too much experience in game programming and I’m also not an expert at MonoGame or C#, but I do have more than a decade of experience in programming in general and code design. Game programming is a specific domain where sometimes you have to trade in some good design practice in exchange of performance, but I try to find the middle ground between code that favors me and the code that runs as fast as possible.
With this in mind, here are my thoughts on the example you ask about:
I find this code to be very specific to this game, I don’t see many reusable design there which would the base of a manageable codebase.
I always try to use abstract classes to maximize code reusability to a point where I can just create a controllable player character class like this:

public Knight(ContentManager contentManager, Vector2 position, SpriteFont font = null) : base(RootContainer.Instance.EntityLayer, null, position, null, true, font)
    {

        SetupAnimations(contentManager);
        SetupController();
        animations.Offset = new Vector2(0, -10f);
        shotEffect = contentManager.Load<SoundEffect>("Audio/Effects/GunShot");

    }

    private void SetupController()
    {
        Input = new UserInputController();

        UserInput.RegisterControllerState(Keys.Right, () => {
            Direction.X += Config.CHARACTER_SPEED * elapsedTime;
            CurrentFaceDirection = Engine.Source.Entities.FaceDirection.RIGHT;
        });

        UserInput.RegisterControllerState(Keys.Left, () => {
            Direction.X -= Config.CHARACTER_SPEED * elapsedTime;
            CurrentFaceDirection = Engine.Source.Entities.FaceDirection.LEFT;
        });

        UserInput.RegisterControllerState(Keys.Space, () => {
            Direction.Y -= Config.JUMP_FORCE;
        }, true);

        UserInput.RegisterControllerState(Keys.Down, () => {
            if (HasGravity)
            {
                return;
            }
            Direction.Y += Config.CHARACTER_SPEED * elapsedTime;
            CurrentFaceDirection = Engine.Source.Entities.FaceDirection.DOWN;
        });

        UserInput.RegisterControllerState(Keys.Up, () => {
            if (HasGravity)
            {
                return;
            }
            Direction.Y -= Config.CHARACTER_SPEED * elapsedTime;
            CurrentFaceDirection = Engine.Source.Entities.FaceDirection.UP;
        });

        Action shoot = () =>
        {
            if (lastBulletInSeconds >= SHOOT_RATE)
            {
                lastBulletInSeconds = 0;
                new Bullet(this, CurrentFaceDirection);
                shotEffect.Play();
            }
        };

        UserInput.RegisterControllerState(Keys.RightShift, shoot, true);
        UserInput.RegisterControllerState(Keys.LeftShift, shoot, true);

        UserInput.RegisterMouseActions(() => {Config.SCALE += 0.1f; Globals.Camera.Recenter();  }, () => { Config.SCALE -= 0.1f; Globals.Camera.Recenter(); });
    }

    public override void Update(GameTime gameTime)
    {
        lastBulletInSeconds += gameTime.ElapsedGameTime.TotalSeconds;
        base.Update(gameTime);
    }

    private void SetupAnimations(ContentManager contentManager)
    {
        animations = new AnimationStateMachine();

        string folder = "SideScroller/KnightAssets/HeroKnight/";

        List<Texture2D> knightIdle = SpriteUtil.LoadTextures(folder + "Idle/HeroKnight_Idle_", 7);
        AnimatedSpriteGroup knightAnimationIdleRight = new AnimatedSpriteGroup(knightIdle, this, animationFps);
        Func<bool> isIdleRight = () => CurrentFaceDirection == FaceDirection.RIGHT;
        animations.RegisterAnimation("IdleRight", knightAnimationIdleRight, isIdleRight);

        AnimatedSpriteGroup knightAnimationIdleLeft = new AnimatedSpriteGroup(knightIdle, this, animationFps, SpriteEffects.FlipHorizontally);
        Func<bool> isIdleLeft = () => CurrentFaceDirection == FaceDirection.LEFT;
        animations.RegisterAnimation("IdleLeft", knightAnimationIdleLeft, isIdleLeft);

        List<Texture2D> knightRun = SpriteUtil.LoadTextures(folder + "Run/HeroKnight_Run_", 7);
        AnimatedSpriteGroup knightRunRightAnimation = new AnimatedSpriteGroup(knightRun, this, animationFps);
        Func<bool> isRunningRight = () => Direction.X > 0.5f && !CollisionChecker.HasColliderAt(GridUtil.GetRightGrid(GridCoordinates));
        animations.RegisterAnimation("RunRight", knightRunRightAnimation, isRunningRight, 1);

        List<Texture2D> knightJump = SpriteUtil.LoadTextures(folder + "Jump/HeroKnight_Jump_", 2);
        AnimatedSpriteGroup knightJumpRightAnimation = new AnimatedSpriteGroup(knightJump, this, animationFps);
        Func<bool> isJumpingRight = () => JumpStart > 0f && CurrentFaceDirection == FaceDirection.RIGHT;
        animations.RegisterAnimation("JumpRight", knightJumpRightAnimation, isJumpingRight, 2);

        List<Texture2D> knightRun = SpriteUtil.LoadTextures(folder + "Run/HeroKnight_Run_", 7);
        AnimatedSpriteGroup knightRunRightAnimation = new AnimatedSpriteGroup(knightRun, this, animationFps);
        Func<bool> isRunningRight = () => Direction.X > 0.5f && !CollisionChecker.HasColliderAt(GridUtil.GetRightGrid(GridCoordinates));
        animations.RegisterAnimation("RunRight", knightRunRightAnimation, isRunningRight, 1);

        List<Texture2D> knightFall = SpriteUtil.LoadTextures(folder + "Fall/HeroKnight_Fall_", 3);
        AnimatedSpriteGroup knightFallRightAnimation = new AnimatedSpriteGroup(knightFall, this, animationFps);
        Func<bool> isFallingRight = () => Direction.Y > 0f && CurrentFaceDirection == FaceDirection.RIGHT;
        animations.RegisterAnimation("FallRight", knightFallRightAnimation, isFallingRight, 3);
        //and so on...

        Animations = animations;
    }

This example showcases my approach of code design. Each of my displayed entities has one instance of my class called AnimationStateMachine, this can contain as many animations as you want, and they are invoked when their condition becomes true based on their priority. For example, the condition for playing the “RightRun” animation is checking whether character’s X direction is bigger than 0.5 (it could be checked against simply 0, but this 0.5 gives better look) and whether there is a collider to the right of the character:

Func<bool> isRunningRight = () => Direction.X > 0.5f && !CollisionChecker.HasColliderAt(GridUtil.GetRightGrid(GridCoordinates));

When you are running to the right, the “RunRight” animation will be played , but if you also hit jump while running, the jump has higher priority so the animation controller will paly the “JumpRight” animation, so the character is not going to run in the air. I can add another animation very easily, for example if I want to add shooting, that must be the highest priority, becuase when I’m running right, and then jump and shoot in the air, the shooting animation must take precedence. This would really just take 3-4 lines of code to add to my character and it works.
It also works when an AI controls the entity, as the animations are not tied to keyboard inputs, but to the state of my objects, which also changes if an AI does something to my character, which is a huge advantage and time saver!
This kind of approach maximize my code reusability and minimizes the maintenance/refactoring efforts, and I’m not hardcoding animations like they do in the platformer example you linked. I can create a new kind of visual entity in my game by simply instantianating my Entity class or creating a child class from it, setup the audio, animation states, controllers if necessary and that’s it!

I use a similar, but reversed logic for the key inputs:
You can register key presses with an action, and when the user hits the key, my inputcontroller will fire that specific action, like how shooting is mapped to my left/right shift keys:

Action shoot = () =>
    {
        if (lastBulletInSeconds >= SHOOT_RATE)
        {
            lastBulletInSeconds = 0;
            new Bullet(this, CurrentFaceDirection);
            shotEffect.Play();
        }
    };

    UserInput.RegisterControllerState(Keys.RightShift, shoot, true);
    UserInput.RegisterControllerState(Keys.LeftShift, shoot, true);

I’m not saying that my approach is the best (there is no best anyways), I’m not even stating that it’s among the best or nicest solutions, but it served me well so far and I can create a basic platformer or top down game example with really not so much code (although there is still a lot to be implemented, as I only started writing this engine a few weeks ago in my free time). I also take the time to refactor and redesign if I see that something isn’t as good as good as I though or I come up with a better design or better performance.

There a few things that I try to keep in mind:
1, Is this piece of code very specific to this object I’m working on, or can it be reused somewhere else? If so, I go for abstract class and interface based design (although I try avoid unnecessary casting in Draw/Update loops)
2, Can’t this forest of if statements be simplified to a better design? like my anmation controller, that decides by itself which animation to play based on the set of criteria. Check “finite state machines” in general to understand to understand the idea behind this design.
3, Am I repeating a piece of code for too many times now? If so, then it’s time to add/change an abstract class or create a new child class that encapsulates the repeated code and just instainate that instead.
4, Am I executing some code unnecessarily in a loop? Or during Draw() or Update() maybe? If so, I put them outside of loops or redesign the code that loops only contain only the absolutely necessary code pieces, everything else is only executed once or as few time as possible.
5, When designing a new feature, what are the “core” features that will most likely be shared across the game? Is there a code that it’s mostly the same for all my objects and the only difference is just a small implementation detail? If so, I just put it to an abstract class and call an abstract function that I override in each child classes for the desired functionality.
6, Am I creating new objects in a high volume? Will this functionality trigger fequent garbage collection? If so, then I’ll add some kind of Object Pooling solution and reuse whatever is possible and makes sense.
7, And of course: is this code going to perform well? :slight_smile:

Some people to follow if you’d like to see good design patterns and high performance solutions:
https://deepnight.net/tutorials/ - the blog of the creator of Dead Dells, his git repos are amazing. Even though it’s not MonoGame, his ideas are still great and can easily be translated to mono.
https://www.patreon.com/NemoKrad/posts - he is writing a game engine in monogame, although it’s many aspects are applicable in 2D as well :slight_smile:

I hope this writing is helpful to you, good luck on your journey.

2 Likes

A lot to absorb, but good information here. I’m sure the person who made that Platformer sample made it in a way that was personalized to him. At some point I’ll try to come up with my own design patterns to make that particular game fit my purposes.

1 Like

Yes, don’t get me wrong, if I was given a task to implement a simple 2D platformer as an example for a game engine/framework, I’d pretty much do the same as it’s on the website :slight_smile: It’s small, specific, easy to understand for beginners and very good introduction to the framework. However, if I was about write my own game for commercial purposes, I’d definitely put aside simplicity and beginner friendliness, and instead design my engine first, on which I can build the desired game, but can also reuse it in general in the future whenever I need it :slight_smile: it’s more work on short run, but much less work on the long run.