Skip to main content
tutorial

How to Add AI Chat to Unity: ChatGPT, Claude & Gemini Guide (2025)

Angry Shark Studio
15 min read
Unity ChatGPT Claude Gemini AI LLM Tutorial NPCs Beginner Unity AI Integration Smart NPCs

Difficulty Level: Beginner

What You’ll Build Today

By the end of this tutorial, you’ll have NPCs that:

  • Remember who you are
  • Respond differently based on what you did
  • Have actual personalities
  • Never repeat the same dialogue twice

Quick Answer: We’re connecting Unity to AI language models (ChatGPT, Claude, or Gemini) so your NPCs can think and talk like real characters. It’s like giving them actual brains instead of pre-written scripts.

What You Need Before Starting

  • Unity 2022.3 or newer (older versions work too)
  • Basic Unity knowledge (you can make a cube move)
  • Internet connection (for the AI to work)
  • API Key (free tier available - I’ll show you how)
  • 10 minutes (seriously, it’s that quick)

No coding experience? No problem. Just copy and paste. If you’re new to Unity development, check our beginner’s guide to common Unity mistakes first.

Why Your NPCs Need This

Split screen showing two Unity game views side by side with NPC dialogue comparison

Remember talking to NPCs in Skyrim?

You: Saves the world
Guard: “Let me guess, someone stole your sweetroll?”

That’s what we’re fixing. After this tutorial:

You: Saves the world
Guard: “By the Nine! You’re the hero who slayed the dragon! The drinks are on me tonight!”

Step 1: Get Your API Key (2 Minutes)

You need an API key to make the AI work. Here’s how:

Option A: OpenAI (ChatGPT)

  1. Go to platform.openai.com
  2. Sign up (free)
  3. Click your profile → “View API Keys”
  4. Click “Create new secret key”
  5. Copy it somewhere safe (you can’t see it again!)

Option B: Claude (Anthropic)

  1. Go to console.anthropic.com
  2. Sign up (free)
  3. Go to API Keys
  4. Create key
  5. Copy it

Option C: Gemini (Google)

  1. Go to aistudio.google.com
  2. Sign in with Google account
  3. Click “Get API Key”
  4. Create new API key
  5. Copy and save it

Which AI Should You Choose?

ProviderBest ModelsBest ForStrengthsConsiderations
OpenAIGPT-3.5 Turbo, GPT-5 miniGeneral NPCs, quick responsesFast, affordable, well-documentedCan be generic
ClaudeClaude 4 SonnetStory NPCs, character depthExcellent at staying in character, nuancedMore expensive than GPT
GeminiGemini 2.0 FlashBudget projects, high volumeExtremely cheap ($5/month!), fastLess sophisticated responses

Quick Recommendations:

  • Budget Conscious: Gemini 2.0 Flash (10x cheaper than GPT-3.5!)
  • Best Quality: Claude 4 Opus (premium but worth it for main NPCs)
  • Best Balance: GPT-3.5 Turbo or Claude 4 Sonnet
  • Free Testing: Gemini (generous free tier)

Step 2: Unity Setup (3 Minutes)

  1. Open Unity and create a new 3D project
  2. Create folders in your Project window:
    Assets/
    └── Scripts/
        └── AI/
    
  3. IMPORTANT: Keep your API key safe!
    • In your project root (next to Assets), create api-config.json:
    {
      "activeProvider": "openai",
      "globalSettings": {
        "maxTokens": 150,
        "temperature": 0.7
      },
      "providers": {
        "openai": {
          "apiKey": "paste-your-openai-key-here",
          "apiUrl": "https://api.openai.com/v1/chat/completions",
          "model": "gpt-3.5-turbo"
        }
      }
    }
    
    • Add to .gitignore:
    api-config.json
    

How It All Works Together

Unity to LLM API flow diagram showing Unity game connecting through API gateway to AI services

Here’s the flow:

  1. Player talks to NPC → Unity sends the message
  2. LlmManager formats request → Adds context and personality
  3. API processes → AI generates unique response
  4. Response returns → NPC speaks with personality

