admin 管理员组

文章数量: 1184232

第14章 如何运用魔兽世界API接口

13.1 理解魔兽世界API

在UE5.2的游戏开发中,我们可以借鉴魔兽世界API的设计理念来创建强大且灵活的交互系统。魔兽世界拥有一套成熟的API架构,允许开发者和插件创作者访问游戏数据、响应事件并修改用户界面。本节将探讨如何将这些概念应用到UE5.2项目中。

13.1.1 常规API

魔兽世界的常规API提供了基本功能,如玩家信息获取、技能操作和UI交互。在UE5.2中,我们可以实现类似的API系统。

UE5.2中的游戏API系统 :

cpp

// GameAPISubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "GameAPISubsystem.generated.h"
/**
 * 游戏API子系统 - 提供核心游戏功能访问
 */
UCLASS()
class MYGAME_API UGameAPISubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()
public:
    // 初始化和清理
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    
    // 玩家相关API
    UFUNCTION(BlueprintCallable, Category = "Game API|Player")
    FString GetPlayerName();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Player")
    int32 GetPlayerLevel();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Player")
    float GetPlayerHealth();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Player")
    float GetPlayerMaxHealth();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Player")
    float GetPlayerResource(const FString& ResourceType);
    
    // 目标相关API
    UFUNCTION(BlueprintCallable, Category = "Game API|Target")
    AActor* GetTargetActor();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Target")
    FString GetTargetName();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Target")
    bool IsTargetHostile();
    
    // UI相关API
    UFUNCTION(BlueprintCallable, Category = "Game API|UI")
    void ShowUIFrame(const FString& FrameName);
    
    UFUNCTION(BlueprintCallable, Category = "Game API|UI")
    void HideUIFrame(const FString& FrameName);
    
    UFUNCTION(BlueprintCallable, Category = "Game API|UI")
    bool IsUIFrameVisible(const FString& FrameName);
    
    // 战斗相关API
    UFUNCTION(BlueprintCallable, Category = "Game API|Combat")
    bool IsInCombat();
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Combat")
    void UseAbility(const FString& AbilityName, AActor* Target = nullptr);
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Combat")
    bool IsAbilityOnCooldown(const FString& AbilityName);
    
    UFUNCTION(BlueprintCallable, Category = "Game API|Combat")
    float GetAbilityCooldownRemaining(const FString& AbilityName);
private:
    // 获取当前玩家角色
    class APlayerCharacter* GetPlayerCharacter() const;
    
    // 玩家技能管理器缓存
    TWeakObjectPtr<class UAbilitySystemComponent> CachedAbilitySystem;
    
    // UI管理器缓存
    TWeakObjectPtr<class UUIManager> CachedUIManager;
};

cpp

// GameAPISubsystem.cpp (部分实现)
#include "GameAPISubsystem.h"
#include "PlayerCharacter.h"
#include "AbilitySystemComponent.h"
#include "UIManager.h"
#include "Kismet/GameplayStatics.h"
void UGameAPISubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    
    // 初始化缓存
    CachedUIManager = GetGameInstance()->GetSubsystem<UUIManager>();
    
    // 可能需要等待玩家生成后再缓存技能系统
    UE_LOG(LogTemp, Log, TEXT("GameAPI Subsystem initialized"));
}
void UGameAPISubsystem::Deinitialize()
{
    // 清理缓存
    CachedAbilitySystem.Reset();
    CachedUIManager.Reset();
    
    Super::Deinitialize();
}
FString UGameAPISubsystem::GetPlayerName()
{
    APlayerCharacter* PlayerChar = GetPlayerCharacter();
    if (!PlayerChar)
    {
        return TEXT("Unknown");
    }
    
    return PlayerChar->GetPlayerName();
}
int32 UGameAPISubsystem::GetPlayerLevel()
{
    APlayerCharacter* PlayerChar = GetPlayerCharacter();
    if (!PlayerChar)
    {
        return 0;
    }
    
    return PlayerChar->GetLevel();
}
float UGameAPISubsystem::GetPlayerHealth()
{
    APlayerCharacter* PlayerChar = GetPlayerCharacter();
    if (!PlayerChar)
    {
        return 0.0f;
    }
    
    return PlayerChar->GetHealth();
}
float UGameAPISubsystem::GetPlayerMaxHealth()
{
    APlayerCharacter* PlayerChar = GetPlayerCharacter();
    if (!PlayerChar)
    {
        return 0.0f;
    }
    
    return PlayerChar->GetMaxHealth();
}
APlayerCharacter* UGameAPISubsystem::GetPlayerCharacter() const
{
    APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    if (!PC)
    {
        return nullptr;
    }
    
    return Cast<APlayerCharacter>(PC->GetPawn());
}
void UGameAPISubsystem::ShowUIFrame(const FString& FrameName)
{
    if (CachedUIManager.IsValid())
    {
        CachedUIManager->ShowUIFrame(FrameName);
    }
}
void UGameAPISubsystem::HideUIFrame(const FString& FrameName)
{
    if (CachedUIManager.IsValid())
    {
        CachedUIManager->HideUIFrame(FrameName);
    }
}
bool UGameAPISubsystem::IsUIFrameVisible(const FString& FrameName)
{
    if (CachedUIManager.IsValid())
    {
        return CachedUIManager->IsUIFrameVisible(FrameName);
    }
    
    return false;
}

在蓝图中访问常规API:

// 蓝图中获取玩家生命值示例
GameAPI = GetGameInstance().GetSubsystem(GameAPISubsystem)
CurrentHealth = GameAPI.GetPlayerHealth()
MaxHealth = GameAPI.GetPlayerMaxHealth()
HealthPercent = CurrentHealth / MaxHealth
// 设置生命条值
HealthBar.SetPercent(HealthPercent)

13.1.2 类库API

魔兽世界提供了丰富的类库API,如字符串处理、数学计算和表格操作。在UE5.2中,我们可以创建专用的辅助类来实现类似功能。

UE5.2中的辅助类库 :

cpp

// GameUtilityLibrary.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameUtilityLibrary.generated.h"
/**
 * 游戏工具库 - 提供通用工具函数
 */
UCLASS()
class MYGAME_API UGameUtilityLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
    
public:
    // 字符串操作
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|String")
    static FString FormatString(const FString& Format, const TArray<FString>& Args);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|String")
    static TArray<FString> SplitString(const FString& String, const FString& Delimiter, bool bCullEmpty = true);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|String")
    static FString StringToUpper(const FString& String);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|String")
    static FString StringToLower(const FString& String);
    
    // 数学工具
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Math")
    static float Round(float Value, int32 Decimals = 0);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Math")
    static FString FormatNumber(int32 Number);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Math")
    static FString FormatTime(float Seconds, bool bShowMilliseconds = false);
    
    // 颜色工具
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Color")
    static FLinearColor HexToColor(const FString& HexString);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Color")
    static FString ColorToHex(const FLinearColor& Color);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Color")
    static FLinearColor LerpColor(const FLinearColor& A, const FLinearColor& B, float Alpha);
    
    // 表格操作
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Table")
    static TArray<FString> GetTableColumnAsStrings(UDataTable* DataTable, FName ColumnName);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Table")
    static TArray<float> GetTableColumnAsFloats(UDataTable* DataTable, FName ColumnName);
    
    UFUNCTION(BlueprintCallable, Category = "Game Utilities|Table")
    static TArray<int32> GetTableColumnAsIntegers(UDataTable* DataTable, FName ColumnName);
};

cpp

// GameUtilityLibrary.cpp (部分实现)
#include "GameUtilityLibrary.h"
#include "Misc/DefaultValueHelper.h"
FString UGameUtilityLibrary::FormatString(const FString& Format, const TArray<FString>& Args)
{
    FString Result = Format;
    
    for (int32 i = 0; i < Args.Num(); i++)
    {
        FString Placeholder = FString::Printf(TEXT("{%d}"), i);
        Result = Result.Replace(*Placeholder, *Args[i]);
    }
    
    return Result;
}
TArray<FString> UGameUtilityLibrary::SplitString(const FString& String, const FString& Delimiter, bool bCullEmpty)
{
    TArray<FString> Result;
    String.ParseIntoArray(Result, *Delimiter, bCullEmpty);
    return Result;
}
FString UGameUtilityLibrary::StringToUpper(const FString& String)
{
    return String.ToUpper();
}
FString UGameUtilityLibrary::StringToLower(const FString& String)
{
    return String.ToLower();
}
float UGameUtilityLibrary::Round(float Value, int32 Decimals)
{
    float Multiplier = FMath::Pow(10.0f, static_cast<float>(Decimals));
    return FMath::RoundToFloat(Value * Multiplier) / Multiplier;
}
FString UGameUtilityLibrary::FormatNumber(int32 Number)
{
    FString Result;
    
    // 添加千位分隔符
    FString NumberStr = FString::FromInt(Number);
    int32 Length = NumberStr.Len();
    
    for (int32 i = 0; i < Length; i++)
    {
        if (i > 0 && (Length - i) % 3 == 0)
        {
            Result += TEXT(",");
        }
        
        Result += NumberStr[i];
    }
    
    return Result;
}
FString UGameUtilityLibrary::FormatTime(float Seconds, bool bShowMilliseconds)
{
    int32 TotalSeconds = FMath::FloorToInt(Seconds);
    int32 Minutes = TotalSeconds / 60;
    int32 Hours = Minutes / 60;
    
    Minutes %= 60;
    TotalSeconds %= 60;
    
    if (Hours > 0)
    {
        if (bShowMilliseconds)
        {
            int32 Milliseconds = FMath::FloorToInt((Seconds - FMath::FloorToFloat(Seconds)) * 1000.0f);
            return FString::Printf(TEXT("%02d:%02d:%02d.%03d"), Hours, Minutes, TotalSeconds, Milliseconds);
        }
        else
        {
            return FString::Printf(TEXT("%02d:%02d:%02d"), Hours, Minutes, TotalSeconds);
        }
    }
    else
    {
        if (bShowMilliseconds)
        {
            int32 Milliseconds = FMath::FloorToInt((Seconds - FMath::FloorToFloat(Seconds)) * 1000.0f);
            return FString::Printf(TEXT("%02d:%02d.%03d"), Minutes, TotalSeconds, Milliseconds);
        }
        else
        {
            return FString::Printf(TEXT("%02d:%02d"), Minutes, TotalSeconds);
        }
    }
}
FLinearColor UGameUtilityLibrary::HexToColor(const FString& HexString)
{
    FColor Color;
    Color.InitFromString(HexString);
    return FLinearColor(Color);
}
FString UGameUtilityLibrary::ColorToHex(const FLinearColor& Color)
{
    FColor SRGBColor = Color.ToFColor(true);
    return SRGBColor.ToHex();
}
FLinearColor UGameUtilityLibrary::LerpColor(const FLinearColor& A, const FLinearColor& B, float Alpha)
{
    return FMath::Lerp(A, B, Alpha);
}

在蓝图中使用工具类库:

ini

// 格式化字符串示例
GameUtils = GameUtilityLibrary
Args = [PlayerName, "1000", "Fireball"]
FormattedString = GameUtils.FormatString("{0} deals {1} damage with {2}!", Args)
// 转换十六进制颜色
RareColor = GameUtils.HexToColor("#0070DD")
TextBlock.SetColorAndOpacity(RareColor)

13.1.3 FrameXML 函数

魔兽世界的FrameXML函数用于操作UI元素。在UE5.2中,我们可以创建一个UI框架来处理基于XML的界面定义和操作。

UE5.2中的XML UI框架 :

cpp

// XMLUIManager.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "XMLUITypes.h"
#include "XMLUIManager.generated.h"
/**
 * XML UI管理器 - 管理基于XML的UI系统
 */
UCLASS()
class MYGAME_API UXMLUIManager : public UGameInstanceSubsystem
{
    GENERATED_BODY()
    
public:
    // 初始化和清理
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    
    // 创建UI框架
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    UUserWidget* CreateFrameFromXML(const FString& XMLString, UObject* WorldContextObject);
    
    // 根据名称获取UI框架
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    UUserWidget* GetFrameByName(const FString& FrameName);
    
    // 设置框架属性
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    void SetFrameAttribute(const FString& FrameName, const FString& AttributeName, const FString& AttributeValue);
    
    // 获取框架属性
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    FString GetFrameAttribute(const FString& FrameName, const FString& AttributeName);
    
    // 注册框架事件处理器
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    void RegisterFrameEventHandler(const FString& FrameName, EFrameEvent EventType, const FFrameEventDelegate& EventHandler);
    
    // 触发框架事件
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    void TriggerFrameEvent(const FString& FrameName, EFrameEvent EventType, const FString& EventData);
    
    // 显示和隐藏框架
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    void ShowFrame(const FString& FrameName);
    
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    void HideFrame(const FString& FrameName);
    
    // 检查框架可见性
    UFUNCTION(BlueprintCallable, Category = "XML UI")
    bool IsFrameVisible(const FString& FrameName);
    
private:
    // 已加载的UI框架
    UPROPERTY()
    TMap<FString, UUserWidget*> LoadedFrames;
    
    // 框架事件处理器
    TMap<FString, TMap<EFrameEvent, FFrameEventDelegate>> EventHandlers;
    
    // XML解析器
    UPROPERTY()
    class UXMLParser* XMLParser;
    
    // 加载XML文件
    FString LoadXMLFile(const FString& FilePath);
    
    // 解析XML字符串
    bool ParseXMLString(const FString& XMLString, struct FXMLNode& OutRootNode);
};

cpp

// XMLUITypes.h
#pragma once
#include "CoreMinimal.h"
#include "XMLUITypes.generated.h"
// 框架事件类型
UENUM(BlueprintType)
enum class EFrameEvent : uint8
{
    OnLoad,
    OnShow,
    OnHide,
    OnUpdate,
    OnClick,
    OnEnter,
    OnLeave,
    OnDragStart,
    OnDragStop,
    OnValueChanged
};
// 框架事件委托
DECLARE_DYNAMIC_DELEGATE_OneParam(FFrameEventDelegate, const FString&, EventData);
// XML节点结构
USTRUCT()
struct FXMLNode
{
    GENERATED_BODY()
    
    // 节点名称
    UPROPERTY()
    FString Name;
    
    // 节点值
    UPROPERTY()
    FString Value;
    
    // 节点属性
    UPROPERTY()
    TMap<FString, FString> Attributes;
    
    // 子节点
    UPROPERTY()
    TArray<FXMLNode> Children;
};

cpp

