Вы когда‑нибудь задумывались о том, как здорово было бы, чтобы в вашей игре происходили настоящие человеческие диалоги? Представьте себе систему диалогов NPC, которая кажется невероятно реалистичной, или нарратора, который моментально реагирует на действия игроков. А может быть, вам нужен инструмент, способный генерировать уникальный контент «на лету» прямо в игре? Вне зависимости от того, создаете ли вы одиночную или многопользовательскую игру, внедрение генеративного искусственного интеллекта может сделать ваше творение намного интереснее и динамичнее. В этом руководстве я поделюсь с вами пошаговой инструкцией того, как это можно воплотить в вашем проекте!

Я занимаюсь креативными медиа и игровыми технологиями в Бредском университете прикладных наук. В рамках своей деятельности я разработал плагин для генеративного искусственного интеллекта, который позволяет генерировать диалоги с NPC. Я с радостью поделюсь своими знаниями и опытом в этой области. Это руководство рассчитано на начинающих, и я постараюсь объяснить все максимально подробно, чтобы помочь вам избежать проблем, с которыми столкнулся я во время своего исследования. В этом руководстве я расскажу о том, как создать плагин для Unreal Engine на C++ и интегрировать стороннюю библиотеку, такую как генеративная модель искусственного интеллекта.
Прежде всего, давайте немного поговорим о генеративном искусственном интеллекте. Затем я продемонстрирую, как настроить плагин для Unreal Engine, чтобы подключить стороннюю библиотеку для автономной ИИ‑генерации. В качестве дополнительного бонуса я также поделюсь своим опытом в реализации онлайн‑генерации с использованием искусственного интеллекта. Этот плагин может работать как в режиме онлайн, так и оффлайн, что делает его достаточно гибким и полезным для самых разнообразных игр и проектов!
Зачем вам может понадобится автономная ИИ-генерация?
По мере того как системы генеративного искусственного интеллекта, такие как OpenAI и другие платформы, становятся все более популярными, все больше и больше программ стремятся внедрить эти системы, чтобы оставаться в тренде. Однако не все осознают проблемы, связанные с использованием онлайн ИИ‑генерации. Например, это стоит денег. Отправка запросов на OpenAI требует подписки или наличия определенного баланса на вашем аккаунте. Количество токенов, которые вы используете для отправки запросов, и ответ, который вы получаете, определяют размер оплаты. Кроме того, для использования онлайн ИИ‑генерации требуется подключение к интернету и учетная запись. Не говоря уже о том, что этот метод может быть уязвим для атак и подвергать опасности конфиденциальные данные.
Автономные генеративные модели искусственного интеллекта, в отличие от облачных, можно использовать совершенно бесплатно. Для их работы не требуется ни учетная запись, ни подключение к интернету — только мощный процессор и, возможно, видеокарта. Однако у автономного ИИ есть свои недостатки. Например, для генерации даже простых ответов требуется значительное количество вычислительных ресурсов, а также большой языковой модели (LLM) для обработки сложных текстов. Тем не менее, никогда не вредно поэкспериментировать и посмотреть, какие результаты можно получить!
Давайте приступим к созданию автономного генеративного ИИ для Unreal Engine!
Поиск библиотеки
Прежде всего, нам нужно определиться с языком программирования, на котором мы будем работать. Мы выберем C++, так как он предоставляет широкий спектр возможностей для создания сложных систем. Нам также потребуется библиотека, предоставляющая API для работы с генеративным ИИ. Для этой задачи мы собираемся использовать llama.cpp.
Конечно, есть и другие библиотеки, но llama.cpp выделяется тем, что она написана на C++ и не требует никаких дополнительных зависимостей. Это большое преимущество при подключении к Unreal Engine. Кроме того, у этой библиотеки есть активное сообщество, которое постоянно работает над обновлениями и исправлениями. Мы же не хотим сталкиваться с проблемами при использовании других библиотек, не так ли? Единственный недостаток — репозиторий на GitHub не очень удобен для начинающих пользователей. Также библиотека требует от пользователя определенного уровня знаний C ++ и генеративного искусственного интеллекта. Но не волнуйтесь, именно поэтому я и написал это руководство!
Собираем библиотеку
Давайте начнем со сборки библиотеки llama.cpp. Изначально в нашем распоряжении нет никаких готовых библиотек или файлов, которые мы могли бы просто загрузить и подключить. Мы должны собрать ее с помощью CMake.
Если в двух словах, CMake — это инструмент, который помогает создавать необходимые файлы и библиотеки специально для вашей текущей платформы. Это позволяет вам избежать создания отдельных сборок для каждой отдельной машины и вместо этого иметь одну сборку, которую вы можете скомпилировать для различных платформ с помощью CMake.
Теперь, когда вы знаете, что такое CMake, давайте загрузим его с официального сайта и используем для сборки llama.cpp. Не забудьте также установить приложение github! Перейдите в папку, в которой вы хотите собрать библиотеку, и введите следующие команды в консоль:
git clone
https://github.com/ggerganov/llama.cpp
cd llama.cpp
После этого вы можете собрать библиотеку с помощью CMake, выполнив следующие команды:
cmake ‑B build ‑DBUILD_SHARED_LIBS=OFF
cmake ‑build build ‑config Release
Добавляя строку ‑DBUILD_SHARED_LIBS=OFF, мы сообщаем CMake, что хотим выполнить сборку llama.cpp без.dll‑файлов (нам нужны только.lib‑файлы). Если у вас возникли проблемы или вы хотите собрать его другим способом, рекомендую вам начать с github‑репозитория llama.cpp.
Теперь, когда мы сгенерировали все эти файлы для llama.cpp, возникает вопрос: какие именно файлы нам нужны? CMake создал множество папок, но не стоит беспокоиться! В основном нам будут нужны только эти файлы: llama.lib (llama.cpp /build/src/release), ggml.lib и ggml‑base.lib (llama.cpp /build/ggml/src/release), и ggml‑cpu.lib (llama.cpp/build/ggml/src/ggml‑cpu/release). Нам также понадобится заголовочный файл llama.h (lama.cpp/include) и все заголовки внутри папки ggml.h, ggml‑alloc.h и т. д. (llama.cpp/ggml/include).
Создание пустого плагина в Unreal Engine
Прежде чем приступить к созданию пустого плагина в Unreal Engine, вам необходимо установить Visual Studio вместе со всеми необходимыми инструментами. Если вы не знаете, как настроить Visual Studio для разработки в Unreal Engine, настоятельно рекомендую вам ознакомиться с официальным руководством: настройка Visual Studio.
После установки перейдите в свой проект в Unreal Engine (или создайте новый пустой проект) и откройте меню «Edit». В раскрывшемся списке выберите «Plugins» и найдите кнопку «+ Add». Нажмите на нее, чтобы создать свой первый пустой плагин. Мы дадим ему название «AwesomeAIPlugin», и после его создания он должен автоматически открыть Visual Studio.



