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}]"); });
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!