Step 3: The Code (Just Copy This)

LlmManager.cs - The Brain

Create Assets/Scripts/AI/LlmManager.cs:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

// This makes your NPCs smart!
public class LlmManager : MonoBehaviour
{
    // Make it easy to access from anywhere
    public static LlmManager Instance { get; private set; }
    
    [Header("Debug")]
    [SerializeField] private bool showDebugLogs = true;
    
    // Configuration from JSON file - no hardcoded API keys!
    private string apiKey = "";
    private AIProvider provider = AIProvider.OpenAI;
    private string model = "gpt-3.5-turbo";
    
    // API endpoints now loaded from config
    
    // Configuration loaded from file
    private APIConfiguration config;
    private ProviderConfig currentProviderConfig;
    
    public enum AIProvider
    {
        OpenAI,
        Claude,
        Gemini
    }
    
    void Awake()
    {
        // Singleton pattern (only one Llm manager in the game)
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            LoadConfiguration();
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    // Load configuration from external file
    void LoadConfiguration()
    {
        try
        {
            string path = System.IO.Path.Combine(Application.dataPath, "../api-config.json");
            if (System.IO.File.Exists(path))
            {
                string json = System.IO.File.ReadAllText(path);
                config = JsonUtility.FromJson<APIConfiguration>(json);
                
                // Load active provider settings
                LoadProviderConfig();
                
                if (showDebugLogs) 
                    Debug.Log("Configuration loaded from api-config.json");
            }
            else
            {
                Debug.LogWarning("WARNING: No api-config.json found!");
                config = new APIConfiguration();
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"ERROR: Error loading configuration: {e.Message}");
            config = new APIConfiguration();
        }
    }
    
    void LoadProviderConfig()
    {
        // Get active provider from config
        string activeProvider = config.activeProvider;
        provider = activeProvider.ToLower() switch
        {
            "openai" => AIProvider.OpenAI,
            "claude" => AIProvider.Claude,
            "gemini" => AIProvider.Gemini,
            _ => AIProvider.OpenAI
        };
        
        // Get provider-specific configuration
        currentProviderConfig = provider switch
        {
            AIProvider.OpenAI => config.providers.openai,
            AIProvider.Claude => config.providers.claude,
            AIProvider.Gemini => config.providers.gemini,
            _ => new ProviderConfig()
        };
        
        // Load API key, model, etc. from provider config
        if (currentProviderConfig != null)
        {
            apiKey = currentProviderConfig.apiKey;
            model = currentProviderConfig.model;
        }
    }
    
    // Main function - send a message and get a response
    public async Task<string> GetAIResponse(string message)
    {
        if (string.IsNullOrEmpty(apiKey))
        {
            Debug.LogError("ERROR: No API key! Check the LlmManager.");
            return "Error: No API key set";
        }
        
        try
        {
            // Get URL from config
            string url = currentProviderConfig?.apiUrl ?? "";
            if (string.IsNullOrEmpty(url))
            {
                Debug.LogError("ERROR: No API URL configured!");
                return "Error: No API URL configured";
            }
            
            // Handle Gemini URL template
            if (provider == AIProvider.Gemini && url.Contains("{model}"))
            {
                url = url.Replace("{model}", model);
            }
            string jsonBody = CreateRequestBody(message);
            
            using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
            {
                byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody);
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                request.downloadHandler = new DownloadHandlerBuffer();
                
                // Headers
                request.SetRequestHeader("Content-Type", "application/json");
                
                switch (provider)
                {
                    case AIProvider.OpenAI:
                        request.SetRequestHeader("Authorization", $"Bearer {apiKey}");
                        break;
                    case AIProvider.Claude:
                        request.SetRequestHeader("x-api-key", apiKey);
                        if (!string.IsNullOrEmpty(currentProviderConfig?.apiVersion))
                        {
                            request.SetRequestHeader("anthropic-version", currentProviderConfig.apiVersion);
                        }
                        break;
                    case AIProvider.Gemini:
                        // Gemini uses API key in URL params
                        url += $"?key={apiKey}";
                        break;
                }
                
                // Send and wait
                var operation = request.SendWebRequest();
                
                while (!operation.isDone)
                    await Task.Yield();
                
                if (request.result == UnityWebRequest.Result.Success)
                {
                    string response = ParseResponse(request.downloadHandler.text);
                    
                    if (showDebugLogs)
                        Debug.Log($"AI Response: {response}");
                    
                    return response;
                }
                else
                {
                    Debug.LogError($"ERROR: API Error: {request.error}");
                    return "Sorry, I couldn't think of a response.";
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"ERROR: {e.Message}");
            return "Sorry, something went wrong.";
        }
    }
    
    // Create the JSON request
    string CreateRequestBody(string message)
    {
        switch (provider)
        {
            case AIProvider.OpenAI:
                return JsonUtility.ToJson(new OpenAIRequest
                {
                    model = model,
                    messages = new List<OpenAIMessage>
                    {
                        new OpenAIMessage { role = "user", content = message }
                    },
                    temperature = config.globalSettings.temperature,
                    max_tokens = config.globalSettings.maxTokens
                });
                
            case AIProvider.Claude:
                return JsonUtility.ToJson(new ClaudeRequest
                {
                    model = model,
                    messages = new List<ClaudeMessage>
                    {
                        new ClaudeMessage { role = "user", content = message }
                    },
                    maxTokens = config.globalSettings.maxTokens
                });
                
            case AIProvider.Gemini:
                return JsonUtility.ToJson(new GeminiRequest
                {
                    contents = new List<GeminiContent>
                    {
                        new GeminiContent
                        {
                            parts = new List<GeminiPart>
                            {
                                new GeminiPart { text = message }
                            }
                        }
                    },
                    generationConfig = new GeminiGenerationConfig
                    {
                        maxOutputTokens = config.globalSettings.maxTokens,
                        temperature = config.globalSettings.temperature
                    }
                });
                
            default:
                return "";
        }
    }
    
    // Extract the response text
    string ParseResponse(string json)
    {
        try
        {
            switch (provider)
            {
                case AIProvider.OpenAI:
                    var openAIResponse = JsonUtility.FromJson<OpenAIResponse>(json);
                    if (openAIResponse?.choices != null && openAIResponse.choices.Count > 0)
                        return openAIResponse.choices[0].message.content;
                    break;
                    
                case AIProvider.Claude:
                    var claudeResponse = JsonUtility.FromJson<ClaudeResponse>(json);
                    if (claudeResponse?.content != null && claudeResponse.content.Count > 0)
                        return claudeResponse.content[0].text;
                    break;
                    
                case AIProvider.Gemini:
                    var geminiResponse = JsonUtility.FromJson<GeminiResponse>(json);
                    if (geminiResponse?.candidates != null && geminiResponse.candidates.Count > 0)
                        return geminiResponse.candidates[0].content.parts[0].text;
                    break;
            }
            
            Debug.LogError("Failed to parse response - unexpected format");
            return "Error parsing response.";
        }
        catch (Exception e)
        {
            Debug.LogError($"Failed to parse response: {e.Message}");
            return "Sorry, I couldn't understand the response.";
        }
    }
    
    #region Data Classes
    
    [Serializable]
    public class APIConfiguration
    {
        public string activeProvider = "openai";
        public GlobalSettings globalSettings = new GlobalSettings();
        public ProvidersConfig providers = new ProvidersConfig();
    }
    
    [Serializable]
    public class GlobalSettings
    {
        public int maxTokens = 150;
        public float temperature = 0.7f;
    }
    
    [Serializable]
    public class ProvidersConfig
    {
        public ProviderConfig openai = new ProviderConfig();
        public ProviderConfig claude = new ProviderConfig();
        public ProviderConfig gemini = new ProviderConfig();
    }
    
    [Serializable]
    public class ProviderConfig
    {
        public string apiKey = "";
        public string apiUrl = "";
        public string model = "";
        public string apiVersion = ""; // Required for Claude API
    }
    
    [Serializable]
    class OpenAIRequest
    {
        public string model;
        public List<OpenAIMessage> messages;
        public float temperature;
        public int max_tokens;
    }
    
    [Serializable]
    class OpenAIMessage
    {
        public string role;
        public string content;
    }
    
    [Serializable]
    class ClaudeRequest
    {
        public string model;
        public List<ClaudeMessage> messages;
        public int maxTokens;
    }
    
    [Serializable]
    class ClaudeMessage
    {
        public string role;
        public string content;
    }
    
    [Serializable]
    class GeminiRequest
    {
        public List<GeminiContent> contents;
        public GeminiGenerationConfig generationConfig;
    }
    
    [Serializable]
    class GeminiGenerationConfig
    {
        public int maxOutputTokens;
        public float temperature;
    }
    
    [Serializable]
    class GeminiContent
    {
        public List<GeminiPart> parts;
    }
    
    [Serializable]
    class GeminiPart
    {
        public string text;
    }
    
    // Response models for parsing API responses
    [Serializable]
    class OpenAIResponse
    {
        public List<OpenAIChoice> choices;
    }
    
    [Serializable]
    class OpenAIChoice
    {
        public OpenAIMessage message;
    }
    
    [Serializable]
    class ClaudeResponse
    {
        public List<ClaudeContent> content;
    }
    
    [Serializable]
    class ClaudeContent
    {
        public string text;
    }
    
    [Serializable]
    class GeminiResponse
    {
        public List<GeminiCandidate> candidates;
    }
    
    [Serializable]
    class GeminiCandidate
    {
        public GeminiContent content;
    }
    
    #endregion
}

SmartNpc.cs - Your First Smart Character (with Built-in UI)

Create Assets/Scripts/AI/SmartNpc.cs:

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Threading.Tasks;
using TMPro;

/// <summary>
/// Smart NPC that uses LLM for dynamic dialogue
/// </summary>
public class SmartNpc : MonoBehaviour
{
    [Header("NPC Configuration")]
    [Tooltip("What's this character's name?")]
    [SerializeField] private string npcName = "Bob the Merchant";
    
