属性

属性

此文档包含了属性与各种数据结构的声明和初始化

基础属性类型

数据类型 描述
uint64 64位无符号
int64 64位有符号
int32 32位有符号
float 浮点
double 多精的浮点
bool 布尔:true,false
FString 字符串
FName 字符
FText 文本
FVetor 向量
FRotator 旋转向量
FTransform 位置,旋转,缩放
Tarray 数组
TMap 字典
UENUM 枚举
TSubclassOf 类引用

标识

标识
F 代表结构体(Structure)。这些通常是值类型,用于存储数据,如FRotator用于表示旋转信息,FVector表示三维向量等
A 代表Unreal的类(UClass),这些是通过Unreal的反射系统管理的类,通常用于复杂的对象或者需要序列化和编辑器支持的类,例如游戏组件、蓝图类等。
U 代表Unreal的类(UClass),这些是通过Unreal的反射系统管理的类,通常用于复杂的对象或者需要序列化和编辑器支持的类,例如游戏组件、蓝图类等。
UP 代表一个智能指针(UObject指针的包装器),通常用于管理对UClass实例的引用。
T 用于模板类型,如容器类(如TArray, TMap)
E 代表枚举类型(Enum)。
G 代表全局变量(Global variable)。这类变量在整个程序运行期间都是可访问的,例如GEngine, GWorld等,它们提供对引擎核心组件的访问。
I 代表接口(Interface)。接口定义了一组纯虚函数,用于规定类需要实现的行为规范,没有实际的数据成员,例如IInputProcessor。
S 虽然不如其他几个前缀常见,但有时用于表示界面相关类(Widget类),特别是与UMG(Unreal Motion Graphics)相关的类,如SButton、STextBlock等。不过,这一规则并不严格,很多UMG相关的类也直接使用U前缀。
P 在某些情况下,尤其是与网络或复制相关的代码中,可能会见到P前缀,代表“replicated property”或者是代理属性相关的类或结构,但这个前缀的使用不如上述几个那样广泛或固定。
FLAT 用于一些扁平化数据结构的版本,这些通常是为优化存储或传输而设计的,例如FLAT版本的FTransform。

