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

Oh yeah, that very much smells of bizdev. : D

First of all, nobody calls game states context. They are usually called scenes. Second, your input manager is terribly overcomplicated.
Throw it out completely and just leave it like:
if (Input.CheckKeyPress(Buttons.Space))
{}
You really don’t need more than that.

There’s a bit more to it than that e.g.
when you want to action on key down or key up (e.g. thrust)
when you want to time the length of a key press
when you want to have a delay before a held down key repeats e.g. for text entry.

I think this project is trying to abstract it so that you can just make a few calls to set it up and not have to worry about implementing the above behaviours.

But it’s difficult to see without the full api or code.

So basically, reimplementing the functionality which already exists in monogame. ( :

if (Input.CheckKeyPress/Release()) will do the job just fine.

Make a separate class for that, why shove it into general input?

Basic stuff like checking if the button was pressed should be as simple as possible – nobody wants to create lambdas and action triggers for that.

1 Like

Ah I see you are agreeing with me. It is more than that :upside_down_face:

To do keymappings at runtime instead of having

KeyPressed(Keys.Space)

you put

Key jump = Keys.Space;
KeyPressed(jump);

If you want to be able to map more then one key then you just do

List<Key> jump = new List<Key>() { Keys.Space };

then you can write a function so you can still check of KeyPressed in one line

function KeyPressed(List<Key> actionKeys)
{
    foreach(Key key in actionKeys)
    {
        if (KeyPressed(key))
        {
            return true;
        }
    }
    return false;
}

Onto the other part of the statement I quoted, I’m not sure why you would want to be able to use triggers to change behaviour at runtime? Can you give me an example of how a game would use that? I’d be wary of adding complexity to add features that have no use case. You should consider developing the tool alongside a game so that you are creating features as you need it and not in preparation for some theoretical case.

3 Likes

You can have a decent set of features AND a syntax that doesn’t make you throw your pc out of the window.

1 Like

I also went the route of writing a relatively complex input handling system with events and stuff. But I now think otherwise. It makes more sense to keep input handling lightweight and simple.

1 Like

I am developing this stuff alongside an actual game. You my friend are jumping to a lot of wrong conclusions about this based on a small sample of code. I asked for feedback so I am not going to sit here and argue with it, but your feedback has been neither constructive or we’ll thought out.

What my code intends to do is let the user abstract out a given input action into a single, isolated scope which is far more maintainable than throwing it all in a long chain of conditionals. If that isn’t a benefit you are interested in, well you have made your opinion clear and there isn’t need for any more discussion.

His feedback was fine, someone’s just salty. Why even ask for feedback if you’re going to shutdown people like that.

1 Like

Very salty indeed…But what are you going to do? Some people are just like that.