    [TextArea(3, 5)]
    [Tooltip("Describe their personality in a few sentences")]
    [SerializeField] private string personality = 
        "A friendly merchant who loves to tell stories about his adventuring days. " +
        "Always tries to get the best deal but has a soft spot for new adventurers.";
    
    [Header("Memory Settings")]
    [Tooltip("How many conversations to remember")]
    [SerializeField] private int memorySize = 5;
    
    [Header("UI References")]
    [Tooltip("Drag your dialogue text here")]
    [SerializeField] private TextMeshProUGUI dialogueText;
    
    [Tooltip("Drag your dialogue panel/bubble here")]
    [SerializeField] private GameObject dialoguePanel;
    
    [Tooltip("Optional speaker name text")]
    [SerializeField] private TextMeshProUGUI speakerNameText;
    
    [Header("Interaction Settings")]
    [Tooltip("How long to show dialogue (seconds)")]
    [SerializeField] private float dialogueDisplayTime = 5f;
    
    [Tooltip("Show thinking animation?")]
    [SerializeField] private bool showThinkingText = true;
    
    [Tooltip("Can the player interact while NPC is talking?")]
    [SerializeField] private bool allowInterruptDialogue = false;
    
    // Private fields
    private List<string> conversationMemory = new List<string>();
    private bool currentlyTalking = false;
    private LlmManager llmManager;
    
