Game Development Aug 5, 2025

How to Build a Roguelike Game from Scratch - Complete Unity Tutorial

Learn how to build a complete roguelike game in Unity from scratch. This comprehensive tutorial covers procedural generation, permadeath mechanics, and creating engaging roguelike gameplay.

By GamineAI Team

How to Build a Roguelike Game from Scratch - Complete Unity Tutorial

Roguelike games have captured players' imaginations for decades, combining challenging gameplay with infinite replayability. Building a roguelike in Unity gives you the perfect opportunity to learn procedural generation, game design patterns, and how to create engaging gameplay loops that keep players coming back.

This comprehensive tutorial will guide you through building a complete roguelike game from scratch, covering everything from basic movement to procedural dungeon generation. By the end, you'll have a playable roguelike that demonstrates core mechanics and can serve as a foundation for your own projects.

What You'll Build

By following this tutorial, you'll create a roguelike game featuring:

  • Procedurally generated dungeons
  • Turn-based movement and combat
  • Permadeath mechanics
  • Random item generation
  • Enemy AI and combat systems
  • Multiple levels with increasing difficulty

Prerequisites

Before starting, make sure you have:

  • Unity 2021.3 LTS or later installed
  • Basic understanding of C# programming
  • Familiarity with Unity's interface
  • A passion for roguelike games

Setting Up Your Unity Project

Step 1: Create New Project

  1. Open Unity Hub
  2. Click New Project
  3. Select 2D Core template
  4. Name your project "RoguelikeGame"
  5. Choose a location and click Create

Step 2: Organize Project Structure Create these folders in your Assets directory:

  • Scripts/ - All C# scripts
  • Prefabs/ - Game object prefabs
  • Sprites/ - 2D sprites and textures
  • Scenes/ - Unity scenes
  • Materials/ - Materials and shaders

Step 3: Set Up the Main Scene

  1. Create a new scene: FileNew Scene2D
  2. Save it as MainScene in your Scenes folder
  3. Set up a camera with orthographic projection
  4. Create an empty GameObject named "GameManager"

Creating the Player Character

The player character is the heart of your roguelike. Let's start with basic movement and then add combat capabilities.

Step 1: Create Player Sprite

  1. Create a simple square sprite or import a character sprite
  2. Create a GameObject with SpriteRenderer component
  3. Name it "Player"
  4. Position it at (0, 0, 0)

Step 2: Implement Turn-Based Movement Create a script called PlayerController.cs:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 1f;
    private Vector3 targetPosition;
    private bool isMoving = false;

    void Start()
    {
        targetPosition = transform.position;
    }

    void Update()
    {
        HandleInput();
        MoveTowardsTarget();
    }

    void HandleInput()
    {
        if (isMoving) return;

        Vector3 moveDirection = Vector3.zero;

        if (Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow))
            moveDirection = Vector3.up;
        else if (Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.DownArrow))
            moveDirection = Vector3.down;
        else if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow))
            moveDirection = Vector3.left;
        else if (Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow))
            moveDirection = Vector3.right;

        if (moveDirection != Vector3.zero)
        {
            targetPosition = transform.position + moveDirection;
            isMoving = true;
        }
    }

    void MoveTowardsTarget()
    {
        if (isMoving)
        {
            transform.position = Vector3.MoveTowards(
                transform.position,
                targetPosition,
                moveSpeed * Time.deltaTime
            );

            if (Vector3.Distance(transform.position, targetPosition) < 0.01f)
            {
                transform.position = targetPosition;
                isMoving = false;
            }
        }
    }
}

Step 3: Add Collision Detection Modify the movement to check for walls and obstacles before moving:

bool CanMoveTo(Vector3 position)
{
    // Check for walls using Physics2D
    Collider2D hit = Physics2D.OverlapPoint(position);
    return hit == null || !hit.CompareTag("Wall");
}

Procedural Dungeon Generation

Procedural generation is what makes roguelikes endlessly replayable. We'll implement a simple but effective dungeon generator.

Step 1: Create Dungeon Generator Script Create DungeonGenerator.cs:

using UnityEngine;
using System.Collections.Generic;

public class DungeonGenerator : MonoBehaviour
{
    [SerializeField] private int width = 50;
    [SerializeField] private int height = 50;
    [SerializeField] private int roomCount = 10;
    [SerializeField] private int minRoomSize = 4;
    [SerializeField] private int maxRoomSize = 10;

