Creating Smart NPCs with AI - Lesson 3

Build intelligent NPCs that use AI for decision-making, dialogue generation, and behavior adaptation in your Unity game.

By GamineAI Team

Creating Smart NPCs with AI

Welcome to Lesson 3! In this lesson, you'll learn how to create intelligent NPCs that use AI for decision-making, dialogue generation, and behavior adaptation. We'll build a comprehensive NPC system that brings your AI Dungeon Explorer to life with smart, responsive characters.

What You'll Build

In this lesson, you'll create an AI-Powered NPC System that includes:

  • Intelligent NPCs with AI-driven decision making
  • Dynamic Dialogue System with AI-generated conversations
  • Adaptive Behavior that responds to player actions
  • Personality System that creates unique character traits
  • Memory System that tracks player interactions

Step 1: Understanding AI-Powered NPCs

Traditional vs AI-Enhanced NPCs

Traditional NPCs:

  • Fixed dialogue trees and responses
  • Scripted behaviors and interactions
  • Limited personality and character depth
  • Predictable and repetitive interactions

AI-Enhanced NPCs:

  • Dynamic, context-aware dialogue generation
  • Adaptive behaviors based on player actions
  • Rich personality systems with consistent traits
  • Memorable interactions that evolve over time

Core AI NPC Components

  1. Personality Engine: Defines character traits and behavioral patterns
  2. Dialogue Generator: Creates contextually appropriate conversations
  3. Decision Maker: Chooses actions based on personality and situation
  4. Memory System: Tracks and recalls past interactions
  5. Behavior Adapter: Modifies behavior based on player relationship

Step 2: Creating the NPC Personality System

Personality Traits and Characteristics

using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public class PersonalityTraits
{
    [Header("Core Traits")]
    [Range(0, 100)] public int friendliness = 50;
    [Range(0, 100)] public int intelligence = 50;
    [Range(0, 100)] public int courage = 50;
    [Range(0, 100)] public int humor = 50;
    [Range(0, 100)] public int aggression = 50;

    [Header("Communication Style")]
    [Range(0, 100)] public int formality = 50;
    [Range(0, 100)] public int verbosity = 50;
    [Range(0, 100)] public int directness = 50;

    [Header("Values and Beliefs")]
    public List<string> values = new List<string>();
    public List<string> fears = new List<string>();
    public List<string> goals = new List<string>();

    public string GetPersonalityDescription()
    {
        return $"A {GetTraitLevel(friendliness)} person who is {GetTraitLevel(intelligence)} and " +
               $"tends to be {GetTraitLevel(courage)}. They communicate in a {GetTraitLevel(formality)} manner.";
    }

    string GetTraitLevel(int value)
    {
        if (value < 20) return "very low";
        if (value < 40) return "low";
        if (value < 60) return "moderate";
        if (value < 80) return "high";
        return "very high";
    }
}

[System.Serializable]
public class NPCData
{
    public string name;
    public string role;
    public string background;
    public PersonalityTraits personality;
    public List<string> dialogueTopics = new List<string>();
    public Dictionary<string, int> relationshipScores = new Dictionary<string, int>();
    public List<string> memories = new List<string>();
}

public class AINPC : MonoBehaviour
{
    [Header("NPC Configuration")]
    public NPCData npcData;
    public Transform player;
    public float interactionRange = 3f;

    [Header("AI Settings")]
    public AIDialogueGenerator dialogueGenerator;
    public AIBehaviorController behaviorController;

    [Header("Visual Components")]
    public Animator animator;
    public GameObject speechBubble;
    public TextMesh speechText;

    private bool isInteracting = false;
    private string currentDialogue = "";

    void Start()
    {
        InitializeNPC();
    }

    void InitializeNPC()
    {
        // Generate personality if not set
        if (npcData.personality == null)
        {
            npcData.personality = GenerateRandomPersonality();
        }

        // Initialize relationship with player
        if (!npcData.relationshipScores.ContainsKey("Player"))
        {
            npcData.relationshipScores["Player"] = 50; // Neutral
        }

        // Set up AI components
        if (dialogueGenerator == null)
        {
            dialogueGenerator = gameObject.AddComponent<AIDialogueGenerator>();
        }

        if (behaviorController == null)
        {
            behaviorController = gameObject.AddComponent<AIBehaviorController>();
        }
    }

