UnrealOllamaConnect ist ein spezieller Blueprint-Node für Unreal Engine. Er nimmt einen Text-Prompt (Eingabe) entgegen, sendet ihn an das lokale KI-Modell Gemma4 (in unserem Fall das Modell gemma4:e4b) und leitet die generierte Antwort zurück an Unreal Engine weiter.
Voraussetzungen
-
Ollama muss installiert und im Hintergrund laufen (
ollama serve) -
Das Modell
gemma4:e4bmuss gepullt sein (ollama pull gemma4:e4b) -
Visual Studio 2022 mit C++-Entwicklungskomponenten muss installiert sein
Installation
-
Öffne dein Unreal-Projekt und füge eine neue C++-Klasse hinzu (
Tools→New C++ Class). -
Wähle als Klassentyp
Blueprint Function Libraryaus. -
Benenne die Klasse
MyOllamaLibraryund setze sie aufpublic. -
Es öffnen sich eine
.cppund eine.h(Header) Datei. Kopiere den bereitgestellten Code jeweils in die richtige Datei. - Ersetze MYWORLD_API mit deinem PROJEKTNAME_API in der Header Datei.
-
Speichere und schließe dein Projekt.
- Falls Probleme auftreten: Klicke mit der rechten Maustaste auf deine
.uproject-Datei und wähleGenerate Visual Studio project filesaus.
- Falls Probleme auftreten: Klicke mit der rechten Maustaste auf deine
-
Öffne die Datei
/DeinProjekt/Source/DeinProjekt/DeinProjekt.Build.cs
in einem Texteditor (z. B. Visual Studio) und ändere die folgende Zeile:Von:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });Zu:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HTTP", "Json", "JsonUtilities" }); -
Öffne Unreal Engine und Visual Studio erneut. Drücke in Unreal
Strg + Alt + F11, um Live Coding zu starten und das Projekt zu kompilieren. -
Nach erfolgreichem Build findest du im Blueprint-Event-Graph die Funktion
Ask Gemma (Async).Wichtig: Verwende ausschließlich die Async-Version. Die synchrone Version blockiert Unreal, während die Anfrage läuft, und kann den Editor zum Einfrieren bringen.
//MyOllamaLibrary.cpp
#include "MyOllamaLibrary.h"
#include "HttpModule.h"
#include "Interfaces/IHttpResponse.h"
#include "Json.h"
#include "JsonUtilities.h"
// Hilfsfunktion zum Bereinigen des Antwort-Texts
FString SanitizeOllamaResponse(const FString& RawText)
{
FString CleanText = RawText;
// Entferne Escape-Sequenzen
CleanText = CleanText.Replace(TEXT("\\n"), TEXT("\n")); // Zeilenumbruch
CleanText = CleanText.Replace(TEXT("\\t"), TEXT("\t")); // Tabulator
CleanText = CleanText.Replace(TEXT("\\\""), TEXT("\"")); // Anführungszeichen
CleanText = CleanText.Replace(TEXT("\\\\"), TEXT("\\")); // Backslash
// Entferne nicht druckbare Zeichen (ASCII 0-31, außer \n = 10, \r = 13, \t = 9)
FString FilteredText;
for (int32 i = 0; i < CleanText.Len(); i++)
{
TCHAR Char = CleanText[i];
int32 CharCode = static_cast<int32>(Char);
// Behalte normale Zeichen, Leerzeichen, Zeilenumbrüche und Tabs
if (CharCode >= 32 || CharCode == 9 || CharCode == 10 || CharCode == 13)
{
FilteredText.AppendChar(Char);
}
}
return FilteredText;
}
void UOllamaBPLibrary::AskGemmaAsync(const FString& Prompt, FOnOllamaResponse OnComplete)
{
if (!FModuleManager::Get().IsModuleLoaded("HTTP"))
{
FModuleManager::Get().LoadModule("HTTP");
}
// HTTP Request erstellen
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
// JSON Payload erstellen
TSharedPtr<FJsonObject> JsonPayload = MakeShareable(new FJsonObject);
JsonPayload->SetStringField("model", "gemma4:e4b"); // oder "gemma3:latest"
JsonPayload->SetStringField("prompt", Prompt);
JsonPayload->SetBoolField("stream", false);
// JSON serialisieren
FString JsonString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonString);
FJsonSerializer::Serialize(JsonPayload.ToSharedRef(), Writer);
// Request konfigurieren
Request->SetURL("http://localhost:11434/api/generate");
Request->SetVerb("POST");
Request->SetHeader("Content-Type", "application/json");
Request->SetContentAsString(JsonString);
// Timeout auf 60 Sekunden erhöhen (Standard ist ca. 20-30 Sekunden)
Request->SetTimeout(240.0f); // 240 Sekunden
Request->SetActivityTimeout(120.0f);
// Callback binden
Request->OnProcessRequestComplete().BindStatic(&UOllamaBPLibrary::HandleHttpResponse, OnComplete);
// Request senden
if (!Request->ProcessRequest())
{
FOllamaResponse ErrorResponse;
ErrorResponse.bSuccess = false;
ErrorResponse.ErrorMessage = "Failed to send HTTP request";
OnComplete.ExecuteIfBound(ErrorResponse);
}
}
void UOllamaBPLibrary::HandleHttpResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FOnOllamaResponse OnComplete)
{
FOllamaResponse Result;
if (!bWasSuccessful || !Response.IsValid())
{
Result.bSuccess = false;
Result.ErrorMessage = "Network error - make sure Ollama is running";
OnComplete.ExecuteIfBound(Result);
return;
}
if (Response->GetResponseCode() != 200)
{
Result.bSuccess = false;
Result.ErrorMessage = FString::Printf(TEXT("HTTP Error: %d"), Response->GetResponseCode());
OnComplete.ExecuteIfBound(Result);
return;
}
// JSON Antwort parsen
TSharedPtr<FJsonObject> JsonResponse;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonResponse) && JsonResponse.IsValid())
{
if (JsonResponse->HasField("response"))
{
Result.bSuccess = true;
FString RawResponse = JsonResponse->GetStringField("response");
Result.ResponseText = SanitizeOllamaResponse(RawResponse); // ← Bereinigen!
}
else
{
Result.bSuccess = false;
Result.ErrorMessage = "Invalid response format from Ollama";
}
}
else
{
Result.bSuccess = false;
Result.ErrorMessage = "Failed to parse JSON response";
}
OnComplete.ExecuteIfBound(Result);
}
// Synchrone Version (nicht für Haupt-Thread!)
FOllamaResponse UOllamaBPLibrary::AskGemmaSync(const FString& Prompt)
{
// Implementierung ähnlich wie AskGemmaAsync, aber mit FHttpRequest::ECompletionMode::Blocking
// Achtung: Blockiert den Thread - nur in Hintergrund-Threads verwenden!
FOllamaResponse Result;
// ... (Implementierung analog zur async Version, aber mit Blocking)
return Result;
}
// MyOllamaLibrary.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Http.h"
#include "MyOllamaLibrary.generated.h"
// Struktur für die Antwort
USTRUCT(BlueprintType)
struct FOllamaResponse
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
FString ResponseText;
UPROPERTY(BlueprintReadOnly)
bool bSuccess;
UPROPERTY(BlueprintReadOnly)
FString ErrorMessage;
};
// Delegaten für asynchrone Callbacks
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnOllamaResponse, const FOllamaResponse&, Response);
// MYWORLD_API mit deinem Projektnamen "PROJEKTNAME_API" ersetzen!!! Wichtig
UCLASS()
class MYWORLD_API UOllamaBPLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// Asynchrone Funktion für Blueprints (empfohlen)
UFUNCTION(BlueprintCallable, Category = "Ollama", meta = (DisplayName = "Ask Gemma (Async)"))
static void AskGemmaAsync(const FString& Prompt, FOnOllamaResponse OnComplete);
// Synchrone Version (blockiert, nicht für Haupt-Thread empfohlen)
UFUNCTION(BlueprintCallable, Category = "Ollama", meta = (DisplayName = "Ask Gemma (Sync - Blocking)"))
static FOllamaResponse AskGemmaSync(const FString& Prompt);
private:
static void HandleHttpResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FOnOllamaResponse OnComplete);
};