AI-Powered Level Generation
Welcome to Lesson 2! In this lesson, you'll learn how to create procedural dungeons and levels using AI assistance. We'll build a dynamic level generation system that creates unique, engaging environments for our AI Dungeon Explorer game.
What You'll Build
In this lesson, you'll create a Procedural Dungeon Generator that:
- Uses AI to design level layouts and room configurations
- Generates unique dungeon structures with varied themes
- Creates intelligent room connections and pathways
- Implements AI-driven content placement and balancing
Step 1: Understanding Procedural Generation with AI
Traditional vs AI-Assisted Procedural Generation
Traditional Approach:
- Fixed algorithms and mathematical formulas
- Predictable patterns and limited variation
- Requires extensive manual tuning
- Difficult to create thematically coherent content
AI-Assisted Approach:
- AI generates design concepts and layouts
- Natural language descriptions for level themes
- Intelligent content placement and balancing
- Dynamic adaptation based on player behavior
AI Prompting for Level Design
Basic Level Concept Generation
Design a dungeon level for a 3D game with these specifications:
- Size: 15x15 units
- Theme: Ancient temple with mystical elements
- Rooms: 4 main chambers, 2 corridors, 1 secret area
- Hazards: Traps, puzzles, environmental challenges
- Objectives: Find 3 keys, solve 2 puzzles, defeat mini-boss
- Atmosphere: Mysterious, slightly foreboding
- Lighting: Dim, flickering torches, magical glow
- Include specific room descriptions and connections
Advanced Layout Generation
Create a detailed floor plan for a dungeon level:
- Start room: Player spawn point with tutorial elements
- Hub room: Central area with multiple exits
- Challenge rooms: 3 rooms with different difficulty levels
- Boss room: Final confrontation area
- Secret areas: Hidden rooms with valuable rewards
- Include dimensions, door placements, and room purposes
- Specify lighting, atmosphere, and audio cues for each area
Step 2: Setting Up the Level Generation System
Creating the Dungeon Generator Script
Let's create our AI-assisted dungeon generator:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
[System.Serializable]
public class DungeonRoom
{
public string name;
public string description;
public Vector2 position;
public Vector2 size;
public RoomType type;
public List<string> connections;
public List<string> contents;
public string atmosphere;
public string lighting;
}
public enum RoomType
{
Start,
Hub,
Challenge,
Boss,
Secret,
Corridor
}
public class AIDungeonGenerator : MonoBehaviour
{
[Header("Generation Settings")]
public int dungeonSize = 15;
public int minRooms = 5;
public int maxRooms = 12;
public float roomSpacing = 3f;
[Header("AI Integration")]
public string aiPrompt = "";
public string theme = "Ancient Temple";
public string difficulty = "Medium";
[Header("Generated Data")]
public List<DungeonRoom> generatedRooms = new List<DungeonRoom>();
public string dungeonDescription = "";
void Start()
{
GenerateDungeon();
}
public void GenerateDungeon()
{
// Step 1: Generate dungeon concept with AI
GenerateDungeonConcept();
// Step 2: Create room layout
CreateRoomLayout();
// Step 3: Generate room contents
GenerateRoomContents();
// Step 4: Create connections
CreateRoomConnections();
// Step 5: Apply theming and atmosphere
ApplyTheming();
}
void GenerateDungeonConcept()
{
// This would integrate with your AI service
// For now, we'll use a template approach
dungeonDescription = $"A {theme} dungeon with {Random.Range(minRooms, maxRooms + 1)} rooms, " +
$"featuring {difficulty} difficulty challenges and mysterious atmosphere.";
}
void CreateRoomLayout()
{
generatedRooms.Clear();
// Generate rooms using AI-assisted layout
int roomCount = Random.Range(minRooms, maxRooms + 1);
for (int i = 0; i < roomCount; i++)
{
DungeonRoom room = new DungeonRoom();
room.name = GenerateRoomName(i);
room.description = GenerateRoomDescription(i);
room.position = GenerateRoomPosition();
room.size = GenerateRoomSize();
room.type = DetermineRoomType(i, roomCount);
room.contents = new List<string>();
room.connections = new List<string>();
generatedRooms.Add(room);
}
}
string GenerateRoomName(int index)
{
string[] roomNames = {
"Entrance Chamber", "Central Hall", "Treasure Vault", "Guardian's Sanctum",
"Ancient Library", "Crystal Cavern", "Shadow Passage", "Light Chamber",
"Puzzle Room", "Resting Place", "Boss Arena", "Secret Sanctum"
};
return roomNames[index % roomNames.Length];
}
string GenerateRoomDescription(int index)
{
// This would be generated by AI in a real implementation
string[] descriptions = {
"A grand entrance with towering pillars and mystical energy",
"A central hub connecting multiple pathways",
"A treasure chamber filled with ancient artifacts",
"A guardian's domain with powerful defenses",
"A library of ancient knowledge and secrets",
"A cavern filled with glowing crystals",
"A dark passage shrouded in mystery",
"A bright chamber filled with divine light"
};
return descriptions[index % descriptions.Length];
}
Vector2 GenerateRoomPosition()
{
return new Vector2(
Random.Range(-dungeonSize/2, dungeonSize/2),
Random.Range(-dungeonSize/2, dungeonSize/2)
);
}
Vector2 GenerateRoomSize()
{
return new Vector2(
Random.Range(3f, 8f),
Random.Range(3f, 8f)
);
}
RoomType DetermineRoomType(int index, int totalRooms)
{
if (index == 0) return RoomType.Start;
if (index == totalRooms - 1) return RoomType.Boss;
if (index == totalRooms - 2) return RoomType.Secret;
if (index % 3 == 0) return RoomType.Hub;
return RoomType.Challenge;
}
void GenerateRoomContents()
{
foreach (var room in generatedRooms)
{
// Generate AI-assisted room contents
room.contents = GenerateContentsForRoom(room);
}
}
List<string> GenerateContentsForRoom(DungeonRoom room)
{
List<string> contents = new List<string>();
switch (room.type)
{
case RoomType.Start:
contents.Add("Tutorial elements");
contents.Add("Basic equipment");
contents.Add("Save point");
break;
case RoomType.Hub:
contents.Add("Multiple exits");
contents.Add("Information NPC");
contents.Add("Rest area");
break;
case RoomType.Challenge:
contents.Add("Puzzle elements");
contents.Add("Enemy encounters");
contents.Add("Reward chests");
break;
case RoomType.Boss:
contents.Add("Boss enemy");
contents.Add("Arena setup");
contents.Add("Victory rewards");
break;
case RoomType.Secret:
contents.Add("Hidden treasures");
contents.Add("Secret passages");
contents.Add("Lore elements");
break;
}
return contents;
}
void CreateRoomConnections()
{
// Create logical connections between rooms
for (int i = 0; i < generatedRooms.Count - 1; i++)
{
var currentRoom = generatedRooms[i];
var nextRoom = generatedRooms[i + 1];
currentRoom.connections.Add(nextRoom.name);
nextRoom.connections.Add(currentRoom.name);
}
// Add some random connections for complexity
AddRandomConnections();
}
void AddRandomConnections()
{
int connectionCount = Random.Range(1, 3);
for (int i = 0; i < connectionCount; i++)
{
var room1 = generatedRooms[Random.Range(0, generatedRooms.Count)];
var room2 = generatedRooms[Random.Range(0, generatedRooms.Count)];
if (room1 != room2 && !room1.connections.Contains(room2.name))
{
room1.connections.Add(room2.name);
room2.connections.Add(room1.name);
}
}
}
void ApplyTheming()
{
foreach (var room in generatedRooms)
{
room.atmosphere = GenerateAtmosphere(room);
room.lighting = GenerateLighting(room);
}
}
string GenerateAtmosphere(DungeonRoom room)
{
string[] atmospheres = {
"Mysterious and foreboding",
"Ancient and sacred",
"Dark and dangerous",
"Magical and ethereal",
"Cold and lifeless",
"Warm and inviting"
};
return atmospheres[Random.Range(0, atmospheres.Length)];
}
string GenerateLighting(DungeonRoom room)
{
string[] lightingOptions = {
"Dim torchlight with flickering shadows",
"Bright magical glow from crystals",
"Eerie blue light from ancient sources",
"Warm golden light from braziers",
"Dark with only minimal illumination",
"Dynamic lighting that changes mood"
};
return lightingOptions[Random.Range(0, lightingOptions.Length)];
}
}
Step 3: AI Integration for Content Generation
Creating the AI Content Generator
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class AIContentGenerator : MonoBehaviour
{
[Header("AI Settings")]
public string apiKey = "";
public string baseUrl = "https://api.openai.com/v1/chat/completions";
[Header("Generation Parameters")]
public string theme = "Ancient Temple";
public string difficulty = "Medium";
public int roomCount = 8;
public IEnumerator GenerateDungeonWithAI(System.Action<string> onComplete)
{
string prompt = CreateDungeonPrompt();
// This would make an actual API call to your AI service
// For now, we'll simulate the response
yield return new WaitForSeconds(1f);
string aiResponse = SimulateAIResponse();
onComplete?.Invoke(aiResponse);
}
string CreateDungeonPrompt()
{
return $@"Create a detailed dungeon layout for a 3D game with these specifications:
Theme: {theme}
Difficulty: {difficulty}
Room Count: {roomCount}
Size: 15x15 units
Requirements:
- Include room names, descriptions, and purposes
- Specify connections between rooms
- Add atmospheric details and lighting
- Include puzzles, challenges, and rewards
- Create a coherent narrative flow
Format the response as JSON with the following structure:
{{
""dungeon"": {{
""name"": ""Dungeon Name"",
""description"": ""Overall description"",
""rooms"": [
{{
""name"": ""Room Name"",
""description"": ""Room description"",
""type"": ""start|hub|challenge|boss|secret"",
""position"": [x, y],
""size"": [width, height],
""connections"": [""room1"", ""room2""],
""contents"": [""item1"", ""item2""],
""atmosphere"": ""Atmospheric description"",
""lighting"": ""Lighting description""
}}
]
}}
}}";
}
string SimulateAIResponse()
{
// This simulates an AI response - in reality, you'd parse the actual API response
return @"{
""dungeon"": {
""name"": ""Temple of Eternal Shadows"",
""description"": ""An ancient temple filled with mystical energy and dark secrets"",
""rooms"": [
{
""name"": ""Entrance Hall"",
""description"": ""A grand entrance with towering pillars and mystical energy"",
""type"": ""start"",
""position"": [0, 0],
""size"": [5, 5],
""connections"": [""Central Chamber""],
""contents"": [""Tutorial elements"", ""Basic equipment""],
""atmosphere"": ""Mysterious and foreboding"",
""lighting"": ""Dim torchlight with flickering shadows""
}
]
}
}";
}
}
Step 4: Level Visualization and Testing
Creating the Dungeon Visualizer
using UnityEngine;
using System.Collections.Generic;
public class DungeonVisualizer : MonoBehaviour
{
[Header("Visualization Settings")]
public GameObject roomPrefab;
public GameObject corridorPrefab;
public Material[] roomMaterials;
public Color[] roomColors;
[Header("Generated Dungeon")]
public AIDungeonGenerator dungeonGenerator;
private List<GameObject> spawnedObjects = new List<GameObject>();
void Start()
{
if (dungeonGenerator != null)
{
VisualizeDungeon();
}
}
public void VisualizeDungeon()
{
ClearVisualization();
foreach (var room in dungeonGenerator.generatedRooms)
{
CreateRoomVisualization(room);
}
CreateCorridorVisualizations();
}
void CreateRoomVisualization(DungeonRoom room)
{
GameObject roomObj = Instantiate(roomPrefab);
roomObj.transform.position = new Vector3(room.position.x, 0, room.position.y);
roomObj.transform.localScale = new Vector3(room.size.x, 1, room.size.y);
// Apply visual theming
ApplyRoomTheming(roomObj, room);
// Add room information
RoomInfoDisplay infoDisplay = roomObj.AddComponent<RoomInfoDisplay>();
infoDisplay.roomData = room;
spawnedObjects.Add(roomObj);
}
void ApplyRoomTheming(GameObject roomObj, DungeonRoom room)
{
Renderer renderer = roomObj.GetComponent<Renderer>();
if (renderer != null)
{
// Apply material based on room type
int materialIndex = (int)room.type % roomMaterials.Length;
renderer.material = roomMaterials[materialIndex];
// Apply color based on room type
int colorIndex = (int)room.type % roomColors.Length;
renderer.material.color = roomColors[colorIndex];
}
// Add lighting based on room description
AddRoomLighting(roomObj, room);
}
void AddRoomLighting(GameObject roomObj, DungeonRoom room)
{
Light roomLight = roomObj.AddComponent<Light>();
roomLight.type = LightType.Point;
roomLight.range = 10f;
roomLight.intensity = 1f;
// Adjust lighting based on room type and description
switch (room.type)
{
case RoomType.Start:
roomLight.color = Color.white;
roomLight.intensity = 1.5f;
break;
case RoomType.Boss:
roomLight.color = Color.red;
roomLight.intensity = 2f;
break;
case RoomType.Secret:
roomLight.color = Color.blue;
roomLight.intensity = 0.5f;
break;
default:
roomLight.color = Color.yellow;
roomLight.intensity = 1f;
break;
}
}
void CreateCorridorVisualizations()
{
foreach (var room in dungeonGenerator.generatedRooms)
{
foreach (var connection in room.connections)
{
var connectedRoom = dungeonGenerator.generatedRooms.Find(r => r.name == connection);
if (connectedRoom != null)
{
CreateCorridor(room, connectedRoom);
}
}
}
}
void CreateCorridor(DungeonRoom room1, DungeonRoom room2)
{
Vector3 start = new Vector3(room1.position.x, 0, room1.position.y);
Vector3 end = new Vector3(room2.position.x, 0, room2.position.y);
Vector3 direction = (end - start).normalized;
float distance = Vector3.Distance(start, end);
GameObject corridor = Instantiate(corridorPrefab);
corridor.transform.position = (start + end) / 2;
corridor.transform.rotation = Quaternion.LookRotation(direction);
corridor.transform.localScale = new Vector3(1, 1, distance);
spawnedObjects.Add(corridor);
}
void ClearVisualization()
{
foreach (var obj in spawnedObjects)
{
if (obj != null)
{
DestroyImmediate(obj);
}
}
spawnedObjects.Clear();
}
}
[System.Serializable]
public class RoomInfoDisplay : MonoBehaviour
{
public DungeonRoom roomData;
void OnMouseDown()
{
DisplayRoomInfo();
}
void DisplayRoomInfo()
{
Debug.Log($"Room: {roomData.name}");
Debug.Log($"Description: {roomData.description}");
Debug.Log($"Type: {roomData.type}");
Debug.Log($"Atmosphere: {roomData.atmosphere}");
Debug.Log($"Lighting: {roomData.lighting}");
Debug.Log($"Contents: {string.Join(", ", roomData.contents)}");
}
}
Step 5: Advanced AI Integration Techniques
Dynamic Content Adaptation
using UnityEngine;
using System.Collections.Generic;
public class DynamicContentAdapter : MonoBehaviour
{
[Header("Player Data")]
public int playerLevel = 1;
public string playerClass = "Warrior";
public List<string> completedChallenges = new List<string>();
[Header("AI Settings")]
public AIContentGenerator contentGenerator;
public void AdaptDungeonToPlayer()
{
string adaptationPrompt = CreateAdaptationPrompt();
// Send to AI for content adaptation
StartCoroutine(AdaptContent(adaptationPrompt));
}
string CreateAdaptationPrompt()
{
return $@"Adapt the current dungeon to better suit this player:
Player Level: {playerLevel}
Player Class: {playerClass}
Completed Challenges: {string.Join(", ", completedChallenges)}
Requirements:
- Adjust difficulty to match player level
- Include content relevant to player class
- Avoid repeating completed challenges
- Add new challenges appropriate for progression
- Maintain thematic coherence
Provide specific recommendations for:
- Enemy types and difficulty
- Puzzle complexity
- Reward quality and type
- Environmental hazards
- Narrative elements";
}
System.Collections.IEnumerator AdaptContent(string prompt)
{
// This would make an actual API call
yield return new WaitForSeconds(1f);
// Process the AI response and apply adaptations
ApplyContentAdaptations();
}
void ApplyContentAdaptations()
{
// Apply AI-suggested adaptations to the dungeon
Debug.Log("Applying AI-generated content adaptations...");
}
}
Step 6: Testing and Iteration
Dungeon Testing Framework
using UnityEngine;
using System.Collections.Generic;
public class DungeonTester : MonoBehaviour
{
[Header("Test Settings")]
public bool runAutomatedTests = true;
public int testIterations = 10;
[Header("Test Results")]
public List<TestResult> testResults = new List<TestResult>();
void Start()
{
if (runAutomatedTests)
{
RunAutomatedTests();
}
}
void RunAutomatedTests()
{
for (int i = 0; i < testIterations; i++)
{
TestResult result = TestDungeonGeneration();
testResults.Add(result);
}
AnalyzeTestResults();
}
TestResult TestDungeonGeneration()
{
TestResult result = new TestResult();
// Test dungeon generation
AIDungeonGenerator generator = FindObjectOfType<AIDungeonGenerator>();
if (generator != null)
{
generator.GenerateDungeon();
// Test room connectivity
result.roomsGenerated = generator.generatedRooms.Count;
result.connectivityScore = TestConnectivity(generator.generatedRooms);
result.themeCoherence = TestThemeCoherence(generator.generatedRooms);
result.balanceScore = TestBalance(generator.generatedRooms);
}
return result;
}
float TestConnectivity(List<DungeonRoom> rooms)
{
// Test if all rooms are reachable
int connectedRooms = 0;
foreach (var room in rooms)
{
if (room.connections.Count > 0)
{
connectedRooms++;
}
}
return (float)connectedRooms / rooms.Count;
}
float TestThemeCoherence(List<DungeonRoom> rooms)
{
// Test thematic consistency
int coherentRooms = 0;
foreach (var room in rooms)
{
if (IsThematicallyCoherent(room))
{
coherentRooms++;
}
}
return (float)coherentRooms / rooms.Count;
}
bool IsThematicallyCoherent(DungeonRoom room)
{
// Check if room description matches its type and atmosphere
return !string.IsNullOrEmpty(room.description) &&
!string.IsNullOrEmpty(room.atmosphere) &&
!string.IsNullOrEmpty(room.lighting);
}
float TestBalance(List<DungeonRoom> rooms)
{
// Test difficulty and reward balance
int challengeRooms = 0;
int rewardRooms = 0;
foreach (var room in rooms)
{
if (room.type == RoomType.Challenge) challengeRooms++;
if (room.contents.Contains("treasure") || room.contents.Contains("reward")) rewardRooms++;
}
// Ideal ratio: 2 challenge rooms per reward room
float idealRatio = 2f;
float actualRatio = (float)challengeRooms / Mathf.Max(1, rewardRooms);
return 1f - Mathf.Abs(actualRatio - idealRatio) / idealRatio;
}
void AnalyzeTestResults()
{
float avgConnectivity = 0f;
float avgThemeCoherence = 0f;
float avgBalance = 0f;
foreach (var result in testResults)
{
avgConnectivity += result.connectivityScore;
avgThemeCoherence += result.themeCoherence;
avgBalance += result.balanceScore;
}
avgConnectivity /= testResults.Count;
avgThemeCoherence /= testResults.Count;
avgBalance /= testResults.Count;
Debug.Log($"Test Results Analysis:");
Debug.Log($"Average Connectivity: {avgConnectivity:P}");
Debug.Log($"Average Theme Coherence: {avgThemeCoherence:P}");
Debug.Log($"Average Balance Score: {avgBalance:P}");
if (avgConnectivity < 0.8f)
{
Debug.LogWarning("Connectivity issues detected. Consider improving room connection logic.");
}
if (avgThemeCoherence < 0.9f)
{
Debug.LogWarning("Theme coherence issues detected. Consider improving AI prompting.");
}
if (avgBalance < 0.7f)
{
Debug.LogWarning("Balance issues detected. Consider improving content distribution.");
}
}
}
[System.Serializable]
public class TestResult
{
public int roomsGenerated;
public float connectivityScore;
public float themeCoherence;
public float balanceScore;
}
Next Steps
In the next lesson, you'll learn Creating Smart NPCs with AI - how to build intelligent characters that use AI for decision-making, dialogue generation, and behavior adaptation.
Key Takeaways
- AI can generate complex, thematically coherent level designs
- Procedural generation benefits greatly from AI assistance
- Testing and iteration are crucial for quality assurance
- Dynamic content adaptation improves player experience
Resources for Further Learning
- Unity Procedural Generation
- AI Game Builder - Practice AI-assisted level design
- Community Forums - Share your procedural generation techniques
Ready to create intelligent NPCs? Let's move on to Lesson 3: Creating Smart NPCs with AI!