├── .gitignore ├── Source └── TobenotLLMGameplay │ ├── Private │ ├── Common │ │ ├── TAPromptDefinitions.cpp │ │ ├── TAGameLibrary.cpp │ │ ├── TASystemLibrary.cpp │ │ ├── TATargetComponent.cpp │ │ └── TAEmbeddingSystem.cpp │ ├── TASettings.cpp │ ├── TAPromptSetting.cpp │ ├── Chat │ │ ├── TAChatCallback.cpp │ │ ├── TAChatLogCategory.cpp │ │ ├── TAInteractionComponent.cpp │ │ ├── Dialogue │ │ │ ├── TADialogueManager.cpp │ │ │ └── TADialogueInstance.cpp │ │ ├── Shout │ │ │ └── TAShoutManager.cpp │ │ └── TAFunctionInvokeComponent.cpp │ ├── Mechanism │ │ └── UTAGameMasterSubsystem.cpp │ ├── Save │ │ ├── TASaveGame.cpp │ │ ├── TAGuidSubsystem.cpp │ │ ├── TAGuidInterface.cpp │ │ └── TASaveGameSubsystem.cpp │ ├── Event │ │ ├── TAEventLogCategory.cpp │ │ ├── Data │ │ │ ├── TAEventInfo.cpp │ │ │ └── TAEventWarehouse.cpp │ │ ├── Generator │ │ │ ├── TAEventGenPrompt.cpp │ │ │ └── TAEventGenerator.cpp │ │ └── Core │ │ │ ├── TAEventInstance.cpp │ │ │ ├── TAEventPool.cpp │ │ │ └── TAEventSubsystem.cpp │ ├── Scene │ │ ├── TASceneLogCategory.cpp │ │ ├── TAPlaceActor.cpp │ │ ├── TAInteractiveActor.cpp │ │ ├── TAAreaScene.cpp │ │ └── TASceneSubsystem.cpp │ ├── TobenotLLMGameplay.cpp │ ├── UI │ │ ├── TAChatMessageItemWidget.cpp │ │ ├── TAChatBoxWidget.cpp │ │ └── TAChatWidget.cpp │ ├── Agent │ │ ├── TAAgentInterface.cpp │ │ ├── TAAgentComponent.cpp │ │ └── TANarrativeAgent.cpp │ └── Image │ │ └── TAImageGenerator.cpp │ ├── Public │ ├── Event │ │ ├── Generator │ │ │ ├── TAEventGenPrompt.h │ │ │ └── TAEventGenerator.h │ │ ├── TAEventLogCategory.h │ │ ├── Data │ │ │ ├── TAEventWarehouse.h │ │ │ └── TAEventInfo.h │ │ ├── Core │ │ │ ├── TAEventInstance.h │ │ │ ├── TAEventSubsystem.h │ │ │ └── TAEventPool.h │ │ └── Plot │ │ │ └── TAPlotManager.h │ ├── Chat │ │ ├── TAChatLogCategory.h │ │ ├── TAChatCallback.h │ │ ├── Dialogue │ │ │ ├── TADialogueManager.h │ │ │ ├── TADialogueInstance.h │ │ │ └── TADialogueComponent.h │ │ ├── Shout │ │ │ ├── TAShoutManager.h │ │ │ └── TAShoutComponent.h │ │ ├── TAInteractionComponent.h │ │ ├── TAFunctionInvokeComponent.h │ │ └── TAChatComponent.h │ ├── Scene │ │ ├── TASceneLogCategory.h │ │ ├── TAAreaScene.h │ │ ├── TASceneSubsystem.h │ │ ├── TAPlaceActor.h │ │ └── TAInteractiveActor.h │ ├── TobenotLLMGameplay.h │ ├── Mechanism │ │ └── UTAGameMasterSubsystem.h │ ├── UI │ │ ├── TAChatMessageItemWidget.h │ │ ├── TAChatBoxWidget.h │ │ └── TAChatWidget.h │ ├── Save │ │ ├── TAGuidSubsystem.h │ │ ├── TASaveGame.h │ │ ├── TAGuidInterface.h │ │ └── TASaveGameSubsystem.h │ ├── Common │ │ ├── TAGameLibrary.h │ │ ├── TAPromptDefinitions.h │ │ ├── TASystemLibrary.h │ │ ├── TATargetComponent.h │ │ ├── TALLMLibrary.h │ │ └── TAEmbeddingSystem.h │ ├── Image │ │ └── TAImageGenerator.h │ ├── TASettings.h │ ├── Agent │ │ ├── TAAgentComponent.h │ │ ├── TAAgentInterface.h │ │ └── TANarrativeAgent.h │ └── TAPromptSetting.h │ └── TobenotLLMGameplay.Build.cs ├── Resources └── Icon128.png ├── TobenotLLMGameplay.uplugin ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | Intermediate 3 | Binaries 4 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Common/TAPromptDefinitions.cpp: -------------------------------------------------------------------------------- 1 | #include "Common/TAPromptDefinitions.h" -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobenot/TobenotLLMGameplay/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Generator/TAEventGenPrompt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/TASettings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "TASettings.h" 5 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/TAPromptSetting.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "TAPromptSetting.h" 5 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/TAChatCallback.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Chat/TAChatCallback.h" 5 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Mechanism/UTAGameMasterSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Mechanism/UTAGameMasterSubsystem.h" 5 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Save/TASaveGame.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Save/TASaveGame.h" 4 | 5 | UTASaveGame::UTASaveGame() 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/TAChatLogCategory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Chat/TAChatLogCategory.h" 5 | 6 | DEFINE_LOG_CATEGORY(LogTAChat); 7 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/TAEventLogCategory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Event/TAEventLogCategory.h" 4 | 5 | DEFINE_LOG_CATEGORY(LogTAEventSystem); 6 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Scene/TASceneLogCategory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Scene/TASceneLogCategory.h" 4 | 5 | DEFINE_LOG_CATEGORY(LogTASceneSystem); 6 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/TAChatLogCategory.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(LogTAChat, Log, All); 8 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/TAEventLogCategory.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(LogTAEventSystem, Log, All); -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Scene/TASceneLogCategory.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(LogTASceneSystem, Log, All); 8 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/TobenotLLMGameplay.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | class FTobenotLLMGameplayModule : public IModuleInterface 8 | { 9 | public: 10 | 11 | /** IModuleInterface implementation */ 12 | virtual void StartupModule() override; 13 | virtual void ShutdownModule() override; 14 | }; 15 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Mechanism/UTAGameMasterSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "UTAGameMasterSubsystem.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class TOBENOTLLMGAMEPLAY_API UUTAGameMasterSubsystem : public UWorldSubsystem 14 | { 15 | GENERATED_BODY() 16 | }; 17 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/TobenotLLMGameplay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot 2 | // This code is licensed under the MIT License. See LICENSE in the project root for license information. 3 | 4 | 5 | #include "TobenotLLMGameplay.h" 6 | 7 | #define LOCTEXT_NAMESPACE "FTobenotLLMGameplayModule" 8 | 9 | void FTobenotLLMGameplayModule::StartupModule() 10 | { 11 | } 12 | 13 | // 模块卸载时调用 14 | void FTobenotLLMGameplayModule::ShutdownModule() 15 | { 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FTobenotLLMGameplayModule, TobenotLLMGameplay) -------------------------------------------------------------------------------- /TobenotLLMGameplay.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "TobenotLLMGameplay", 6 | "Description": "This is a collection plugin of the universal logic used by Tobenot in his LLM game.", 7 | "Category": "Other", 8 | "CreatedBy": "tobenot", 9 | "CreatedByURL": "https://github.com/tobenot", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "TobenotLLMGameplay", 20 | "Type": "Runtime", 21 | "LoadingPhase": "PreLoadingScreen" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/UI/TAChatMessageItemWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "UI/TAChatMessageItemWidget.h" 4 | 5 | void UTAChatMessageItemWidget::SetupMessage(const FString& Message, AActor* Sender, const FString& DynamicParam) 6 | { 7 | if(MessageTextBlock) 8 | { 9 | // 构建信息文本,并将它设置到文本块中 10 | FString DisplayText; 11 | if(Sender != nullptr) 12 | { 13 | //DisplayText = FString::Printf(TEXT("%s: %s"), *Sender->GetName(), *Message); 14 | DisplayText = Message; 15 | }else 16 | { 17 | DisplayText = Message; 18 | } 19 | MessageTextBlock->SetText(FText::FromString(DisplayText)); 20 | } 21 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Save/TAGuidSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Save/TAGuidSubsystem.h" 4 | 5 | // 在 UTAEventWarehouse.cpp 中实现相关方法 6 | 7 | AActor* UTAGuidSubsystem::GetActorByGUID(const FGuid& Guid) 8 | { 9 | AActor** FoundActor = GuidToActorMap.Find(Guid); 10 | return FoundActor ? *FoundActor : nullptr; 11 | } 12 | 13 | void UTAGuidSubsystem::RegisterActorGUID(const FGuid& Guid, AActor* Actor) 14 | { 15 | if (Actor != nullptr) 16 | { 17 | GuidToActorMap.Add(Guid, Actor); 18 | } 19 | } 20 | 21 | void UTAGuidSubsystem::UnregisterActorGUID(const FGuid& Guid) 22 | { 23 | GuidToActorMap.Remove(Guid); 24 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Data/TAEventWarehouse.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAEventWarehouse.h 4 | // 管理预设事件,通常用于读取配置表格数据 5 | 6 | #pragma once 7 | 8 | #include "CoreMinimal.h" 9 | #include "TAEventInfo.h" 10 | #include "TAEventWarehouse.generated.h" 11 | 12 | UCLASS() 13 | class TOBENOTLLMGAMEPLAY_API UTAEventWarehouse : public UWorldSubsystem 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | // 预设事件集合 19 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Event") 20 | TArray PresetEvents; 21 | 22 | // 声明一个函数,用于加载数据表并添加事件到事件池 23 | UFUNCTION(BlueprintCallable, Category = "Event") 24 | void LoadEventsFromDataTable(UDataTable* DataTable); 25 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/UI/TAChatMessageItemWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAChatMessageItemWidget.h - 聊天信息Item类声明 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "Blueprint/UserWidget.h" 8 | #include "Components/TextBlock.h" 9 | #include "TAChatMessageItemWidget.generated.h" 10 | 11 | UCLASS() 12 | class TOBENOTLLMGAMEPLAY_API UTAChatMessageItemWidget : public UUserWidget 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | UFUNCTION(BlueprintCallable, Category="TAChatMessageItemWidget") 18 | virtual void SetupMessage(const FString& Message, AActor* Sender, const FString& DynamicParam); 19 | 20 | protected: 21 | UPROPERTY(Meta = (BindWidget)) 22 | class UTextBlock* MessageTextBlock; 23 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Save/TAGuidSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "TAGuidSubsystem.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class TOBENOTLLMGAMEPLAY_API UTAGuidSubsystem : public UWorldSubsystem 14 | { 15 | GENERATED_BODY() 16 | public: 17 | // 根据 GUID 获取 Actor 的函数 18 | AActor* GetActorByGUID(const FGuid& Guid); 19 | 20 | // 当注册新 Actor 时调用,添加映射关系 21 | void RegisterActorGUID(const FGuid& Guid, AActor* Actor); 22 | 23 | // 当 Actor 销毁时调用,移除映射关系 24 | void UnregisterActorGUID(const FGuid& Guid); 25 | 26 | private: 27 | // 用于存储 GUID 和 Actor 的映射 28 | UPROPERTY() 29 | TMap GuidToActorMap; 30 | }; 31 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Save/TASaveGame.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Chat/TAChatComponent.h" 7 | #include "GameFramework/SaveGame.h" 8 | #include "TASaveGame.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS() 14 | class TOBENOTLLMGAMEPLAY_API UTASaveGame : public USaveGame 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | UTASaveGame(); 20 | 21 | public: 22 | UPROPERTY(VisibleAnywhere, Category = "TAGuid") 23 | TMap NameGuidMap; 24 | 25 | UPROPERTY(VisibleAnywhere, Category = "Serialization") 26 | TMap SerializedDataMap; 27 | 28 | UPROPERTY(VisibleAnywhere, Category = "Chat") 29 | TMap TAChatDataMap; 30 | }; 31 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Scene/TAAreaScene.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Chat/TAInteractionComponent.h" 7 | #include "UObject/Object.h" 8 | #include "TAAreaScene.generated.h" 9 | 10 | struct FTAEventInfo; 11 | class ATAInteractiveActor; 12 | /** 13 | * 14 | */ 15 | UCLASS() 16 | class TOBENOTLLMGAMEPLAY_API UTAAreaScene : public UObject 17 | { 18 | GENERATED_BODY() 19 | public: 20 | // 加载区域地图时调用,生成交互点 21 | void LoadAreaScene(const FTAEventInfo& EventInfo); 22 | 23 | protected: 24 | // 存储所有生成的交互点actors 25 | TArray InteractiveActors; 26 | 27 | TArray InteractablesArray; 28 | 29 | private: 30 | UPROPERTY() 31 | class UOpenAIChat* CacheChat; 32 | }; 33 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Common/TAGameLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Kismet/BlueprintFunctionLibrary.h" 7 | #include "TAGameLibrary.generated.h" 8 | 9 | USTRUCT(BlueprintType) 10 | struct FCategoryTextureData : public FTableRowBase 11 | { 12 | GENERATED_BODY() 13 | 14 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 15 | FName CategoryName; 16 | 17 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 18 | TSoftObjectPtr Texture; 19 | }; 20 | 21 | UCLASS() 22 | class TOBENOTLLMGAMEPLAY_API UTAGameLibrary : public UBlueprintFunctionLibrary 23 | { 24 | GENERATED_BODY() 25 | 26 | public: 27 | UFUNCTION(BlueprintCallable, Category = "Utility|Textures") 28 | static TSoftObjectPtr GetRandomTextureFromCategory(const UDataTable* DataTable, const FName& CategoryName); 29 | }; 30 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Common/TAPromptDefinitions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "TAPromptDefinitions.generated.h" 5 | 6 | USTRUCT(BlueprintType) 7 | struct TOBENOTLLMGAMEPLAY_API FTAPrompt 8 | { 9 | GENERATED_BODY() 10 | 11 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Prompt") 12 | FString PromptTemplate; 13 | 14 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Prompt") 15 | int32 VersionNumber; 16 | 17 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Prompt") 18 | bool bUseJsonFormat; 19 | 20 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Prompt") 21 | FString PromptExample; 22 | 23 | explicit FTAPrompt(const FString& InPromptTemplate = "", int32 InVersionNumber = 1, bool InUseJsonFormat = false, const FString& InPromptExample = "") 24 | : PromptTemplate(InPromptTemplate), 25 | VersionNumber(InVersionNumber), 26 | bUseJsonFormat(InUseJsonFormat), 27 | PromptExample(InPromptExample) 28 | { 29 | } 30 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Data/TAEventInfo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Event/Data/TAEventInfo.h" 4 | 5 | FString FTAEventInfo::ToString() const 6 | { 7 | FString EventTypeStr; 8 | switch (PresetData.EventType) 9 | { 10 | case ETAEventType::Combat: EventTypeStr = TEXT("Normal Combat"); break; 11 | case ETAEventType::BossFight: EventTypeStr = TEXT("Boss Fight"); break; 12 | case ETAEventType::Exploration: EventTypeStr = TEXT("Exploration"); break; 13 | case ETAEventType::Story: EventTypeStr = TEXT("Story"); break; 14 | case ETAEventType::Other: EventTypeStr = TEXT("Other"); break; 15 | default: EventTypeStr = TEXT("Unknown"); break; 16 | } 17 | return FString::Printf(TEXT("{\"EventID\": %d, \"Description\": \"%s\", \"Type\": \"%s\", \"Weight\": %d, \"LocationGuid: %s\"}"), 18 | PresetData.EventID, 19 | *PresetData.Description, 20 | *EventTypeStr, 21 | PresetData.Weight 22 | , *LocationGuid.ToString() 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Agent/TAAgentInterface.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Agent/TAAgentInterface.h" 5 | 6 | 7 | // Add default functionality here for any ITAAgentInterface functions that are not pure virtual. 8 | int32 ITAAgentInterface::GetAgentSpeakPriority() const 9 | { 10 | return 100; 11 | } 12 | 13 | void ITAAgentInterface::AddOrUpdateDesire(const FGuid& DesireId, const FString& DesireDescription) 14 | { 15 | } 16 | 17 | void ITAAgentInterface::RemoveDesire(const FGuid& DesireId) 18 | { 19 | } 20 | 21 | bool ITAAgentInterface::IsVoiceover() const 22 | { 23 | return false; 24 | } 25 | 26 | TMap ITAAgentInterface::QueryInventoryItems() const 27 | { 28 | TMap InventoryItems; 29 | return InventoryItems; 30 | } 31 | int32 ITAAgentInterface::QueryItemAmountByName(FName ItemName) const 32 | { 33 | return 0; 34 | } 35 | 36 | bool ITAAgentInterface::ConsumeInventoryItem(FName ItemName, int32 ConsumeCount) 37 | { 38 | return false; 39 | } 40 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/TAChatCallback.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Object.h" 7 | #include "OpenAIDefinitions.h" 8 | #include "TAChatCallback.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS(Blueprintable) 14 | class TOBENOTLLMGAMEPLAY_API UTAChatCallback : public UObject 15 | { 16 | GENERATED_BODY() 17 | 18 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTAChatSuccessCallbackDelegate, FChatCompletion, Message); 19 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FTAChatSuccessWithSenderCallbackDelegate, FChatCompletion, Message, AActor*, Sender); 20 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTAChatFailedCallbackDelegate); 21 | 22 | public: 23 | UPROPERTY(BlueprintAssignable) 24 | FTAChatSuccessCallbackDelegate OnSuccess; 25 | 26 | UPROPERTY(BlueprintAssignable) 27 | FTAChatSuccessWithSenderCallbackDelegate OnSuccessWithSender; 28 | 29 | UPROPERTY(BlueprintAssignable) 30 | FTAChatFailedCallbackDelegate OnFailure; 31 | }; 32 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Data/TAEventWarehouse.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Event/Data/TAEventWarehouse.h" 5 | 6 | #include "Engine/DataTable.h" 7 | #include "Event/TAEventLogCategory.h" 8 | #include "Event/Core/TAEventSubsystem.h" 9 | 10 | void UTAEventWarehouse::LoadEventsFromDataTable(UDataTable* DataTable) 11 | { 12 | // 检查数据表是否有效 13 | if (!DataTable) 14 | { 15 | UE_LOG(LogTAEventSystem, Error, TEXT("LoadEventsFromDataTable 数据表无效")); 16 | return; 17 | } 18 | 19 | // 获取数据表行 20 | TArray Events; 21 | DataTable->GetAllRows(TEXT("查找所有预设事件数据"), Events); 22 | 23 | UE_LOG(LogTAEventSystem, Log, TEXT("导入预设事件数据:[%s]"), *DataTable->GetName()); 24 | 25 | // 将事件添加到事件池中 26 | for (FTAPresetEventData* EventData : Events) 27 | { 28 | if (EventData) 29 | { 30 | UTAEventSubsystem* EventSubsystem = GetWorld()->GetSubsystem(); 31 | if (EventSubsystem) 32 | { 33 | EventSubsystem->AddEventToPoolByData(*EventData); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 tobenot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Save/TAGuidInterface.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Save/TAGuidInterface.h" 4 | #include "Save/TAGuidSubsystem.h" 5 | #include "Save/TASaveGameSubsystem.h" 6 | 7 | // Add default functionality here for any ITAGuidInterface functions that are not pure virtual. 8 | FGuid ITAGuidInterface::GetTAGuid() 9 | { 10 | return TAGuid; 11 | } 12 | 13 | void ITAGuidInterface::RegisterActorTAGuid(AActor* Actor, FName Name) 14 | { 15 | if (UTASaveGameSubsystem* SaveGameSubsystem = Actor->GetGameInstance()->GetSubsystem()) 16 | { 17 | SaveGameSubsystem->RegisterActorTAGuid(Actor, Name); 18 | } 19 | if (UTAGuidSubsystem* GuidSubsystem = Actor->GetWorld()->GetSubsystem()) 20 | { 21 | const FGuid ActorGuid = GetTAGuid(); 22 | GuidSubsystem->RegisterActorGUID(ActorGuid, Actor); 23 | } 24 | } 25 | 26 | void ITAGuidInterface::SetTAGuid(FGuid NewGuid) 27 | { 28 | TAGuid = NewGuid; 29 | } 30 | 31 | FString ITAGuidInterface::SerializeCustomData() 32 | { 33 | return ""; 34 | } 35 | 36 | void ITAGuidInterface::DeserializeCustomData(const FString& SerializedData) 37 | { 38 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/UI/TAChatBoxWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "UI/TAChatBoxWidget.h" 5 | #include "UI/TAChatMessageItemWidget.h" 6 | 7 | UTAChatBoxWidget::UTAChatBoxWidget(const FObjectInitializer& ObjectInitializer) 8 | : Super(ObjectInitializer) 9 | { 10 | // 初始化代码可以放这里(如果需要的话) 11 | } 12 | 13 | void UTAChatBoxWidget::NativeConstruct() 14 | { 15 | // 建议调用基类构造函数 16 | Super::NativeConstruct(); 17 | 18 | // 构造方法的其他UI初始化代码可以放这里 19 | } 20 | 21 | void UTAChatBoxWidget::AddChatMessage(const FString& Message, AActor* Sender, const FString& DynamicParam) 22 | { 23 | if(!ChatMessageItemWidgetClass) 24 | { 25 | UE_LOG(LogTemp, Error, TEXT("UTAChatBoxWidget ChatMessageItemWidgetClass is not set.")); 26 | return; 27 | } 28 | 29 | if(ChatScrollBox) 30 | { 31 | UTAChatMessageItemWidget* NewMessageItem = CreateWidget(this, ChatMessageItemWidgetClass); 32 | if(NewMessageItem) 33 | { 34 | NewMessageItem->SetupMessage(Message, Sender, DynamicParam); 35 | ChatScrollBox->AddChild(NewMessageItem); 36 | } 37 | } 38 | } 39 | 40 | void UTAChatBoxWidget::ClearChatMessage() 41 | { 42 | if(ChatScrollBox) 43 | { 44 | ChatScrollBox->ClearChildren(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/UI/TAChatBoxWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAChatBoxWidget.h - 聊天框类声明 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "TAChatMessageItemWidget.h" 8 | #include "Blueprint/UserWidget.h" 9 | #include "Components/ScrollBox.h" 10 | #include "TAChatBoxWidget.generated.h" 11 | 12 | UCLASS() 13 | class TOBENOTLLMGAMEPLAY_API UTAChatBoxWidget : public UUserWidget 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | UTAChatBoxWidget(const FObjectInitializer& ObjectInitializer); 19 | 20 | virtual void NativeConstruct() override; 21 | 22 | UFUNCTION(BlueprintCallable, Category = "TAChatBoxWidget") 23 | UScrollBox* GetChatScrollBox() const 24 | { 25 | return ChatScrollBox; 26 | } 27 | 28 | UPROPERTY(EditDefaultsOnly, Category = "TAChatBoxWidget") 29 | TSubclassOf ChatMessageItemWidgetClass; 30 | 31 | UFUNCTION(BlueprintCallable, Category="TAChatBoxWidget") 32 | void AddChatMessage(const FString& Message, AActor* Sender, const FString& DynamicParam); 33 | 34 | UFUNCTION(BlueprintCallable, Category="TAChatBoxWidget") 35 | void ClearChatMessage(); 36 | protected: 37 | 38 | UPROPERTY(Meta = (BindWidget)) 39 | class UScrollBox* ChatScrollBox; 40 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Core/TAEventInstance.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Object.h" 7 | #include "Event/Data/TAEventInfo.h" 8 | #include "TAEventInstance.generated.h" 9 | 10 | class ITAAgentInterface; 11 | class UTAAreaScene; 12 | /** 13 | * 14 | */ 15 | UCLASS() 16 | class TOBENOTLLMGAMEPLAY_API UTAEventInstance : public UObject 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // 绑定到此实例的事件信息 22 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 23 | FTAEventInfo EventInfo; 24 | 25 | // 用于打印信息的函数,以模拟事件的触发 26 | UFUNCTION(BlueprintCallable, Category = "Event") 27 | void TriggerEvent(); 28 | 29 | UFUNCTION(BlueprintCallable, Category = "Event") 30 | void OnEventFinished(int32 OutcomeID); 31 | 32 | private: 33 | bool bTriggered = false; 34 | 35 | public: 36 | // Assigning desires to NPC. 37 | UFUNCTION(BlueprintCallable, Category = "Event") 38 | void AssignDesiresToAgent(); 39 | 40 | // Revoking specific desires from an NPC. 41 | UFUNCTION(BlueprintCallable, Category = "Event") 42 | void RevokeAgentDesires(); 43 | 44 | private: 45 | // 用于跟踪所有欲望的GUID和与之关联的NPC 46 | UPROPERTY() 47 | TMap DesireAgentMap; 48 | }; 49 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Image/TAImageGenerator.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | // UTAImageGenerator.h 6 | // 图片生成器类,用于处理图片的异步生成请求 7 | 8 | #include "CoreMinimal.h" 9 | #include "UObject/NoExportTypes.h" 10 | #include "Common/TALLMLibrary.h" 11 | #include "TAImageGenerator.generated.h" 12 | 13 | class UOpenAIChat; 14 | struct FTAEventInfo; 15 | class UTAChatCallback; 16 | 17 | UCLASS() 18 | class TOBENOTLLMGAMEPLAY_API UTAImageGenerator : public UObject 19 | { 20 | GENERATED_BODY() 21 | 22 | UTAImageGenerator(); 23 | public: 24 | // 绑定的事件信息,用于图片生成回调时的映射,到时候要找回这个事件去设置。 25 | int32 BoundEventID; 26 | 27 | // 请求生成图片 28 | UFUNCTION(BlueprintCallable, Category = "Image|Generation") 29 | void RequestGenerateImage(const FTAEventInfo& EventInfo); 30 | 31 | UFUNCTION(BlueprintCallable, Category = "Image|Generation") 32 | void RequestGeneratePureImage(const FString& PureDescription); 33 | 34 | UFUNCTION() 35 | void OnDownloadComplete(UTexture2DDynamic* Texture); 36 | UFUNCTION() 37 | void OnDownloadFailed(UTexture2DDynamic* Texture); 38 | 39 | public: 40 | FTAImageDownloadedDelegate OnDownloadCompleteDelegate; 41 | FTAImageDownloadedDelegate OnDownloadFailedDelegate; 42 | 43 | private: 44 | UPROPERTY() 45 | UOpenAIChat* CacheOpenAIChat; 46 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/TASettings.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "TAPromptSetting.h" 7 | #include "Engine/DeveloperSettings.h" 8 | #include "TASettings.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS(config = Game, defaultconfig) 14 | class TOBENOTLLMGAMEPLAY_API UTASettings : public UDeveloperSettings 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | // 设置要使用的事件生成器的类名。需要是 UTAEventGenerator 的子类 20 | UPROPERTY(config, EditAnywhere, Category="Event") 21 | FSoftClassPath EventGeneratorClass; 22 | 23 | // 设置要使用的ATAPlaceActor的子类的类名。 24 | UPROPERTY(config, EditAnywhere, Category="Scene") 25 | FSoftClassPath PlaceActorClass; 26 | 27 | // 设置要使用的Prompt模板,UTAPromptSetting子类 28 | UPROPERTY(config, EditAnywhere, Category="Prompt") 29 | FSoftClassPath TAPromptSetting; 30 | 31 | // 设置要使用的交互Actor类,ATAInteractiveActor子类 32 | UPROPERTY(config, EditAnywhere, Category="Event") 33 | FSoftClassPath InteractiveActorClass; 34 | 35 | // 设置要使用的交互组件类,UTAInteractionComponent子类 36 | UPROPERTY(config, EditAnywhere, Category="Event") 37 | FSoftClassPath InteractionComponentClass; 38 | 39 | // 设置要使用的怪物类,UAActor类的子类 40 | UPROPERTY(config, EditAnywhere, Category = "Scene") 41 | FSoftClassPath MonsterClass; 42 | }; 43 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Save/TAGuidInterface.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Interface.h" 7 | #include "TAGuidInterface.generated.h" 8 | 9 | UINTERFACE(BlueprintType, NotBlueprintable) 10 | class UTAGuidInterface : public UInterface 11 | { 12 | GENERATED_BODY() 13 | }; 14 | 15 | class TOBENOTLLMGAMEPLAY_API ITAGuidInterface 16 | { 17 | GENERATED_BODY() 18 | public: 19 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 20 | virtual FGuid GetTAGuid(); 21 | 22 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 23 | virtual void RegisterActorTAGuid(AActor* Actor, FName Name); 24 | 25 | virtual void SetTAGuid(FGuid NewGuid); 26 | 27 | virtual FString SerializeCustomData(); 28 | 29 | virtual void DeserializeCustomData(const FString& SerializedData); 30 | 31 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 32 | virtual FName GetIdentityPositionName() const 33 | { 34 | return IdentityPositionName; 35 | } 36 | 37 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 38 | virtual void SetIdentityPositionName(FName NewIdentityPositionName) 39 | { 40 | IdentityPositionName = NewIdentityPositionName; 41 | } 42 | 43 | private: 44 | FGuid TAGuid; 45 | 46 | FName IdentityPositionName; 47 | }; 48 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/UI/TAChatWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "TAChatBoxWidget.h" 7 | #include "Blueprint/UserWidget.h" 8 | #include "Components/EditableTextBox.h" 9 | #include "TAChatWidget.generated.h" 10 | 11 | UCLASS() 12 | class TOBENOTLLMGAMEPLAY_API UTAChatWidget : public UUserWidget 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | UTAChatWidget(const FObjectInitializer& ObjectInitializer); 18 | 19 | virtual void NativeConstruct() override; 20 | virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; 21 | 22 | UFUNCTION(BlueprintCallable, Category="TAChatWidget") 23 | FString GetMessage(); 24 | 25 | UFUNCTION(BlueprintCallable, Category = "TAChatWidget") 26 | UEditableTextBox* GetChatTextBox() 27 | { 28 | return ChatTextBox; 29 | } 30 | 31 | UFUNCTION(BlueprintCallable, Category = "COWChatWidget") 32 | UTAChatBoxWidget* GetChatBoxWidget() const 33 | { 34 | return ChatBoxWidget; 35 | } 36 | 37 | UFUNCTION() 38 | virtual void OnChatTextCommitted(const FText& Text, ETextCommit::Type CommitMethod); 39 | 40 | protected: 41 | UPROPERTY(Meta = (BindWidget)) 42 | class UEditableTextBox* ChatTextBox; 43 | UPROPERTY(Meta = (BindWidget)) 44 | UTAChatBoxWidget* ChatBoxWidget; 45 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/Dialogue/TADialogueManager.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "TADialogueInstance.h" 8 | #include "Subsystems/WorldSubsystem.h" 9 | #include "TADialogueManager.generated.h" 10 | 11 | // DialogueManager.h 12 | UCLASS() 13 | class TOBENOTLLMGAMEPLAY_API UTADialogueManager : public UWorldSubsystem 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | // 创建对话实例 19 | UFUNCTION(BlueprintCallable, Category = "Dialogue Manager") 20 | UTADialogueInstance* CreateDialogueInstance(UObject* WorldContext); 21 | 22 | // 销毁对话实例 23 | UFUNCTION(BlueprintCallable, Category = "Dialogue Manager") 24 | void DestroyDialogueInstance(FGuid DialogueId); 25 | 26 | // 获取指定对话实例 27 | UFUNCTION(BlueprintCallable, Category = "Dialogue Manager") 28 | UTADialogueInstance* GetDialogueInstance(const FGuid& DialogueId); 29 | 30 | // 受理加入对话请求 31 | UFUNCTION(BlueprintCallable, Category = "Dialogue Manager") 32 | bool AcceptJoinRequest(AActor* JoiningActor, UTADialogueInstance* DialogueInstance); 33 | 34 | // 发送对话邀请(实现在后面提供) 35 | UFUNCTION(BlueprintCallable, Category = "Dialogue Manager") 36 | bool InviteToDialogue(AActor* InvitedActor, UTADialogueInstance* DialogueInstance); 37 | 38 | private: 39 | UPROPERTY() 40 | TMap DialogueInstances; 41 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Agent/TAAgentComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Components/ActorComponent.h" 5 | #include "TAAgentComponent.generated.h" 6 | 7 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 8 | class TOBENOTLLMGAMEPLAY_API UTAAgentComponent : public UActorComponent 9 | { 10 | GENERATED_BODY() 11 | 12 | public: 13 | UTAAgentComponent(); 14 | 15 | protected: 16 | virtual void BeginPlay() override; 17 | 18 | public: 19 | // Called every frame 20 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 21 | 22 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agent") 23 | bool bEnableScheduleShout; 24 | 25 | // 函数用于调用Owner上的ShoutComponent的RequestToSpeak 26 | UFUNCTION(BlueprintCallable, Category="Agent") 27 | void RequestSpeak(); 28 | 29 | // 变量声明为蓝图可配置 30 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agent") 31 | float MinTimeBetweenShouts; 32 | 33 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agent") 34 | float MaxTimeBetweenShouts; 35 | 36 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agent") 37 | float MinTimeBetweenRetryShouts; 38 | 39 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agent") 40 | float MaxTimeBetweenRetryShouts; 41 | private: 42 | // 内部追踪下一次应该喊话的时间 43 | float TimeToNextShout; 44 | // 内部用于生成下一次喊话的时间间隔 45 | void ScheduleNextShout(); 46 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/Shout/TAShoutManager.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "TAShoutManager.generated.h" 8 | 9 | struct FChatCompletion; 10 | class UTAShoutComponent; 11 | 12 | /** 13 | * 14 | */ 15 | UCLASS() 16 | class TOBENOTLLMGAMEPLAY_API UTAShoutManager : public UWorldSubsystem 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // Initializes the instance of the subsystem. 22 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 23 | 24 | // Cleans up the instance before it is destroyed. 25 | virtual void Deinitialize() override; 26 | 27 | // Functions to manage shout components. 28 | void RegisterShoutComponent(UTAShoutComponent* Component); 29 | void UnregisterShoutComponent(UTAShoutComponent* Component); 30 | 31 | // Function to broadcast a shout to listening components. 32 | void BroadcastShout(const FChatCompletion& Message, AActor* Shouter, float Volume); 33 | 34 | public: 35 | // Helper function to get all shout components in range of the shouter. 36 | TArray GetShoutComponentsInRange(AActor* Shouter, float Range); 37 | 38 | private: 39 | // Stores references to all registered shout components. 40 | UPROPERTY() 41 | TArray RegisteredShoutComponents; 42 | private: 43 | bool IsValidAgentName(const FString& Message, AActor* Shouter) const; 44 | }; 45 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/TAPromptSetting.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Common/TAPromptDefinitions.h" 7 | #include "UObject/Object.h" 8 | #include "TAPromptSetting.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | 14 | UCLASS() 15 | class TOBENOTLLMGAMEPLAY_API UTAPromptSetting : public UObject 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | FTAPrompt PromptEventGenInteractables = FTAPrompt{ 21 | "Enhance game interactivity by examining event details provided: [{EventInfo}]. Create engaging game elements with unique, witty characteristics. Use this JSON template:" 22 | "{" 23 | "\"Interactables\": [" 24 | "{" 25 | "\"Name\": \"[{Language}] Label for interactive entities like Enchanted Cauldron or Arcane Relic\"," 26 | "\"UniqueFeature\": \"Describes distinctive quirks, from humorous dialogue to enchanting effects\"," 27 | "\"Objective\": \"Goals for players, such as brewing a potion, defeating a foe, or solving riddles\"" 28 | "}" 29 | "// Develop more interactables using this format" 30 | "]" 31 | "}" 32 | "Design with these guidelines: Include clever, humor-filled challenges with distinctive eccentricities that demand bravery, focusing on combat encounters, as peaceful solutions with monsters aren't an option." 33 | "Please generate interactive objects that are directly related to the event and will progress the event when interacted with." 34 | ,1 35 | ,true 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/TAInteractionComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // UTAInteractionComponent.h 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "Components/ActorComponent.h" 8 | #include "Common/TAPromptDefinitions.h" 9 | #include "TAInteractionComponent.generated.h" 10 | 11 | USTRUCT(BlueprintType) 12 | struct FInteractableInfo 13 | { 14 | GENERATED_BODY() 15 | 16 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 17 | FString Name; 18 | 19 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 20 | FString UniqueFeature; 21 | 22 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 23 | FString Objective; 24 | }; 25 | 26 | /** 27 | * TA游戏中用于特定交互物的组件 28 | */ 29 | UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 30 | class TOBENOTLLMGAMEPLAY_API UTAInteractionComponent : public UActorComponent 31 | { 32 | GENERATED_BODY() 33 | 34 | public: 35 | UTAInteractionComponent(); 36 | virtual void InitPrompt(); 37 | virtual void InitializeComponent() override; 38 | virtual void BeginPlay() override; 39 | 40 | UPROPERTY(BlueprintReadOnly, VisibleDefaultsOnly, Category = "Prompts") 41 | FTAPrompt InteractiveActorPromptTemplate; 42 | 43 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Prompts") 44 | FInteractableInfo InteractableInfo; 45 | 46 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Prompts") 47 | FString BelongEventDescription; 48 | 49 | FString GetFullPrompt(); 50 | const FString& GetInteractableName(){return InteractableInfo.Name;}; 51 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/UI/TAChatWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "UI/TAChatWidget.h" 4 | 5 | UTAChatWidget::UTAChatWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) 6 | { 7 | } 8 | 9 | void UTAChatWidget::NativeConstruct() 10 | { 11 | Super::NativeConstruct(); 12 | if (ChatTextBox) 13 | { 14 | ChatTextBox->OnTextCommitted.AddDynamic(this, &UTAChatWidget::OnChatTextCommitted); 15 | 16 | // 将键盘焦点设置到 ChatTextBox 上。 17 | APlayerController* PlayerController = GetOwningPlayer(); 18 | if (PlayerController) 19 | { 20 | // Change the input mode to Game and UI 21 | FInputModeGameAndUI InputModeData; 22 | InputModeData.SetWidgetToFocus(ChatTextBox->TakeWidget()); 23 | InputModeData.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); 24 | PlayerController->SetInputMode(InputModeData); 25 | PlayerController->bShowMouseCursor = true; 26 | } 27 | FSlateApplication::Get().SetKeyboardFocus(ChatTextBox->TakeWidget(), EFocusCause::SetDirectly); 28 | } 29 | } 30 | 31 | void UTAChatWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) 32 | { 33 | Super::NativeTick(MyGeometry, InDeltaTime); 34 | } 35 | 36 | FString UTAChatWidget::GetMessage() 37 | { 38 | return ChatTextBox->GetText().ToString(); 39 | } 40 | 41 | void UTAChatWidget::OnChatTextCommitted(const FText& Text, ETextCommit::Type CommitMethod) 42 | { 43 | if(CommitMethod == ETextCommit::OnEnter) 44 | { 45 | // ... 46 | 47 | ChatTextBox->SetText(FText::FromString("")); 48 | } 49 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Common/TAGameLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Common/TAGameLibrary.h" 5 | 6 | TSoftObjectPtr UTAGameLibrary::GetRandomTextureFromCategory(const UDataTable* DataTable, const FName& CategoryName) 7 | { 8 | TArray MatchingTextures; 9 | FCategoryTextureData* TempRow = nullptr; 10 | 11 | if (!DataTable) 12 | { 13 | return nullptr; 14 | } 15 | 16 | for (auto& Row : DataTable->GetRowMap()) 17 | { 18 | TempRow = (FCategoryTextureData*)Row.Value; 19 | 20 | if (!TempRow) 21 | { 22 | // 输出日志,指出Row.Value转换为FCategoryTextureData*失败 23 | //UE_LOG(LogTemp, Warning, TEXT("Row value is null after casting.")); 24 | continue; 25 | } 26 | 27 | if (!TempRow->CategoryName.IsEqual(CategoryName)) 28 | { 29 | // 输出日志,指出CategoryName不匹配 30 | //UE_LOG(LogTemp, Warning, TEXT("CategoryName does not match.")); 31 | continue; 32 | } 33 | 34 | // 软指针的 is valid 是加载好了 重载的bool也是这个意思 35 | if (!TempRow->Texture.ToSoftObjectPath().IsValid()) 36 | { 37 | // 输出日志,指出Texture无效 38 | //UE_LOG(LogTemp, Warning, TEXT("Texture is not valid.")); 39 | continue; 40 | } 41 | 42 | // 添加匹配纹理 43 | MatchingTextures.Add(TempRow); 44 | } 45 | 46 | if (MatchingTextures.Num() > 0) 47 | { 48 | // Randomly select an index 49 | int32 RandomIndex = FMath::RandRange(0, MatchingTextures.Num() - 1); 50 | return MatchingTextures[RandomIndex]->Texture; 51 | } 52 | 53 | // Return a null pointer if there is no match or list is empty 54 | return nullptr; 55 | } 56 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Core/TAEventSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | // TAEventSubsystem.h 6 | // 主要负责管理和协调所有事件的系统 7 | 8 | #pragma once 9 | 10 | #include "CoreMinimal.h" 11 | #include "Subsystems/WorldSubsystem.h" 12 | #include "TAEventSubsystem.generated.h" 13 | 14 | struct FTAPresetEventData; 15 | struct FTAEventInfo; 16 | class UTAEventPool; 17 | 18 | UCLASS() 19 | class TOBENOTLLMGAMEPLAY_API UTAEventSubsystem : public UWorldSubsystem 20 | { 21 | GENERATED_BODY() 22 | 23 | public: 24 | UFUNCTION(BlueprintCallable, BlueprintPure) 25 | inline UTAEventPool* GetEventPool(); 26 | 27 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 28 | virtual void Deinitialize() override; 29 | virtual void Start(const int32& GenEventNum); 30 | 31 | UFUNCTION(BlueprintCallable, Category = "Event") 32 | bool HasAnyEventsInPool() const; 33 | 34 | UFUNCTION(BlueprintCallable, Category = "Event") 35 | void GenerateEventByDescriptionInLocation(const FString& Description, const FVector& InLocation); 36 | 37 | public: 38 | UFUNCTION() 39 | void AddEventToPoolByData(FTAPresetEventData EventData); 40 | 41 | UFUNCTION(BlueprintCallable, Category = "Event") 42 | void FinishEvent(int32 EventID, int32 OutcomeID); 43 | 44 | private: 45 | UFUNCTION() 46 | void HandleGeneratedEvents(TArray& GeneratedEvents); 47 | 48 | UFUNCTION() 49 | void HandleGeneratedEventsByDescriptionInLocation(TArray& GeneratedEvents, const FVector& InLocation); 50 | 51 | void GenerateImageForEvent(const FTAEventInfo& GeneratedEvent); 52 | 53 | UPROPERTY() 54 | UTAEventPool* EventPoolRef; 55 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/TAInteractionComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Chat/TAInteractionComponent.h" 5 | 6 | #include "Common/TALLMLibrary.h" 7 | #include "Common/TASystemLibrary.h" 8 | #include "Chat/TAChatLogCategory.h" 9 | 10 | UTAInteractionComponent::UTAInteractionComponent() 11 | { 12 | PrimaryComponentTick.bCanEverTick = true; 13 | } 14 | 15 | void UTAInteractionComponent::BeginPlay() 16 | { 17 | Super::BeginPlay(); 18 | } 19 | 20 | FString UTAInteractionComponent::GetFullPrompt() 21 | { 22 | return UTALLMLibrary::PromptToStr(InteractiveActorPromptTemplate) 23 | .Replace(TEXT("{Language}"), *UTASystemLibrary::GetGameLanguage()) 24 | .Replace(TEXT("{InteractableActorProfile}"), *InteractableInfo.Name) 25 | .Replace(TEXT("{UniqueFeature}"), *InteractableInfo.UniqueFeature) 26 | .Replace(TEXT("{Objective}"), *InteractableInfo.Objective) 27 | .Replace(TEXT("{BelongEventDescription}"), *BelongEventDescription) 28 | ; 29 | } 30 | 31 | void UTAInteractionComponent::InitPrompt() 32 | { 33 | InteractiveActorPromptTemplate = FTAPrompt{ 34 | "Become an interactive entity within a game, embodying the role of [{InteractableActorProfile}]." 35 | "Use the following JSON format for your responses:" 36 | 37 | "{" 38 | " \"message\": \"A concise narrative description of the interaction's progress.\"," 39 | "}" 40 | " Employ clear and engrossing language to deepen immersion and avoid player confusion." 41 | "Please provide your response in [{Language}]." 42 | ,1 43 | ,true 44 | }; 45 | } 46 | 47 | void UTAInteractionComponent::InitializeComponent() 48 | { 49 | Super::InitializeComponent(); 50 | InitPrompt(); 51 | } 52 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Common/TASystemLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Interfaces/IHttpRequest.h" 7 | #include "TASystemLibrary.generated.h" 8 | 9 | DECLARE_DYNAMIC_DELEGATE_OneParam(FOnTextFileDownloaded, const FString&, DownloadedText); 10 | 11 | /** 12 | * 13 | */ 14 | UCLASS() 15 | class TOBENOTLLMGAMEPLAY_API UTASystemLibrary : public UBlueprintFunctionLibrary 16 | { 17 | GENERATED_BODY() 18 | 19 | UFUNCTION(BlueprintCallable, Category = "Chat Engine") 20 | static void SetGlobalProxyAddress(const FString& ProxyAddress, int32 ProxyPort); 21 | 22 | UFUNCTION(BlueprintCallable, Category = "Chat Engine") 23 | static void ClearGlobalProxy(); 24 | 25 | UFUNCTION(BlueprintCallable, Category = "HTTP") 26 | static void DownloadTextFile(const FString& URL, const FOnTextFileDownloaded& OnDownloadComplete, const FOnTextFileDownloaded& OnDownloadFailed); 27 | 28 | UFUNCTION(BlueprintCallable, Category = "Utilities") 29 | static void ClipboardActionCopy(const FString& Context); 30 | 31 | private: 32 | static void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, const FOnTextFileDownloaded& OnDownloadComplete, const FOnTextFileDownloaded& OnDownloadFailed); 33 | 34 | public: 35 | UFUNCTION(BlueprintCallable, Category = "Localization") 36 | static void SetGameLanguage(const FString& Language); 37 | 38 | UFUNCTION(BlueprintPure, Category = "Localization") 39 | static FString GetGameLanguage(); 40 | 41 | UFUNCTION(BlueprintCallable, Category = "Localization") 42 | static void TASetCurrentCulture(FString Culture); 43 | 44 | static FString CurrentGameLanguage; 45 | }; 46 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Scene/TASceneSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "TASceneSubsystem.generated.h" 8 | 9 | class UTAAreaScene; 10 | class ATAPlaceActor; 11 | struct FTAEventInfo; 12 | /** 13 | * 14 | */ 15 | UCLASS() 16 | class TOBENOTLLMGAMEPLAY_API UTASceneSubsystem : public UWorldSubsystem 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // 在地图上生成怪物,请在gamemode中适当的时候调用 22 | UFUNCTION(BlueprintCallable, Category = "Scene") 23 | void PopulateMapWithMonsters(const TArray& ForbiddenLocations); 24 | 25 | // 查询当前关卡的人文、地理信息 26 | FString QuerySceneMapInfo(); 27 | 28 | // 查询指定位置所在的区域地点 29 | FString QueryLocationInfo(const FVector& Location); 30 | 31 | // 根据事件信息查询推荐的事件位置 32 | UFUNCTION(BlueprintCallable, Category = "Scene") 33 | ATAPlaceActor* QueryEventLocationByInfo(const FTAEventInfo& EventInfo); 34 | 35 | // 创建并添加新的位点到列表中,返回创建的位点Actor 36 | UFUNCTION(BlueprintCallable, Category = "Scene") 37 | ATAPlaceActor* CreateAndAddPlace(const FVector& Location, float Radius, const FString& Name); 38 | 39 | // 创建并返回一个UTAAreaScene实例的函数,同时加载区域地图 40 | UTAAreaScene* CreateAndLoadAreaScene(const FTAEventInfo& EventInfo); 41 | 42 | UFUNCTION(BlueprintCallable, Category = "Scene") 43 | ATAPlaceActor* CreatePlaceActorAtLocation(const FVector& NewLocation, float NewRadius, const FString& NewName); 44 | private: 45 | // 存储所有位点Actors的引用的列表 46 | UPROPERTY() 47 | TArray PlaceActors; 48 | 49 | int32 MaxQueryEventLocationRetryCount = 100; 50 | 51 | TMap AreaScenesMap; 52 | 53 | bool bHasPopulateMapWithMonsters = false; 54 | 55 | UClass* GetMonsterClass() const; 56 | 57 | // 用于跟踪是否已经创建了首个事件位点 58 | bool bHasCreatedStartingEvent = false; 59 | }; 60 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Scene/TAPlaceActor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Scene/TAPlaceActor.h" 4 | #include "Components/SphereComponent.h" 5 | 6 | // Sets default values 7 | ATAPlaceActor::ATAPlaceActor() 8 | { 9 | // Set this actor to call Tick() every frame. 10 | PrimaryActorTick.bCanEverTick = true; 11 | 12 | // Initialize the sphere component to visualize the place's area 13 | AreaDisplaySphere = CreateDefaultSubobject(TEXT("AreaDisplaySphere")); 14 | AreaDisplaySphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); 15 | RootComponent = AreaDisplaySphere; 16 | 17 | // Default place radius 18 | PlaceRadius = 100.0f; 19 | 20 | // By default, update the sphere radius to match our place radius 21 | AreaDisplaySphere->SetSphereRadius(PlaceRadius); 22 | } 23 | 24 | // Called when the game starts or when spawned 25 | void ATAPlaceActor::BeginPlay() 26 | { 27 | Super::BeginPlay(); 28 | } 29 | 30 | // Called every frame 31 | void ATAPlaceActor::Tick(float DeltaTime) 32 | { 33 | Super::Tick(DeltaTime); 34 | } 35 | 36 | void ATAPlaceActor::SetPlaceRadius(float Radius) 37 | { 38 | PlaceRadius = Radius; 39 | // Ensure the sphere component always matches the specified place radius 40 | if(AreaDisplaySphere->GetUnscaledSphereRadius() != PlaceRadius) 41 | { 42 | AreaDisplaySphere->SetSphereRadius(PlaceRadius); 43 | } 44 | } 45 | 46 | // Implementation of SetPlaceName 47 | void ATAPlaceActor::SetPlaceName(const FString& NewName) 48 | { 49 | if (PlaceName != NewName) 50 | { 51 | PlaceName = NewName; 52 | OnPlaceNameChanged.Broadcast(NewName); 53 | } 54 | } 55 | 56 | // Implementation of SetPlaceTexture 57 | void ATAPlaceActor::SetPlaceTexture(UTexture2DDynamic* NewTexture) 58 | { 59 | if (PlaceTexture != NewTexture) 60 | { 61 | PlaceTexture = NewTexture; 62 | OnTextureChanged.Broadcast(NewTexture); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Agent/TAAgentInterface.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Interface.h" 7 | #include "TAAgentInterface.generated.h" 8 | 9 | // This class does not need to be modified. 10 | UINTERFACE(BlueprintType, NotBlueprintable) 11 | class TOBENOTLLMGAMEPLAY_API UTAAgentInterface : public UInterface 12 | { 13 | GENERATED_BODY() 14 | }; 15 | 16 | /** 17 | * 18 | */ 19 | class TOBENOTLLMGAMEPLAY_API ITAAgentInterface 20 | { 21 | GENERATED_BODY() 22 | 23 | // Add interface functions to this class. This is the class that will be inherited to implement this interface. 24 | public: 25 | virtual FString GetSystemPrompt() = 0; 26 | 27 | UFUNCTION(BlueprintCallable, Category = "TA|Agent") 28 | virtual const FString& GetAgentName() const = 0; 29 | 30 | virtual int32 GetAgentSpeakPriority() const; 31 | 32 | // 增加或更新Agent的欲望 33 | UFUNCTION(BlueprintCallable, Category = "TA|Agent") 34 | virtual void AddOrUpdateDesire(const FGuid& DesireId, const FString& DesireDescription); 35 | 36 | // 移除Agent的欲望 37 | UFUNCTION(BlueprintCallable, Category = "TA|Agent") 38 | virtual void RemoveDesire(const FGuid& DesireId); 39 | 40 | // 目前的作用:如果是旁白的话,UTAShoutManager就不会检查消息前面有没有带着Agent名字 41 | virtual bool IsVoiceover() const; 42 | 43 | UFUNCTION(BlueprintCallable, Category = "TA|Agent") 44 | virtual TSoftObjectPtr GetAgentPortrait() const 45 | { 46 | return TSoftObjectPtr(); 47 | } 48 | 49 | UFUNCTION(BlueprintCallable, Category = "TA|Inventory") 50 | virtual TMap QueryInventoryItems() const; 51 | 52 | UFUNCTION(BlueprintCallable, Category = "TA|Inventory") 53 | virtual int32 QueryItemAmountByName(FName ItemName) const; 54 | 55 | UFUNCTION(BlueprintCallable, Category = "TA|Inventory") 56 | virtual bool ConsumeInventoryItem(FName ItemName, int32 ConsumeCount); 57 | }; 58 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Core/TAEventPool.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAEventPool.h 4 | // 管理游戏中的活跃/待触发事件集合 5 | 6 | #pragma once 7 | 8 | #include "CoreMinimal.h" 9 | #include "TAEventInstance.h" 10 | #include "TAEventPool.generated.h" 11 | 12 | UCLASS() 13 | class TOBENOTLLMGAMEPLAY_API UTAEventPool : public UObject 14 | { 15 | GENERATED_BODY() 16 | 17 | virtual void BeginDestroy() override; 18 | public: 19 | UFUNCTION(BlueprintCallable, Category = "Event") 20 | FTAEventInfo& AddEvent(FTAEventInfo EventInfo); 21 | 22 | UFUNCTION(BlueprintCallable, Category = "Event") 23 | FTAEventInfo& GetEventByID(int32 EventID, bool& bSuccess); 24 | 25 | FTAEventInfo ZeroEvent; 26 | 27 | UFUNCTION(BlueprintCallable, Category = "Event") 28 | bool HasAnyEvents() const; 29 | 30 | UFUNCTION(BlueprintCallable, Category = "Event") 31 | void AddCompletedEvent(int32 EventID, int32 OutcomeID); 32 | 33 | UTAEventInstance* GetEventInstanceByID(int32 EventID); 34 | private: 35 | // 所有事件信息的集合,这里是唯一的最持久的保存事件信息的地方。由EventSubsystem管理。请用指针指它 36 | UPROPERTY(VisibleAnywhere, Category = "Event") 37 | TArray AllEventInfo; 38 | 39 | // 活跃事件的集合 40 | UPROPERTY(VisibleAnywhere, Category = "Event") 41 | TArray ActiveEvents; 42 | 43 | UPROPERTY() 44 | TArray PendingEventInfos; 45 | // 傻眼了吧孩子,这个结构体指针不能暴露给蓝图 46 | private: 47 | bool bHasStartedProximityCheck = false; 48 | 49 | // 定义定时器句柄 50 | FTimerHandle EventTriggerTimerHandle; 51 | 52 | // 此函数用于开启周期性检查 53 | void StartTriggerCheck(); 54 | 55 | UPROPERTY(VisibleAnywhere, Category = "Event") 56 | TMap CompletedEventsOutcomeMap; 57 | 58 | public: 59 | // 定义检查功能函数 60 | UFUNCTION() 61 | void CheckPlayerProximityToEvents(); 62 | 63 | UFUNCTION() 64 | void CheckAndTriggerEvents(); 65 | 66 | bool CheckAgentCondition(const FTAAgentCondition& Condition); 67 | 68 | bool IsDependencyMet(const FTAEventDependency& Dependency); 69 | }; 70 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/Dialogue/TADialogueManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // DialogueManager.cpp 4 | #include "Chat/Dialogue/TADialogueManager.h" 5 | 6 | #include "Chat/Dialogue/TADialogueComponent.h" 7 | #include "Chat/TAChatLogCategory.h" 8 | 9 | UTADialogueInstance* UTADialogueManager::CreateDialogueInstance(UObject* WorldContext) 10 | { 11 | UTADialogueInstance* NewDialogueInstance = NewObject(WorldContext); 12 | NewDialogueInstance->DialogueId = FGuid::NewGuid(); 13 | DialogueInstances.Add(NewDialogueInstance->DialogueId, NewDialogueInstance); 14 | return NewDialogueInstance; 15 | } 16 | 17 | void UTADialogueManager::DestroyDialogueInstance(FGuid DialogueId) 18 | { 19 | UTADialogueInstance** DialogueInstance = DialogueInstances.Find(DialogueId); 20 | if (DialogueInstance) 21 | { 22 | DialogueInstances.Remove(DialogueId); 23 | (*DialogueInstance)->ConditionalBeginDestroy(); // 注意:这里要确保会话实例不被其他地方引用 24 | } 25 | } 26 | 27 | UTADialogueInstance* UTADialogueManager::GetDialogueInstance(const FGuid& DialogueId) 28 | { 29 | UTADialogueInstance** DialogueInstance = DialogueInstances.Find(DialogueId); 30 | if (DialogueInstance) 31 | { 32 | return *DialogueInstance; 33 | } 34 | return nullptr; 35 | } 36 | 37 | bool UTADialogueManager::AcceptJoinRequest(AActor* JoiningActor, UTADialogueInstance* DialogueInstance) 38 | { 39 | if (DialogueInstance && JoiningActor) 40 | { 41 | DialogueInstance->AddParticipant(JoiningActor); 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | bool UTADialogueManager::InviteToDialogue(AActor* InvitedActor, UTADialogueInstance* DialogueInstance) 48 | { 49 | if (DialogueInstance && InvitedActor) 50 | { 51 | UTADialogueComponent* DialogueComp = UTADialogueComponent::GetTADialogueComponent(InvitedActor); 52 | if(DialogueComp) 53 | { 54 | DialogueComp->SetCurrentDialogue(DialogueInstance); 55 | DialogueInstance->AddParticipant(InvitedActor); 56 | } 57 | return true; 58 | } 59 | return false; 60 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Save/TASaveGameSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/GameInstanceSubsystem.h" 7 | #include "TASaveGameSubsystem.generated.h" 8 | 9 | 10 | class ITAAgentInterface; 11 | class ITAGuidInterface; 12 | class UTASaveGame; 13 | /** 14 | * 15 | */ 16 | UCLASS() 17 | class TOBENOTLLMGAMEPLAY_API UTASaveGameSubsystem : public UGameInstanceSubsystem 18 | { 19 | GENERATED_BODY() 20 | 21 | public: 22 | // USubsystem BEGIN 23 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 24 | // USubsystem END 25 | 26 | // 一局游戏只读一次档就行了,回主菜单记得调用一下这个 27 | UFUNCTION(BlueprintCallable, Category = "SaveGame") 28 | void AllowRestoreNameTAGuidMap(); 29 | 30 | // Call it in GameMode's InitGame!!! 31 | UFUNCTION(BlueprintCallable, Category = "SaveGame") 32 | void RestoreNameTAGuidMap(); 33 | 34 | // Call it when server save game 35 | UFUNCTION(BlueprintCallable, Category = "SaveGame") 36 | void StoreAllTAData(); 37 | 38 | // 现在是每个actor生成时自己来拿存档 39 | //UFUNCTION(BlueprintCallable, Category = "SaveGame") 40 | //void RestoreAllTAData(); 41 | 42 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 43 | void RegisterActorTAGuid(AActor* Actor, FName Name); 44 | 45 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 46 | void UnregisterActorName(FName Name); 47 | 48 | UFUNCTION(BlueprintCallable, Category = "TAGuid") 49 | FGuid FindNamedTAGuid(FName Name); 50 | 51 | UFUNCTION(BlueprintCallable, Category = "Agent") 52 | AActor* FindActorByName(const FName& Name); 53 | 54 | private: 55 | bool hasRestoreNameTAGuidMap = false; 56 | 57 | // 这里定义了Actor定位名和GUID的对应关系 58 | TMap NameGuidMap; 59 | UPROPERTY() 60 | TMap GuidActorMap; // 新增的映射关系 61 | 62 | void SerializeActorData(AActor* Actor, ITAGuidInterface* TAGuidInterface); 63 | 64 | void RestoreActorData(AActor* Actor, ITAGuidInterface* TAGuidInterface, FGuid ActorGuid); 65 | 66 | UPROPERTY() 67 | UTASaveGame* SaveGameInstance; 68 | }; 69 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Scene/TAPlaceActor.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/Actor.h" 7 | #include "Components/SphereComponent.h" 8 | #include "Save/TAGuidInterface.h" 9 | #include "UObject/NoExportTypes.h" 10 | #include "TAPlaceActor.generated.h" 11 | 12 | // Declare delegates for broadcasting changes 13 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlaceNameChanged, const FString&, NewPlaceName); 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTextureChanged, UTexture2DDynamic*, NewTexture); 15 | 16 | UCLASS() 17 | class TOBENOTLLMGAMEPLAY_API ATAPlaceActor : public AActor 18 | , public ITAGuidInterface 19 | { 20 | GENERATED_BODY() 21 | 22 | public: 23 | // Place radius 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Place") 25 | float PlaceRadius; 26 | 27 | // Place name 28 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Place") 29 | FString PlaceName; 30 | 31 | // Dynamic texture for the place 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Place") 33 | UTexture2DDynamic* PlaceTexture; 34 | 35 | // Events for broadcasting changes 36 | UPROPERTY(BlueprintAssignable, Category = "Place") 37 | FOnPlaceNameChanged OnPlaceNameChanged; 38 | 39 | UPROPERTY(BlueprintAssignable, Category = "Place") 40 | FOnTextureChanged OnTextureChanged; 41 | 42 | public: 43 | // Sets default values for this actor's properties 44 | ATAPlaceActor(); 45 | 46 | protected: 47 | // Called when the game starts or when spawned 48 | virtual void BeginPlay() override; 49 | 50 | public: 51 | // Called every frame 52 | virtual void Tick(float DeltaTime) override; 53 | 54 | void SetPlaceRadius(float Radius); 55 | void SetPlaceName(const FString& NewName); 56 | void SetPlaceTexture(UTexture2DDynamic* NewTexture); 57 | 58 | // Used to show the place's area of effect 59 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Place") 60 | class USphereComponent* AreaDisplaySphere; 61 | }; 62 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Generator/TAEventGenPrompt.cpp: -------------------------------------------------------------------------------- 1 | #include "Event/Generator/TAEventGenPrompt.h" 2 | #include "Common/TAPromptDefinitions.h" 3 | #include "Event/Generator/TAEventGenerator.h" 4 | 5 | void UTAEventGenerator::InitPrompt() 6 | { 7 | PromptGenerateEvent = FTAPrompt{ 8 | "Your role is specialized in constructing thrilling events for games. " 9 | "Themes you could explore, but are not confined to:" 10 | "- SaveVillager: The player expected to rescue a villager from impending peril." 11 | "- FindItem: The player tasked with locating a significant item in the vicinity." 12 | "- EnemyEncounter: The player on the verge of an enemy encounter." 13 | "Follow the ensuing JSON template for the event's structure: " 14 | "[" 15 | "{" 16 | "\"AdventurePoints\": [English] As an adventure game," 17 | "\"LocationName\": Please provide in [{Language}]. A more precise geographical location used to describe the occurrence of the event. " 18 | "\"Description\": A detailed narrative of the event." 19 | "\"EventType\": A number denoting the type of event, options ranging from 0Combat, 1BossFight, 2Exploration, 3Story, to 4Other," 20 | "\"Weight\": a numerical value (0-99) indicating the probability of the event's occurrence" 21 | "}," 22 | "]" 23 | ,1 24 | ,true 25 | }; 26 | 27 | PromptGenerateEventByDescription = FTAPrompt{ 28 | "Your role is specialized in constructing a thrilling event for games. " 29 | "Theme is [{Theme}]." 30 | "Follow the ensuing JSON template for the event's structure: " 31 | "[" 32 | "{" 33 | "\"AdventurePoints\": [English] As an adventure game," 34 | "\"LocationName\": Please provide in [{Language}]. A more precise geographical location used to describe the occurrence of the event. " 35 | "\"Description\": A detailed narrative of the event. This not show to player, you can put some development notes here." 36 | "\"EventType\": A number denoting the type of event, options ranging from 0Combat, 1BossFight, 2Exploration, 3Story, to 4Other," 37 | "\"Weight\": a numerical value (0-99) indicating the probability of the event's occurrence" 38 | "}," 39 | "]" 40 | ,1 41 | ,true 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Generator/TAEventGenerator.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/NoExportTypes.h" 7 | #include "Common/TAPromptDefinitions.h" 8 | #include "TAEventGenerator.generated.h" 9 | 10 | struct FChatCompletion; 11 | class UTAChatCallback; 12 | struct FTAEventInfo; 13 | 14 | // 事件完成的委托声明 15 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTAEventGenerationSuccessDelegate, TArray&, GeneratedEvents); 16 | 17 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FTAEventGenerationSuccessInLocationDelegate, TArray&, GeneratedEvents, const FVector&, InLocation); 18 | 19 | UCLASS() 20 | class TOBENOTLLMGAMEPLAY_API UTAEventGenerator : public UObject 21 | { 22 | GENERATED_BODY() 23 | 24 | protected: 25 | virtual void InitPrompt(); 26 | 27 | public: 28 | UPROPERTY(BlueprintAssignable) 29 | FTAEventGenerationSuccessDelegate OnEventGenerationSuccess; 30 | 31 | UPROPERTY(BlueprintAssignable) 32 | FTAEventGenerationSuccessInLocationDelegate OnEventGenerationSuccessInLocation; 33 | 34 | // 使用地理人文信息生成事件 35 | UFUNCTION(BlueprintCallable, Category = "Event|Generation") 36 | void RequestEventGeneration(const FString& SceneInfo, const int32& Num); 37 | 38 | UFUNCTION(BlueprintCallable, Category = "Event|Generation") 39 | void RequestEventGenerationByDescription(const FString& SceneInfo, const FString& Description, const FVector& InLocation); 40 | 41 | protected: 42 | FTAPrompt PromptGenerateEvent; 43 | 44 | FTAPrompt PromptGenerateEventByDescription; 45 | 46 | // 当大模型处理完成后调用 47 | UFUNCTION() 48 | void OnChatSuccess(FChatCompletion ChatCompletion); 49 | 50 | // 当大模型处理失败时调用 51 | UFUNCTION() 52 | void OnChatFailure(); 53 | 54 | private: 55 | // 解析大模型返回的JSON字符串并转换为事件数组 56 | TArray ParseEventsFromJson(const FString& JsonString); 57 | 58 | void ProcessEventObject(const TSharedPtr& EventObject, TArray& ParsedEvents); 59 | 60 | UPROPERTY() 61 | UTAChatCallback* CacheCallbackObject; 62 | 63 | UPROPERTY() 64 | class UOpenAIChat* CacheChat; 65 | 66 | bool IsInLocation = false; 67 | FVector GenerateInLocation; 68 | }; 69 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Scene/TAInteractiveActor.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAInteractiveActor.h 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "GameFramework/Actor.h" 8 | #include "Chat/TAInteractionComponent.h" 9 | #include "Chat/TAChatComponent.h" 10 | #include "Agent/TAAgentInterface.h" 11 | #include "Save/TAGuidInterface.h" 12 | #include "TAInteractiveActor.generated.h" 13 | 14 | class UTADialogueComponent; 15 | class USphereComponent; 16 | class UTAFunctionInvokeComponent; 17 | /** 18 | * TA游戏中的交互物基类 19 | */ 20 | UCLASS() 21 | class TOBENOTLLMGAMEPLAY_API ATAInteractiveActor : public AActor 22 | ,public ITAAgentInterface 23 | ,public ITAGuidInterface 24 | { 25 | GENERATED_BODY() 26 | virtual void OnConstruction(const FTransform& Transform) override; 27 | public: 28 | ATAInteractiveActor(); 29 | 30 | protected: 31 | virtual void BeginPlay() override; 32 | 33 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 34 | USceneComponent* RootSceneComponent; 35 | 36 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 37 | UTAChatComponent* ChatComponent; 38 | 39 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 40 | UTADialogueComponent* DialogueComponent; 41 | 42 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 43 | UTAFunctionInvokeComponent* FunctionInvokeComponent ; 44 | 45 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 46 | UTAInteractionComponent* InteractionComponent; 47 | 48 | // 添加碰撞组件 49 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 50 | USphereComponent* CollisionComponent; 51 | 52 | // 添加静态Mesh组件 53 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 54 | UStaticMeshComponent* DisplayMeshComponent; 55 | 56 | // ITAAgentInterface中定义的获取系统prompt的函数实现 57 | virtual FString GetSystemPrompt() override; 58 | 59 | virtual const FString& GetAgentName() const override; 60 | 61 | virtual int32 GetAgentSpeakPriority() const override{return 150;}; //交互物先说 62 | 63 | public: 64 | UFUNCTION(BlueprintCallable, Category = "TAInteractiveActor") 65 | UTAInteractionComponent* GetInteractionComponent() const; 66 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Agent/TAAgentComponent.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Agent/TAAgentComponent.h" 3 | #include "TimerManager.h" 4 | #include "Chat/Shout/TAShoutComponent.h" 5 | #include "Chat/Shout/TAShoutManager.h" 6 | 7 | UTAAgentComponent::UTAAgentComponent() 8 | { 9 | PrimaryComponentTick.bCanEverTick = true; 10 | bEnableScheduleShout = true; 11 | 12 | // 默认喊话时间间隔范围 13 | MinTimeBetweenShouts = 15.f; 14 | MaxTimeBetweenShouts = 30.f; 15 | 16 | // 没有响应时重试喊话的默认时间间隔范围 17 | MinTimeBetweenRetryShouts = 0.5f; 18 | MaxTimeBetweenRetryShouts = 2.0f; 19 | } 20 | 21 | void UTAAgentComponent::BeginPlay() 22 | { 23 | Super::BeginPlay(); 24 | TimeToNextShout = MaxTimeBetweenRetryShouts; 25 | } 26 | 27 | void UTAAgentComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 28 | { 29 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 30 | 31 | if(GetOwner()->GetLocalRole() == ROLE_Authority) 32 | { 33 | if(bEnableScheduleShout) 34 | { 35 | // 减少等待时间 36 | TimeToNextShout -= DeltaTime; 37 | 38 | // 当等待时间结束时,调用RequestSpeak并计划下一次喊话 39 | if(TimeToNextShout <= 0) 40 | { 41 | RequestSpeak(); 42 | } 43 | } 44 | } 45 | } 46 | 47 | void UTAAgentComponent::RequestSpeak() 48 | { 49 | // 获取对 UTAShoutManager 的引用 50 | UTAShoutManager* ShoutManager = GetWorld()->GetSubsystem(); 51 | if (!ShoutManager) return; 52 | 53 | // 获取周围范围内的 UTAShoutComponent 列表 54 | float ShoutRange = 700.f; 55 | TArray NearbyShoutComponents = ShoutManager->GetShoutComponentsInRange(GetOwner(), ShoutRange); 56 | 57 | // 检查除了自己之外是否有其他 UTAShoutComponent 58 | if (NearbyShoutComponents.Num() > 1 || (NearbyShoutComponents.Num() == 1 && NearbyShoutComponents[0] != GetOwner()->FindComponentByClass())) 59 | { 60 | // 如果有其他组件,则请求发言 61 | UTAShoutComponent* ShoutComponent = GetOwner()->FindComponentByClass(); 62 | if (ShoutComponent) 63 | { 64 | ShoutComponent->RequestToSpeak(); 65 | } 66 | ScheduleNextShout(); 67 | } 68 | else 69 | { 70 | // 如果没有其他接收者,则立即计划一个短时的下次喊话。 71 | // 因为这样子可以营造出玩家一走过去,Agent马上说话的情形。 72 | TimeToNextShout = FMath::RandRange(MinTimeBetweenRetryShouts, MaxTimeBetweenRetryShouts); 73 | } 74 | } 75 | 76 | void UTAAgentComponent::ScheduleNextShout() 77 | { 78 | // 在配置的最小和最大时间之间随机选择下次喊话的时间 79 | TimeToNextShout = FMath::RandRange(MinTimeBetweenShouts, MaxTimeBetweenShouts); 80 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Common/TATargetComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot 2 | // This code is licensed under the MIT License. See LICENSE in the project root for license information. 3 | 4 | 5 | #pragma once 6 | 7 | #include "CoreMinimal.h" 8 | #include "Components/ActorComponent.h" 9 | #include "TATargetComponent.generated.h" 10 | 11 | 12 | UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 13 | class TOBENOTLLMGAMEPLAY_API UTATargetComponent : public UActorComponent 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | UTATargetComponent(); 19 | 20 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "TA|Targeting") 21 | float SearchRadius = 500.f; 22 | 23 | // 在一个循环结束之后轮空一次吗? 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "TA|Targeting") 25 | bool bEmptyBeforeCycle = false; 26 | 27 | // 轮选下一个目标的函数 28 | UFUNCTION(BlueprintCallable, Category="TA|Targeting") 29 | void SelectNextTarget(); 30 | 31 | // 获取当前选中的目标 32 | UFUNCTION(BlueprintCallable, Category="TA|Targeting") 33 | AActor* GetCurrentTarget() const; 34 | 35 | // 获取附近的可交互目标列表 36 | UFUNCTION(BlueprintCallable, Category="TA|Targeting") 37 | const TArray& GetNearbyTargets() const; 38 | 39 | // 目标变更事件的委托 40 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTargetChangedSignature, AActor*, NewTarget); 41 | 42 | // 目标变更事件 43 | UPROPERTY(BlueprintAssignable, Category="TA|Targeting") 44 | FTargetChangedSignature OnTargetChanged; 45 | 46 | // 目标列表更新事件的委托 47 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTargetListUpdatedSignature); 48 | 49 | // 目标列表更新事件 50 | UPROPERTY(BlueprintAssignable, Category="TA|Targeting") 51 | FTargetListUpdatedSignature OnTargetListUpdated; 52 | 53 | protected: 54 | virtual void BeginPlay() override; 55 | 56 | public: 57 | // 附近的可交互目标列表 58 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "TA|Targeting") 59 | TArray NearbyTargets; 60 | 61 | // 当前选中的目标 62 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "TA|Targeting") 63 | AActor* CurrentTarget = nullptr; 64 | 65 | UFUNCTION(BlueprintCallable, Category="TA|Targeting") 66 | void SetCurrentTarget(AActor* NewTarget){CurrentTarget = NewTarget;} 67 | 68 | private: 69 | // 更新附近目标列表的函数 70 | void UpdateNearbyTargets(); 71 | 72 | // 轮选逻辑的辅助函数 73 | void CycleToNextTarget(); 74 | 75 | bool HasTargetListChanged(const TArray& OldList, const TArray& NewList) const; 76 | public: 77 | void StartTargetCheckTimer(); 78 | 79 | private: 80 | FTimerHandle TargetingCheckTimerHandle; 81 | }; 82 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/TobenotLLMGameplay.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot 2 | // This code is licensed under the MIT License. See LICENSE in the project root for license information. 3 | 4 | using UnrealBuildTool; 5 | 6 | public class TobenotLLMGameplay : ModuleRules 7 | { 8 | public TobenotLLMGameplay(ReadOnlyTargetRules Target) : base(Target) 9 | { 10 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 11 | 12 | PublicIncludePaths.AddRange( 13 | new string[] { 14 | // ... add public include paths required here ... 15 | } 16 | ); 17 | 18 | 19 | PrivateIncludePaths.AddRange( 20 | new string[] { 21 | // ... add other private include paths required here ... 22 | } 23 | ); 24 | 25 | 26 | PublicDependencyModuleNames.AddRange( 27 | new string[] 28 | { 29 | "Core", 30 | // ... add other public dependencies that you statically link with here ... 31 | } 32 | ); 33 | 34 | 35 | PrivateDependencyModuleNames.AddRange( 36 | new string[] 37 | { 38 | "CoreUObject", 39 | "Engine", 40 | "Slate", 41 | "SlateCore", 42 | "OpenAIAPI", 43 | "UMG", 44 | "Json", 45 | "Http", 46 | "ApplicationCore", 47 | "DeveloperSettings", 48 | 49 | // 下载图片 50 | "RHI", 51 | "RenderCore", 52 | 53 | "TobenotToolkit", 54 | "NavigationSystem", // 场景系统用 55 | // ... add private dependencies that you statically link with here ... 56 | } 57 | ); 58 | 59 | 60 | DynamicallyLoadedModuleNames.AddRange( 61 | new string[] 62 | { 63 | // ... add any modules that your module loads dynamically here ... 64 | } 65 | ); 66 | 67 | PublicIncludePaths.AddRange( 68 | new string[] { 69 | "TobenotLLMGameplay", 70 | "TobenotLLMGameplay/Agent", 71 | "TobenotLLMGameplay/Chat", 72 | "TobenotLLMGameplay/Common", 73 | "TobenotLLMGameplay/Event", 74 | "TobenotLLMGameplay/Image", 75 | "TobenotLLMGameplay/Save", 76 | "TobenotLLMGameplay/Scene", 77 | "TobenotLLMGameplay/UI", 78 | } 79 | ); 80 | 81 | PrivateIncludePaths.AddRange( 82 | new string[] { 83 | "TobenotLLMGameplay", 84 | "TobenotLLMGameplay/Agent", 85 | "TobenotLLMGameplay/Chat", 86 | "TobenotLLMGameplay/Common", 87 | "TobenotLLMGameplay/Event", 88 | "TobenotLLMGameplay/Image", 89 | "TobenotLLMGameplay/Save", 90 | "TobenotLLMGameplay/Scene", 91 | "TobenotLLMGameplay/UI", 92 | } 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Common/TASystemLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Common/TASystemLibrary.h" 5 | #include "HttpModule.h" 6 | #include "Interfaces/IHttpRequest.h" 7 | #include "Interfaces/IHttpResponse.h" 8 | #include "Windows/WindowsPlatformApplicationMisc.h" 9 | 10 | FString UTASystemLibrary::CurrentGameLanguage; 11 | 12 | void UTASystemLibrary::SetGlobalProxyAddress(const FString& ProxyAddress, int32 ProxyPort) 13 | { 14 | // Construct the full proxy address by combining the IP address and port 15 | const FString FullProxyAddress = FString::Printf(TEXT("%s:%d"), *ProxyAddress, ProxyPort); 16 | 17 | // Set the proxy address in the FHttpModule 18 | FHttpModule::Get().SetProxyAddress(FullProxyAddress); 19 | } 20 | 21 | void UTASystemLibrary::ClearGlobalProxy() 22 | { 23 | // Set the proxy address to an empty string to clear the proxy 24 | FHttpModule::Get().SetProxyAddress(TEXT("")); 25 | } 26 | 27 | void UTASystemLibrary::DownloadTextFile(const FString& URL, const FOnTextFileDownloaded& OnDownloadComplete, const FOnTextFileDownloaded& OnDownloadFailed) 28 | { 29 | const TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); 30 | 31 | // Use Lambda to handle callbacks 32 | HttpRequest->OnProcessRequestComplete().BindLambda([OnDownloadComplete, OnDownloadFailed](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 33 | { 34 | OnResponseReceived(Request, Response, bWasSuccessful, OnDownloadComplete, OnDownloadFailed); 35 | }); 36 | 37 | HttpRequest->SetURL(URL); 38 | HttpRequest->SetVerb(TEXT("GET")); 39 | 40 | HttpRequest->ProcessRequest(); 41 | } 42 | 43 | void UTASystemLibrary::ClipboardActionCopy(const FString& Context) 44 | { 45 | FPlatformApplicationMisc::ClipboardCopy(*Context); 46 | } 47 | 48 | void UTASystemLibrary::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, 49 | const FOnTextFileDownloaded& OnDownloadComplete, const FOnTextFileDownloaded& OnDownloadFailed) 50 | { 51 | if (bWasSuccessful && Response.IsValid()) 52 | { 53 | FString DownloadedText = Response->GetContentAsString(); 54 | OnDownloadComplete.ExecuteIfBound(DownloadedText); 55 | } 56 | else 57 | { 58 | OnDownloadFailed.ExecuteIfBound(""); 59 | } 60 | } 61 | 62 | void UTASystemLibrary::SetGameLanguage(const FString& Language) 63 | { 64 | CurrentGameLanguage = Language; 65 | } 66 | 67 | FString UTASystemLibrary::GetGameLanguage() 68 | { 69 | return CurrentGameLanguage; 70 | } 71 | 72 | void UTASystemLibrary::TASetCurrentCulture(FString Culture) 73 | { 74 | FInternationalization& I18N = FInternationalization::Get(); 75 | I18N.SetCurrentCulture(Culture); 76 | } 77 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/TAFunctionInvokeComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | // FunctionInvoke功能 3 | // 解析大模型回复里面的func_invoke并调用相应的函数 4 | // 和FunctionCall不一样的是,这里的函数不需要返还给大模型,只管触发就行了! 5 | /* 6 | prompt示例: 7 | "Please use the following JSON template for your response:" 8 | "Response Template: {" 9 | "..." 10 | "// Optionally add func_invokes if functions need to be triggered" 11 | "\"func_invoke\": [" 12 | "{" 13 | "\"name\": \"TriggerBattle\"," 14 | "\"depict\": \"Ice Mushroom Monster\"" 15 | "}," 16 | "// Include additional function calls in the array as required" 17 | "]" 18 | "}" 19 | "func_invoke library:" 20 | "- \"TriggerBattle\": Use when the player is about to enter into a combat scenario. The \"depict\" should describe enemies for the fight." 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "CoreMinimal.h" 26 | #include "Components/ActorComponent.h" 27 | #include "Dom/JsonObject.h" 28 | #include "Serialization/JsonSerializer.h" 29 | #include "TAFunctionInvokeComponent.generated.h" 30 | 31 | DECLARE_LOG_CATEGORY_EXTERN(LogFunctionInvoke, Log, All); 32 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpecialEventDelegate, const FString&, Depict); 33 | 34 | UCLASS(Blueprintable, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 35 | class TOBENOTLLMGAMEPLAY_API UTAFunctionInvokeComponent : public UActorComponent 36 | { 37 | GENERATED_BODY() 38 | 39 | public: 40 | UTAFunctionInvokeComponent(); 41 | 42 | // 理解成一些剧情特殊事件,要单独开发机制的那种,让Agent蓝图去绑定这个组件的这个委托 43 | UPROPERTY(BlueprintAssignable) 44 | FSpecialEventDelegate OnSpecialEvent; 45 | 46 | // 解析func_invoke并调用相应的函数 47 | UFUNCTION(BlueprintCallable, Category = "FunctionInvoke") 48 | void ParseAndTriggerFunctions(const FString& Response); 49 | 50 | protected: 51 | // Helper function to deserialize a JSON string into a JSON object 52 | bool TryDeserializeJson(const FString& Response, TSharedPtr& OutJsonObject); 53 | 54 | // Helper function to handle an individual function invoke 55 | void HandleFunctionInvoke(const TSharedPtr& FuncInvoke); 56 | 57 | // 在这里添加对应的函数执行操作。注意命名与你的具体游戏逻辑上的函数名保持一致。 58 | UFUNCTION() 59 | virtual void TriggerBattle(const FString& Depict); 60 | 61 | UFUNCTION() 62 | virtual void GiveItem(const FString& Depict); 63 | 64 | UFUNCTION() 65 | virtual void FinishEvent(const FString& Depict); 66 | 67 | UFUNCTION() 68 | virtual void SpecialEvent(const FString& Depict); 69 | 70 | UFUNCTION() 71 | void RequestShoutCompToSpeak(const FString& Message); 72 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Common/TALLMLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "OpenAIDefinitions.h" 7 | #include "Kismet/BlueprintFunctionLibrary.h" 8 | #include "Interfaces/IHttpRequest.h" 9 | #include "TALLMLibrary.generated.h" 10 | 11 | class FTAImageDownloadedDelegate; 12 | struct FTAPrompt; 13 | class UOpenAIChat; 14 | 15 | UENUM(BlueprintType) 16 | enum class ELLMChatEngineQuality : uint8 17 | { 18 | Fast UMETA(DisplayName = "Fast"), 19 | Moderate UMETA(DisplayName = "Moderate"), 20 | HighQuality UMETA(DisplayName = "High Quality") 21 | }; 22 | 23 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTAImageDownloadedDelegate, UTexture2DDynamic*, Texture); 24 | /** 25 | * 26 | */ 27 | UCLASS() 28 | class TOBENOTLLMGAMEPLAY_API UTALLMLibrary : public UBlueprintFunctionLibrary 29 | { 30 | GENERATED_BODY() 31 | public: 32 | UFUNCTION(BlueprintCallable, Category = "Chat Engine") 33 | static EOAChatEngineType GetChatEngineTypeFromQuality(const ELLMChatEngineQuality Quality); 34 | 35 | static UOpenAIChat* SendMessageToOpenAIWithRetry(const FChatSettings& ChatSettings, TFunction Callback, const UObject* LogObject, const int32 NewRetryCount = MaxRetryCount); 36 | 37 | static UOpenAIChat* DownloadImageFromPollinations(const FString& ImagePrompt, const FTAImageDownloadedDelegate & OnDownloadComplete, const FTAImageDownloadedDelegate & OnDownloadFailed, const UObject* LogObject); 38 | 39 | static TSharedRef DownloadImageFromPollinationsPure(const FString& PureDescription, const FTAImageDownloadedDelegate & OnDownloadComplete, const FTAImageDownloadedDelegate & OnDownloadFailed, const UObject* LogObject); 40 | 41 | UFUNCTION(BlueprintCallable, Category = "Prompt") 42 | static FString PromptToStr(const FTAPrompt& Prompt); 43 | 44 | UFUNCTION(BlueprintCallable, Category = "Token Accounting") 45 | static void GetAccumulatedTokenCost(int32 &TotalTokenCount, float &AccumulatedCost); 46 | 47 | UFUNCTION(BlueprintCallable, Category = "Token Accounting") 48 | static void GetLogObjectTokenCosts(TArray& LogObjectNames, TArray& TotalTokenCounts, TArray& AccumulatedCosts); 49 | 50 | private: 51 | // 最大重试次数和重试间隔定义 52 | static constexpr int32 MaxRetryCount = 3; 53 | static constexpr float RetryDelay = 3.0f; 54 | inline static int32 TotalTokens = 0; 55 | inline static float TotalCost = 0.0f; 56 | 57 | // 定义结构体用于存储每个LogObject的token统计和花费 58 | struct FTokenCostStats 59 | { 60 | int32 TotalTokens; 61 | float TotalCost; 62 | 63 | FTokenCostStats() : TotalTokens(0), TotalCost(0.0f) {} 64 | }; 65 | 66 | static TMap LogObjectTokenCosts; 67 | 68 | private: 69 | /** Handles image requests coming from the web */ 70 | static void HandleImageRequest(FHttpRequestPtr HttpRequest, const FHttpResponsePtr& HttpResponse, bool bSucceeded, const FTAImageDownloadedDelegate & OnDownloadComplete, const FTAImageDownloadedDelegate & OnDownloadFailed); 71 | }; 72 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/Dialogue/TADialogueInstance.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/NoExportTypes.h" 7 | #include "TimerManager.h" 8 | #include "TADialogueInstance.generated.h" 9 | 10 | struct FChatCompletion; 11 | struct FChatLog; 12 | // 声明一个枚举,用于描述对话的状态 13 | UENUM(BlueprintType) 14 | enum class EDialogueState : uint8 15 | { 16 | WaitingForParticipants UMETA(DisplayName = "WaitingForParticipants"), 17 | Active UMETA(DisplayName = "Active"), 18 | WaitingForSomeOne UMETA(DisplayName = "WaitingForSomeOne"), 19 | End UMETA(DisplayName = "End"), 20 | }; 21 | 22 | /** 23 | * DialogueInstance represents an ongoing dialogue environment with multiple participants 24 | */ 25 | UCLASS(Blueprintable) 26 | class TOBENOTLLMGAMEPLAY_API UTADialogueInstance : public UObject 27 | { 28 | GENERATED_BODY() 29 | 30 | public: 31 | UTADialogueInstance(); 32 | 33 | // 唯一标识符 34 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Dialogue Instance") 35 | FGuid DialogueId; 36 | 37 | // 参与对话的Actors列表 38 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Dialogue Instance") 39 | TArray Participants; 40 | 41 | // 对话的历史记录 42 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Dialogue Instance") 43 | TArray DialogueHistory; 44 | 45 | // 对话的状态 46 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Dialogue Instance") 47 | EDialogueState DialogueState; 48 | 49 | // 添加参与者 50 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 51 | void AddParticipant(AActor* Participant); 52 | 53 | // 移除参与者 54 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 55 | void RemoveParticipant(AActor* Participant); 56 | 57 | // 尝试更新对话状态 58 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 59 | void TryEnterDialogueState(EDialogueState NewState); 60 | 61 | // 添加消息到历史记录并触发消息分发 62 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 63 | void ReceiveMessage(const FChatCompletion& Message, AActor* Sender); 64 | 65 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 66 | void RefuseToSay(AActor* Sender); 67 | 68 | UFUNCTION() 69 | void CycleParticipants(); 70 | 71 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 72 | void EndDialogue(); 73 | 74 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 75 | TArray GetParticipantNamesFromAgents() const; 76 | 77 | UFUNCTION(BlueprintCallable, Category = "Dialogue Instance") 78 | FString GetParticipantsNamesStringFromAgents() const; 79 | 80 | private: 81 | // 内部方法用于添加消息到历史记录和分发消息给参与者 82 | void AddMessageToHistory(const FChatLog& Message, AActor* Sender); 83 | void DistributeMessage(const FChatCompletion& Message, AActor* Sender); 84 | 85 | FTimerHandle DialogueTimerHandle; 86 | int32 CurrentParticipantIndex; 87 | protected: 88 | virtual void BeginDestroy() override; 89 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Scene/TAInteractiveActor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Scene/TAInteractiveActor.h" 5 | 6 | #include "TASettings.h" 7 | #include "Chat/TAFunctionInvokeComponent.h" 8 | #include "Chat/Dialogue/TADialogueComponent.h" 9 | #include "Components/SphereComponent.h" 10 | 11 | void ATAInteractiveActor::OnConstruction(const FTransform& Transform) 12 | { 13 | Super::OnConstruction(Transform); 14 | 15 | // 初始化InteractionComponent 16 | UClass* InteractionComponentClass = nullptr; 17 | const UTASettings* Settings = GetDefault(); 18 | if (Settings) 19 | { 20 | InteractionComponentClass = Settings->InteractionComponentClass.TryLoadClass(); 21 | } 22 | 23 | // 如果没有指定类或者类加载失败,使用默认的InteractionComponentClass类 24 | if (!InteractionComponentClass) 25 | { 26 | InteractionComponentClass = UTAInteractionComponent::StaticClass(); 27 | } 28 | 29 | InteractionComponent = NewObject(this, InteractionComponentClass, TEXT("InteractionComponent")); 30 | if (InteractionComponent) 31 | { 32 | InteractionComponent->RegisterComponent(); // 注册组件到Actor 33 | InteractionComponent->InitializeComponent(); // 调用自定义的初始化方法 34 | } 35 | } 36 | 37 | ATAInteractiveActor::ATAInteractiveActor() 38 | { 39 | // 初始化一个简单的SceneComponent作为根组件 40 | RootSceneComponent = CreateDefaultSubobject(TEXT("RootSceneComponent")); 41 | SetRootComponent(RootSceneComponent); 42 | 43 | // 初始化碰撞组件,并将其附加到根组件 44 | CollisionComponent = CreateDefaultSubobject(TEXT("CollisionComponent")); 45 | CollisionComponent->SetupAttachment(RootComponent); 46 | CollisionComponent->SetCollisionProfileName(TEXT("WorldDynamic")); // 设置为世界动态的碰撞预设 47 | CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); 48 | CollisionComponent->CanCharacterStepUpOn = ECB_No; 49 | 50 | // 初始化静态Mesh组件,并将其附加到碰撞组件 51 | DisplayMeshComponent = CreateDefaultSubobject(TEXT("DisplayMeshComponent")); 52 | DisplayMeshComponent->SetupAttachment(CollisionComponent); 53 | DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); // 如果只用于显示不参与碰撞,可以禁用碰撞 54 | 55 | // 初始化ChatComponent 56 | ChatComponent = CreateDefaultSubobject(TEXT("ChatComponent")); 57 | ChatComponent->bEnableFunctionInvoke = true; 58 | 59 | DialogueComponent = CreateDefaultSubobject(TEXT("DialogueComponent")); 60 | DialogueComponent->bEnableFunctionInvoke = true; 61 | //FunctionInvokeComponent = CreateDefaultSubobject(TEXT("FunctionInvokeComponent")); 62 | } 63 | 64 | void ATAInteractiveActor::BeginPlay() 65 | { 66 | Super::BeginPlay(); 67 | } 68 | 69 | FString ATAInteractiveActor::GetSystemPrompt() 70 | { 71 | return InteractionComponent->GetFullPrompt(); 72 | } 73 | 74 | const FString& ATAInteractiveActor::GetAgentName() const 75 | { 76 | return InteractionComponent->GetInteractableName(); 77 | } 78 | 79 | // getter函数 80 | UTAInteractionComponent* ATAInteractiveActor::GetInteractionComponent() const 81 | { 82 | return InteractionComponent; 83 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Common/TAEmbeddingSystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "OpenAIDefinitions.h" 7 | #include "Subsystems/GameInstanceSubsystem.h" 8 | #include "TAEmbeddingSystem.generated.h" 9 | 10 | const int32 MaxRetryCount = 3; 11 | class UOpenAIEmbedding; 12 | 13 | UENUM(BlueprintType) 14 | enum class ETagEmbeddingStatus : uint8 15 | { 16 | NotEmbedded, // 未词嵌 17 | Embedding, // 正在词嵌 18 | Embedded // 已经被词嵌 19 | }; 20 | 21 | /** 22 | * 词嵌数据结构,存储标签及其对应的词嵌结果 23 | */ 24 | USTRUCT(BlueprintType) 25 | struct FTagEmbeddingData 26 | { 27 | GENERATED_BODY() 28 | 29 | // 标签,使用FName而不是FString以提高效率 30 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 31 | FName Tag; 32 | 33 | // 词嵌向量 34 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 35 | FHighDimensionalVector EmbeddingVector = FHighDimensionalVector(); 36 | 37 | // 标签的词嵌状态 38 | UPROPERTY(EditAnywhere, BlueprintReadOnly) 39 | ETagEmbeddingStatus Status = ETagEmbeddingStatus::NotEmbedded; 40 | }; 41 | 42 | USTRUCT() 43 | struct FEmbeddingArchive 44 | { 45 | GENERATED_BODY() 46 | 47 | UPROPERTY() 48 | FName Tag; 49 | 50 | UPROPERTY() 51 | FHighDimensionalVector EmbeddingVector; 52 | 53 | UPROPERTY() 54 | FString ModelName; 55 | 56 | friend FArchive& operator<<(FArchive& Ar, FEmbeddingArchive& ArchiveData) 57 | { 58 | Ar << ArchiveData.Tag; 59 | Ar << ArchiveData.EmbeddingVector; 60 | Ar << ArchiveData.ModelName; 61 | return Ar; 62 | } 63 | }; 64 | 65 | /** 66 | * 词嵌系统基类,继承自游戏实例子系统 67 | */ 68 | UCLASS() 69 | class UTAEmbeddingSystem : public UGameInstanceSubsystem 70 | { 71 | GENERATED_BODY() 72 | 73 | public: 74 | // 请求一个Tag的词嵌 75 | // 调用它时,如果Tag还未嵌入完成,会返回false并开始嵌入过程 76 | bool GetTagEmbedding(const FName& Tag, FHighDimensionalVector& OutEmbeddingVec); 77 | 78 | // 请求词嵌的接口 79 | UOpenAIEmbedding* SendEmbeddingToOpenAIWithRetry(const FEmbeddingSettings& EmbeddingSettings, TFunction Callback, const UObject* LogObject, const int32 NewRetryCount = MaxRetryCount); 80 | 81 | // 检索一个标签的词嵌状态 82 | UFUNCTION(BlueprintCallable, Category = "Embedding") 83 | ETagEmbeddingStatus GetTagEmbeddingStatus(const FName& Tag) const; 84 | 85 | // 检索一个标签的词嵌数据 86 | UFUNCTION(BlueprintCallable, Category = "Embedding") 87 | FTagEmbeddingData GetTagEmbeddingData(const FName& Tag) const; 88 | 89 | // 计算两个词嵌向量之间的余弦相似度 90 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Embedding") 91 | static float CalculateCosineSimilarity(const FHighDimensionalVector& VectorA, const FHighDimensionalVector& VectorB); 92 | 93 | protected: 94 | 95 | // 存储所有标签及其词嵌数据,使用FName作为键 96 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly) 97 | TMap EmbeddingsCache; 98 | 99 | private: 100 | bool bHasTagEmbedding = false; 101 | FString GetSaveFilePath(const FName& Tag, const FString& ModelName) const; 102 | bool SaveEmbeddingToArchive(const FName& Tag, const FHighDimensionalVector& EmbeddingVector, const FString& ModelName); 103 | bool LoadEmbeddingFromArchive(const FName& Tag, const FString& ModelName, FHighDimensionalVector& OutEmbeddingVector); 104 | }; 105 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Core/TAEventInstance.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Event/Core/TAEventInstance.h" 5 | 6 | #include "Event/Core/TAEventSubsystem.h" 7 | #include "Agent/TAAgentInterface.h" 8 | #include "Event/TAEventLogCategory.h" 9 | #include "Save/TASaveGameSubsystem.h" 10 | #include "Scene/TASceneSubsystem.h" 11 | #include "Chat/Shout/TAShoutComponent.h" 12 | 13 | void UTAEventInstance::TriggerEvent() 14 | { 15 | if(bTriggered) 16 | { 17 | UE_LOG(LogTAEventSystem, Warning, TEXT("TriggerEvent 尝试激活一个事件两次")); 18 | return; 19 | } 20 | bTriggered = true; 21 | 22 | // 在控制台和屏幕上打印事件信息 23 | if (GEngine) 24 | { 25 | FString Message = EventInfo.ToString(); 26 | 27 | // 在屏幕上显示消息 28 | GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, Message); 29 | 30 | // 在控制台打印消息 31 | UE_LOG(LogTAEventSystem, Log, TEXT("%s"), *Message); 32 | } 33 | 34 | UWorld* World = GetWorld(); 35 | if (!World) 36 | { 37 | UE_LOG(LogTAEventSystem, Error, TEXT("TriggerEvent 无法获取当前世界")); 38 | return; 39 | } 40 | 41 | UTASceneSubsystem* SceneSubsystem = World->GetSubsystem(); 42 | if (!SceneSubsystem) 43 | { 44 | UE_LOG(LogTAEventSystem, Error, TEXT("TriggerEvent 无法获取SceneSubsystem")); 45 | return; 46 | } 47 | 48 | if(EventInfo.ActivationType == EEventActivationType::Proximity) 49 | { 50 | SceneSubsystem->CreateAndLoadAreaScene(EventInfo); 51 | } 52 | 53 | AssignDesiresToAgent(); 54 | } 55 | 56 | void UTAEventInstance::OnEventFinished(int32 OutcomeID) 57 | { 58 | RevokeAgentDesires(); 59 | } 60 | 61 | void UTAEventInstance::AssignDesiresToAgent() 62 | { 63 | UWorld* World = GetWorld(); 64 | if(World) 65 | { 66 | if (UTASaveGameSubsystem* SaveGameSubsystem = World->GetGameInstance()->GetSubsystem()) 67 | { 68 | UE_LOG(LogTAEventSystem, Log, TEXT("事件 [%s] 开始分配欲望"), *EventInfo.PresetData.EventName); 69 | 70 | for (const FTAAgentDesire& Desire : EventInfo.PresetData.AgentDesires) 71 | { 72 | AActor* FoundActor = SaveGameSubsystem->FindActorByName(Desire.AgentName); 73 | if (FoundActor) 74 | { 75 | ITAAgentInterface* AgentActor = Cast(FoundActor); 76 | if (AgentActor) 77 | { 78 | FGuid DesireGUID = FGuid::NewGuid(); 79 | 80 | // 这里添加了事件ID和名字到欲望描述 81 | FString DesireWithEventID = FString::Printf(TEXT("[EventID:%d] %s"), EventInfo.PresetData.EventID, *Desire.DesireDescription); 82 | AgentActor->AddOrUpdateDesire(DesireGUID, DesireWithEventID); 83 | 84 | if(Desire.ImmediatelyWantToSpeak) 85 | { 86 | UTAShoutComponent* ShoutComponent = FoundActor->FindComponentByClass(); 87 | if(ShoutComponent) 88 | { 89 | ShoutComponent->RequestToSpeakCheckSurrounding(); 90 | } 91 | } 92 | 93 | DesireAgentMap.Add(DesireGUID, FoundActor); 94 | 95 | UE_LOG(LogTAEventSystem, Log, TEXT("事件 [%s] 分配给 [%s] 的欲望 : %s"), *EventInfo.PresetData.EventName, *Desire.AgentName.ToString(), *DesireWithEventID); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | void UTAEventInstance::RevokeAgentDesires() 104 | { 105 | UE_LOG(LogTAEventSystem, Log, TEXT("事件 [%s] 开始撤销欲望"), *EventInfo.PresetData.EventName); 106 | 107 | for (const auto& Pair : DesireAgentMap) 108 | { 109 | const FGuid& DesireGUID = Pair.Key; 110 | AActor* FoundActor = Pair.Value; 111 | if (FoundActor) 112 | { 113 | ITAAgentInterface* AgentActor = Cast(FoundActor); 114 | if (AgentActor) 115 | { 116 | AgentActor->RemoveDesire(DesireGUID); 117 | UE_LOG(LogTAEventSystem, Log, TEXT("事件 [%s] 分配给 [%s] 的欲望 已撤销"), *EventInfo.PresetData.EventName, *AgentActor->GetAgentName()); 118 | } 119 | } 120 | } 121 | 122 | DesireAgentMap.Empty(); 123 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/Dialogue/TADialogueComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | #include "Common/TAPromptDefinitions.h" 8 | #include "TADialogueComponent.generated.h" 9 | 10 | struct FChatCompletion; 11 | class UTADialogueInstance; 12 | struct FChatLog; 13 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnDialogueReceivedMessageUpdated, const FChatCompletion&, ReceivedMessage, AActor*, Sender); 14 | 15 | UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 16 | class TOBENOTLLMGAMEPLAY_API UTADialogueComponent : public UActorComponent 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // 这个委托的sender意思是真正发送ReceivedMessage的人,不是促使它发送ReceivedMessage的人,注意区分 22 | UPROPERTY(BlueprintAssignable, Category = "TADialogueComponent") 23 | FOnDialogueReceivedMessageUpdated OnDialogueReceivedMessage; 24 | 25 | // Constructor for the dialogue component 26 | UTADialogueComponent(); 27 | 28 | // Static function to get dialogue component from an actor 29 | static UTADialogueComponent* GetTADialogueComponent(AActor* Actor); 30 | 31 | // Functions related to dialogue instance 32 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 33 | void SetCurrentDialogue(UTADialogueInstance* NewDialogueInstance); 34 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 35 | UTADialogueInstance* GetCurrentDialogueInstance() const{return CurrentDialogueInstance;}; 36 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 37 | void HandleReceivedMessage(const FChatCompletion& ReceivedMessage, AActor* Sender); 38 | 39 | // Functions related to chat log history 40 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 41 | TArray GetDialogueHistory() const{return DialogueHistory;}; 42 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 43 | FString GetDialogueHistoryCompressedStr() const{return DialogueHistoryCompressedStr;}; 44 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 45 | void SendMessageToDialogue(const FChatCompletion& Message); 46 | UFUNCTION(BlueprintCallable, Category = "TADialogueComponent") 47 | void RequestToSpeak(); 48 | void RefuseToSay(); 49 | 50 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TADialogueComponent") 51 | bool IsPlayer; 52 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TADialogueComponent") 53 | bool IsRequestingMessage; 54 | protected: 55 | // Begins play for the component 56 | virtual void BeginPlay() override; 57 | 58 | private: 59 | // Current dialogue instance 60 | UPROPERTY() 61 | UTADialogueInstance* CurrentDialogueInstance; 62 | 63 | // Chat log history within the component 64 | UPROPERTY() 65 | TArray DialogueHistory; 66 | 67 | UPROPERTY() 68 | TArray FullDialogueHistory; 69 | 70 | FString DialogueHistoryCompressedStr; 71 | 72 | // Handles updating the chat history 73 | void UpdateDialogueHistory(const FChatCompletion& NewChatLog); 74 | 75 | // Notifies UI of dialogue history update 76 | void NotifyUIOfDialogueHistoryUpdate(const FChatCompletion& ReceivedMessage, AActor* Sender); 77 | 78 | FString GetSystemPromptFromOwner() const; 79 | 80 | private: 81 | UPROPERTY() 82 | class UOpenAIChat* CacheChat; 83 | 84 | public: 85 | // 根据大语言模型的响应来执行游戏中的行为,默认关闭,需要继承UTAFunctionInvokeComponent做支持 86 | UPROPERTY() 87 | bool bEnableFunctionInvoke = false; 88 | 89 | UFUNCTION(BlueprintCallable, Category = "Chat") 90 | void PerformFunctionInvokeBasedOnResponse(const FString& Response); 91 | 92 | public: 93 | UFUNCTION(BlueprintCallable, Category = "Chat") 94 | bool GetAcceptMessages() const{return bAcceptMessages;} 95 | 96 | UFUNCTION(BlueprintCallable, Category = "Chat") 97 | void SetAcceptMessages(bool bInAcceptMessages); 98 | 99 | private: 100 | // 是否接受消息的标志位 101 | bool bAcceptMessages = true; 102 | 103 | public: 104 | UFUNCTION(BlueprintCallable, Category = "Chat") 105 | void RequestDialogueCompression(); 106 | 107 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "TADialogueComponent") 108 | bool bEnableCompressDialogue = true; 109 | 110 | private: 111 | bool bIsCompressingDialogue = false; 112 | int32 LastCompressedIndex; 113 | 114 | public: 115 | static const FTAPrompt PromptCompressDialogueHistory; 116 | 117 | FString JoinDialogueHistory(); 118 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Plot/TAPlotManager.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Common/TAPromptDefinitions.h" 7 | 8 | #include "Subsystems/WorldSubsystem.h" 9 | #include "TAPlotManager.generated.h" 10 | 11 | struct FTAEventInfo; 12 | struct FHighDimensionalVector; 13 | struct FChatLog; 14 | struct FChatCompletion; 15 | /** 16 | * Structure to represent a group of FName tags. 17 | */ 18 | USTRUCT(BlueprintType) 19 | struct FTATagGroup 20 | { 21 | GENERATED_BODY() 22 | 23 | // An array of FName tags, forming the tag group. 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Plot Tags") 25 | TArray Tags; 26 | 27 | // can use for logic/cache, 在事件配置里,其表示第几个tag必须是action_tag,标0就是没这个要求。标1就是第一个tag必须在action_tag里面被匹配到 28 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Plot Tags") 29 | int FlagIndex = 0; 30 | 31 | // can use for logic/cache, 在事件配置里,false表示或,true表示与,一个事件要满足前置,必须全部与组匹配,且如果有或组的话,至少有一个或组匹配 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Plot Tags") 33 | bool Flag = false; 34 | 35 | FTATagGroup() {} 36 | }; 37 | 38 | /** 39 | * UTAPlotManager 40 | * 41 | * This class serves as a subsystem to manage the narrative event tagging and triggering 42 | * within the game world. It listens to in-game events, processes them to generate tag groups, 43 | * and checks for prerequisites of events that could be triggered as a consequence. 44 | */ 45 | 46 | UCLASS() 47 | class TOBENOTLLMGAMEPLAY_API UTAPlotManager : public UWorldSubsystem 48 | { 49 | GENERATED_BODY() 50 | 51 | public: 52 | // Constructor and destructor 53 | UTAPlotManager(); 54 | virtual ~UTAPlotManager(); 55 | 56 | // Initializes the subsystem 57 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 58 | 59 | // Deinitializes the subsystem 60 | virtual void Deinitialize() override; 61 | 62 | // Listens to in-game events and processes them 63 | // 对话上的直接监听,监听UTAShoutManager 的回调, 监听到的是剧情内容(也就是发生了什么事) 64 | UFUNCTION(BlueprintCallable, Category="Plot Management") 65 | void ProcessShoutInGame(const FChatCompletion& Message, AActor* Shouter, float Volume); 66 | 67 | // Adds a new event to be tracked for potential triggering 68 | // 机制上的,TAPlotManager自己提供上报接口,别的地方可以调用,以直接上报对应PlotTag 69 | UFUNCTION(BlueprintCallable, Category="Plot Management") 70 | void ReportEventTagGroups(const FTATagGroup& EventTagGroups); 71 | 72 | // TAPlotManager提供发起检测的接口,让别的系统定时调用。 73 | // Checks for event prerequisites and triggers them if satisfied 74 | void CheckEventsTagGroupCondition(TArray& Events); 75 | 76 | protected: 77 | UPROPERTY() 78 | TArray PlotTagGroups; 79 | 80 | // Internal function to parse events and categorize them into tag groups 81 | // 监听到的剧情内容,让transformer大语言模型根据上下文,拆分成多个标签组(这是一个异步过程,学习UTAShoutComponent使用SendMessageToOpenAIWithRetry的方法) 82 | // 每个标签组串起来能表达结果含义(遵循先行动者(即主动方或触发方)后结果(即影响或状态变化)的原则) 83 | // 让gpt用数组形式回复,注意标签顺序,这些标签就是PlotTag 84 | /* 如果事件有多个结果,那么就产生多个记录。 85 | 每一个记录本身应当只表达一件事。 86 | 所有的标签组PlotTag组成事件记录(Plot记录)。 87 | */ 88 | void ParseNewEventToTagGroups(); 89 | 90 | //在标签组记录录入的时候,调用嵌入模型对所有的标签进行词嵌(缓存单个标签的词嵌结果,这样子大量的重复记录不会重复进行词嵌) 91 | // 获取tag的嵌入,调用它时,如果Tag还未嵌入完成,会返回false并开始嵌入过程,此时请跳过处理。 92 | bool GetTagEmbeddingsFromSystem(const FName& Tag, FHighDimensionalVector& OutEmbeddingVec); 93 | 94 | static const FTAPrompt PromptTagEvent; 95 | 96 | private: 97 | // 这个就是上下文缓存 和上下文压缩相关的代码 98 | UPROPERTY() 99 | TArray ShoutHistory; 100 | 101 | UPROPERTY() 102 | TArray FullShoutHistory; 103 | 104 | FString ShoutHistoryCompressedStr; 105 | 106 | void RequestShoutCompression(); 107 | 108 | bool bIsCompressingShout = false; 109 | int32 LastCompressedIndex; 110 | 111 | public: 112 | static const FTAPrompt PromptCompressShoutHistory; 113 | 114 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Compress History") 115 | bool bEnableCompressShout = true; 116 | 117 | FString JoinShoutHistory(); 118 | 119 | private: 120 | TSet PrintedLogPairs; 121 | TMap, float> SimilarityCache; 122 | 123 | public: 124 | float GetCachedCosineSimilarity(FName TagA, FName TagB, const FHighDimensionalVector& VectorA, const FHighDimensionalVector& VectorB); 125 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Event/Data/TAEventInfo.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAEventInfo.h 4 | // 存储事件的基本信息 5 | 6 | #pragma once 7 | 8 | #include "CoreMinimal.h" 9 | 10 | #include "TAEventInfo.generated.h" 11 | 12 | 13 | struct FTATagGroup; 14 | // 定义事件类型枚举 15 | UENUM(BlueprintType) 16 | enum class ETAEventType : uint8 17 | { 18 | Combat UMETA(DisplayName = "普通战"), 19 | BossFight UMETA(DisplayName = "BOSS战"), 20 | Exploration UMETA(DisplayName = "探索"), 21 | Story UMETA(DisplayName = "剧情"), 22 | Other UMETA(DisplayName = "其他"), 23 | }; 24 | 25 | // 定义事件激活类型枚举 26 | UENUM(BlueprintType) 27 | enum class EEventActivationType : uint8 28 | { 29 | Proximity UMETA(DisplayName = "靠近位点触发"), 30 | Geographic UMETA(DisplayName = "地理信息触发"), 31 | PlotProgress UMETA(DisplayName = "剧情进展触发"), 32 | }; 33 | 34 | // Agent要满足的条件 35 | USTRUCT(BlueprintType) 36 | struct FTAAgentCondition 37 | { 38 | GENERATED_BODY() 39 | 40 | // Agent的名称,唯一标识一个对象 41 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Condition") 42 | FName AgentName; 43 | 44 | // Agent需满足的状态条件,可能包括和其他Agent的距离,血量健康状态等 45 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Condition") 46 | FString RequiredState; 47 | }; 48 | 49 | // Agent的欲望信息 50 | USTRUCT(BlueprintType) 51 | struct FTAAgentDesire 52 | { 53 | GENERATED_BODY() 54 | 55 | // 欲望的唯一标识FGuid(用于追踪和取消)是在添加时决定的 56 | 57 | // Agent的名称,唯一标识一个对象 58 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Desire") 59 | FName AgentName; 60 | 61 | // 欲望描述和对话内容 62 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Desire") 63 | FString DesireDescription; 64 | 65 | // 获得这个欲望后是否马上想说话 66 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Desire") 67 | bool ImmediatelyWantToSpeak = false; 68 | }; 69 | 70 | USTRUCT(BlueprintType) 71 | struct FTAEventDependency 72 | { 73 | GENERATED_BODY() 74 | 75 | // 前置事件ID 76 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Dependency") 77 | int32 PrecedingEventID; 78 | 79 | // 前置事件需要达到的结果ID 80 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Dependency") 81 | int32 RequiredOutcomeID; 82 | }; 83 | 84 | // 定义预设事件数据结构体 85 | USTRUCT(BlueprintType) 86 | struct TOBENOTLLMGAMEPLAY_API FTAPresetEventData : public FTableRowBase 87 | { 88 | GENERATED_BODY() 89 | 90 | // 事件ID 91 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 92 | int32 EventID; 93 | 94 | // 事件名称 95 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 96 | FString EventName; 97 | 98 | // 事件触发地名(如水井、草丛) 99 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 100 | FString LocationName; 101 | 102 | // 事件描述 103 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 104 | FString Description; 105 | 106 | // 事件类型 107 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 108 | ETAEventType EventType; 109 | 110 | // 事件权重 111 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 112 | int32 Weight; 113 | 114 | // 事件的特殊点,用于生成交互物 115 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 116 | FString PeculiarPoint; 117 | 118 | // 关联的Agent触发条件 119 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Dependency") 120 | TArray AgentConditions; 121 | 122 | // 关联的Agent欲望列表 123 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 124 | TArray AgentDesires; 125 | 126 | // 前置事件和结果的数组 127 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Dependency") 128 | TArray PrecedingEvents; 129 | 130 | // 前置的事件记录标签组 131 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event Dependency") 132 | TArray PrecedingPlotTagGroups; 133 | }; 134 | 135 | // 更新FTAEventInfo结构体,包含FTAPresetEventData 136 | USTRUCT(BlueprintType) 137 | struct FTAEventInfo 138 | { 139 | GENERATED_BODY() 140 | 141 | // 保存地点的唯一标识符 142 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 143 | FGuid LocationGuid; 144 | 145 | // 事件触发方式 146 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 147 | EEventActivationType ActivationType; 148 | 149 | // LLM start 150 | 151 | // 包含预设事件数据 152 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 153 | FTAPresetEventData PresetData; 154 | 155 | // LLM end 156 | 157 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Event") 158 | bool PrecedingPlotTagGroupsConditionMet = false; 159 | 160 | // 事件转换为字符串的函数,用于调试打印输出 161 | FString ToString() const; 162 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Common/TATargetComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot 2 | // This code is licensed under the MIT License. See LICENSE in the project root for license information. 3 | 4 | #include "Common/TATargetComponent.h" 5 | 6 | #include "Agent/TAAgentInterface.h" 7 | 8 | DEFINE_LOG_CATEGORY_STATIC(LogTATarget, Log, All); 9 | 10 | // 构造函数 11 | UTATargetComponent::UTATargetComponent() 12 | { 13 | PrimaryComponentTick.bCanEverTick = false; 14 | } 15 | 16 | // 调用BeginPlay时,进行初始化 17 | void UTATargetComponent::BeginPlay() 18 | { 19 | Super::BeginPlay(); 20 | 21 | // 现在不更新目标了 22 | return; 23 | UpdateNearbyTargets(); 24 | StartTargetCheckTimer(); 25 | } 26 | 27 | // 轮选下一个目标 28 | void UTATargetComponent::SelectNextTarget() 29 | { 30 | CycleToNextTarget(); 31 | } 32 | 33 | // 获取当前选中的目标 34 | AActor* UTATargetComponent::GetCurrentTarget() const 35 | { 36 | return CurrentTarget; 37 | } 38 | 39 | // 获取附近的可交互目标列表 40 | const TArray& UTATargetComponent::GetNearbyTargets() const 41 | { 42 | return NearbyTargets; 43 | } 44 | // 检查目标列表是否有变化的辅助函数 45 | bool UTATargetComponent::HasTargetListChanged(const TArray& OldList, const TArray& NewList) const 46 | { 47 | if (OldList.Num() != NewList.Num()) 48 | { 49 | return true; 50 | } 51 | 52 | for (AActor* Actor : NewList) 53 | { 54 | if (!OldList.Contains(Actor)) 55 | { 56 | return true; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | // 更新附近目标列表 64 | void UTATargetComponent::UpdateNearbyTargets() 65 | { 66 | TArray OldNearbyTargets = NearbyTargets; 67 | 68 | // 清空当前的目标列表 69 | NearbyTargets.Empty(); 70 | 71 | // 获取Actor的位置 72 | FVector Location = GetOwner()->GetActorLocation(); 73 | 74 | // 定义碰撞查询参数 75 | FCollisionQueryParams QueryParams; 76 | QueryParams.AddIgnoredActor(GetOwner()); // 忽略自己 77 | QueryParams.bReturnPhysicalMaterial = false; 78 | 79 | // 定义碰撞形状,这里使用球形 80 | FCollisionShape CollisionShape; 81 | CollisionShape.SetSphere(SearchRadius); 82 | 83 | // 执行重叠检查,这里我们使用所有的Actor类型 84 | TArray OverlapResults; 85 | GetWorld()->OverlapMultiByChannel( 86 | OverlapResults, 87 | Location, 88 | FQuat::Identity, 89 | ECollisionChannel::ECC_WorldDynamic, // 注意:这里可能需要修改为合适的碰撞通道 90 | CollisionShape, 91 | QueryParams 92 | ); 93 | 94 | // 遍历重叠结果 95 | for (const FOverlapResult& Result : OverlapResults) 96 | { 97 | AActor* PotentialTarget = Result.GetActor(); 98 | if (PotentialTarget 99 | && !PotentialTarget->ActorHasTag(FName("CanNotBeTargeted")) 100 | && !NearbyTargets.Contains(PotentialTarget) 101 | && IsValid(PotentialTarget)) 102 | { 103 | // 检查目标是否实现了ITAAgentInterface接口 104 | ITAAgentInterface* AgentInterface = Cast(PotentialTarget); 105 | if(AgentInterface) 106 | { 107 | // 将实现了接口的潜在目标添加到列表中 108 | NearbyTargets.Add(PotentialTarget); 109 | } 110 | } 111 | } 112 | 113 | if (HasTargetListChanged(OldNearbyTargets, NearbyTargets)) 114 | { 115 | // 如果列表发生了变化,则触发目标列表更新事件 116 | OnTargetListUpdated.Broadcast(); 117 | } 118 | 119 | // 检查当前选中的目标是否仍在更新后的NearbyTargets数组中 120 | if (CurrentTarget && !NearbyTargets.Contains(CurrentTarget)) 121 | { 122 | // 如果当前选中的目标不在范围内,则取消选中 123 | CurrentTarget = nullptr; 124 | OnTargetChanged.Broadcast(CurrentTarget); 125 | } 126 | // 能轮空的话,不自动取消目标 127 | else if (!bEmptyBeforeCycle && !CurrentTarget && NearbyTargets.Num() > 0) 128 | { 129 | // 选取第一个实现了接口的Actor作为当前目标 130 | CurrentTarget = NearbyTargets[0]; 131 | OnTargetChanged.Broadcast(CurrentTarget); 132 | } 133 | } 134 | 135 | // 轮选逻辑的辅助函数 136 | void UTATargetComponent::CycleToNextTarget() 137 | { 138 | int32 CurrentIndex = NearbyTargets.IndexOfByKey(CurrentTarget); 139 | 140 | // 检查是否需要在轮选前置空 141 | if (bEmptyBeforeCycle && CurrentIndex == NearbyTargets.Num() - 1) 142 | { 143 | // 先置空当前目标 144 | CurrentTarget = nullptr; 145 | OnTargetChanged.Broadcast(CurrentTarget); 146 | // 下一次调用时将从第一个目标开始 147 | return; 148 | } 149 | 150 | // 如果没有目标或者当前目标是列表中的最后一个 151 | if (CurrentIndex == INDEX_NONE || CurrentIndex == NearbyTargets.Num() - 1) 152 | { 153 | // 选择列表中的第一个目标 154 | CurrentTarget = NearbyTargets.Num() > 0 ? NearbyTargets[0] : nullptr; 155 | } 156 | else 157 | { 158 | // 否则选择下一个目标 159 | CurrentTarget = NearbyTargets[++CurrentIndex]; 160 | } 161 | 162 | // 触发目标变更事件 163 | OnTargetChanged.Broadcast(CurrentTarget); 164 | } 165 | 166 | // 设置检测目标的计时器 167 | void UTATargetComponent::StartTargetCheckTimer() 168 | { 169 | // 设置定时器以每0.3秒调用UpdateNearbyTargets函数 170 | GetWorld()->GetTimerManager().SetTimer(TargetingCheckTimerHandle, this, &UTATargetComponent::UpdateNearbyTargets, 0.3f, true); 171 | } 172 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/TAChatComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | #include "OpenAIDefinitions.h" 8 | #include "TAChatCallback.h" 9 | #include "TAChatComponent.generated.h" 10 | 11 | USTRUCT(BlueprintType) 12 | struct FTAActorChatHistory 13 | { 14 | GENERATED_BODY() 15 | 16 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Chat") 17 | TArray ChatHistory; 18 | }; 19 | 20 | USTRUCT(BlueprintType) 21 | struct FTAChatComponentSaveData 22 | { 23 | GENERATED_BODY() 24 | 25 | // 保存所有Actor的聊天历史映射 26 | UPROPERTY(BlueprintReadWrite, Category = "SaveData") 27 | TMap SavedActorChatHistoryMap; 28 | }; 29 | USTRUCT() 30 | struct FTAActorMessageQueue 31 | { 32 | GENERATED_BODY() 33 | 34 | UPROPERTY() 35 | TArray MessageQueue; 36 | 37 | UPROPERTY() 38 | UTAChatCallback* CallbackObject; 39 | }; 40 | 41 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMessageSent, const FChatCompletion&, Message); 42 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMessageSentWithSender, const FChatCompletion&, Message, AActor*, SenderCom); 43 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnMessageFailed); 44 | 45 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 46 | class TOBENOTLLMGAMEPLAY_API UTAChatComponent : public UActorComponent 47 | { 48 | GENERATED_BODY() 49 | 50 | public: 51 | // Sets default values for this component's properties 52 | UTAChatComponent(); 53 | 54 | protected: 55 | // Called when the game starts 56 | virtual void BeginPlay() override; 57 | 58 | public: 59 | UFUNCTION(BlueprintCallable, Category="TAChatWidget") 60 | static UTAChatComponent* GetTAChatComponent(AActor* Actor); 61 | 62 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LLM") 63 | bool ChatMessageJsonFormat = true; 64 | 65 | // Called every frame 66 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 67 | 68 | UPROPERTY() 69 | TMap ActorMessageQueueMap; 70 | 71 | UFUNCTION(BlueprintCallable, Category = "TAChatComponent") 72 | void SendMessageToOpenAI(AActor* OriActor, FString UserMessage, UTAChatCallback* CallbackObject, bool IsSystemMessage = false); 73 | 74 | UFUNCTION(BlueprintCallable, Category = "TAChatComponent") 75 | FString GetSystemPromptFromOwner() const; 76 | 77 | UFUNCTION(BlueprintCallable, Category = "TAChatComponent") 78 | TArray& GetChatHistoryWithActor(AActor* OtherActor); 79 | 80 | UFUNCTION(BlueprintCallable, Category = "TAChatComponent") 81 | void ClearChatHistoryWithActor(AActor* OtherActor); 82 | 83 | UPROPERTY(BlueprintAssignable, Category = "TAChatComponent") 84 | FOnMessageSent OnMessageSent; 85 | 86 | UPROPERTY(BlueprintAssignable, Category = "TAChatComponent") 87 | FOnMessageSentWithSender OnMessageSentWithSender; 88 | 89 | UPROPERTY(BlueprintAssignable, Category = "TAChatComponent") 90 | FOnMessageFailed OnMessageFailed; 91 | 92 | private: 93 | UFUNCTION() 94 | void HandleSuccessfulMessage(FChatCompletion Message,AActor* Sender); 95 | UFUNCTION() 96 | void HandleFailedMessage(); 97 | 98 | void ProcessMessage(AActor* OriActor, const FString& UserMessage, UTAChatCallback* CallbackObject, bool IsSystemMessage); 99 | void CheckMessageQueue(); 100 | 101 | UPROPERTY() 102 | TMap ActorChatHistoryMap; 103 | 104 | UPROPERTY() 105 | TMap CallbackMap; // 用于缓存 Callback 对象 106 | 107 | UPROPERTY() 108 | class UOpenAIChat* CacheChat; 109 | 110 | UPROPERTY() 111 | TSet ActiveActors; 112 | 113 | public: 114 | // 根据大语言模型的响应来执行游戏中的行为,默认关闭,需要继承UTAFunctionInvokeComponent做支持 115 | UPROPERTY() 116 | bool bEnableFunctionInvoke = false; 117 | 118 | UFUNCTION(BlueprintCallable, Category = "Chat") 119 | void PerformFunctionInvokeBasedOnResponse(const FString& Response); 120 | 121 | public: 122 | UFUNCTION(BlueprintCallable, Category = "ChatHistorySave") 123 | void SetChatHistoryData(const FTAChatComponentSaveData& NewData); 124 | 125 | UFUNCTION(BlueprintCallable, Category = "ChatHistorySave") 126 | FTAChatComponentSaveData GetChatHistoryData() const; 127 | 128 | public: 129 | // 标志位的 Getter 方法 130 | UFUNCTION(BlueprintCallable, Category = "Chat") 131 | bool GetAcceptMessages() const{return bAcceptMessages;} 132 | 133 | // 标志位的 Setter 方法 134 | UFUNCTION(BlueprintCallable, Category = "Chat") 135 | void SetAcceptMessages(bool bInAcceptMessages); 136 | 137 | private: 138 | // 是否接受消息的标志位 139 | bool bAcceptMessages = true; 140 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/Shout/TAShoutManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Chat/Shout/TAShoutManager.h" 4 | #include "Chat/Shout/TAShoutComponent.h" 5 | #include "OpenAIDefinitions.h" 6 | #include "Agent/TAAgentInterface.h" 7 | #include "Chat/TAChatLogCategory.h" 8 | #include "Dom/JsonObject.h" 9 | #include "Event/Plot/TAPlotManager.h" 10 | #include "Serialization/JsonSerializer.h" 11 | 12 | void UTAShoutManager::Initialize(FSubsystemCollectionBase& Collection) 13 | { 14 | // Initialization logic here (if necessary) 15 | } 16 | 17 | void UTAShoutManager::Deinitialize() 18 | { 19 | // Cleanup logic here (if necessary) 20 | } 21 | 22 | void UTAShoutManager::RegisterShoutComponent(UTAShoutComponent* Component) 23 | { 24 | if (Component && Component->GetOwner()) 25 | { 26 | RegisteredShoutComponents.Add(Component); 27 | } 28 | } 29 | 30 | void UTAShoutManager::UnregisterShoutComponent(UTAShoutComponent* Component) 31 | { 32 | if (Component && Component->GetOwner()) 33 | { 34 | RegisteredShoutComponents.Remove(Component); 35 | } 36 | } 37 | 38 | void UTAShoutManager::BroadcastShout(const FChatCompletion& Message, AActor* Shouter, float Volume) 39 | { 40 | TArray ComponentsInRange = GetShoutComponentsInRange(Shouter, Volume); 41 | 42 | // 目前发给其他人的消息只保留message字段 43 | FChatCompletion NewMessage = Message; 44 | bool bIsNewMessageCreated = false; 45 | 46 | TSharedPtr JsonObject; 47 | TSharedRef> Reader = TJsonReaderFactory<>::Create(Message.message.content); 48 | 49 | // 装载JSON和执行检查 50 | if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid() && JsonObject->HasField(TEXT("message"))) 51 | { 52 | FString MessageContent; 53 | JsonObject->TryGetStringField(TEXT("message"), MessageContent); 54 | 55 | TSharedPtr NewJsonMessage = MakeShareable(new FJsonObject()); 56 | NewJsonMessage->SetStringField(TEXT("message"), MessageContent); 57 | FString NewRawJson; 58 | TSharedRef> Writer = TJsonWriterFactory<>::Create(&NewRawJson); 59 | FJsonSerializer::Serialize(NewJsonMessage.ToSharedRef(), Writer); 60 | 61 | NewMessage.message.content = NewRawJson; 62 | bIsNewMessageCreated = true; 63 | } 64 | 65 | if(IsValidAgentName(Message.message.content, Shouter)) 66 | { 67 | for (UTAShoutComponent* Listener : ComponentsInRange) 68 | { 69 | if (Listener && Listener->IsActive()) 70 | { 71 | // 如果接收者不是发送者且新消息已被创建,则发送处理过的消息 72 | // 如果接收者是发送者,即使没有message字段,也应发送原始消息 73 | if(Listener->GetOwner() == Shouter) 74 | { 75 | Listener->HandleShoutReceived(Message, Shouter, Volume); 76 | } 77 | else if(bIsNewMessageCreated) 78 | { 79 | Listener->HandleShoutReceived(NewMessage, Shouter, Volume); 80 | } 81 | // 如果没有有效的message字段并且接收者不是发送者,不发送消息 82 | } 83 | } 84 | 85 | // 网状叙事系统监听只有message的消息 86 | UWorld* World = GetWorld(); 87 | UTAPlotManager* PlotManager = World->GetSubsystem(); 88 | if(PlotManager) 89 | { 90 | PlotManager->ProcessShoutInGame(NewMessage, Shouter, Volume); 91 | } 92 | } 93 | } 94 | 95 | TArray UTAShoutManager::GetShoutComponentsInRange(AActor* Shouter, float Range) 96 | { 97 | TArray ComponentsInRange; 98 | for (const auto& Comp : RegisteredShoutComponents) 99 | { 100 | if(!Comp->IsActive()) 101 | { 102 | continue; 103 | } 104 | const AActor* ListenerActor = Comp->GetOwner(); 105 | if (ListenerActor && Shouter && (ListenerActor->GetDistanceTo(Shouter) <= Range)) 106 | { 107 | ComponentsInRange.Add(Comp); 108 | } 109 | } 110 | return ComponentsInRange; 111 | } 112 | 113 | bool UTAShoutManager::IsValidAgentName(const FString& MessageContent, AActor* Shouter) const 114 | { 115 | const FString& MessageKey = TEXT("\"message\": \""); 116 | int32 MessageStartIndex = MessageContent.Find(MessageKey); 117 | if (MessageStartIndex > 0) 118 | { 119 | // 计算出实际消息内容的起始位置 120 | int32 ActualMessageStart = MessageStartIndex + MessageKey.Len(); 121 | 122 | const ITAAgentInterface* AgentInterface = Cast(Shouter); 123 | if (AgentInterface) 124 | { 125 | FString AgentName = AgentInterface->GetAgentName(); 126 | if(AgentInterface->IsVoiceover()) 127 | { 128 | UE_LOG(LogTemp, Log, TEXT("Is Voiceover, agent [%s], send message without name check."), *AgentName); 129 | return true; 130 | } 131 | // 截取与AgentName等长的字符串,用于比较 132 | FString AgentNameInContent = MessageContent.Mid(ActualMessageStart, AgentName.Len()).TrimStartAndEnd(); 133 | if (AgentNameInContent.Equals(AgentName, ESearchCase::IgnoreCase)) 134 | { 135 | return true; 136 | } 137 | else 138 | { 139 | UE_LOG(LogTemp, Warning, TEXT("Invalid Agent name in content: '%s' Expected: '%s'"), *AgentNameInContent, *AgentName); 140 | return false; 141 | } 142 | } 143 | } 144 | else 145 | { 146 | UE_LOG(LogTemp, Warning, TEXT("Cannot find key '\"message\": \"' in content: %s"), *MessageContent); 147 | return true; 148 | } 149 | 150 | // 如果没有AgentInterface,则默认消息有效 151 | return true; 152 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Image/TAImageGenerator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAImageGenerator.cpp 4 | 5 | #include "Image/TAImageGenerator.h" 6 | 7 | #include "IImageWrapperModule.h" 8 | #include "IImageWrapper.h" 9 | #include "Common/TALLMLibrary.h" 10 | 11 | #include "Engine/Texture2DDynamic.h" 12 | 13 | #include "Event/Core/TAEventPool.h" 14 | #include "Event/Core/TAEventSubsystem.h" 15 | #include "Scene/TAPlaceActor.h" 16 | #include "Save/TAGuidSubsystem.h" 17 | #include "Event/Data/TAEventInfo.h" 18 | 19 | UTAImageGenerator::UTAImageGenerator() 20 | { 21 | OnDownloadCompleteDelegate.AddDynamic(this, &UTAImageGenerator::OnDownloadComplete); 22 | OnDownloadFailedDelegate.AddDynamic(this, &UTAImageGenerator::OnDownloadFailed); 23 | } 24 | 25 | // 请求生成图片的实现 26 | void UTAImageGenerator::RequestGenerateImage(const FTAEventInfo& EventInfo) 27 | { 28 | // 将当前事件信息保存到这个对象,为了在回调中使用 29 | BoundEventID = EventInfo.PresetData.EventID; 30 | 31 | // 从 EventInfo 的描述出发,发起下载图片的异步请求 32 | // 注意:这里我们直接使用UTASystemLibrary提供的函数和委托类型 33 | CacheOpenAIChat = UTALLMLibrary::DownloadImageFromPollinations( 34 | EventInfo.PresetData.LocationName+' '+EventInfo.PresetData.Description, 35 | OnDownloadCompleteDelegate, OnDownloadFailedDelegate, this); 36 | } 37 | 38 | // 请求生成图片的实现 39 | void UTAImageGenerator::RequestGeneratePureImage(const FString& PureDescription) 40 | { 41 | UTALLMLibrary::DownloadImageFromPollinationsPure( 42 | PureDescription, 43 | OnDownloadCompleteDelegate, OnDownloadFailedDelegate, this); 44 | } 45 | 46 | void UTAImageGenerator::OnDownloadComplete(UTexture2DDynamic* Texture) 47 | { 48 | if (Texture != nullptr) 49 | { 50 | if(BoundEventID) 51 | { 52 | // 设置到位点上 53 | UTAEventSubsystem* EventSubsystem = GetWorld()->GetSubsystem(); 54 | if (EventSubsystem) 55 | { 56 | auto Pool = EventSubsystem->GetEventPool(); 57 | if(Pool) 58 | { 59 | bool bFlag; 60 | FTAEventInfo& BoundEventInfo = Pool->GetEventByID(BoundEventID,bFlag); 61 | if(bFlag) 62 | { 63 | UTAGuidSubsystem* GuidSubsystem = GetWorld()->GetSubsystem(); 64 | if(GuidSubsystem) 65 | { 66 | ATAPlaceActor* PlaceActor = Cast(GuidSubsystem->GetActorByGUID(BoundEventInfo.LocationGuid)); 67 | PlaceActor->SetPlaceTexture(Texture); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | #if !UE_BUILD_SHIPPING 75 | // 下面是存文件 76 | // 将纹理转换为2D纹理资源 77 | FTexture2DDynamicResource* TextureResource = static_cast(Texture->GetResource()); 78 | if (TextureResource != nullptr) 79 | { 80 | FTexture2DRHIRef TextureRHI; 81 | TArray SurfData; 82 | ENQUEUE_RENDER_COMMAND(CheckTextureRHICommand)( 83 | [TextureResource, &TextureRHI, &SurfData](FRHICommandListImmediate& RHICmdList) 84 | { 85 | if (TextureResource != nullptr) 86 | { 87 | // 可选的额外检查,确认RHI资源是否已经创建。 88 | if (TextureResource->IsInitialized()) 89 | { 90 | // 在这里,您处于渲染线程上下文中 91 | TextureRHI = TextureResource->TextureRHI; 92 | if (TextureRHI.IsValid()) 93 | { 94 | // 读取像素数据 95 | if (TextureRHI.IsValid()) 96 | { 97 | RHICmdList.ReadSurfaceData( 98 | TextureRHI, 99 | FIntRect(0, 0, TextureRHI->GetSizeX(), TextureRHI->GetSizeY()), 100 | SurfData, 101 | FReadSurfaceDataFlags() 102 | ); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | ); 109 | 110 | // 确保渲染命令被执行 111 | FlushRenderingCommands(); 112 | 113 | // 检查是否已经读取了数据 114 | if (SurfData.Num() > 0) 115 | { 116 | // 将纹理数据保存到PNG文件 117 | int32 Width = TextureRHI->GetSizeX(); 118 | int32 Height = TextureRHI->GetSizeY(); 119 | FString SavePath = FPaths::ProjectSavedDir() / TEXT("DownloadedTexture/Texture"); 120 | FString DataTimeStr = FDateTime::UtcNow().ToIso8601(); 121 | DataTimeStr.ReplaceInline(TEXT("-"), TEXT("")); 122 | DataTimeStr.ReplaceInline(TEXT(":"), TEXT("")); 123 | DataTimeStr.ReplaceInline(TEXT("."), TEXT("")); 124 | SavePath = SavePath + DataTimeStr + TEXT(".png"); // 添加文件扩展名为.png 125 | 126 | // 使用ImageWrapper模块中的功能来保存PNG 127 | IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); 128 | TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); 129 | 130 | if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(SurfData.GetData(), SurfData.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8)) 131 | { 132 | const TArray64& PngData = ImageWrapper->GetCompressed(); 133 | FFileHelper::SaveArrayToFile(PngData, *SavePath); 134 | } 135 | 136 | if (FPaths::FileExists(SavePath)) 137 | { 138 | UE_LOG(LogTemp, Log, TEXT("Image saved to %s"), *SavePath); 139 | } 140 | else 141 | { 142 | UE_LOG(LogTemp, Log, TEXT("Image saved failed to %s"), *SavePath); 143 | } 144 | } 145 | } 146 | #endif 147 | } 148 | } 149 | 150 | void UTAImageGenerator::OnDownloadFailed(UTexture2DDynamic* Texture) 151 | { 152 | UE_LOG(LogTemp, Log, TEXT("Texture DownloadFailed")); 153 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Chat/Shout/TAShoutComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | #include "Common/TAPromptDefinitions.h" 8 | #include "OpenAIDefinitions.h" 9 | #include "TAShoutComponent.generated.h" 10 | 11 | struct FChatCompletion; 12 | class UTAShoutInstance; 13 | struct FChatLog; 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnShoutReceivedMessageUpdated, const FChatCompletion&, ReceivedMessage, AActor*, Sender); 15 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnProvidePlayerChoices, const TArray&, Choices); 16 | 17 | UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 18 | class TOBENOTLLMGAMEPLAY_API UTAShoutComponent : public UActorComponent 19 | { 20 | GENERATED_BODY() 21 | 22 | public: 23 | // 这个委托的sender意思是真正发送ReceivedMessage的人,不是促使它发送ReceivedMessage的人,注意区分 24 | UPROPERTY(BlueprintAssignable, Category = "TAShoutComponent") 25 | FOnShoutReceivedMessageUpdated OnShoutReceivedMessage; 26 | 27 | // 委托:提供给玩家的备选回复项目 28 | UPROPERTY(BlueprintAssignable, Category = "TAShoutComponent") 29 | FOnProvidePlayerChoices OnProvidePlayerChoices; 30 | 31 | // Constructor for the Shout component 32 | UTAShoutComponent(); 33 | 34 | // Static function to get Shout component from an actor 35 | static UTAShoutComponent* GetTAShoutComponent(AActor* Actor); 36 | 37 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 38 | void HandleShoutReceived(const FChatCompletion& Message, AActor* Shouter, float Volume); 39 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 40 | void HandleReceivedMessage(const FChatCompletion& ReceivedMessage, AActor* Sender); 41 | 42 | // Functions related to chat log history 43 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 44 | TArray GetShoutHistory() const{return ShoutHistory;}; 45 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 46 | FString GetShoutHistoryCompressedStr() const{return ShoutHistoryCompressedStr;}; 47 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 48 | void ShoutMessage(const FChatCompletion& Message, float Volume = 700.f); 49 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 50 | FString GetNearbyAgentNames(); 51 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 52 | void RequestToSpeak(); 53 | UFUNCTION(BlueprintCallable, Category = "TAShoutComponent") 54 | void RequestToSpeakCheckSurrounding(); 55 | 56 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UTAShoutComponent") 57 | bool IsPartner; 58 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TAShoutComponent") 59 | bool IsPlayer; 60 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TAShoutComponent") 61 | bool IsRequestingMessage; 62 | 63 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UTAShoutComponent") 64 | float PartnerDelayRequestToSpeakTime = 3.0f; 65 | FTimerHandle DelayRequestToSpeakTimerHandle; 66 | UPROPERTY(BlueprintReadOnly, Category = "UTAShoutComponent") 67 | bool bIsDelayRequestToSpeakTimerFinished; 68 | 69 | UFUNCTION() 70 | void ContinueRequestToSpeak(); 71 | 72 | protected: 73 | // Begins play for the component 74 | virtual void BeginPlay() override; 75 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 76 | 77 | public: 78 | void UpdateShoutHistory(const FChatCompletion& NewChatLog); 79 | 80 | private: 81 | // Chat log history within the component 82 | UPROPERTY() 83 | TArray ShoutHistory; 84 | 85 | UPROPERTY() 86 | TArray FullShoutHistory; 87 | 88 | FString ShoutHistoryCompressedStr; 89 | 90 | // Notifies UI of Shout history update 91 | void NotifyUIOfShoutHistoryUpdate(const FChatCompletion& ReceivedMessage, AActor* Sender); 92 | 93 | FString GetSystemPromptFromOwner() const; 94 | 95 | private: 96 | UPROPERTY() 97 | class UOpenAIChat* CacheChat; 98 | 99 | public: 100 | // 根据大语言模型的响应来执行游戏中的行为,需要UTAFunctionInvokeComponent做支持 101 | UPROPERTY() 102 | bool bEnableFunctionInvoke = true; 103 | 104 | UFUNCTION(BlueprintCallable, Category = "Chat") 105 | void PerformFunctionInvokeBasedOnResponse(const FString& Response); 106 | 107 | public: 108 | UFUNCTION(BlueprintCallable, Category = "Chat") 109 | bool GetAcceptMessages() const{return bAcceptMessages;} 110 | 111 | UFUNCTION(BlueprintCallable, Category = "Chat") 112 | void SetAcceptMessages(bool bInAcceptMessages); 113 | 114 | private: 115 | // 是否接受消息的标志位 116 | bool bAcceptMessages = true; 117 | 118 | public: 119 | UFUNCTION(BlueprintCallable, Category = "Chat") 120 | void RequestShoutCompression(); 121 | 122 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "TAShoutComponent") 123 | bool bEnableCompressShout = true; 124 | 125 | private: 126 | bool bIsCompressingShout = false; 127 | int32 LastCompressedIndex; 128 | 129 | public: 130 | static const FTAPrompt PromptCompressShoutHistory; 131 | 132 | FString JoinShoutHistory(); 133 | 134 | public: 135 | UFUNCTION(BlueprintCallable, Category = "Chat") 136 | void RequestChoices(); 137 | TArray ParseChoicesFromResponse(const FString& Response); 138 | 139 | private: 140 | FString LastMessageContent; 141 | 142 | float LastRequestToSpeakTimestamp = -10.f; // 上次RequestToSpeak的时间戳 143 | float RequestToSpeakInterval = 10.f; // 定义最小RequestToSpeak间隔限制时间,X秒,在这个间隔内再调用会被拖到 X秒的时限上 144 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/TAFunctionInvokeComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Chat/TAFunctionInvokeComponent.h" 5 | #include "Chat/TAChatLogCategory.h" 6 | #include "Chat/Shout/TAShoutComponent.h" 7 | #include "Event/Core/TAEventSubsystem.h" 8 | 9 | DEFINE_LOG_CATEGORY(LogFunctionInvoke); 10 | 11 | UTAFunctionInvokeComponent::UTAFunctionInvokeComponent() 12 | { 13 | PrimaryComponentTick.bCanEverTick = false; 14 | } 15 | 16 | bool UTAFunctionInvokeComponent::TryDeserializeJson(const FString& Response, TSharedPtr& OutJsonObject) 17 | { 18 | TSharedRef> Reader = TJsonReaderFactory<>::Create(Response); 19 | return FJsonSerializer::Deserialize(Reader, OutJsonObject) && OutJsonObject.IsValid(); 20 | } 21 | 22 | void UTAFunctionInvokeComponent::HandleFunctionInvoke(const TSharedPtr& FuncInvoke) 23 | { 24 | FString FunctionName; 25 | FString Depict; 26 | if (FuncInvoke->AsObject()->TryGetStringField(TEXT("name"), FunctionName) && 27 | FuncInvoke->AsObject()->TryGetStringField(TEXT("depict"), Depict)) 28 | { 29 | UFunction* FunctionToCall = this->FindFunction(FName(*FunctionName)); 30 | if (FunctionToCall) 31 | { 32 | UE_LOG(LogFunctionInvoke, Log, TEXT("FunctionInvoke: calling function %s , depict: %s."), *FunctionName, *Depict); 33 | this->ProcessEvent(FunctionToCall, &Depict); 34 | } 35 | else 36 | { 37 | UE_LOG(LogFunctionInvoke, Error, TEXT("FunctionInvoke: function %s does not exist. depict: %s."), *FunctionName, *Depict); 38 | } 39 | }else 40 | { 41 | UE_LOG(LogFunctionInvoke, Error, TEXT("FunctionInvoke: invaild function name or depict.")); 42 | } 43 | } 44 | 45 | void UTAFunctionInvokeComponent::ParseAndTriggerFunctions(const FString& Response) 46 | { 47 | TSharedPtr JsonObject; 48 | if (TryDeserializeJson(Response, JsonObject)) 49 | { 50 | const TArray>* FuncInvokes; 51 | if (JsonObject->TryGetArrayField(TEXT("func_invoke"), FuncInvokes)) 52 | { 53 | for (const TSharedPtr& FuncInvoke : *FuncInvokes) 54 | { 55 | UE_LOG(LogFunctionInvoke, Log, TEXT("FunctionInvoke: Call HandleFunctionInvoke")); 56 | HandleFunctionInvoke(FuncInvoke); 57 | } 58 | } 59 | else 60 | { 61 | // Try parsing as a single object instead of an array 62 | const TSharedPtr* FuncInvokeObject; 63 | if (JsonObject->TryGetObjectField(TEXT("func_invoke"), FuncInvokeObject)) 64 | { 65 | UE_LOG(LogFunctionInvoke, Log, TEXT("FunctionInvoke: Call HandleFunctionInvoke")); 66 | HandleFunctionInvoke(MakeShareable(new FJsonValueObject(*FuncInvokeObject))); 67 | } 68 | else 69 | { 70 | UE_LOG(LogFunctionInvoke, Verbose, TEXT("FunctionInvoke: 'func_invoke' field is neither an array nor a valid object.")); 71 | } 72 | } 73 | } 74 | else 75 | { 76 | UE_LOG(LogFunctionInvoke, Warning, TEXT("FunctionInvoke: JSON deserialization failed.")); 77 | } 78 | } 79 | 80 | // 实现触发战斗的逻辑 81 | void UTAFunctionInvokeComponent::TriggerBattle(const FString& Depict) 82 | { 83 | UE_LOG(LogFunctionInvoke, Warning, TEXT("FunctionInvoke: TriggerBattle! Depict: %s"), *Depict); 84 | // TODO: 根据Depict实现具体的触发战斗逻辑 85 | } 86 | 87 | // 实现给玩家物品的逻辑 88 | void UTAFunctionInvokeComponent::GiveItem(const FString& Depict) 89 | { 90 | UE_LOG(LogFunctionInvoke, Warning, TEXT("FunctionInvoke: GiveItemToPlayer! Depict: %s"), *Depict); 91 | } 92 | 93 | void UTAFunctionInvokeComponent::FinishEvent(const FString& Depict) 94 | { 95 | // 使用空格作为分隔符来从Depict分解出事件ID和结果ID 96 | TArray TmpArray; 97 | Depict.ParseIntoArray(TmpArray, TEXT(" ")); 98 | 99 | if (TmpArray.Num() > 1) 100 | { 101 | int32 ParsedEventID = FCString::Atoi(*TmpArray[0]); 102 | int32 ParsedOutcomeID = FCString::Atoi(*TmpArray[1]); 103 | 104 | UE_LOG(LogFunctionInvoke, Log, TEXT("FunctionInvoke: FinishEvent called with EventID: %d, OutcomeID: %d"), ParsedEventID, ParsedOutcomeID); 105 | 106 | // 获取事件子系统实例 107 | UTAEventSubsystem* EventSubsystem = GetWorld()->GetSubsystem(); 108 | 109 | if (EventSubsystem) 110 | { 111 | EventSubsystem->FinishEvent(ParsedEventID, ParsedOutcomeID); 112 | RequestShoutCompToSpeak("Event finished, you should say one more sentence."); 113 | } 114 | else 115 | { 116 | UE_LOG(LogFunctionInvoke, Error, TEXT("FunctionInvoke: Unable to retrieve EventSubsystem.")); 117 | } 118 | } 119 | else 120 | { 121 | UE_LOG(LogFunctionInvoke, Error, TEXT("FunctionInvoke: Failed to parse EventID and OutcomeID from Depict.")); 122 | } 123 | } 124 | 125 | void UTAFunctionInvokeComponent::SpecialEvent(const FString& Depict) 126 | { 127 | OnSpecialEvent.Broadcast(Depict); 128 | } 129 | 130 | void UTAFunctionInvokeComponent::RequestShoutCompToSpeak(const FString& Message) 131 | { 132 | UTAShoutComponent* ShoutComponent = GetOwner()->FindComponentByClass(); 133 | if(ShoutComponent) 134 | { 135 | FChatCompletion ChatCompletion; 136 | ChatCompletion.message.role = EOAChatRole::SYSTEM; 137 | ChatCompletion.message.content = Message; 138 | ShoutComponent->UpdateShoutHistory(ChatCompletion); 139 | ShoutComponent->RequestToSpeakCheckSurrounding(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Agent/TANarrativeAgent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | #include "Agent/TANarrativeAgent.h" 3 | #include "Chat/Shout/TAShoutComponent.h" 4 | #include "Agent/TAAgentComponent.h" 5 | #include "Chat/TAFunctionInvokeComponent.h" 6 | 7 | ATANarrativeAgent::ATANarrativeAgent() 8 | { 9 | // 设置默认值 10 | AgentInfo.AgentName = TEXT("NarrativeAgent"); 11 | SystemPrompt = TEXT("Default System Prompt"); 12 | 13 | ShoutComponent = CreateDefaultSubobject(TEXT("ShoutComponent")); 14 | ShoutComponent->bEnableFunctionInvoke = false; 15 | 16 | AgentComponent = CreateDefaultSubobject(TEXT("AgentComponent")); 17 | AgentComponent->bEnableScheduleShout = false; 18 | 19 | bIsVoiceover = true; 20 | } 21 | 22 | void ATANarrativeAgent::CheckAndHandleInventoryEmpty() 23 | { 24 | if(AgentInfo.ItemTable.Num() == 0 && AgentInfo.bHideWhenInventoryEmpty) 25 | { 26 | HideSelf(); 27 | } 28 | } 29 | 30 | void ATANarrativeAgent::BeginPlay() 31 | { 32 | Super::BeginPlay(); 33 | 34 | if(FunctionInvokeCompClass) 35 | { 36 | FunctionInvokeComponent = NewObject(this, FunctionInvokeCompClass); 37 | FunctionInvokeComponent->RegisterComponent(); 38 | OnFunctionInvokeComponentCreated(FunctionInvokeComponent); 39 | } 40 | 41 | if(AgentID > 0) 42 | { 43 | InitAgentByID(AgentID); 44 | } 45 | } 46 | 47 | void ATANarrativeAgent::InitAgentByID(int32 NewAgentID) 48 | { 49 | AgentID = NewAgentID; 50 | // 将AgentID转为DataTable行名 51 | FName DataTableRowName = FName(*FString::Printf(TEXT("%d"), AgentID)); 52 | 53 | auto DataTable = GetAgentDataTable(); 54 | if(DataTable) 55 | { 56 | // 从数据表中查找与AgentID相匹配的行 57 | FNarrativeAgentData* AgentData = DataTable->FindRow(DataTableRowName, TEXT("LookUpAgentData")); 58 | 59 | // 如果找到对应行则更新Agent信息 60 | if(AgentData) 61 | { 62 | AgentInfo = *AgentData; 63 | 64 | SetIdentityPositionName(FName(AgentData->AgentName)); 65 | RegisterActorTAGuid(this, GetIdentityPositionName()); 66 | 67 | // 根据系统提示模板种类和参数生成系统提示 68 | SystemPrompt = GenerateSystemPrompt(AgentData->SystemPromptType, AgentData->SystemPromptParameters); 69 | 70 | // 使Agent可以开始说话 71 | AgentComponent->bEnableScheduleShout = true; 72 | 73 | InitAgentByID_BP(AgentID); 74 | 75 | UE_LOG(LogTemp, Log, TEXT("Agent data for ID %d found."), AgentID); 76 | } 77 | else 78 | { 79 | // 处理未找到数据的情况,可能包括设置默认值,或者记录日志 80 | UE_LOG(LogTemp, Error, TEXT("Agent data for ID %d not found."), AgentID); 81 | } 82 | } 83 | else 84 | { 85 | UE_LOG(LogTemp, Error, TEXT("Agent data table not found.")); 86 | } 87 | } 88 | 89 | UDataTable* ATANarrativeAgent::GetAgentDataTable_Implementation() const 90 | { 91 | return nullptr; 92 | } 93 | 94 | FString ATANarrativeAgent::GetSystemPrompt() 95 | { 96 | if (!TotalDesire.IsEmpty()) 97 | { 98 | return FString::Printf(TEXT("%s. Your Near Information is:[%s]"), *SystemPrompt, *TotalDesire); 99 | } 100 | return SystemPrompt + AppendSystemPrompt; 101 | } 102 | 103 | const FString& ATANarrativeAgent::GetAgentName() const 104 | { 105 | return AgentInfo.AgentName; 106 | } 107 | 108 | void ATANarrativeAgent::AddOrUpdateDesire(const FGuid& DesireId, const FString& DesireDescription) 109 | { 110 | DesireMap.Add(DesireId, DesireDescription); 111 | 112 | TotalDesire.Empty(); 113 | for (auto& Elem : DesireMap) 114 | { 115 | TotalDesire += Elem.Value + TEXT("\n"); 116 | } 117 | } 118 | 119 | void ATANarrativeAgent::RemoveDesire(const FGuid& DesireId) 120 | { 121 | DesireMap.Remove(DesireId); 122 | 123 | TotalDesire.Empty(); 124 | for (auto& Elem : DesireMap) 125 | { 126 | TotalDesire += Elem.Value + TEXT("\n"); 127 | } 128 | } 129 | 130 | TSoftObjectPtr ATANarrativeAgent::GetAgentPortrait() const 131 | { 132 | return AgentInfo.AgentPortrait; 133 | } 134 | 135 | TMap ATANarrativeAgent::QueryInventoryItems() const 136 | { 137 | return AgentInfo.ItemTable; 138 | } 139 | 140 | int32 ATANarrativeAgent::QueryItemAmountByName(FName ItemName) const 141 | { 142 | const int32* FoundAmount = AgentInfo.ItemTable.Find(ItemName); 143 | return FoundAmount ? *FoundAmount : 0; 144 | } 145 | 146 | bool ATANarrativeAgent::ConsumeInventoryItem(FName ItemName, int32 ConsumeCount) 147 | { 148 | int32* FoundAmount = AgentInfo.ItemTable.Find(ItemName); 149 | if (FoundAmount && *FoundAmount >= ConsumeCount) 150 | { 151 | *FoundAmount -= ConsumeCount; 152 | if(*FoundAmount == 0) 153 | { 154 | AgentInfo.ItemTable.Remove(ItemName); 155 | } 156 | CheckAndHandleInventoryEmpty(); 157 | return true; 158 | } 159 | return false; 160 | } 161 | 162 | bool ATANarrativeAgent::IsVoiceover() const 163 | { 164 | return bIsVoiceover; 165 | } 166 | 167 | FString ATANarrativeAgent::GenerateSystemPrompt(EPromptType PromptType, const TArray& Parameters) 168 | { 169 | switch (PromptType) 170 | { 171 | case EPromptType::Simple: 172 | { 173 | FSimplePromptTemplate Template; 174 | return Template.BuildPrompt(Parameters); 175 | } 176 | case EPromptType::Environment: 177 | { 178 | FEnvironmentPromptTemplate Template; 179 | if (Parameters.Num() >= 2) 180 | { 181 | Template.Description = Parameters[0]; 182 | Template.Hint = Parameters[1]; 183 | return Template.BuildPrompt(); 184 | } 185 | break; 186 | } 187 | case EPromptType::InteractiveObject: 188 | { 189 | FInteractiveObjectPromptTemplate Template; 190 | if (Parameters.Num() >= 2) 191 | { 192 | Template.ObjectName = Parameters[0]; 193 | Template.InteractionAction = Parameters[1]; 194 | return Template.BuildPrompt(); 195 | } 196 | break; 197 | } 198 | default: 199 | break; 200 | } 201 | 202 | // Default fallback prompt if the type doesn't match 203 | return TEXT("System is unable to generate prompt due to unknown type or missing parameters."); 204 | } 205 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Scene/TAAreaScene.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Scene/TAAreaScene.h" 4 | #include "Scene/TAInteractiveActor.h" 5 | #include "TASettings.h" 6 | #include "Common/TALLMLibrary.h" 7 | #include "Common/TASystemLibrary.h" 8 | #include "Event/Data/TAEventInfo.h" 9 | #include "Save/TAGuidSubsystem.h" 10 | #include "Scene/TASceneLogCategory.h" 11 | 12 | void UTAAreaScene::LoadAreaScene(const FTAEventInfo& EventInfo) 13 | { 14 | if (!GetWorld()) 15 | { 16 | UE_LOG(LogTASceneSystem, Error, TEXT("LoadAreaScene 无法获取world")); 17 | return; 18 | } 19 | 20 | TArray TempMessagesList; 21 | FString SystemPrompt; 22 | 23 | const UTASettings* Settings = GetDefault(); 24 | if (Settings) 25 | { 26 | if (UClass* TAPromptSettingClass = Settings->TAPromptSetting.TryLoadClass()) 27 | { 28 | const UTAPromptSetting* DefaultPromptSettings = GetDefault(TAPromptSettingClass); 29 | if (DefaultPromptSettings) 30 | { 31 | //const FString EventInfoDes = EventInfo.AdventurePoint+EventInfo.PeculiarPoint+EventInfo.Description; 32 | const FString EventInfoDes = EventInfo.PresetData.LocationName+EventInfo.PresetData.PeculiarPoint+EventInfo.PresetData.Description; 33 | SystemPrompt = UTALLMLibrary::PromptToStr(DefaultPromptSettings->PromptEventGenInteractables) 34 | .Replace(TEXT("{EventInfo}"), *EventInfoDes) 35 | .Replace(TEXT("{Language}"), *UTASystemLibrary::GetGameLanguage()) 36 | ; 37 | } 38 | } 39 | } 40 | if(SystemPrompt.IsEmpty()) 41 | { 42 | UE_LOG(LogTASceneSystem, Error, TEXT("LoadAreaScene 生成交互物失败")); 43 | return; 44 | } 45 | 46 | TempMessagesList.Add({EOAChatRole::SYSTEM, SystemPrompt}); 47 | FChatSettings ChatSettings{ 48 | UTALLMLibrary::GetChatEngineTypeFromQuality(ELLMChatEngineQuality::Fast), 49 | TempMessagesList, 50 | 0 51 | }; 52 | ChatSettings.jsonFormat = true; 53 | 54 | // 异步发送消息 55 | CacheChat = UTALLMLibrary::SendMessageToOpenAIWithRetry(ChatSettings, [this, EventInfo](const FChatCompletion& Message, const FString& ErrorMessage, bool Success) 56 | { 57 | if (Success) 58 | { 59 | TSharedPtr JsonObject; 60 | const TArray>* InteractablesArrayJson; 61 | const TSharedRef> Reader = TJsonReaderFactory<>::Create(Message.message.content); 62 | 63 | if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) 64 | { 65 | InteractablesArray.Empty(); 66 | if (JsonObject->TryGetArrayField(TEXT("Interactables"), InteractablesArrayJson)) 67 | { 68 | // 遍历JSON数组并处理每个交互物 69 | // 遍历JSON数组 70 | for (int32 Index = 0; Index < InteractablesArrayJson->Num(); ++Index) 71 | { 72 | // 获取每个交互物的JSON对象 73 | TSharedPtr InteractableJson = (*InteractablesArrayJson)[Index]->AsObject(); 74 | if (InteractableJson.IsValid()) 75 | { 76 | // 创建结构体实例并填充数据 77 | FInteractableInfo InteractableInfo; 78 | InteractableInfo.Name = InteractableJson->GetStringField(TEXT("Name")); 79 | InteractableInfo.UniqueFeature = InteractableJson->GetStringField(TEXT("UniqueFeature")); 80 | InteractableInfo.Objective = InteractableJson->GetStringField(TEXT("Objective")); 81 | 82 | // 将填充好的结构体添加到数组中 83 | InteractablesArray.Add(InteractableInfo); 84 | } 85 | } 86 | } 87 | else 88 | { 89 | // 创建结构体实例并填充数据 90 | FInteractableInfo InteractableInfo; 91 | if (JsonObject->HasField(TEXT("Name"))) 92 | { 93 | InteractableInfo.Name = JsonObject->GetStringField(TEXT("Name")); 94 | } 95 | if (JsonObject->HasField(TEXT("UniqueFeature"))) 96 | { 97 | InteractableInfo.UniqueFeature = JsonObject->GetStringField(TEXT("UniqueFeature")); 98 | } 99 | if (JsonObject->HasField(TEXT("Objective"))) 100 | { 101 | InteractableInfo.Objective = JsonObject->GetStringField(TEXT("Objective")); 102 | } 103 | 104 | // 将填充好的结构体添加到数组中 105 | InteractablesArray.Add(InteractableInfo); 106 | } 107 | if (InteractablesArray.Num()>0) 108 | { 109 | UClass* InteractiveActorClass = nullptr; 110 | const UTASettings* Settings = GetDefault(); 111 | if (Settings) 112 | { 113 | InteractiveActorClass = Settings->InteractiveActorClass.TryLoadClass(); 114 | } 115 | // 如果没有指定类或者类加载失败,使用默认的InteractiveActorClass类 116 | if (!InteractiveActorClass) 117 | { 118 | InteractiveActorClass = ATAInteractiveActor::StaticClass(); 119 | } 120 | 121 | // 定义位置和旋转 122 | UTAGuidSubsystem* GuidSubsystem = GetWorld()->GetSubsystem(); 123 | if(GuidSubsystem) 124 | { 125 | AActor* PlaceActor = GuidSubsystem->GetActorByGUID(EventInfo.LocationGuid); 126 | if (PlaceActor) 127 | { 128 | // 获取位置 129 | FVector Location = PlaceActor->GetActorLocation(); 130 | FRotator Rotation = PlaceActor->GetActorRotation(); 131 | 132 | // 生成交互物 133 | for (const FInteractableInfo& Interactable : InteractablesArray) 134 | { 135 | FVector NewLocation = Location + FMath::VRand() * 200; 136 | NewLocation.Z = Location.Z; 137 | FRotator NewRotation = Rotation + FRotator(0, (FMath::FRand() - 0.5) * 180, 0); 138 | 139 | // 旋转直接随机数 140 | ATAInteractiveActor* NewActor = GetWorld()->SpawnActor(InteractiveActorClass, NewLocation, NewRotation); 141 | if (NewActor) 142 | { 143 | UTAInteractionComponent* InteractionCom = NewActor->GetInteractionComponent(); 144 | if (InteractionCom) 145 | { 146 | InteractionCom->InteractableInfo = Interactable; 147 | InteractionCom->BelongEventDescription = EventInfo.PresetData.Description; 148 | } 149 | InteractiveActors.Add(NewActor); 150 | } 151 | } 152 | }else 153 | { 154 | UE_LOG(LogTASceneSystem, Error, TEXT("LoadAreaScene 未绑定位点,生成交互物失败")); 155 | } 156 | } 157 | }else 158 | { 159 | UE_LOG(LogTASceneSystem, Error, TEXT("未解析到数组或者非数组形式的交互物数据")); 160 | } 161 | } 162 | } 163 | CacheChat = nullptr; 164 | },this); 165 | } 166 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Core/TAEventPool.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Event/Core/TAEventPool.h" 5 | 6 | #include "Event/TAEventLogCategory.h" 7 | #include "Event/Plot/TAPlotManager.h" 8 | #include "Save/TAGuidSubsystem.h" 9 | #include "Scene/TAPlaceActor.h" 10 | 11 | // 修改后的 AddEvent 方法 12 | FTAEventInfo& UTAEventPool::AddEvent(FTAEventInfo EventInfo) 13 | { 14 | FTAEventInfo& EventInfoRef = AllEventInfo.Add_GetRef(EventInfo); 15 | if(EventInfoRef.PresetData.EventID == 0) 16 | { 17 | EventInfoRef.PresetData.EventID = AllEventInfo.Num() + 660000; 18 | } 19 | PendingEventInfos.Add(AllEventInfo.Last()); 20 | 21 | if (!bHasStartedProximityCheck) 22 | { 23 | StartTriggerCheck(); 24 | bHasStartedProximityCheck = true; 25 | } 26 | 27 | return EventInfoRef; 28 | } 29 | 30 | FTAEventInfo& UTAEventPool::GetEventByID(int32 EventID, bool& bSuccess) 31 | { 32 | for (FTAEventInfo& EventInfoRef : AllEventInfo) 33 | { 34 | if (EventInfoRef.PresetData.EventID == EventID) 35 | { 36 | bSuccess = true; 37 | return EventInfoRef; 38 | } 39 | } 40 | 41 | bSuccess = false; 42 | return ZeroEvent; 43 | } 44 | 45 | // 开启周期性检查 46 | void UTAEventPool::StartTriggerCheck() { 47 | UE_LOG(LogTAEventSystem, Log, TEXT("StartProximityCheck")); 48 | // 配置定时器代理来定时执行检查函数 49 | GetWorld()->GetTimerManager().SetTimer(EventTriggerTimerHandle, this, &UTAEventPool::CheckAndTriggerEvents, 1.0f, true); 50 | } 51 | 52 | void UTAEventPool::BeginDestroy() 53 | { 54 | if(EventTriggerTimerHandle.IsValid()) 55 | { 56 | GetWorld()->GetTimerManager().ClearTimer(EventTriggerTimerHandle); 57 | } 58 | UObject::BeginDestroy(); 59 | } 60 | 61 | void UTAEventPool::CheckAndTriggerEvents() 62 | { 63 | // 首先执行原来的接近性检查 64 | CheckPlayerProximityToEvents(); 65 | 66 | // 检测网状叙事系统前置 67 | UTAPlotManager* PlotManager = GetWorld()->GetSubsystem(); 68 | if (PlotManager) 69 | { 70 | PlotManager->CheckEventsTagGroupCondition(PendingEventInfos); 71 | } 72 | 73 | // 接下来遍历处于待触发状态的事件,检查更复杂的条件 74 | for (int32 i = PendingEventInfos.Num() - 1; i >= 0; --i) { 75 | const auto& EventInfo = PendingEventInfos[i]; 76 | bool bDependencyMet = EventInfo.PrecedingPlotTagGroupsConditionMet; 77 | if(!bDependencyMet) 78 | { 79 | continue; 80 | } 81 | 82 | // 检查所有前置事件是否满足条件 83 | for (const FTAEventDependency& Dependency : EventInfo.PresetData.PrecedingEvents) 84 | { 85 | if (!IsDependencyMet(Dependency)) 86 | { 87 | bDependencyMet = false; // 如果有任何一个前置事件的条件没有满足,当前事件将不会被触发 88 | break; 89 | } 90 | } 91 | 92 | if(bDependencyMet) 93 | { 94 | bool bConditionsMet = true; 95 | 96 | // 检查每个事件的所有Agent条件 97 | for (auto& Condition : EventInfo.PresetData.AgentConditions) { 98 | if (!CheckAgentCondition(Condition)) { 99 | bConditionsMet = false; 100 | break; // 如果任何条件失败了,跳过剩余的检查 101 | } 102 | } 103 | 104 | if (bConditionsMet) { 105 | // 若所有条件都满足,则触发事件 106 | UE_LOG(LogTAEventSystem, Log, TEXT("CheckAndTriggerEvents, trigger %s") , *EventInfo.PresetData.EventName); 107 | UTAEventInstance* NewEventInstance = NewObject(this, UTAEventInstance::StaticClass()); 108 | if(NewEventInstance) { 109 | // 使用生成的事件信息初始化NewEvent 110 | NewEventInstance->EventInfo = EventInfo; 111 | ActiveEvents.Add(NewEventInstance); 112 | NewEventInstance->TriggerEvent(); 113 | } 114 | PendingEventInfos.RemoveAt(i); 115 | } 116 | } 117 | } 118 | } 119 | 120 | // 定义检查函数 121 | void UTAEventPool::CheckPlayerProximityToEvents() { 122 | // 循环遍历待激活事件信息 123 | for (int32 i = PendingEventInfos.Num() - 1; i >= 0; --i) { 124 | const auto& EventInfo = PendingEventInfos[i]; 125 | 126 | // 获取玩家位置 127 | const APawn* PlayerPawn = GetWorld()->GetFirstPlayerController()->GetPawn(); 128 | if(PlayerPawn) { 129 | FVector PlayerLocation = PlayerPawn->GetActorLocation(); 130 | 131 | // 获取位点Actor 132 | UTAGuidSubsystem* GuidSubsystem = GetWorld()->GetSubsystem(); 133 | if(GuidSubsystem) { 134 | const ATAPlaceActor* PlaceActor = Cast(GuidSubsystem->GetActorByGUID(EventInfo.LocationGuid)); 135 | 136 | // 检查玩家是否在位点附近 137 | if(PlaceActor && FVector::Dist(PlayerLocation, PlaceActor->GetActorLocation()) <= PlaceActor->PlaceRadius) { 138 | // 玩家在范围内,激活事件并从待激活列表移除 139 | UE_LOG(LogTAEventSystem, Log, TEXT("CheckPlayerProximityToEvents, trigger one , remain pending %d") , PendingEventInfos.Num() - 1); 140 | UTAEventInstance* NewEventInstance = NewObject(this, UTAEventInstance::StaticClass()); 141 | if(NewEventInstance) { 142 | // 使用生成的事件信息初始化NewEvent 143 | NewEventInstance->EventInfo = EventInfo; 144 | ActiveEvents.Add(NewEventInstance); 145 | NewEventInstance->TriggerEvent(); 146 | } 147 | 148 | PendingEventInfos.RemoveAt(i); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | bool UTAEventPool::HasAnyEvents() const 156 | { 157 | return AllEventInfo.Num() > 0; 158 | } 159 | 160 | bool UTAEventPool::CheckAgentCondition(const FTAAgentCondition& Condition) 161 | { 162 | // 对Agent条件进行检查的逻辑... 163 | return true; // 示例代码,默认返回true 164 | } 165 | 166 | void UTAEventPool::AddCompletedEvent(int32 EventID, int32 OutcomeID) 167 | { 168 | // 将事件添加到已完成的事件映射中 169 | CompletedEventsOutcomeMap.Add(EventID, OutcomeID); 170 | } 171 | 172 | UTAEventInstance* UTAEventPool::GetEventInstanceByID(int32 EventID) 173 | { 174 | // 遍历活动事件数组 175 | for (UTAEventInstance* EventInstance : ActiveEvents) 176 | { 177 | // 检查当前事件实例的事件ID是否匹配 178 | if (EventInstance && EventInstance->EventInfo.PresetData.EventID == EventID) 179 | { 180 | // 如果找到匹配的事件实例,返回该实例 181 | return EventInstance; 182 | } 183 | } 184 | 185 | // 如果没有找到匹配的事件实例,返回nullptr 186 | return nullptr; 187 | } 188 | 189 | bool UTAEventPool::IsDependencyMet(const FTAEventDependency& Dependency) 190 | { 191 | // 通过查询映射查看依赖的事件是否已完成,并且结果符合所需的结果ID 192 | if (int32* FoundOutcomeID = CompletedEventsOutcomeMap.Find(Dependency.PrecedingEventID)) 193 | { 194 | // 如果需要的结果ID为0,或者实际结果ID符合Dependency 195 | return Dependency.RequiredOutcomeID == 0 || *FoundOutcomeID == Dependency.RequiredOutcomeID; 196 | } 197 | 198 | // 如果该前置事件未完成,返回false 199 | return false; 200 | } 201 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Public/Agent/TANarrativeAgent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // 简单Agent类,实际使用可能要配合上项目自己的FunctionInvoke组件 4 | 5 | #pragma once 6 | 7 | #include "CoreMinimal.h" 8 | #include "TAAgentInterface.h" 9 | #include "Common/TASystemLibrary.h" 10 | #include "GameFramework/Actor.h" 11 | #include "Save/TAGuidInterface.h" 12 | #include "TANarrativeAgent.generated.h" 13 | 14 | class UTAShoutComponent; 15 | class UTAFunctionInvokeComponent; 16 | class UTAAgentComponent; 17 | 18 | UENUM(BlueprintType) 19 | enum class EPromptType : uint8 20 | { 21 | Simple UMETA(DisplayName = "Simple"), 22 | Environment UMETA(DisplayName = "Environment"), 23 | InteractiveObject UMETA(DisplayName = "InteractiveObject") 24 | }; 25 | 26 | USTRUCT(BlueprintType) 27 | struct FNarrativeAgentData : public FTableRowBase 28 | { 29 | GENERATED_BODY() 30 | 31 | // Agent的名称 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 33 | FString AgentName; 34 | 35 | // 系统提示模板种类 36 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 37 | EPromptType SystemPromptType; 38 | 39 | // 提示模板使用的参数 40 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 41 | TArray SystemPromptParameters; 42 | 43 | // 物品信息 44 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 45 | TMap ItemTable; 46 | 47 | // 物品栏空后是否隐藏自己,比如拾取物 48 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 49 | bool bHideWhenInventoryEmpty; 50 | 51 | //肖像 52 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 53 | TSoftObjectPtr AgentPortrait; 54 | 55 | //备注 56 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 57 | TArray Note; 58 | }; 59 | 60 | USTRUCT(BlueprintType) 61 | struct FEnvironmentPromptTemplate 62 | { 63 | GENERATED_BODY() 64 | 65 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 66 | FString Description; 67 | 68 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 69 | FString Hint; 70 | 71 | FString BuildPrompt() const 72 | { 73 | return FString::Printf(TEXT("%s. %s"), *Description, *Hint); 74 | } 75 | }; 76 | 77 | USTRUCT(BlueprintType) 78 | struct FInteractiveObjectPromptTemplate 79 | { 80 | GENERATED_BODY() 81 | 82 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 83 | FString ObjectName; 84 | 85 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 86 | FString InteractionAction; 87 | 88 | FString BuildPrompt() const 89 | { 90 | return FString::Printf(TEXT("This is a %s. To use it, you must %s."), *ObjectName); 91 | } 92 | }; 93 | 94 | USTRUCT(BlueprintType) 95 | struct FSimplePromptTemplate 96 | { 97 | GENERATED_BODY() 98 | 99 | // 使用参数创建直接拼接的系统提示 100 | FString BuildPrompt(const TArray& Parameters) const 101 | { 102 | FString Prompt; 103 | for (const FString& Param : Parameters) 104 | { 105 | Prompt += Param; 106 | } 107 | Prompt += "Please use the following JSON template for your response:" 108 | "\"Response Template\": {" 109 | "\"message\": \"A string that vividly describes the ongoing scene, situation or adventurer's actions. You can use metaphoric or metaphorical phrases for immersion. Remember to keep the narration insightful and intriguing, without showing the direct speech from either the adventurer or the NPCs.\"," 110 | "\"func_invoke\": [" 111 | "{" 112 | "\"name\": \"XXX\"," 113 | "\"depict\": \"XXX\"," 114 | "//Add additional function invokes to the array as per the narration's requirement." 115 | "}" 116 | "]" 117 | "}.Please make sure your response is a valid JSON object, otherwise there will be serious consequences!" 118 | " Response message in " 119 | + UTASystemLibrary::GetGameLanguage() 120 | ; 121 | return Prompt; 122 | } 123 | }; 124 | 125 | UCLASS() 126 | class TOBENOTLLMGAMEPLAY_API ATANarrativeAgent : public AActor 127 | ,public ITAAgentInterface 128 | ,public ITAGuidInterface 129 | { 130 | GENERATED_BODY() 131 | 132 | public: 133 | UPROPERTY(EditDefaultsOnly, Category = "Narrative Agent") 134 | TSubclassOf FunctionInvokeCompClass; 135 | 136 | // 构造函数 137 | ATANarrativeAgent(); 138 | 139 | UFUNCTION(BlueprintCallable, Category = "Narrative Agent") 140 | virtual void InitAgentByID(int32 NewAgentID); 141 | 142 | UFUNCTION(BlueprintImplementableEvent, Category = "Narrative Agent") 143 | void InitAgentByID_BP(int32 NewAgentID); 144 | 145 | UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Narrative Agent") 146 | void HideSelf(); 147 | 148 | UFUNCTION(BlueprintImplementableEvent, Category = "Narrative Agent|Function Invoke") 149 | void OnFunctionInvokeComponentCreated(UTAFunctionInvokeComponent* CreatedComponent); 150 | 151 | void CheckAndHandleInventoryEmpty(); 152 | 153 | protected: 154 | virtual void BeginPlay(); 155 | 156 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Narrative Agent") 157 | int32 AgentID; 158 | 159 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Narrative Agent") 160 | FNarrativeAgentData AgentInfo; 161 | 162 | // 系统提示 163 | UPROPERTY(VisibleAnywhere, Category="Narrative Agent") 164 | FString SystemPrompt; 165 | 166 | // 定位是子类赋值它,比如放调用函数的列表什么的? 167 | UPROPERTY(VisibleAnywhere, Category="Narrative Agent") 168 | FString AppendSystemPrompt; 169 | 170 | UPROPERTY() 171 | TMap DesireMap; 172 | 173 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Prompts") 174 | FString TotalDesire; 175 | 176 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Narrative Agent") 177 | bool bIsVoiceover; 178 | 179 | public: 180 | UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Narrative Agent") 181 | UDataTable* GetAgentDataTable() const; 182 | 183 | // 获取系统提示 184 | virtual FString GetSystemPrompt() override; 185 | 186 | // 获取Agent的名字 187 | virtual const FString& GetAgentName() const override; 188 | 189 | // 增加或更新Agent的欲望 190 | UFUNCTION(BlueprintCallable, Category = "Narrative Agent") 191 | virtual void AddOrUpdateDesire(const FGuid& DesireId, const FString& DesireDescription) override; 192 | 193 | // 移除Agent的欲望 194 | UFUNCTION(BlueprintCallable, Category = "Narrative Agent") 195 | virtual void RemoveDesire(const FGuid& DesireId) override; 196 | 197 | virtual TSoftObjectPtr GetAgentPortrait() const override; 198 | virtual TMap QueryInventoryItems() const override; 199 | virtual int32 QueryItemAmountByName(FName ItemName) const override; 200 | virtual bool ConsumeInventoryItem(FName ItemName, int32 ConsumeCount) override; 201 | public: 202 | virtual bool IsVoiceover() const; 203 | 204 | private: 205 | FString GenerateSystemPrompt(EPromptType PromptType, const TArray& Parameters); 206 | 207 | protected: 208 | // Shout组件 209 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 210 | UTAShoutComponent* ShoutComponent; 211 | 212 | // Agent组件 213 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 214 | UTAAgentComponent* AgentComponent; 215 | 216 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") 217 | UTAFunctionInvokeComponent * FunctionInvokeComponent; 218 | }; -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Generator/TAEventGenerator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | // TAEventGenerator.cpp 4 | 5 | #include "Event/Generator/TAEventGenerator.h" 6 | 7 | #include "OpenAIDefinitions.h" 8 | #include "Chat/TAChatCallback.h" 9 | #include "Common/TALLMLibrary.h" 10 | #include "Common/TASystemLibrary.h" 11 | #include "Event/TAEventLogCategory.h" 12 | #include "Event/Data/TAEventInfo.h" 13 | 14 | void UTAEventGenerator::RequestEventGeneration(const FString& SceneInfo, const int32& Num) 15 | { 16 | InitPrompt(); 17 | TArray TempMessagesList; 18 | FString NumTag; 19 | if(Num>1) 20 | { 21 | NumTag = "// Add {Num} events following the same structure"; 22 | NumTag = NumTag.Replace(TEXT("{Num}"), *FString::FromInt(Num - 1)); 23 | }else 24 | { 25 | NumTag = "// only 1 event please"; 26 | } 27 | const FString SystemPrompt = UTALLMLibrary::PromptToStr(PromptGenerateEvent) 28 | .Replace(TEXT("{SceneInfo}"), *SceneInfo) 29 | .Replace(TEXT("{Language}"), *UTASystemLibrary::GetGameLanguage()) 30 | .Replace(TEXT("{NumTag}"), *NumTag) 31 | ; 32 | TempMessagesList.Add({EOAChatRole::SYSTEM, SystemPrompt}); 33 | FChatSettings ChatSettings{ 34 | UTALLMLibrary::GetChatEngineTypeFromQuality(ELLMChatEngineQuality::Fast), 35 | TempMessagesList, 36 | 0 37 | }; 38 | ChatSettings.jsonFormat = true; 39 | 40 | // 创建回调对象并注册成功和失败委托 41 | UTAChatCallback* CallbackObject = NewObject(); 42 | CacheCallbackObject = CallbackObject; 43 | CallbackObject->OnSuccess.AddDynamic(this, &UTAEventGenerator::OnChatSuccess); 44 | CallbackObject->OnFailure.AddDynamic(this, &UTAEventGenerator::OnChatFailure); 45 | 46 | // 异步发送消息 47 | CacheChat = UTALLMLibrary::SendMessageToOpenAIWithRetry(ChatSettings, [this](const FChatCompletion& Message, const FString& ErrorMessage, bool Success) 48 | { 49 | if (Success) 50 | { 51 | CacheCallbackObject->OnSuccess.Broadcast(Message); 52 | } 53 | else 54 | { 55 | CacheCallbackObject->OnFailure.Broadcast(); 56 | } 57 | CacheChat = nullptr; 58 | },this); 59 | } 60 | 61 | void UTAEventGenerator::RequestEventGenerationByDescription(const FString& SceneInfo, const FString& Description, const FVector& InLocation) 62 | { 63 | InitPrompt(); 64 | IsInLocation = true; 65 | GenerateInLocation = InLocation; 66 | TArray TempMessagesList; 67 | FString NumTag = "// only 1 event please"; 68 | const FString SystemPrompt = UTALLMLibrary::PromptToStr(PromptGenerateEventByDescription) 69 | .Replace(TEXT("{SceneInfo}"), *SceneInfo) 70 | .Replace(TEXT("{Language}"), *UTASystemLibrary::GetGameLanguage()) 71 | .Replace(TEXT("{NumTag}"), *NumTag) 72 | .Replace(TEXT("{Theme}"), *Description) 73 | ; 74 | TempMessagesList.Add({EOAChatRole::SYSTEM, SystemPrompt}); 75 | FChatSettings ChatSettings{ 76 | UTALLMLibrary::GetChatEngineTypeFromQuality(ELLMChatEngineQuality::Fast), 77 | TempMessagesList 78 | }; 79 | ChatSettings.jsonFormat = true; 80 | 81 | // 创建回调对象并注册成功和失败委托 82 | UTAChatCallback* CallbackObject = NewObject(); 83 | CacheCallbackObject = CallbackObject; 84 | CallbackObject->OnSuccess.AddDynamic(this, &UTAEventGenerator::OnChatSuccess); 85 | CallbackObject->OnFailure.AddDynamic(this, &UTAEventGenerator::OnChatFailure); 86 | 87 | // 异步发送消息 88 | CacheChat = UTALLMLibrary::SendMessageToOpenAIWithRetry(ChatSettings, [this](const FChatCompletion& Message, const FString& ErrorMessage, bool Success) 89 | { 90 | if (Success) 91 | { 92 | CacheCallbackObject->OnSuccess.Broadcast(Message); 93 | } 94 | else 95 | { 96 | CacheCallbackObject->OnFailure.Broadcast(); 97 | } 98 | CacheChat = nullptr; 99 | },this); 100 | } 101 | 102 | void UTAEventGenerator::OnChatSuccess(FChatCompletion ChatCompletion) 103 | { 104 | // 解析返回的消息 105 | TArray GeneratedEvents = ParseEventsFromJson(ChatCompletion.message.content); 106 | 107 | if(IsInLocation) 108 | { 109 | OnEventGenerationSuccessInLocation.Broadcast(GeneratedEvents, GenerateInLocation); 110 | }else 111 | { 112 | OnEventGenerationSuccess.Broadcast(GeneratedEvents); 113 | } 114 | } 115 | 116 | void UTAEventGenerator::OnChatFailure() 117 | { 118 | // 可以在此处处理失败的逻辑,例如重试或者提供错误信息反馈 119 | } 120 | 121 | TArray UTAEventGenerator::ParseEventsFromJson(const FString& JsonString) 122 | { 123 | TArray ParsedEvents; 124 | TSharedPtr JsonObject; 125 | const TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); 126 | 127 | if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) 128 | { 129 | // 检查是否有"Events"数组字段 130 | if (JsonObject->HasField(TEXT("Events"))) 131 | { 132 | // 获取事件数组 133 | TArray> EventsArray = JsonObject->GetArrayField(TEXT("Events")); 134 | 135 | for (int32 Index = 0; Index < EventsArray.Num(); Index++) 136 | { 137 | TSharedPtr EventObject = EventsArray[Index]->AsObject(); 138 | ProcessEventObject(EventObject, ParsedEvents); 139 | } 140 | } 141 | else 142 | { 143 | // 直接解析整个JSON对象 144 | ProcessEventObject(JsonObject, ParsedEvents); 145 | } 146 | } 147 | else 148 | { 149 | UE_LOG(LogTAEventSystem, Error, TEXT("事件生成 JSON字符串解析失败 %s"), *JsonString); 150 | } 151 | 152 | return ParsedEvents; 153 | } 154 | 155 | void UTAEventGenerator::ProcessEventObject(const TSharedPtr& EventObject, TArray& ParsedEvents) 156 | { 157 | if (EventObject.IsValid()) 158 | { 159 | FTAEventInfo EventInfo; 160 | 161 | // 获取并设置地点名称 162 | EventInfo.PresetData.LocationName = EventObject->GetStringField(TEXT("LocationName")); 163 | 164 | // 获取并设置事件描述 165 | EventInfo.PresetData.Description = EventObject->GetStringField(TEXT("Description")); 166 | 167 | int32 EventTypeInt; 168 | FString EventTypeStr; 169 | if (EventObject->TryGetNumberField(TEXT("EventType"), EventTypeInt)) 170 | { 171 | // 处理数值字段 172 | EventInfo.PresetData.EventType = static_cast(EventTypeInt); 173 | }else if (EventObject->TryGetStringField(TEXT("EventType"), EventTypeStr)) 174 | { 175 | // 尝试将字符串的第一个字符转换为数字 176 | TCHAR FirstChar = EventTypeStr[0]; 177 | if (FChar::IsDigit(FirstChar)) 178 | { 179 | EventTypeInt = FCString::Atoi(*EventTypeStr); 180 | EventInfo.PresetData.EventType = static_cast(EventTypeInt); 181 | } 182 | else 183 | { 184 | // 处理非数字开始的字符串或其他情况 185 | UE_LOG(LogTAEventSystem, Error, TEXT("无效的事件类型格式 %s"), *EventTypeStr); 186 | } 187 | } 188 | else 189 | { 190 | // 处理既不是字符串也不是数字的情况 191 | UE_LOG(LogTAEventSystem, Error, TEXT("无EventType字段")); 192 | } 193 | 194 | // 获取并设置事件权重 195 | EventInfo.PresetData.Weight = EventObject->GetIntegerField(TEXT("Weight")); 196 | 197 | /*if (EventObject->HasField(TEXT("AdventurePoint"))) 198 | { 199 | EventInfo.AdventurePoint = EventObject->GetStringField(TEXT("AdventurePoint")); 200 | }*/ 201 | 202 | if (EventObject->HasField(TEXT("PeculiarPoint"))) 203 | { 204 | EventInfo.PresetData.PeculiarPoint = EventObject->GetStringField(TEXT("PeculiarPoint")); 205 | } 206 | 207 | ParsedEvents.Add(EventInfo); 208 | } 209 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Event/Core/TAEventSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Event/Core/TAEventSubsystem.h" 5 | 6 | #include "TASettings.h" 7 | #include "Event/Core/TAEventInstance.h" 8 | #include "Event/Core/TAEventPool.h" 9 | #include "Event/TAEventLogCategory.h" 10 | #include "Event/Generator/TAEventGenerator.h" 11 | #include "Image/TAImageGenerator.h" 12 | #include "Save/TAGuidInterface.h" 13 | #include "Scene/TASceneSubsystem.h" 14 | 15 | UTAEventPool* UTAEventSubsystem::GetEventPool() 16 | { 17 | if(!EventPoolRef) 18 | { 19 | EventPoolRef = NewObject(this, UTAEventPool::StaticClass()); 20 | } 21 | return EventPoolRef; 22 | } 23 | 24 | void UTAEventSubsystem::Initialize(FSubsystemCollectionBase& Collection) 25 | { 26 | Super::Initialize(Collection); 27 | } 28 | 29 | // HandleGeneratedEvents 方法,用于处理生成器成功生成的事件 30 | void UTAEventSubsystem::HandleGeneratedEvents(TArray& GeneratedEvents) 31 | { 32 | UTASceneSubsystem* SceneSubsystem = GetWorld()->GetSubsystem(); 33 | 34 | for (FTAEventInfo& EventInfo : GeneratedEvents) 35 | { 36 | // 通过场景子系统查询地点信息,获得位点Actor 37 | ATAPlaceActor* PlaceActor = SceneSubsystem->QueryEventLocationByInfo(EventInfo); 38 | 39 | if (PlaceActor) 40 | { 41 | // 获取位点Actor的Guid 42 | ITAGuidInterface* GuidInterface = Cast(PlaceActor); 43 | FGuid LocationGuid = GuidInterface->GetTAGuid(); 44 | 45 | // 为事件设置地点GUID 46 | EventInfo.LocationGuid = LocationGuid; 47 | EventInfo.ActivationType = EEventActivationType::Proximity; 48 | 49 | UTAEventPool* EventPool = GetEventPool(); 50 | // 将事件和它的地点GUID添加到事件池 51 | auto& Info = EventPool->AddEvent(EventInfo); 52 | 53 | // 如果有必要,生成事件对应的图像或者其他资源 54 | GenerateImageForEvent(Info); 55 | } 56 | else 57 | { 58 | UE_LOG(LogTAEventSystem, Warning, TEXT("无法为EventID %d 找到对应的位点Actor"), EventInfo.PresetData.EventID); 59 | } 60 | } 61 | } 62 | 63 | void UTAEventSubsystem::AddEventToPoolByData(FTAPresetEventData EventData) 64 | { 65 | UE_LOG(LogTAEventSystem, Log, TEXT("UTAEventSubsystem AddEventToPoolByData %s"), *EventData.EventName); 66 | FTAEventInfo EventInfo; 67 | EventInfo.PresetData = EventData; 68 | EventInfo.ActivationType = EEventActivationType::PlotProgress; 69 | if(UTAEventPool* EventPool = GetEventPool()) 70 | { 71 | auto& Info = EventPool->AddEvent(EventInfo); 72 | } 73 | } 74 | 75 | void UTAEventSubsystem::FinishEvent(int32 EventID, int32 OutcomeID) 76 | { 77 | if (UTAEventPool* EventPool = GetEventPool()) 78 | { 79 | auto Event = EventPool->GetEventInstanceByID(EventID); 80 | if(Event) 81 | { 82 | Event->OnEventFinished(OutcomeID); 83 | EventPool->AddCompletedEvent(EventID, OutcomeID); 84 | } 85 | } 86 | } 87 | 88 | // Deinitialize 方法,负责资源的清理 89 | void UTAEventSubsystem::Deinitialize() 90 | { 91 | // 清理事件池里面的事件实例等 92 | // ... 93 | 94 | Super::Deinitialize(); 95 | } 96 | 97 | void UTAEventSubsystem::Start(const int32& GenEventNum) 98 | { 99 | // 确保事件池创建成功 100 | if (UTAEventPool* EventPool = GetEventPool()) 101 | { 102 | UClass* EventGeneratorClass; 103 | const UTASettings* Settings = GetDefault(); 104 | if (Settings) 105 | { 106 | FString ClassPath = Settings->EventGeneratorClass.ToString(); 107 | EventGeneratorClass = LoadClass(nullptr, *ClassPath); 108 | if (EventGeneratorClass != nullptr) 109 | { 110 | // 创建事件生成器实例 111 | UTAEventGenerator* EventGenerator = NewObject(this, EventGeneratorClass); 112 | if (EventGenerator) 113 | { 114 | // 给生成成功的委托绑定一个lambda函数,用以处理生成的事件 115 | EventGenerator->OnEventGenerationSuccess.AddDynamic(this, &UTAEventSubsystem::HandleGeneratedEvents); 116 | 117 | UTASceneSubsystem* SceneSubsystem = GetWorld()->GetSubsystem(); 118 | if (SceneSubsystem) 119 | { 120 | FString CurrentSceneInfo = SceneSubsystem->QuerySceneMapInfo(); 121 | 122 | // 请求生成事件,传入场景信息 123 | EventGenerator->RequestEventGeneration(CurrentSceneInfo, GenEventNum); 124 | } 125 | } 126 | }else 127 | { 128 | UE_LOG(LogTAEventSystem, Error, TEXT("EventGeneratorClass 未配置,你可以在项目设置里找到设置位置,建议自己继承一个事件生成器类UTAEventGenerator")); 129 | } 130 | } 131 | 132 | } 133 | } 134 | 135 | bool UTAEventSubsystem::HasAnyEventsInPool() const 136 | { 137 | if (EventPoolRef) 138 | { 139 | return EventPoolRef->HasAnyEvents(); 140 | } 141 | else 142 | { 143 | // 如果没有初始化事件池,那么返回false 144 | return false; 145 | } 146 | } 147 | 148 | void UTAEventSubsystem::GenerateEventByDescriptionInLocation(const FString& Description, const FVector& InLocation) 149 | { 150 | if (UTAEventPool* EventPool = GetEventPool()) 151 | { 152 | UClass* EventGeneratorClass; 153 | const UTASettings* Settings = GetDefault(); 154 | if (Settings) 155 | { 156 | FString ClassPath = Settings->EventGeneratorClass.ToString(); 157 | EventGeneratorClass = LoadClass(nullptr, *ClassPath); 158 | if (EventGeneratorClass != nullptr) 159 | { 160 | // 创建事件生成器实例 161 | UTAEventGenerator* EventGenerator = NewObject(this, EventGeneratorClass); 162 | if (EventGenerator) 163 | { 164 | EventGenerator->OnEventGenerationSuccessInLocation.AddDynamic(this, &UTAEventSubsystem::HandleGeneratedEventsByDescriptionInLocation); 165 | 166 | UTASceneSubsystem* SceneSubsystem = GetWorld()->GetSubsystem(); 167 | if (SceneSubsystem) 168 | { 169 | FString CurrentSceneInfo = SceneSubsystem->QuerySceneMapInfo(); 170 | 171 | EventGenerator->RequestEventGenerationByDescription(CurrentSceneInfo, Description, InLocation); 172 | } 173 | } 174 | }else 175 | { 176 | UE_LOG(LogTAEventSystem, Error, TEXT("EventGeneratorClass 未配置,你可以在项目设置里找到设置位置,建议自己继承一个事件生成器类UTAEventGenerator")); 177 | } 178 | } 179 | 180 | } 181 | } 182 | 183 | void UTAEventSubsystem::GenerateImageForEvent(const FTAEventInfo& GeneratedEvent) 184 | { 185 | UTAImageGenerator* ImageGenerator = NewObject(this); 186 | if (ImageGenerator) 187 | { 188 | ImageGenerator->RequestGenerateImage(GeneratedEvent); 189 | } 190 | } 191 | 192 | 193 | void UTAEventSubsystem::HandleGeneratedEventsByDescriptionInLocation(TArray& GeneratedEvents, const FVector& InLocation) 194 | { 195 | UTASceneSubsystem* SceneSubsystem = GetWorld()->GetSubsystem(); 196 | if (!SceneSubsystem) 197 | { 198 | UE_LOG(LogTAEventSystem, Error, TEXT("TriggerEvent 无法获取SceneSubsystem")); 199 | return; 200 | } 201 | 202 | if(GeneratedEvents.Num()>0) 203 | { 204 | auto EventInfo = GeneratedEvents[0]; 205 | ATAPlaceActor* PlaceActor = SceneSubsystem->CreatePlaceActorAtLocation(InLocation, 512, EventInfo.PresetData.LocationName); 206 | if (PlaceActor) 207 | { 208 | // 获取位点Actor的Guid 209 | ITAGuidInterface* GuidInterface = Cast(PlaceActor); 210 | FGuid LocationGuid = GuidInterface->GetTAGuid(); 211 | 212 | // 为事件设置地点GUID 213 | EventInfo.LocationGuid = LocationGuid; 214 | EventInfo.ActivationType = EEventActivationType::Proximity; 215 | EventInfo.PresetData.EventID = 0; 216 | 217 | UTAEventPool* EventPool = GetEventPool(); 218 | // 将事件和它的地点GUID添加到事件池 219 | auto& Info = EventPool->AddEvent(EventInfo); 220 | 221 | // 如果有必要,生成事件对应的图像或者其他资源 222 | GenerateImageForEvent(Info); 223 | } 224 | else 225 | { 226 | UE_LOG(LogTAEventSystem, Warning, TEXT("无法为EventID %d 找到对应的位点Actor"), EventInfo.PresetData.EventID); 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chinese Version is below. 2 | 3 | 中文版在英文版之后。 4 | 5 | main branch is the stable one. 6 | 7 | main 分支是稳定分支。 8 | 9 | # Introduction to TobenotLLMGameplay Plugin Features 10 | 11 | TobenotLLMGameplay: Tobenot's LLM Game Plugin —— "Langchain" for Unreal Engine C++ 12 | 13 | This is a dedicated LLM game plugin for Unreal Engine C++. Perhaps with this plugin, one could quickly establish the relevant logic for large model games. 14 | 15 | ## Supported Unreal Engine Version 16 | 17 | - Unreal Engine 5.3 18 | 19 | ## Installation Guide 20 | 21 | 1. Download the plugin repository. 22 | 2. Create a new folder named `Plugins` in your Unreal Engine project's root directory. 23 | 3. Extract the downloaded repository contents into the `Plugins` folder. 24 | 4. Compile the project using C++. 25 | 5. There are some settings available in the project settings, where some blueprint classes that override in the project can be set up initially with the base class. 26 | 27 | ## Overview of Features 28 | 29 | ### Character System 30 | - **Shout Component**: Allows characters to interact naturally with nearby Agents, offering functionalities like providing dialogue options for players and gathering names and identities of surrounding Agents. 31 | - **Dialogue Component**: Facilitates multi-person conversations, manages dialogue instances, and features long-term memory (automatic compression method). 32 | - **Chat Component**: Offers one-on-one dialogues along with the preservation of historic messages. 33 | - **Target Component**: Detects and selects dialogue targets. 34 | - **FunctionInvoke Component**: A lightweight component for function invocation, facilitating operations without strictly following OpenAI's FunctionCall process. 35 | - **Interactive Objects**: Designs basic information and logic for interactive items. 36 | - **Chat UI**: Ideally, UI should be done in blueprints, but here are dialog boxes used for Dialogue and Chat. 37 | - **Agent Interface**: Provides character identity names, positioning information, portrait display, and a simplified inventory. 38 | - **Agent Component**: Allows characters to decide communication moments based on timers. 39 | - **General Narrator (NarrativeAgent)**: Allows simplified settings and reading of preset JSON data, facilitating conversations and interactions. 40 | 41 | ### Scene System 42 | - **Site Module**: Uses spherical models to represent game areas. 43 | 44 | ### Event System 45 | - **Dynamic Event Generation**: Supports the dynamic creation of events including preset event import, loading, triggering, and the distribution and tracking process of various agents' desires within events. 46 | - **Event Triggering**: Implements proximity-based triggering mechanisms and plot-driven narrative triggering mechanisms. 47 | - **Interactive Item Generation**: Generates interactive items following event triggering. 48 | 49 | #### Network Narrative System 50 | - A system that records events to trigger new ones. 51 | 52 | ### Peripheral Systems 53 | - **Identity Tables, GUID Archiving System**: Generates a unique GUID for each identity Actor, supporting conversation history save and restoration for chat components. 54 | - **Live-Image Module**: AI-generated images. 55 | - **Network Module**: Provides alternative API URLs and ports. 56 | - **Language Module**: Configures game language options. 57 | 58 | ### API System 59 | **GPT Plugin**: Enhances [OpenAI-Api-Unreal](https://github.com/tobenot/OpenAI-Api-Unreal) 60 | - Natively supports C++. 61 | - Now allows mid-request HTTP cancellation. 62 | - Adds word embedding interfaces. 63 | 64 | ## Design Concepts 65 | ### Network Narrative System Design 66 | 67 | The core of implementing a meshed narrative system is creating rich storylines through recording and triggering events. This system's design primarily includes event recording and the mechanism for triggering new events based on those records. 68 | 69 | #### Event Recording 70 | 71 | To accurately record the dynamics within the game, the system is capable of: 72 | 73 | - **Event Monitoring**: 74 | - Monitoring and recording various in-game events, such as dialogues, player interactions, and changes in game mechanics. 75 | - For game mechanics changes, an interface is provided for event reporting. 76 | 77 | - **Event Analysis**: 78 | - Processes event results using a transformer large language model, breaking them down into several tags that encapsulate the event's meaning. 79 | - Adheres to the "actor-then-outcome" tag recording principle. 80 | 81 | - **Tag Recording Principle**: 82 | - Single record principle: Each record expresses only one event entity. 83 | - Sequential coherence: Event outcomes are coherently linked through tag groups. 84 | 85 | - **Data Embedding**: 86 | - Word embedding processing for each tag, facilitating further analysis and matching. 87 | 88 | #### Event Triggering 89 | 90 | New events are triggered based on recorded events and tags, including: 91 | 92 | - **Trigger Detection Interface**: 93 | - Provides an external interface for the system to detect if event triggering conditions are met. 94 | 95 | - **Tag Matching**: 96 | - Handles word embedding processing for tag groups against the prerequisites of the event to be triggered. 97 | - Searches for matching tag groups within event records. 98 | - Matching criteria: A tag group meets conditions only if all tags in a group appear in the correct order within a record and the similarity between tags exceeds 65%. 99 | 100 | - **Logic Fulfillment**: 101 | - Determines whether tag groups fully satisfy conditions based on Boolean logic. 102 | - Prerequisite verification: An event's prerequisite conditions are considered fulfilled only if all corresponding Boolean logic conditions are met. 103 | 104 | 105 | # TobenotLLMGameplay 插件功能介绍 106 | 107 | TobenotLLMGameplay: Tobenot's LLM Game Plugin —— "Langchain" for Unreal Engine C++ 108 | 109 | 虚幻引擎中的"Langchain"!这是一个专用于Unreal Engine C++的LLM游戏插件。也许用这个插件能快速地建立起大模型游戏的相关逻辑~ 110 | 111 | ## 支持的虚幻引擎版本 112 | 113 | - Unreal Engine 5.3 114 | 115 | ## 安装指南 116 | 117 | 1. 下载插件仓库。 118 | 2. 在你的Unreal Engine项目根目录下创建一个名为`Plugins`的新文件夹。 119 | 3. 将下载的仓库内容解压到`Plugins`文件夹中。 120 | 4. 通过C++进行项目编译。 121 | 5. 有一些设置可以在项目设置里看到,需要设置一些在项目中覆写的蓝图类,一开始可以直接写基类 122 | 123 | ## 功能概览 124 | 125 | ### 人物系统 126 | - **Shout喊话组件**:允许角色在附近的Agent间自然地交互。在里面示例地做了一些小功能,比如支持为玩家提供可选的对话选项,收集周围Agent的姓名和身份。 127 | (用喊话组件说话会发给附近的Agent,以此来实现更自然的交互,比起它来说,一对一和多对一组件可以用来做那种通讯软件和小空间私聊,也可以用作文字交互小游戏,像做饭游戏、说服游戏)。 128 | - **Dialogue组件**:实现多人会话,管理对话实例,拥有长期记忆(自动压缩方式)的功能。 129 | - **Chat组件**:提供一对一对话及历史信息的保存。 130 | - **目标组件**:检测和选择对话目标。 131 | - **FunctionInvoke组件**:轻量级功能调用组件,实现函数调用操作,不严格走OpenAI的FunctionCall流程。 132 | - **交互物**:设计了基本的互动物品信息和逻辑。 133 | - **Chat UI**:说实话UI应该在蓝图里做,不过这里写了Dialogue和Chat用的对话框。 134 | - **Agent接口**:提供角色身份名字、身份定位信息、肖像显示和简易物品栏。 135 | - **Agent组件**:允许角色根据定时器自行决定交流时机。 136 | - **通用叙述者NarrativeAgent**:可以非常简化的设置和读取预设JSON数据、进行对话和交互。 137 | 138 | ### 场景系统 139 | - **位点模块**:使用球型模型表示游戏区域。 140 | 141 | ### 事件系统 142 | - **事件动态生成**:支持动态创建事件。预设事件的导入、加载、触发,以及事件中各个Agent欲望的分发和追踪过程。 143 | - **事件触发**:目前实现了基于接近的触发机制和基于剧情的网状叙事触发机制。 144 | - **交互物生成**:在事件触发后可以生成可互动物品。 145 | 146 | #### 网状叙事系统 147 | - 记录已经发生的事件来触发新事件的系统 148 | 149 | ### 外围系统 150 | - **身份表、GUID存档系统**:对每个身份的Actor生成唯一GUID,支持聊天组件的对话历史保存和恢复。 151 | - **生图模块**:AI生成图片。 152 | - **网络模块**:提供替换的API网址与端口。 153 | - **语言模块**:配置游戏语言选项。 154 | 155 | ### API系统 156 | **GPT插件**:改进了[OpenAI-Api-Unreal](https://github.com/tobenot/OpenAI-Api-Unreal) 157 | - 使其原生支持C++。 158 | - 现可中途取消HTTP请求。 159 | - 添加了词嵌接口 160 | 161 | ## 设计稿 162 | ### 网状叙事系统设计 163 | 164 | 实现网状叙事系统的核心是通过记录和触发事件来创建丰富的故事线。本系统设计主要包含事件的记录和基于此记录触发新事件的机制。 165 | 166 | #### 事件记录 167 | 168 | 为了精确记录游戏中的动态,系统具备以下能力: 169 | 170 | - **事件监听**: 171 | - 监听和记录游戏内发生的各种事件,如对话、玩家互动和游戏机制变化。 172 | - 对于游戏机制变化,提供接口以供事件上报。 173 | 174 | - **事件分析**: 175 | - 利用transformer大语言模型处理事件结果,拆分成能够表达事件含义的多个标签组。 176 | - 遵循"先行动者后结果"的标签记录原则。 177 | 178 | - **标签记录原则**: 179 | - 单一记录原则:每条记录仅表达一个事件实体。 180 | - 结果连贯性:通过标签组串接,使得事件结果意义上连贯。 181 | 182 | - **数据嵌入**: 183 | - 对每个标签进行词嵌入处理,以便进一步的分析和匹配。 184 | 185 | #### 事件触发 186 | 187 | 依据记录的事件和标签对新事件进行触发,触发机制包括: 188 | 189 | - **触发检测接口**: 190 | - 对外提供接口,以供系统检测事件触发条件是否满足。 191 | 192 | - **标签匹配**: 193 | - 针对待触发事件的前置条件,进行标签组的词嵌入处理。 194 | - 在事件记录中寻找符合条件的标签组。 195 | - 条件匹配准则:一个标签组满足条件,当且仅当该组中所有标签在一条记录中以正确顺序出现,标签和标签之间词嵌相似度大于65%。 196 | 197 | - **逻辑满足**: 198 | - 根据布尔逻辑判断标签组是否全都满足条件。 199 | - 前置条件验证:只有当相应的布尔逻辑条件全部满足时,才认为事件触发的前置条件已经成立。 200 | 201 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Chat/Dialogue/TADialogueInstance.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Chat/Dialogue/TADialogueInstance.h" 4 | 5 | #include "OpenAIDefinitions.h" 6 | #include "Chat/Dialogue/TADialogueComponent.h" 7 | #include "Agent/TAAgentInterface.h" 8 | #include "Engine/World.h" 9 | #include "Chat/TAChatLogCategory.h" 10 | 11 | // 帮助根据说话优先级排序的结构体 12 | struct FParticipantPriority 13 | { 14 | AActor* Participant; 15 | int32 Priority; 16 | 17 | FParticipantPriority(AActor* InParticipant, int32 InPriority) 18 | : Participant(InParticipant) 19 | , Priority(InPriority) 20 | { 21 | } 22 | 23 | // 排序规则,优先级高的在前 24 | bool operator<(const FParticipantPriority& Other) const 25 | { 26 | return Priority > Other.Priority; 27 | } 28 | }; 29 | 30 | UTADialogueInstance::UTADialogueInstance() 31 | { 32 | DialogueState = EDialogueState::WaitingForParticipants; 33 | CurrentParticipantIndex = 0; 34 | } 35 | 36 | void UTADialogueInstance::AddParticipant(AActor* Participant) 37 | { 38 | if (Participant && !Participants.Contains(Participant)) 39 | { 40 | // 如果Actor不在参与者列表中,添加到列表 41 | Participants.Add(Participant); 42 | if (Participants.Num() >= 2 && DialogueState == EDialogueState::WaitingForParticipants) 43 | { 44 | DialogueState = EDialogueState::Active; 45 | 46 | // Start the timer (e.g., every 5 seconds) 47 | GetWorld()->GetTimerManager().SetTimer(DialogueTimerHandle, this, &UTADialogueInstance::CycleParticipants, 48 | 1.0f, true, 2.0f); 49 | } 50 | } 51 | } 52 | 53 | void UTADialogueInstance::RemoveParticipant(AActor* Participant) 54 | { 55 | if (Participant) 56 | { 57 | // 如果Actor在参与者列表中,从列表移除 58 | Participants.Remove(Participant); 59 | } 60 | } 61 | 62 | void UTADialogueInstance::TryEnterDialogueState(EDialogueState NewState) 63 | { 64 | // 校验权限或者其他逻辑,在适当的条件下更新对话状态 65 | DialogueState = NewState; 66 | } 67 | 68 | void UTADialogueInstance::ReceiveMessage(const FChatCompletion& Message, AActor* Sender) 69 | { 70 | // 收到消息后,将其添加到历史记录并分发给所有参与者 71 | AddMessageToHistory(Message.message,Sender); 72 | DistributeMessage(Message,Sender); 73 | DialogueState = EDialogueState::Active; 74 | } 75 | 76 | void UTADialogueInstance::CycleParticipants() 77 | { 78 | if(DialogueState != EDialogueState::Active) 79 | { 80 | // 如果对话状态不是激活状态,直接返回 81 | return; 82 | } 83 | 84 | // 遍历所有参与者以检查他们的会话组件中的接受消息标志 85 | for (AActor* Participant : Participants) 86 | { 87 | UTADialogueComponent* DialogueComponent = Participant ? Participant->FindComponentByClass() : nullptr; 88 | if (DialogueComponent && !DialogueComponent->GetAcceptMessages()) 89 | { 90 | // 如果有参与者不愿意接受消息,结束对话 91 | EndDialogue(); 92 | return; 93 | } 94 | } 95 | 96 | // 创建一个数组用于存储带有优先级的参与者 97 | TArray SortedParticipants; 98 | 99 | // 遍历所有参与者并获取他们的优先级 100 | for (AActor* Participant : Participants) 101 | { 102 | if (Participant) 103 | { 104 | const ITAAgentInterface* AgentInterface = Cast(Participant); 105 | if (AgentInterface) 106 | { 107 | // 获取参与者的优先级并创建FParticipantPriority结构体存入数组 108 | int32 Priority = AgentInterface->GetAgentSpeakPriority(); 109 | SortedParticipants.Add(FParticipantPriority(Participant, Priority)); 110 | } 111 | } 112 | } 113 | 114 | // 根据优先级进行排序 115 | SortedParticipants.Sort(); 116 | 117 | // 根据当前参与者索引选择下一个发言者 118 | if (SortedParticipants.IsValidIndex(CurrentParticipantIndex)) 119 | { 120 | // 获取选择的参与者 121 | AActor* ParticipantToSpeak = SortedParticipants[CurrentParticipantIndex].Participant; 122 | 123 | // 请求选择的参与者发言 124 | UTADialogueComponent* DialogueComponent = ParticipantToSpeak->FindComponentByClass(); 125 | if (DialogueComponent) 126 | { 127 | DialogueState = EDialogueState::WaitingForSomeOne; 128 | DialogueComponent->RequestToSpeak(); 129 | } 130 | 131 | // 更新当前参与者索引 132 | CurrentParticipantIndex = (CurrentParticipantIndex + 1) % SortedParticipants.Num(); 133 | } 134 | 135 | // 如果排序后的参与者数组为空,或者当前索引无效,则重置对话状态 136 | if(SortedParticipants.Num() == 0 || !SortedParticipants.IsValidIndex(CurrentParticipantIndex)) 137 | { 138 | DialogueState = EDialogueState::Active; 139 | CurrentParticipantIndex = 0; // 重置索引 140 | } 141 | } 142 | 143 | void UTADialogueInstance::EndDialogue() 144 | { 145 | DialogueState = EDialogueState::End; 146 | DialogueTimerHandle.Invalidate(); 147 | } 148 | 149 | void UTADialogueInstance::RefuseToSay(AActor* Sender) 150 | { 151 | // 参与者拒绝说话,重置对话状态为Active 152 | DialogueState = EDialogueState::Active; 153 | } 154 | 155 | void UTADialogueInstance::AddMessageToHistory(const FChatLog& Message, AActor* Sender) 156 | { 157 | // 将消息添加到对话历史记录 158 | DialogueHistory.Add(Message); 159 | } 160 | 161 | void UTADialogueInstance::DistributeMessage(const FChatCompletion& Message, AActor* Sender) 162 | { 163 | // 其他人只收的到message字段 164 | // 定义传递给DistributeMessage函数的新消息 165 | FChatCompletion NewMessage = Message; 166 | bool bIsNewMessageCreated = false; 167 | 168 | TSharedPtr JsonObject; 169 | TSharedRef> Reader = TJsonReaderFactory<>::Create(Message.message.content); 170 | 171 | // 装载JSON和执行检查 172 | if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid() && JsonObject->HasField(TEXT("message"))) 173 | { 174 | FString MessageContent; 175 | JsonObject->TryGetStringField(TEXT("message"), MessageContent); 176 | 177 | TSharedPtr NewJsonMessage = MakeShareable(new FJsonObject()); 178 | NewJsonMessage->SetStringField(TEXT("message"), MessageContent); 179 | FString NewRawJson; 180 | TSharedRef> Writer = TJsonWriterFactory<>::Create(&NewRawJson); 181 | FJsonSerializer::Serialize(NewJsonMessage.ToSharedRef(), Writer); 182 | 183 | NewMessage.message.content = NewRawJson; 184 | bIsNewMessageCreated = true; 185 | } 186 | 187 | // 广播消息给参与者 188 | for (AActor* Participant : Participants) 189 | { 190 | UTADialogueComponent* ChatComponent = Participant->FindComponentByClass(); 191 | if (ChatComponent) 192 | { 193 | // 参与者通过其会话组件来处理接收到的消息 194 | // 如果接收者不是发送者且新消息已被创建,则发送处理过的消息 195 | // 如果接收者是发送者,即使没有message字段,也应发送原始消息 196 | if(Participant == Sender) 197 | { 198 | ChatComponent->HandleReceivedMessage(Message, Sender); 199 | } 200 | else if(bIsNewMessageCreated) 201 | { 202 | ChatComponent->HandleReceivedMessage(NewMessage, Sender); 203 | } 204 | // 如果没有有效的message字段并且接收者不是发送者,不发送消息 205 | } 206 | } 207 | } 208 | 209 | void UTADialogueInstance::BeginDestroy() 210 | { 211 | DialogueTimerHandle.Invalidate(); 212 | UObject::BeginDestroy(); 213 | } 214 | 215 | 216 | TArray UTADialogueInstance::GetParticipantNamesFromAgents() const 217 | { 218 | TArray ParticipantNames; 219 | for (const AActor* Participant : Participants) 220 | { 221 | if (Participant) 222 | { 223 | const ITAAgentInterface* AgentInterface = Cast(Participant); 224 | if (AgentInterface) 225 | { 226 | //获取接口中的Participant名字并加到数组中 227 | ParticipantNames.Add(AgentInterface->GetAgentName()); 228 | } 229 | else 230 | { 231 | // 如果Participant没有实现ITAAgentInterface, 记录错误 232 | UE_LOG(LogTAChat, Error, TEXT("Participant '%s' does not implement ITAAgentInterface."), *Participant->GetName()); 233 | } 234 | } 235 | else 236 | { 237 | UE_LOG(LogTAChat, Error, TEXT("One of the participants is null.")); 238 | } 239 | } 240 | 241 | return ParticipantNames; 242 | } 243 | 244 | FString UTADialogueInstance::GetParticipantsNamesStringFromAgents() const 245 | { 246 | FString ParticipantsNamesString; 247 | for (const AActor* Participant : Participants) 248 | { 249 | if (Participant) 250 | { 251 | const ITAAgentInterface* AgentInterface = Cast(Participant); 252 | if (AgentInterface) 253 | { 254 | if (!ParticipantsNamesString.IsEmpty()) 255 | { 256 | ParticipantsNamesString += TEXT(", "); 257 | } 258 | ParticipantsNamesString += AgentInterface->GetAgentName(); 259 | } 260 | else 261 | { 262 | UE_LOG(LogTAChat, Error, TEXT("Participant '%s' does not implement ITAAgentInterface."), *Participant->GetName()); 263 | } 264 | } 265 | else 266 | { 267 | UE_LOG(LogTAChat, Error, TEXT("One of the participants is null in DialogueComponent.")); 268 | } 269 | } 270 | 271 | return ParticipantsNamesString; 272 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Save/TASaveGameSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Save/TASaveGameSubsystem.h" 4 | 5 | #include "EngineUtils.h" 6 | #include "Save/TAGuidInterface.h" 7 | #include "Save/TASaveGame.h" 8 | #include "Chat/TAChatComponent.h" 9 | #include "Kismet/GameplayStatics.h" 10 | 11 | 12 | DECLARE_LOG_CATEGORY_EXTERN(logTASave, Log, All); 13 | DEFINE_LOG_CATEGORY(logTASave); 14 | 15 | void UTASaveGameSubsystem::StoreAllTAData() 16 | { 17 | //创建一个存档实例 18 | SaveGameInstance = Cast(UGameplayStatics::CreateSaveGameObject(UTASaveGame::StaticClass())); 19 | 20 | //打印日志,表示开始存储所有TA数据 21 | UE_LOG(logTASave, Display, TEXT("StoreAllTAData - Begin storing all TA data.")); 22 | 23 | for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) 24 | { 25 | AActor* Actor = *ActorItr; 26 | ITAGuidInterface* TAGuidInterface = Cast(Actor); 27 | if (TAGuidInterface) 28 | { 29 | // 调用序列化Actor数据的函数 30 | SerializeActorData(Actor, TAGuidInterface); 31 | } 32 | } 33 | 34 | //存储NameGuidMap到存档实例中 35 | SaveGameInstance->NameGuidMap = this->NameGuidMap; 36 | 37 | //存档 38 | UGameplayStatics::SaveGameToSlot(SaveGameInstance, "TASaveGameSlot", 0); 39 | 40 | //打印日志,表示存储完成 41 | UE_LOG(logTASave, Display, TEXT("StoreAllTAData - All TA data stored successfully.")); 42 | } 43 | 44 | void UTASaveGameSubsystem::Initialize(FSubsystemCollectionBase& Collection) 45 | { 46 | Super::Initialize(Collection); 47 | } 48 | 49 | void UTASaveGameSubsystem::AllowRestoreNameTAGuidMap() 50 | { 51 | hasRestoreNameTAGuidMap = false; 52 | } 53 | 54 | void UTASaveGameSubsystem::RestoreNameTAGuidMap() 55 | { 56 | if(hasRestoreNameTAGuidMap) 57 | { 58 | UE_LOG(logTASave, Warning, TEXT("RestoreNameTAGuidMap - Please load only once, now return")); 59 | return; 60 | } 61 | //从存档槽中加载存档实例 62 | SaveGameInstance = Cast(UGameplayStatics::LoadGameFromSlot("TASaveGameSlot", 0)); 63 | 64 | if(!SaveGameInstance) 65 | { 66 | //如果没有找到对应的存档,就创建一个 67 | SaveGameInstance = Cast(UGameplayStatics::CreateSaveGameObject(UTASaveGame::StaticClass())); 68 | UE_LOG(logTASave, Display, TEXT("RestoreNameTAGuidMap - No TASave Data found, Create New Save.")); 69 | if(!SaveGameInstance) 70 | { 71 | UE_LOG(logTASave, Error, TEXT("RestoreNameTAGuidMap - Create New Save Failed, return.")); 72 | return; 73 | } 74 | } 75 | 76 | hasRestoreNameTAGuidMap = true; 77 | //恢复NameGuidMap 78 | this->NameGuidMap = SaveGameInstance->NameGuidMap; 79 | 80 | UE_LOG(logTASave, Display, TEXT("RestoreNameTAGuidMap - Restored NameGuidMap data.")); 81 | } 82 | 83 | /*void UTASaveGameSubsystem::RestoreAllTAData() 84 | { 85 | //从存档槽中加载存档实例 86 | UTASaveGame* SaveGameInstance = Cast(UGameplayStatics::LoadGameFromSlot("TASaveGameSlot", 0)); 87 | 88 | if(!SaveGameInstance) 89 | { 90 | //如果没有找到对应的存档,就直接返回 91 | UE_LOG(logTASave, Display, TEXT("RestoreAllTAData - No TASave Data found.")); 92 | return; 93 | } 94 | 95 | //打印日志,表示开始恢复所有TA数据 96 | UE_LOG(logTASave, Display, TEXT("RestoreAllTAData - Begin restoring all TA data.")); 97 | 98 | //我们需要遍历所有带有UTAChatComponent的actor 99 | for (TObjectIterator Itr; Itr; ++Itr) 100 | { 101 | //这是一个带有UTAChatComponent组件的actor 102 | UTAChatComponent* ChatComponent = *Itr; 103 | 104 | //通过ITAGuid接口获取该actor的TAGuid 105 | ITAGuidInterface* TAGuidInterface = Cast(ChatComponent->GetOwner()); 106 | if (TAGuidInterface) 107 | { 108 | FGuid ActorGuid = TAGuidInterface->GetTAGuid(); 109 | 110 | //查找对应的存档聊天数据 111 | const FTAChatComponentSaveData* ActorChatData = SaveGameInstance->TAChatDataMap.Find(ActorGuid); 112 | 113 | if (ActorChatData) 114 | { 115 | //如果找到,那么就用存档的聊天数据来设置这个ChatComponent 116 | ChatComponent->SetChatHistoryData(*ActorChatData); 117 | } 118 | } 119 | } 120 | 121 | //打印日志,表示恢复完成 122 | UE_LOG(logTASave, Display, TEXT("RestoreAllTAData - All TA data restored successfully.")); 123 | }*/ 124 | 125 | void UTASaveGameSubsystem::RegisterActorTAGuid(AActor* Actor, FName Name) 126 | { 127 | if(!hasRestoreNameTAGuidMap) 128 | { 129 | UE_LOG(logTASave, Error, TEXT("RegisterActorTAGuid befroe 'RestoreNameTAGuidMap'!!! Please be mindful of the timing; you might be calling 'RestoreNameTAGuidMap' too late! Consider trying call 'RestoreNameTAGuidMap' in 'InitGame' in the GameMode!")); 130 | return; 131 | } 132 | UE_LOG(logTASave, Display, TEXT("RegisterActorTAGuid")); 133 | if (Actor) 134 | { 135 | ITAGuidInterface* GuidInterface = Cast(Actor); 136 | if(GuidInterface) 137 | { 138 | if (NameGuidMap.Contains(Name) && NameGuidMap[Name].IsValid()) 139 | { 140 | // 如果NameGuidMap已经包含了这个名字,我们就取出来并设置给Actor 141 | FGuid StoredGuid = NameGuidMap[Name]; 142 | GuidInterface->SetTAGuid(StoredGuid); 143 | GuidActorMap.Add(StoredGuid, Actor); 144 | // 恢复存档数据给Actor 145 | RestoreActorData(Actor, GuidInterface, StoredGuid); 146 | UE_LOG(logTASave, Display, TEXT("RegisterActorTAGuid - RestoreActorData")); 147 | } 148 | else 149 | { 150 | // 如果没有,我们就为Actor生成一个新的GUID并添加进NameGuidMap 151 | FGuid NewGuid = FGuid::NewGuid(); 152 | GuidInterface->SetTAGuid(NewGuid); 153 | NameGuidMap.Add(Name, NewGuid); 154 | GuidActorMap.Add(NewGuid, Actor); 155 | UE_LOG(logTASave, Display, TEXT("RegisterActorTAGuid - NewGuid")); 156 | } 157 | UE_LOG(logTASave, Log, TEXT("RegisterActorTAGuid - Actor: %s , NameGuid: %s, Guid: %s"), *Actor->GetName(), *Name.ToString(), *GuidInterface->GetTAGuid().ToString()); 158 | }else 159 | { 160 | UE_LOG(logTASave, Display, TEXT("RegisterActorTAGuid - Actor is not ITAGuidInterface")); 161 | } 162 | } 163 | } 164 | 165 | void UTASaveGameSubsystem::SerializeActorData(AActor* Actor, ITAGuidInterface* TAGuidInterface) 166 | { 167 | FGuid ActorGuid = TAGuidInterface->GetTAGuid(); 168 | 169 | //获取ChatComponent的聊天数据 170 | UTAChatComponent* ChatComponent = Actor->FindComponentByClass(); 171 | if (ChatComponent) 172 | { 173 | FTAChatComponentSaveData ActorChatData = ChatComponent->GetChatHistoryData(); 174 | 175 | //把Actor的聊天数据和相应的TAGuid一起保存到存档实例中 176 | SaveGameInstance->TAChatDataMap.Add(ActorGuid, ActorChatData); 177 | } 178 | FString SerializedData = TAGuidInterface->SerializeCustomData(); 179 | // 将序列化数据存储到TMap中 180 | SaveGameInstance->SerializedDataMap.Add(ActorGuid, SerializedData); 181 | // 需要存储的其他数据也可以在这里添加相应的逻辑 182 | } 183 | 184 | //新增函数,根据Guid恢复Actor数据 185 | void UTASaveGameSubsystem::RestoreActorData(AActor* Actor, ITAGuidInterface* TAGuidInterface, FGuid ActorGuid) 186 | { 187 | if(!SaveGameInstance) 188 | { 189 | //如果没有找到对应的存档,就直接返回 190 | UE_LOG(logTASave, Error, TEXT("RestoreActorData - No TASave Data found. Have you call RestoreNameTAGuidMap in GameMode's StartPlay? ")); 191 | return; 192 | } 193 | 194 | //查找对应的存档聊天数据 195 | const FTAChatComponentSaveData* ActorChatData = SaveGameInstance->TAChatDataMap.Find(ActorGuid); 196 | 197 | if (ActorChatData) 198 | { 199 | UTAChatComponent* ChatComponent = Actor->FindComponentByClass(); 200 | if (ChatComponent) 201 | { 202 | //如果找到,那么就用存档的聊天数据来设置这个ChatComponent 203 | ChatComponent->SetChatHistoryData(*ActorChatData); 204 | } 205 | } 206 | const FString* SerializedData = SaveGameInstance->SerializedDataMap.Find(ActorGuid); 207 | if (SerializedData) 208 | { 209 | // 存在序列化数据,调用接口方法进行反序列化操作 210 | TAGuidInterface->DeserializeCustomData(*SerializedData); 211 | } 212 | } 213 | 214 | void UTASaveGameSubsystem::UnregisterActorName(FName Name) 215 | { 216 | if (NameGuidMap.Contains(Name)) 217 | { 218 | NameGuidMap.Remove(Name); 219 | UE_LOG(logTASave, Display, TEXT("Unregister %s from NameGuidMap."), *Name.ToString()); 220 | } 221 | else 222 | { 223 | UE_LOG(logTASave, Warning, TEXT("Attempted to unregister a non-existent entry %s from NameGuidMap"), *Name.ToString()); 224 | } 225 | } 226 | 227 | // 查找一个名字对应的GUID 228 | FGuid UTASaveGameSubsystem::FindNamedTAGuid(FName Name) 229 | { 230 | FGuid ResultGuid; 231 | if (NameGuidMap.Contains(Name)) 232 | { 233 | ResultGuid = NameGuidMap[Name]; 234 | } 235 | return ResultGuid; 236 | } 237 | 238 | AActor* UTASaveGameSubsystem::FindActorByName(const FName& Name) 239 | { 240 | FGuid ActorGuid = FindNamedTAGuid(Name); 241 | if (!ActorGuid.IsValid()) 242 | { 243 | UE_LOG(logTASave, Warning, TEXT("FindActorByName - No GUID found for name %s."), *Name.ToString()); 244 | return nullptr; 245 | } 246 | 247 | AActor** FoundActor = GuidActorMap.Find(ActorGuid); 248 | if (FoundActor) 249 | { 250 | return *FoundActor; 251 | } 252 | else 253 | { 254 | UE_LOG(logTASave, Warning, TEXT("FindActorByName - No Actor found for GUID.")); 255 | return nullptr; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Scene/TASceneSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | 4 | #include "Scene/TASceneSubsystem.h" 5 | 6 | #include "NavigationSystem.h" 7 | #include "Scene/TAPlaceActor.h" 8 | #include "Event/Data/TAEventInfo.h" 9 | #include "Scene/TASceneLogCategory.h" 10 | #include "TASettings.h" 11 | #include "Scene/TAAreaScene.h" 12 | 13 | FString UTASceneSubsystem::QuerySceneMapInfo() 14 | { 15 | return "The Mushroom Village on the grassland, where the mushroom monsters are hostile, mushroom villagers may be friendly."; 16 | } 17 | 18 | FString UTASceneSubsystem::QueryLocationInfo(const FVector& Location) 19 | { 20 | return "High GrassLand"; 21 | } 22 | 23 | ATAPlaceActor* UTASceneSubsystem::CreateAndAddPlace(const FVector& Location, float Radius, const FString& Name) 24 | { 25 | // 假设这个方法被正确的调用在允许创建Actor的上下文中 26 | if (UWorld* World = GetWorld()) 27 | { 28 | UClass* PlaceActorClass = nullptr; 29 | 30 | // 从配置文件中获取类路径 31 | const UTASettings* Settings = GetDefault(); 32 | if (Settings) 33 | { 34 | PlaceActorClass = Settings->PlaceActorClass.TryLoadClass(); 35 | } 36 | 37 | // 如果没有指定类或者类加载失败,使用默认的ATAPlaceActor类 38 | if (!PlaceActorClass) 39 | { 40 | PlaceActorClass = ATAPlaceActor::StaticClass(); 41 | } 42 | 43 | // 使用加载的类生成Actor实例 44 | ATAPlaceActor* NewPlaceActor = World->SpawnActor(PlaceActorClass, Location, FRotator::ZeroRotator); 45 | if (NewPlaceActor) 46 | { 47 | NewPlaceActor->SetPlaceName(Name); 48 | NewPlaceActor->SetPlaceRadius(Radius); 49 | PlaceActors.Add(NewPlaceActor); 50 | ITAGuidInterface* GuidInterface = Cast(NewPlaceActor); 51 | if (GuidInterface) 52 | { 53 | // todo: save recoverable name 54 | const FString GuidNameStr = FString::Printf(TEXT("%s%d"),*Name, PlaceActors.Num()); 55 | GuidInterface->RegisterActorTAGuid(NewPlaceActor, FName(*GuidNameStr)); 56 | } 57 | return NewPlaceActor; 58 | } 59 | } 60 | return nullptr; 61 | } 62 | 63 | UTAAreaScene* UTASceneSubsystem::CreateAndLoadAreaScene(const FTAEventInfo& EventInfo) 64 | { 65 | // 创建区域地图实例 66 | UTAAreaScene* NewAreaScene = NewObject(this, UTAAreaScene::StaticClass()); 67 | if (NewAreaScene) 68 | { 69 | AreaScenesMap.Add(EventInfo.PresetData.EventID, NewAreaScene); 70 | NewAreaScene->LoadAreaScene(EventInfo); 71 | } 72 | return NewAreaScene; // 返回创建的实例 73 | } 74 | 75 | void UTASceneSubsystem::PopulateMapWithMonsters(const TArray& ForbiddenLocations) 76 | { 77 | UE_LOG(LogTASceneSystem, Error, TEXT("PopulateMapWithMonsters Deprecated")); 78 | return; 79 | 80 | // 检查世界是否有效 81 | if (GetWorld() == nullptr) 82 | { 83 | UE_LOG(LogTASceneSystem, Error, TEXT("Invalid world in PopulateMapWithMonsters")); 84 | return; 85 | } 86 | bHasPopulateMapWithMonsters = true; 87 | 88 | // 初始化随机数生成器 89 | FRandomStream RandomStream; 90 | RandomStream.GenerateNewSeed(); 91 | 92 | // 定义地图生成怪物的配置项 93 | constexpr float Interval = 800.f; // 间隔 94 | constexpr float SafeZoneRadius = 2000.f; // PlayerStart 安全区域半径 95 | // 定义方形地图的边界 96 | constexpr float MinX = 200.f; 97 | constexpr float MaxX = 19800.f; 98 | constexpr float MinY = 200.f; 99 | constexpr float MaxY = 4800.f; 100 | 101 | FVector GenStartLocation = FVector::ZeroVector; 102 | 103 | // 获取怪物类引用 104 | UClass* MonsterClass = GetMonsterClass(); 105 | if (!MonsterClass || MonsterClass == AActor::StaticClass()) 106 | { 107 | UE_LOG(LogTASceneSystem, Error, TEXT("Invalid MonsterClass in PopulateMapWithMonsters")); 108 | return; 109 | } 110 | 111 | int32 GeneratedMonsterCount = 0; 112 | 113 | // 在地图上随机生成怪物 114 | for (float X = MinX; X < MaxX; X += Interval) 115 | { 116 | for (float Y = MinY; Y < MaxY; Y += Interval) 117 | { 118 | FVector RandomLocation = FVector(X, Y, 0.f) + GenStartLocation; 119 | RandomLocation.Z = GenStartLocation.Z; // 假设地图是平坦的,使用PlayerStart的Z值 120 | 121 | // 确保不在ForbiddenLocations中的位置生成怪物 122 | bool bIsForbiddenLocation = false; 123 | for (const FVector& ForbiddenLocation : ForbiddenLocations) 124 | { 125 | if (FVector::Dist2D(RandomLocation, ForbiddenLocation) < SafeZoneRadius) 126 | { 127 | bIsForbiddenLocation = true; 128 | break; 129 | } 130 | } 131 | 132 | if (!bIsForbiddenLocation) 133 | { 134 | // 使用导航系统来确保点是可达的 135 | FNavLocation NavLocation; 136 | UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); 137 | if (NavSys && NavSys->GetRandomPointInNavigableRadius(RandomLocation, Interval, NavLocation, nullptr)) 138 | { 139 | // 在这个位置生成一个怪物 140 | AActor* NewMonster = GetWorld()->SpawnActor(MonsterClass, NavLocation.Location, FRotator::ZeroRotator); 141 | if (NewMonster) 142 | { 143 | GeneratedMonsterCount++; 144 | } 145 | } 146 | } 147 | } 148 | } 149 | 150 | UE_LOG(LogTASceneSystem, Log, TEXT("怪物生成完毕,生成数量:%d"), GeneratedMonsterCount); 151 | } 152 | 153 | ATAPlaceActor* UTASceneSubsystem::QueryEventLocationByInfo(const FTAEventInfo& EventInfo) 154 | { 155 | UE_LOG(LogTASceneSystem, Log, TEXT("开始查询事件位置信息...")); 156 | // ... 这里可以引入基于EventInfo的更复杂的逻辑 ... 157 | bool bIsValidLocation; 158 | int32 RetryCount = 0; 159 | 160 | UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); 161 | FNavLocation NavLocation; 162 | 163 | do 164 | { 165 | bIsValidLocation = true; 166 | constexpr float MinX = 2000.0f + 200.f; 167 | float MaxX = 20000.0f- 200.f; 168 | constexpr float MinY = 0.0f + 200.f; 169 | constexpr float MaxY = 5000.0f - 200.f; 170 | if (!bHasCreatedStartingEvent) 171 | { 172 | MaxX = 4000.0f; // 定义起始区域的X坐标 173 | bHasCreatedStartingEvent = true; // 更新标志位,表明起始事件已经创建 174 | } 175 | FVector RandomPoint(FMath::FRandRange(MinX, MaxX), FMath::FRandRange(MinY, MaxY), 0); 176 | 177 | constexpr float MinRadius = 1000.0f; 178 | constexpr float MaxRadius = 1500.0f; 179 | const float RandomRadius = FMath::RandRange(MinRadius, MaxRadius); 180 | 181 | // 使用导航系统来确保点是可达的 182 | if (!NavSys || !NavSys->GetRandomPointInNavigableRadius(RandomPoint, RandomRadius, NavLocation, nullptr)) 183 | { 184 | UE_LOG(LogTASceneSystem, Warning, TEXT("Failed to find a navigable point.")); 185 | bIsValidLocation = false; 186 | } 187 | 188 | if(bIsValidLocation) 189 | { 190 | // 测试新生成的点与现有位点之间的距离 191 | for (ATAPlaceActor* PlaceActor : PlaceActors) 192 | { 193 | if (PlaceActor) 194 | { 195 | float Distance = (PlaceActor->GetActorLocation() - NavLocation.Location).Size(); 196 | if (Distance < (PlaceActor->PlaceRadius + RandomRadius)) 197 | { 198 | bIsValidLocation = false; 199 | break; 200 | } 201 | } 202 | } 203 | } 204 | 205 | if (bIsValidLocation) 206 | { 207 | UE_LOG(LogTASceneSystem, Log, TEXT("位置有效,位置选取重试次数:%d,开始创建并添加新的位点到列表中..."), RetryCount); 208 | // 创建并添加新的位点到列表中 209 | ATAPlaceActor* NewPlaceActor = CreateAndAddPlace(NavLocation.Location, RandomRadius, EventInfo.PresetData.LocationName); 210 | return NewPlaceActor; 211 | } 212 | else 213 | { 214 | // 如果位置不合适,增加重试计数 215 | RetryCount++; 216 | // 如果达到最大重试次数,退出循环 217 | if (RetryCount >= MaxQueryEventLocationRetryCount) 218 | { 219 | UE_LOG(LogTASceneSystem, Error, TEXT("达到最大重试次数%d,查询失败,返回空指针。"), MaxQueryEventLocationRetryCount); 220 | break; 221 | } 222 | } 223 | 224 | } while (!bIsValidLocation); 225 | 226 | return nullptr; 227 | } 228 | 229 | 230 | UClass* UTASceneSubsystem::GetMonsterClass() const 231 | { 232 | UClass* MonsterClass = nullptr; 233 | 234 | // Fetch the default class path from settings 235 | const UTASettings* Settings = GetDefault(); 236 | if (Settings) 237 | { 238 | MonsterClass = Settings->MonsterClass.TryLoadClass(); 239 | } 240 | 241 | // If no specified class or class loading fails, use the default AActor class 242 | if (!MonsterClass) 243 | { 244 | MonsterClass = AActor::StaticClass(); 245 | } 246 | 247 | return MonsterClass; 248 | } 249 | 250 | ATAPlaceActor* UTASceneSubsystem::CreatePlaceActorAtLocation(const FVector& NewLocation, float NewRadius, const FString& NewName) 251 | { 252 | if (GetWorld()) 253 | { 254 | ATAPlaceActor* NewPlaceActor = CreateAndAddPlace(NewLocation, NewRadius, NewName); 255 | if (NewPlaceActor) 256 | { 257 | // 位点创建成功,可以在这里执行任何额外的初始化逻辑 258 | UE_LOG(LogTASceneSystem, Log, TEXT("位点Actor在坐标 %s 创建成功。"), *NewLocation.ToString()); 259 | return NewPlaceActor; 260 | } 261 | else 262 | { 263 | // 处理创建失败的情况 264 | UE_LOG(LogTASceneSystem, Warning, TEXT("位点Actor创建失败。")); 265 | } 266 | } 267 | else 268 | { 269 | UE_LOG(LogTASceneSystem, Error, TEXT("无法获取世界上下文(UWorld*),位点Actor创建失败。")); 270 | } 271 | return nullptr; 272 | } -------------------------------------------------------------------------------- /Source/TobenotLLMGameplay/Private/Common/TAEmbeddingSystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 tobenot, See LICENSE in the project root for license information. 2 | 3 | #include "Common/TAEmbeddingSystem.h" 4 | 5 | #include "OpenAIEmbedding.h" 6 | #include "OpenAIUtils.h" 7 | #include "Serialization/ArrayReader.h" 8 | #include "Serialization/BufferArchive.h" 9 | #include "TobenotToolkit/Debug/CategoryLogSubsystem.h" 10 | 11 | 12 | const float RetryDelaySeconds = 5.0f; 13 | 14 | bool UTAEmbeddingSystem::GetTagEmbedding(const FName& Tag, FHighDimensionalVector& OutEmbeddingVec) 15 | { 16 | // 检查嵌入缓存是否已经有我们的Tag 17 | if (EmbeddingsCache.Contains(Tag)) 18 | { 19 | if (EmbeddingsCache[Tag].Status == ETagEmbeddingStatus::Embedded) 20 | { 21 | // 如果Tag已经被嵌入,我们直接从缓存中取出嵌入的向量返回 22 | OutEmbeddingVec = EmbeddingsCache[Tag].EmbeddingVector; 23 | return true; 24 | } 25 | // 如果Tag正在嵌入过程中,那么我们立刻返回false 26 | else if (EmbeddingsCache[Tag].Status == ETagEmbeddingStatus::Embedding) 27 | { 28 | return false; 29 | } 30 | } 31 | else 32 | { 33 | // 如果Tag还未开始嵌入 34 | if(LoadEmbeddingFromArchive(Tag,TEXT("TEXT_EMBEDDING_3_LARGE"), OutEmbeddingVec)) 35 | { 36 | // 如果本地存档中有结果,直接返回true,表示成功获取到了词嵌结果 37 | return true; 38 | } 39 | 40 | // 看看有没有Tag在嵌入,因为Http并发限制,我们一次就嵌一个Tag就好 41 | if(bHasTagEmbedding) 42 | { 43 | return false; 44 | } 45 | bHasTagEmbedding = true; 46 | 47 | // 如果没有Tag正在嵌入我们需要将新的Tag加入到嵌入缓存中,启动嵌入请求,并立刻返回false 48 | FTagEmbeddingData NewEmbeddingData; 49 | NewEmbeddingData.Tag = Tag; 50 | NewEmbeddingData.Status = ETagEmbeddingStatus::Embedding; 51 | 52 | EmbeddingsCache.Add(Tag, NewEmbeddingData); 53 | FEmbeddingSettings EmbeddingSettings; 54 | EmbeddingSettings.model = EEmbeddingEngineType::TEXT_EMBEDDING_3_LARGE; 55 | EmbeddingSettings.input = Tag.ToString(); // 假设FEmbeddingSettings有一个tag字段用来标识请求 56 | 57 | SendEmbeddingToOpenAIWithRetry(EmbeddingSettings, [this, Tag](const FEmbeddingResult& Result, const FString& ErrorMessage, bool Success) 58 | { 59 | if (Success) 60 | { 61 | UE_LOG(LogTemp, Log, TEXT("[%s] SendEmbeddingToOpenAIWithRetry Success"), *Tag.ToString()); 62 | 63 | // 更新状态和EmbeddingVector 64 | if (EmbeddingsCache.Contains(Tag)) 65 | { 66 | FTagEmbeddingData& EmbeddingData = EmbeddingsCache[Tag]; 67 | EmbeddingData.Status = ETagEmbeddingStatus::Embedded; 68 | EmbeddingData.EmbeddingVector = Result.embeddingVector; 69 | SaveEmbeddingToArchive(Tag, Result.embeddingVector, TEXT("TEXT_EMBEDDING_3_LARGE")); 70 | } 71 | } 72 | else 73 | { 74 | UE_LOG(LogTemp, Log, TEXT("[%s] SendEmbeddingToOpenAIWithRetry Fail: %s"), *Tag.ToString(), *ErrorMessage); 75 | 76 | // 如果失败,将状态更新为NotEmbedded以便之后重试 77 | if (EmbeddingsCache.Contains(Tag)) 78 | { 79 | EmbeddingsCache[Tag].Status = ETagEmbeddingStatus::NotEmbedded; 80 | } 81 | } 82 | bHasTagEmbedding = false; 83 | }, this); 84 | 85 | return false; 86 | } 87 | return false; 88 | } 89 | 90 | UOpenAIEmbedding* UTAEmbeddingSystem::SendEmbeddingToOpenAIWithRetry(const FEmbeddingSettings& EmbeddingSettings, 91 | TFunction Callback, 92 | const UObject* LogObject, const int32 NewRetryCount) 93 | { 94 | // 调用OpenAIEmbedding进行通信,并定义重试逻辑 95 | UOpenAIEmbedding* Embedding = UOpenAIEmbedding::Embedding(EmbeddingSettings, [this, Callback, NewRetryCount, LogObject, EmbeddingSettings /*, Embedding 这个赋值是在绑定之后,传进来就是个空指针*/] 96 | (const FEmbeddingResult& Message, const FString& ErrorMessage, bool Success) 97 | { 98 | if (Success) 99 | { 100 | // 处理成功的响应 101 | if(LogObject && LogObject->IsValidLowLevel()) 102 | { 103 | UE_LOG(LogTemp, Log, TEXT("[%s] Embedding success [%s]"), *LogObject->GetName(),*EmbeddingSettings.input); 104 | if (UCategoryLogSubsystem* CategoryLogSubsystem = LogObject->GetWorld()->GetSubsystem()) 105 | { 106 | const FString ResponseStr = FString::Printf(TEXT("[%s] Embedding success:\n%s\n"), *LogObject->GetName(), *EmbeddingSettings.input); 107 | CategoryLogSubsystem->WriteLog(TEXT("Embedding"), *ResponseStr); 108 | } 109 | Callback(Message, ErrorMessage, true); 110 | }else 111 | { 112 | UE_LOG(LogTemp, Log, TEXT("[NULL] Embedding success [%s]"),*EmbeddingSettings.input); 113 | } 114 | //Embedding = nullptr; 115 | } 116 | else if(ErrorMessage == "Request cancelled") 117 | { 118 | UE_LOG(LogTemp, Log, TEXT("[%s] Response cancelled"), *LogObject->GetName()); 119 | } 120 | else 121 | { 122 | // 是否还能重试 123 | if (NewRetryCount > 0) 124 | { 125 | UE_LOG(LogTemp, Warning, TEXT("Response failed: %s. Retrying..."), *ErrorMessage); 126 | 127 | // 设置重试延时调用 128 | FTimerHandle RetryTimerHandle; 129 | if(LogObject) 130 | { 131 | UWorld* World = GEngine->GetWorldFromContextObject(LogObject, EGetWorldErrorMode::LogAndReturnNull); // 或者用其他方式获取World上下文 132 | if (World) 133 | { 134 | World->GetTimerManager().SetTimer(RetryTimerHandle, [this, NewRetryCount, LogObject, EmbeddingSettings, Callback]() 135 | { 136 | // 重新发送请求,传递新的重试次数 137 | SendEmbeddingToOpenAIWithRetry(EmbeddingSettings, Callback, LogObject, NewRetryCount - 1); 138 | }, RetryDelaySeconds, false); 139 | } 140 | } 141 | } 142 | else 143 | { 144 | // 如果重试次数已用尽,执行最初提供的失败回调函数 145 | UE_LOG(LogTemp, Error, TEXT("Exhausted all retries! Response failed after retries: %s"), *ErrorMessage); 146 | if(LogObject) 147 | { 148 | if (UCategoryLogSubsystem* CategoryLogSubsystem = LogObject->GetWorld()->GetSubsystem()) 149 | { 150 | const FString ResponseStr = FString::Printf(TEXT("[%s] Assistant Response exhausted all retries!\n%s\n"), *LogObject->GetName(),*ErrorMessage); 151 | CategoryLogSubsystem->WriteLog(TEXT("Embedding"), *ResponseStr); 152 | } 153 | Callback(Message, ErrorMessage, false); 154 | } 155 | //Embedding = nullptr; 156 | } 157 | } 158 | //Embedding->RemoveFromRoot(); 159 | }); 160 | //Embedding->AddToRoot(); 161 | 162 | 163 | // 打印EmbeddingSettings的调试信息 164 | if(LogObject) 165 | { 166 | UE_LOG(LogTemp, Log, TEXT("[%s] Send Embedding [%s]"), *LogObject->GetName(), *EmbeddingSettings.input); 167 | } 168 | 169 | // 返回Embedding实例 170 | return Embedding; 171 | } 172 | 173 | ETagEmbeddingStatus UTAEmbeddingSystem::GetTagEmbeddingStatus(const FName& Tag) const 174 | { 175 | const FTagEmbeddingData* FoundTagEmbeddingData = EmbeddingsCache.Find(Tag); 176 | if (FoundTagEmbeddingData == nullptr) 177 | { 178 | return ETagEmbeddingStatus::NotEmbedded; 179 | } 180 | return FoundTagEmbeddingData->Status; 181 | } 182 | 183 | FTagEmbeddingData UTAEmbeddingSystem::GetTagEmbeddingData(const FName& Tag) const 184 | { 185 | const FTagEmbeddingData* FoundTagEmbeddingData = EmbeddingsCache.Find(Tag); 186 | //检查找到的对象是否为nullptr,如果是则返回默认的FTagEmbeddingData 187 | if (FoundTagEmbeddingData == nullptr) 188 | { 189 | FTagEmbeddingData DefaultData; 190 | DefaultData.Tag = Tag; 191 | DefaultData.Status = ETagEmbeddingStatus::NotEmbedded; 192 | return DefaultData; 193 | } 194 | return *FoundTagEmbeddingData; 195 | } 196 | 197 | float UTAEmbeddingSystem::CalculateCosineSimilarity(const FHighDimensionalVector& VectorA,const FHighDimensionalVector& VectorB) 198 | { 199 | return UOpenAIUtils::HDVectorCosineSimilaritySIMD(VectorA, VectorB); 200 | } 201 | 202 | FString UTAEmbeddingSystem::GetSaveFilePath(const FName& Tag, const FString& ModelName) const 203 | { 204 | // 获取项目的Saved目录路径 205 | FString SavedDir = FPaths::ProjectSavedDir(); 206 | 207 | // 使用标签和模型名称生成文件名,确保名称对文件系统是有效的 208 | FString FileName = FString::Printf(TEXT("Embeddings/%s_%s.embedding"), *Tag.ToString(), *ModelName); 209 | 210 | // 清洁文件名,移除可能的非法字符 211 | FPaths::MakeValidFileName(FileName); 212 | 213 | // 最后返回完整的文件路径 214 | return SavedDir / FileName; 215 | } 216 | 217 | bool UTAEmbeddingSystem::SaveEmbeddingToArchive(const FName& Tag, const FHighDimensionalVector& EmbeddingVector, const FString& ModelName) 218 | { 219 | FEmbeddingArchive ArchiveData; 220 | ArchiveData.Tag = Tag; 221 | ArchiveData.EmbeddingVector = EmbeddingVector; 222 | ArchiveData.ModelName = ModelName; 223 | 224 | FString FilePath = GetSaveFilePath(Tag, ModelName); // 生成文件路径 225 | FBufferArchive ToBinary; 226 | ToBinary << ArchiveData; // 序列化数据到二进制 227 | 228 | // 将二进制数据写入文件 229 | if (FFileHelper::SaveArrayToFile(ToBinary, *FilePath)) 230 | { 231 | ToBinary.FlushCache(); 232 | ToBinary.Empty(); 233 | return true; 234 | } 235 | return false; 236 | } 237 | 238 | bool UTAEmbeddingSystem::LoadEmbeddingFromArchive(const FName& Tag, const FString& ModelName, FHighDimensionalVector& OutEmbeddingVector) 239 | { 240 | FString FilePath = GetSaveFilePath(Tag, ModelName); // 生成文件路径 241 | FArrayReader FromBinary; 242 | 243 | // 从文件读取二进制数据 244 | if (FFileHelper::LoadFileToArray(FromBinary, *FilePath)) 245 | { 246 | FEmbeddingArchive ArchiveData; 247 | FromBinary << ArchiveData; // 从二进制反序列化数据 248 | 249 | FTagEmbeddingData NewEmbeddingData; 250 | NewEmbeddingData.Tag = Tag; 251 | NewEmbeddingData.Status = ETagEmbeddingStatus::Embedded; 252 | NewEmbeddingData.EmbeddingVector = ArchiveData.EmbeddingVector; 253 | EmbeddingsCache.Add(Tag, NewEmbeddingData); 254 | 255 | OutEmbeddingVector = ArchiveData.EmbeddingVector; 256 | FromBinary.FlushCache(); 257 | FromBinary.Empty(); 258 | return true; 259 | } 260 | return false; 261 | } 262 | --------------------------------------------------------------------------------