    private bool[,] dungeonMap;
    private List<Rect> rooms = new List<Rect>();

    public void GenerateDungeon()
    {
        dungeonMap = new bool[width, height];
        rooms.Clear();

        // Initialize map as all walls
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                dungeonMap[x, y] = false; // false = wall
            }
        }

        // Generate rooms
        GenerateRooms();

        // Connect rooms with corridors
        ConnectRooms();

        // Visualize dungeon
        VisualizeDungeon();
    }

    void GenerateRooms()
    {
        for (int i = 0; i < roomCount; i++)
        {
            int roomWidth = Random.Range(minRoomSize, maxRoomSize + 1);
            int roomHeight = Random.Range(minRoomSize, maxRoomSize + 1);
            int roomX = Random.Range(1, width - roomWidth - 1);
            int roomY = Random.Range(1, height - roomHeight - 1);

            Rect newRoom = new Rect(roomX, roomY, roomWidth, roomHeight);

            // Check if room overlaps with existing rooms
            bool overlaps = false;
            foreach (Rect room in rooms)
            {
                if (newRoom.Overlaps(room))
                {
                    overlaps = true;
                    break;
                }
            }

            if (!overlaps)
            {
                rooms.Add(newRoom);
                CarveRoom(newRoom);
            }
        }
    }

    void CarveRoom(Rect room)
    {
        for (int x = (int)room.x; x < room.x + room.width; x++)
        {
            for (int y = (int)room.y; y < room.y + room.height; y++)
            {
                dungeonMap[x, y] = true; // true = floor
            }
        }
    }

    void ConnectRooms()
    {
        for (int i = 1; i < rooms.Count; i++)
        {
            Vector2 previousRoomCenter = new Vector2(
                rooms[i - 1].x + rooms[i - 1].width / 2,
                rooms[i - 1].y + rooms[i - 1].height / 2
            );

            Vector2 currentRoomCenter = new Vector2(
                rooms[i].x + rooms[i].width / 2,
                rooms[i].y + rooms[i].height / 2
            );

            // Create horizontal corridor
            if (Random.Range(0, 2) == 0)
            {
                CarveHorizontalCorridor((int)previousRoomCenter.x, (int)currentRoomCenter.x, (int)previousRoomCenter.y);
                CarveVerticalCorridor((int)previousRoomCenter.y, (int)currentRoomCenter.y, (int)currentRoomCenter.x);
            }
            else
            {
                CarveVerticalCorridor((int)previousRoomCenter.y, (int)currentRoomCenter.y, (int)previousRoomCenter.x);
                CarveHorizontalCorridor((int)previousRoomCenter.x, (int)currentRoomCenter.x, (int)currentRoomCenter.y);
            }
        }
    }

    void CarveHorizontalCorridor(int x1, int x2, int y)
    {
        for (int x = Mathf.Min(x1, x2); x <= Mathf.Max(x1, x2); x++)
        {
            dungeonMap[x, y] = true;
        }
    }

    void CarveVerticalCorridor(int y1, int y2, int x)
    {
        for (int y = Mathf.Min(y1, y2); y <= Mathf.Max(y1, y2); y++)
        {
            dungeonMap[x, y] = true;
        }
    }

    void VisualizeDungeon()
    {
        // Create visual representation of dungeon
        // This would create GameObjects for walls and floors
        // Implementation depends on your art assets
    }
}

Step 2: Integrate with Game Manager Add dungeon generation to your GameManager:

public class GameManager : MonoBehaviour
{
    [SerializeField] private DungeonGenerator dungeonGenerator;

    void Start()
    {
        dungeonGenerator.GenerateDungeon();
    }
}

Implementing Combat System

Combat in roguelikes is typically turn-based and simple but strategic.

Step 1: Create Combat System Create CombatSystem.cs:

using UnityEngine;

public class CombatSystem : MonoBehaviour
{
    [SerializeField] private int playerHealth = 100;
    [SerializeField] private int playerAttack = 10;
    [SerializeField] private int playerDefense = 5;

    public void AttackEnemy(Enemy enemy)
    {
        int damage = Mathf.Max(1, playerAttack - enemy.defense);
        enemy.TakeDamage(damage);
    }

    public void TakeDamage(int damage)
    {
        int actualDamage = Mathf.Max(1, damage - playerDefense);
        playerHealth -= actualDamage;

        if (playerHealth <= 0)
        {
            Die();
        }
    }