反射系统

  • 属性声明需要UPROPERTY

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //常规
    UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="分类A|分类B")
    int 属性;

    //DisplayName 别名
    EditAnywhere,BlueprintReadWrite,meta=(DisplayName="别名")


    //通过布尔控制编辑
    EditAnywhere,BlueprintReadWrite,meta=(DisplayName="别名A")
    bool a;
    EditAnywhere,BlueprintReadWrite,meta=(EditCondition="a")
    float value3;

    //类型实例
    UPROPERTY()
    TObjectPtr<UAuraUserWidget> OverlayWidget;

    //类型引用
    UPROPERTY(EditAnywhere)
    TSubclassOf<UAuraUserWidget> OverlayWidgetClass;
  • 枚举声明,需要在类外边声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 枚举声明1
    UENUM(BlueprintType
    namespace Myenmeumtype
    {
    enum Mycusto
    {
    Type1,
    Type2,
    Type3,
    }
    }
  • 类声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    UENUM(BlueprintType)
    enum class my:uint8
    {
    on UMEAT(DisplayName="maya"),
    aa UMEAT(DisplayName="aaa"),
    oc UMEAT(DisplayName="bbb"),
    }
    EditAnywhere,BlueprintReadWrite,,Category="my"
    my aaad
  • 结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //结构体
    USTRUCT(BlueprintType)
    struct GAME
    {
    GENERATED_BUCLASS_BODY()

    }

    EditAnywhere,BlueprintReadWrite,,Category="my"
    GAME MAYA

  • 数据表结构体

    •     //数据表结构体
      UCLASS(blueprintable, BlueprintType)
         class TESTGUI_API UFairyItemBase : public UObject
         {
             GENERATED_BODY()
             
         public:
             // 物品数据结构体
             UPROPERTY(EditAnywhere, BlueprintReadWrite)
             FFairyItemStruct FairyItemStruct;
      
             UPROPERTY(EditAnywhere, BlueprintReadWrite)
             UDataTable* ItemName;
      
             // 初始化结构体
             UFUNCTION(BlueprintCallable)
             virtual void InitItem(const FFairyItemStruct& InFairyItemStruct)
             {
                 FairyItemStruct = InFairyItemStruct;
             }
      
             // 从数据表初始化结构体
             UFUNCTION(BlueprintCallable)
             virtual void DataInitItem(UDataAsset* DataAsset)
             {
                 if (ItemName && ItemName->GetRowMap().Num() > 0)
                 {
                     FString ContextString;
                     for (auto& Pair : ItemName->GetRowMap())
                     {
                         const FFairyItemDataRow* Row = ItemName->FindRow<FFairyItemDataRow>(Pair.Key, ContextString);
                         if (Row)
                         {
                             FFairyItemStruct ItemStruct;
                             ItemStruct.ID = Row->ID;
                             ItemStruct.Quantity = Row->Quantity;
                             ItemStruct.Icon = Row->Icon;
                             ItemStruct.bIsStack = Row->bIsStack;
                             InitItem(ItemStruct);
                         }
                     }
                 }
             }
         };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13



      + 强转换声明

      ```cpp
      /** type of damage */

      UPROPERTY(EditDefaultsOnly, Category=Damage)

      TSubclassOf<UDamageType> DamageType;


属性方法

元数据说明

元数据 描述
EditAnywhere 属性可导入蓝图
EditAnywhere,BlueprintReadOnly 属性可导入蓝图,不可修改
EditAnywhere,BlueprintReadWrite 属性可导入蓝图,可修改
VisibleAnywhere 属性,实例,可见,不可编辑
VisibleInstanceOnly 仅在实例化可见/不可编辑
VisibleDefaultsOnly 仅在属性可见/不可编辑
EditAnywhere 说明此属性可通过属性窗口在原型和实例上进行编辑/可编辑
EditDefaultsOnly 仅在属性窗口编辑/可编辑
EditInstanceOnly 仅通过实例化窗口编辑/可编辑

字符串转换

  • 打印字符串

    1
    UE_LOG(LogTemp,Warning,TEXT("%d"),*it)
    1
    UE_LOG(LogTemp, Log, TEXT("(UE_LOG-logTemp)    Hello world!"));
    1
    2
    UE_LOG(LogTemp, Warning, TEXT("玩家:%s, Pawn:%s"), *PlayerController->GetName(), *Pawn->GetName()

    1
    2
    FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("WWW"));
    //控制台弹框显示
    1
    2
    //整数转换为文本
    ItemBoxWidget->TextBlock->SetText(FText::AsNumber(ItemShowStructArray[Index].quantity));
  • 打印到屏幕

    1
    2
    3
    4
    5
    6
    // 打印至屏幕
    FString screenMessage = "(AddOnScreenDebugMessage) Hello world!";
    GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, screenMessage);

    // 打印至屏幕
    UKismetSystemLibrary::PrintString(this, "(UKismetSystemLibrary::PrintString) Hello world!");
  • 带变量打印log

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    	
    //创建FString 变量 FString::Printf
    FString playerName = "User";
    int32 healthValue = 100;
    FString outputMessage1 = FString::Printf(TEXT("Name is %s, health value is %d"), *playerName, healthValue);
    UE_LOG(LogTemp, Warning, TEXT("FStringFormatArg: %s"), *outputMessage1);

    //创建FString变量 FString::Format
    TArray<FStringFormatArg> args;
    args.Add(FStringFormatArg(playerName));
    args.Add(FStringFormatArg(healthValue));

    FString outputMessage2 = FString::Format(TEXT("Name is {0}, health value is {1}"), args);
    UE_LOG(LogTemp, Warning, TEXT("FString::Format: %s"), *outputMessage2);
  • FString

1
2
3
4
5
//声明FString
FString mystring=TEXT("hahaah");

FName myname=FName(*mystring); //字符串转FName
FText mytext=FText::FromSring(*mystring) //字符串转FText
  • FromName
1
2
mystring=myname.ToString();   //myname转String
FText text1=FText::FromName(myname); //FromName转FTtext
  • strFromText
1
FString strFromText=text1.ToString();   //strFromText转String     要转FromName可以通过FString转

纯函数(绿色函数)

1
2
UFUNCTION(BlueprintPure, Category = "MyFunctions")
int32 AddTwoIntegers(int32 A, int32 B);

自定义事件

在Unreal Engine 5(UE5)中,绑定和使用委托涉及几个关键步骤。以下是一个详细的指南,包括如何绑定和使用单播委托、多播委托以及动态委托。

一、单播委托的绑定和使用

  1. 声明委托

    首先,在头文件中声明一个单播委托类型,并在类中声明一个该类型的委托变量。

    1
    2
    3
    4
    5
    6
    7
    DECLARE_DELEGATE(MySinglecastDelegate);

    class MyClass
    {
    public:
    MySinglecastDelegate MyDelegate;
    };
  2. 绑定回调

    在需要绑定回调的地方,使用Bind方法(对于UObject子类成员函数使用BindUObject,静态函数使用BindStatic,Lambda表达式使用BindLambda等)。

    1
    2
    MyClass myObject;
    myObject.MyDelegate.BindUObject(&myObject, &MyClass::MyFunction);

    或者,对于静态函数:

    1
    myObject.MyDelegate.BindStatic(&MyStaticFunction);

    对于Lambda表达式:

    1
    myObject.MyDelegate.BindLambda([](){ /* Your code here */ });
  3. 执行委托

    当需要执行委托时,调用Execute方法。

    1
    myObject.MyDelegate.Execute();

二、多播委托的绑定和使用

  1. 声明多播委托

    与单播委托类似,首先声明一个多播委托类型,并在类中声明一个该类型的委托变量。

    1
    2
    3
    4
    5
    6
    7
    DECLARE_MULTICAST_DELEGATE(MyMulticastDelegate);

    class MyClass
    {
    public:
    MyMulticastDelegate MyMulticastDelegateVar;
    };
  2. 添加回调

    使用Add方法(对于UObject子类成员函数使用AddUObject,Lambda表达式使用AddLambda等)来添加多个回调。

    1
    2
    3
    MyClass myObject;
    myObject.MyMulticastDelegateVar.AddUObject(&myObject, &MyClass::MyFunction1);
    myObject.MyMulticastDelegateVar.AddUObject(&myObject, &MyClass::MyFunction2);

    或者,对于Lambda表达式:

    1
    myObject.MyMulticastDelegateVar.AddLambda([](){ /* Your code here */ });
  3. 广播委托

    当需要执行多播委托时,调用Broadcast方法。

    1
    myObject.MyMulticastDelegateVar.Broadcast();

三、动态委托的绑定和使用

  1. 声明动态委托

    动态委托需要使用UDELEGATEUMULTICAST_DELEGATE宏,并在类中声明一个UPROPERTY(BlueprintAssignable)属性的委托变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(void, MyDynamicDelegate, int32, float);
    UCLASS()
    class MyClass : public UObject
    {
    GENERATED_BODY()

    public:
    UPROPERTY(BlueprintAssignable)
    MyDynamicDelegate MyDynamicDelegateVar;
    };
  2. 绑定回调

    在C++中,动态委托的绑定方式与单播委托类似,但需要使用BindDynamic方法(对于UObject子类成员函数)。在蓝图中,可以直接通过节点来绑定和解绑回调。

    1
    2
    MyClass myObject;
    myObject.MyDynamicDelegateVar.BindDynamic(&myObject, &MyClass::MyDynamicFunction);

    在蓝图中,找到对应的委托变量,并使用“绑定事件”节点来绑定回调。

  3. 执行或广播委托

    动态单播委托使用Execute方法,动态多播委托使用Broadcast方法。

    1
    myObject.MyDynamicDelegateVar.Execute();

    或者(对于多播委托):

    1
    myObject.MyDynamicMulticastDelegateVar.Broadcast();

注意事项

  • 确保委托在绑定和执行时都是有效的,避免在对象销毁后还尝试执行委托。
  • 对于动态委托,特别是与蓝图交互时,要确保回调函数是安全的,并且不会导致内存泄漏或访问违规。
  • 委托的声明和使用通常与UObject子类相关联,因为委托的生命周期管理通常与UObject的生命周期管理相结合。

通过以上步骤,你可以在UE5中有效地绑定和使用委托来实现事件驱动编程和回调机制。

E5中的委托提供了多种绑定方式,以满足不同的需求:

  1. BindUObject:用于绑定UObject子类的成员函数。
  2. BindStatic:用于绑定静态函数或全局函数。
  3. BindLambda:用于绑定Lambda表达式,提供匿名函数的绑定方式。
  4. BindDynamic(仅适用于动态委托):用于绑定使用UFUNCTION()修饰的成员函数。

此外,对于多播委托,还提供了AddUObject、AddStatic、AddLambda等添加回调的方式。

在UE5中,动态委托(Dynamic Delegate和Dynamic Multicast Delegate)特别适用于与蓝图的交互。为了在蓝图中能够绑定和解绑回调函数,这些动态委托必须使用UFUNCTION()修饰,并且需要在声明时指定参数名称。这进一步增加了绑定方式的多样性。

  1. 定义
1
2
3
4
5
6
7
8
9
10
11
12
13
// MyCustomEvent.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyCustomEvent); //定义委托

UCLASS()
class YOURGAME_API AYourActor : public AActor
{
GENERATED_BODY()

public:
// 定义一个事件,使用之前声明的委托类型
UPROPERTY(BlueprintAssignable)
FMyCustomEvent OnMyCustomEvent; //定义事件
};
  1. 发送事件
1
2
3
4
5
6
7
8
void AYourActor::TriggerMyCustomEvent()
{
// 确保事件代理不为空,然后广播事件
if (OnMyCustomEvent.IsBound())
{
OnMyCustomEvent.Broadcast();
}
}
  1. 绑定和接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AnotherClass.cpp
void AAnotherClass::BeginPlay()
{
Super::BeginPlay();

AYourActor* yourActor = Cast<AYourActor>(UGameplayStatics::GetActorOfClass(this, AYourActor::StaticClass()));
if (yourActor)
{
//绑定
yourActor->OnMyCustomEvent.AddDynamic(this, &AAnotherClass::OnMyCustomEventReceived);
}
}

//函数声明
void AAnotherClass::OnMyCustomEventReceived()
{
UE_LOG(LogTemp, Warning, TEXT("Custom event received!"));
}

委托宏

1
2
3
4
5
6
7
DECLARE_DYNAMIC_MULTICAST_DELEGATE:无参数的多播委托。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam:一个参数的多播委托。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams:两个参数的多播委托。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams

向量

  • 位置ActorLocation(FVector)

  • 旋转ActorRotation(FRotator)

  • 大小ActorScale3D(FVector)

CPP类

虚幻引擎中有许多常用的父类,它们提供了各种功能,用于创建游戏对象、处理输入、管理游戏状态等。以下是一些常用的虚幻引擎父类:

AActor(Actor类):表示在虚拟场景中的一个对象。所有在游戏中出现的物体都是Actor的子类,包括玩家角色、道具、敌人等。

UObject:是所有虚幻对象的基类。几乎所有的虚幻引擎类都是UObject或其子类。

UActorComponent:表示可以被附加到Actor上的组件,用于添加额外的功能或属性。例如,MeshComponent用于处理渲染,AudioComponent用于处理声音等。

UGameInstance:在整个游戏实例中保持持久性数据和状态的类,通常用于保存全局性的信息。

UUserWidget:用于创建用户界面的基类,通过蓝图和代码可以创建各种界面元素。

UInputComponent:用于处理输入的组件,可以附加到Actor或Pawn上,用于响应玩家的键盘、鼠标或手柄输入。

UGameplayStatics:提供了在游戏中执行各种通用操作的静态类,包括生成粒子效果、播放声音等。

AGameSession:处理游戏会话的类,用于管理玩家连接、处理会话状态等。

APlayerController:控制玩家输入和视图,处理与玩家直接互动的逻辑。

APlayerState:表示玩家的状态,包括分数、生命等信息。

这只是虚幻引擎中一小部分常用父类的示例。根据游戏的需求,还有许多其他的父类可供使用。开发者通常会根据具体情况选择合适的父类来构建游戏对象和逻辑。
————————————————

在Unreal Engine 5(UE5)中,C++作为主要的编程语言之一,提供了大量的类和API用于游戏开发。这些类和API涵盖了游戏对象管理、渲染、物理、输入、音频、网络等多个方面。以下是一些UE5 C++中常用的类和API的概述:

1. 核心基类

  • UObject:所有虚幻引擎类的基类,提供了对象序列化、垃圾回收、依赖跟踪等功能。几乎所有的UE5类都是UObject或其子类。
  • AActor:表示游戏世界中的一个对象,如角色、道具、环境物体等。AActor提供了位置、旋转、缩放等属性,以及碰撞检测、事件处理等机制。
  • UActorComponent:可以附加到AActor上的组件,用于添加额外的功能或属性。例如,MeshComponent用于处理渲染,AudioComponent用于处理声音等。

2. 场景管理

  • AGameModeBase:定义了游戏的规则和流程,包括游戏开始、结束等逻辑。
  • APlayerController:控制玩家输入和视图,处理与玩家直接互动的逻辑。
  • APlayerState:表示玩家的状态,包括分数、生命等信息。

3. 渲染和材质

  • UMaterialUMaterialInstance:用于定义和实例化材质,控制物体的外观。
  • USceneComponentUMeshComponent:用于处理场景中的组件和网格体的渲染。
  • UStaticMesh:静态网格体,用于表示不会移动的物体,如建筑、地形等。

4. 物理和碰撞

  • UPhysicsBodyUPhysicsConstraintComponent:用于处理物理模拟和约束。
  • UCollisionComponent:用于碰撞检测和处理。

5. 输入和UI

  • UInputComponent:用于处理输入的组件,可以附加到Actor或Pawn上,用于响应玩家的键盘、鼠标或手柄输入。
  • UUserWidget:用于创建用户界面的基类,通过蓝图和代码可以创建各种界面元素。

6. 网络和多人游戏

  • ANetDriverAConnection:用于处理网络通信的类。
  • AControllerAPawn:在多人游戏中,用于表示玩家控制的角色和物体。

7. 数据管理和序列化

  • FStringTArrayTMap:UE5中常用的字符串、数组和映射类型,用于数据存储和序列化。
  • FSerializerFArchive:用于自定义序列化和反序列化过程。

8. 其他常用API

  • GConfig:用于访问和修改配置文件中的设置。
  • FJsonWriterFJsonReader:用于JSON数据的序列化和反序列化。
  • FDateTimeFTimespan:用于处理时间和日期。

请注意,以上只是UE5 C++中常用类和API的一个概览,实际上UE5提供了更为丰富和复杂的API体系。为了更有效地使用这些API,建议深入阅读UE5的官方文档,并结合实际项目进行实践。此外,随着UE5的不断更新和发展,新的类和API也会不断出现,因此保持对UE5最新动态的关注也是非常重要的。

指针

智能指针类型 适用情形
共享指针TSharedPtr 共享指针拥有其引用的对象,无限防止该对象被删除,并在无共享指针或共享引用(见下文)引用其时,最终处理其的删除。共享指针可为空白,意味其不引用任何对象。任何非空共享指针都可对其引用的对象生成共享引用。
共享引用TSharedRef 共享引用的行为与共享指针类似,即其拥有自身引用的对象。对于空对象而言,其存在不同;共享引用须固定引用非空对象。共享指针无此类限制,因此共享引用可固定转换为共享指针,且该共享指针固定引用有效对象。要确认引用的对象是非空,或者要表明共享对象所有权时,请使用共享引用。
弱指针(TWeakPtrTSharedPtr 弱指针类与共享指针类似,但不拥有其引用的对象,因此不影响其生命周期。此属性中断引用循环,因此十分有用,但也意味弱指针可在无预警的情况下随时变为空。因此,弱指针可生成指向其引用对象的共享指针,确保程序员能对该对象进行安全临时访问。
唯一指针TUniquePtr 唯一指针仅会显式拥有其引用的对象。仅有一个唯一指针指向给定资源,因此唯一指针可转移所有权,但无法共享。复制唯一指针的任何尝试都将导致编译错误。唯一指针超出范围时,其将自动删除其所引用的对象。
NewObject 使用所有可用创建选项的可选参数创建一个新实例。提供极高的灵活性,包括带自动生成命名的简单使用案例。
CreateDefaultSubobject<class>

创建对象,不应该直接使用NEW

NewObject 使用所有可用创建选项的可选参数创建一个新实例。提供极高的灵活性,包括带自动生成命名的简单使用案例。
CreateDefaultSubobject<class>
1
2
class MYPROJECT_API UMyObject : public UObject

  • IsValid() 用于检查它是 null 还是垃圾,但是大部分情况下 IsValid 可以被更正规的编程规则替换,比如在调用 OnDestroy 事件时将指针清除至 Actor。
  • 如果禁用了 PendingKill()MarkGarbage() 将会提醒对象的所有者该对象将要被销毁,但是对象本身直到所有对它的引用都解除之后才会被垃圾回收。
  • 对于 Actor,即使 Actor 被调用了 Destroy(),并且被从关卡中移除,它还是会等到所有对它的引用都解除之后才会被垃圾回收。
  • 对于证书持有者的主要区别在于,对花费较大的对象进行垃圾回收的函数 MarkPendingKill() 不再起效。
  • 已有的用于 nullptr 的检查应该被 IsValid() 调用所替代,除非你进行手动清除,因为指针不再会被垃圾回收器通过 MarkPendingKill() 自动清除。

控制台

  • 变量
1
2
3
4
5
inline static int32 testDebugWeaponDrawing = 0;
inline FAutoConsoleVariableRef CVARDebugWeaponDrawing(TEXT("COOP.DebugWeapons"),//控制台输入 COOP.DebugWeapons 数字 设置DebugWeaponDrawing值
testDebugWeaponDrawing, //控制台变量
TEXT("Draw Debug Lines for Weapons"),
ECVF_Cheat);
  • 委托
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//声明函数
static void aShowQualityDebugWidget(const TArray<FString>& Args, UWorld* World)
{


if(World)
{
APlayerController* PlayerController = World->GetFirstPlayerController();
if(PlayerController)
{
ATestPlayerCharacter* PlayerCharacter = Cast<ATestPlayerCharacter>(PlayerController->GetPawn());
if (PlayerCharacter)
{

UE_LOG(LogTemp, Log, TEXT("Player Character Found: %d"), Args.Num());
UE_LOG(LogTemp, Log, TEXT("Player Character Found: %s"), *PlayerCharacter->GetName());
ATestPlayerController*cont=Cast<ATestPlayerController>(PlayerCharacter->GetController());
cont->OnTestCustomEvent.Broadcast(LexToString(Args[0]), FCString::Atoi64(*Args[1]));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Player Character Not Found"));
}
} else
{
UE_LOG(LogTemp, Warning, TEXT("Player Controller Not Found"));
}

} else
{
UE_LOG(LogTemp, Error, TEXT("World is null"));
}

}

//控制台绑定委托
static FAutoConsoleCommandWithWorldAndArgs ShowQualityDebugWidgetCmd(
TEXT("ShowQualityDebugWidget"),
TEXT("Show Quality Debug Tool Widget : \"ShowQualityDebugWidget true or ShowQualityWidget false\""),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(aShowQualityDebugWidget));

游戏系统

  • ARPG(动作角色扮演游戏)结合了角色扮演游戏(RPG)的元素和动作游戏的实时战斗机制。一款完整的ARPG游戏通常包含以下系统:

    1. 角色系统

      • 角色创建与管理:包括玩家角色的创建、属性管理(如生命值、攻击力、防御力等)和状态管理(如站立、移动、攻击、死亡等)。
      • 角色成长:通常包含角色养成和技能升级系统,玩家可以通过完成任务、击败敌人等方式获取经验值,提升角色等级和技能。
    2. 战斗系统

      • 实时战斗机制:玩家需要进行实时的战斗操作,包括移动、攻击、释放技能等。
      • 攻击判定与伤害计算:系统需要能够准确判定攻击是否命中,并计算相应的伤害值。
      • 技能系统:玩家可以学习和使用各种技能,这些技能通常具有不同的效果和伤害值。
    3. 地图与探索系统

      • 地图加载与场景管理:游戏需要能够加载不同的地图场景,并根据玩家的位置进行切换。
      • 探索与冒险:玩家可以在游戏世界中进行自由探索,发现隐藏的任务、宝藏和敌人。
    4. 任务与剧情系统

      • 任务系统:游戏通常包含各种任务,包括主线任务、支线任务和日常任务等。这些任务可以引导玩家进行游戏,并提供丰富的游戏体验。
      • 剧情系统:游戏通常有完整的故事情节,通过剧情动画、对话和任务等方式展现给玩家。
    5. 物品与装备系统

      • 物品管理:游戏需要能够管理各种物品,包括装备、消耗品和任务物品等。
      • 装备系统:玩家可以通过获取和穿戴不同的装备来提升角色的属性和能力。这些装备通常具有不同的品质和等级,玩家可以根据自己的需求进行选择。
    6. 用户界面(UI)系统

      • 游戏界面设计:包括血条、蓝条、技能栏、背包等游戏元素的显示和管理。
      • 交互设计:玩家需要通过UI系统进行游戏操作,如选择技能、查看物品、与NPC交互等。因此,UI系统需要具有良好的交互性和易用性。
    7. AI系统

      • NPC行为控制:如巡逻、追逐、战斗策略等。这些行为可以使NPC在游戏中表现得更加真实和有趣。
      • 敌人智能:敌人也需要具有一定的智能,能够根据玩家的位置和行动进行反应和攻击。
    8. 声音与特效系统

      • 音效管理:包括背景音乐、战斗音效和角色配音等。这些音效可以增强游戏的氛围和代入感。
      • 特效管理:包括技能特效、战斗特效和场景特效等。这些特效可以使游戏画面更加华丽和震撼。
    9. 游戏逻辑与物理模拟系统

      • 游戏逻辑:根据玩家的输入和当前游戏状态更新游戏逻辑,如角色的移动、攻击和死亡等。
      • 物理模拟:处理角色移动、碰撞等物理行为,使游戏世界表现得更加真实和可信。

    综上所述,一款完整的ARPG游戏需要包含多个复杂的系统,这些系统相互协作,共同为玩家提供丰富的游戏体验。

对于一款完整的ARPG游戏,除了之前提到的角色系统、战斗系统、地图与探索系统、任务与剧情系统、物品与装备系统、用户界面(UI)系统、AI系统、声音与特效系统以及游戏逻辑与物理模拟系统之外,还有一些系统也是必不可少的:

  1. 网络与系统交互系统

    • 网络同步:对于多人在线的ARPG游戏,网络同步是至关重要的。游戏需要确保所有玩家的操作和状态能够实时同步,以避免出现卡顿或不同步的情况。
    • 服务器与客户端交互:游戏需要与服务器进行交互,以处理玩家的登录、存档、交易等操作。这需要游戏具备稳定的服务器和高效的客户端-服务器通信协议。
  2. 存档与进度管理系统

    • 存档功能:玩家需要能够随时保存游戏进度,以便在之后继续游戏。这要求游戏具备可靠的存档机制,能够确保存档数据的完整性和安全性。
    • 进度管理:游戏还需要提供进度管理功能,如查看历史记录、选择存档等,以便玩家能够方便地管理自己的游戏进度。
  3. 成就与奖励系统

    • 成就系统:为了激励玩家完成更多的游戏内容,游戏通常会设置成就系统。玩家可以通过完成特定的任务或挑战来获得成就,并展示给其他玩家。
    • 奖励系统:游戏还需要设置奖励系统,如经验值、金币、装备等,以奖励玩家的努力和成就。这些奖励可以激励玩家继续游戏,并提升游戏的可玩性。
  4. 社交与互动系统

    • 好友系统:玩家可以添加其他玩家为好友,并与其进行聊天、组队等互动。这有助于增强游戏的社交性和互动性。
    • 公会系统:一些ARPG游戏还包含公会系统,玩家可以加入或创建公会,与其他公会成员一起完成任务、挑战副本等。这有助于提升游戏的团队合作和社交体验。
  5. 反作弊与安全系统

    • 反作弊机制:为了防止玩家使用外挂或作弊软件,游戏需要设置反作弊机制。这可以通过检测异常行为、封停作弊账号等方式来实现。
    • 安全保护:游戏还需要确保玩家的账号和信息安全。这包括加密存储敏感信息、提供安全的登录方式等。

综上所述,一款完整的ARPG游戏需要包含多个复杂的系统,这些系统相互协作,共同为玩家提供丰富的游戏体验。同时,游戏开发者还需要不断关注玩家的反馈和需求,持续优化和改进游戏系统,以提升游戏的竞争力和吸引力。

个人背包系统思路

  • 存储ID:通过ID(数据资产数组索引获取对应物品结构生成对象或者显示),背包数组存ID

标签

1
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "GameplayTags" });

声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "NativeGameplayTags.h"

namespace LyraGameplayTags
{

// Declare all of the custom native tags that Lyra will use
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_IsDead);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_Cooldown);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_Cost);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_TagsBlocked);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_TagsMissing);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_Networking);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_ActivateFail_ActivationGroup);
};


// Fill out your copyright notice in the Description page of Project Settings.


#include "FairyTag/FairyTag.h"

#include "Engine/EngineTypes.h"
#include "GameplayTagsManager.h"

namespace LyraGameplayTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_IsDead, "Ability.ActivateFail.IsDead", "Ability failed to activate because its owner is dead.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_Cooldown, "Ability.ActivateFail.Cooldown", "Ability failed to activate because it is on cool down.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_Cost, "Ability.ActivateFail.Cost", "Ability failed to activate because it did not pass the cost checks.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_TagsBlocked, "Ability.ActivateFail.TagsBlocked", "Ability failed to activate because tags are blocking it.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_TagsMissing, "Ability.ActivateFail.TagsMissing", "Ability failed to activate because tags are missing.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_Networking, "Ability.ActivateFail.Networking", "Ability failed to activate because it did not pass the network checks.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_ActivateFail_ActivationGroup, "Ability.ActivateFail.ActivationGroup", "Ability failed to activate because of its activation group.");
}

要读取 FGameplayTag 标签,你可以使用 Unreal Engine 提供的 API 函数。以下是几种常见的方法来读取和检查这些标签:

  1. 通过 HasTag 方法检查某个对象是否具有特定标签:

    1
    2
    3
    4
    if (SomeActor->Tags.HasTag(LyraGameplayTags::Ability_ActivateFail_IsDead))
    {
    // 处理逻辑
    }
  2. 通过 GetAllTags 获取所有标签并遍历:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    FGameplayTagContainer AllTags;
    SomeActor->GetAllTags(AllTags);

    for (const FGameplayTag& Tag : AllTags)
    {
    if (Tag == LyraGameplayTags::Ability_ActivateFail_IsDead)
    {
    // 处理逻辑
    }
    }
  3. 使用 MatchExactMatchAny 来匹配标签:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    FGameplayTagQuery Query;
    Query.TagQueries.Empty();
    Query.TagQueries.Add(FGameplayTagQueryExpression::CreateExpression_Lambda(
    [](const FGameplayTag& Tag) -> bool
    {
    return Tag.MatchesTag(LyraGameplayTags::Ability_ActivateFail_IsDead);
    }));

    if (SomeActor->Tags.MatchExact(Query))
    {
    // 处理逻辑
    }
  4. 直接比较标签:

    1
    2
    3
    4
    5
    FGameplayTag TagToCheck = ...; // 从某处获取标签
    if (TagToCheck == LyraGameplayTags::Ability_ActivateFail_IsDead)
    {
    // 处理逻辑
    }
  5. 使用 FGameplayTagContainer 管理多个标签:

    1
    2
    3
    4
    5
    6
    7
    8
    FGameplayTagContainer TagsToCheck;
    TagsToCheck.AddTag(LyraGameplayTags::Ability_ActivateFail_IsDead);
    TagsToCheck.AddTag(LyraGameplayTags::Ability_ActivateFail_Cooldown);

    if (SomeActor->Tags.HasAny(TagsToCheck))
    {
    // 处理逻辑
    }

注意事项:

  • 确保你已经包含了必要的头文件,例如 #include "GameplayTagsManager.h"
  • 确保你使用的对象(如 SomeActor)确实有 Tags 属性或类似的方法来访问标签。

根据你的具体需求选择合适的方法来读取和处理这些标签。

在处理大量标签时,使用大量的 if 语句确实可能会带来一些问题,尤其是在代码的可维护性和性能方面。以下是一些潜在的问题以及相应的优化建议:

潜在问题:

  1. 代码冗长

    • 大量的 if 语句会使代码变得冗长且难以阅读。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      if (Weapon->Tags.HasTag(FName("Item.Weapon.Weapon1")))
      {
      // 处理 Weapon1
      }
      else if (Weapon->Tags.HasTag(FName("Item.Weapon.Weapon2")))
      {
      // 处理 Weapon2
      }
      // 以此类推...
  2. 可维护性差

    • 每次添加或修改武器时,都需要更新 if 语句,增加了维护成本。
    • 如果标签数量非常多,维护这些 if 语句会变得非常繁琐。
  3. 性能影响

    • 虽然单个 if 语句的性能影响很小,但如果 if 语句的数量非常多,可能会对性能产生一定的影响。
    • 尤其是在每帧或每秒频繁执行这些检查时,性能开销会更加明显。
  4. 错误风险

    • 大量的 if 语句增加了出错的可能性,例如拼写错误或遗漏某些条件。

优化建议:

  1. 使用映射(Map)

    • 使用 TMapTMultiMap 来映射标签到处理函数。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      #include "Containers/Map.h"
      #include "UObject/WeakObjectPtr.h"

      typedef void (*WeaponHandlerFunc)(AWeapon*);

      void HandleWeapon1(AWeapon* Weapon);
      void HandleWeapon2(AWeapon* Weapon);
      // 以此类推...

      TMap<FName, WeaponHandlerFunc> WeaponHandlers;

      void InitializeWeaponHandlers()
      {
      WeaponHandlers.Add(FName("Item.Weapon.Weapon1"), HandleWeapon1);
      WeaponHandlers.Add(FName("Item.Weapon.Weapon2"), HandleWeapon2);
      // 以此类推...
      }

      void AYourCharacter::PickupWeapon(AWeapon* Weapon)
      {
      for (const FName& Tag : Weapon->Tags)
      {
      if (WeaponHandlers.Contains(Tag))
      {
      WeaponHandlers[Tag](Weapon);
      }
      }
      }

      void HandleWeapon1(AWeapon* Weapon)
      {
      // 处理 Weapon1
      }

      void HandleWeapon2(AWeapon* Weapon)
      {
      // 处理 Weapon2
      }
  2. 使用策略模式

    • 使用策略模式来定义不同的处理逻辑,并根据标签选择相应的策略。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      class IWeaponStrategy
      {
      public:
      virtual void Handle(AWeapon* Weapon) = 0;
      virtual ~IWeaponStrategy() {}
      };

      class Weapon1Strategy : public IWeaponStrategy
      {
      public:
      virtual void Handle(AWeapon* Weapon) override
      {
      // 处理 Weapon1
      }
      };

      class Weapon2Strategy : public IWeaponStrategy
      {
      public:
      virtual void Handle(AWeapon* Weapon) override
      {
      // 处理 Weapon2
      }
      };

      TMap<FName, TSharedPtr<IWeaponStrategy>> WeaponStrategies;

      void InitializeWeaponStrategies()
      {
      WeaponStrategies.Add(FName("Item.Weapon.Weapon1"), MakeShared<Weapon1Strategy>());
      WeaponStrategies.Add(FName("Item.Weapon.Weapon2"), MakeShared<Weapon2Strategy>());
      // 以此类推...
      }

      void AYourCharacter::PickupWeapon(AWeapon* Weapon)
      {
      for (const FName& Tag : Weapon->Tags)
      {
      if (WeaponStrategies.Contains(Tag))
      {
      WeaponStrategies[Tag]->Handle(Weapon);
      }
      }
      }
  3. 使用反射机制

    • 使用反射机制来动态调用处理函数。
    • 例如,使用 Unreal Engine 的 UFunctionUObject 系统:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      void AYourCharacter::PickupWeapon(AWeapon* Weapon)
      {
      for (const FName& Tag : Weapon->Tags)
      {
      FString HandlerName = FString::Printf(TEXT("Handle%s"), *Tag.ToString());
      UFunction* HandlerFunction = GetClass()->FindFunctionByName(FName(*HandlerName));
      if (HandlerFunction)
      {
      this->ProcessEvent(HandlerFunction, &Weapon);
      }
      }
      }

      void AYourCharacter::HandleItemWeaponWeapon1(AWeapon* Weapon)
      {
      // 处理 Weapon1
      }

      void AYourCharacter::HandleItemWeaponWeapon2(AWeapon* Weapon)
      {
      // 处理 Weapon2
      }
  4. 使用事件系统

    • 使用 Unreal Engine 的事件系统来处理不同类型的武器。
    • 例如,使用 DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParamDECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWeaponHandler, AWeapon*, Weapon);

      UPROPERTY(BlueprintAssignable)
      FWeaponHandler OnWeapon1PickedUp;

      UPROPERTY(BlueprintAssignable)
      FWeaponHandler OnWeapon2PickedUp;

      // 以此类推...

      void AYourCharacter::PickupWeapon(AWeapon* Weapon)
      {
      if (Weapon->Tags.HasTag(FName("Item.Weapon.Weapon1")))
      {
      OnWeapon1PickedUp.Broadcast(Weapon);
      }
      else if (Weapon->Tags.HasTag(FName("Item.Weapon.Weapon2")))
      {
      OnWeapon2PickedUp.Broadcast(Weapon);
      }
      // 以此类推...
      }

示例代码:

使用映射(Map):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "Containers/Map.h"
#include "UObject/WeakObjectPtr.h"

typedef void (*WeaponHandlerFunc)(AWeapon*);

void HandleWeapon1(AWeapon* Weapon);
void HandleWeapon2(AWeapon* Weapon);
// 以此类推...

TMap<FName, WeaponHandlerFunc> WeaponHandlers;

void InitializeWeaponHandlers()
{
WeaponHandlers.Add(FName("Item.Weapon.Weapon1"), HandleWeapon1);
WeaponHandlers.Add(FName("Item.Weapon.Weapon2"), HandleWeapon2);
// 以此类推...
}

void AYourCharacter::PickupWeapon(AWeapon* Weapon)
{
for (const FName& Tag : Weapon->Tags)
{
if (WeaponHandlers.Contains(Tag))
{
WeaponHandlers[Tag](Weapon);
}
}
}

void HandleWeapon1(AWeapon* Weapon)
{
// 处理 Weapon1
}

void HandleWeapon2(AWeapon* Weapon)
{
// 处理 Weapon2
}

使用策略模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class IWeaponStrategy
{
public:
virtual void Handle(AWeapon* Weapon) = 0;
virtual ~IWeaponStrategy() {}
};

class Weapon1Strategy : public IWeaponStrategy
{
public:
virtual void Handle(AWeapon* Weapon) override
{
// 处理 Weapon1
}
};

class Weapon2Strategy : public IWeaponStrategy
{
public:
virtual void Handle(AWeapon* Weapon) override
{
// 处理 Weapon2
}
};

TMap<FName, TSharedPtr<IWeaponStrategy>> WeaponStrategies;

void InitializeWeaponStrategies()
{
WeaponStrategies.Add(FName("Item.Weapon.Weapon1"), MakeShared<Weapon1Strategy>());
WeaponStrategies.Add(FName("Item.Weapon.Weapon2"), MakeShared<Weapon2Strategy>());
// 以此类推...
}

void AYourCharacter::PickupWeapon(AWeapon* Weapon)
{
for (const FName& Tag : Weapon->Tags)
{
if (WeaponStrategies.Contains(Tag))
{
WeaponStrategies[Tag]->Handle(Weapon);
}
}
}

总结:

使用大量的 if 语句来处理大量标签会导致代码冗长、可维护性差和潜在的性能问题。通过使用映射、策略模式、反射机制或事件系统,可以更高效地管理和处理这些标签,提高代码的可维护性和性能。选择适合你项目需求的方法来优化标签处理逻辑。

GameplayTags 是 Unreal Engine 中用于管理和操作标签(tags)的一种强大工具。它们非常适合用于标记和分类游戏中的各种元素,如角色、物品、技能等。然而,直接使用 GameplayTags 来创建对象并不是其设计初衷,也不是最佳实践。
GameplayTags 的用途:
标记和分类:
用于标记和分类游戏中的各种元素,例如武器类型、技能效果、角色状态等。
例如:Item.Weapon.Rifle、Skill.Fireball、Character.State.Invincible。
条件判断和逻辑控制:
通过检查对象是否具有特定的标签来执行不同的逻辑。
例如:根据角色是否具有 Character.State.Invincible 标签来决定是否受到伤害。
数据驱动设计:
使用 GameplayTags 可以实现更灵活的数据驱动设计,减少硬编码逻辑。
例如:通过配置文件或蓝图动态添加或移除标签,改变游戏行为。
不适合用 GameplayTags 创建对象的原因:
职责分离:
GameplayTags 主要用于标记和分类,而不是创建对象。创建对象通常涉及更多的逻辑,如初始化属性、设置行为等,这些应该由类构造函数或工厂模式来处理。
性能考虑:
每次创建对象时都依赖 GameplayTags 进行复杂的标签匹配和处理,可能会引入不必要的性能开销。
直接通过类或工厂模式创建对象更加高效和明确。
代码可读性和维护性:
使用 GameplayTags 创建对象会使代码变得不直观,难以理解和维护。
类和工厂模式提供了更清晰的对象创建逻辑,便于调试和扩展。

UI

  • 在控件中创建面板控件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void ULatticeUIWidget::NativeConstruct()
    {
    Super::NativeConstruct();
    if(!WidgetTree)
    return;
    USizeBox *SizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass());

    SizeBox->SetWidthOverride(300.0f);
    SizeBox->SetHeightOverride(300.0f);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Super::NativePreConstruct();
    if(!WidgetTree)
    return;
    USizeBox *SizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass());

    SizeBox->SetWidthOverride(300.0f);
    SizeBox->SetHeightOverride(300.0f);
    WidgetTree->RootWidget=SizeBox;

    UImage *Image = WidgetTree->ConstructWidget<UImage>(UImage::StaticClass());
    SizeBox->AddChild(Image);

    背包

    • 小格子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // Fill out your copyright notice in the Description page of Project Settings.

    #pragma once

    #include "CoreMinimal.h"
    #include "Blueprint/UserWidget.h"
    #include "MyUserWidget.generated.h"

    //使用物触发的委托,用于传给控制器
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyUsetheEvent);
    /**
    *
    */
    //小格子控件,用于显示背包物品纹理和数量
    UCLASS()
    class TESTDEMO_API UMyUserWidget : public UUserWidget
    {
    GENERATED_BODY()
    public:
    //聚焦时触发
    virtual FReply NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent) override;
    //结束聚焦时触发
    virtual void NativeOnFocusLost(const FFocusEvent& InFocusEvent) override;
    //按键按下时触发
    virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;

    //对其他格子的导航
    UFUNCTION(BlueprintCallable)
    void UINavigation(int p_index=1);

    //控件动画
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
    UWidgetAnimation* m_animation;

    UPROPERTY(BlueprintAssignable)
    FMyUsetheEvent OnUsethe;
    void Useitems();
    };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    // Fill out your copyright notice in the Description page of Project Settings.


    #include "MyUserWidget.h"

    #include "Components/PanelWidget.h"
    #include "Kismet/BlueprintTypeConversions.h"

    FReply UMyUserWidget::NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent)
    {
    PlayAnimation(m_animation);
    return Super::NativeOnFocusReceived(InGeometry, InFocusEvent);
    }

    void UMyUserWidget::NativeOnFocusLost(const FFocusEvent& InFocusEvent)
    {

    UE_LOG(LogTemp,Warning,TEXT("%s"),*GetName());
    Super::NativeOnFocusLost(InFocusEvent);
    }

    FReply UMyUserWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
    {

    // 检查按键并确定导航方向
    if (InKeyEvent.GetKey() == EKeys::Up)
    {
    UINavigation(-6);
    }
    else if (InKeyEvent.GetKey() == EKeys::Down)
    {
    UINavigation(6);
    }
    else if (InKeyEvent.GetKey() == EKeys::Left)
    {
    UINavigation(-1);
    }
    else if (InKeyEvent.GetKey() == EKeys::Right)
    {
    UINavigation(1);
    }else if (InKeyEvent.GetKey() == EKeys::SpaceBar)
    {
    Useitems();
    }



    return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
    }

    void UMyUserWidget::UINavigation(int p_index)
    {
    // 获取自身位置索引
    UPanelWidget* parent = GetParent();
    if (!parent)
    {
    UE_LOG(LogTemp, Warning, TEXT("Parent widget is null"));
    return;
    }

    int childrenCount = parent->GetChildrenCount();
    if (childrenCount == 0)
    {
    UE_LOG(LogTemp, Warning, TEXT("Parent widget has no children"));
    return;
    }

    int index = parent->GetChildIndex(this);
    if (index == INDEX_NONE)
    {
    UE_LOG(LogTemp, Warning, TEXT("This widget is not a child of the parent widget"));
    return;
    }

    // 获取导航控件并设置导航聚焦
    int new_index = UE::Geometry::VectorUtil::Clamp(index + p_index, 0, childrenCount - 1);
    UUserWidget* bc = Cast<UUserWidget>(parent->GetChildAt(new_index));
    if (bc && IsValid(bc))
    {
    bc->SetFocus();
    }
    else
    {
    UE_LOG(LogTemp, Warning, TEXT("Failed to cast child widget to UUserWidget or child widget is invalid"));
    }
    }

    void UMyUserWidget::Useitems()
    {
    // UItem* item = NewObject<UItem>();
    // item->m_name = "test";
    // item->m_price = 100;
    // item->m_num = 1;
    // item->m_type = 1;
    // item->m_id = 1;
    // item->m_icon = LoadObject<UTexture2D>(nullptr, TEXT("/Game/UI/Icon/Item/Item_1.Item_1"));
    // item->m_desc = "test";
    // item->m_effect ="test";
    OnUsethe.Broadcast();
    }


    1
    2
    3
    4
    UPROPERTY(BlueprintReadOnly, Transient, meta = (BindWidgetOptional) , meta = (BindWidgetAnim))
    UWidgetAnimation* Fade;
    UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional))
    UButton* Butt;

    UI二

    • 这里写了两个类,一个为包裹框,一个为物品框

    • 物品框主要包含了尺寸框,背景图像,图标纹理,数量显示

    • 包裹框用于包裹框组件,可以获得物品框的索引,还用于一个事件,物品框点击触发这个事件并发送出去

    • 包裹框还封装了对子节点显示清理/初始化和刷新的方法,只要通过遍历节点在获取物品属性数组索引来设置

    • 物品框代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    // Fill out your copyright notice in the Description page of Project Settings.

    #pragma once

    #include "CoreMinimal.h"
    #include "Blueprint/UserWidget.h"
    #include "ItemBoxWidgetBase.generated.h"

    class UTestUserWidget;
    class UTextBlock;
    class UImage;


    USTRUCT(BlueprintType)
    struct FItemShowStruct
    {
    GENERATED_BODY()
    public:
    //道具数量
    UPROPERTY(BlueprintReadWrite,EditAnywhere)
    int32 quantity;
    // 道具图标
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (AllowedClasses = "Texture2D,PaperSprite"))
    UTexture2D *Icon;
    };


    UCLASS()
    class TESTGUI_API UItemBoxWidgetBase : public UUserWidget
    {
    GENERATED_BODY()
    public:

    //Image
    UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidgetOptional))
    UImage* icon;

    UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidgetOptional))
    UTextBlock* TextBlock;


    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Image", meta = (FieldNotify))
    FString ImagePath;

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    UTestUserWidget* TestUserWidget;

    virtual void NativeConstruct() override;
    virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
    virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
    };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // Fill out your copyright notice in the Description page of Project Settings.


    #include "Widget/ItemBoxWidgetBase.h"

    #include "TestUserWidget.h"
    #include "Blueprint/WidgetTree.h"
    #include "Components/Image.h"
    #include "Components/PanelWidget.h"

    void UItemBoxWidgetBase::NativeConstruct()
    {
    Super::NativeConstruct();

    }

    void UItemBoxWidgetBase::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
    {

    }

    FReply UItemBoxWidgetBase::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
    {
    try {
    // 检查鼠标按键
    if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) {
    // 处理左键按下

    int32 index = GetParent()->GetChildIndex(this);
    if (TestUserWidget) {
    TestUserWidget->OnItemBoxClick.Broadcast(this, index);
    UE_LOG(LogTemp, Log, TEXT("Left mouse button pressed"));
    }


    } else if (InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton) {
    // 处理右键按下
    UE_LOG(LogTemp, Log, TEXT("Right mouse button pressed"));
    } else {
    // 其他按键
    UE_LOG(LogTemp, Log, TEXT("Other mouse button pressed"));
    }
    } catch (const std::exception& e) {
    // 记录异常信息
    UE_LOG(LogTemp, Error, TEXT("Exception in NativeOnMouseButtonDown: %s"), *FString(e.what()));
    }
    return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);

    }

    • 包裹框代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    // Fill out your copyright notice in the Description page of Project Settings.

    #pragma once

    #include "CoreMinimal.h"
    #include "Blueprint/UserWidget.h"
    #include "Components/WrapBox.h"
    #include "TestUserWidget.generated.h"

    struct FItemShowStruct;
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnItemBoxClick, const UItemBoxWidgetBase*, ItemIndex, int32, Quantity);

    USTRUCT(BlueprintType)
    struct FBoxStruct
    {
    public:
    GENERATED_BODY()

    //要生成的格子数量
    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    int32 Quantity;

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    float ChildWidgetWidth;

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    int32 line;

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    int32 Column;


    };

    UCLASS()
    class TESTGUI_API UTestUserWidget : public UUserWidget
    {
    GENERATED_BODY()
    public:
    virtual void NativeConstruct() override;
    virtual void NativeOnInitialized() override;
    void InitBox();

    //包裹框控件
    UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidgetOptional))
    UWrapBox* WrapBox;

    //子控件类型
    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    TSubclassOf<UUserWidget> ItemBoxWidgetClass;

    //包裹框参数
    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FBoxStruct BoxStruct;

    //点击子节点事件
    UPROPERTY(BlueprintAssignable)
    FOnItemBoxClick OnItemBoxClick;

    //刷新显示
    UFUNCTION(BlueprintCallable)
    void RefreshShow(const TArray<FItemShowStruct>& ItemShowStructArray);
    private:
    //清空纹理
    void CleaShow();

    };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    // Fill out your copyright notice in the Description page of Project Settings.


    #include "TestUserWidget.h"

    #include "Components/Image.h"
    #include "Components/TextBlock.h"
    #include "Widget/ItemBoxWidgetBase.h"

    void UTestUserWidget::NativeOnInitialized()
    {
    Super::NativeOnInitialized();
    InitBox();
    if (ItemBoxWidgetClass)
    {
    for (int32 i = 0; i < BoxStruct.line*BoxStruct.Column; i++)
    {
    UItemBoxWidgetBase* ItemBoxWidget = CreateWidget<UItemBoxWidgetBase>(GetWorld(), ItemBoxWidgetClass);
    ItemBoxWidget->TestUserWidget = this;
    WrapBox->AddChild(ItemBoxWidget);

    }

    }
    }

    void UTestUserWidget::InitBox()
    {
    //InnerSlotPadding
    float wrapsize=BoxStruct.Column*BoxStruct.ChildWidgetWidth+(BoxStruct.Column-1)*WrapBox->GetInnerSlotPadding().X;
    WrapBox->SetWrapSize(wrapsize);
    }

    void UTestUserWidget::CleaShow()
    {
    //ResourceObject
    for (auto Element : WrapBox->GetAllChildren())
    {
    UItemBoxWidgetBase *ItemBoxWidget = Cast<UItemBoxWidgetBase>(Element);
    ItemBoxWidget->icon->SetBrushResourceObject(nullptr);
    }
    }

    void UTestUserWidget::RefreshShow(const TArray<FItemShowStruct>& ItemShowStructArray)
    {
    CleaShow();

    // 获取所有子元素
    int32 Children = WrapBox->GetChildrenCount();

    // 确保 ItemShowStructArray 的长度不小于子元素数量
    if (ItemShowStructArray.Num() < Children)
    {
    UE_LOG(LogTemp, Error, TEXT("ItemShowStructArray length (%d) is less than children count (%d)"), ItemShowStructArray.Num(), Children);
    return;
    }

    // 遍历所有子元素
    for (int32 Index = 0; Index < Children; Index++)
    {
    UItemBoxWidgetBase* ItemBoxWidget = Cast<UItemBoxWidgetBase>(WrapBox->GetChildAt(Index));

    // 检查类型转换是否成功
    if (ItemBoxWidget)
    {
    if (ItemBoxWidget->icon)
    {
    // 设置图标资源
    ItemBoxWidget->icon->SetBrushResourceObject(ItemShowStructArray[Index].Icon);
    ItemBoxWidget->TextBlock->SetText(FText::AsNumber(ItemShowStructArray[Index].quantity));
    }
    else
    {
    UE_LOG(LogTemp, Warning, TEXT("Icon is null at index %d"), Index);
    }
    }
    else
    {
    UE_LOG(LogTemp, Warning, TEXT("Failed to cast at index %d"), Index);
    }
    }
    }



    void UTestUserWidget::NativeConstruct()
    {
    Super::NativeConstruct();
    }



    框架设计

    控制器类

    • 对玩家移动输入和动画操作
    • 对UI进行显示和隐藏

    状态类

    • 玩家背包
    • 玩家数据

    HUD

    • UI创建和管理

    背包

    通过遍历组件通过索引获取纹理数组显示

    • 增:
      1. 向背包添加物品,如果相同可叠加且存在同类物品则叠加
      2. 向背包添加物品,如果不可叠加则创建新的对象并寻找空的第一个空位放下
      3. 向背包添加物品,如果物品大于最大叠加数量则创建新的对象,并寻找空的位置放下
    • 删:
      1. 删除指定的物品
    • 改:
      1. 按索引修改指定位置物品
      2. 交换物品
    • 查:
      1. 遍历所有物品并显示到UI

三渲二修改渲染管线

EMaterialShadingModel