    void Start()
    {
        // Cache reference to Llm Manager
        llmManager = LlmManager.Instance;
        
        if (llmManager == null)
        {
            Debug.LogError($"[{npcName}] No LlmManager found! Make sure to add one to your scene.");
        }
        
        // Set speaker name if we have the UI element
        if (speakerNameText != null)
        {
            speakerNameText.text = npcName;
        }
        
        // Hide dialogue at start
        if (dialoguePanel != null)
        {
            dialoguePanel.SetActive(false);
        }
    }
    
    /// <summary>
    /// Call this when the player interacts with the NPC
    /// </summary>
    public void OnPlayerInteract()
    {
        // Don't interrupt if already talking (unless allowed)
        if (currentlyTalking && !allowInterruptDialogue) 
        {
            if (showDebugLogs)
                Debug.Log($"[{npcName}] Already talking, interaction ignored.");
            return;
        }
        
        // Start the conversation
        _ = HaveConversation();
    }
    
    /// <summary>
    /// Main conversation logic
    /// </summary>
    private async Task HaveConversation()
    {
        currentlyTalking = true;
        
        // Show the dialogue UI
        ShowDialogueUI();
        
        // Show thinking message
        if (showThinkingText && dialogueText != null)
        {
            dialogueText.text = $"{npcName} is thinking...";
        }
        
        // Build the prompt for the AI
        string aiPrompt = CreatePrompt();
        
        // Get response from AI
        string response = await GetAIResponse(aiPrompt);
        
        // Remember what was said
        RememberConversation(response);
        
        // Display the response
        if (dialogueText != null)
        {
            dialogueText.text = response;
        }
        
        // Wait before hiding
        await Task.Delay((int)(dialogueDisplayTime * 1000));
        
        // Hide dialogue
        HideDialogueUI();
        
        currentlyTalking = false;
    }
    