// XMLUIManager.cpp (部分实现)
#include "XMLUIManager.h"
#include "XMLParser.h"
#include "Blueprint/UserWidget.h"
#include "Misc/FileHelper.h"
void UXMLUIManager::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    
    // 创建XML解析器
    XMLParser = NewObject<UXMLParser>(this);
    
    UE_LOG(LogTemp, Log, TEXT("XML UI Manager initialized"));
}
void UXMLUIManager::Deinitialize()
{
    // 清理已加载的框架
    for (auto& Pair : LoadedFrames)
    {
        if (Pair.Value)
        {
            Pair.Value->RemoveFromParent();
        }
    }
    
    LoadedFrames.Empty();
    EventHandlers.Empty();
    
    Super::Deinitialize();
}
UUserWidget* UXMLUIManager::CreateFrameFromXML(const FString& XMLString, UObject* WorldContextObject)
{
    // 解析XML
    FXMLNode RootNode;
    if (!ParseXMLString(XMLString, RootNode))
    {
        UE_LOG(LogTemp, Error, TEXT("Failed to parse XML string"));
        return nullptr;
    }
    
    // 检查根节点是否是Frame
    if (RootNode.Name != "Frame")
    {
        UE_LOG(LogTemp, Error, TEXT("Root node must be a Frame"));
        return nullptr;
    }
    
    // 获取框架名称
    FString FrameName;
    if (!RootNode.Attributes.Contains("name") || RootNode.Attributes["name"].IsEmpty())
    {
        UE_LOG(LogTemp, Error, TEXT("Frame must have a name"));
        return nullptr;
    }
    
    FrameName = RootNode.Attributes["name"];
    
    // 创建用户界面
    UUserWidget* FrameWidget = CreateWidget<UUserWidget>(WorldContextObject);
    if (!FrameWidget)
    {
        UE_LOG(LogTemp, Error, TEXT("Failed to create widget"));
        return nullptr;
    }
    
    // 处理框架属性和子元素
    // 这部分实现将较为复杂,需要根据XML结构创建相应的UMG组件
    
    // 存储框架引用
    LoadedFrames.Add(FrameName, FrameWidget);
    
    // 触发OnLoad事件
    TriggerFrameEvent(FrameName, EFrameEvent::OnLoad, "");
    
    return FrameWidget;
}
UUserWidget* UXMLUIManager::GetFrameByName(const FString& FrameName)
{
    UUserWidget** FoundFrame = LoadedFrames.Find(FrameName);
    if (FoundFrame && *FoundFrame)
    {
        return *FoundFrame;
    }
    
    return nullptr;
}
void UXMLUIManager::ShowFrame(const FString& FrameName)
{
    UUserWidget* Frame = GetFrameByName(FrameName);
    if (Frame)
    {
        Frame->SetVisibility(ESlateVisibility::Visible);
        TriggerFrameEvent(FrameName, EFrameEvent::OnShow, "");
    }
}
void UXMLUIManager::HideFrame(const FString& FrameName)
{
    UUserWidget* Frame = GetFrameByName(FrameName);
    if (Frame)
    {
        Frame->SetVisibility(ESlateVisibility::Collapsed);
        TriggerFrameEvent(FrameName, EFrameEvent::OnHide, "");
    }
}
bool UXMLUIManager::IsFrameVisible(const FString& FrameName)
{
    UUserWidget* Frame = GetFrameByName(FrameName);
    if (Frame)
    {
        return Frame->IsVisible();
    }
    
    return false;
}
void UXMLUIManager::RegisterFrameEventHandler(const FString& FrameName, EFrameEvent EventType, const FFrameEventDelegate& EventHandler)
{
    // 确保框架事件映射存在
    if (!EventHandlers.Contains(FrameName))
    {
        EventHandlers.Add(FrameName, TMap<EFrameEvent, FFrameEventDelegate>());
    }
    
    // 注册事件处理器
    EventHandlers[FrameName].Add(EventType, EventHandler);
}
void UXMLUIManager::TriggerFrameEvent(const FString& FrameName, EFrameEvent EventType, const FString& EventData)
{
    // 检查是否有事件处理器
    if (!EventHandlers.Contains(FrameName))
    {
        return;
    }
    
    TMap<EFrameEvent, FFrameEventDelegate>& FrameEvents = EventHandlers[FrameName];
    if (FrameEvents.Contains(EventType))
    {
        // 触发事件处理器
        FrameEvents[EventType].ExecuteIfBound(EventData);
    }
}
FString UXMLUIManager::LoadXMLFile(const FString& FilePath)
{
    FString XMLContent;
    if (FFileHelper::LoadFileToString(XMLContent, *FilePath))
    {
        return XMLContent;
    }
    
    UE_LOG(LogTemp, Error, TEXT("Failed to load XML file: %s"), *FilePath);
    return "";
}
bool UXMLUIManager::ParseXMLString(const FString& XMLString, FXMLNode& OutRootNode)
{
    if (XMLParser)
    {
        return XMLParser->ParseXMLString(XMLString, OutRootNode);
    }
    
    return false;
}

在蓝图中使用XML UI框架:

reasonml

// 创建框架示例
XMLContent = XmlUIManager.LoadXMLFile("UI/PlayerFrame.xml")
PlayerFrame = XmlUIManager.CreateFrameFromXML(XMLContent, self)
// 注册事件处理
EventHandler = new FFrameEventDelegate()
EventHandler.BindFunction(self, "OnPlayerFrameShown")
XmlUIManager.RegisterFrameEventHandler("PlayerFrame", EFrameEvent.OnShow, EventHandler)
// 显示框架
XmlUIManager.ShowFrame("PlayerFrame")

13.1.4 受保护函数

魔兽世界API中有一些受保护的函数,需要特殊权限才能访问。在UE5.2中,我们可以实现类似的安全层级API系统。

UE5.2中的受保护API :

cpp

// ProtectedGameAPI.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ProtectedGameAPI.generated.h"
// API访问级别
UENUM(BlueprintType)
enum class EAPIAccessLevel : uint8
{
    Public,     // 所有代码可访问
    Protected,  // 仅授权模块可访问
    Admin       // 仅管理员/开发者可访问
};
/**
 * 受保护的游戏API - 提供需要权限的高级功能
 */
UCLASS()
class MYGAME_API UProtectedGameAPI : public UObject
{
    GENERATED_BODY()
    
public:
    // 初始化
    void Initialize();
    
    // 验证访问级别
    bool ValidateAccess(EAPIAccessLevel RequiredLevel, const FString& CallerModule);
    
    // 授权模块
    void AuthorizeModule(const FString& ModuleName, EAPIAccessLevel AccessLevel);
    
    // 撤销授权
    void RevokeAuthorization(const FString& ModuleName);
    
    // 受保护的API函数
    
    // 需要Protected级别
    UFUNCTION(BlueprintCallable, Category = "Protected API", meta=(RequiresAuth="Protected"))
    void SetPlayerStat(const FString& StatName, float Value);
    
    UFUNCTION(BlueprintCallable, Category = "Protected API", meta=(RequiresAuth="Protected"))
    void AddItemToInventory(int32 ItemID, int32 Count = 1);
    
    // 需要Admin级别
    UFUNCTION(BlueprintCallable, Category = "Protected API", meta=(RequiresAuth="Admin"))
    void SetPlayerPosition(const FVector& NewPosition);
    
    UFUNCTION(BlueprintCallable, Category = "Protected API", meta=(RequiresAuth="Admin"))
    void SetPlayerGodMode(bool bEnable);
    
private:
    // 模块授权映射
    TMap<FString, EAPIAccessLevel> AuthorizedModules;
    
    // 获取调用者模块名称
    FString GetCallerModule() const;
};

cpp

// ProtectedGameAPI.cpp
#include "ProtectedGameAPI.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
void UProtectedGameAPI::Initialize()
{
    // 初始化授权映射
    AuthorizedModules.Empty();
    
    // 预授权某些内部模块
    AuthorizedModules.Add("GameCore", EAPIAccessLevel::Admin);
    AuthorizedModules.Add("DevConsole", EAPIAccessLevel::Admin);
    AuthorizedModules.Add("QuestSystem", EAPIAccessLevel::Protected);
    
    UE_LOG(LogTemp, Log, TEXT("Protected Game API initialized"));
}
bool UProtectedGameAPI::ValidateAccess(EAPIAccessLevel RequiredLevel, const FString& CallerModule)
{
    // 如果需要的是公共级别,直接通过
    if (RequiredLevel == EAPIAccessLevel::Public)
    {
        return true;
    }
    
    // 检查调用者是否已授权
    if (!AuthorizedModules.Contains(CallerModule))
    {
        UE_LOG(LogTemp, Warning, TEXT("Access denied: Module %s is not authorized"), *CallerModule);
        return false;
    }
    
    // 检查调用者的授权级别
    EAPIAccessLevel CallerLevel = AuthorizedModules[CallerModule];
    
    // Admin级别可以访问所有内容
    if (CallerLevel == EAPIAccessLevel::Admin)
    {
        return true;
    }
    
    // Protected级别只能访问Public和Protected内容
    if (CallerLevel == EAPIAccessLevel::Protected && RequiredLevel != EAPIAccessLevel::Admin)
    {
        return true;
    }
    
    // 访问被拒绝
    UE_LOG(LogTemp, Warning, TEXT("Access denied: Module %s (level %d) cannot access API requiring level %d"), 
           *CallerModule, static_cast<int32>(CallerLevel), static_cast<int32>(RequiredLevel));
    
    return false;
}
void UProtectedGameAPI::AuthorizeModule(const FString& ModuleName, EAPIAccessLevel AccessLevel)
{
    // 检查当前调用者是否有Admin权限
    FString CallerModule = GetCallerModule();
    if (!ValidateAccess(EAPIAccessLevel::Admin, CallerModule))
    {
        UE_LOG(LogTemp, Warning, TEXT("Authorization failed: Caller %s does not have Admin privileges"), *CallerModule);
        return;
    }
    
    // 授权新模块
    AuthorizedModules.Add(ModuleName, AccessLevel);
    UE_LOG(LogTemp, Log, TEXT("Module %s authorized with access level %d"), *ModuleName, static_cast<int32>(AccessLevel));
}
void UProtectedGameAPI::RevokeAuthorization(const FString& ModuleName)
{
    // 检查当前调用者是否有Admin权限
    FString CallerModule = GetCallerModule();
    if (!ValidateAccess(EAPIAccessLevel::Admin, CallerModule))
    {
        UE_LOG(LogTemp, Warning, TEXT("Revocation failed: Caller %s does not have Admin privileges"), *CallerModule);
        return;
    }
    
    // 撤销授权
    if (AuthorizedModules.Remove(ModuleName) > 0)
    {
        UE_LOG(LogTemp, Log, TEXT("Authorization revoked for module %s"), *ModuleName);
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("Module %s was not authorized"), *ModuleName);
    }
}
void UProtectedGameAPI::SetPlayerStat(const FString& StatName, float Value)
{
    // 验证调用者权限
    FString CallerModule = GetCallerModule();
    if (!ValidateAccess(EAPIAccessLevel::Protected, CallerModule))
    {
        return;
    }
    
    // 实现设置玩家属性的逻辑
    UE_LOG(LogTemp, Log, TEXT("Setting player stat %s to %f (requested by %s)"), *StatName, Value, *CallerModule);
    
    // 获取玩家角色并设置属性
    ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);
    if (PlayerCharacter)
    {
        // 根据属性名称设置相应的属性
        // 这需要根据你的游戏系统实现
    }
}
void UProtectedGameAPI::AddItemToInventory(int32 ItemID, int32 Count)
{
    // 验证调用者权限
    FString CallerModule = GetCallerModule();
    if (!ValidateAccess(EAPIAccessLevel::Protected, CallerModule))
    {
        return;
    }
    
    // 实现添加物品到背包的逻辑
    UE_LOG(LogTemp, Log, TEXT("Adding %d items with ID %d to inventory (requested by %s)"), Count, ItemID, *CallerModule);
    
    // 获取背包系统并添加物品
    // 这需要根据你的游戏系统实现
}
void UProtectedGameAPI::SetPlayerPosition(const FVector& NewPosition)
{
    // 验证调用者权限
    FString CallerModule = GetCallerModule();
    if (!ValidateAccess(EAPIAccessLevel::Admin, CallerModule))
    {
        return;
    }
    
    // 实现设置玩家位置的逻辑
    UE_LOG(LogTemp, Log, TEXT("Setting player position to %s (requested by %s)"), *NewPosition.ToString(), *CallerModule);
    
    // 获取玩家角色并设置位置
    ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);
    if (PlayerCharacter)
    {
        PlayerCharacter->SetActorLocation(NewPosition, false, nullptr, ETeleportType::TeleportPhysics);
    }
}
void UProtectedGameAPI::SetPlayerGodMode(bool bEnable)
{
    // 验证调用者权限
    FString CallerModule = GetCallerModule();
    if (!ValidateAccess(EAPIAccessLevel::Admin, CallerModule))
    {
        return;
    }
    
    // 实现设置无敌模式的逻辑
    UE_LOG(LogTemp, Log, TEXT("%s god mode (requested by %s)"), bEnable ? TEXT("Enabling") : TEXT("Disabling"), *CallerModule);
    
    // 获取玩家角色并设置无敌
    ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);
    if (PlayerCharacter)
    {
        // 设置无敌状态
        // 这需要根据你的游戏系统实现
    }
}
FString UProtectedGameAPI::GetCallerModule() const
{
    // 在实际实现中,这可能需要更复杂的堆栈跟踪或调用上下文
    // 简化版本仅用于演示
    return FString(FPlatformMisc::GetCallingModuleName());
}

在游戏中使用受保护API:

cpp

// QuestModule.cpp
void UQuestModule::CompleteQuest(int32 QuestID)
{
    // 获取受保护API
    UProtectedGameAPI* ProtectedAPI = GetGameInstance()->GetSubsystem<UProtectedGameAPI>();
    if (!ProtectedAPI)
    {
        UE_LOG(LogTemp, Error, TEXT("Protected API not available"));
        return;
    }
    
    // 根据任务奖励发放物品
    FQuestReward Reward = GetQuestReward(QuestID);
    ProtectedAPI->AddItemToInventory(Reward.ItemID, Reward.ItemCount);
    
    // 增加玩家经验值
    ProtectedAPI->SetPlayerStat("Experience", Reward.ExperienceReward);
}

13.1.5 单位函数的使用与关闭

魔兽世界提供了处理游戏单位(如玩家、NPC等)的函数。在UE5.2中,我们可以实现一个单位管理系统来处理游戏实体。

UE5.2中的单位管理系统 :

cpp

// UnitManager.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UnitTypes.h"
#include "UnitManager.generated.h"
/**
 * 单位管理器 - 管理游戏单位的中央系统
 */
UCLASS()
class MYGAME_API UUnitManager : public UGameInstanceSubsystem
{
    GENERATED_BODY()
    
public:
    // 初始化和清理
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    
    // 根据ID获取单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    class AActor* GetUnitByID(const FString& UnitID);
    
    // 根据名称获取单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    class AActor* GetUnitByName(const FString& UnitName);
    
    // 获取玩家单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    class ACharacter* GetPlayerUnit();
    
    // 获取目标单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    class AActor* GetTargetUnit();
    
    // 设置目标单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    void SetTargetUnit(AActor* NewTarget);
    