    void Die()
    {
        // Implement permadeath - restart game
        Debug.Log("You died! Game Over.");
        // Restart or show game over screen
    }
}

Step 2: Create Enemy System Create Enemy.cs:

using UnityEngine;

public class Enemy : MonoBehaviour
{
    [SerializeField] private int health = 30;
    [SerializeField] private int attack = 5;
    [SerializeField] private int defense = 2;

    public int defense => this.defense;

    public void TakeDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            Die();
        }
    }

    void Die()
    {
        // Drop loot, grant experience
        Destroy(gameObject);
    }
}

Adding Permadeath and Progression

Permadeath is a core roguelike mechanic. When the player dies, they restart from the beginning, but they can unlock permanent upgrades.

Step 1: Implement Permadeath Modify your GameManager to handle death:

public void OnPlayerDeath()
{
    // Save any permanent progress
    SavePermanentProgress();

    // Reset game state
    ResetGame();

    // Show death screen
    ShowDeathScreen();
}

void ResetGame()
{
    // Regenerate dungeon
    dungeonGenerator.GenerateDungeon();

    // Reset player stats
    playerController.ResetPlayer();

    // Clear enemies and items
    ClearLevel();
}

Step 2: Add Permanent Upgrades Create a system for permanent upgrades that persist across runs:

public class PermanentUpgrades : MonoBehaviour
{
    private int totalRunsCompleted = 0;
    private int totalEnemiesKilled = 0;
    private int permanentHealthBonus = 0;
    private int permanentAttackBonus = 0;

    public void OnRunComplete()
    {
        totalRunsCompleted++;
        // Unlock upgrades based on progress
    }

    public void ApplyUpgrades(PlayerController player)
    {
        player.AddHealth(permanentHealthBonus);
        player.AddAttack(permanentAttackBonus);
    }
}

Adding Items and Loot

Items add depth and strategy to roguelikes. Let's implement a simple item system.

Step 1: Create Item System Create Item.cs:

using UnityEngine;

[System.Serializable]
public class Item
{
    public string name;
    public ItemType type;
    public int value;
    public string description;

    public enum ItemType
    {
        Weapon,
        Armor,
        Consumable,
        Key
    }
}

public class ItemPickup : MonoBehaviour
{
    [SerializeField] private Item item;

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            PickupItem(other.GetComponent<PlayerController>());
        }
    }

    void PickupItem(PlayerController player)
    {
        player.AddItem(item);
        Destroy(gameObject);
    }
}

Step 2: Random Item Generation Create items procedurally:

public class ItemGenerator : MonoBehaviour
{
    public Item GenerateRandomItem(int level)
    {
        Item item = new Item();
        item.type = (Item.ItemType)Random.Range(0, System.Enum.GetValues(typeof(Item.ItemType)).Length);
        item.value = Random.Range(level, level + 5);
        item.name = GenerateItemName(item.type);
        item.description = GenerateItemDescription(item);
        return item;
    }

    string GenerateItemName(Item.ItemType type)
    {
        string[] prefixes = { "Rusty", "Iron", "Steel", "Magic", "Ancient" };
        string[] suffixes = { "Sword", "Shield", "Potion", "Key" };

        return prefixes[Random.Range(0, prefixes.Length)] + " " + 
               suffixes[(int)type];
    }
}

Enemy AI and Behavior

Enemies need simple AI to move and attack the player.

Step 1: Implement Enemy AI Create EnemyAI.cs:

using UnityEngine;

public class EnemyAI : MonoBehaviour
{
    [SerializeField] private float moveCooldown = 1f;
    private float lastMoveTime = 0f;
    private Transform player;

    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player").transform;
    }

    void Update()
    {
        if (Time.time - lastMoveTime >= moveCooldown)
        {
            MoveTowardsPlayer();
            lastMoveTime = Time.time;
        }
    }

    void MoveTowardsPlayer()
    {
        Vector3 direction = (player.position - transform.position).normalized;
        Vector3 targetPosition = transform.position + direction;

        // Check if can move
        if (CanMoveTo(targetPosition))
        {
            transform.position = targetPosition;
        }
        else if (Vector3.Distance(transform.position, player.position) < 1.5f)
        {
            // Attack player if adjacent
            AttackPlayer();
        }
    }

    bool CanMoveTo(Vector3 position)
    {
        Collider2D hit = Physics2D.OverlapPoint(position);
        return hit == null || (!hit.CompareTag("Wall") && !hit.CompareTag("Enemy"));
    }

    void AttackPlayer()
    {
        // Deal damage to player
        player.GetComponent<CombatSystem>().TakeDamage(GetComponent<Enemy>().attack);
    }
}