    /// <summary>
    /// Get response from LLM with error handling
    /// </summary>
    private async Task<string> GetAIResponse(string prompt)
    {
        if (llmManager == null)
        {
            return GetFallbackResponse();
        }
        
        try
        {
            string response = await llmManager.GetAIResponse(prompt);
            return response;
        }
        catch (Exception e)
        {
            Debug.LogError($"[{npcName}] AI Error: {e.Message}");
            return GetFallbackResponse();
        }
    }
    
    /// <summary>
    /// Create the prompt that tells the AI how to behave
    /// </summary>
    private string CreatePrompt()
    {
        // Get player information (in a real game, get this from your player manager)
        string playerName = GetPlayerName();
        int playerLevel = GetPlayerLevel();
        
        // Build memory context
        string memoryContext = "";
        if (conversationMemory.Count > 0)
        {
            memoryContext = "Previous conversations:\n" + 
                          string.Join("\n", conversationMemory) + "\n\n";
        }
        
        // Create the full prompt
        return $@"You are {npcName}. {personality}

{memoryContext}The player ({playerName}, Level {playerLevel}) approaches you.

Respond as {npcName} would, staying in character. Keep your response under 50 words. Be conversational and natural.";
    }
    
    /// <summary>
    /// Remember conversations for context
    /// </summary>
    private void RememberConversation(string whatWasSaid)
    {
        conversationMemory.Add($"{npcName}: {whatWasSaid}");
        
        // Keep memory limited
        while (conversationMemory.Count > memorySize)
        {
            conversationMemory.RemoveAt(0);
        }
    }
    
    /// <summary>
    /// Fallback responses when AI is unavailable
    /// </summary>
    private string GetFallbackResponse()
    {
        string[] fallbacks = 
        {
            $"Greetings, traveler! I'm {npcName}.",
            "Welcome to my humble shop!",
            "What can I do for you today?",
            "Ah, another adventurer! How can I help?",
            "Good to see you again!"
        };
        
        // Return appropriate fallback based on interaction count
        int index = Mathf.Min(conversationMemory.Count, fallbacks.Length - 1);
        return fallbacks[index];
    }
    
    /// <summary>
    /// Get player name (override this in your game)
    /// </summary>
    protected virtual string GetPlayerName()
    {
        // In a real game, get this from your player manager
        // Example: return PlayerManager.Instance.PlayerName;
        return "Adventurer";
    }
    