    PersonalityTraits GenerateRandomPersonality()
    {
        PersonalityTraits traits = new PersonalityTraits();

        // Generate random traits with some coherence
        traits.friendliness = Random.Range(20, 80);
        traits.intelligence = Random.Range(30, 90);
        traits.courage = Random.Range(20, 80);
        traits.humor = Random.Range(10, 70);
        traits.aggression = Random.Range(10, 60);

        // Communication style
        traits.formality = Random.Range(20, 80);
        traits.verbosity = Random.Range(30, 90);
        traits.directness = Random.Range(20, 80);

        // Generate values and goals
        traits.values = GenerateRandomValues();
        traits.fears = GenerateRandomFears();
        traits.goals = GenerateRandomGoals();

        return traits;
    }

    List<string> GenerateRandomValues()
    {
        string[] possibleValues = {
            "Honor", "Knowledge", "Peace", "Justice", "Freedom", "Family", "Power", "Wisdom"
        };

        List<string> values = new List<string>();
        int valueCount = Random.Range(2, 5);

        for (int i = 0; i < valueCount; i++)
        {
            string value = possibleValues[Random.Range(0, possibleValues.Length)];
            if (!values.Contains(value))
            {
                values.Add(value);
            }
        }

        return values;
    }

    List<string> GenerateRandomFears()
    {
        string[] possibleFears = {
            "Death", "Failure", "Betrayal", "Darkness", "Loneliness", "Weakness", "Change"
        };

        List<string> fears = new List<string>();
        int fearCount = Random.Range(1, 4);

        for (int i = 0; i < fearCount; i++)
        {
            string fear = possibleFears[Random.Range(0, possibleFears.Length)];
            if (!fears.Contains(fear))
            {
                fears.Add(fear);
            }
        }

        return fears;
    }

    List<string> GenerateRandomGoals()
    {
        string[] possibleGoals = {
            "Protect the innocent", "Gain knowledge", "Find treasure", "Defeat evil", 
            "Help others", "Become stronger", "Explore the world", "Make friends"
        };

        List<string> goals = new List<string>();
        int goalCount = Random.Range(1, 3);

        for (int i = 0; i < goalCount; i++)
        {
            string goal = possibleGoals[Random.Range(0, possibleGoals.Length)];
            if (!goals.Contains(goal))
            {
                goals.Add(goal);
            }
        }

        return goals;
    }

    void Update()
    {
        if (player != null)
        {
            float distance = Vector3.Distance(transform.position, player.position);

            if (distance <= interactionRange && !isInteracting)
            {
                ShowInteractionPrompt();
            }
            else if (distance > interactionRange && isInteracting)
            {
                HideInteractionPrompt();
            }
        }
    }

    void ShowInteractionPrompt()
    {
        // Show interaction UI
        if (speechBubble != null)
        {
            speechBubble.SetActive(true);
        }
    }

    void HideInteractionPrompt()
    {
        // Hide interaction UI
        if (speechBubble != null)
        {
            speechBubble.SetActive(false);
        }
    }

    public void StartInteraction()
    {
        if (!isInteracting)
        {
            isInteracting = true;
            StartCoroutine(GenerateAndDisplayDialogue());
        }
    }

    System.Collections.IEnumerator GenerateAndDisplayDialogue()
    {
        // Generate AI dialogue based on context
        string dialogue = yield return StartCoroutine(dialogueGenerator.GenerateDialogue(npcData, GetCurrentContext()));

        // Display dialogue
        DisplayDialogue(dialogue);

        // Update relationship based on interaction
        UpdateRelationship();

        // Store memory of interaction
        StoreInteractionMemory();
    }

    DialogueContext GetCurrentContext()
    {
        DialogueContext context = new DialogueContext();
        context.playerLevel = 1; // Get from player data
        context.timeOfDay = System.DateTime.Now.Hour;
        context.location = "Dungeon";
        context.recentEvents = GetRecentEvents();
        context.playerMood = GetPlayerMood();

        return context;
    }

    List<string> GetRecentEvents()
    {
        // Get recent game events that might affect dialogue
        List<string> events = new List<string>();
        events.Add("Player entered dungeon");
        events.Add("Recent monster encounters");
        return events;
    }