    // 获取单位信息
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    FUnitInfo GetUnitInfo(const AActor* Unit);
    
    // 获取单位属性
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    float GetUnitStat(const AActor* Unit, EUnitStat StatType);
    
    // 检查单位关系
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    EUnitRelation GetUnitRelation(const AActor* UnitA, const AActor* UnitB);
    
    // 检查单位是否在战斗中
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    bool IsUnitInCombat(const AActor* Unit);
    
    // 检查单位是否存活
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    bool IsUnitAlive(const AActor* Unit);
    
    // 注册单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    void RegisterUnit(AActor* Unit, const FString& UnitID);
    
    // 注销单位
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    void UnregisterUnit(const FString& UnitID);
    
    // 应用伤害
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    float ApplyDamage(AActor* Source, AActor* Target, float Amount, EUnitDamageType DamageType);
    
    // 应用治疗
    UFUNCTION(BlueprintCallable, Category = "Unit System")
    float ApplyHealing(AActor* Source, AActor* Target, float Amount);
    
private:
    // 单位ID到Actor的映射
    TMap<FString, AActor*> UnitsByID;
    
    // 单位名称到Actor的映射
    TMap<FString, AActor*> UnitsByName;
    
    // 当前目标单位
    UPROPERTY()
    AActor* CurrentTarget;
    
    // 验证单位是否有效
    bool IsValidUnit(const AActor* Unit) const;
    
    // 从组件获取单位信息
    FUnitInfo GetUnitInfoFromComponents(const AActor* Unit) const;
};

cpp

// UnitTypes.h
#pragma once
#include "CoreMinimal.h"
#include "UnitTypes.generated.h"
// 单位类型
UENUM(BlueprintType)
enum class EUnitType : uint8
{
    Player,
    NPC,
    Creature,
    Vehicle,
    GameObject
};
// 单位关系
UENUM(BlueprintType)
enum class EUnitRelation : uint8
{
    Friendly,
    Neutral,
    Hostile
};
// 单位属性类型
UENUM(BlueprintType)
enum class EUnitStat : uint8
{
    Health,
    MaxHealth,
    Mana,
    MaxMana,
    Stamina,
    MaxStamina,
    Strength,
    Agility,
    Intelligence,
    MoveSpeed,
    AttackPower,
    SpellPower,
    Armor,
    MagicResistance
};
// 伤害类型
UENUM(BlueprintType)
enum class EUnitDamageType : uint8
{
    Physical,
    Fire,
    Frost,
    Arcane,
    Nature,
    Shadow,
    Holy
};
// 单位信息结构体
USTRUCT(BlueprintType)
struct FUnitInfo
{
    GENERATED_BODY()
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    FString UnitID;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    FString Name;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    EUnitType Type;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    int32 Level;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    EUnitRelation Relation;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    bool bIsInCombat;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    bool bIsAlive;
    
    UPROPERTY(BlueprintReadWrite, Category = "Unit Info")
    TMap<EUnitStat, float> Stats;
};
// 单位组件接口
UINTERFACE(MinimalAPI, BlueprintType)
class UUnitInterface : public UInterface
{
    GENERATED_BODY()
};
class IUnitInterface
{
    GENERATED_BODY()
    
public:
    // 获取单位ID
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    FString GetUnitID() const;
    
    // 获取单位信息
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    FUnitInfo GetUnitInfo() const;
    
    // 获取单位属性
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    float GetUnitStat(EUnitStat StatType) const;
    
    // 设置单位属性
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    void SetUnitStat(EUnitStat StatType, float Value);
    
    // 检查单位是否在战斗中
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    bool IsInCombat() const;
    
    // 设置战斗状态
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    void SetCombatState(bool bInCombat);
    
    // 检查单位是否存活
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    bool IsAlive() const;
    
    // 应用伤害
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    float TakeDamage(AActor* Source, float Amount, EUnitDamageType DamageType);
    
    // 应用治疗
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Unit Interface")
    float TakeHealing(AActor* Source, float Amount);
};

cpp

// UnitManager.cpp (部分实现)
#include "UnitManager.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
void UUnitManager::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    
    UnitsByID.Empty();
    UnitsByName.Empty();
    CurrentTarget = nullptr;
    
    UE_LOG(LogTemp, Log, TEXT("Unit Manager initialized"));
}
void UUnitManager::Deinitialize()
{
    UnitsByID.Empty();
    UnitsByName.Empty();
    CurrentTarget = nullptr;
    
    Super::Deinitialize();
}
AActor* UUnitManager::GetUnitByID(const FString& UnitID)
{
    AActor** FoundUnit = UnitsByID.Find(UnitID);
    if (FoundUnit && *FoundUnit)
    {
        return *FoundUnit;
    }
    
    return nullptr;
}
AActor* UUnitManager::GetUnitByName(const FString& UnitName)
{
    AActor** FoundUnit = UnitsByName.Find(UnitName);
    if (FoundUnit && *FoundUnit)
    {
        return *FoundUnit;
    }
    
    return nullptr;
}
ACharacter* UUnitManager::GetPlayerUnit()
{
    return UGameplayStatics::GetPlayerCharacter(this, 0);
}
AActor* UUnitManager::GetTargetUnit()
{
    return CurrentTarget;
}
void UUnitManager::SetTargetUnit(AActor* NewTarget)
{
    // 验证目标是否是有效单位
    if (NewTarget && IsValidUnit(NewTarget))
    {
        CurrentTarget = NewTarget;
        UE_LOG(LogTemp, Log, TEXT("New target set: %s"), *NewTarget->GetName());
    }
    else
    {
        CurrentTarget = nullptr;
        UE_LOG(LogTemp, Log, TEXT("Target cleared"));
    }
}
FUnitInfo UUnitManager::GetUnitInfo(const AActor* Unit)
{
    FUnitInfo EmptyInfo;
    
    if (!IsValidUnit(Unit))
    {
        return EmptyInfo;
    }
    
    // 检查单位是否实现了接口
    if (Unit->GetClass()->ImplementsInterface(UUnitInterface::StaticClass()))
    {
        // 通过接口获取单位信息
        return IUnitInterface::Execute_GetUnitInfo(const_cast<AActor*>(Unit));
    }
    
    // 如果没有接口,尝试从组件获取信息
    return GetUnitInfoFromComponents(Unit);
}
float UUnitManager::GetUnitStat(const AActor* Unit, EUnitStat StatType)
{
    if (!IsValidUnit(Unit))
    {
        return 0.0f;
    }
    
    // 检查单位是否实现了接口
    if (Unit->GetClass()->ImplementsInterface(UUnitInterface::StaticClass()))
    {
        // 通过接口获取单位属性
        return IUnitInterface::Execute_GetUnitStat(const_cast<AActor*>(Unit), StatType);
    }
    
    // 如果没有接口,返回默认值
    return 0.0f;
}
EUnitRelation UUnitManager::GetUnitRelation(const AActor* UnitA, const AActor* UnitB)
{
    if (!IsValidUnit(UnitA) || !IsValidUnit(UnitB))
    {
        return EUnitRelation::Neutral;
    }
    
    // 获取单位信息
    FUnitInfo InfoA = GetUnitInfo(UnitA);
    FUnitInfo InfoB = GetUnitInfo(UnitB);
    
    // 实现关系判断逻辑
    // 这里简化处理,实际游戏中可能需要更复杂的阵营系统
    
    // 玩家与玩家默认友好
    if (InfoA.Type == EUnitType::Player && InfoB.Type == EUnitType::Player)
    {
        return EUnitRelation::Friendly;
    }
    
    // 其他关系逻辑...
    
    return EUnitRelation::Neutral;
}
bool UUnitManager::IsUnitInCombat(const AActor* Unit)
{
    if (!IsValidUnit(Unit))
    {
        return false;
    }
    
    // 检查单位是否实现了接口
    if (Unit->GetClass()->ImplementsInterface(UUnitInterface::StaticClass()))
    {
        // 通过接口检查战斗状态
        return IUnitInterface::Execute_IsInCombat(const_cast<AActor*>(Unit));
    }
    
    // 如果没有接口,假设不在战斗中
    return false;
}
bool UUnitManager::IsUnitAlive(const AActor* Unit)
{
    if (!IsValidUnit(Unit))
    {
        return false;
    }
    
    // 检查单位是否实现了接口
    if (Unit->GetClass()->ImplementsInterface(UUnitInterface::StaticClass()))
    {
        // 通过接口检查生命状态
        return IUnitInterface::Execute_IsAlive(const_cast<AActor*>(Unit));
    }
    
    // 如果没有接口,检查是否有健康组件或其他生命指标
    return true; // 简化实现,假设单位存活
}
void UUnitManager::RegisterUnit(AActor* Unit, const FString& UnitID)
{
    if (!Unit || UnitID.IsEmpty())
    {
        return;
    }
    
    // 注册单位到映射表
    UnitsByID.Add(UnitID, Unit);
    UnitsByName.Add(Unit->GetName(), Unit);
    
    UE_LOG(LogTemp, Log, TEXT("Registered unit: %s (ID: %s)"), *Unit->GetName(), *UnitID);
}
void UUnitManager::UnregisterUnit(const FString& UnitID)
{
    AActor** FoundUnit = UnitsByID.Find(UnitID);
    if (FoundUnit && *FoundUnit)
    {
        UnitsByName.Remove((*FoundUnit)->GetName());
        UnitsByID.Remove(UnitID);
        
        UE_LOG(LogTemp, Log, TEXT("Unregistered unit with ID: %s"), *UnitID);
    }
}
float UUnitManager::ApplyDamage(AActor* Source, AActor* Target, float Amount, EUnitDamageType DamageType)
{
    if (!IsValidUnit(Source) || !IsValidUnit(Target))
    {
        return 0.0f;
    }
    
    // 检查目标是否实现了接口
    if (Target->GetClass()->ImplementsInterface(UUnitInterface::StaticClass()))
    {
        // 通过接口应用伤害
        return IUnitInterface::Execute_TakeDamage(Target, Source, Amount, DamageType);
    }
    
    // 如果没有接口,使用UE的伤害系统
    FDamageEvent DamageEvent;
    return Target->TakeDamage(Amount, DamageEvent, nullptr, Source);
}
float UUnitManager::ApplyHealing(AActor* Source, AActor* Target, float Amount)
{
    if (!IsValidUnit(Source) || !IsValidUnit(Target))
    {
        return 0.0f;
    }
    
    // 检查目标是否实现了接口
    if (Target->GetClass()->ImplementsInterface(UUnitInterface::StaticClass()))
    {
        // 通过接口应用治疗
        return IUnitInterface::Execute_TakeHealing(Target, Source, Amount);
    }
    
    // 如果没有接口,无法应用治疗
    return 0.0f;
}
bool UUnitManager::IsValidUnit(const AActor* Unit) const
{
    return Unit != nullptr && !Unit->IsPendingKill();
}
FUnitInfo UUnitManager::GetUnitInfoFromComponents(const AActor* Unit) const
{
    FUnitInfo Info;
    
    // 填充基本信息
    Info.Name = Unit->GetName();
    Info.UnitID = ""; // 需要从某处获取
    
    // 确定单位类型
    if (Unit->IsA<ACharacter>())
    {
        ACharacter* Character = const_cast<ACharacter*>(Cast<ACharacter>(Unit));
        if (Character && Character->IsPlayerControlled())
        {
            Info.Type = EUnitType::Player;
        }
        else
        {
            Info.Type = EUnitType::NPC;
        }
    }
    else
    {
        Info.Type = EUnitType::GameObject;
    }
    
    // 尝试从组件获取其他信息
    // 这取决于游戏的组件设计
    
    return Info;
}

在蓝图中使用单位管理系统:

ebnf

// 获取目标单位示例
UnitManager = GetGameInstance().GetSubsystem(UnitManager)
TargetUnit = UnitManager.GetTargetUnit()
if (TargetUnit != None):
    TargetInfo = UnitManager.GetUnitInfo(TargetUnit)
    TargetName = TargetInfo.Name
    TargetHealth = UnitManager.GetUnitStat(TargetUnit, EUnitStat.Health)
    TargetMaxHealth = UnitManager.GetUnitStat(TargetUnit, EUnitStat.MaxHealth)
    
    # 更新目标信息UI
    TargetNameText.SetText(TargetName)
    TargetHealthBar.SetPercent(TargetHealth / TargetMaxHealth)

13.2 创建简单的单位窗体

在UE5.2中,我们可以创建类似魔兽世界单位框架的UI元素,用于显示玩家、目标和其他单位的信息。

13.2.1 创建窗体

首先,我们需要定义单位窗体的XML结构。

单位窗体XML定义 :

xml

<!-- UnitFrame.xml -->
<Frame name="UnitFrame" width="200" height="80">
    <Backdrop bgColor="#222222" borderColor="#444444" cornerRadius="5">
        <BorderSize val="2" />
    </Backdrop>
    
    <!-- 单位名称 -->
    <FontString name="$parentName" text="Unit Name" font="Arial" size="14">
        <Anchors>
            <Anchor point="TOP" relativeTo="$parent" relativePoint="TOP" y="5" />
        </Anchors>
        <Color r="1.0" g="1.0" b="1.0" />
    </FontString>
    
    <!-- 生命条 -->
    <StatusBar name="$parentHealthBar" height="20">
        <Anchors>
            <Anchor point="LEFT" relativeTo="$parent" relativePoint="LEFT" x="5" />
            <Anchor point="RIGHT" relativeTo="$parent" relativePoint="RIGHT" x="-5" />
            <Anchor point="TOP" relativeTo="$parentName" relativePoint="BOTTOM" y="5" />
        </Anchors>
        <BarTexture file="Interface/UnitFrames/HealthBar" />
        <BarColor r="0.0" g="0.7" b="0.0" />
    </StatusBar>
    
    <!-- 生命值文本 -->
    <FontString name="$parentHealthText" text="100/100" font="Arial" size="12">
        <Anchors>
            <Anchor point="CENTER" relativeTo="$parentHealthBar" relativePoint="CENTER" />
        </Anchors>
        <Color r="1.0" g="1.0" b="1.0" />
    </FontString>
    
    <!-- 资源条(法力/怒气等) -->
    <StatusBar name="$parentResourceBar" height="15">
        <Anchors>
            <Anchor point="LEFT" relativeTo="$parent" relativePoint="LEFT" x="5" />
            <Anchor point="RIGHT" relativeTo="$parent" relativePoint="RIGHT" x="-5" />
            <Anchor point="TOP" relativeTo="$parentHealthBar" relativePoint="BOTTOM" y="5" />
        </Anchors>
        <BarTexture file="Interface/UnitFrames/ResourceBar" />
        <BarColor r="0.0" g="0.0" b="0.8" />
    </StatusBar>
    
    <!-- 资源值文本 -->
    <FontString name="$parentResourceText" text="100/100" font="Arial" size="10">
        <Anchors>
            <Anchor point="CENTER" relativeTo="$parentResourceBar" relativePoint="CENTER" />
        </Anchors>
        <Color r="1.0" g="1.0" b="1.0" />
    </FontString>
    
    <!-- 等级标签 -->
    <FontString name="$parentLevelText" text="1" font="Arial" size="12">
        <Anchors>
            <Anchor point="BOTTOMLEFT" relativeTo="$parent" relativePoint="BOTTOMLEFT" x="5" y="5" />
        </Anchors>
        <Color r="1.0" g="0.82" b="0.0" />
    </FontString>
    
    <!-- 初始化脚本 -->
    <Scripts>
        <OnLoad>
            UnitFrame_OnLoad(self)
        </OnLoad>
        <OnUpdate>
            UnitFrame_OnUpdate(self, dt)
        </OnUpdate>
    </Scripts>