    /// <summary>
    /// Get player level (override this in your game)
    /// </summary>
    protected virtual int GetPlayerLevel()
    {
        // In a real game, get this from your player manager
        // Example: return PlayerManager.Instance.Level;
        return 5;
    }
    
    private void ShowDialogueUI()
    {
        if (dialoguePanel != null) 
            dialoguePanel.SetActive(true);
    }
    
    private void HideDialogueUI()
    {
        if (dialoguePanel != null)
            dialoguePanel.SetActive(false);
    }
    
    // Test in Unity Editor
    [ContextMenu("Test Conversation")]
    void TestInEditor()
    {
        OnPlayerInteract();
    }
    
    // Optional: Connect to Unity's collision system
    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            OnPlayerInteract();
        }
    }
    
    // Optional: Connect to mouse clicks
    void OnMouseDown()
    {
        OnPlayerInteract();
    }
    
    // Debug settings
    [Header("Debug")]
    [SerializeField] private bool showDebugLogs = false;
}

Step 4: Setting Everything Up in Unity

Create the Llm Manager:

  1. Create empty GameObject (right-click in Hierarchy → Create Empty)
  2. Name it “Llm Manager”
  3. Add the LlmManager script (click Add Component → Scripts → LlmManager)
  4. That’s it! The script only shows a “Show Debug Logs” checkbox (everything else loads from api-config.json)

Create Your First Smart NPC:

  1. Create a Capsule (right-click → 3D Object → Capsule)
  2. Name it “Smart Merchant”
  3. Add the SmartNpc script
  4. Configure in Inspector:
    • NPC Name: “Bob the Merchant” (or your choice)
    • Personality: Write a few sentences about their character
    • Memory Size: 5 (how many conversations to remember)
    • Leave UI References empty for now (we’ll set them up next)

Complete UI Setup:

  1. Create Canvas (right-click in Hierarchy → UI → Canvas)

  2. Add Panel as child of Canvas:

    • Right-click Canvas → UI → Panel
    • Name it “Dialogue Panel”
    • In the Rect Transform, click the Anchor Presets (square icon)
    • Hold Shift+Alt and click bottom-center preset
    • Set Pos Y: 100, Width: 600, Height: 200
    • In Image component, set Color alpha to 200 (semi-transparent)
  3. Add TWO Text components as children of Dialogue Panel:

    • Right-click Dialogue Panel → UI → Text - TextMeshPro
    • Name first one “Speaker Name” (optional - shows NPC name)
    • Name second one “Dialogue Text” (required - shows NPC response)
    • Position Speaker Name at top (Pos Y: 70)
    • Position Dialogue Text in center (Pos Y: 0)
  4. Connect UI to your NPC:

    • Select your Smart Merchant NPC in Hierarchy
    • In SmartNpc component, find “UI References” section
    • Drag Dialogue Panel → “Dialogue Panel” slot
    • Drag Dialogue Text → “Dialogue Text” slot
    • Drag Speaker Name → “Speaker Name Text” slot (optional)

Step 5: Test in Play Mode!

Testing Methods:

Method 1: Click to Talk (easiest)

  1. Press Play in Unity
  2. Click on your NPC in the Game view
  3. Watch the dialogue appear!

Unity game view showing 3D capsule NPC with semi-transparent dialogue panel displaying text above

Method 2: Walk into NPC (for 3D games)

  1. Add a Collider to your NPC (check “Is Trigger”)
  2. Tag your player as “Player”
  3. Walk into the NPC to trigger conversation

Method 3: Test from Inspector (development)

  1. Select your NPC in Hierarchy
  2. Right-click SmartNpc component header
  3. Click “Test Conversation”

What You Should See:

  • Dialogue panel appears
  • “Bob the Merchant is thinking…” (if enabled)
  • AI-generated response appears
  • Panel auto-hides after 5 seconds

Troubleshooting:

  • Nothing happens? Check Console for errors
  • Can’t click NPC? Make sure it has a Collider
  • UI not showing? Verify all UI slots are connected