    string GetPlayerMood()
    {
        // Determine player mood based on recent actions
        return "Curious"; // Default mood
    }

    void DisplayDialogue(string dialogue)
    {
        currentDialogue = dialogue;

        if (speechText != null)
        {
            speechText.text = dialogue;
        }

        // Animate NPC
        if (animator != null)
        {
            animator.SetTrigger("Talk");
        }

        // Show dialogue for a duration
        StartCoroutine(ShowDialogueForDuration(5f));
    }

    System.Collections.IEnumerator ShowDialogueForDuration(float duration)
    {
        yield return new WaitForSeconds(duration);
        HideDialogue();
    }

    void HideDialogue()
    {
        if (speechText != null)
        {
            speechText.text = "";
        }

        isInteracting = false;
    }

    void UpdateRelationship()
    {
        // Update relationship based on interaction quality
        int relationshipChange = CalculateRelationshipChange();
        npcData.relationshipScores["Player"] = Mathf.Clamp(
            npcData.relationshipScores["Player"] + relationshipChange, 0, 100);
    }

    int CalculateRelationshipChange()
    {
        // Calculate relationship change based on dialogue quality and NPC personality
        int change = 0;

        // Friendly NPCs are easier to please
        if (npcData.personality.friendliness > 70)
        {
            change += Random.Range(1, 4);
        }
        else if (npcData.personality.friendliness < 30)
        {
            change += Random.Range(-2, 1);
        }

        return change;
    }

    void StoreInteractionMemory()
    {
        string memory = $"Interacted with player at {System.DateTime.Now:HH:mm}";
        npcData.memories.Add(memory);

        // Keep only recent memories
        if (npcData.memories.Count > 10)
        {
            npcData.memories.RemoveAt(0);
        }
    }
}

Step 3: AI Dialogue Generation System

Creating the Dialogue Generator

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class DialogueContext
{
    public int playerLevel;
    public int timeOfDay;
    public string location;
    public List<string> recentEvents;
    public string playerMood;
    public string weather;
    public string season;
}

public class AIDialogueGenerator : MonoBehaviour
{
    [Header("AI Settings")]
    public string apiKey = "";
    public string baseUrl = "https://api.openai.com/v1/chat/completions";

    [Header("Dialogue Settings")]
    public int maxDialogueLength = 100;
    public string[] dialogueTopics = {
        "Greeting", "Farewell", "Question", "Information", "Story", "Advice", "Warning"
    };

    public IEnumerator GenerateDialogue(NPCData npcData, DialogueContext context)
    {
        string prompt = CreateDialoguePrompt(npcData, context);

        // This would make an actual API call to your AI service
        // For now, we'll simulate the response
        yield return new WaitForSeconds(1f);

        string dialogue = SimulateDialogueGeneration(npcData, context);
        yield return dialogue;
    }

    string CreateDialoguePrompt(NPCData npcData, DialogueContext context)
    {
        return $@"Generate dialogue for an NPC in a fantasy game:

NPC Information:
- Name: {npcData.name}
- Role: {npcData.role}
- Background: {npcData.background}
- Personality: {npcData.personality.GetPersonalityDescription()}
- Values: {string.Join(", ", npcData.personality.values)}
- Fears: {string.Join(", ", npcData.personality.fears)}
- Goals: {string.Join(", ", npcData.personality.goals)}

Context:
- Player Level: {context.playerLevel}
- Time: {context.timeOfDay}:00
- Location: {context.location}
- Player Mood: {context.playerMood}
- Recent Events: {string.Join(", ", context.recentEvents)}

Relationship with Player: {npcData.relationshipScores.GetValueOrDefault("Player", 50)}/100

Requirements:
- Keep dialogue under {maxDialogueLength} characters
- Match the NPC's personality and communication style
- Be contextually appropriate for the situation
- Include personality quirks and traits
- Make it feel natural and engaging
- Consider the relationship level with the player

Generate a single dialogue response that the NPC would say to the player.";
    }