</Frame>

在UE5.2中实现单位窗体 :

cpp

// UnitFrameWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/TextBlock.h"
#include "Components/ProgressBar.h"
#include "Components/Border.h"
#include "UnitTypes.h"
#include "UnitFrameWidget.generated.h"
UENUM(BlueprintType)
enum class EUnitFrameType : uint8
{
    Player,
    Target,
    Pet,
    Party,
    Custom
};
/**
 * 单位框架小部件 - 显示单位信息的UI元素
 */
UCLASS()
class MYGAME_API UUnitFrameWidget : public UUserWidget
{
    GENERATED_BODY()
    
protected:
    virtual void NativeConstruct() override;
    virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
    
    // UI元素
    UPROPERTY(meta = (BindWidget))
    UTextBlock* NameText;
    
    UPROPERTY(meta = (BindWidget))
    UProgressBar* HealthBar;
    
    UPROPERTY(meta = (BindWidget))
    UTextBlock* HealthText;
    
    UPROPERTY(meta = (BindWidget))
    UProgressBar* ResourceBar;
    
    UPROPERTY(meta = (BindWidget))
    UTextBlock* ResourceText;
    
    UPROPERTY(meta = (BindWidget))
    UTextBlock* LevelText;
    
    UPROPERTY(meta = (BindWidget))
    UBorder* FrameBorder;
    
public:
    // 设置窗体类型
    UFUNCTION(BlueprintCallable, Category = "Unit Frame")
    void SetFrameType(EUnitFrameType Type);
    
    // 设置跟踪的单位
    UFUNCTION(BlueprintCallable, Category = "Unit Frame")
    void SetUnit(AActor* InUnit);
    
    // 获取当前单位
    UFUNCTION(BlueprintCallable, Category = "Unit Frame")
    AActor* GetUnit() const;
    
    // 更新显示
    UFUNCTION(BlueprintCallable, Category = "Unit Frame")
    void UpdateDisplay();
    
    // 设置资源类型(法力、怒气等)
    UFUNCTION(BlueprintCallable, Category = "Unit Frame")
    void SetResourceType(EUnitStat ResourceType);
    
private:
    // 框架类型
    UPROPERTY()
    EUnitFrameType FrameType;
    
    // 跟踪的单位
    UPROPERTY()
    AActor* TrackingUnit;
    
    // 资源类型
    UPROPERTY()
    EUnitStat CurrentResourceType;
    
    // 单位管理器引用
    UPROPERTY()
    class UUnitManager* UnitManager;
    
    // 更新单位(基于框架类型)
    void UpdateTrackingUnit();
    
    // 设置生命条颜色(基于关系)
    void SetHealthBarColor(EUnitRelation Relation);
};

cpp

// UnitFrameWidget.cpp
#include "UnitFrameWidget.h"
#include "UnitManager.h"
#include "Kismet/GameplayStatics.h"
#include "Components/Border.h"
void UUnitFrameWidget::NativeConstruct()
{
    Super::NativeConstruct();
    
    // 初始化属性
    FrameType = EUnitFrameType::Custom;
    TrackingUnit = nullptr;
    CurrentResourceType = EUnitStat::Mana;
    
    // 获取单位管理器
    UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
    
    // 设置默认外观
    if (FrameBorder)
    {
        FrameBorder->SetBrushColor(FLinearColor(0.2f, 0.2f, 0.2f, 0.8f));
    }
    
    // 更新显示
    UpdateDisplay();
}
void UUnitFrameWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
    Super::NativeTick(MyGeometry, InDeltaTime);
    
    // 根据框架类型更新跟踪的单位
    UpdateTrackingUnit();
    
    // 更新显示
    UpdateDisplay();
}
void UUnitFrameWidget::SetFrameType(EUnitFrameType Type)
{
    FrameType = Type;
    
    // 根据类型更新跟踪的单位
    UpdateTrackingUnit();
    
    // 更新显示
    UpdateDisplay();
}
void UUnitFrameWidget::SetUnit(AActor* InUnit)
{
    TrackingUnit = InUnit;
    
    // 如果设置了自定义单位,框架类型变为自定义
    if (InUnit)
    {
        FrameType = EUnitFrameType::Custom;
    }
    
    // 更新显示
    UpdateDisplay();
}
AActor* UUnitFrameWidget::GetUnit() const
{
    return TrackingUnit;
}
void UUnitFrameWidget::UpdateDisplay()
{
    // 如果没有跟踪单位或单位管理器,显示空白
    if (!TrackingUnit || !UnitManager)
    {
        if (NameText)
        {
            NameText->SetText(FText::FromString("No Unit"));
        }
        
        if (HealthBar)
        {
            HealthBar->SetPercent(0.0f);
        }
        
        if (HealthText)
        {
            HealthText->SetText(FText::FromString("0/0"));
        }
        
        if (ResourceBar)
        {
            ResourceBar->SetPercent(0.0f);
        }
        
        if (ResourceText)
        {
            ResourceText->SetText(FText::FromString("0/0"));
        }
        
        if (LevelText)
        {
            LevelText->SetText(FText::FromString("??"));
        }
        
        return;
    }
    
    // 获取单位信息
    FUnitInfo UnitInfo = UnitManager->GetUnitInfo(TrackingUnit);
    
    // 设置名称
    if (NameText)
    {
        NameText->SetText(FText::FromString(UnitInfo.Name));
    }
    
    // 设置等级
    if (LevelText)
    {
        LevelText->SetText(FText::AsNumber(UnitInfo.Level));
    }
    
    // 设置生命值
    float Health = UnitManager->GetUnitStat(TrackingUnit, EUnitStat::Health);
    float MaxHealth = UnitManager->GetUnitStat(TrackingUnit, EUnitStat::MaxHealth);
    
    if (HealthBar && MaxHealth > 0.0f)
    {
        HealthBar->SetPercent(Health / MaxHealth);
    }
    
    if (HealthText)
    {
        HealthText->SetText(FText::FromString(FString::Printf(TEXT("%.0f/%.0f"), Health, MaxHealth)));
    }
    
    // 设置资源值
    float Resource = UnitManager->GetUnitStat(TrackingUnit, CurrentResourceType);
    float MaxResource = UnitManager->GetUnitStat(TrackingUnit, GetMaxResourceStat(CurrentResourceType));
    
    if (ResourceBar && MaxResource > 0.0f)
    {
        ResourceBar->SetPercent(Resource / MaxResource);
    }
    
    if (ResourceText)
    {
        ResourceText->SetText(FText::FromString(FString::Printf(TEXT("%.0f/%.0f"), Resource, MaxResource)));
    }
    
    // 设置生命条颜色
    if (FrameType == EUnitFrameType::Target)
    {
        // 获取与玩家的关系
        ACharacter* PlayerChar = UnitManager->GetPlayerUnit();
        if (PlayerChar)
        {
            EUnitRelation Relation = UnitManager->GetUnitRelation(PlayerChar, TrackingUnit);
            SetHealthBarColor(Relation);
        }
    }
}
void UUnitFrameWidget::SetResourceType(EUnitStat ResourceType)
{
    CurrentResourceType = ResourceType;
    
    // 根据资源类型设置颜色
    if (ResourceBar)
    {
        switch (ResourceType)
        {
            case EUnitStat::Mana:
                ResourceBar->SetFillColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.8f));
                break;
            case EUnitStat::Stamina:
                ResourceBar->SetFillColorAndOpacity(FLinearColor(0.8f, 0.7f, 0.0f));
                break;
            default:
                ResourceBar->SetFillColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f));
                break;
        }
    }
    
    // 更新显示
    UpdateDisplay();
}
void UUnitFrameWidget::UpdateTrackingUnit()
{
    if (!UnitManager)
    {
        return;
    }
    
    // 根据框架类型更新跟踪的单位
    switch (FrameType)
    {
        case EUnitFrameType::Player:
            TrackingUnit = UnitManager->GetPlayerUnit();
            break;
        case EUnitFrameType::Target:
            TrackingUnit = UnitManager->GetTargetUnit();
            break;
        case EUnitFrameType::Pet:
            // 实现获取宠物的逻辑
            break;
        case EUnitFrameType::Party:
            // 实现获取队伍成员的逻辑
            break;
        case EUnitFrameType::Custom:
            // 使用当前设置的自定义单位
            break;
    }
}
void UUnitFrameWidget::SetHealthBarColor(EUnitRelation Relation)
{
    if (!HealthBar)
    {
        return;
    }
    
    switch (Relation)
    {
        case EUnitRelation::Friendly:
            HealthBar->SetFillColorAndOpacity(FLinearColor(0.0f, 0.7f, 0.0f));
            break;
        case EUnitRelation::Neutral:
            HealthBar->SetFillColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.0f));
            break;
        case EUnitRelation::Hostile:
            HealthBar->SetFillColorAndOpacity(FLinearColor(0.7f, 0.0f, 0.0f));
            break;
    }
}
EUnitStat UUnitFrameWidget::GetMaxResourceStat(EUnitStat ResourceType) const
{
    switch (ResourceType)
    {
        case EUnitStat::Mana:
            return EUnitStat::MaxMana;
        case EUnitStat::Stamina:
            return EUnitStat::MaxStamina;
        default:
            return EUnitStat::MaxMana;
    }
}

13.2.2 添加数据域

接下来,我们需要为单位窗体添加数据字段,用于显示和更新单位信息。

在单位窗体中添加数据字段 :

cpp

// UnitFrameData.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "UnitTypes.h"
#include "UnitFrameData.generated.h"
/**
 * 单位窗体数据 - 存储单位框架需要的数据
 */
UCLASS(BlueprintType)
class MYGAME_API UUnitFrameData : public UObject
{
    GENERATED_BODY()
    
public:
    // 单位ID
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    FString UnitID;
    
    // 单位名称
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    FString Name;
    
    // 单位等级
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    int32 Level;
    
    // 单位类型
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    EUnitType Type;
    
    // 与玩家的关系
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    EUnitRelation Relation;
    
    // 是否在战斗中
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    bool bInCombat;
    
    // 是否存活
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    bool bIsAlive;
    
    // 生命值
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    float Health;
    
    // 最大生命值
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    float MaxHealth;
    
    // 资源值(法力、怒气等)
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    float Resource;
    
    // 最大资源值
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    float MaxResource;
    
    // 职业/类型颜色
    UPROPERTY(BlueprintReadWrite, Category = "Unit Frame Data")
    FLinearColor ClassColor;
};
// 扩展UnitFrameWidget.h
// 在UUnitFrameWidget类中添加
private:
    // 单位数据缓存
    UPROPERTY()
    UUnitFrameData* FrameData;
    
    // 更新缓存的单位数据
    void UpdateFrameData();
    
    // 从缓存数据更新UI
    void UpdateUIFromData();
// UnitFrameWidget.cpp 中添加实现
void UUnitFrameWidget::NativeConstruct()
{
    Super::NativeConstruct();
    
    // 初始化数据
    FrameData = NewObject<UUnitFrameData>(this);
    
    // ... 现有代码 ...
}
void UUnitFrameWidget::UpdateDisplay()
{
    // 更新缓存数据
    UpdateFrameData();
    
    // 使用缓存数据更新UI
    UpdateUIFromData();
}
void UUnitFrameWidget::UpdateFrameData()
{
    if (!TrackingUnit || !UnitManager || !FrameData)
    {
        return;
    }
    
    // 获取单位信息
    FUnitInfo UnitInfo = UnitManager->GetUnitInfo(TrackingUnit);
    
    // 更新基本信息
    FrameData->UnitID = UnitInfo.UnitID;
    FrameData->Name = UnitInfo.Name;
    FrameData->Level = UnitInfo.Level;
    FrameData->Type = UnitInfo.Type;
    FrameData->bInCombat = UnitInfo.bIsInCombat;
    FrameData->bIsAlive = UnitInfo.bIsAlive;
    
    // 更新关系
    ACharacter* PlayerChar = UnitManager->GetPlayerUnit();
    if (PlayerChar)
    {
        FrameData->Relation = UnitManager->GetUnitRelation(PlayerChar, TrackingUnit);
    }
    else
    {
        FrameData->Relation = EUnitRelation::Neutral;
    }
    
    // 更新生命值
    FrameData->Health = UnitManager->GetUnitStat(TrackingUnit, EUnitStat::Health);
    FrameData->MaxHealth = UnitManager->GetUnitStat(TrackingUnit, EUnitStat::MaxHealth);
    
    // 更新资源值
    FrameData->Resource = UnitManager->GetUnitStat(TrackingUnit, CurrentResourceType);
    FrameData->MaxResource = UnitManager->GetUnitStat(TrackingUnit, GetMaxResourceStat(CurrentResourceType));
    
    // 设置职业/类型颜色
    SetClassColor();
}
void UUnitFrameWidget::UpdateUIFromData()
{
    if (!FrameData)
    {
        return;
    }
    
    // 设置名称
    if (NameText)
    {
        NameText->SetText(FText::FromString(FrameData->Name));
        
        // 根据关系设置名称颜色
        switch (FrameData->Relation)
        {
            case EUnitRelation::Friendly:
                NameText->SetColorAndOpacity(FSlateColor(FLinearColor(0.0f, 0.7f, 0.0f)));
                break;
            case EUnitRelation::Neutral:
                NameText->SetColorAndOpacity(FSlateColor(FLinearColor(0.7f, 0.7f, 0.0f)));
                break;
            case EUnitRelation::Hostile:
                NameText->SetColorAndOpacity(FSlateColor(FLinearColor(0.7f, 0.0f, 0.0f)));
                break;
        }
    }
    
    // 设置等级
    if (LevelText)
    {
        LevelText->SetText(FText::AsNumber(FrameData->Level));
    }
    
    // 设置生命值
    if (HealthBar && FrameData->MaxHealth > 0.0f)
    {
        HealthBar->SetPercent(FrameData->Health / FrameData->MaxHealth);
    }
    
    if (HealthText)
    {
        HealthText->SetText(FText::FromString(FString::Printf(TEXT("%.0f/%.0f"), FrameData->Health, FrameData->MaxHealth)));
    }
    
    // 设置资源值
    if (ResourceBar && FrameData->MaxResource > 0.0f)
    {
        ResourceBar->SetPercent(FrameData->Resource / FrameData->MaxResource);
    }
    
    if (ResourceText)
    {
        ResourceText->SetText(FText::FromString(FString::Printf(TEXT("%.0f/%.0f"), FrameData->Resource, FrameData->MaxResource)));
    }
    
    // 设置战斗状态指示器
    if (FrameBorder)
    {
        if (FrameData->bInCombat)
        {
            // 战斗中有红色边框
            FrameBorder->SetBrushColor(FLinearColor(0.5f, 0.1f, 0.1f, 0.8f));
        }
        else
        {
            // 非战斗状态恢复正常
            FrameBorder->SetBrushColor(FLinearColor(0.2f, 0.2f, 0.2f, 0.8f));
        }
    }
    
    // 如果单位已死亡,灰化显示
    if (!FrameData->bIsAlive)
    {
        if (NameText)
        {
            NameText->SetColorAndOpacity(FSlateColor(FLinearColor(0.5f, 0.5f, 0.5f)));
        }
        
        if (HealthBar)
        {
            HealthBar->SetFillColorAndOpacity(FLinearColor(0.3f, 0.3f, 0.3f));
        }
        
        if (ResourceBar)
        {
            ResourceBar->SetFillColorAndOpacity(FLinearColor(0.3f, 0.3f, 0.3f));
        }
    }
}
void UUnitFrameWidget::SetClassColor()
{
    if (!FrameData)
    {
        return;
    }
    
    // 根据单位类型设置职业颜色
    // 这是一个简化的示例,实际游戏中可能基于单位职业
    switch (FrameData->Type)
    {
        case EUnitType::Player:
            FrameData->ClassColor = FLinearColor(0.0f, 0.7f, 0.7f); // 青色
            break;
        case EUnitType::NPC:
            FrameData->ClassColor = FLinearColor(1.0f, 1.0f, 1.0f); // 白色
            break;
        case EUnitType::Creature:
            FrameData->ClassColor = FLinearColor(0.7f, 0.0f, 0.7f); // 紫色
            break;
        default:
            FrameData->ClassColor = FLinearColor(0.7f, 0.7f, 0.7f); // 灰色
            break;
    }
}

