alca259/monogame-skills

monogame-ecs

MonoGame component system (ECS) guide using GameBehaviour / GameEntity / GameWorld.

Vedi sorgente
Documento Skill originale

Contenuto dal repository con titoli, esempi, codice, tabelle, link e immagini preservati.

MonoGame Component System (ECS)

This skill covers the custom lightweight component system for MonoGame projects. The naming and mental model deliberately mirror Unity:

This systemUnity equivalent
GameEntityGameObject
GameBehaviourMonoBehaviour
GameWorldScene
entity.GetComponent<T>()gameObject.GetComponent<T>()

For the full API reference and complete implementation code see references/ecs.md.


Core Concepts

GameBehaviour is the base class for every piece of logic. Override only the lifecycle hooks you need — the engine only calls the ones a behaviour actually overrides (see Performance below).

csharp
class GravityBehaviour : GameBehaviour, IGravity
{
    public float Scale { get; set; } = 1f;
    private TransformBehaviour _transform;

    // Awake: resolve sibling dependencies — like Unity's Awake()
    public override void Awake()
        => _transform = Entity.GetComponent<TransformBehaviour>();

    public override void Update(GameTime gt)
    {
        float dt = (float)gt.ElapsedGameTime.TotalSeconds;
        _transform.Velocity += Vector2.UnitY * Scale * 980f * dt;
    }
}

GameEntity is a named container of GameBehaviours. Every entity is born with a Guid Id and a TransformBehaviour pre-attached — accessible as entity.Transform, exactly like Unity's gameObject.transform. You never add Transform manually:

csharp
_world.CreateEntity("Player", new Vector2(100, 200))
      .Add(new SpriteBehaviour(texture))
      .Add(new GravityBehaviour { Scale = 1f })
      .Add(new PlayerInputBehaviour());

// Direct access — no GetComponent<> needed:
entity.Transform.Position += Vector2.UnitX * speed;
Console.WriteLine(entity.Id);   // Guid
Console.WriteLine(entity.Name); // "Player"

GameWorld owns all entities, drives the loop, and handles safe mid-frame creation/destruction:

csharp
// In Game1.Update():
_world.Update(gameTime);

// In Game1.Draw():
_world.Draw(gameTime, _spriteBatch);

Lifecycle Order

Add()       → Awake()          (immediately, synchronous)
              ↓
First frame → Start()          (before first Update, after all Awakes in scene)
              ↓
Every frame → Update(gt)       (only if behaviour overrides it)
              ↓
Every frame → Draw(gameTime, sb)  (only if behaviour overrides it)
              ↓
Destroy()   → OnDestroy()      (deferred to end of frame)

Use Awake to cache sibling component references. Use Start for logic that depends on all entities/behaviours being initialized.


Domain Interfaces

Define capability contracts as interfaces for queries and loose coupling — never for logic or inheritance chains. The interfaces below (IGravity, ICollidable, IDamageable) are illustrative examples — they are not part of the library. Define your own project interfaces.

csharp
interface IGravity    { float Scale { get; set; } }
interface ICollidable { Rectangle Bounds { get; } }
interface IDamageable { int Health { get; } void TakeDamage(int amount); }
interface IPoolable   { void Reset(); }

GetComponent<T>() and HasComponent<T>() work with both concrete types and interfaces:

csharp
// Concrete:
var transform = entity.GetComponent<TransformBehaviour>();

// Interface — works even when you don't know the concrete type:
var damageable = entity.GetComponent<IDamageable>();
damageable?.TakeDamage(10);

// Query all entities with a given capability:
foreach (var e in _world.FindEntities<ICollidable>())
    ResolveCollision(e);

Inter-Component Communication

Components talk to siblings through the entity, not through direct references or statics:

csharp
// GOOD — resolved once in Awake, used every frame
class AttackBehaviour : GameBehaviour
{
    private IDamageable _target;

    public override void Awake()
        => _target = Entity.GetComponent<IDamageable>();

    public override void Update(GameTime gt)
    {
        if (ShouldAttack())
            _target?.TakeDamage(10);
    }
}

Enable / Disable

Individual behaviours and entire entities can be toggled at runtime:

csharp
entity.GetComponent<GravityBehaviour>().Enabled = false;  // pause gravity
entity.Active = false;                                     // freeze whole entity

Disabled behaviours are skipped in Update/Draw. Inactive entities skip everything.


Creating and Destroying Entities

Creation and destruction are always deferred to the start of the next Update — safe to call from inside Update or collision callbacks:

csharp
// Spawn a bullet from inside another behaviour's Update():
var bullet = Entity.World.CreateEntity("Bullet")
                   .Add(new TransformBehaviour(pos))
                   .Add(new BulletBehaviour(direction, speed));