Common Problems & Quick Fixes

”No API key!” error

  • Did you create api-config.json?
  • Is it in the right place? (next to Assets folder)
  • Did you paste your actual key?

”API Error: 401”

  • Your API key is wrong
  • Check you copied it correctly
  • Make sure you have credits (free tier is limited)

Nothing happens when clicking NPC

  • Is the Llm Manager in your scene?
  • Is your internet working?
  • Check the Console window for errors

Response is cut off

  • Increase maxTokens in your config (default: 150)
  • Try 300 for longer responses

Switching AI Providers

Want to use Claude or Gemini instead of ChatGPT? Easy:

  1. Update your api-config.json:

    {
      "activeProvider": "claude",  // or "gemini"
      "globalSettings": {
        "maxTokens": 150,
        "temperature": 0.7
      },
      "providers": {
        "claude": {
          "apiKey": "your-claude-key-here",
          "apiUrl": "https://api.anthropic.com/v1/messages",
          "apiVersion": "2023-06-01",
          "model": "claude-3-haiku-20240307"
        }
      }
    }
    
  2. Restart Unity - the configuration loads on start

That’s it! The system automatically handles the different API formats.

Important Notes About Model Names

Model names must be exact! Claude models include dates:

  • claude-3-haiku-20240307 (not just claude-3-haiku)
  • claude-3-sonnet-20240229
  • claude-3-opus-20240229

Why does Claude need apiVersion? Claude’s API requires the anthropic-version header. Without it, you’ll get an error! This is how Anthropic ensures backward compatibility - they can update their API while keeping old code working. Always include it for Claude.

Gemini models use simpler names like gemini-1.5-flash or gemini-pro.

Making It Look Professional

Add Typing Effect

// Add to SmartNpc.cs
async Task TypeText(string text)
{
    dialogueText.text = "";
    foreach (char letter in text)
    {
        dialogueText.text += letter;
        await Task.Delay(50); // Adjust speed
    }
}

Add Voice Sounds

[Header("Audio")]
public AudioClip[] mumbleClips;
AudioSource audioSource;

void PlayMumble()
{
    if (mumbleClips.Length > 0)
    {
        var clip = mumbleClips[Random.Range(0, mumbleClips.Length)];
        audioSource.PlayOneShot(clip);
    }
}

API Costs - Will This Break the Bank?

Current Pricing (2025)

ModelInput CostOutput CostMonthly Cost*
GPT-3.5 Turbo$0.0005/1K$0.0015/1K~$30
GPT-4$0.01/1K$0.03/1K~$600
GPT-4 Turbo$0.01/1K$0.03/1K~$600
GPT-5$0.00125/1K$0.01/1K~$125
GPT-5 mini$0.00025/1K$0.002/1K~$25
Claude 3 Haiku$0.00025/1K$0.00125/1K~$25
Claude 3 Sonnet$0.003/1K$0.015/1K~$150
Claude 4 Sonnet$0.003/1K$0.015/1K~$150
Claude 4 Opus$0.015/1K$0.075/1K~$750
Gemini 2.0 Flash$0.0001/1K$0.0004/1K~$5
Gemini 2.5 Pro$0.00125/1K$0.01/1K~$125
Gemini 2.5 Flash$0.00006/1K$0.0006/1K~$7

*Assuming 1000 daily players, 10 conversations each

Notes:

  • GPT-5 released August 2025 with improved reasoning
  • Claude 4 launched May 2025, Opus 4.1 in August 2025
  • Gemini 2.0 Flash is incredibly cost-effective at $5/month
  • Gemini offers generous free tier (15 RPM)

Check current pricing:

Money-Saving Tips

  1. Use Gemini 2.0 Flash for most NPCs (incredibly cheap at $5/month!)
  2. Cache common responses (greetings, shop talk)
  3. Shorter prompts = less tokens (be concise)
  4. Set daily limits in your API dashboard

Simple Caching System

Dictionary<string, string> responseCache = new Dictionary<string, string>();