13.2.3 设置窗体事件处理程序

最后,我们需要实现单位窗体的事件处理程序,以响应用户交互和游戏事件。

单位窗体事件处理 :

cpp

// UnitFrameWidget.h 中添加
protected:
    // 鼠标输入事件
    virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
    virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
    virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
    
    // 事件响应
    UFUNCTION()
    void OnUnitClicked();
    
    UFUNCTION()
    void OnUnitEnter();
    
    UFUNCTION()
    void OnUnitLeave();
    
public:
    // 事件委托
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUnitFrameClicked, AActor*, Unit);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUnitFrameEnter, AActor*, Unit);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUnitFrameLeave, AActor*, Unit);
    
    // 事件委托实例
    UPROPERTY(BlueprintAssignable, Category = "Unit Frame|Events")
    FOnUnitFrameClicked OnFrameClicked;
    
    UPROPERTY(BlueprintAssignable, Category = "Unit Frame|Events")
    FOnUnitFrameEnter OnFrameEnter;
    
    UPROPERTY(BlueprintAssignable, Category = "Unit Frame|Events")
    FOnUnitFrameLeave OnFrameLeave;
// UnitFrameWidget.cpp 中添加实现
FReply UUnitFrameWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
    // 处理鼠标点击
    if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
    {
        OnUnitClicked();
        return FReply::Handled();
    }
    
    return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
void UUnitFrameWidget::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
    Super::NativeOnMouseEnter(InGeometry, InMouseEvent);
    
    OnUnitEnter();
}
void UUnitFrameWidget::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
{
    Super::NativeOnMouseLeave(InMouseEvent);
    
    OnUnitLeave();
}
void UUnitFrameWidget::OnUnitClicked()
{
    // 如果有有效单位,设置为目标并广播事件
    if (TrackingUnit && UnitManager)
    {
        // 如果点击的不是玩家框架,设置为目标
        if (FrameType != EUnitFrameType::Player)
        {
            UnitManager->SetTargetUnit(TrackingUnit);
        }
        
        // 广播点击事件
        OnFrameClicked.Broadcast(TrackingUnit);
        
        UE_LOG(LogTemp, Log, TEXT("Unit frame clicked: %s"), *TrackingUnit->GetName());
    }
}
void UUnitFrameWidget::OnUnitEnter()
{
    // 当鼠标进入框架时
    if (TrackingUnit)
    {
        // 高亮框架
        if (FrameBorder)
        {
            FrameBorder->SetBrushColor(FLinearColor(0.3f, 0.3f, 0.3f, 0.9f));
        }
        
        // 广播进入事件
        OnFrameEnter.Broadcast(TrackingUnit);
        
        UE_LOG(LogTemp, Log, TEXT("Mouse entered unit frame: %s"), *TrackingUnit->GetName());
    }
}
void UUnitFrameWidget::OnUnitLeave()
{
    // 当鼠标离开框架时
    if (TrackingUnit)
    {
        // 恢复正常外观
        if (FrameBorder)
        {
            FLinearColor BorderColor = FrameData && FrameData->bInCombat ? 
                FLinearColor(0.5f, 0.1f, 0.1f, 0.8f) : FLinearColor(0.2f, 0.2f, 0.2f, 0.8f);
            
            FrameBorder->SetBrushColor(BorderColor);
        }
        
        // 广播离开事件
        OnFrameLeave.Broadcast(TrackingUnit);
        
        UE_LOG(LogTemp, Log, TEXT("Mouse left unit frame: %s"), *TrackingUnit->GetName());
    }
}

创建单位框架管理器 :

cpp

// UnitFrameManager.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UnitFrameWidget.h"
#include "UnitFrameManager.generated.h"
/**
 * 单位框架管理器 - 管理游戏中的所有单位框架
 */
UCLASS()
class MYGAME_API UUnitFrameManager : public UGameInstanceSubsystem
{
    GENERATED_BODY()
    
public:
    // 初始化和清理
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    
    // 创建玩家框架
    UFUNCTION(BlueprintCallable, Category = "Unit Frames")
    UUnitFrameWidget* CreatePlayerFrame(UObject* WorldContextObject, const FName& WidgetName = "PlayerFrame");
    
    // 创建目标框架
    UFUNCTION(BlueprintCallable, Category = "Unit Frames")
    UUnitFrameWidget* CreateTargetFrame(UObject* WorldContextObject, const FName& WidgetName = "TargetFrame");
    
    // 创建自定义单位框架
    UFUNCTION(BlueprintCallable, Category = "Unit Frames")
    UUnitFrameWidget* CreateCustomFrame(UObject* WorldContextObject, AActor* Unit, const FName& WidgetName = NAME_None);
    
    // 获取框架引用
    UFUNCTION(BlueprintCallable, Category = "Unit Frames")
    UUnitFrameWidget* GetFrame(const FName& FrameName);
    
    // 更新所有框架
    UFUNCTION(BlueprintCallable, Category = "Unit Frames")
    void UpdateAllFrames();
    
private:
    // 已创建的框架映射
    UPROPERTY()
    TMap<FName, UUnitFrameWidget*> Frames;
    
    // 单位管理器引用
    UPROPERTY()
    class UUnitManager* UnitManager;
    
    // 创建框架的通用方法
    UUnitFrameWidget* CreateFrame(UObject* WorldContextObject, EUnitFrameType FrameType, AActor* Unit, const FName& WidgetName);
};
// UnitFrameManager.cpp
#include "UnitFrameManager.h"
#include "UnitManager.h"
#include "Kismet/GameplayStatics.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
void UUnitFrameManager::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    
    // 清空框架映射
    Frames.Empty();
    
    // 获取单位管理器
    UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
    
    UE_LOG(LogTemp, Log, TEXT("Unit Frame Manager initialized"));
}
void UUnitFrameManager::Deinitialize()
{
    // 移除所有框架
    for (auto& Pair : Frames)
    {
        if (Pair.Value)
        {
            Pair.Value->RemoveFromParent();
        }
    }
    
    Frames.Empty();
    
    Super::Deinitialize();
}
UUnitFrameWidget* UUnitFrameManager::CreatePlayerFrame(UObject* WorldContextObject, const FName& WidgetName)
{
    return CreateFrame(WorldContextObject, EUnitFrameType::Player, nullptr, WidgetName);
}
UUnitFrameWidget* UUnitFrameManager::CreateTargetFrame(UObject* WorldContextObject, const FName& WidgetName)
{
    return CreateFrame(WorldContextObject, EUnitFrameType::Target, nullptr, WidgetName);
}
UUnitFrameWidget* UUnitFrameManager::CreateCustomFrame(UObject* WorldContextObject, AActor* Unit, const FName& WidgetName)
{
    return CreateFrame(WorldContextObject, EUnitFrameType::Custom, Unit, WidgetName);
}
UUnitFrameWidget* UUnitFrameManager::GetFrame(const FName& FrameName)
{
    UUnitFrameWidget** FoundFrame = Frames.Find(FrameName);
    if (FoundFrame && *FoundFrame)
    {
        return *FoundFrame;
    }
    
    return nullptr;
}
void UUnitFrameManager::UpdateAllFrames()
{
    for (auto& Pair : Frames)
    {
        if (Pair.Value)
        {
            Pair.Value->UpdateDisplay();
        }
    }
}
UUnitFrameWidget* UUnitFrameManager::CreateFrame(UObject* WorldContextObject, EUnitFrameType FrameType, AActor* Unit, const FName& WidgetName)
{
    // 确定框架名称
    FName ActualWidgetName = WidgetName;
    if (ActualWidgetName == NAME_None)
    {
        // 生成唯一名称
        ActualWidgetName = FName(*FString::Printf(TEXT("UnitFrame_%d"), Frames.Num()));
    }
    
    // 检查是否已存在
    UUnitFrameWidget* ExistingFrame = GetFrame(ActualWidgetName);
    if (ExistingFrame)
    {
        UE_LOG(LogTemp, Warning, TEXT("Frame with name %s already exists"), *ActualWidgetName.ToString());
        return ExistingFrame;
    }
    
    // 创建新框架
    UUnitFrameWidget* NewFrame = CreateWidget<UUnitFrameWidget>(WorldContextObject, UUnitFrameWidget::StaticClass());
    if (!NewFrame)
    {
        UE_LOG(LogTemp, Error, TEXT("Failed to create unit frame widget"));
        return nullptr;
    }
    
    // 设置框架类型和单位
    NewFrame->SetFrameType(FrameType);
    if (Unit && FrameType == EUnitFrameType::Custom)
    {
        NewFrame->SetUnit(Unit);
    }
    
    // 存储框架引用
    Frames.Add(ActualWidgetName, NewFrame);
    
    UE_LOG(LogTemp, Log, TEXT("Created unit frame: %s"), *ActualWidgetName.ToString());
    
    return NewFrame;
}

13.3 使用API

在开发游戏过程中,我们需要使用这些API来创建和管理UI元素,显示单位信息,并响应游戏事件。

13.3.1 显示和隐藏窗体

首先,我们需要实现窗体的显示和隐藏功能。

在C++中显示和隐藏窗体 :

cpp

// GameHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "UnitFrameWidget.h"
#include "GameHUD.generated.h"
/**
 * 游戏HUD - 管理游戏界面元素
 */
UCLASS()
class MYGAME_API AGameHUD : public AHUD
{
    GENERATED_BODY()
    
public:
    AGameHUD();
    
    virtual void BeginPlay() override;
    virtual void Tick(float DeltaTime) override;
    
    // 显示单位框架
    UFUNCTION(BlueprintCallable, Category = "Game HUD")
    void ShowUnitFrame(const FName& FrameName);
    
    // 隐藏单位框架
    UFUNCTION(BlueprintCallable, Category = "Game HUD")
    void HideUnitFrame(const FName& FrameName);
    
    // 切换单位框架可见性
    UFUNCTION(BlueprintCallable, Category = "Game HUD")
    void ToggleUnitFrame(const FName& FrameName);
    
    // 显示所有单位框架
    UFUNCTION(BlueprintCallable, Category = "Game HUD")
    void ShowAllUnitFrames();
    
    // 隐藏所有单位框架
    UFUNCTION(BlueprintCallable, Category = "Game HUD")
    void HideAllUnitFrames();
    
private:
    // 玩家框架
    UPROPERTY()
    UUnitFrameWidget* PlayerFrame;
    
    // 目标框架
    UPROPERTY()
    UUnitFrameWidget* TargetFrame;
    
    // 自定义框架列表
    UPROPERTY()
    TMap<FName, UUnitFrameWidget*> CustomFrames;
    
    // 单位框架管理器
    UPROPERTY()
    class UUnitFrameManager* FrameManager;
    
    // 初始化框架
    void InitializeFrames();
    
    // 处理目标变更
    UFUNCTION()
    void OnTargetChanged(AActor* NewTarget);
};
// GameHUD.cpp
#include "GameHUD.h"
#include "UnitFrameManager.h"
#include "UnitManager.h"
#include "Blueprint/UserWidget.h"
#include "Kismet/GameplayStatics.h"
AGameHUD::AGameHUD()
{
    // 默认构造函数
}
void AGameHUD::BeginPlay()
{
    Super::BeginPlay();
    
    // 获取框架管理器
    FrameManager = GetGameInstance()->GetSubsystem<UUnitFrameManager>();
    
    // 初始化框架
    InitializeFrames();
    
    // 注册目标变更事件
    UUnitManager* UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
    if (UnitManager)
    {
        // 这里假设UnitManager有一个OnTargetChanged委托
        // UnitManager->OnTargetChanged.AddDynamic(this, &AGameHUD::OnTargetChanged);
    }
}
void AGameHUD::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    
    // 更新框架位置或其他动态属性
}
void AGameHUD::ShowUnitFrame(const FName& FrameName)
{
    UUnitFrameWidget* Frame = FrameManager->GetFrame(FrameName);
    if (Frame)
    {
        Frame->SetVisibility(ESlateVisibility::Visible);
    }
}
void AGameHUD::HideUnitFrame(const FName& FrameName)
{
    UUnitFrameWidget* Frame = FrameManager->GetFrame(FrameName);
    if (Frame)
    {
        Frame->SetVisibility(ESlateVisibility::Collapsed);
    }
}
void AGameHUD::ToggleUnitFrame(const FName& FrameName)
{
    UUnitFrameWidget* Frame = FrameManager->GetFrame(FrameName);
    if (Frame)
    {
        ESlateVisibility CurrentVis = Frame->GetVisibility();
        Frame->SetVisibility(CurrentVis == ESlateVisibility::Visible ? ESlateVisibility::Collapsed : ESlateVisibility::Visible);
    }
}
void AGameHUD::ShowAllUnitFrames()
{
    if (PlayerFrame)
    {
        PlayerFrame->SetVisibility(ESlateVisibility::Visible);
    }
    
    if (TargetFrame)
    {
        TargetFrame->SetVisibility(ESlateVisibility::Visible);
    }
    
    for (auto& Pair : CustomFrames)
    {
        if (Pair.Value)
        {
            Pair.Value->SetVisibility(ESlateVisibility::Visible);
        }
    }
}
void AGameHUD::HideAllUnitFrames()
{
    if (PlayerFrame)
    {
        PlayerFrame->SetVisibility(ESlateVisibility::Collapsed);
    }
    
    if (TargetFrame)
    {
        TargetFrame->SetVisibility(ESlateVisibility::Collapsed);
    }
    
    for (auto& Pair : CustomFrames)
    {
        if (Pair.Value)
        {
            Pair.Value->SetVisibility(ESlateVisibility::Collapsed);
        }
    }
}
void AGameHUD::InitializeFrames()
{
    if (!FrameManager)
    {
        UE_LOG(LogTemp, Error, TEXT("Frame Manager not available"));
        return;
    }
    
    // 创建玩家框架
    PlayerFrame = FrameManager->CreatePlayerFrame(this, "PlayerFrame");
    if (PlayerFrame)
    {
        PlayerFrame->AddToViewport();
        
        // 设置位置
        PlayerFrame->SetPositionInViewport(FVector2D(50, 50));
    }
    
    // 创建目标框架
    TargetFrame = FrameManager->CreateTargetFrame(this, "TargetFrame");
    if (TargetFrame)
    {
        TargetFrame->AddToViewport();
        
        // 设置位置
        TargetFrame->SetPositionInViewport(FVector2D(50, 150));
        
        // 初始隐藏,直到选择目标
        TargetFrame->SetVisibility(ESlateVisibility::Collapsed);
    }
}
void AGameHUD::OnTargetChanged(AActor* NewTarget)
{
    // 处理目标变更
    if (TargetFrame)
    {
        if (NewTarget)
        {
            // 显示目标框架
            TargetFrame->SetVisibility(ESlateVisibility::Visible);
            
            // 更新目标框架
            TargetFrame->UpdateDisplay();
        }
        else
        {
            // 隐藏目标框架
            TargetFrame->SetVisibility(ESlateVisibility::Collapsed);
        }
    }
}