    string SimulateDialogueGeneration(NPCData npcData, DialogueContext context)
    {
        // Simulate AI-generated dialogue based on NPC personality
        List<string> possibleDialogues = new List<string>();

        // Generate dialogues based on personality traits
        if (npcData.personality.friendliness > 70)
        {
            possibleDialogues.Add("Hello there, friend! It's wonderful to see you!");
            possibleDialogues.Add("Welcome! I'm so glad you've come to visit!");
        }
        else if (npcData.personality.friendliness < 30)
        {
            possibleDialogues.Add("What do you want?");
            possibleDialogues.Add("I don't have time for this...");
        }
        else
        {
            possibleDialogues.Add("Hello. How can I help you?");
            possibleDialogues.Add("Good to see you. What brings you here?");
        }

        // Adjust based on intelligence
        if (npcData.personality.intelligence > 80)
        {
            possibleDialogues.Add("I've been studying the ancient texts. Fascinating stuff!");
            possibleDialogues.Add("The mysteries of this place are quite intriguing, don't you think?");
        }

        // Adjust based on courage
        if (npcData.personality.courage > 80)
        {
            possibleDialogues.Add("I'm not afraid of what lies ahead. Are you?");
            possibleDialogues.Add("Danger? Ha! I've faced worse than this!");
        }
        else if (npcData.personality.courage < 30)
        {
            possibleDialogues.Add("Please be careful out there... I worry about you.");
            possibleDialogues.Add("I... I don't like to think about what might be lurking...");
        }

        // Adjust based on relationship
        int relationship = npcData.relationshipScores.GetValueOrDefault("Player", 50);
        if (relationship > 80)
        {
            possibleDialogues.Add("My dear friend, it's always a pleasure to see you!");
            possibleDialogues.Add("You're like family to me now. How are you doing?");
        }
        else if (relationship < 20)
        {
            possibleDialogues.Add("I don't trust you. What are you really after?");
            possibleDialogues.Add("Stay away from me. I don't want any trouble.");
        }

        // Select appropriate dialogue
        if (possibleDialogues.Count > 0)
        {
            return possibleDialogues[Random.Range(0, possibleDialogues.Count)];
        }

        return "Hello there. How can I help you?";
    }
}

Step 4: AI Behavior Controller