Level Progression

Add multiple levels with increasing difficulty.

Step 1: Create Level System Modify GameManager to handle level progression:

public class LevelManager : MonoBehaviour
{
    private int currentLevel = 1;
    [SerializeField] private int enemiesPerLevel = 5;

    public void OnLevelComplete()
    {
        currentLevel++;
        GenerateNextLevel();
    }

    void GenerateNextLevel()
    {
        // Increase difficulty
        dungeonGenerator.SetDifficulty(currentLevel);
        dungeonGenerator.GenerateDungeon();

        // Spawn more enemies
        SpawnEnemies(enemiesPerLevel + currentLevel);

        // Reset player position
        ResetPlayerPosition();
    }
}

Polishing Your Roguelike

Add these finishing touches to make your roguelike feel complete:

UI Elements

  • Health bar
  • Minimap
  • Inventory display
  • Level indicator
  • Score/statistics

Visual Polish

  • Sprite animations
  • Particle effects
  • Screen shake on combat
  • Smooth camera follow
  • Tile-based rendering

Audio

  • Background music
  • Sound effects for movement
  • Combat sounds
  • Ambient dungeon sounds

Game Feel

  • Screen transitions
  • Death animations
  • Item pickup effects
  • Victory conditions
  • Tutorial or help system

Testing and Balancing

Playtesting Checklist

  • Test dungeon generation for playability
  • Verify combat feels balanced
  • Check that permadeath works correctly
  • Ensure items provide meaningful choices
  • Test difficulty progression

Common Issues and Fixes

  • Dungeons too easy: Increase enemy count, reduce room sizes
  • Combat too hard: Adjust damage values, add more healing items
  • Too repetitive: Add more room types, enemy varieties, items
  • Performance issues: Optimize dungeon generation, use object pooling

Expanding Your Roguelike

Once you have the basics working, consider adding:

Advanced Features

  • Multiple character classes
  • Skill trees and abilities
  • Boss encounters
  • Secret rooms and special events
  • Multiple biomes or themes
  • Save system for runs in progress

Content Additions

  • More enemy types with unique behaviors
  • Expanded item system with set bonuses
  • Environmental hazards
  • Traps and puzzles
  • NPCs and shops
  • Multiple endings

Best Practices

Code Organization

  • Use clear naming conventions
  • Separate concerns (movement, combat, generation)
  • Create reusable systems
  • Comment complex algorithms
  • Use ScriptableObjects for data

Performance

  • Object pool enemies and items
  • Optimize dungeon generation
  • Use efficient collision detection
  • Minimize garbage collection
  • Profile regularly

Design Principles

  • Keep mechanics simple but deep
  • Ensure every decision matters
  • Balance risk and reward
  • Provide clear feedback
  • Test difficulty carefully

Conclusion

Building a roguelike game teaches you valuable skills in procedural generation, game design, and system architecture. The foundation you've created can be expanded into a full game or serve as a learning project for understanding roguelike mechanics.

The key to a great roguelike is balancing randomness with player agency. Every run should feel different, but players should feel their choices matter. Keep iterating, playtesting, and refining until your game feels satisfying to play.

Ready to start building? Follow this tutorial step by step, and don't be afraid to experiment and add your own features. The best roguelikes come from developers who understand the genre and add their own creative twists.


FAQ

Q: How long does it take to build a roguelike?

A: A basic roguelike can be built in a few days, but a polished game takes weeks or months. Start simple and iterate.

Q: Do I need advanced programming skills?

A: Basic C# and Unity knowledge is enough to start. The tutorial covers everything you need.

Q: Can I use this for commercial projects?

A: Yes, the code in this tutorial is yours to use. Consider adding your own unique features and art.

Q: What makes a good roguelike?

A: Good roguelikes balance randomness with strategy, offer meaningful choices, and provide clear feedback. Every run should feel winnable but challenging.

Q: How do I make my roguelike stand out?

A: Add unique mechanics, interesting themes, or innovative systems. Study successful roguelikes and find what makes them special, then add your own twist.


Found this tutorial helpful? Bookmark it for reference and share your roguelike creations with the community. For more Unity tutorials and game development guides, check out our Unity guides and game development resources.