public async Task<string> GetCachedResponse(string prompt)
{
    // Check cache first
    if (responseCache.ContainsKey(prompt))
        return responseCache[prompt];
    
    // Not in cache, get from API
    string response = await GetAIResponse(prompt);
    
    // Save for next time
    responseCache[prompt] = response;
    
    return response;
}

Cool Things to Try Next

1. Shop System with Dynamic Prices

string prompt = $@"You are a merchant. The player has {playerGold} gold.
They want to buy a sword (worth 100g).
Their reputation is {reputation}.
Quote them a price and explain why.";

2. Quest Giver That Remembers

string prompt = $@"You are the village elder.
Last time, you asked the player to {lastQuest}.
They {questResult}.
Now give them a follow-up quest based on what happened.";

3. Combat Companion

string prompt = $@"You are the player's companion in combat.
Enemy: {enemyType} at {enemyHealth}% health
Player: {playerHealth}% health
Give a quick battle tip or warning!";

Get the Complete Source Code

All the code from this tutorial is available on GitHub, plus extra features:

Clone the repository:

git clone https://github.com/angrysharkstudio/unity-ai-chat-tutorial.git

What’s Next?

You now have NPCs that can think! Here’s what to explore:

  1. Add more personality traits - Make each NPC unique
  2. Connect to game events - NPCs react to what happens
  3. Build reputation system - NPCs remember if you’re good/bad
  4. Create dynamic quests - Quests based on your actions

Want to see more advanced AI features? Check out:

For official AI documentation, visit OpenAI’s Unity guide, Anthropic’s API reference, or Google’s Gemini docs.

Frequently Asked Questions

Can I use this in commercial games?

Yes! Just check the terms of service for your chosen AI provider. Most allow commercial use but have usage limits.

How much will this cost per player?

With Gemini 2.0 Flash: about $0.005 per player per month (assuming 10 conversations daily). GPT-3.5: about $0.03 per player.

Will NPCs remember conversations between game sessions?

Not with this basic implementation. You’d need to save the conversation history to PlayerPrefs or a database.

Can I use this for multiplayer games?

Yes, but run the API calls on the server, not client-side. Never expose API keys to players!

What if the AI says something inappropriate?

Add content filtering in your prompts. Example: “Keep responses family-friendly. Never mention violence or inappropriate topics.”

How do I make responses faster?

  • Use streaming responses (advanced topic)
  • Cache common interactions
  • Use Gemini 2.0 Flash (fastest model)

Can NPCs have different languages?

Yes! Add to your prompt: “Respond in [language]” and the AI will adapt.

Need Help?

Working on something cool? Got stuck? Contact us - we love seeing what people build!

Remember: Players don’t care about your code. They care that the grumpy blacksmith finally remembered they never paid for that sword. Now you can make that happen.


Need Expert Unity AI Integration?

Want to take your game’s AI to the next level? Our Unity Certified team specializes in:

  • Advanced NPC AI Systems - Multi-agent conversations, memory systems, emotional AI
  • Custom AI Integration - Tailored solutions for your specific game needs
  • Performance Optimization - Minimize API costs while maximizing player experience
  • Full Game AI Architecture - Design and implement complete AI-driven gameplay

Get a free AI consultation →


Level Up Your Unity Development

Join thousands of Unity developers creating smarter games. Get weekly tutorials on:

  • AI integration techniques
  • Performance optimization
  • Advanced Unity patterns
  • Industry best practices

Subscribe to our newsletter on the blog →


Happy coding!

Angry Shark Studio Logo

About Angry Shark Studio

Angry Shark Studio is a professional Unity AR/VR development studio specializing in mobile multiplatform applications and AI solutions. Our team includes Unity Certified Expert Programmers with extensive experience in AR/VR development.

Related Articles

More Articles

Explore more insights on Unity AR/VR development, mobile apps, and emerging technologies.

View All Articles

Need Help?

Have questions about this article or need assistance with your project?

Get in Touch