Want more MonoGame content? Explore the C# Programming Academy where you can find a comprehensive, project-based learning pathway on C#!
Kickstart your game dev journey with our MonoGame free course!
The MonoGame framework enables developers to create cross-platform games with ease, making it an excellent choice for budding and experienced game creators alike. In this tutorial, we’ll guide you through the process of setting up a base for a simple yet exciting space shooter game using MonoGame. We’ll cover everything from setting up MonoGame to implementing essential game mechanics like player controls, obstacle spawning, and more.
Additionally, you can also review our full course, Create a Complete Game in C# with MonoGame which covers these concepts in-depth.
By the end, you’ll have a strong base for a game and the necessary foundation to expand on for your own creative projects. To follow along with this tutorial, you should have some familiarity with the following concepts:
- Basic programming with C#
- Using Visual Studio or similar IDEs
- Understanding game development workflows, such as importing assets and managing sprites
Without delay, let’s jump into our MonoGame free course!
Project Files
To make your development journey smoother, we’ve included the assets used in this MonoGame free course as downloadable resources. Feel free to use them to follow along or experiment further with your project. Download project files here: MonoGame Free Course Assets
Setup and Import
To start our MonoGame free course, we will go through the process of installing MonoGame and setting up a new project. MonoGame is a powerful game development framework that allows developers to create cross-platform games with ease. Let’s get started.
Step 1: Download MonoGame
Start by visiting the official MonoGame website at monogame.net. Click on the ‘Getting Started’ link and select your operating system. In this tutorial, we’ll be using Windows as an example.
Step 2: Install Visual Studio
MonoGame is best installed via Visual Studio. If you don’t have Visual Studio installed, you can download the community edition by clicking on the appropriate link on the MonoGame website. Ensure that you have .NET desktop enabled during the Visual Studio installation process.
Step 3: Install the MonoGame Extension
Launch Visual Studio and select ‘Continue without code’. Navigate to the ‘Extensions’ tab and select ‘Manage Extensions’. In the search box, type ‘MonoGame’ and install the extension that appears.
Step 4: Create a New MonoGame Project
With the MonoGame extension installed, you can now create a new project. Launch Visual Studio and select ‘Create a new project’. Search for ‘MonoGame’ and select the ‘Cross-Platform Desktop Application’. Enter your project name and click ‘Create’.
Step 5: Import Sprite Files
Once your project is created, you can import sprite files. Under ‘Content’, run the file to open the Content Manager window. Here, you can create a new folder for your sprites and import them. The sprite files can be found in the project zip file. After importing an asset, remember to click on ‘Build’, then ‘Save’ and ‘Close’.
That’s it! You have successfully installed MonoGame and set up a new project.
GameManager
Next in our MonoGame free course, we will delve deeper into MonoGame. Specifically, we’ll learn how to create our own game manager by replacing the default game1.cs file with a custom game manager script.
Creating a Game Manager Script
To start, open your MonoGame project. We’ll need to replace the existing game1.cs script with a new one. Here’s how:
- Create a new folder in your project. You can do this by right-clicking in the Solution Explorer, selecting ‘Add’, then ‘New Folder’. Let’s name this new folder ‘Script’.
- Add a new script to the ‘Script’ folder. Right-click on the folder, select ‘Add’, then ‘New Item’. This will open a dialog where you can name your script. Let’s call it ‘GameManager’.
- Once the GameManager script is created, you’ll see some default code in it. The first thing we need to change is the access modifier from ‘internal’ to ‘public’. So, replace the word ‘internal’ with ‘public’.
public class GameManager { // GameManager code goes here... }
Copying Content from game1.cs to GameManager
Next, we need to copy all the code from the game1.cs file to our GameManager script. The game1.cs file contains properties at the top, an initializer for game one, and a few methods (Initialize, LoadContent, Update, and Draw).
Keep in mind that the Update and Draw methods run every frame. For instance, if your game operates at 60 frames per second, these methods will run 60 times per second.
Copy everything from the game1.cs class and paste it into our GameManager script. Then, rename the game1 method to the GameManager method. It’s important that this method name matches your class name. You’ll also notice that game1 is derived from the game class. Our GameManager class should do the same.
public class GameManager : Game { private GraphicsDeviceManager _graphics; private SpriteBatch _spriteBatch; public GameManager() { _graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; IsMouseVisible = true; } protected override void Initialize() { base.Initialize(); } protected override void LoadContent() { _spriteBatch = new SpriteBatch(GraphicsDevice); } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); base.Draw(gameTime); } }
If any imports are missing after copying the script content, make sure you have the same imports as in the original file.
Changing the Project’s Starting Point
After copying the content, we can remove the game1.cs file. However, if you try to run the game now, it won’t work and will give you an error. This is because we also need to change our project’s starting point to be the GameManager.
To do this, go to the Program script and change game1 to Script.GameManager.
using var game = new Script.GameManager(); game.Run();
Don’t forget to save your script whenever you make changes. Now, if you run your game, it should work properly.
With that, we’ve successfully created a custom game manager in MonoGame.
Looking to elevate your MonoGame projects? Our C# Programming Academy offers hands-on projects to help you build a portfolio of high-performance games and C# projects, in-depth video tutorials to optimize your workflow, and written summaries to reinforce your learning.
Player – Part 1
Continuing on in our GameMaker free course, we are going to focus on creating our player script in a space shooter game using C#. The player script will handle tasks such as instantiating the player, drawing the sprite for the player, and controlling player interactions within the game. Let’s dive into it.
Creating the Player Script
To create our player script, we first need to add a new item to our script folder:
- Right-click on the script folder.
- Select ‘Add’.
- Then select ‘New Item’.
- Name it ‘player.cs’.
Once you’ve created the player.cs script, you’ll need to change its accessibility level from ‘internal’ to ‘public’. This is done by replacing the keyword ‘internal’ with ‘public’ at the top of the script.
public class Player { // Player properties and methods will go here... }
Instantiating the Player
Now that we have our player script, let’s instantiate it. We do this by creating a reference to the player script in our game manager. In the game manager script, add a private variable of type player:
private Player _player;
Next, we’ll spawn the player in the ‘LoadContent’ method of the game manager script:
_player = new Player();
At this point, if you run the game, it should execute without any errors. However, you won’t see any changes yet because we haven’t drawn the sprite for the player.
Drawing the Player Sprite
Let’s define a property of type ‘Texture2D’ in the player script. This will hold the texture of our player sprite:
public Texture2D sprite;
Next, we’ll create a new method named ‘LoadContent’ in the player script. This method will load the player sprite:
public void LoadContent() { sprite = GameManager.Instance.Content.Load<Texture2D>("Sprite/player_ship"); }
Now that we’ve loaded the player sprite, it’s time to draw it. We’ll define a new method named ‘Draw’ with a ‘SpriteBatch’ parameter. This method will draw the sprite onto the screen:
public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(sprite, Vector2.Zero, Color.White); }
However, we’re not calling these methods yet. To do this, let’s create an initializer method for the player and call ‘LoadContent’ within it:
public Player() { LoadContent(); }
This will only call the ‘LoadContent’ method. For the ‘Draw’ method, we need to go back to ‘GameManager’. Under the ‘Draw’ method, call ‘spriteBatch.Begin()’, then call ‘player.Draw()’ passing ‘spriteBatch’, then call ‘spriteBatch.End()’:
_spriteBatch.Begin(); _player.Draw(_spriteBatch); _spriteBatch.End();
Now, if you hit play again, you should see the player sprite appearing on the screen.
Player – Part 2
With our player made in our GameMaker free course, we will explore how to read input from the keyboard and control a spaceship using the WASD keys. This involves defining a new method, listening to keyboard input, and updating the position of the spaceship. Let’s get started!
Setting Up the Player Class
Firstly, open the player.cs class. Here, we will define a new method called “update” which will take the game time as a parameter. This method will be responsible for reading keyboard input and controlling the spaceship’s movement.
void Update(GameTime gameTime) { //Method body goes here. }
Listening to Keyboard Input
Inside the update method, define a 2D vector. This vector will represent the direction of movement for the spaceship. We will listen to keyboard input and change the X and Y values of the vector to match the movement direction when the player presses the WASD keys.
Define a property of type Vector2 called “direction” and set it to 0. Get the state of the keyboard by calling “keyboard.getState” and store it in a variable.
Vector2 direction = Vector2.Zero; KeyboardState keyboardState = Keyboard.GetState();
Changing the Direction Vector
Next, determine if a key is pressed by calling keyboardState.isKeyDown. If the W key is pressed, set direction.y to -1, which will move the spaceship forward. Repeat this process for the S, A, and D keys. The value of x and y should be either 1 or -1 depending on the movement direction.
if (keyboardState.IsKeyDown(Keys.W)) { direction.Y = -1; } if (keyboardState.IsKeyDown(Keys.S)) { direction.Y = 1; } if (keyboardState.IsKeyDown(Keys.A)) { direction.X = -1; } if (keyboardState.IsKeyDown(Keys.D)) { direction.X = 1; }
To see if the direction value changes when you press the WASD keys, print the direction value using Debug.WriteLine, passing the direction vector as a parameter.
Debug.WriteLine(direction);
Before running the game, ensure to call the playerUpdate method from the gameManagerUpdate. If you run the game now, you should see the value of the input direction change as you press W, A, S, or D.
Moving the Player
Now, to make the player move, we need to define a speed and position for the player. When the WASD keys are pressed, we will change the player’s position based on the defined speed. In the player script, define a float for speed and a Vector2 to store the position.
float speed = 300f; //Any value Vector2 position;
In the update method, add the direction multiplied by speed to the position value. To ensure the speed isn’t affected by the user’s FPS, multiply by the float value of gameTime.elapsedGameTime.totalSeconds, which is the time between every two frames.
position += direction * speed * (float)gameTime.ElapsedGameTime.TotalSeconds;
Remember, players’ devices vary in GPU and CPU speeds. Therefore, a player who can run the game at 120 FPS will have their spaceship moving much faster than one with a PC running at 60 FPS. To prevent this, we move the spaceship with smaller steps on the 120 FPS devices and bigger steps on the 60 FPS device, resulting in both moving at the same speed.
Updating the Draw Method
Running the game will show that the spaceship still isn’t moving. This is because we are not using the position to draw our sprite. To fix this, go to the draw method in the player script and change Vector2 to position instead.
spriteBatch.Draw(sprite, position, Color.White);
Now, if you run the game, you should be able to control the player spaceship using the WASD keys.
Obstacle
With our player done, we will create our first obstacle in a game using C# for our MonoGame free course. We will create a parent obstacle class and also a meteor class as one implementation of that parent class. We will start by creating a class for Obstacle.cs which will serve as the parent class for all of our obstacles. Then, we will create another class for our Meteor obstacle called ObstacleMeteor.cs.
Creating the Obstacle Class
public abstract class Obstacle { public Vector2 position; protected float speed = 200f; protected Texture2D sprite; public abstract void Update(GameTime gameTime); public abstract void Draw(GameTime spriteBatch, GameTime gameTime); }
This is the basic structure of our Obstacle class. We define the position of the obstacle, its speed and a sprite to represent the obstacle graphically. We also define two abstract methods – Update and Draw. The Update method is used to change the state of the obstacle based on the game time. The Draw method is used to draw the obstacle on the screen.
Creating the Meteor Class
public class ObstacleMeteor : Obstacle { public ObstacleMeteor(Vector2 position) { this.position = position; this.sprite = GameManager.Instance.Content.Load<Texture2D>("Sprite/meteor_small"); // Update and Draw methods go here } }
The Meteor class inherits from the Obstacle class and adds its own specific details. In the constructor, we set the position of the meteor and load its sprite. The Update and Draw methods will be filled in later.
Updating and Drawing the Meteor
In the Update method, we change the position.y by speed multiplied by the total elapsed time. This makes the meteor fall down.
public override void Update(GameTime gameTime) { position.Y += speed * (float)gameTime.ElapsedGameTime.TotalSeconds; }
For the Draw method, we draw the obstacle normally by calling the spriteBatch.draw() method, giving it the texture, position and white color.
public override void Draw(GameTime gameTime, SpriteBatch spriteBatch) { spriteBatch.Draw(sprite, position, Color.White); }
Updating the GameManager
Now we have set up the basic structure for our obstacles. In the GameManager, we can spawn a meteor and call the Update and Draw methods to see it on the screen. To do this, we first need to create a private variable in our GameManager script.
private Obstacle meteor
Then in LoadContent(), we add in a line to spawn the meteor. For now, we’ll put it at Vector2.Zero as we’ll be dealing with its spawn position in the future.
meteor = new ObstacleMeteor(Vector2.Zero)
In the update method, we need to add in a line for the meteor (similar to how we did for the player).
meteor.Update(gameTime)
Last, in the Draw method, we need to call the meteor’s draw function there too.
meteor.Draw(gameTime, _spriteBatch);
If you test your game now, you should see the meteor added to the game!
Ready to unleash your creativity with MonoGame? Our C# Programming Academy offers in-depth video tutorials on building C# projects and covers popular game development topics to help you start creating your own games!
Spawner – Part 1
Let’s expand our meteor now in our MonoGame free course. We will learn how to create a Spawner class in a space shooter game. The Spawner class will be responsible for spawning enemies and obstacles at random points on the screen at regular intervals. To accomplish this, we will create a timer that will execute a block of code every few seconds.
Creating the Spawner Class
Start by creating a new class and name it Spawner.cs. Change the class accessibility modifier from internal to public. Then, add an update method which takes game time as a parameter and a draw method which takes game time and sprite batch as parameters. These methods should be familiar to you from previous parts.
public class Spawner { public void Update(GameTime gameTime) { // Update logic goes here } public void Draw(GameTime gameTime, SpriteBatch spriteBatch) { // Draw logic goes here } }
Integrating the Spawner Class with the Game Manager
To make the update and draw methods work, we need to inform our GameManager about the Spawner script and call these methods from there. Go to GameManager and define a new property of type Spawner named _spawner. Then, initialize this Spawner in the GameManager’s LoadContent method and store it in the _spawner property. In the GameManager’s Update method, call _spawner.Update and pass the game time. Similarly, in the GameManager’s Draw method, call _spawner.Draw and pass game time and sprite batch.
public class GameManager : Game { private Spawner _spawner; protected override void LoadContent() { _spawner = new Spawner(); // Other initialization code } protected override void Update(GameTime gameTime) { _spawner.Update(gameTime); // Other update code } protected override void Draw(GameTime gameTime) { _spawner.Draw(gameTime, _spriteBatch); // Other draw code } }
Creating a Timer in the Spawner Class
Next, go back to the Spawner class. To run code every few seconds, we need to create two TimeSpan variables: one for spawn rate and another for timer. In the update method, we will keep adding the elapsed game time to the timer. When the timer is equal to or greater than the spawn rate, we will run our code to spawn the obstacle and reset the timer.
public class Spawner { private TimeSpan obstacleSpawnRate; private TimeSpan obstacleTimer; public Spawner() { obstacleSpawnRate = TimeSpan.FromSeconds(3); obstacleTimer = TimeSpan.Zero; } public void Update(GameTime gameTime) { obstacleTimer += gameTime.ElapsedGameTime; if (obstacleTimer >= obstacleSpawnRate) { // Code to spawn obstacle goes here obstacleTimer = TimeSpan.Zero; } } // Other code }
Testing the Timer
To test if the timer is working correctly, we can use Debug.WriteLine to print a message every time an obstacle is supposed to spawn. If everything is set up correctly, we should see the message “spawn” printed every 3 seconds when we run the game.
public void Update(GameTime gameTime) { obstacleTimer += gameTime.ElapsedGameTime; if (obstacleTimer >= obstacleSpawnRate) { Debug.WriteLine("spawn"); obstacleTimer = TimeSpan.Zero; } }
Spawning the Obstacles
Now, instead of just printing a message, let’s spawn obstacles. To do this, we need to pick a random point on the left or right side of the screen for the X coordinate, and a point above the screen viewport for the Y coordinate. We then spawn a new obstacle at this random position.
public void Update(GameTime gameTime) { obstacleTimer += gameTime.ElapsedGameTime; if (obstacleTimer >= obstacleSpawnRate) { Random random = new Random(); int randomX = random.Next(0, GameManager.Instance.GraphicsDevice.Viewport.Width); Vector2 position = new Vector2(randomX, -100); new ObstacleMeteor(position); obstacleTimer = TimeSpan.Zero; } }
Removing Unnecessary Code
Since we are now spawning obstacles using the Spawner class, we no longer need the test meteor that was created in the previous sections. Delete its reference from the top of the GameManager class and remove it from the LoadContent method. Also, remove the update and draw calls for the test meteor.
Spawner – Part 2
In this final part of our MonoGame free course, we will continue working on our spawner class in our space shooter game. Our objective is to update and draw the meteor on the screen, as well as manage the deletion of meteors that cross a certain boundary below the screen. This involves creating a list to hold all the obstacles and iterating through this list during the spawner’s update process. We will also manage the drawing process in a similar fashion. Let’s get started.
Creating a List to Hold Obstacles
First, within the spawner, we need to declare the obstacle list and initialize it. This will be a list of Obstacle objects, which we will refer to as ‘obstacles’.
List<Obstacle> obstacles = new List<Obstacle>();
Adding Obstacles to the List
Before we instantiate our Meteor in the spawner update, let’s store the Meteor into a variable of type Obstacle, which we will name ‘currentObstacle’. After this line, we’ll add the current obstacle to our list. This step is crucial for ensuring that every new obstacle is included in the list.
Obstacle currentObstacle = new ObstacleMeteor(position); obstacles.Add(currentObstacle);
Updating and Drawing Obstacles
Now, to enable the drawing and updating of obstacles, in the update method loop through all the obstacles, calling the update method for each.
for (int i = 0; i < obstacles.Count; i++) { obstacles[i].Update(gameTime); }
Repeat a similar process for the draw method, where you loop through all obstacles, calling the draw method for each one of them.
for (int i = 0; i < obstacles.Count; i++) { obstacles[i].Draw(gameTime, spriteBatch); }
Managing Obstacles
Currently, our code continues to loop through meteors that have moved beyond our viewport, invoking the update and draw methods unnecessarily. This inefficiency can lead to performance issues over time. To solve this issue, we will instruct our spawner that any obstacle passing a certain boundary below the screen should be removed from the obstacle list. This ensures that the update and draw methods are no longer called for these obstacles.
To implement this, let’s revisit our spawner update method. Inside the loop, immediately after updating a meteor, check if the obstacle’s position on the y-axis is equal to or greater than the screen height plus 100 units. If this condition is met, remove the obstacle from the list. Remember to adjust the loop index accordingly by decrementing it by 1 to account for the change in the list’s indexing.
for (int i = 0; i < obstacles.Count; i++) { obstacles[i].Update(gameTime); if (obstacles[i].position.Y > GameManager.Instance.GraphicsDevice.Viewport.Height + 100f) { obstacles.RemoveAt(i); i--; } }
Organizing Code
To further organize our code and create room for future additions, let’s encapsulate the spawning and management of meteors into a single method. Define a new method named ‘handleMeteors’. Cut the code from the update and paste it inside the new method and don’t forget to add game time as a parameter to this method too.
public void HandleMeteors(GameTime gameTime) { obstacleTimer += gameTime.ElapsedGameTime; if (obstacleTimer >= obstacleSpawnRate) { obstacleTimer = TimeSpan.Zero; Random random = new Random(); int randomX = random.Next(0, GameManager.Instance.GraphicsDevice.Viewport.Width); Vector2 position = new Vector2(randomX, -100); Obstacle currentObstacle = new ObstacleMeteor(position); obstacles.Add(currentObstacle); } for (int i = 0; i < obstacles.Count; i++) { obstacles[i].Update(gameTime); if (obstacles[i].position.Y > GameManager.Instance.GraphicsDevice.Viewport.Height + 100f) { obstacles.RemoveAt(i); i--; } } }
Finally, within the update method, don’t forget to call ‘handleMeteors’. This keeps the update method clean and more manageable for future expansions.
public void Update(GameTime gameTime) { HandleMeteors(gameTime); }
By implementing these steps, you should be able to see the obstacle appear on the screen. Moreover, your game should now be more efficient as it will no longer waste resources updating and drawing meteors that have moved beyond the viewport.
MonoGame Free Course Wrap-Up
And that wraps up this MonoGame free course! By now, you’ve built the base of a space shooter game that includes a fully functioning player, dynamic obstacle spawning, and an expandable game architecture. While we’ve covered the essentials, there’s no limit to what you can do from here!
Consider adding power-ups, creating a scoring system, or integrating more complex enemy AI to take your game to the next level. These enhancements not only make the game more engaging but also give you the opportunity to deepen your game development skills.
If you’re eager to learn more, Zenva offers a wide range of courses that delve into all things programming. For example, the C# Programming Academy is a carefully constructed learning pathway to help you master not just C#, but a variety of C# game development topics (including courses on MonoGame).
We hope this tutorial has inspired you and equipped you with the skills to bring your game ideas to life. Good luck, and happy coding!
Get industry-ready with the C# Programming Academy! Perfectly suited to any skill level, you’ll get the tools you need to succeed while building a slew of projects!
Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it! FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.