属性

属性
凝雨属性
此文档包含了属性与各种数据结构的声明和初始化
基础属性类型
| 数据类型 | 描述 |
|---|---|
| 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
9UENUM(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
2UE_LOG(LogTemp, Warning, TEXT("玩家:%s, Pawn:%s"), *PlayerController->GetName(), *Pawn->GetName()
1
2FMessageDialog::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 | //声明FString |
- FromName
1 | mystring=myname.ToString(); //myname转String |
- strFromText
1 | FString strFromText=text1.ToString(); //strFromText转String 要转FromName可以通过FString转 |
纯函数(绿色函数)
1 | UFUNCTION(BlueprintPure, Category = "MyFunctions") |
自定义事件
在Unreal Engine 5(UE5)中,绑定和使用委托涉及几个关键步骤。以下是一个详细的指南,包括如何绑定和使用单播委托、多播委托以及动态委托。
一、单播委托的绑定和使用
声明委托
首先,在头文件中声明一个单播委托类型,并在类中声明一个该类型的委托变量。
1
2
3
4
5
6
7DECLARE_DELEGATE(MySinglecastDelegate);
class MyClass
{
public:
MySinglecastDelegate MyDelegate;
};绑定回调
在需要绑定回调的地方,使用
Bind方法(对于UObject子类成员函数使用BindUObject,静态函数使用BindStatic,Lambda表达式使用BindLambda等)。1
2MyClass myObject;
myObject.MyDelegate.BindUObject(&myObject, &MyClass::MyFunction);或者,对于静态函数:
1
myObject.MyDelegate.BindStatic(&MyStaticFunction);
对于Lambda表达式:
1
myObject.MyDelegate.BindLambda([](){ /* Your code here */ });
执行委托
当需要执行委托时,调用
Execute方法。1
myObject.MyDelegate.Execute();
二、多播委托的绑定和使用
声明多播委托
与单播委托类似,首先声明一个多播委托类型,并在类中声明一个该类型的委托变量。
1
2
3
4
5
6
7DECLARE_MULTICAST_DELEGATE(MyMulticastDelegate);
class MyClass
{
public:
MyMulticastDelegate MyMulticastDelegateVar;
};添加回调
使用
Add方法(对于UObject子类成员函数使用AddUObject,Lambda表达式使用AddLambda等)来添加多个回调。1
2
3MyClass myObject;
myObject.MyMulticastDelegateVar.AddUObject(&myObject, &MyClass::MyFunction1);
myObject.MyMulticastDelegateVar.AddUObject(&myObject, &MyClass::MyFunction2);或者,对于Lambda表达式:
1
myObject.MyMulticastDelegateVar.AddLambda([](){ /* Your code here */ });
广播委托
当需要执行多播委托时,调用
Broadcast方法。1
myObject.MyMulticastDelegateVar.Broadcast();
三、动态委托的绑定和使用
声明动态委托
动态委托需要使用
UDELEGATE或UMULTICAST_DELEGATE宏,并在类中声明一个UPROPERTY(BlueprintAssignable)属性的委托变量。1
2
3
4
5
6
7
8
9
10DECLARE_DYNAMIC_MULTICAST_DELEGATE(void, MyDynamicDelegate, int32, float);
UCLASS()
class MyClass : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
MyDynamicDelegate MyDynamicDelegateVar;
};绑定回调
在C++中,动态委托的绑定方式与单播委托类似,但需要使用
BindDynamic方法(对于UObject子类成员函数)。在蓝图中,可以直接通过节点来绑定和解绑回调。1
2MyClass myObject;
myObject.MyDynamicDelegateVar.BindDynamic(&myObject, &MyClass::MyDynamicFunction);在蓝图中,找到对应的委托变量,并使用“绑定事件”节点来绑定回调。
执行或广播委托
动态单播委托使用
Execute方法,动态多播委托使用Broadcast方法。1
myObject.MyDynamicDelegateVar.Execute();
或者(对于多播委托):
1
myObject.MyDynamicMulticastDelegateVar.Broadcast();
注意事项
- 确保委托在绑定和执行时都是有效的,避免在对象销毁后还尝试执行委托。
- 对于动态委托,特别是与蓝图交互时,要确保回调函数是安全的,并且不会导致内存泄漏或访问违规。
- 委托的声明和使用通常与UObject子类相关联,因为委托的生命周期管理通常与UObject的生命周期管理相结合。
通过以上步骤,你可以在UE5中有效地绑定和使用委托来实现事件驱动编程和回调机制。
E5中的委托提供了多种绑定方式,以满足不同的需求:
- BindUObject:用于绑定UObject子类的成员函数。
- BindStatic:用于绑定静态函数或全局函数。
- BindLambda:用于绑定Lambda表达式,提供匿名函数的绑定方式。
- BindDynamic(仅适用于动态委托):用于绑定使用UFUNCTION()修饰的成员函数。
此外,对于多播委托,还提供了AddUObject、AddStatic、AddLambda等添加回调的方式。
在UE5中,动态委托(Dynamic Delegate和Dynamic Multicast Delegate)特别适用于与蓝图的交互。为了在蓝图中能够绑定和解绑回调函数,这些动态委托必须使用UFUNCTION()修饰,并且需要在声明时指定参数名称。这进一步增加了绑定方式的多样性。
- 定义
1 | // MyCustomEvent.h |
- 发送事件
1 | void AYourActor::TriggerMyCustomEvent() |
- 绑定和接收
1 | // AnotherClass.cpp |
委托宏
1 | DECLARE_DYNAMIC_MULTICAST_DELEGATE:无参数的多播委托。 |
向量
位置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. 渲染和材质
- UMaterial、UMaterialInstance:用于定义和实例化材质,控制物体的外观。
- USceneComponent、UMeshComponent:用于处理场景中的组件和网格体的渲染。
- UStaticMesh:静态网格体,用于表示不会移动的物体,如建筑、地形等。
4. 物理和碰撞
- UPhysicsBody、UPhysicsConstraintComponent:用于处理物理模拟和约束。
- UCollisionComponent:用于碰撞检测和处理。
5. 输入和UI
- UInputComponent:用于处理输入的组件,可以附加到Actor或Pawn上,用于响应玩家的键盘、鼠标或手柄输入。
- UUserWidget:用于创建用户界面的基类,通过蓝图和代码可以创建各种界面元素。
6. 网络和多人游戏
- ANetDriver、AConnection:用于处理网络通信的类。
- AController、APawn:在多人游戏中,用于表示玩家控制的角色和物体。
7. 数据管理和序列化
- FString、TArray、TMap:UE5中常用的字符串、数组和映射类型,用于数据存储和序列化。
- FSerializer、FArchive:用于自定义序列化和反序列化过程。
8. 其他常用API
- GConfig:用于访问和修改配置文件中的设置。
- FJsonWriter、FJsonReader:用于JSON数据的序列化和反序列化。
- FDateTime、FTimespan:用于处理时间和日期。
请注意,以上只是UE5 C++中常用类和API的一个概览,实际上UE5提供了更为丰富和复杂的API体系。为了更有效地使用这些API,建议深入阅读UE5的官方文档,并结合实际项目进行实践。此外,随着UE5的不断更新和发展,新的类和API也会不断出现,因此保持对UE5最新动态的关注也是非常重要的。
指针
| 智能指针类型 | 适用情形 |
|---|---|
共享指针(TSharedPtr) |
共享指针拥有其引用的对象,无限防止该对象被删除,并在无共享指针或共享引用(见下文)引用其时,最终处理其的删除。共享指针可为空白,意味其不引用任何对象。任何非空共享指针都可对其引用的对象生成共享引用。 |
共享引用(TSharedRef) |
共享引用的行为与共享指针类似,即其拥有自身引用的对象。对于空对象而言,其存在不同;共享引用须固定引用非空对象。共享指针无此类限制,因此共享引用可固定转换为共享指针,且该共享指针固定引用有效对象。要确认引用的对象是非空,或者要表明共享对象所有权时,请使用共享引用。 |
弱指针(TWeakPtrTSharedPtr) |
弱指针类与共享指针类似,但不拥有其引用的对象,因此不影响其生命周期。此属性中断引用循环,因此十分有用,但也意味弱指针可在无预警的情况下随时变为空。因此,弱指针可生成指向其引用对象的共享指针,确保程序员能对该对象进行安全临时访问。 |
唯一指针(TUniquePtr) |
唯一指针仅会显式拥有其引用的对象。仅有一个唯一指针指向给定资源,因此唯一指针可转移所有权,但无法共享。复制唯一指针的任何尝试都将导致编译错误。唯一指针超出范围时,其将自动删除其所引用的对象。 |
NewObject |
使用所有可用创建选项的可选参数创建一个新实例。提供极高的灵活性,包括带自动生成命名的简单使用案例。 |
|---|---|
CreateDefaultSubobject<class> |
创建对象,不应该直接使用NEW
NewObject |
使用所有可用创建选项的可选参数创建一个新实例。提供极高的灵活性,包括带自动生成命名的简单使用案例。 |
|---|---|
CreateDefaultSubobject<class> |
1 | class MYPROJECT_API UMyObject : public UObject |
IsValid()用于检查它是 null 还是垃圾,但是大部分情况下 IsValid 可以被更正规的编程规则替换,比如在调用OnDestroy事件时将指针清除至 Actor。- 如果禁用了
PendingKill(),MarkGarbage()将会提醒对象的所有者该对象将要被销毁,但是对象本身直到所有对它的引用都解除之后才会被垃圾回收。 - 对于 Actor,即使 Actor 被调用了
Destroy(),并且被从关卡中移除,它还是会等到所有对它的引用都解除之后才会被垃圾回收。 - 对于证书持有者的主要区别在于,对花费较大的对象进行垃圾回收的函数
MarkPendingKill()不再起效。 - 已有的用于 nullptr 的检查应该被
IsValid()调用所替代,除非你进行手动清除,因为指针不再会被垃圾回收器通过MarkPendingKill()自动清除。
控制台
- 变量
1 | inline static int32 testDebugWeaponDrawing = 0; |
- 委托
1 | //声明函数 |
游戏系统
ARPG(动作角色扮演游戏)结合了角色扮演游戏(RPG)的元素和动作游戏的实时战斗机制。一款完整的ARPG游戏通常包含以下系统:
角色系统:
- 角色创建与管理:包括玩家角色的创建、属性管理(如生命值、攻击力、防御力等)和状态管理(如站立、移动、攻击、死亡等)。
- 角色成长:通常包含角色养成和技能升级系统,玩家可以通过完成任务、击败敌人等方式获取经验值,提升角色等级和技能。
战斗系统:
- 实时战斗机制:玩家需要进行实时的战斗操作,包括移动、攻击、释放技能等。
- 攻击判定与伤害计算:系统需要能够准确判定攻击是否命中,并计算相应的伤害值。
- 技能系统:玩家可以学习和使用各种技能,这些技能通常具有不同的效果和伤害值。
地图与探索系统:
- 地图加载与场景管理:游戏需要能够加载不同的地图场景,并根据玩家的位置进行切换。
- 探索与冒险:玩家可以在游戏世界中进行自由探索,发现隐藏的任务、宝藏和敌人。
任务与剧情系统:
- 任务系统:游戏通常包含各种任务,包括主线任务、支线任务和日常任务等。这些任务可以引导玩家进行游戏,并提供丰富的游戏体验。
- 剧情系统:游戏通常有完整的故事情节,通过剧情动画、对话和任务等方式展现给玩家。
物品与装备系统:
- 物品管理:游戏需要能够管理各种物品,包括装备、消耗品和任务物品等。
- 装备系统:玩家可以通过获取和穿戴不同的装备来提升角色的属性和能力。这些装备通常具有不同的品质和等级,玩家可以根据自己的需求进行选择。
用户界面(UI)系统:
- 游戏界面设计:包括血条、蓝条、技能栏、背包等游戏元素的显示和管理。
- 交互设计:玩家需要通过UI系统进行游戏操作,如选择技能、查看物品、与NPC交互等。因此,UI系统需要具有良好的交互性和易用性。
AI系统:
- NPC行为控制:如巡逻、追逐、战斗策略等。这些行为可以使NPC在游戏中表现得更加真实和有趣。
- 敌人智能:敌人也需要具有一定的智能,能够根据玩家的位置和行动进行反应和攻击。
声音与特效系统:
- 音效管理:包括背景音乐、战斗音效和角色配音等。这些音效可以增强游戏的氛围和代入感。
- 特效管理:包括技能特效、战斗特效和场景特效等。这些特效可以使游戏画面更加华丽和震撼。
游戏逻辑与物理模拟系统:
- 游戏逻辑:根据玩家的输入和当前游戏状态更新游戏逻辑,如角色的移动、攻击和死亡等。
- 物理模拟:处理角色移动、碰撞等物理行为,使游戏世界表现得更加真实和可信。
综上所述,一款完整的ARPG游戏需要包含多个复杂的系统,这些系统相互协作,共同为玩家提供丰富的游戏体验。
对于一款完整的ARPG游戏,除了之前提到的角色系统、战斗系统、地图与探索系统、任务与剧情系统、物品与装备系统、用户界面(UI)系统、AI系统、声音与特效系统以及游戏逻辑与物理模拟系统之外,还有一些系统也是必不可少的:
网络与系统交互系统:
- 网络同步:对于多人在线的ARPG游戏,网络同步是至关重要的。游戏需要确保所有玩家的操作和状态能够实时同步,以避免出现卡顿或不同步的情况。
- 服务器与客户端交互:游戏需要与服务器进行交互,以处理玩家的登录、存档、交易等操作。这需要游戏具备稳定的服务器和高效的客户端-服务器通信协议。
存档与进度管理系统:
- 存档功能:玩家需要能够随时保存游戏进度,以便在之后继续游戏。这要求游戏具备可靠的存档机制,能够确保存档数据的完整性和安全性。
- 进度管理:游戏还需要提供进度管理功能,如查看历史记录、选择存档等,以便玩家能够方便地管理自己的游戏进度。
成就与奖励系统:
- 成就系统:为了激励玩家完成更多的游戏内容,游戏通常会设置成就系统。玩家可以通过完成特定的任务或挑战来获得成就,并展示给其他玩家。
- 奖励系统:游戏还需要设置奖励系统,如经验值、金币、装备等,以奖励玩家的努力和成就。这些奖励可以激励玩家继续游戏,并提升游戏的可玩性。
社交与互动系统:
- 好友系统:玩家可以添加其他玩家为好友,并与其进行聊天、组队等互动。这有助于增强游戏的社交性和互动性。
- 公会系统:一些ARPG游戏还包含公会系统,玩家可以加入或创建公会,与其他公会成员一起完成任务、挑战副本等。这有助于提升游戏的团队合作和社交体验。
反作弊与安全系统:
- 反作弊机制:为了防止玩家使用外挂或作弊软件,游戏需要设置反作弊机制。这可以通过检测异常行为、封停作弊账号等方式来实现。
- 安全保护:游戏还需要确保玩家的账号和信息安全。这包括加密存储敏感信息、提供安全的登录方式等。
综上所述,一款完整的ARPG游戏需要包含多个复杂的系统,这些系统相互协作,共同为玩家提供丰富的游戏体验。同时,游戏开发者还需要不断关注玩家的反馈和需求,持续优化和改进游戏系统,以提升游戏的竞争力和吸引力。
个人背包系统思路
- 存储ID:通过ID(数据资产数组索引获取对应物品结构生成对象或者显示),背包数组存ID
标签
1 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "GameplayTags" }); |
声明
1 | // Copyright Epic Games, Inc. All Rights Reserved. |
要读取 FGameplayTag 标签,你可以使用 Unreal Engine 提供的 API 函数。以下是几种常见的方法来读取和检查这些标签:
通过
HasTag方法检查某个对象是否具有特定标签:1
2
3
4if (SomeActor->Tags.HasTag(LyraGameplayTags::Ability_ActivateFail_IsDead))
{
// 处理逻辑
}通过
GetAllTags获取所有标签并遍历:1
2
3
4
5
6
7
8
9
10FGameplayTagContainer AllTags;
SomeActor->GetAllTags(AllTags);
for (const FGameplayTag& Tag : AllTags)
{
if (Tag == LyraGameplayTags::Ability_ActivateFail_IsDead)
{
// 处理逻辑
}
}使用
MatchExact或MatchAny来匹配标签:1
2
3
4
5
6
7
8
9
10
11
12FGameplayTagQuery 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))
{
// 处理逻辑
}直接比较标签:
1
2
3
4
5FGameplayTag TagToCheck = ...; // 从某处获取标签
if (TagToCheck == LyraGameplayTags::Ability_ActivateFail_IsDead)
{
// 处理逻辑
}使用
FGameplayTagContainer管理多个标签:1
2
3
4
5
6
7
8FGameplayTagContainer TagsToCheck;
TagsToCheck.AddTag(LyraGameplayTags::Ability_ActivateFail_IsDead);
TagsToCheck.AddTag(LyraGameplayTags::Ability_ActivateFail_Cooldown);
if (SomeActor->Tags.HasAny(TagsToCheck))
{
// 处理逻辑
}
注意事项:
- 确保你已经包含了必要的头文件,例如
#include "GameplayTagsManager.h"。 - 确保你使用的对象(如
SomeActor)确实有Tags属性或类似的方法来访问标签。
根据你的具体需求选择合适的方法来读取和处理这些标签。
在处理大量标签时,使用大量的 if 语句确实可能会带来一些问题,尤其是在代码的可维护性和性能方面。以下是一些潜在的问题以及相应的优化建议:
潜在问题:
代码冗长:
- 大量的
if语句会使代码变得冗长且难以阅读。 - 例如:
1
2
3
4
5
6
7
8
9if (Weapon->Tags.HasTag(FName("Item.Weapon.Weapon1")))
{
// 处理 Weapon1
}
else if (Weapon->Tags.HasTag(FName("Item.Weapon.Weapon2")))
{
// 处理 Weapon2
}
// 以此类推...
- 大量的
可维护性差:
- 每次添加或修改武器时,都需要更新
if语句,增加了维护成本。 - 如果标签数量非常多,维护这些
if语句会变得非常繁琐。
- 每次添加或修改武器时,都需要更新
性能影响:
- 虽然单个
if语句的性能影响很小,但如果if语句的数量非常多,可能会对性能产生一定的影响。 - 尤其是在每帧或每秒频繁执行这些检查时,性能开销会更加明显。
- 虽然单个
错误风险:
- 大量的
if语句增加了出错的可能性,例如拼写错误或遗漏某些条件。
- 大量的
优化建议:
使用映射(Map):
- 使用
TMap或TMultiMap来映射标签到处理函数。 - 例如:
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
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
44class 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);
}
}
}
使用反射机制:
- 使用反射机制来动态调用处理函数。
- 例如,使用 Unreal Engine 的
UFunction和UObject系统:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22void 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
}
使用事件系统:
- 使用 Unreal Engine 的事件系统来处理不同类型的武器。
- 例如,使用
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam和DECLARE_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
22DECLARE_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 |
|
使用策略模式:
1 | class IWeaponStrategy |
总结:
使用大量的 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
10void 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
11Super::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.
//使用物触发的委托,用于传给控制器
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.
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
4UPROPERTY(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.
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.
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.
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.
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创建和管理
背包
通过遍历组件通过索引获取纹理数组显示
- 增:
- 向背包添加物品,如果相同可叠加且存在同类物品则叠加
- 向背包添加物品,如果不可叠加则创建新的对象并寻找空的第一个空位放下
- 向背包添加物品,如果物品大于最大叠加数量则创建新的对象,并寻找空的位置放下
- 删:
- 删除指定的物品
- 改:
- 按索引修改指定位置物品
- 交换物品
- 查:
- 遍历所有物品并显示到UI
三渲二修改渲染管线
EMaterialShadingModel