Creating Adaptive Behaviors

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class AIBehaviorController : MonoBehaviour
{
    [Header("Behavior Settings")]
    public float updateInterval = 1f;
    public float behaviorChangeThreshold = 0.3f;

    [Header("Current Behavior")]
    public NPCBehavior currentBehavior;
    public float behaviorIntensity = 0.5f;

    private AINPC npc;
    private Transform player;
    private float lastUpdateTime;

    void Start()
    {
        npc = GetComponent<AINPC>();
        player = GameObject.FindGameObjectWithTag("Player")?.transform;

        // Initialize with default behavior
        currentBehavior = NPCBehavior.Idle;
    }

    void Update()
    {
        if (Time.time - lastUpdateTime >= updateInterval)
        {
            UpdateBehavior();
            lastUpdateTime = Time.time;
        }
    }

    void UpdateBehavior()
    {
        if (player == null) return;

        // Calculate behavior based on context
        NPCBehavior newBehavior = CalculateOptimalBehavior();

        if (newBehavior != currentBehavior)
        {
            ChangeBehavior(newBehavior);
        }

        // Update behavior intensity
        UpdateBehaviorIntensity();
    }

    NPCBehavior CalculateOptimalBehavior()
    {
        float distance = Vector3.Distance(transform.position, player.position);
        int relationship = npc.npcData.relationshipScores.GetValueOrDefault("Player", 50);

        // Calculate behavior based on distance and relationship
        if (distance < 2f)
        {
            if (relationship > 70)
                return NPCBehavior.Friendly;
            else if (relationship < 30)
                return NPCBehavior.Defensive;
            else
                return NPCBehavior.Cautious;
        }
        else if (distance < 5f)
        {
            if (relationship > 60)
                return NPCBehavior.Approaching;
            else
                return NPCBehavior.Observing;
        }
        else
        {
            return NPCBehavior.Idle;
        }
    }

    void ChangeBehavior(NPCBehavior newBehavior)
    {
        currentBehavior = newBehavior;
        ApplyBehavior(newBehavior);
    }

    void ApplyBehavior(NPCBehavior behavior)
    {
        switch (behavior)
        {
            case NPCBehavior.Idle:
                SetIdleBehavior();
                break;
            case NPCBehavior.Friendly:
                SetFriendlyBehavior();
                break;
            case NPCBehavior.Defensive:
                SetDefensiveBehavior();
                break;
            case NPCBehavior.Cautious:
                SetCautiousBehavior();
                break;
            case NPCBehavior.Approaching:
                SetApproachingBehavior();
                break;
            case NPCBehavior.Observing:
                SetObservingBehavior();
                break;
        }
    }

    void SetIdleBehavior()
    {
        // Idle animations and positioning
        if (npc.animator != null)
        {
            npc.animator.SetBool("IsIdle", true);
            npc.animator.SetBool("IsFriendly", false);
            npc.animator.SetBool("IsDefensive", false);
        }
    }

    void SetFriendlyBehavior()
    {
        // Friendly animations and positioning
        if (npc.animator != null)
        {
            npc.animator.SetBool("IsIdle", false);
            npc.animator.SetBool("IsFriendly", true);
            npc.animator.SetBool("IsDefensive", false);
        }

        // Face player
        if (player != null)
        {
            Vector3 direction = (player.position - transform.position).normalized;
            transform.rotation = Quaternion.LookRotation(direction);
        }
    }

    void SetDefensiveBehavior()
    {
        // Defensive animations and positioning
        if (npc.animator != null)
        {
            npc.animator.SetBool("IsIdle", false);
            npc.animator.SetBool("IsFriendly", false);
            npc.animator.SetBool("IsDefensive", true);
        }

        // Step back from player
        if (player != null)
        {
            Vector3 direction = (transform.position - player.position).normalized;
            transform.rotation = Quaternion.LookRotation(direction);
        }
    }

    void SetCautiousBehavior()
    {
        // Cautious animations and positioning
        if (npc.animator != null)
        {
            npc.animator.SetBool("IsIdle", false);
            npc.animator.SetBool("IsFriendly", false);
            npc.animator.SetBool("IsDefensive", false);
            npc.animator.SetBool("IsCautious", true);
        }
    }

    void SetApproachingBehavior()
    {
        // Approach player slowly
        if (player != null)
        {
            Vector3 direction = (player.position - transform.position).normalized;
            transform.position += direction * Time.deltaTime * 0.5f;
            transform.rotation = Quaternion.LookRotation(direction);
        }
    }

    void SetObservingBehavior()
    {
        // Observe player from distance
        if (player != null)
        {
            Vector3 direction = (player.position - transform.position).normalized;
            transform.rotation = Quaternion.LookRotation(direction);
        }
    }

    void UpdateBehaviorIntensity()
    {
        // Adjust behavior intensity based on personality and context
        float baseIntensity = 0.5f;

        // Adjust based on personality traits
        if (npc.npcData.personality.aggression > 70)
        {
            baseIntensity += 0.2f;
        }

        if (npc.npcData.personality.courage > 70)
        {
            baseIntensity += 0.1f;
        }

        behaviorIntensity = Mathf.Clamp(baseIntensity, 0f, 1f);
    }
}

public enum NPCBehavior
{
    Idle,
    Friendly,
    Defensive,
    Cautious,
    Approaching,
    Observing
}

Step 5: Memory and Learning System

Creating NPC Memory

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

[System.Serializable]
public class NPCMemory
{
    public string eventDescription;
    public System.DateTime timestamp;
    public string eventType;
    public int importance;
    public Dictionary<string, int> emotionalImpact;

    public NPCMemory(string description, string type, int importance)
    {
        eventDescription = description;
        timestamp = System.DateTime.Now;
        eventType = type;
        this.importance = importance;
        emotionalImpact = new Dictionary<string, int>();
    }
}

public class NPCMemorySystem : MonoBehaviour
{
    [Header("Memory Settings")]
    public int maxMemories = 50;
    public float memoryDecayRate = 0.1f;

    private List<NPCMemory> memories = new List<NPCMemory>();
    private AINPC npc;

    void Start()
    {
        npc = GetComponent<AINPC>();
    }

    public void AddMemory(string description, string type, int importance)
    {
        NPCMemory memory = new NPCMemory(description, type, importance);
        memories.Add(memory);

        // Remove old memories if we exceed the limit
        if (memories.Count > maxMemories)
        {
            memories = memories.OrderByDescending(m => m.importance)
                              .ThenByDescending(m => m.timestamp)
                              .Take(maxMemories)
                              .ToList();
        }
    }

