AI Skill Report Card
Developing 2D MonoGame Games
Quick Start
CSHARP// Basic MonoGame 2D game structure public class Game1 : Game { private GraphicsDeviceManager _graphics; private SpriteBatch _spriteBatch; private Texture2D _playerTexture; private Vector2 _playerPosition; protected override void LoadContent() { _spriteBatch = new SpriteBatch(GraphicsDevice); _playerTexture = Content.Load<Texture2D>("player"); _playerPosition = new Vector2(400, 300); } protected override void Update(GameTime gameTime) { var keyboardState = Keyboard.GetState(); float speed = 200f * (float)gameTime.ElapsedGameTime.TotalSeconds; if (keyboardState.IsKeyDown(Keys.W)) _playerPosition.Y -= speed; if (keyboardState.IsKeyDown(Keys.S)) _playerPosition.Y += speed; if (keyboardState.IsKeyDown(Keys.A)) _playerPosition.X -= speed; if (keyboardState.IsKeyDown(Keys.D)) _playerPosition.X += speed; base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); _spriteBatch.Begin(); _spriteBatch.Draw(_playerTexture, _playerPosition, Color.White); _spriteBatch.End(); base.Draw(gameTime); } }
Workflow
Game Architecture Setup
Progress:
- Create solution with proper folder structure (Content, Entities, Systems, Managers)
- Set up Content Pipeline with appropriate processors
- Implement base Entity/Component system or use existing framework
- Create GameStateManager for scenes/screens
- Set up input handling system
Core Development Loop
- Design Phase: Define game mechanics, create sprite sheets, plan entity relationships
- Implementation: Code entities, systems, and game logic using composition patterns
- Content Integration: Import assets via Content Pipeline, set up animations
- Testing: Profile performance, test on target platforms
- Polish: Add particle effects, audio, UI, and juice effects
Performance Optimization
- Profile with MonoGame's built-in tools
- Batch sprite draws by texture
- Use object pooling for frequently created/destroyed objects
- Implement spatial partitioning for collision detection
- Cache frequently used calculations
Examples
Example 1: Entity Component System Input: Need a flexible player entity with movement, health, and sprite components Output:
CSHARPpublic class Entity { private Dictionary<Type, IComponent> _components = new(); public T GetComponent<T>() where T : IComponent => (T)_components[typeof(T)]; public void AddComponent<T>(T component) where T : IComponent => _components[typeof(T)] = component; } public class MovementComponent : IComponent { public Vector2 Velocity { get; set; } public float Speed { get; set; } = 100f; } public class MovementSystem { public void Update(Entity entity, GameTime gameTime) { var movement = entity.GetComponent<MovementComponent>(); var transform = entity.GetComponent<TransformComponent>(); transform.Position += movement.Velocity * movement.Speed * (float)gameTime.ElapsedGameTime.TotalSeconds; } }
Example 2: Optimized Sprite Batch Rendering Input: Render multiple sprites efficiently with different textures Output:
CSHARPpublic class SpriteRenderer { private readonly List<SpriteData> _sprites = new(); public void AddSprite(Texture2D texture, Vector2 position, Rectangle? source = null) { _sprites.Add(new SpriteData(texture, position, source)); } public void Render(SpriteBatch spriteBatch) { // Group by texture to minimize state changes var grouped = _sprites.GroupBy(s => s.Texture); foreach (var group in grouped) { spriteBatch.Begin(); foreach (var sprite in group) { spriteBatch.Draw(sprite.Texture, sprite.Position, sprite.SourceRect, Color.White); } spriteBatch.End(); } _sprites.Clear(); } }
Best Practices
- Use fixed timestep for deterministic physics:
IsFixedTimeStep = true - Separate logic from rendering: Update in Update(), draw in Draw()
- Implement proper game states: Menu, Playing, Paused, GameOver
- Use Rectangle for collision detection with spatial partitioning for large numbers
- Cache ContentManager.Load calls or use dependency injection
- Handle different screen resolutions with viewport scaling
- Use Vector2.Zero, Vector2.One instead of new Vector2(0,0)
Common Pitfalls
- Don't load content in Update() - causes frame drops and memory issues
- Don't create new objects every frame - use object pooling for bullets, particles
- Don't ignore GameTime.ElapsedGameTime - leads to frame rate dependent movement
- Don't use string concatenation in Update() - use StringBuilder or string interpolation
- Don't call spriteBatch.Begin() multiple times unnecessarily - batch draws by texture
- Don't forget to call base.Update() and base.Draw() - breaks MonoGame lifecycle
- Don't hardcode screen dimensions - use GraphicsDevice.Viewport