在蓝图中显示和隐藏窗体 :

reasonml

// 显示玩家框架
GameHUD = GetOwningPlayerController().GetHUD()
GameHUD.ShowUnitFrame("PlayerFrame")
// 隐藏目标框架
GameHUD.HideUnitFrame("TargetFrame")
// 切换UI可见性
function ToggleUI():
    if (bUIVisible):
        GameHUD.HideAllUnitFrames()
        bUIVisible = false
    else:
        GameHUD.ShowAllUnitFrames()
        bUIVisible = true

13.3.2 实现简单的更新函数

接下来,我们需要实现更新函数来保持UI显示的最新状态。

实现UI更新逻辑 :

cpp

// UIUpdater.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "UIUpdater.generated.h"
/**
 * UI更新器组件 - 定期更新UI元素
 */
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UUIUpdater : public UActorComponent
{
    GENERATED_BODY()
public:    
    UUIUpdater();
    
    virtual void BeginPlay() override;
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    
    // 设置更新间隔
    UFUNCTION(BlueprintCallable, Category = "UI Updater")
    void SetUpdateInterval(float Interval);
    
    // 启用/禁用更新
    UFUNCTION(BlueprintCallable, Category = "UI Updater")
    void SetEnabled(bool bEnable);
    
private:
    // 更新间隔(秒)
    UPROPERTY(EditAnywhere, Category = "UI Updater")
    float UpdateInterval;
    
    // 是否启用
    UPROPERTY(EditAnywhere, Category = "UI Updater")
    bool bEnabled;
    
    // 计时器
    float TimeSinceLastUpdate;
    
    // 单位框架管理器
    UPROPERTY()
    class UUnitFrameManager* FrameManager;
    
    // 执行更新
    void PerformUpdate();
};
// UIUpdater.cpp
#include "UIUpdater.h"
#include "UnitFrameManager.h"
UUIUpdater::UUIUpdater()
{
    PrimaryComponentTick.bCanEverTick = true;
    
    UpdateInterval = 0.1f; // 默认每0.1秒更新一次
    bEnabled = true;
    TimeSinceLastUpdate = 0.0f;
}
void UUIUpdater::BeginPlay()
{
    Super::BeginPlay();
    
    // 获取框架管理器
    FrameManager = GetGameInstance()->GetSubsystem<UUnitFrameManager>();
}
void UUIUpdater::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    if (!bEnabled)
    {
        return;
    }
    
    // 累计时间
    TimeSinceLastUpdate += DeltaTime;
    
    // 检查是否应该更新
    if (TimeSinceLastUpdate >= UpdateInterval)
    {
        PerformUpdate();
        TimeSinceLastUpdate = 0.0f;
    }
}
void UUIUpdater::SetUpdateInterval(float Interval)
{
    UpdateInterval = FMath::Max(0.01f, Interval);
}
void UUIUpdater::SetEnabled(bool bEnable)
{
    bEnabled = bEnable;
}
void UUIUpdater::PerformUpdate()
{
    if (!FrameManager)
    {
        return;
    }
    
    // 更新所有单位框架
    FrameManager->UpdateAllFrames();
}

在蓝图中使用UI更新器 :

awk

// 游戏角色蓝图初始化
function BeginPlay():
    Super.BeginPlay()
    
    // 添加UI更新器组件
    UIUpdater = AddComponent("UIUpdater", UIUpdater)
    
    // 设置更新频率
    UIUpdater.SetUpdateInterval(0.2)  // 每0.2秒更新一次UI

创建专用更新函数 :

cpp

// PlayerController.h 中添加
// UI更新函数
UFUNCTION(BlueprintCallable, Category = "UI")
void UpdatePlayerUI();
UFUNCTION(BlueprintCallable, Category = "UI")
void UpdateTargetUI();
UFUNCTION(BlueprintCallable, Category = "UI")
void UpdateAllUI();
// PlayerController.cpp 中实现
void AMyPlayerController::UpdatePlayerUI()
{
    // 获取玩家框架
    UUnitFrameManager* FrameManager = GetGameInstance()->GetSubsystem<UUnitFrameManager>();
    if (!FrameManager)
    {
        return;
    }
    
    UUnitFrameWidget* PlayerFrame = FrameManager->GetFrame("PlayerFrame");
    if (PlayerFrame)
    {
        PlayerFrame->UpdateDisplay();
    }
}
void AMyPlayerController::UpdateTargetUI()
{
    // 获取目标框架
    UUnitFrameManager* FrameManager = GetGameInstance()->GetSubsystem<UUnitFrameManager>();
    if (!FrameManager)
    {
        return;
    }
    
    UUnitFrameWidget* TargetFrame = FrameManager->GetFrame("TargetFrame");
    if (TargetFrame)
    {
        // 检查是否有目标
        UUnitManager* UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
        if (UnitManager && UnitManager->GetTargetUnit())
        {
            TargetFrame->SetVisibility(ESlateVisibility::Visible);
            TargetFrame->UpdateDisplay();
        }
        else
        {
            TargetFrame->SetVisibility(ESlateVisibility::Collapsed);
        }
    }
}
void AMyPlayerController::UpdateAllUI()
{
    // 更新所有UI
    UpdatePlayerUI();
    UpdateTargetUI();
    
    // 更新其他UI元素...
}

13.3.3 显示生命和法力值

下面,我们将实现显示单位生命值和法力值的功能。

实现生命值和法力值显示 :

cpp

// UnitStatsComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "UnitTypes.h"
#include "UnitStatsComponent.generated.h"
/**
 * 单位属性组件 - 管理单位的生命、法力和其他属性
 */
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UUnitStatsComponent : public UActorComponent, public IUnitInterface
{
    GENERATED_BODY()
public:    
    UUnitStatsComponent();
    
    virtual void BeginPlay() override;
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    // IUnitInterface实现
    virtual FString GetUnitID_Implementation() const override;
    virtual FUnitInfo GetUnitInfo_Implementation() const override;
    virtual float GetUnitStat_Implementation(EUnitStat StatType) const override;
    virtual void SetUnitStat_Implementation(EUnitStat StatType, float Value) override;
    virtual bool IsInCombat_Implementation() const override;
    virtual void SetCombatState_Implementation(bool bInCombat) override;
    virtual bool IsAlive_Implementation() const override;
    virtual float TakeDamage_Implementation(AActor* Source, float Amount, EUnitDamageType DamageType) override;
    virtual float TakeHealing_Implementation(AActor* Source, float Amount) override;
    
    // 获取单位ID
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    FString GetUnitID() const;
    
    // 设置单位ID
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    void SetUnitID(const FString& NewID);
    
    // 获取生命值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    float GetHealth() const;
    
    // 设置生命值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    void SetHealth(float NewHealth);
    
    // 获取最大生命值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    float GetMaxHealth() const;
    
    // 设置最大生命值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    void SetMaxHealth(float NewMaxHealth);
    
    // 获取法力值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    float GetMana() const;
    
    // 设置法力值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    void SetMana(float NewMana);
    
    // 获取最大法力值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    float GetMaxMana() const;
    
    // 设置最大法力值
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    void SetMaxMana(float NewMaxMana);
    
    // 是否在战斗中
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    bool IsInCombat() const;
    
    // 设置战斗状态
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    void SetCombatState(bool bInCombat);
    
    // 应用伤害
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    float ApplyDamage(AActor* Source, float Amount, EUnitDamageType DamageType);
    
    // 应用治疗
    UFUNCTION(BlueprintCallable, Category = "Unit Stats")
    float ApplyHealing(AActor* Source, float Amount);
    
    // 事件委托
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, NewHealth, float, MaxHealth);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnManaChanged, float, NewMana, float, MaxMana);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCombatStateChanged, bool, bInCombat);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
    
    // 事件委托实例
    UPROPERTY(BlueprintAssignable, Category = "Unit Stats|Events")
    FOnHealthChanged OnHealthChanged;
    
    UPROPERTY(BlueprintAssignable, Category = "Unit Stats|Events")
    FOnManaChanged OnManaChanged;
    
    UPROPERTY(BlueprintAssignable, Category = "Unit Stats|Events")
    FOnCombatStateChanged OnCombatStateChanged;
    
    UPROPERTY(BlueprintAssignable, Category = "Unit Stats|Events")
    FOnDeath OnDeath;
    
private:
    // 单位ID
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    FString UnitID;
    
    // 生命值
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    float Health;
    
    // 最大生命值
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    float MaxHealth;
    
    // 法力值
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    float Mana;
    
    // 最大法力值
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    float MaxMana;
    
    // 战斗状态
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    bool bInCombat;
    
    // 战斗状态计时器
    float CombatTimer;
    
    // 离开战斗的时间阈值
    UPROPERTY(EditAnywhere, Category = "Unit Stats")
    float CombatExitTime;
    
    // 检查战斗状态
    void CheckCombatState(float DeltaTime);
};
// UnitStatsComponent.cpp (部分实现)
#include "UnitStatsComponent.h"
UUnitStatsComponent::UUnitStatsComponent()
{
    PrimaryComponentTick.bCanEverTick = true;
    
    // 默认值
    UnitID = "";
    Health = 100.0f;
    MaxHealth = 100.0f;
    Mana = 100.0f;
    MaxMana = 100.0f;
    bInCombat = false;
    CombatTimer = 0.0f;
    CombatExitTime = 5.0f; // 5秒无战斗活动后离开战斗状态
}
void UUnitStatsComponent::BeginPlay()
{
    Super::BeginPlay();
    
    // 生成唯一ID
    if (UnitID.IsEmpty())
    {
        UnitID = FGuid::NewGuid().ToString();
    }
    
    // 注册到单位管理器
    UUnitManager* UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
    if (UnitManager)
    {
        UnitManager->RegisterUnit(GetOwner(), UnitID);
    }
}
void UUnitStatsComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    // 检查战斗状态
    CheckCombatState(DeltaTime);
}
float UUnitStatsComponent::GetHealth() const
{
    return Health;
}
void UUnitStatsComponent::SetHealth(float NewHealth)
{
    float OldHealth = Health;
    Health = FMath::Clamp(NewHealth, 0.0f, MaxHealth);
    
    // 检查是否死亡
    if (OldHealth > 0.0f && Health <= 0.0f)
    {
        OnDeath.Broadcast();
    }
    
    // 广播健康变化
    OnHealthChanged.Broadcast(Health, MaxHealth);
}
float UUnitStatsComponent::GetMaxHealth() const
{
    return MaxHealth;
}
void UUnitStatsComponent::SetMaxHealth(float NewMaxHealth)
{
    MaxHealth = FMath::Max(1.0f, NewMaxHealth);
    
    // 确保当前生命值不超过最大值
    if (Health > MaxHealth)
    {
        Health = MaxHealth;
    }
    
    // 广播变化
    OnHealthChanged.Broadcast(Health, MaxHealth);
}
float UUnitStatsComponent::GetMana() const
{
    return Mana;
}
void UUnitStatsComponent::SetMana(float NewMana)
{
    Mana = FMath::Clamp(NewMana, 0.0f, MaxMana);
    
    // 广播变化
    OnManaChanged.Broadcast(Mana, MaxMana);
}
float UUnitStatsComponent::GetMaxMana() const
{
    return MaxMana;
}
void UUnitStatsComponent::SetMaxMana(float NewMaxMana)
{
    MaxMana = FMath::Max(1.0f, NewMaxMana);
    
    // 确保当前法力值不超过最大值
    if (Mana > MaxMana)
    {
        Mana = MaxMana;
    }
    
    // 广播变化
    OnManaChanged.Broadcast(Mana, MaxMana);
}
bool UUnitStatsComponent::IsInCombat() const
{
    return bInCombat;
}
void UUnitStatsComponent::SetCombatState(bool bNewInCombat)
{
    if (bInCombat != bNewInCombat)
    {
        bInCombat = bNewInCombat;
        
        // 重置战斗计时器
        if (bInCombat)
        {
            CombatTimer = 0.0f;
        }
        
        // 广播状态变化
        OnCombatStateChanged.Broadcast(bInCombat);
    }
}
float UUnitStatsComponent::ApplyDamage(AActor* Source, float Amount, EUnitDamageType DamageType)
{
    // 进入战斗状态
    SetCombatState(true);
    
    // 应用伤害
    float OldHealth = Health;
    SetHealth(Health - Amount);
    
    // 返回实际应用的伤害
    return OldHealth - Health;
}
float UUnitStatsComponent::ApplyHealing(AActor* Source, float Amount)
{
    // 如果已经死亡,不能治疗
    if (Health <= 0.0f)
    {
        return 0.0f;
    }
    
    // 应用治疗
    float OldHealth = Health;
    SetHealth(Health + Amount);
    
    // 返回实际治愈的量
    return Health - OldHealth;
}
void UUnitStatsComponent::CheckCombatState(float DeltaTime)
{
    if (bInCombat)
    {
        // 累计战斗不活动时间
        CombatTimer += DeltaTime;
        
        // 检查是否应该离开战斗
        if (CombatTimer >= CombatExitTime)
        {
            SetCombatState(false);
        }
    }
}
// IUnitInterface实现
FString UUnitStatsComponent::GetUnitID_Implementation() const
{
    return UnitID;
}
void UUnitStatsComponent::SetUnitID(const FString& NewID)
{
    // 如果已注册,需要先注销
    if (!UnitID.IsEmpty())
    {
        UUnitManager* UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
        if (UnitManager)
        {
            UnitManager->UnregisterUnit(UnitID);
        }
    }
    
    // 设置新ID
    UnitID = NewID;
    
    // 注册新ID
    if (!UnitID.IsEmpty())
    {
        UUnitManager* UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
        if (UnitManager)
        {
            UnitManager->RegisterUnit(GetOwner(), UnitID);
        }
    }
}
FUnitInfo UUnitStatsComponent::GetUnitInfo_Implementation() const
{
    FUnitInfo Info;
    
    // 填充基本信息
    Info.UnitID = UnitID;
    Info.Name = GetOwner()->GetName();
    Info.bIsInCombat = bInCombat;
    Info.bIsAlive = Health > 0.0f;
    
    // 确定单位类型
    APawn* OwnerPawn = Cast<APawn>(GetOwner());
    if (OwnerPawn)
    {
        if (OwnerPawn->IsPlayerControlled())
        {
            Info.Type = EUnitType::Player;
        }
        else
        {
            Info.Type = EUnitType::NPC;
        }
    }
    else
    {
        Info.Type = EUnitType::GameObject;
    }
    
    // 填充属性
    Info.Stats.Add(EUnitStat::Health, Health);
    Info.Stats.Add(EUnitStat::MaxHealth, MaxHealth);
    Info.Stats.Add(EUnitStat::Mana, Mana);
    Info.Stats.Add(EUnitStat::MaxMana, MaxMana);
    
    return Info;
}
float UUnitStatsComponent::GetUnitStat_Implementation(EUnitStat StatType) const
{
    switch (StatType)
    {
        case EUnitStat::Health:
            return Health;
        case EUnitStat::MaxHealth:
            return MaxHealth;
        case EUnitStat::Mana:
            return Mana;
        case EUnitStat::MaxMana:
            return MaxMana;
        default:
            return 0.0f;
    }
}
void UUnitStatsComponent::SetUnitStat_Implementation(EUnitStat StatType, float Value)
{
    switch (StatType)
    {
        case EUnitStat::Health:
            SetHealth(Value);
            break;
        case EUnitStat::MaxHealth:
            SetMaxHealth(Value);
            break;
        case EUnitStat::Mana:
            SetMana(Value);
            break;
        case EUnitStat::MaxMana:
            SetMaxMana(Value);
            break;
        default:
            break;
    }
}
bool UUnitStatsComponent::IsInCombat_Implementation() const
{
    return bInCombat;
}
void UUnitStatsComponent::SetCombatState_Implementation(bool bInCombat)
{
    SetCombatState(bInCombat);
}
bool UUnitStatsComponent::IsAlive_Implementation() const
{
    return Health > 0.0f;
}
float UUnitStatsComponent::TakeDamage_Implementation(AActor* Source, float Amount, EUnitDamageType DamageType)
{
    return ApplyDamage(Source, Amount, DamageType);
}
float UUnitStatsComponent::TakeHealing_Implementation(AActor* Source, float Amount)
{
    return ApplyHealing(Source, Amount);
}