    public List<NPCMemory> GetRelevantMemories(string context)
    {
        return memories.Where(m => 
            m.eventDescription.ToLower().Contains(context.ToLower()) ||
            m.eventType.ToLower().Contains(context.ToLower())
        ).OrderByDescending(m => m.importance)
         .ThenByDescending(m => m.timestamp)
         .Take(5)
         .ToList();
    }

    public List<NPCMemory> GetRecentMemories(int count = 5)
    {
        return memories.OrderByDescending(m => m.timestamp)
                      .Take(count)
                      .ToList();
    }

    public List<NPCMemory> GetImportantMemories(int count = 3)
    {
        return memories.Where(m => m.importance > 7)
                      .OrderByDescending(m => m.importance)
                      .Take(count)
                      .ToList();
    }

    public void UpdateMemoryDecay()
    {
        foreach (var memory in memories)
        {
            // Reduce importance over time
            memory.importance = Mathf.Max(0, memory.importance - memoryDecayRate);
        }

        // Remove memories with zero importance
        memories.RemoveAll(m => m.importance <= 0);
    }

    public string GetMemorySummary()
    {
        if (memories.Count == 0)
        {
            return "I don't have any memories yet.";
        }

        var recentMemories = GetRecentMemories(3);
        var importantMemories = GetImportantMemories(2);

        string summary = "I remember ";

        if (importantMemories.Count > 0)
        {
            summary += importantMemories[0].eventDescription;
            if (importantMemories.Count > 1)
            {
                summary += $" and {importantMemories[1].eventDescription}";
            }
        }
        else if (recentMemories.Count > 0)
        {
            summary += recentMemories[0].eventDescription;
        }

        return summary + ".";
    }
}

Step 6: Testing and Debugging NPCs

NPC Testing Framework

using UnityEngine;
using System.Collections.Generic;

public class NPCTester : MonoBehaviour
{
    [Header("Test Settings")]
    public bool runAutomatedTests = true;
    public int testIterations = 10;

    [Header("Test Results")]
    public List<NPCTestResult> testResults = new List<NPCTestResult>();

    void Start()
    {
        if (runAutomatedTests)
        {
            RunNPCTests();
        }
    }

    void RunNPCTests()
    {
        AINPC[] npcs = FindObjectsOfType<AINPC>();

        foreach (var npc in npcs)
        {
            NPCTestResult result = TestNPC(npc);
            testResults.Add(result);
        }

        AnalyzeNPCTestResults();
    }

    NPCTestResult TestNPC(AINPC npc)
    {
        NPCTestResult result = new NPCTestResult();
        result.npcName = npc.npcData.name;

        // Test personality coherence
        result.personalityCoherence = TestPersonalityCoherence(npc);

        // Test dialogue generation
        result.dialogueQuality = TestDialogueQuality(npc);

        // Test behavior adaptation
        result.behaviorAdaptation = TestBehaviorAdaptation(npc);

        // Test memory system
        result.memoryFunctionality = TestMemorySystem(npc);

        return result;
    }

    float TestPersonalityCoherence(AINPC npc)
    {
        // Test if personality traits are consistent
        var traits = npc.npcData.personality;

        float coherence = 0f;

        // Check if traits make sense together
        if (traits.friendliness > 70 && traits.aggression < 30)
        {
            coherence += 0.3f;
        }

        if (traits.intelligence > 70 && traits.verbosity > 50)
        {
            coherence += 0.2f;
        }

        if (traits.courage > 70 && traits.aggression > 50)
        {
            coherence += 0.2f;
        }

        // Check if values align with personality
        if (traits.values.Contains("Honor") && traits.friendliness > 60)
        {
            coherence += 0.3f;
        }

        return Mathf.Clamp01(coherence);
    }

    float TestDialogueQuality(AINPC npc)
    {
        // Test dialogue generation quality
        int testCount = 5;
        int successfulGenerations = 0;

        for (int i = 0; i < testCount; i++)
        {
            try
            {
                // Simulate dialogue generation
                string dialogue = GenerateTestDialogue(npc);

                if (!string.IsNullOrEmpty(dialogue) && dialogue.Length > 10)
                {
                    successfulGenerations++;
                }
            }
            catch
            {
                // Dialogue generation failed
            }
        }

        return (float)successfulGenerations / testCount;
    }

