Looking for feedback regarding my Monogame based libraries.

Hey all. I am a software engineer by trade but not in gaming, although I have dabbled in the space for long time. Looking to get more serious about it and get something on steam one day, so I started building some libraries to use as a way of helping me understand Monogame. I think I have enough built to prove my concepts, so I wanted to get some real feedback from people who know a lot more than I do about game code.

My primary concern with these libraries is code architecture. I have built games in the past and I find the game code has a tendency to become a tangled mess very easily. Due to the interconnected nature of game code and the sheer amount of interactions and objects that need to know about so many other objects, I knew that I had to solve that problem first. My second concern was usability. I wanted my library to be as user friendly as possible without placing users under any burdensome constraints.

What I have built so far:

Cloaked - the main library
Completed features:

  • Game State Management
  • Game Context Switching
  • Pub/Sub Events and Trigger System
  • Timed Effect System
  • Keyboard Input System
  • Mouse Input System
  • Sprite Rendering System
  • Sprite Animation System
  • Texture Management
  • Basic 2D Camera

It’s nothing fancy, but it’s a really well thought out (hopefully) foundation for what could be a great library/framework in the future. I am hoping to generate some interest and any feedback.

My library is used like this:

ContextualGameState GameState = new ContextualGameState("global", "game");
Cloaked.ContextManager.AddNewContext(newId: "global")
            .AddComponent(new RenderableManager(graphicsDeviceManager, new Camera2D()))
            .AddComponent(GameState);

This sets up a game state container that know how to retrieve game state based on the current context your game is in (for example, when you are in a menu vs actively playing, etc). Also it sets up the RenderableManager that is responsible for drawing everything, attaching it to the ‘global’ state.

The AddComponent calls are actually adding components to a dictionary, and by default the keys are the type names of the object. As you will often only add only one of certain types of components, this makes it more convenient.

Cloaked.ContextManager.AddShallowCopyOf(id: "global", newId: "game")
        .AddComponent(new KeyboardInputManager())
        .AddComponent(new TriggerManager())
        .AddComponent(new ValueGameState());

This add another context that represents the start screen, which is the first state the game will be in. Because it is a shallow copy of the global context, it will be identical until you override any of it’s components. The above code adds a KeyboardInputManager, TriggerManager and ValueGameState to the newly created game context that is returned.