// Destroy when expired (deferred — entity finishes this frame normally):
Entity.World.Destroy(Entity);

Performance

Separate update/draw lists

GameEntity uses reflection once at Add time to detect whether a behaviour overrides Update or Draw. Only those behaviours land in the hot-path lists. A component that only has data (e.g. TransformBehaviour, HealthBehaviour) costs zero per frame.

1,000 entities × 3 behaviours = 3,000 Add() checks  (one-time, at spawn)
                                = 0–3,000 Update() calls/frame  (only overrides)

Object Pooling for Frequent Spawns

For entities that spawn and die constantly (bullets, particles, enemies), allocating on the heap each time will pressure the GC. Implement IPoolable on the behaviour and maintain a pool in the spawning system:

csharp
// Mark poolable behaviours with the interface
class BulletBehaviour : GameBehaviour, IPoolable
{
    public void Reset()  // called when retrieved from pool
    {
        Enabled = true;
        _lifetime = 0f;
    }
}

// Simple pool — reuse instead of new/Destroy
var bullet = _bulletPool.Get();    // Reset() called internally
// ...
_bulletPool.Return(bullet);        // instead of World.Destroy()

See references/ecs.md for a minimal generic pool implementation.


Why GameEntity is sealed

GameEntity is a dumb container — it has no game logic and should never have any. Customization always goes in behaviours, not in subclasses:

csharp
// ❌ Never do this
class EnemyEntity : GameEntity { public int Health { get; set; } }

// ✅ Compose instead
world.CreateEntity("Enemy")
     .Add(new HealthBehaviour(100))
     .Add(new AIBehaviour())
     .Add(new SpriteBehaviour(tex));

The payoff: behaviours can be added/removed at runtime, shared across entity types, and tested in isolation. A HealthBehaviour works on a player, an enemy, or a destructible crate — same code, no inheritance.


Rules

  • Cache GetComponent<T>() calls in Awake() — never call them inside Update().
  • Awake resolves own siblings. Start resolves cross-entity dependencies.
  • Never call World.Destroy() on an entity and then read its state the same frame —

treat it as dead immediately after the call.

  • Keep GameBehaviours focused: one responsibility per class. Prefer small behaviours

composed together over large monolithic ones.

  • Domain interfaces (IGravity, ICollidable…) define what, not how. Define your own — the library does not ship these.
  • Use entity.Active = false to pause temporarily; use World.Destroy() to remove

permanently.

dallo stesso repository

Altri Skills

Tutti gli Skills
alca259
Community

monogame-audio

MonoGame audio implementation guide covering SoundEffect vs Song, SoundEffectInstance pooling, volume/pitch control, 3D audio, DynamicSoundEffectInstance, WAV streaming via TitleContainer, microphone recording, and memory management rules. Use this skill whenever the user asks about playing sounds, background music, audio effects, looping audio, 3D positional sound, sound pools, microphone input, recording audio, raw WAV loading, InstancePlayLimitException, or any audio-related implementation in

installazioni
2
GitHub Stars
2
Aggiornato
22 set
alca259
Community

monogame-camera-modes

MonoGame 3D camera mode patterns — Fixed, Fixed Tracking, First Person, Third Person (with spring), Top-Down Fixed, and Top-Down Tracked. Use this skill whenever the user asks about camera modes, first person camera, third person camera, top-down camera, tracking camera, camera spring, follow camera, or how to implement a specific camera behaviour in a 3D game — even if they just say "I want the camera to follow the player" or "how do I make a first person / third person / top-down view".

installazioni
2
GitHub Stars
2
Aggiornato
22 set
alca259
Community

monogame-effects

MonoGame custom effects guide covering MGFX runtime, stock effects (BasicEffect, SpriteEffect, etc.), cross-platform shader compilation (HLSL vs GLSL), shader model targets, preprocessor symbols, and portability pitfalls. Use this skill whenever the user asks about custom effects, MGFX, stock effects, cross-platform shaders, shader model compatibility, porting a shader to MonoGame, preprocessor conditionals in .fx files, or gets compilation errors with shaders — even if they just say "my shader

installazioni
2
GitHub Stars
2
Aggiornato
22 set
alca259
Community

monogame-math

MonoGame math implementation guide covering Vector2/3/4, Matrix, Rectangle, collision detection, camera transforms, and MathHelper utilities. Use this skill whenever the user asks about vectors, positions, distances, rotations, scaling, collision detection, AABB, bounding volumes, 2D camera math, coordinate transforms, or any math-related implementation in MonoGame.

installazioni
2
GitHub Stars
2
Aggiornato
22 set