在蓝图中监听生命值和法力值变化 :

reasonml

// 角色蓝图中初始化
function BeginPlay():
    Super.BeginPlay()
    
    // 获取属性组件
    StatsComponent = GetComponentByClass(UnitStatsComponent)
    
    // 绑定事件
    StatsComponent.OnHealthChanged.AddDynamic(self, "OnHealthChanged")
    StatsComponent.OnManaChanged.AddDynamic(self, "OnManaChanged")
    StatsComponent.OnCombatStateChanged.AddDynamic(self, "OnCombatStateChanged")
    StatsComponent.OnDeath.AddDynamic(self, "OnDeath")
// 生命值变化处理
function OnHealthChanged(NewHealth, MaxHealth):
    // 更新生命值显示
    PlayerController = GetPlayerController()
    if (PlayerController != None):
        PlayerController.UpdatePlayerUI()
    
    // 如果生命值低于30%,显示危险提示
    if (NewHealth / MaxHealth < 0.3):
        ShowLowHealthWarning()
// 战斗状态变化处理
function OnCombatStateChanged(bInCombat):
    // 更新UI元素
    PlayerController = GetPlayerController()
    if (PlayerController != None):
        PlayerController.UpdateAllUI()
    
    // 播放相应音效
    if (bInCombat):
        PlaySound(CombatStartSound)
    else:
        PlaySound(CombatEndSound)

13.3.4 更新敌对信息

在游戏中,我们需要显示和更新敌对单位的信息,如生命值、等级和名称。以下是实现这一功能的代码:

实现敌对信息更新功能 :

cpp

复制

// TargetManager.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "UnitTypes.h"
#include "TargetManager.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTargetChanged, AActor*, NewTarget);
/**
 * 目标管理器组件 - 处理目标选择和信息更新
 */
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UTargetManager : public UActorComponent
{
    GENERATED_BODY()
public:    
    UTargetManager();
    
    virtual void BeginPlay() override;
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    // 设置当前目标
    UFUNCTION(BlueprintCallable, Category = "Target Manager")
    void SetTarget(AActor* NewTarget);
    
    // 清除当前目标
    UFUNCTION(BlueprintCallable, Category = "Target Manager")
    void ClearTarget();
    
    // 获取当前目标
    UFUNCTION(BlueprintCallable, Category = "Target Manager")
    AActor* GetTarget() const;
    
    // 选择最近的敌对目标
    UFUNCTION(BlueprintCallable, Category = "Target Manager")
    AActor* SelectNearestHostileTarget(float MaxDistance = 1000.0f);
    
    // 选择下一个敌对目标
    UFUNCTION(BlueprintCallable, Category = "Target Manager")
    AActor* SelectNextHostileTarget(float MaxDistance = 1000.0f);
    
    // 选择上一个敌对目标
    UFUNCTION(BlueprintCallable, Category = "Target Manager")
    AActor* SelectPreviousHostileTarget(float MaxDistance = 1000.0f);
    
    // 目标变更事件
    UPROPERTY(BlueprintAssignable, Category = "Target Manager|Events")
    FOnTargetChanged OnTargetChanged;
    
private:
    // 当前目标
    UPROPERTY()
    AActor* CurrentTarget;
    
    // 单位管理器引用
    UPROPERTY()
    class UUnitManager* UnitManager;
    
    // 获取周围的敌对单位
    TArray<AActor*> GetNearbyHostileUnits(float MaxDistance);
    
    // 检查单位是否为敌对
    bool IsHostileUnit(AActor* Unit);
};
// TargetManager.cpp
#include "TargetManager.h"
#include "UnitManager.h"
#include "Kismet/GameplayStatics.h"
#include "DrawDebugHelpers.h"
UTargetManager::UTargetManager()
{
    PrimaryComponentTick.bCanEverTick = true;
    
    CurrentTarget = nullptr;
}
void UTargetManager::BeginPlay()
{
    Super::BeginPlay();
    
    // 获取单位管理器
    UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
}
void UTargetManager::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    // 检查当前目标是否仍然有效
    if (CurrentTarget)
    {
        if (CurrentTarget->IsPendingKill() || !IsHostileUnit(CurrentTarget))
        {
            ClearTarget();
        }
        else
        {
            // 在开发模式下绘制调试线条到目标
            #if WITH_EDITOR
            DrawDebugLine(
                GetWorld(),
                GetOwner()->GetActorLocation(),
                CurrentTarget->GetActorLocation(),
                FColor::Red,
                false,
                -1.0f,
                0,
                2.0f
            );
            #endif
        }
    }
}
void UTargetManager::SetTarget(AActor* NewTarget)
{
    // 避免重复设置相同目标
    if (CurrentTarget == NewTarget)
    {
        return;
    }
    
    // 检查新目标是否有效
    if (NewTarget && !NewTarget->IsPendingKill() && IsHostileUnit(NewTarget))
    {
        CurrentTarget = NewTarget;
        
        // 通知单位管理器
        if (UnitManager)
        {
            UnitManager->SetTargetUnit(NewTarget);
        }
        
        // 广播目标变更事件
        OnTargetChanged.Broadcast(NewTarget);
        
        UE_LOG(LogTemp, Log, TEXT("New target set: %s"), *NewTarget->GetName());
    }
    else
    {
        ClearTarget();
    }
}
void UTargetManager::ClearTarget()
{
    if (CurrentTarget)
    {
        AActor* OldTarget = CurrentTarget;
        CurrentTarget = nullptr;
        
        // 通知单位管理器
        if (UnitManager)
        {
            UnitManager->SetTargetUnit(nullptr);
        }
        
        // 广播目标变更事件
        OnTargetChanged.Broadcast(nullptr);
        
        UE_LOG(LogTemp, Log, TEXT("Target cleared: %s"), *OldTarget->GetName());
    }
}
AActor* UTargetManager::GetTarget() const
{
    return CurrentTarget;
}
AActor* UTargetManager::SelectNearestHostileTarget(float MaxDistance)
{
    // 获取周围的敌对单位
    TArray<AActor*> NearbyHostiles = GetNearbyHostileUnits(MaxDistance);
    
    if (NearbyHostiles.Num() == 0)
    {
        return nullptr;
    }
    
    // 获取自身位置
    FVector OwnerLocation = GetOwner()->GetActorLocation();
    
    // 查找最近的敌对单位
    AActor* NearestHostile = nullptr;
    float NearestDistanceSq = MaxDistance * MaxDistance;
    
    for (AActor* Hostile : NearbyHostiles)
    {
        float DistanceSq = FVector::DistSquared(OwnerLocation, Hostile->GetActorLocation());
        
        if (DistanceSq < NearestDistanceSq)
        {
            NearestDistanceSq = DistanceSq;
            NearestHostile = Hostile;
        }
    }
    
    // 设置为目标
    if (NearestHostile)
    {
        SetTarget(NearestHostile);
    }
    
    return NearestHostile;
}
AActor* UTargetManager::SelectNextHostileTarget(float MaxDistance)
{
    // 获取周围的敌对单位
    TArray<AActor*> NearbyHostiles = GetNearbyHostileUnits(MaxDistance);
    
    if (NearbyHostiles.Num() == 0)
    {
        return nullptr;
    }
    
    // 如果没有当前目标,选择第一个
    if (!CurrentTarget)
    {
        SetTarget(NearbyHostiles[0]);
        return CurrentTarget;
    }
    
    // 查找当前目标在列表中的位置
    int32 CurrentIndex = NearbyHostiles.Find(CurrentTarget);
    
    // 选择下一个目标
    int32 NextIndex = (CurrentIndex + 1) % NearbyHostiles.Num();
    
    // 设置为目标
    SetTarget(NearbyHostiles[NextIndex]);
    
    return CurrentTarget;
}
AActor* UTargetManager::SelectPreviousHostileTarget(float MaxDistance)
{
    // 获取周围的敌对单位
    TArray<AActor*> NearbyHostiles = GetNearbyHostileUnits(MaxDistance);
    
    if (NearbyHostiles.Num() == 0)
    {
        return nullptr;
    }
    
    // 如果没有当前目标,选择最后一个
    if (!CurrentTarget)
    {
        SetTarget(NearbyHostiles.Last());
        return CurrentTarget;
    }
    
    // 查找当前目标在列表中的位置
    int32 CurrentIndex = NearbyHostiles.Find(CurrentTarget);
    
    // 选择上一个目标
    int32 PrevIndex = (CurrentIndex - 1 + NearbyHostiles.Num()) % NearbyHostiles.Num();
    
    // 设置为目标
    SetTarget(NearbyHostiles[PrevIndex]);
    
    return CurrentTarget;
}
TArray<AActor*> UTargetManager::GetNearbyHostileUnits(float MaxDistance)
{
    TArray<AActor*> NearbyHostiles;
    
    if (!UnitManager)
    {
        return NearbyHostiles;
    }
    
    // 获取所有单位
    TArray<AActor*> AllUnits;
    UGameplayStatics::GetAllActorsWithInterface(GetWorld(), UUnitInterface::StaticClass(), AllUnits);
    
    // 筛选敌对单位
    for (AActor* Unit : AllUnits)
    {
        if (IsHostileUnit(Unit))
        {
            // 检查距离
            float Distance = FVector::Dist(GetOwner()->GetActorLocation(), Unit->GetActorLocation());
            
            if (Distance <= MaxDistance)
            {
                NearbyHostiles.Add(Unit);
            }
        }
    }
    
    return NearbyHostiles;
}
bool UTargetManager::IsHostileUnit(AActor* Unit)
{
    if (!Unit || !UnitManager)
    {
        return false;
    }
    
    // 检查单位是否存活
    if (!UnitManager->IsUnitAlive(Unit))
    {
        return false;
    }
    
    // 检查与玩家的关系
    EUnitRelation Relation = UnitManager->GetUnitRelation(GetOwner(), Unit);
    
    return Relation == EUnitRelation::Hostile;
}

为玩家角色添加目标管理器 :

cpp

// PlayerCharacter.h 中添加
// 目标管理器组件
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UTargetManager* TargetManager;
// 目标按键处理
UFUNCTION(BlueprintCallable, Category = "Input")
void HandleTargetKey();
UFUNCTION(BlueprintCallable, Category = "Input")
void HandleNextTargetKey();
UFUNCTION(BlueprintCallable, Category = "Input")
void HandlePreviousTargetKey();
// PlayerCharacter.cpp 中实现
APlayerCharacter::APlayerCharacter()
{
    // ... 其他初始化代码 ...
    
    // 创建目标管理器组件
    TargetManager = CreateDefaultSubobject<UTargetManager>(TEXT("TargetManager"));
}
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    // ... 其他输入绑定 ...
    
    // 目标按键绑定
    PlayerInputComponent->BindAction("Target", IE_Pressed, this, &APlayerCharacter::HandleTargetKey);
    PlayerInputComponent->BindAction("NextTarget", IE_Pressed, this, &APlayerCharacter::HandleNextTargetKey);
    PlayerInputComponent->BindAction("PreviousTarget", IE_Pressed, this, &APlayerCharacter::HandlePreviousTargetKey);
}
void APlayerCharacter::HandleTargetKey()
{
    if (TargetManager)
    {
        // 如果当前有目标,清除目标;否则选择最近的敌对目标
        if (TargetManager->GetTarget())
        {
            TargetManager->ClearTarget();
        }
        else
        {
            TargetManager->SelectNearestHostileTarget();
        }
    }
}
void APlayerCharacter::HandleNextTargetKey()
{
    if (TargetManager)
    {
        TargetManager->SelectNextHostileTarget();
    }
}
void APlayerCharacter::HandlePreviousTargetKey()
{
    if (TargetManager)
    {
        TargetManager->SelectPreviousHostileTarget();
    }
}