    string GenerateTestDialogue(AINPC npc)
    {
        // Simulate dialogue generation for testing
        return $"Hello, I'm {npc.npcData.name}. How can I help you?";
    }

    float TestBehaviorAdaptation(AINPC npc)
    {
        // Test if NPC behavior adapts to different situations
        var behaviorController = npc.GetComponent<AIBehaviorController>();
        if (behaviorController == null) return 0f;

        // Test behavior changes
        NPCBehavior initialBehavior = behaviorController.currentBehavior;

        // Simulate different scenarios
        SimulatePlayerApproach(npc);
        System.Threading.Thread.Sleep(100);

        NPCBehavior newBehavior = behaviorController.currentBehavior;

        return initialBehavior != newBehavior ? 1f : 0f;
    }

    void SimulatePlayerApproach(AINPC npc)
    {
        // Simulate player approaching NPC
        Transform player = GameObject.FindGameObjectWithTag("Player")?.transform;
        if (player != null)
        {
            player.position = npc.transform.position + Vector3.forward * 2f;
        }
    }

    float TestMemorySystem(AINPC npc)
    {
        var memorySystem = npc.GetComponent<NPCMemorySystem>();
        if (memorySystem == null) return 0f;

        // Test memory functionality
        memorySystem.AddMemory("Test event", "Test", 5);

        var memories = memorySystem.GetRecentMemories(1);
        return memories.Count > 0 ? 1f : 0f;
    }

    void AnalyzeNPCTestResults()
    {
        float avgPersonalityCoherence = 0f;
        float avgDialogueQuality = 0f;
        float avgBehaviorAdaptation = 0f;
        float avgMemoryFunctionality = 0f;

        foreach (var result in testResults)
        {
            avgPersonalityCoherence += result.personalityCoherence;
            avgDialogueQuality += result.dialogueQuality;
            avgBehaviorAdaptation += result.behaviorAdaptation;
            avgMemoryFunctionality += result.memoryFunctionality;
        }

        int npcCount = testResults.Count;
        avgPersonalityCoherence /= npcCount;
        avgDialogueQuality /= npcCount;
        avgBehaviorAdaptation /= npcCount;
        avgMemoryFunctionality /= npcCount;

        Debug.Log($"NPC Test Results Analysis:");
        Debug.Log($"Average Personality Coherence: {avgPersonalityCoherence:P}");
        Debug.Log($"Average Dialogue Quality: {avgDialogueQuality:P}");
        Debug.Log($"Average Behavior Adaptation: {avgBehaviorAdaptation:P}");
        Debug.Log($"Average Memory Functionality: {avgMemoryFunctionality:P}");

        // Provide recommendations
        if (avgPersonalityCoherence < 0.7f)
        {
            Debug.LogWarning("Personality coherence issues detected. Consider improving personality generation logic.");
        }

        if (avgDialogueQuality < 0.8f)
        {
            Debug.LogWarning("Dialogue quality issues detected. Consider improving AI prompting.");
        }

        if (avgBehaviorAdaptation < 0.6f)
        {
            Debug.LogWarning("Behavior adaptation issues detected. Consider improving behavior logic.");
        }

        if (avgMemoryFunctionality < 0.9f)
        {
            Debug.LogWarning("Memory system issues detected. Consider improving memory implementation.");
        }
    }
}

[System.Serializable]
public class NPCTestResult
{
    public string npcName;
    public float personalityCoherence;
    public float dialogueQuality;
    public float behaviorAdaptation;
    public float memoryFunctionality;
}

Next Steps

In the final lesson, you'll learn Prototype Completion and Polish - how to finish your AI Dungeon Explorer with professional touches, optimization, and deployment.

Key Takeaways

  • AI can create rich, personality-driven NPCs with consistent behaviors
  • Dynamic dialogue generation creates engaging, contextual conversations
  • Memory systems make NPCs feel alive and responsive
  • Testing frameworks ensure NPC quality and functionality

Resources for Further Learning

Ready to polish your prototype? Let's move on to Lesson 4: Prototype Completion and Polish!