Page 6. Microsoft XNA: Refactoring our code
Cleaning, reviewing and breaking down
Last updated
Cleaning, reviewing and breaking down
Last updated
Before we dive into implementing mouse interactivity in your MonoGame project, let's ensure that you have the necessary foundation in place. If you're new to Microsoft XNA and MonoGame or need a refresher, it's highly recommended to start with our introductory project. You can follow the step-by-step walkthrough provided on to get acquainted with the basics.
List of Prerequisites:
Visual Studio: Make sure you have a working installation of Visual Studio, which is the development environment we'll use for this project.
MonoGame Framework: Ensure that you have the MonoGame framework installed. This framework provides the essential tools and libraries for game development.
.xnb Font File: To display text in your MonoGame project, you'll need a .xnb file of your desired font. If you haven't created one yet, you can refer back to Page 1 for guidance on how to generate it.
Before we dissect, it's essential to understand the structure that we are aiming for. Our goal is to adhere to the Object-Oriented Programming (OOP) principles, ensuring that each class has a single responsibility and interacts with other classes in a well-defined way.
Refactoring is like tidying up your code, making it neat and understandable without changing what it actually does. This practice is crucial in Object-Oriented Programming (OOP) for several reasons:
Maintainability: It makes the code more understandable and easier to maintain.
Extensibility: A well-organized codebase is easier to extend with new features.
Error Reduction: Refactoring can help identify and fix hidden bugs.
Performance: Improved code structure can lead to better performance.
Readability: It improves the readability of the code, making it easier for others (and future-you) to understand.
Reuse: Promotes code reuse, reducing the amount of duplicated code.
Adherence to Design Principles: Helps in adhering to OOP principles like SOLID, making the system well-architected.
The SOLID principles are a set of five design guidelines in Object-Oriented Programming (OOP) that encourage clean, maintainable, and robust code. Here they are broken down:
Single Responsibility Principle (SRP): Each class should have one reason to change, meaning it should have only one responsibility. For example, a "Printer" class should only handle printing, not also scanning or faxing.
Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification. You should be able to add new features or functionality to a system without altering its existing code, like adding new accessories to a smartphone without changing the phone itself.
Liskov Substitution Principle (LSP): Subtypes should be substitutable for their base types without causing errors. If Class B is a subclass of Class A, you should be able to use Class B wherever you use Class A without any hitches, like using a rechargeable battery in place of a regular battery.
Interface Segregation Principle (ISP): It's better to have many specific interfaces rather than one general-purpose interface. Instead of having one big set of features in a single interface, break it down into smaller sets, so users aren't forced to work with features they don't need.
Dependency Inversion Principle (DIP): Depend on abstractions, not on concrete implementations. For example, a car depends on an engine running, not the specifics of how an engine works.
Let's identify the potential classes we could extract from our Game1.cs:
MouseInputManager: This class is responsible for managing mouse input. It keeps track of the current and previous mouse states and provides methods to check for specific mouse actions, like left or right button clicks.
TextElement: This class encapsulates the text element, holding properties such as position, velocity, color, and the text string itself. It represents the text that is being manipulated in your game.
PhysicsEngine: This class is focused on the physics of the text element, managing its movement based on its velocity, and handling any collisions with boundaries, ensuring the text bounces back when it hits the edges of the window.
ColorManager: This class takes charge of managing color changes over time. It has a method to update the color of the text element, which in your code, alters the green color component in a cyclic manner based on elapsed time.
The MouseInputManager
class is dedicated to managing mouse interactions in your game. It resides under a namespace, YourNameSpaceNameHere
which helps organize related classes. The class is publicly accessible, with two pivotal members: currentMouseState
and previousMouseState
, to track the mouse's state across frames. The update
method is crucial as it refreshes these state trackers each frame, ensuring accurate, up-to-date information. Two methods, isLeftButtonClicked
and isRightButtonClicked
, leverage this state information to determine if the left or right mouse buttons have been clicked, providing a clear interface to mouse button interactions. Through encapsulation and clear method naming, this class greatly eases the handling of mouse inputs, a crucial aspect for interactive applications like games, making the codebase more readable and manageable.
By now you may have noticed the use of { get; set; }
line of code. This defines a property named and type
. This is a common pattern in C# known as an auto-implemented property. The public
keyword denotes that this property can be accessed and modified from outside the class. The get
and set
accessors imply that the property can be both read and written to. This simplifies code by automatically generating a private backing field behind the scenes, while still providing the encapsulation benefits that come with defining properties, aligning with the Object-Oriented Programming (OOP) paradigm of encapsulation.
The TextElement
class, nested within the namespace YourNameSpaceNameHere
, serves as a robust representation of a textual element within your game, embodying vital properties like Position
, Velocity
, Font
, Color
, Text
, and Texture
. It showcases well-structured code through its constructor, which not only accepts external configurations but also provides sensible defaults. The GenerateTextureFromText
method is a meticulous procedure converting text into a texture for rendering, emphasizing the class's purpose in bridging textual content and graphical representation. By encapsulating related properties and methods, TextElement
epitomizes object-oriented principles, promoting a clear, manageable approach to handling textual graphics within your game environment.
The initializer (constructor) in the TextElement
class is crafted to accept six parameters, where the last three have default values set to null
. This is designed for flexibility, allowing optional customization at the point of object creation. The ??
operator plays a crucial role here; it's a null-coalescing operator that checks if a value is null, and if so, provides a default value. For instance, Font = font ?? content.Load<SpriteFont>("Fonts/MyFont");
checks if font
is null, and if it is, loads a default font. This pattern is useful in providing fallback values, ensuring that crucial properties like Font
, Velocity
, and Color
have valid initial values, thus enhancing robustness and reducing potential runtime errors.
PhysicsEngine.cs
a class encapsulating the physics logic within your game realm. It's tasked with managing how the TextElement
interacts with the game environment, especially regarding its movement and response to mouse events. The methods ApplyWallBouncingToText
and ApplyMouseEventHandlingToText
are the crux of this class, handling the text elements bouncing off walls and reacting to mouse clicks, respectively. This segregation of physics logic into its own class promotes a cleaner, more organized codebase, making it easier to pinpoint and amend any physics-related logic independently from other game logic, adhering to the Single Responsibility Principle of SOLID.
In the updated ColorManager
class, the UpdateTextColor
method has been enhanced for flexibility. It now accepts additional parameters: colorChangeSpeed
, changeRed
, changeGreen
, and changeBlue
with default values. This setup allows the user to control the speed of color change and choose which color channels to modify. Each color channel (Red, Green, Blue) is independently updated based on the respective boolean flags. If a flag is set to true, that color channel's value is incremented by a rate of colorChangeSpeed * elapsed
modulo 256, ensuring it stays within valid bounds. This setup offers a tailored color change experience while keeping the code user-friendly and adaptable.
Now, integrate these classes back into Game1
class, ensuring each class interacts harmoniously with others. Note that this is a simplified restructuring. Real-world scenarios may require more complex designs.
The Game1
class serves as the core orchestrator for various components and activities within the game. It's structured within the HelloWorldXNA
namespace and inherits from the Game
class provided by Microsoft.Xna.Framework. Within Game1
, several critical objects are declared that represent various aspects of the game: graphicsManager
for graphics device management, spriteBatch
for rendering, textElement
for text manipulation, physicsEngine
for physics logic, colorManager
for color adjustments, and mouseInputManager
for mouse input handling.
The constructor Game1()
initializes the graphicsManager
and sets the content root directory. The Initialize
method is overridden to set up essential game settings like mouse visibility, window resizing allowance, and the instantiation of various game-related objects like textElement
, physicsEngine
, colorManager
, and mouseInputManager
.
LoadContent
is another overridden method where resources are loaded; here, it initializes spriteBatch
and generates texture from the text for textElement
.
The Update
method, called once per frame, updates the state of these objects and handles the logic for mouse input, text movement, and color change, with a calculated elapsed
time for smooth transitions.
Lastly, the Draw
method is overridden to clear the graphics device to a solid color and then draw the text element to the screen. This setup ensures a structured approach to managing game states and rendering, making it easier to understand, extend, and maintain.
What is the purpose of refactoring in Object-Oriented Programming?
Explain the Liskov Substitution Principle (LSP) with an example provided in the article.
What is the role of the MouseInputManager
class in the Game1.cs
file?
Which of the following SOLID principles emphasizes that a class should have only one responsibility? a) Single Responsibility Principle (SRP) b) Open/Closed Principle (OCP) c) Liskov Substitution Principle (LSP) d) Dependency Inversion Principle (DIP)
In the UpdateTextColor
method of ColorManager
class, find and fix the error:
Challenge 1: Changing Text Behavior Update the PhysicsEngine
class to reverse the text's velocity whenever a mouse button is clicked, regardless of the mouse position.
Challenge 2: Dynamic Color Changes Modify the ColorManager
class to change the text color to a random color whenever the UpdateTextColor
method is called. You can use the Random
class to generate random numbers for the RGB values.