context.GetComponent<KeyboardInputManager>().RegisterKeyMapping(
    new KeyMapping(//pass in a list of keys to check, etc),
    new GenericInputActionTrigger<KeyStatus>(
        (status) => { // return true or false based on any condition },
        (KeyStatus status) => { //execute any logic when the above predicate is true }
    )
)

The logic for checking if the right keys are pressed, etc is abstracted away by using KeyMapping, but you still have all the control you need to change keybindings on the fly by changing KeyMapping’s properties. The predicate function could simply return true and the action you pass in would still only be called when the right keys are pressed.

ContextualGameState gameState = context.GetComponent<ContextualGameState>();
Dictionary<string, AbstractComponent> GameStateContext = gameState.InitializeContext("game");
GameStateContext.Add("level", level);
GameStateContext.Add("activeShape", ShapeManager.NextShape);

This gets the ContextualGameState that was added earlier and initializes it, then adds some game objects (this code was taken from a tetris clone).

TimedAction effect = new TimedAction((gameTime, actionState) => { Cloaked.GetContext("game").GetComponent<TriggerManager>().Publish("drop"); }, new TimeSpan(0, 0, 1));
context.AddComponent("drop_every_1000_ms_state", new TimedExecutionState(effect));

This creates a timed action, which is code you want executed on a timed interval. By adding it to game state, it will have Update called an execute every second (or whatever interval you specify).

Triggercollision_trigger = new Trigger("collision_trigger", () => { //predicate }, () => { //execute code }, 3, false, false)
context.GetComponent<TriggerManager>().Subscribe("drop", collision_trigger);

This code makes a trigger and subscribes it to an event. When the event is published, the predicate is evaluated and if it is true then the the second lambda will execute. You can see the timed effect code publishing the event above. The boolean parameters tell the trigger manager if it should stop calling triggers for the event when this executes and if this triggers should be removed afterward. This allows you to do one time subscriptions to events, etc.

Cloaked.Initialize(game, graphicsDeviceManager);
Cloaked.ContextManager.ActivateContext("game");

This initializes the Cloaked library and sets the game context as the current context. At that point everything above starts running.

I also have a separate project building off the first one to add UI support. That project makes it really easy to build UI components with dynamic CSS/Javascript like behavior and syntax, as shown here:

ColoredRectangle rect2 = new ColoredRectangle(100, 100, Color.Teal);
rect2.Edges.Radius = 15;
rect2.Border.Width = 1;
rect2.Border.Color = Color.Purple;
rect2.Text.TextValue = "Hello";
rect2.Text.Font = "BasicFont";
rect2.Text.Color = Color.Pink;
rect2.Text.HorizontalAlignment = HorizontalAlignment.Left;
rect2.Hover.Text.TextValue = "World";
rect2.Hover.Background.Color = Color.Maroon;

rect2.Input.OnMouseClicked += new MouseEventHandler((sender, mouseStatus) => { Console.WriteLine($"[#{mouseStatus.MouseState.X},#{mouseStatus.MouseState.Y}]"); });

Colored Rectangle
Colored Rectangle 2

The above code is an example of creating a UI element and assigning various properties, including properties for when the element is hovered over by the mouse. No other code is necessary - the overriding values get applied when appropriate out of the box. The last line shows the simplicity of tying in to events related to the UI element.

My UI library will also have layouts, and many other widgets that I plan to build such as scroll panes, combo boxes, text inputs, etc.

Most importantly, I am building all this in a way that will make it easy to extend and customize. Users will be able to create their own modified versions of ui elements, etc.

This is just a taste of what I have built so far, but if it is well received and there is enough interest I might be interested in a collaborator or two to help speed up development. Mostly I just want to know what you thing of what I have shared.

Thank you all!

This looks nice.

Is the full source code available?
I had to solve most of these for my Steam game a few years ago, so it would be interesting to see the full picture of what you have done.

I am not quite ready to release the source code publicly. It is still missing some key feature, could use some api improvements, and the UI library should be fleshed out. I am open to having collaborators though if you are interested.

I understand completely if you want to keep the code private.
I took a closer look at the above code and it looks quite elegant to me.

In my implementations I used interfaces to nail down the behaviour.
e.g. I have a IGraphicsElement interface which all on-screen widgets need to implement.
This has methods for handling mouse events etc and also Update and Draw.
So my Rectangle widget implements that.

A few other things you might want to consider:
Managing all sounds (background music, effects etc)
Managing fonts (e.g. allowing different ColoredRectangles to have different fonts).

I don’t have the bandwidth to do any serious collaborating at present (full time job + several projects on the go).

My issue with this style or programming in general is that there’s a simple way to do it that’s easier to debug so why would I ever do it the harder way? The simple way of doing the above code is (in your update loop):

switch(GameState)
{
case GameState.Menu:
    if (KeysPressed(pass in list of keys to check) && condition)
    {
        put the logic here
    }
break;
}

And the thing about it is if you have to step the debugger through this style of code it’s so much easier to keep track of whats going on and follow the program flow. I spend a lot of time debugging and so this is very important to make it as easy as possible. It’s also better because you can “else if”, and you can also calculate any values you might need for all cases before the if call.

I think I understand your point about debugging, and if your game is extremely small and simple you would be right. The way you suggest doing it though just isn’t scalable in my view. You will end up with an update loop that is bloated, trying to handle the logic for a million different game objects/ event handlers/ inputs etc. That becomes difficult to maintain very quickly.

Your suggestion that using if else is a benefit is not the way I see things either. An long chain of if else or conditional logic is actually a code smell and it has a name: Repeated Switches. The remedy for this code smell as prescribed by Martin Fowler, who is a well known authority on the topic of Object Oriented Programming and Design, is polymorphism, which is exactly what I have used to avoid the code smell here.

Also, I don’t understand how your example using a switch wouldn’t be very rigid. My system allows the creation of new keymappings and triggers at runtime, but there is no way you could add another case statement to your switch at runtime to accomplish the same thing.

The thing about doing it the “easy” way is that it is almost always harder in the end, when you application grows. Stepping into another class/method while debugging is a small price to pay for the benefit of not having a ton of conditional logic. As for setting values that all your cases use, well there are a lot of ways to skin that cat without putting all your code in one update loop.

Thank you for your feedback.

If you would like to learn more about the concepts I use to help me make design decisions in my code, check out these books:

https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612/ref=asc_df_0201633612/?tag=hyprod-20&linkCode=df0&hvadid=312280575053&hvpos=&hvnetw=g&hvrand=3334596642585053604&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9029726&hvtargid=pla-395340045790&psc=1

https://www.amazon.com/Refactoring-Improving-Existing-Addison-Wesley-Signature/dp/0134757599/ref=sr_1_1?dchild=1&keywords=refactoring&qid=1610935285&s=books&sr=1-1

1 Like