为敌对单位添加视觉元素 :

cpp

// EnemyHighlightComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "EnemyHighlightComponent.generated.h"
/**
 * 敌对单位高亮组件 - 处理被选为目标时的视觉效果
 */
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UEnemyHighlightComponent : public UActorComponent
{
    GENERATED_BODY()
public:    
    UEnemyHighlightComponent();
    
    virtual void BeginPlay() override;
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    // 启用/禁用高亮
    UFUNCTION(BlueprintCallable, Category = "Enemy Highlight")
    void SetHighlighted(bool bHighlight);
    
    // 检查是否高亮
    UFUNCTION(BlueprintCallable, Category = "Enemy Highlight")
    bool IsHighlighted() const;
    
    // 设置高亮颜色
    UFUNCTION(BlueprintCallable, Category = "Enemy Highlight")
    void SetHighlightColor(const FLinearColor& Color);
    
private:
    // 是否高亮
    UPROPERTY(EditAnywhere, Category = "Enemy Highlight")
    bool bHighlighted;
    
    // 高亮颜色
    UPROPERTY(EditAnywhere, Category = "Enemy Highlight")
    FLinearColor HighlightColor;
    
    // 原始材质
    UPROPERTY()
    TArray<UMaterialInterface*> OriginalMaterials;
    
    // 高亮材质实例
    UPROPERTY()
    TArray<UMaterialInstanceDynamic*> HighlightMaterials;
    
    // 是否已初始化
    bool bInitialized;
    
    // 初始化高亮材质
    void InitializeHighlightMaterials();
    
    // 应用高亮效果
    void ApplyHighlight();
    
    // 移除高亮效果
    void RemoveHighlight();
};
// EnemyHighlightComponent.cpp
#include "EnemyHighlightComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
UEnemyHighlightComponent::UEnemyHighlightComponent()
{
    PrimaryComponentTick.bCanEverTick = true;
    
    bHighlighted = false;
    HighlightColor = FLinearColor(1.0f, 0.3f, 0.3f, 1.0f); // 红色高亮
    bInitialized = false;
}
void UEnemyHighlightComponent::BeginPlay()
{
    Super::BeginPlay();
    
    // 不立即初始化材质,等到需要时再初始化
}
void UEnemyHighlightComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    // 如果状态变化,更新高亮效果
    if (bHighlighted && !bInitialized)
    {
        InitializeHighlightMaterials();
        ApplyHighlight();
    }
}
void UEnemyHighlightComponent::SetHighlighted(bool bHighlight)
{
    if (bHighlighted != bHighlight)
    {
        bHighlighted = bHighlight;
        
        if (bHighlighted)
        {
            if (!bInitialized)
            {
                InitializeHighlightMaterials();
            }
            ApplyHighlight();
        }
        else
        {
            RemoveHighlight();
        }
    }
}
bool UEnemyHighlightComponent::IsHighlighted() const
{
    return bHighlighted;
}
void UEnemyHighlightComponent::SetHighlightColor(const FLinearColor& Color)
{
    HighlightColor = Color;
    
    // 如果当前高亮,更新颜色
    if (bHighlighted && bInitialized)
    {
        for (UMaterialInstanceDynamic* Material : HighlightMaterials)
        {
            if (Material)
            {
                Material->SetVectorParameterValue("HighlightColor", HighlightColor);
            }
        }
    }
}
void UEnemyHighlightComponent::InitializeHighlightMaterials()
{
    // 清除现有数据
    OriginalMaterials.Empty();
    HighlightMaterials.Empty();
    
    // 获取所有网格体
    TArray<UMeshComponent*> MeshComponents;
    GetOwner()->GetComponents<UMeshComponent>(MeshComponents);
    
    // 准备高亮材质
    for (UMeshComponent* MeshComp : MeshComponents)
    {
        if (MeshComp && MeshComp->IsVisible())
        {
            int32 MaterialCount = MeshComp->GetNumMaterials();
            
            for (int32 i = 0; i < MaterialCount; ++i)
            {
                UMaterialInterface* OriginalMaterial = MeshComp->GetMaterial(i);
                if (OriginalMaterial)
                {
                    // 存储原始材质
                    OriginalMaterials.Add(OriginalMaterial);
                    
                    // 创建动态材质实例
                    UMaterialInstanceDynamic* HighlightMaterial = UMaterialInstanceDynamic::Create(
                        LoadObject<UMaterial>(nullptr, TEXT("/Game/Materials/M_Highlight")),
                        this
                    );
                    
                    if (HighlightMaterial)
                    {
                        // 设置参数
                        HighlightMaterial->SetTextureParameterValue("BaseTexture", OriginalMaterial->GetTextureParameterValue("BaseTexture"));
                        HighlightMaterial->SetVectorParameterValue("HighlightColor", HighlightColor);
                        
                        HighlightMaterials.Add(HighlightMaterial);
                    }
                    else
                    {
                        HighlightMaterials.Add(nullptr);
                    }
                }
            }
        }
    }
    
    bInitialized = true;
}
void UEnemyHighlightComponent::ApplyHighlight()
{
    if (!bInitialized)
    {
        return;
    }
    
    // 应用高亮材质
    TArray<UMeshComponent*> MeshComponents;
    GetOwner()->GetComponents<UMeshComponent>(MeshComponents);
    
    int32 MaterialIndex = 0;
    
    for (UMeshComponent* MeshComp : MeshComponents)
    {
        if (MeshComp && MeshComp->IsVisible())
        {
            int32 MaterialCount = MeshComp->GetNumMaterials();
            
            for (int32 i = 0; i < MaterialCount; ++i)
            {
                if (MaterialIndex < HighlightMaterials.Num() && HighlightMaterials[MaterialIndex])
                {
                    MeshComp->SetMaterial(i, HighlightMaterials[MaterialIndex]);
                }
                
                MaterialIndex++;
            }
        }
    }
}
void UEnemyHighlightComponent::RemoveHighlight()
{
    if (!bInitialized)
    {
        return;
    }
    
    // 恢复原始材质
    TArray<UMeshComponent*> MeshComponents;
    GetOwner()->GetComponents<UMeshComponent>(MeshComponents);
    
    int32 MaterialIndex = 0;
    
    for (UMeshComponent* MeshComp : MeshComponents)
    {
        if (MeshComp && MeshComp->IsVisible())
        {
            int32 MaterialCount = MeshComp->GetNumMaterials();
            
            for (int32 i = 0; i < MaterialCount; ++i)
            {
                if (MaterialIndex < OriginalMaterials.Num() && OriginalMaterials[MaterialIndex])
                {
                    MeshComp->SetMaterial(i, OriginalMaterials[MaterialIndex]);
                }
                
                MaterialIndex++;
            }
        }
    }
}

实现目标高亮的监听 :

cpp

// 为敌对单位添加监听代码
// EnemyCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "UnitInterface.h"
#include "EnemyCharacter.generated.h"
UCLASS()
class MYGAME_API AEnemyCharacter : public ACharacter, public IUnitInterface
{
    GENERATED_BODY()
    
public:
    AEnemyCharacter();
    
    virtual void BeginPlay() override;
    virtual void Tick(float DeltaTime) override;
    
    // 单位属性组件
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    class UUnitStatsComponent* StatsComponent;
    
    // 高亮组件
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    class UEnemyHighlightComponent* HighlightComponent;
    
    // 目标变更事件处理
    UFUNCTION()
    void OnTargetChanged(AActor* NewTarget);
    
    // 单位接口实现
    virtual FString GetUnitID_Implementation() const override;
    virtual FUnitInfo GetUnitInfo_Implementation() const override;
    virtual float GetUnitStat_Implementation(EUnitStat StatType) const override;
    virtual void SetUnitStat_Implementation(EUnitStat StatType, float Value) override;
    virtual bool IsInCombat_Implementation() const override;
    virtual void SetCombatState_Implementation(bool bInCombat) override;
    virtual bool IsAlive_Implementation() const override;
    virtual float TakeDamage_Implementation(AActor* Source, float Amount, EUnitDamageType DamageType) override;
    virtual float TakeHealing_Implementation(AActor* Source, float Amount) override;
};
// EnemyCharacter.cpp
#include "EnemyCharacter.h"
#include "UnitStatsComponent.h"
#include "EnemyHighlightComponent.h"
#include "UnitManager.h"
#include "Kismet/GameplayStatics.h"
AEnemyCharacter::AEnemyCharacter()
{
    PrimaryActorTick.bCanEverTick = true;
    
    // 创建属性组件
    StatsComponent = CreateDefaultSubobject<UUnitStatsComponent>(TEXT("StatsComponent"));
    
    // 创建高亮组件
    HighlightComponent = CreateDefaultSubobject<UEnemyHighlightComponent>(TEXT("HighlightComponent"));
}
void AEnemyCharacter::BeginPlay()
{
    Super::BeginPlay();
    
    // 注册目标变更事件
    UUnitManager* UnitManager = GetGameInstance()->GetSubsystem<UUnitManager>();
    if (UnitManager)
    {
        // 这里假设UnitManager有一个OnTargetChanged委托
        // UnitManager->OnTargetChanged.AddDynamic(this, &AEnemyCharacter::OnTargetChanged);
    }
}
void AEnemyCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}
void AEnemyCharacter::OnTargetChanged(AActor* NewTarget)
{
    // 当目标变更时,检查是否是自己
    if (HighlightComponent)
    {
        bool bIsTarget = (NewTarget == this);
        HighlightComponent->SetHighlighted(bIsTarget);
    }
}
// 单位接口实现 - 这些方法只是转发到StatsComponent
FString AEnemyCharacter::GetUnitID_Implementation() const
{
    if (StatsComponent)
    {
        return StatsComponent->GetUnitID();
    }
    return "";
}
FUnitInfo AEnemyCharacter::GetUnitInfo_Implementation() const
{
    if (StatsComponent)
    {
        return StatsComponent->GetUnitInfo_Implementation();
    }
    return FUnitInfo();
}
float AEnemyCharacter::GetUnitStat_Implementation(EUnitStat StatType) const
{
    if (StatsComponent)
    {
        return StatsComponent->GetUnitStat_Implementation(StatType);
    }
    return 0.0f;
}
void AEnemyCharacter::SetUnitStat_Implementation(EUnitStat StatType, float Value)
{
    if (StatsComponent)
    {
        StatsComponent->SetUnitStat_Implementation(StatType, Value);
    }
}
bool AEnemyCharacter::IsInCombat_Implementation() const
{
    if (StatsComponent)
    {
        return StatsComponent->IsInCombat_Implementation();
    }
    return false;
}
void AEnemyCharacter::SetCombatState_Implementation(bool bInCombat)
{
    if (StatsComponent)
    {
        StatsComponent->SetCombatState_Implementation(bInCombat);
    }
}
bool AEnemyCharacter::IsAlive_Implementation() const
{
    if (StatsComponent)
    {
        return StatsComponent->IsAlive_Implementation();
    }
    return false;
}
float AEnemyCharacter::TakeDamage_Implementation(AActor* Source, float Amount, EUnitDamageType DamageType)
{
    if (StatsComponent)
    {
        return StatsComponent->TakeDamage_Implementation(Source, Amount, DamageType);
    }
    return 0.0f;
}
float AEnemyCharacter::TakeHealing_Implementation(AActor* Source, float Amount)
{
    if (StatsComponent)
    {
        return StatsComponent->TakeHealing_Implementation(Source, Amount);
    }
    return 0.0f;
}

在蓝图中实现目标切换功能 :

reasonml

// 在玩家控制器蓝图中实现
function SetupPlayerInputComponent(InputComponent):
    Super.SetupPlayerInputComponent(InputComponent)
    
    // Tab键切换目标
    InputComponent.BindAction("TargetSwitch", IE_Pressed, this, "OnTargetSwitchPressed")
    
    // 按下Tab键时选择下一个目标
    InputComponent.BindAction("NextTarget", IE_Pressed, this, "OnNextTargetPressed")
    
    // 按下Shift+Tab键时选择上一个目标
    InputComponent.BindAction("PrevTarget", IE_Pressed, this, "OnPrevTargetPressed")
function OnTargetSwitchPressed():
    TargetManager = GetComponentByClass(TargetManager)
    if (TargetManager.GetTarget() == None):
        TargetManager.SelectNearestHostileTarget()
    else:
        TargetManager.ClearTarget()
function OnNextTargetPressed():
    TargetManager = GetComponentByClass(TargetManager)
    TargetManager.SelectNextHostileTarget()
function OnPrevTargetPressed():
    TargetManager = GetComponentByClass(TargetManager)
    TargetManager.SelectPreviousHostileTarget()
// 目标变更事件处理
function OnTargetChanged(NewTarget):
    // 更新目标框架
    UpdateTargetUI()
    
    // 播放音效
    if (NewTarget != None):
        PlaySound(TargetSelectedSound)
    else:
        PlaySound(TargetClearedSound)

13.4 小结

在本章中,我们探讨了如何在UE5.2中实现类似魔兽世界的API系统,用于创建和管理游戏UI和单位交互。主要涵盖了以下内容:

  1. 理解魔兽世界API : 分析了魔兽世界API的结构和设计理念,包括常规API、类库API、FrameXML函数和受保护函数,并展示了如何在UE5.2中实现类似的系统。

  2. 创建单位窗体 : 实现了基于XML的单位框架,用于显示玩家、目标和其他单位的信息,包括窗体创建、数据域添加和事件处理。

  3. 使用API : 演示了如何使用开发的API进行各种操作,如显示和隐藏窗体、更新UI元素、显示生命和法力值,以及更新敌对信息。

我们开发的系统具有以下优势:

  • 模块化设计 : 将UI、单位管理和事件系统分离,使得代码更易于维护和扩展。
  • 事件驱动 : 使用事件委托系统实现松耦合的组件通信。
  • 可扩展性 : 通过接口和抽象层设计,可以轻松添加新的单位类型和UI元素。
  • 性能优化 : 通过缓存和按需更新策略,减少不必要的计算和渲染。

通过这些实现,我们创建了一个强大且灵活的框架,可以用于开发复杂的RPG游戏UI系统。这种基于API的设计不仅简化了UI开发过程,还提供了一致的接口,使得游戏系统的各个部分能够更好地协同工作。

对于想要进一步扩展此系统的开发者,可以考虑添加以下功能:

  • 更复杂的动画和过渡效果
  • 可拖拽的UI元素重新排列
  • 支持玩家自定义UI布局
  • 集成游戏内插件系统
  • 更高级的单位关系和阵营系统

通过借鉴魔兽世界的API设计理念,我们可以为UE5.2项目创建强大、可扩展且用户友好的UI系统,提升游戏的整体体验和可玩性。

本文标签: 信息 系统 编程