Your First Game

Build a simple collector game where you move a player to collect items while avoiding enemies.

What We'll Build

In this tutorial, you'll create a complete game with:

Step 1: Project Setup

Create a new project and set up the basic engine:

using Xengine.Core;
using Xengine.Engine;
using Xengine.Math;
using Xengine.Scene;
using Xengine.Rendering;

var config = new ApplicationConfig
{
    WindowWidth = 1280,
    WindowHeight = 720,
    WindowTitle = "Collector Game"
};

using var engine = new Engine(config);
engine.Initialize();

var scene = engine.LoadScene("GameScene");

// We'll add game objects here...

engine.Run();

Step 2: Create the Player

Create a player component with WASD movement:

public class Player : Component
{
    public float Speed = 5f;
    public static int Score = 0;
    
    protected override void OnUpdate()
    {
        var move = Vector3.Zero;
        
        if (RaylibInput.IsKeyDown(KeyCode.W)) move.Z += 1;
        if (RaylibInput.IsKeyDown(KeyCode.S)) move.Z -= 1;
        if (RaylibInput.IsKeyDown(KeyCode.A)) move.X -= 1;
        if (RaylibInput.IsKeyDown(KeyCode.D)) move.X += 1;
        
        if (move.LengthSquared > 0)
        {
            move = move.Normalized * Speed * Time.Delta;
            Transform.Position += move;
        }
    }
    
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawCube(Transform.Position, Vector3.One, Color.Blue);
    }
}

Step 3: Add Collectibles

Create items that the player can collect:

public class Collectible : Component
{
    private Vector3 _basePos;
    
    protected override void OnStart()
    {
        _basePos = Transform.Position;
    }
    
    protected override void OnUpdate()
    {
        // Bobbing animation
        var pos = _basePos;
        pos.Y += MathF.Sin(Time.Now * 3f) * 0.2f;
        Transform.Position = pos;
        
        // Check collision with player
        var player = Scene.FindByName("Player");
        if (player != null)
        {
            float dist = Transform.Position.DistanceTo(player.Transform.Position);
            if (dist < 1.2f)
            {
                Player.Score += 10;
                Log.Info($"Score: {Player.Score}");
                GameObject.Destroy();
            }
        }
    }
    
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawSphere(Transform.Position, 0.3f, Color.Yellow);
    }
}

Step 4: Add Camera

Create a camera that follows the player:

public class FollowCamera : Component
{
    public Vector3 Offset = new Vector3(0, 10, -8);
    public float SmoothSpeed = 5f;
    private Vector3 _currentPos;
    
    protected override void OnStart()
    {
        _currentPos = Offset;
    }
    
    protected override void OnUpdate()
    {
        var player = Scene.FindByName("Player");
        if (player == null) return;
        
        var targetPos = player.Transform.Position + Offset;
        _currentPos = Vector3.Lerp(_currentPos, targetPos, SmoothSpeed * Time.Delta);
        
        RaylibRenderer.Instance?.SetCamera(_currentPos, player.Transform.Position, 60f);
    }
}

Step 5: Add Score UI

Display the score on screen:

public class ScoreUI : Component
{
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawText($"Score: {Player.Score}", 20, 20, 32, Color.White);
        r.DrawText("WASD to move, collect the yellow orbs!", 20, 60, 18, Color.Gray);
    }
}

Step 6: Add Ground

Create a simple ground plane:

public class Ground : Component
{
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawPlane(Vector3.Zero, new Vector2(30, 30), new Color(0.2f, 0.25f, 0.2f));
        r.DrawGrid(30, 1f);
    }
}

Step 7: Put It All Together

Add all objects to the scene:

using Xengine.Core;
using Xengine.Engine;
using Xengine.Math;
using Xengine.Scene;
using Xengine.Rendering;

var config = new ApplicationConfig
{
    WindowWidth = 1280,
    WindowHeight = 720,
    WindowTitle = "Collector Game"
};

using var engine = new Engine(config);
engine.Initialize();

var scene = engine.LoadScene("GameScene");

// Create player
var player = scene.CreateGameObject("Player", new Vector3(0, 0.5f, 0));
player.AddComponent<Player>();

// Create camera
var camera = scene.CreateGameObject("Camera");
camera.AddComponent<FollowCamera>();

// Create ground
scene.CreateGameObject("Ground").AddComponent<Ground>();

// Create collectibles
var random = new XRandom(42);
for (int i = 0; i < 15; i++)
{
    var pos = new Vector3(
        random.Float(-12, 12),
        0.8f,
        random.Float(-12, 12)
    );
    var item = scene.CreateGameObject($"Item{i}", pos);
    item.AddComponent<Collectible>();
}

// Create UI
scene.CreateGameObject("UI").AddComponent<ScoreUI>();

engine.Run();

// === Component Classes Below ===

public class Player : Component
{
    public float Speed = 5f;
    public static int Score = 0;
    
    protected override void OnUpdate()
    {
        var move = Vector3.Zero;
        if (RaylibInput.IsKeyDown(KeyCode.W)) move.Z += 1;
        if (RaylibInput.IsKeyDown(KeyCode.S)) move.Z -= 1;
        if (RaylibInput.IsKeyDown(KeyCode.A)) move.X -= 1;
        if (RaylibInput.IsKeyDown(KeyCode.D)) move.X += 1;
        
        if (move.LengthSquared > 0)
            Transform.Position += move.Normalized * Speed * Time.Delta;
    }
    
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawCube(Transform.Position, Vector3.One, Color.Blue);
    }
}

public class Collectible : Component
{
    private Vector3 _basePos;
    
    protected override void OnStart() => _basePos = Transform.Position;
    
    protected override void OnUpdate()
    {
        Transform.Position = _basePos + new Vector3(0, MathF.Sin(Time.Now * 3f) * 0.2f, 0);
        
        var player = Scene.FindByName("Player");
        if (player != null && Transform.Position.DistanceTo(player.Transform.Position) < 1.2f)
        {
            Player.Score += 10;
            GameObject.Destroy();
        }
    }
    
    protected override void OnRender(RaylibRenderer r) => r.DrawSphere(Transform.Position, 0.3f, Color.Yellow);
}

public class FollowCamera : Component
{
    private Vector3 _offset = new(0, 10, -8);
    private Vector3 _pos;
    
    protected override void OnStart() => _pos = _offset;
    
    protected override void OnUpdate()
    {
        var player = Scene.FindByName("Player");
        if (player == null) return;
        _pos = Vector3.Lerp(_pos, player.Transform.Position + _offset, 5f * Time.Delta);
        RaylibRenderer.Instance?.SetCamera(_pos, player.Transform.Position, 60f);
    }
}

public class Ground : Component
{
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawPlane(Vector3.Zero, new Vector2(30, 30), new Color(0.2f, 0.25f, 0.2f));
        r.DrawGrid(30, 1f);
    }
}

public class ScoreUI : Component
{
    protected override void OnRender(RaylibRenderer r)
    {
        r.DrawText($"Score: {Player.Score}", 20, 20, 32, Color.White);
        r.DrawText("WASD to move, collect the yellow orbs!", 20, 60, 18, Color.Gray);
    }
}

Next Steps

Congratulations! You've built your first game. Here are some ideas to expand it: