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和单位交互。主要涵盖了以下内容:
理解魔兽世界API : 分析了魔兽世界API的结构和设计理念,包括常规API、类库API、FrameXML函数和受保护函数,并展示了如何在UE5.2中实现类似的系统。
创建单位窗体 : 实现了基于XML的单位框架,用于显示玩家、目标和其他单位的信息,包括窗体创建、数据域添加和事件处理。
使用API : 演示了如何使用开发的API进行各种操作,如显示和隐藏窗体、更新UI元素、显示生命和法力值,以及更新敌对信息。
我们开发的系统具有以下优势:
- 模块化设计 : 将UI、单位管理和事件系统分离,使得代码更易于维护和扩展。
- 事件驱动 : 使用事件委托系统实现松耦合的组件通信。
- 可扩展性 : 通过接口和抽象层设计,可以轻松添加新的单位类型和UI元素。
- 性能优化 : 通过缓存和按需更新策略,减少不必要的计算和渲染。
通过这些实现,我们创建了一个强大且灵活的框架,可以用于开发复杂的RPG游戏UI系统。这种基于API的设计不仅简化了UI开发过程,还提供了一致的接口,使得游戏系统的各个部分能够更好地协同工作。
对于想要进一步扩展此系统的开发者,可以考虑添加以下功能:
- 更复杂的动画和过渡效果
- 可拖拽的UI元素重新排列
- 支持玩家自定义UI布局
- 集成游戏内插件系统
- 更高级的单位关系和阵营系统
通过借鉴魔兽世界的API设计理念,我们可以为UE5.2项目创建强大、可扩展且用户友好的UI系统,提升游戏的整体体验和可玩性。
版权声明:本文标题:魔兽世界API揭秘:快速提升你的游戏开发实力 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/p/1773275910a3560108.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论