Если вы откроете YourPluginName.uplugin, то увидите внутри две основные настройки (строки после «Modules»). Мы не будем их изменять, но полезно понимать, что они делают:
1. Type (тип): определяет, как модуль используется в движке. Среди распространенных типов можно выделить:
Runtime: Используется во время реального игрового процесса и является наиболее часто используемым типом функционала, с которой взаимодействуют игроки.
Editor: Предназначен для инструментов или функций, которые работают только в редакторе (не в игре, поэтому, возможно, добавляет новый пользовательский интерфейс для Unreal Editor).
RuntimeAndEditor: Используется как в игре, так и в редакторе.
Developer: Используется для отладки или инструментов разработки и не входит в финальную версию игры.
2. LoadingPhase: управляет процессом загрузки модуля. Среди распространенных фаз можно выделить:
Default: загружается, когда это необходимо движку, обычно в начале запуска.
PreDefault: загружается раньше, чем большинство других модулей.
PostDefault: загружается позже фазы по умолчанию.
PreLoadingScreen: загружается до появления экрана загрузки.
PostEngineInit: загружается после завершения инициализации движка.
None: модуль не загружается автоматически (вам необходимо загрузить его вручную).
Теперь, когда мы разобрались с этим, мы можем перейти к следующему шагу:
Импорт llama.cpp в Unreal Engine
Прежде всего, нам необходимо интегрировать нужные библиотеки в Unreal Engine, чтобы он мог их использовать. Для этого мы должны добавить все необходимые файлы в наш проект вместе с «AwesomeAIPlugin». Итак, давайте перейдем в следующую папку: ExampleProject/Plugins/YourPluginName/Source/YourPluginName. Здесь мы создадим новую папку под названием «ThirdParty». Откройте эту папку и поместите в нее файлы, которые мы получили от CMake. Чтобы немного улучшить организацию, я упорядочу их следующим образом: все.lib‑файлы будут находиться внутри папки «ThirdParty», а для всех заголовков я создам новую папку «headers». Таким образом, все необходимые.lib‑файлы будут в «ThirdParty» и все заголовки в ThirdParty/headers.
Отлично, теперь у нас есть все необходимые файлы для нашей библиотеки, но, к сожалению, Unreal Engine еще не может их распознать. Обычные модули для Unreal Engine на C++ объявляются с помощью файла YourPluginName.Build.cs, и сторонние библиотеки не являются исключением. Чтобы исправить эту проблему, нам нужно перейти к файлу YourPluginName.Build.cs (для удобства откройте Visual Studio, создайте.sln‑файлы в папке ExampleProject и затем перейдите в ExampleProject→Plugins→Source→YourPluginName→YourPluginName.Build.Cs). В этом файле нам необходимо изменить несколько строк кода, чтобы он выглядел следующим образом:
// Copyright Epic Games, Inc. All Rights Reserved.
using System.IO;
using UnrealBuildTool;
public class AwesomeAIPlugin : ModuleRules
{
public AwesomeAIPlugin(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... добавляем здесь необходимые публичные пути ...
Path.Combine(ModuleDirectory, "ThirdParty", "headers") // Добавляем наш каталог с заголовками
}
);
// ...
// Добавляем библиотеки llama.cpp
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "ThirdParty", "llama.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "ThirdParty", "ggml.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "ThirdParty", "ggml-base.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "ThirdParty", "ggml-cpu.lib"));
}
}
Полный код должен выглядеть так. Мы почти у цели! Теперь нам нужно заново создать файлы решения, чтобы Visual Studio могла распознавать новые файлы. Для этого перейдите в папку ExampleProject, клиикниет правой кнопкой мыши на ExampleProject.uproject (для меня это BlogExample.uproject) и выберите «Generate Visual Studio project files»:

После этого внутри той же папки откройте файл ExampleProject.sln, а затем кликните правой кнопкой мыши по модулю ExampleProject (для меня это BlogExample) ‑> Rebuild, чтобы перестроить решение.

Вот и все! Несколько хитрых манипуляций и теперь у нас есть библиотека llama.cpp в Unreal Engine! Вы можете попробовать запустить Unreal Engine через Visual Studio и проверить, все ли работает, выбрав Local Windows Debugger. Но не забудьте включить режим Development Editor, который был выделен желтым цветом на предыдущем скриншоте.
Первые взаимодействия с автономным генеративным ИИ!
Итак, мы наконец‑то настроили наш Unreal Engine! Теперь давайте сгенерируем ответ с помощью llama.cpp. Однако прежде чем мы начнем, я хочу проговорить основные термины, связанные с LLM и их работой:
LLM (Large Language Model) — это тип искусственного интеллекта, который обучается на огромном объеме текстовой информации (например, книг, статей и веб‑сайтов). Он изучает языковые паттерны, что позволяет ему отвечать на вопросы, писать истории и даже генерировать код. Представьте себе сверхмощное автозаполнение: вы вводите текст, а LLM предсказывает, что будет дальше. LLM может быть предварительно обучена, что означает, что она уже имеет общие знания о языке и общих темах. Если же вы хотите, чтобы она специализировалась на определенной области, например, на взаимодействии с людьми, вы можете провести дополнительное обучение, известное как тонкая настройка (fine‑tuning).
Токены — это строительные блоки текста для LLM. Токеном может быть слово, часть слова или даже просто отдельная буква или символ (в зависимости от модели). LLM мыслят токенами, а не целыми предложениями. При генерации текста модель шаг за шагом предсказывает следующий токен, подобно тому, как решают головоломку, двигаясь от одного фрагмента к другому.
Температура — определяет, насколько «креативными» или «случайными» являются ответы модели. Низкая температура (обычно в районе 0.2) позволяет модели выбирать наиболее безопасные и предсказуемые ответы. Высокая температура (обычно в районе от 0.8 до 1) делает модель более креативной или разнообразной в своих реакциях.
Я надеюсь, что эти определения помогут вам лучше понять, как работают большие языковые модели (LLM). Но где же нам найти подходящую LLM? LLama.cpp использует LLM с расширением .gguf, и их существует целое множество! Однако перед использованием убедитесь, что вы загрузили точно настроенную версию, иначе генерация текста может оказаться весьма необычной. Я буду использовать модель Mistral-7B‑Instruct‑v0.3-GGUF (Mistral-7B‑Instruct‑v0.3.Q6_K.gguf), так как она обладает впечатляющей мощностью, превосходя другие модели даже с меньшим количеством параметров. Параметры можно сравнить с «мозговыми клетками» LLM: чем больше параметров у модели, тем более «умной» она становится. Загрузите любую понравившуюся модель и сохраните ее на своем компьютере.
Для простоты мы будем писать код внутри файла YourPluginName.cpp. В следующем фрагменте кода вы увидите, как мы конфигурируем эти начальные параметры и подготавливаем базовые данные для взаимодействия с ИИ:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AwesomeAIPlugin.h"
#include <iostream>
#include <sstream>
#include "llama.h"
#define LOCTEXT_NAMESPACE "FAwesomeAIPluginModule"
void FAwesomeAIPluginModule::StartupModule()
{
// Этот код будет выполнен после загрузки вашего модуля в память; точный тайминг указан в .uplugin-файле для каждого модуля
// Устанавливаем в контексте роль помощника
std::string assistantRole = "Helpful assistant";
std::string messageToAI = "Hey, who are you?";
// Путь к локальной модели искусственного интеллекта
std::string model_path = "C:/CPlusPlus/LlamaCppAi/assets/aiModels/Mistral-7B-Instruct-v0.3.Q6_K.gguf";
// ...
После настройки базовых параметров нам необходимо определить несколько ключевых переменных llama.cpp, которые будут управлять взаимодействием между ИИ и пользователем. К ним относятся история чата, сама модель, контекст (ctx, который служит «рабочим пространством» / «workspace» ИИ) и сэмплер для управления тем, как ИИ генерирует ответы. Буфер используется для форматированных промптов (запросов), гарантируя, что все должным образом подготовлено перед передачей модели (гарантирует, что модель поймет запрос):
std::vector<llama_chat_message> messages; // Содержит контекст разговора
llama_model* model = nullptr; // Инстанс модели
llama_context* ctx = nullptr; // Инстанс контекста
llama_sampler* sampler = nullptr; // Инстанс сэмлера
std::vector<char> formatted; // Буфер для форматированных промптов
Далее мы переходим к непосредственной инициализации модели искусственного интеллекта и настройке ее контекста и методов выборки:
// --- Инициализация модели ---
llama_model_params model_params = llama_model_default_params();
model = llama_load_model_from_file(model_path.c_str(), model_params);
if (!model) {
UE_LOG(LogTemp, Error, TEXT("Unable to load model."));
return;
}
llama_context_params ctx_params = llama_context_default_params();
ctx_params.n_ctx = 2048; // Устанавливаем размер контекста по умолчанию, который определяет количество токенов (слов, частей слов и знаков препинания), которые модель может обрабатывать одновременно. Если превысить это ограничение, модель начнет забывать более старые фрагменты разговора.
ctx = llama_new_context_with_model(model, ctx_params); // Создаем рабочее пространство (в памяти) для нашей модели.
if (!ctx) {
UE_LOG(LogTemp, Error, TEXT("Failed to create context."));
return;
}
// Инициализируем сэмплер — инструмент, который определяет, как будет выбираться следующий токен (слово, часть слова или символ) во время генерации текста.
sampler = llama_sampler_chain_init(llama_sampler_chain_default_params());
// Добавляем конкретные методы сэмплинга.
llama_sampler_chain_add(sampler, llama_sampler_init_min_p(0.1f, 1)); // Этот шаг гарантирует, что менее вероятные токены не будут выбираться слишком часто.
llama_sampler_chain_add(sampler, llama_sampler_init_top_p(0.90f, 1)); // Ограничивает пул токенов максимальными 90% по вероятности, что делает вывод более сфокусированным.
llama_sampler_chain_add(sampler, llama_sampler_init_temp(0.7)); // Управляет уровнем случайности; более низкие значения делают ответы ИИ более предсказуемыми, в то время как высокие значения делают их более креативными.
llama_sampler_chain_add(sampler, llama_sampler_init_dist(LLAMA_DEFAULT_SEED));
formatted.resize(llama_n_ctx(ctx));
// -----------------------------
Теперь мы добавляем роль искусственного интеллекта и пользовательский ввод в историю разговоров, используя переменную messages. Это позволяет модели понимать, кто и что говорил. Затем мы устанавливаем ограничение по количеству слов для ответа искусственного интеллекта, в данном случае 50 слов. Далее мы форматируем промпт с учетом обновленных сообщений, обеспечивая правильную структуру диалога. Если форматированный промпт становится слишком длинным, мы изменяем размер буфера, чтобы он соответствовал требуемому размеру. В случае возникновения ошибок при форматировании, мы регистрируем их. Наконец, мы преобразуем отформатированные данные в строку, которая может быть отправлена в модель.
// Добавляем роль для модели искусственного интеллекта и пользовательского ввода в сообщения
messages.push_back({ "system", _strdup(assistantRole.c_str()) });
messages.push_back({ "user", _strdup(messageToAI.c_str()) });
int word_limit = 50;
// Форматируем промпт с учетом обновленных сообщений
int new_len = llama_chat_apply_template(model, nullptr, messages.data(), messages.size(), true, formatted.data(), formatted.size());
if (new_len > static_cast<int>(formatted.size())) {
formatted.resize(new_len);
new_len = llama_chat_apply_template(model, nullptr, messages.data(), messages.size(), true, formatted.data(), formatted.size());
}
if (new_len < 0) {
UE_LOG(LogTemp, Error, TEXT("Failed to format the chat template."));
return;
}
std::string prompt(formatted.begin(), formatted.begin() + new_len);
Теперь начинается самая важная часть — генерация ответа искусственным интеллектом. Мы начинаем с процесса токенизации промпта, разбивая его на более мелкие части, которые модель может понять. Если возникают проблемы с токенизацией, мы возвращаем ошибку. Затем мы входим в цикл, в котором модель генерирует ответ, выдавая по одному токену за раз. Мы отслеживаем количество сгенерированных слов и останавливаем процесс, когда достигаем заданного предела или когда модель завершает работу (llama_token_is_eog). Каждый токен преобразуется обратно в текст и добавляется к ответу. После завершения генерации мы выводим ответ и сохраняем его в истории разговоров. Наконец, мы высвобождаем ресурсы, чтобы избежать утечек памяти.
// Генерация ответа (основной цикл)
auto generate = [&](const std::string& prompt) {
const int n_prompt_tokens = -llama_tokenize(model, prompt.c_str(), prompt.size(), NULL, 0, true, true);
std::vector<llama_token> prompt_tokens(n_prompt_tokens);
if (llama_tokenize(model, prompt.c_str(), prompt.size(), prompt_tokens.data(), prompt_tokens.size(), llama_get_kv_cache_used_cells(ctx) == 0, true) < 0) {
return std::string("Error: Failed to tokenize prompt.");
}
llama_batch batch = llama_batch_get_one(prompt_tokens.data(), prompt_tokens.size());
llama_token new_token_id;
std::ostringstream response_stream;
FString UELOG = "";
int word_count = 0; // Счетчик для ограничения количества слов в ответе
while (true) {
if (llama_decode(ctx, batch)) {
return std::string("Error: Failed to decode.");
}
new_token_id = llama_sampler_sample(sampler, ctx, -1);
if (llama_token_is_eog(model, new_token_id) || word_count >= word_limit) {
break;
}
char buf[256];
int n = llama_token_to_piece(model, new_token_id, buf, sizeof(buf), 0, true);
if (n < 0) {
return std::string("Error: Failed to convert token to piece.");
}
// Вывод слов по мере генерации
std::string word_piece(buf, n);
UELOG += word_piece.c_str();
response_stream << word_piece; // Добавление слова к ответу
word_count++; // Увеличение количества слов
//UE_LOG(LogTemp, Log, TEXT("%s"), *UELOG); // Раскомментировать для вывода токена при его генерации.
batch = llama_batch_get_one(&new_token_id, 1);
}
std::cout << std::endl;
return response_stream.str();
};
std::string response = generate(prompt);
UE_LOG(LogTemp, Warning, TEXT("Generated response: %s"), *FString(response.c_str()));
// Добавление ответа в историю разговоров
messages.push_back({ "assistant", _strdup(response.c_str()) });
// Высвобождение ресурсов
llama_sampler_free(sampler);
llama_free(ctx);
llama_free_model(model);
Полный код можно найти здесь. Если вы не хотите удалять историю разговора, вам не нужно высвобождать ресурсы в конце. Я знаю, что это может быть сложным и запутанным, но код был в основном адаптирован на основе официального примера llama.cpp. Я постарался сделать его максимально простым. Теперь вы можете общаться в чате со своей локальной моделью! (А если что‑то пойдет не так, вы будете знать, где искать проблему.)
Вы можете увидеть результат, запустив свой проект на Unreal Engine и открыв логи с выводом:

Создадим онлайн-режим для нашего генеративного искусственного интеллекта
Поскольку это бонусный раздел, я не буду углубляться в детали. Сам Unreal Engine предлагает возможность отправлять HTTP‑запросы, и мы воспользуемся ею для отправки запросов в OpenAI. Кроме того, нам потребуется JSON для создания этих запросов.
Первым шагом будет создание учетной записи на OpenAI и получение секретного ключа. Как только мы это сделаем, необходимо добавить новую публичную зависимость в наш файл YourPluginName.Build.cs:
PublicDependencyModuleNames.AddRange(
new string[]
{
“Core”,
«HTTP», «Json», «JsonUtilities» // Для выполнения HTTP‑запросов; Для обработки JSON; Для упрощения сериализации и десериализации JSON
}
);
В этом фрагменте кода мы реализуем онлайн‑подключение к искусственному интеллекту по API от OpenAI GPT models. Первым шагом является определение роли помощника и сообщения, которое будет отправлено. Затем мы аутентифицируемся, используя секретный ключ API. После этого мы создаем полезную нагрузку в формате JSON, которая содержит все необходимые данные для запроса, включая название модели, температуру и историю сообщений (как системных, так и пользовательских). Далее мы готовим HTTP‑запрос, указав URL API, заголовки (включая секретный ключ API) и содержимое (строку JSON, которую мы создали).
Как только все параметры будут настроены, мы отправляем запрос в API OpenAI. Когда приходит ответ, мы проверяем его валидность. Если все хорошо, мы парсим его JSON‑представление, извлекаем ответ искусственного интеллекта и записываем его в логи. В случае возникновения ошибки мы также регистрируем ее (надеюсь, нам не придется ее видеть).
// Copyright Epic Games, Inc. All Rights Reserved.
// AwesomeAIPlugin.cpp
#include "AwesomeAIPlugin.h"
#include <iostream>
#include <sstream>
#include "llama.h"
// Для онлайн ИИ-генерации
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "JsonObjectConverter.h"
#define LOCTEXT_NAMESPACE "FAwesomeAIPluginModule"
void FAwesomeAIPluginModule::StartupModule()
{
std::string assistantRole = "Helpful assistant";
std::string messageToAI = "Hey, who are you?";
std::string modelName = "gpt-3.5-turbo";
std::string secretKey = ""; // ВСТАВЬТЕ СЮДА СВОЙ СЕКРЕТНЫЙ КЛЮЧ
// Подготавливаем полезную нагрузку JSON (фактические данные, отправляемые в запросе или сообщении, часто как часть HTTP-запроса или сетевых коммуникаций)
TSharedPtr<FJsonObject> JsonPayload = MakeShareable(new FJsonObject);
JsonPayload->SetStringField("model", FString(modelName.c_str()));
JsonPayload->SetNumberField("max_tokens", 4096);
JsonPayload->SetNumberField("temperature", 0.7);
TArray<TSharedPtr<FJsonValue>> Messages;
TSharedPtr<FJsonObject> SystemMessageObject = MakeShareable(new FJsonObject); // Создаем "system"-сообщение
SystemMessageObject->SetStringField("role", "system");
SystemMessageObject->SetStringField("content", FString(assistantRole.c_str()));
// Оборачиваем объект в FJsonValueObject
Messages.Add(MakeShareable(new FJsonValueObject(SystemMessageObject)));
// Создаем новое "user"-сообщение
TSharedPtr<FJsonObject> UserMessageObject = MakeShareable(new FJsonObject);
UserMessageObject->SetStringField("role", "user");
UserMessageObject->SetStringField("content", FString(messageToAI.c_str()));
Messages.Add(MakeShareable(new FJsonValueObject(UserMessageObject))); // Оборачиваем объект в FJsonValueObject
JsonPayload->SetArrayField("messages", Messages);
FString JsonString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonString);
FJsonSerializer::Serialize(JsonPayload.ToSharedRef(), Writer);
// Настройка HTTP-запроса
TSharedRef<IHttpRequest> HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->SetURL("https://api.openai.com/v1/chat/completions");
HttpRequest->SetVerb("POST");
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
HttpRequest->SetHeader(TEXT("Authorization"), FString(secretKey.c_str()));
HttpRequest->SetContentAsString(JsonString);
UE_LOG(LogTemp, Log, TEXT("Request content: %s \n"), *JsonString);
// Создаем колбек по завершению запроса
HttpRequest->OnProcessRequestComplete().BindLambda(
[](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()))
{
// Логируем весь JSON-ответ для отладки
FString RawResponse = Response->GetContentAsString();
UE_LOG(LogTemp, Log, TEXT("Full OpenAI Response: %s"), *RawResponse);
// Парсим ответ
TSharedPtr<FJsonObject> JsonResponse;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonResponse) && JsonResponse.IsValid())
{
const TArray<TSharedPtr<FJsonValue>>* Choices;
if (JsonResponse->TryGetArrayField("choices", Choices) && Choices->Num() > 0)
{
TSharedPtr<FJsonObject> Choice = (*Choices)[0]->AsObject();
if (Choice.IsValid())
{
TSharedPtr<FJsonObject> Message = Choice->GetObjectField("message");
if (Message.IsValid())
{
const FString AIResponse = Message->GetStringField("content");
UE_LOG(LogTemp, Warning, TEXT("AI Response: %s"), *AIResponse);
}
}
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to parse JSON response."));
}
}
else
{
if (Response.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("HTTP Request Failed: %s"), *Response->GetContentAsString());
}
else
{
UE_LOG(LogTemp, Error, TEXT("HTTP Request Failed: Invalid Response."));
}
}
});
// Отправляем запрос
HttpRequest->ProcessRequest();
}
Полный код можно найти здесь. И все работает! ?:

