name: monogame description: | USE FOR: Building 2D and 3D games with C# using the MonoGame framework. Use when creating cross-platform games for Windows, macOS, Linux, iOS, Android, and consoles with low-level control over rendering, audio, and input. DO NOT USE FOR: Business applications or form-based UIs (use Blazor, MAUI, or WPF), high-fidelity AAA 3D games requiring a full editor workflow (use Unity or Unreal), or applications that do not need a game loop. license: MIT metadata: displayName: MonoGame author: "Tyler-R-Kendrick" version: "1.0.0" compatibility:
- claude
- copilot
- cursor references:
- title: "MonoGame Official Documentation" url: "https://docs.monogame.net/"
- title: "MonoGame GitHub Repository" url: "https://github.com/MonoGame/MonoGame"
- title: "MonoGame Official Site" url: "https://monogame.net/"
MonoGame
Overview
MonoGame is an open-source, cross-platform implementation of the Microsoft XNA 4 framework. It provides APIs for 2D and 3D rendering, audio, input, and content management. MonoGame targets Windows, macOS, Linux, iOS, Android, and various consoles. Unlike engines like Unity, MonoGame is a framework, not an editor -- developers write all game logic, rendering, and scene management in C# code. It uses a fixed-timestep game loop with Update and Draw methods and includes a Content Pipeline for asset preprocessing.
Game Class and Lifecycle
Every MonoGame project centers on a class that inherits from Game. The lifecycle methods Initialize, LoadContent, Update, and Draw form the core loop.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace MyGame;
public class MainGame : Game
{
private readonly GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch = null!;
public MainGame()
{
_graphics = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = 1280,
PreferredBackBufferHeight = 720,
IsFullScreen = false
};
Content.RootDirectory = "Content";
IsMouseVisible = true;
IsFixedTimeStep = true;
TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 60.0);
}
protected override void Initialize()
{
Window.Title = "My MonoGame Project";
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.CornflowerBlue);
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
// Draw calls go here
_spriteBatch.End();
base.Draw(gameTime);
}
}
Sprite Rendering and Animation
Load textures via the Content Pipeline and render animated sprites using a sprite sheet.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MyGame;
public class AnimatedSprite
{
private readonly Texture2D _texture;
private readonly int _frameWidth;
private readonly int _frameHeight;
private readonly int _frameCount;
private readonly float _frameTime;
private int _currentFrame;
private float _elapsed;
private Vector2 _position;
public AnimatedSprite(Texture2D texture, int frameWidth, int frameHeight,
int frameCount, float frameTime, Vector2 startPosition)
{
_texture = texture;
_frameWidth = frameWidth;
_frameHeight = frameHeight;
_frameCount = frameCount;
_frameTime = frameTime;
_position = startPosition;
}
public void Update(GameTime gameTime)
{
_elapsed += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (_elapsed >= _frameTime)
{
_currentFrame = (_currentFrame + 1) % _frameCount;
_elapsed -= _frameTime;
}
}
public void Draw(SpriteBatch spriteBatch)
{
var sourceRect = new Rectangle(
_currentFrame * _frameWidth, 0,
_frameWidth, _frameHeight);
spriteBatch.Draw(_texture, _position, sourceRect,
Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
}
}
Entity-Component Pattern
MonoGame does not include a built-in ECS, but you can implement a lightweight entity-component pattern for game objects.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace MyGame;
public abstract class Component
{
public Entity Entity { get; internal set; } = null!;
public virtual void Initialize() { }
public virtual void Update(GameTime gameTime) { }
public virtual void Draw(SpriteBatch spriteBatch) { }
}
public class Entity
{
private readonly List<Component> _components = new();
public Vector2 Position { get; set; }
public bool IsActive { get; set; } = true;
public T AddComponent<T>(T component) where T : Component
{
component.Entity = this;
_components.Add(component);
component.Initialize();
return component;
}
public T? GetComponent<T>() where T : Component
=> _components.Find(c => c is T) as T;
public void Update(GameTime gameTime)
{
if (!IsActive) return;
foreach (var component in _components)
component.Update(gameTime);
}
public void Draw(SpriteBatch spriteBatch)
{
if (!IsActive) return;
foreach (var component in _components)
component.Draw(spriteBatch);
}
}
public class SpriteRenderer : Component
{
private readonly Texture2D _texture;
public SpriteRenderer(Texture2D texture)
{
_texture = texture;
}
public override void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(_texture, Entity.Position, Color.White);
}
}
public class Velocity : Component
{
public Vector2 Speed { get; set; }
public override void Update(GameTime gameTime)
{
var delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
Entity.Position += Speed * delta;
}
}
Collision Detection
Implement axis-aligned bounding box (AABB) collision detection for 2D game objects.
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace MyGame;
public struct BoundingBox2D
{
public Rectangle Bounds { get; }
public BoundingBox2D(Vector2 position, int width, int height)
{
Bounds = new Rectangle((int)position.X, (int)position.Y, width, height);
}
public bool Intersects(BoundingBox2D other) => Bounds.Intersects(other.Bounds);
}
public class CollisionSystem
{
private readonly List<Entity> _entities;
public CollisionSystem(List<Entity> entities)
{
_entities = entities;
}
public void CheckCollisions()
{
for (int i = 0; i < _entities.Count; i++)
{
for (int j = i + 1; j < _entities.Count; j++)
{
var a = _entities[i].GetComponent<BoxCollider>();
var b = _entities[j].GetComponent<BoxCollider>();
if (a is not null && b is not null && a.GetBounds().Intersects(b.GetBounds()))
{
a.OnCollision?.Invoke(_entities[j]);
b.OnCollision?.Invoke(_entities[i]);
}
}
}
}
}
public class BoxCollider : Component
{
public int Width { get; set; }
public int Height { get; set; }
public Action<Entity>? OnCollision { get; set; }
public BoundingBox2D GetBounds() => new(Entity.Position, Width, Height);
}
MonoGame vs Other Game Frameworks
| Feature | MonoGame | Unity | Godot | FNA |
|---|---|---|---|---|
| Language | C# | C# / C++ | GDScript / C# | C# |
| Editor | None (code-only) | Full visual editor | Full visual editor | None (code-only) |
| 2D support | Excellent | Good | Excellent | Excellent |
| 3D support | Manual | Full engine | Good | Manual |
| Asset pipeline | Content Pipeline tool | Built-in | Built-in | Content Pipeline |
| Console support | Yes (licensed) | Yes (licensed) | Limited | Yes (licensed) |
| License | MIT | Proprietary | MIT | MIT |
| Learning curve | Steep (no editor) | Moderate | Moderate | Steep |
Best Practices
Use the Content Pipeline (MGCB Editor) to preprocess all assets (textures, audio, fonts, effects) into
.xnbformat at build time rather than loading raw files at runtime; the pipeline compresses textures into GPU-native formats and validates assets during the build, catching missing or corrupt files before deployment.Separate game state updates from rendering by keeping all mutation in
Updateand all draw calls inDraw, never modifying entity positions or game state insideDraw; MonoGame may skipDrawcalls under heavy load but always callsUpdateat the fixed timestep rate.Cache frequently used objects like
Vector2,Rectangle, andColoras struct fields rather than allocating them per frame insideUpdateorDraw; allocating reference types every frame increases garbage collection pressure, which causes frame-rate hitches on mobile platforms.Implement object pooling for bullets, particles, and other short-lived entities using a
Queue<T>orStack<T>of pre-allocated instances, callingReset()instead ofnew;List.Add/Removepatterns for hundreds of projectiles cause GC spikes visible in the frame-time profiler.Batch all
SpriteBatch.Drawcalls between a singleBegin/Endpair sorted by texture usingSpriteSortMode.Textureto minimize GPU state changes; eachBegin/Endpair submits a separate draw call, and exceeding 100 draw calls per frame degrades performance on integrated GPUs.Use
gameTime.ElapsedGameTime.TotalSecondsas a delta-time multiplier for all movement and animation instead of assuming a fixed 1/60th-second tick, so that gameplay remains consistent even whenIsFixedTimeStepisfalseor the game runs on a 120Hz display.Store input state from the previous frame (
_previousKeyboard,_previousMouse) and compare with the current frame to detect single-press events viacurrentState.IsKeyDown(key) && !previousState.IsKeyDown(key), preventing continuous-fire behavior from held keys.Load large assets (level data, audio banks) asynchronously using
Task.Runwith a loading screen rather than blocking inLoadContent, because synchronous loads exceeding 2 seconds trigger ANR (Application Not Responding) dialogs on Android and watchdog kills on iOS.Define game constants (screen dimensions, physics gravity, spawn rates) in a static
GameConfigclass or load from JSON rather than scattering magic numbers throughUpdateandDrawmethods, enabling runtime tuning during development without recompilation.Run the game at a target of 60 FPS using
TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 60.0)withIsFixedTimeStep = trueand measure frame time withgameTime.IsRunningSlowlyto detect performance regressions; whenIsRunningSlowlyis true, reduce particle counts or skip non-essential visual effects to maintain a stable frame rate.