Возможные улучшения
Мы научились генерировать ответы с помощью искусственного интеллекта, но что же дальше? Впереди еще много работы! Необходимо добавить историю чатов для онлайн‑режима, освоить асинхронные вызовы автономного ИИ, протестировать другие модели, разработать удобный интерфейс для настройки, создать новые функции на основе блюпринтов и многое другое. Все упирается только в вашу фантазию!
Например, в своем личном проекте я создал полноценную систему диалогов NPC с удобным API (на основе блюпринтов), настройками, настраиваемым пользовательским интерфейсом и компонентами. Вот видео, демонстрирующее, как это работает в моем проекте:
Заключение
Я надеюсь, что это руководство помогло вам разобраться, как настроить плагин для Unreal Engine, работать с llama.cpp, узнать, как функционирует LLM, и объединить эти знания в единое целое! Это руководство демонстрирует, что в вашей игре можно создавать и использовать как локальный, так и удаленный генеративный искусственный интеллект.
Для меня это был действительно увлекательный опыт, и я искренне верю, что в будущем все больше игр будут использовать генеративный ИИ для создания реалистичных миров, где игроки смогут взаимодействовать с каждым NPC.
Спасибо за внимание!
Хотите погрузиться в мир Unreal Engine и узнать, как интегрировать мощные технологии в свои игры? Рекомендуем два открытых урока, которые помогут вам развить навыки и понять, как использовать генеративный ИИ и другие инновационные инструменты в игровом процессе:
«Ваш первый уровень с квестом на Unreal Engine!»
3 июля, 20:00
Получите подробные шаги по созданию своего первого уровня с квестом, а также освоите ключевые основы разработки на этой платформе.
«ИИ для игр: как оживить противника в Unreal Engine»
15 июля, 20:00
Разберем, как интегрировать ИИ в игры на Unreal Engine, и как этот процесс можно использовать для улучшения взаимодействия с игроком.
А если у вас уже есть опыт разработки на Unreal Engine, повысить скиллы и выйти на новый уровень вам поможет курс Unreal Engine Game Developer. Professional.