UE C++基础

UE C++基础

虚幻C++各个头文件的含义

#pragma once
// 预处理程序指令 作用:保护同一个文件不会多次包含,

#include "CoreMinimal.h"
// 包含了一套来自UE4的核心变成环境的普遍存在类型
#include "GameFramework/GameModeBase.h"
// 包含了默认GameModeBase的头文件
#include "UEStudyGameModeBase.generated.h"
// 存储反射信息数据

虚幻 C++各个宏的作用

GENERATED_BODY()
	//表示我们不使用父类的构造函数,如果我们要在我们自定义的类中做一些初始化操作,需要我们自己在.h头文件中
	// 声明构造函数,然后在.cpp文件中实现该构造函数,它之后的成员的是private
GENERATED_UCLASS_BODY()//表示我们使用父类的构造,如果我们在自定义类中做一些初始化操作,可以直接在.cpp文件
	//	中实现构造函数,而不需要在.h文件中去声明,这个宏会自动生成带有特定参数的构造函数,它之后的成员是public
UCLASS()//告知虚幻引擎生成类的反射数据,类必须派生自UObject
UPROPERTY()//叫做属性声明宏,虚幻C++在标准C++基础之上实现了一套反射系统(Reflection System)
	//	反射系统负责垃圾回收、引用更新、编辑器集成等一系列高级且有用的功能,而UPROPERTY的作用就是
	// 声明该属性在反射系统的行为
UFUNCTION()//函数声明宏,反射系统可识别的C++函数
USTRUCT()//结构体声明宏,反射系统可识别的C++结构体
UENUM()//枚举声明宏,反射系统可识别的C++枚举

虚幻C++游戏架构,创建C++默认类

世界场景设置

UEC++1

虚幻游戏架构组成

  • 游戏模式(GameMode):即游戏的技术规则,可包含出现的玩家和观众数量,以及允许的玩家和观众的最大数量,以及游戏是否暂停以及如何处理游戏暂停,玩家进入关卡的方式,关卡之间的过渡等
  • 默认pawn类(Default Pawn Class):默认玩家角色类,可以是带有运动属性的character,也可以是不带有运动属性的pawn,相当于人体躯干,可以被controller控制,这个controller可以是玩家,也可以是aiController
  • HUD类(HUD Class):用户界面类,绘制到屏幕上的UI,用来进行一些UI交互界面的展示
  • 玩家控制器类(Player Controller Class):控制器是一个非物理的actor,可以拥有一个pawn或者是pawn的派生类,比如说charactor来控制其运动,可以是aiController,也可以是playerController,可以控制角色的移动,相当于给角色赋予了灵魂
  • 游戏状态类(Game State Class):主要是追踪记录游戏层面的属性,比如说已经链接玩家的链表,团队的得分,开放世界中完成的任务等等
  • 玩家状态类(Player State Class):追踪游戏玩家的状态属性,比如当前玩家的姓名、得分、在线状态等等
  • 旁观者类(Spectator Class):第三方视角

创建C++默认类:

  1. 在UE中添加以上各个类
    UEC++2
    UEC++3
    UEC++4
    在VS中选择全部重新加载,会发现VS中多了private和public文件夹以及我们创建的类对应的.h和.cpp文件

  2. 在MyGameMode.h文件中添加加载我们创建的头文件,并创建一个默认构造函数
    UEC++5

  3. 在MyGameMode.cpp文件中实现默认构造函数,并创建对应的C++默认类

UEC++6

  1. 关闭UE,生成并运行对应项目
    UEC++7

  2. 在UE的世界场景设置中,将GameMode设置为我们创建的GameMode,即可完成游戏模式默认类创建
    UEC++8

虚幻C++重写Beginplay、Tick、Endplay函数

  1. 在GameMode.h添加对应的构造函数,因为是重写的函数,所以需要关键字override,且Tick和EndPlay函数具有自带的参数
    UEC++9

  2. 在GameMode.cpp中实现对应的构造函数,因为是继承自父类的函数,所以需要通过super关键字添加父类的对应函数
    UEC++10

虚幻C++UE_LOG和AddOnScreenDebugMessage

输出日志UE_LOG

UEC++11
UE中应用此GameMode后运行的输出结果,在输出日志中
UEC++12

打印在屏幕上AddOnScreenDebugMessage

UEC++13
UE中应用此GameMode后运行的输出结果,在屏幕中
UEC++14
此打印相当于蓝图中的print string:
UEC++15

虚幻C++的基础变量类型

	//布尔类型的变量声明
	bool varBool;
	//整形32位的变量声明
	int32 varInt32;
	//整形64位的变量声明
	int64 varInt64;
	// 字节类型的变量声明
	BYTE varByte;
	// 字符串类型的变量声明
	FString varString;
	// 名称类型的变量声明
	FName varName;
	// 文本类型的变量声明
	FText varText;
	// 向量类型的变量声明
	FVector varVector;// 包含x轴、y轴、z轴的左边
	//旋转向量类型的变量声明
	FRotator varRotator;// 包含X轴的旋转Roll,y轴的旋转Pit,Z轴的旋转Yaw
	// FTransform类型的变量声明
	FTransform varTransform;// 包含FVector和FRotator以及缩放Scale三者的集合

虚幻C++FString、FName、FText三者的区别和相互转化

FString:提供了大量的字符串操作接口,三者之中唯一可以修改的字符串类型,消耗更高,性能更低
FName:更着重于表示名称,不区分大小写,不可以更改。引擎中的资源名称都是此类型。通过一个轻型的、系统重复使用的字符串,在创建时会根据内容创建一个哈希值,并且同样的内容指挥存储一次,通过哈希值进行FName的查找和访问的时候,速度比较快,也不需要比较字符串的内容,直接比较哈希值来区分不同FName的字符串
FText:着重于显示和本地化,显示可理解为玩家能直接看到的信息,本地化就是多种语言的处理,不可以更改,相较于另外,FText会更加臃肿,但是提供了非常优秀的本地化功能

	//创建一个FString
	FString myString = TEXT("I am String");
	// FString转化为FName
	FName myName = FName(*myString);
	// FString转化为FText
	FText myText = FText::FromString(myString);

	// FName转化为FString
	myString = myName.ToString();
	// FName转化为FText
	FText text1 = FText::FromName(myName);

	// FText转化为FString
	FString strFromText = text1.ToString();
	// FText不能直接转化为FName,需要通过FString间接转化

虚幻C++容器

TArray

TArray:是虚幻C++中的动态数组TArray,索引值从0开始
特点:速度快,内存消耗小,安全性高,并且TArray所有元素均完全为相同类型,不能进行不同元素类型的混合

  • 声明TArray变量以及打印函数
    //声明一个整型32为的TArray
	TArray<int32>myArray;
	void PrintArray();
  • 打印函数的实现:
void AMyGameMode::PrintArray()
{
	// 用迭代器遍历、打印数组
	for (auto It = myArray.CreateConstIterator(); It; It++)
	{
		UE_LOG(LogTemp, Warning, TEXT("%d"), *It);
		GEngine->AddOnScreenDebugMessage(-1, 5.0, FColor::Blue, FString::Printf(TEXT("%d"), *It));
	}
}
  • 对TArray进行操作:增及其输出
    //数组TArray中的操作
	//增,将元素添加到数组当中
	myArray.Add(10);
	myArray.Add(20);
	myArray.AddUnique(20);//如果数组中没有相同的元素,则将此数加入数组之中
	myArray.Add(30);
	myArray.AddUnique(40);
	
	PrintArray();

UEC++16

  • 对TArray进行操作:删改查
    // 删,将元素从数组中删除
	myArray.Remove(20);// 移除数组中所有等值的元素
	myArray.RemoveAt(0);// 移除数组中索引值为0的元素
	myArray.RemoveSingle(10);// 移除首个匹配到的元素,即移除碰到的第一个10
	myArray.Empty();// 清空数组,数组内容数number为0
	myArray.Reset();// 重置数组,将数组中的所有元素变为0,数量number不变
	// 改
	myArray.Insert(60, 0);// 在索引值为0的位置插入值60
	int32& temp = myArray[0];
	temp = 50;// 修改索引值为0的位置的数值为50

	// 查
	myArray.Contains(10);// 查找数组中是否包含某个元素,返回值为bool类型
	myArray.Find(10);// 正向查找我们的第一个匹配的元素,找到则返回下标,否则返回-1
	myArray.FindLast(10);// 反向查找我们匹配的第一个元素,找到则返回下标,否则返回-1

TMap

TMap:一种键值对容器,里面的数据都是成对出现的(key,value),value通过key值来获取,且key值不能重复,key值唯一

  • 声明TMap变量及打印函数:
    // 创建一个int32位的TMap类型变量
	TMap<int32, int32>myMap;
	void PrintMap();
  • 打印函数的实现:
void AMyGameMode::PrintMap()
{
	for (auto& TestMap : myMap)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.0, FColor::Blue, FString::Printf(TEXT("Key:%d Value:%d"), TestMap.Key, TestMap.Value));
		UE_LOG(LogTemp, Warning, TEXT("Key:%d Value:%d"), TestMap.Key, TestMap.Value);
	}
}
  • TMap的相关操作:
// 增
	myMap.Emplace(0, 1);// 用法同Array的add,往容器中添加数值
	myMap.Emplace(1, 2);
	myMap.Emplace(2, 3);

	PrintMap();
	// 删
	myMap.Remove(0);// 移除key值为0的元素
	myMap.Empty();// 清空Map中的数据
	// 查
	myMap.Contains(1);// 查找匹配的key值是否存在,找到返回真,否则为假,进行两次查找
	int32* isFind = myMap.Find(6);// 查找匹配的key值是否存在,找到为真,否则为假,只进行一次查找,返回的是指针
	const int32* isFind2 = myMap.FindKey(2);//反向查找,通过查找value的值来查找它对应的key,返回类型为指针
	// 分别获取所有的keys和values
	TArray<int32> testKeys;
	TArray<int32> testValues;
	myMap.GenerateKeyArray(testKeys);
	myMap.GenerateValueArray(testValues);

TSet

TSet简介

  1. 是一种快速容器,(通常)用于在排序不重要的情况下存储唯一元素
  2. TSet类似于TMap和TMultiMap,但有一个重要区别,TSet是通过对元素求值的可覆盖函数,使用数据本身作为键,而不是将数据值与独立的键相关联
  3. TSet可以非常快速地添加、查找和删除元素(恒定时间),-TSet也是值类型,支持常规复制、赋值和析构函数的操作,以及其元素较强的所有权
  • 声明TSet变量及打印函数:
    // 声明一个TSet类型的变量
	TSet<FString>mySet;
	void PrintSet();
  • 打印函数的实现:
void AMyGameMode::PrintSet()
{
	for (auto& testSet : mySet)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.0, FColor::Blue, FString::Printf(TEXT("%s"), *testSet));
		UE_LOG(LogTemp, Warning, TEXT("%s"), *testSet);
	}
}
  • TMap的相关操作:
// TSet相关操作
	// 增
	mySet.Add(TEXT("Banana"));
	mySet.Add(TEXT("Apple"));
	mySet.Add(TEXT("Pineapple"));
	mySet.Emplace(TEXT("orange"));//add和emplace都是添加元素到容器中,但是emplace可以避免在插入集合时创建的临时文件

	PrintSet();
	// 合并元素
	TSet<FString>mySet2;
	mySet2.Add(TEXT("zhangsan"));
	mySet2.Add(TEXT("lisi"));
	mySet2.Add(TEXT("wangwu"));
	mySet.Append(mySet2);//将mySet2的值合并到mySet中
	PrintSet();
	// 移除元素
	mySet.Remove(TEXT("Banana"));// 移除匹配的值,会返回已删除元素的数量,如果给定的键未包含在集合中,则返回0
	mySet.Empty();// 清空容器,释放内存
	mySet.Reset();// 清空集合元素,保留内存
	// 查找元素
	int32 Count = mySet.Num();// 查询集合中保存的元素数量,返回值为整型
	bool isFind = mySet.Contains(TEXT("Banana"));// 查询是否包含特点元素,有则返回真,否则为假
	FString* isFind2 = mySet.Find(TEXT("Banana"));// 查找是否包含特定元素,返回指向元素数组的指针,如果映射不包含该键,则返回为Null
	// array函数
	TArray<FString> fruitArray = mySet.Array();//函数会返回一个TArray,其中填充了TSet中每个元素的副本
	// 排序
	TSet<FString>testSet = { TEXT("a"),TEXT("aa"),TEXT("aaa"),TEXT("aaaa") };
	testSet.Sort([](FString A, FString B) {return A.Len() > B.Len(); });// 按长度从长到短排列
	// 赋值运算符  =
	TSet<FString>newSet;
	newSet = mySet;// 把mySet的值赋值给newSet
	newSet.Add(TEXT("OneOne"));
	// []运算符
	FSetElementId index = newSet.Add(TEXT("TwoTwo"));// 根据FSetElementId访问集合对应元素的引用
	testSet[index] = TEXT("One");
	// Reserve  预先分配内存
	TSet<FString>newSet1;
	newSet1.Reserve(10);// 预先分配内存,若输入的Number大于元素个数,则会产生闲置内存(Slack)
	// Shrink 从容器末端移除所有的Slack(闲置内存)
	for (int32 i = 0; i < 10; i++) 
	{
		newSet1.Add(FString::Printf(TEXT("newSet%d"), i));// 添加元素
	}
	for (int32 i = 0; i < 10; i += 2) 
	{
		newSet1.Remove(FSetElementId::FromInteger(i));// 删除当前索引值下面的元素   删除元素产生限制内存
	}
	newSet1.Shrink();// 删除末端的空元素
	// Compact  将容器中的所有空白元素集合到一起放到末尾,方便一起删除
	newSet1.Compact();// 注意,Compact可能会改变元素之间的顺序,如果不想改变的话,可以使用CompactStable
	newSet1.Shrink();

虚幻C++基础数据类型输出打印

    // 基础数据类型的输出打印
	int32 myInt = 10;
	float myFloat = 5.0f;
	bool myBool = true;
	char myChar = 'a';
	FString myString1 = TEXT("myString1");
	FVector myVector = FVector(0, 0, 0);
	UE_LOG(LogTemp, Warning, TEXT("%d"), myInt);
	UE_LOG(LogTemp, Warning, TEXT("%f"), myFloat);
	UE_LOG(LogTemp, Warning, TEXT("%d"), myBool);
	UE_LOG(LogTemp, Warning, TEXT("%c"), myChar);
	UE_LOG(LogTemp, Warning, TEXT("%s"), *myString1);
	UE_LOG(LogTemp, Warning, TEXT("%s"), *myVector.ToString());

虚幻C++的宏及其属性说明符和元数据说明符

UPROPERTY宏,属性说明符和元数据说明符

UPROPERTY:通过反射把属性暴露在蓝图或者实例的细节面板中,从而实现C++和蓝图之间的通信或交互。虚幻会生成PROPERTY的反射数据,利用这些反射数据,我们在蓝图中可以找到这些变量或者方法

定义宏:(定义在MyPawn.h中)

    // 仅在类默认设置中可见
	UPROPERTY(VisibleDefaultsOnly)
		int32 visibleDefaultsOnlyInt;
	// 仅在实例化细节面板可见
	UPROPERTY(VisibleInstanceOnly)
		FString visibleInstanceOnlyString;
	// 类默认设置和实例化细节面板中都可以见到
	UPROPERTY(VisibleAnywhere)
		FVector visibleAnywhereVector;
	// 仅在类默认设置中可以编辑
	UPROPERTY(EditDefaultsOnly)
		int32 editDefaultsOnlyInt;
	// 仅在实例化细节面板可以编辑
	UPROPERTY(EditInstanceOnly)
		FString editInstanceOnlyString;
	// 类默认设置和实例化细节面板中都可以编辑
	UPROPERTY(EditAnywhere)
		FVector editAnywhereVector;
	// 仅在蓝图中可读
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
		int32 blueprintReadOnlyInt;
	// 在蓝图中可读可写即可以获取和设置变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		int32 blueprintReadWriteInt;
	// category目录
	UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "myIntValue")
		int32 value1;
	// category子目录
	UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "myIntValue|mySubIntValue")
		int32 value2;
	// meta元数据说明符
	// DisPlayName别名
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisPlayName = "myValue3DisPlayName"))
		int32 myValue3;
	// EditCondition 条件控制编辑   用一个变量控制另一个变量受否可以被编辑
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisPlayName = "Controller"))
		bool isController;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "isController"))
		float value3;
	//Tooltip  解释说明我们的变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (Tooltip = "isControllerTrue"))
		bool isTrue;
	// 蓝图生成时暴露
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myExposeOnSpawn", meta = (ExposeOnSpawn = "ExposeOnSpawnValue"))
		float myHealth;

定义完成后编译运行,在UE中创建基于MyPawn的蓝图类
UEC++17
命名为BP_MyPawn,位置存放在content中
UEC++18
蓝图的类默认设置面板:可以看到visibleDefaultsOnlyInt和visibleAnywhereVector,可以编辑EditDefaultsOnlyInt和editAnywhereVector
UEC++19
将类拖拽到场景中,即将类实例化,实例化细节面板:可以看到visibleInstanceOnlyString和visibleAnywhereVector,可以编辑editInstanceOnlyString和editAnywhereVector
UEC++20
在蓝图中搜索blueprintRead:其中:blueprintReadOnlyInt只能get,而blueprintReadWriteInt既可以get也可以set
UEC++21
在蓝图中搜索value:myIntValue目录下有一个mySubIntValue目录
UEC++22
在蓝图中搜索myValue3,发现没有,它被别名myValue3DisPlayName所替代了
UEC++23
蓝图的类默认值面板中,只有勾选Controller,才可以编辑Value3
UEC++24
UEC++25
蓝图中将鼠标放置在isTrue上即可查看对应的解释
UEC++26
打开关卡蓝图,搜索SpawnActor from class,class选择BP_MyPawn,即可看到效果
UEC++27
打开BP_MyPawn蓝图,新建变量,勾选生成时公开,即可使用蓝图达到同样的效果
UEC++28

UFUNCTION宏,属性说明符和元数据说明符

UFUNCTION宏:生成函数的一些反射数据,能够将一些函数暴露给蓝图,实现函数和蓝图之间的通信

定义宏:(定义在MyPawn.h中)

// 暴露PrintF1函数在蓝图中,能够在蓝图中进行调用
	UFUNCTION(BlueprintCallable, Category = "myFunction")
		void PrintF1();
	// 纯虚函数的定义BlueprintPure
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "myFunction")
		bool PrintF2();
	// BlueprintImplementableEvent在C++中声明,不能定义,蓝图可重载
	// 无返回值的是事件,有返回值的是函数
	UFUNCTION(BlueprintImplementableEvent)
		void Test1();
	UFUNCTION(BlueprintImplementableEvent)
		int Test2();
	UFUNCTION(BlueprintImplementableEvent)
		void Test11(const FString &myString);
	UFUNCTION(BlueprintImplementableEvent)
		int Test22(const FString &myString);
	// BlueprintNativeEvent在C++中声明和实现,蓝图可选择性重载
	UFUNCTION(BlueprintNativeEvent)
		void TestA();
	UFUNCTION(BlueprintNativeEvent)
		int TestB();
	UFUNCTION(BlueprintNativeEvent)
		void TestC(const FString &myString);
	UFUNCTION(BlueprintNativeEvent)
		int TestD(const FString& myString);
	// meta 元数据说明符
	UFUNCTION(BlueprintCallable,Category="myFunction",meta=(DisPlayName="myPrintTest"))
		void PrintTest();

纯虚函数:也可以叫 抽象函数 ,一般来说它只有函数名、参数和返回值类型 ,不需要函数体 。 这意味着它没有函数的实现,需要让派生类去实现。 C++中的纯虚函数,一般在函数签名后使用=0作为此类函数的标志。

在MyPawn.cpp文件中实现两个printf函数的定义后,运行程序,在蓝图中搜索PrintF,发现显示的两个函数的图标颜色不同,绿色为纯虚函数
UEC++29
在蓝图中重载4个函数,蓝图中显示为:无返回值的是事件,有返回值的是函数
UEC++30
在MyPawn.cpp文件中实现四个TestABCD函数的定义(注意函数名称后要加上_Implementation,否则编译会报错)后,在蓝图中重载,蓝图中显示为:无返回值的是事件,有返回值的是函数
UEC++31
函数的使用:MyPawn.cpp文件

void AMyPawn::BeginPlay()
{
	Super::BeginPlay();
	TestA();
	
}

...

void AMyPawn::TestA_Implementation()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("TestA"));
}

编译运行后,在蓝图中重载TestA函数,并添加输出为TestAHello
UEC++32
将蓝图拖拽到场景中,运行,发现TestAHello和TestA都会打印在屏幕中,即在C++实现的(本地实现)和在蓝图中重载的函数都会运行
UEC++33
在MyPawn.cpp中实现PrintTest函数后,运行项目,在蓝图中输入PrintTest,会发现没有,被myPrintTest替代了
UEC++34

UENUM(枚举)宏

UENUM(枚举)宏:生成枚举的反射数据,通过反射,将枚举暴露给蓝图,进行C++和蓝图之间的通信

定义声明方法一:(MyPawn.h文件中)

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"

// BlueprintType:将枚举类型可以用于蓝图中的变量  蓝图中可以选择生成的变量类型为MyNumType
UENUM(BlueprintType)
namespace MyNumType
{
	enum MyCustomEnum
	{
		Type1,
		Type2,
		Type3,
	};
}

UCLASS()
class UESTUDY_API AMyPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyPawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// 声明一个枚举变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyEnum")
		TEnumAsByte<MyNumType::MyCustomEnum> MyCustomEnum;
};

编译运行后,在UE蓝图的细节面板中可以看到
UEC++35
定义声明方法二:(MyPawn.h文件中)

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"

// 枚举声明方法二
UENUM(BlueprintType)
enum class EMyTestEnum :uint8
{
	OneType UMETA(DisPlayName="OneType"),
	TwoType UMETA(DisPlayName = "TwoType"),
	ThreeType UMETA(DisPlayName = "ThreeType")
};

UCLASS()
class UESTUDY_API AMyPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyPawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// 声明一个枚举变量
	// 方法二对应
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyEnum")
		EMyTestEnum MyCustonEnum2;
};

UEC++36

USTRUCT结构体宏

## USTRUCT结构体宏:生成结构体的反射数据,通过反射,将结构体里的变量或者方法暴露给蓝图

定义声明:(MyPawn.h文件中):

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"

// BlueprintType让结构体可以在蓝图中创建变量,并且进入到变量中,可以将变量设置为结构体
// BlueprintType让蓝图中可以选择生成的变量类型为FMyTestStruct
USTRUCT(BlueprintType)
struct FMyTestStruct
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myTestStruct")
		int32 Healthl;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myTestStruct")
		FString MyName;
};

UCLASS()
class UESTUDY_API AMyPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyPawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	// 声明一个结构体变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myCustomStruct")
		FMyTestStruct MyCustomStruct;

};

编译运行后,在蓝图中可以看到
UEC++37
将结构体用于table中:

  1. 添加C++类Object
    UEC++38
  2. 在VS中选择全部重新加载,并在MyObject.h中声明结构体
#pragma once

#include "CoreMinimal.h"
// 数据表格类对应的头文件
#include "Engine/Classes/Engine/DataTable.h"
#include "UObject/NoExportTypes.h"
#include "MyObject.generated.h"


USTRUCT(BlueprintType)
// :public FTableRowBase:让结构体可以使用数据表格,所以需要继承此类
struct FMyDateTableStruct: public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyTestDataTableStruct")
		float Health;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyTestDataTableStruct")
		FString Name;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyTestDataTableStruct")
		int32 Level;
};

UCLASS()
class UESTUDY_API UMyObject : public UObject
{
	GENERATED_BODY()
};

编译成功后创建数据表格
UEC++39

将数据表格另存为csv格式,并导入ue项目:,选择MyDateTableStruct
UEC++40
UEC++41
UEC++42

虚幻C++Ubject的实例化

  1. 前面步骤中已经新建了MyIObject,所以直接在MyPawn.h中添加头文件和声明
#pragma once

#include "CoreMinimal.h"
#include "MyObject.h"
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"

UCLASS()
class UESTUDY_API AMyPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyPawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	// 声明Object变量
	UPROPERTY()
		UMyObject* myTestObject;
};
  1. 在MyPawn.cpp中实现
#include "MyPawn.h"

// Sets default values
AMyPawn::AMyPawn()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AMyPawn::BeginPlay()
{
	Super::BeginPlay();
	//TestA();

	// Object实例化的实现
	TSubclassOf<UMyObject> MySubClassObject = UMyObject::StaticClass();
	myTestObject = NewObject<UMyObject>(GetWorld(), MySubClassObject);
	if (myTestObject)
	{
		UE_LOG(LogTemp, Warning, TEXT("myTestObject is %s"), *myTestObject->GetName());
	}
}
  1. 编译运行后,打开UE,运行,有输出object的名称,即Object被实例化成功
    UEC++43

获得Object的参数

  1. 为Object添加一个参数,并添加一个构造函数用于初始化:(MyObject.h中)
USTRUCT(BlueprintType)
// :public FTableRowBase:让结构体可以使用数据表格,所以需要继承此类
struct FMyDataTableStruct: public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

	// 构造函数
	FMyDataTableStruct();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyTestDataTableStruct")
		float Health;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyTestDataTableStruct")
		FString Name;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyTestDataTableStruct")
};

UCLASS()
class UESTUDY_API UMyObject : public UObject
{
	GENERATED_BODY()

public:
	// 声明一个结构体变量
	UPROPERTY()
		FMyDataTableStruct myDataTableStruct;
};

MyObject.cpp,初始化:

#include "MyObject.h"

FMyDataTableStruct::FMyDataTableStruct()
{
	// 初始化
	Health = 10;
	Name = TEXT("zhangsan");
	Level = 1;
}
  1. 在MyPawn.cpp中添加打印Object的参数:
void AMyPawn::BeginPlay()
{
	Super::BeginPlay();
	//TestA();

	// Object实例化的实现
	TSubclassOf<UMyObject> MySubClassObject = UMyObject::StaticClass();
	myTestObject = NewObject<UMyObject>(GetWorld(), MySubClassObject);
	if (myTestObject)
	{
		UE_LOG(LogTemp, Warning, TEXT("myTestObject is %s"), *myTestObject->GetName());
		UE_LOG(LogTemp, Warning, TEXT("myHeal is %f"), myTestObject->myDataTableStruct.Health);
		UE_LOG(LogTemp, Warning, TEXT("myName is %s"), *myTestObject->myDataTableStruct.Name);
		UE_LOG(LogTemp, Warning, TEXT("myLevel is %d"), myTestObject->myDataTableStruct.Level);
	}
}
  1. 编译运行,打开UE,选择MyGameMode,运行:
    UEC++44

虚幻C++ UGameInstance的实例化

GameInstance:全局唯一单例,这个在引擎初始化的时候就已经生成,一直存在到引擎关闭

作用

  1. 引擎初始化与关闭时执行的逻辑
  2. 为游戏保存全局数据:比如上一个关卡的信息需要在下一个关卡使用时,我们用GameInstance保存数据但是只是临时数据,游戏结束则小时,如果想要本地持久保存数据需要用SavaGame

在UE中创建GameInstance:
UEC++45
UEC++46
在VS中,在MyGameInstance.h文件中为实例添加参数和构造函数:

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

UCLASS()
class UESTUDY_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()

public:
	// 创建构造函数
	UMyGameInstance();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyInstance")
		FString myAppID;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyInstance")
		FString myUserID;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyInstance")
		FString myName;
};

在MyGameInstance.cpp文件的构造函数实现中,为实例的参数进行初始化:

#include "MyGameInstance.h"

UMyGameInstance::UMyGameInstance()
{
	// 初始化数据
	myAppID = TEXT("123");
	myUserID = TEXT("456");
	myName = TEXT("wangwu");
}

在MyPawn.h中添加MyGameInstance.h头文件,并声明实例:

// 游戏实例的头文件
#include "MyGameInstance.h"

UCLASS()
class UESTUDY_API AMyPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyPawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	// 声明游戏实例
	UPROPERTY()
		UMyGameInstance* myInstance;
};

在MyPawn.cpp中添加MyGameInstance的实例化与输出:

void AMyPawn::BeginPlay()
{
	Super::BeginPlay();
	
	// 单例的使用
	myInstance = Cast<UMyGameInstance>(GetWorld()->GetFirstPlayerController()->GetGameInstance());
	if (myInstance)
	{
		UE_LOG(LogTemp, Warning, TEXT("myInstance is %s"), *myInstance->GetName());
		UE_LOG(LogTemp, Warning, TEXT("myAppID is %s"), *myInstance->myAppID);
		UE_LOG(LogTemp, Warning, TEXT("myUserID is %s"), *myInstance->myUserID);
		UE_LOG(LogTemp, Warning, TEXT("myName is %s"), *myInstance->myName);
	}
}

编译运行,打开UE,在编辑-项目设置-地图和模式中,将游戏实例切换为MyGameInstance:
UEC++47
点击运行,查看输出日志:
UEC++48

虚幻C++创建Actor添加初始化组件

Actor:从程序实现角度说,Actor是构成游戏世界的所有元素以及在此之上,运行游戏的规则以及逻辑的抽象实例

在UE中添加C++类Actor:
UEC++49
在MyActor.h中添加各组件的头文件,并添加参数:

#pragma once

#include "CoreMinimal.h"
// 场景组件头文件
#include "Components/SceneComponent.h"
// StaticMesh组件头文件
#include "Components/StaticMeshComponent.h"
// 碰撞组件头文件
#include "Components/BoxComponent.h"
// 粒子特效组件
#include "Particles/ParticleSystemComponent.h"
// 声音组件
#include "Components/AudioComponent.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class UESTUDY_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// 声明变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class USceneComponent* MyScene;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class UStaticMeshComponent* MyMesh;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class UParticleSystemComponent* MyParticle;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class UBoxCompone
<think>好的,用户想了解Unreal Engine中C++的用法和解决问题的方法。首先,我需要回忆UE C++编程的基础知识,比如类结构、UCLASS宏、UPROPERTY和UFUNCTION的使用。这些是UE特有的语法,对新手来说可能不太熟悉,需要详细说明。 接下来,常见问题方面,用户可能遇到编译错误、蓝图与C++交互的问题、内存管理或崩溃。比如,编译后热重载失败,这时候要检查头文件改动是否影响UHT,或者是否缺少模块依赖。需要给出具体的解决步骤,比如重新生成项目文件,检查Build.cs中的模块。 然后是调试技巧,比如使用UE_LOG输出日志,断点调试,以及处理崩溃时的调用堆栈分析。这里可以提到Visual Studio的调试工具和Unreal Insights的性能分析工具。 性能优化也是重点,包括避免每帧操作、使用Profiler工具定位瓶颈、合理使用对象池和异步加载资源。这部分需要结合具体场景,比如在Actor的Tick函数中减少复杂计算。 资源管理方面,动态加载资源可以使用StaticLoadObject或FStreamableManager,注意路径格式是否正确,以及异步加载时的内存管理。 网络同步问题需要解释角色权限(ROLE_Authority和ROLE_SimulatedProxy),使用属性复制(Replicated)和RPC函数,确保网络逻辑正确。 参考引用部分,需要确保引用的内容相关。用户提供的引用中有文件操作、渲染修复和对象库的Bugfix,可能和资源加载、性能优化或蓝图与C++交互有关。比如引用[1]提到文件大小获取,可以关联到资源加载时的内存管理;引用[3]提到Object Library存储对象类型的修复,可能和C++中正确使用UObject有关。 生成相关问题时要覆盖用户可能进一步想知道的内容,比如UHT的作用、解决崩溃的方法、性能优化工具、资源路径格式、网络同步的实现细节等。 最后,要确保回答符合格式要求,使用正确的LaTeX格式,比如类名和函数名用行内公式符号,独立公式如果有的话用$$,但UE C++可能用不上太多数学公式,所以可能主要用行内符号强调代码元素。同时结构要清晰,分点回答,引用标注正确。</think># Unreal Engine C++ 编程与问题排查指南 ## 一、基础编程规范 1. **类声明规范** UE C++ 类需继承自`UObject`或`AActor`等引擎基类,并使用`UCLASS()`宏声明: ```cpp UCLASS() class MYPROJECT_API AMyActor : public AActor { GENERATED_BODY() }; ``` 2. **属性/方法声明** - 使用`UPROPERTY()`暴露变量到编辑器:`UPROPERTY(EditAnywhere) float Health = 100.0f;` - 使用`UFUNCTION()`声明可调用的方法:`UFUNCTION(BlueprintCallable) void TakeDamage(float Damage);`[^1] 3. **模块依赖管理** 在`Build.cs`文件中添加依赖模块: ```cpp PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" }); ``` ## 二、常见问题排查 ### 1. 编译错误 **症状**:出现`Unresolved external symbol`链接错误 **解决方案**: 1. 检查`.h`和`.cpp`文件是否包含`GENERATED_BODY()`宏 2. 确认`Build.cs`已添加所需模块依赖 3. 执行`Generate Visual Studio Project Files` ### 2. 热重载失效 **症状**:修改代码后热重载无效 **解决方法**: ```cpp // 错误示例(缺少UFUNCTION) void MyFunc() {} // 正确示例 UFUNCTION(BlueprintCallable) void MyFunc(); ``` 需注意:修改头文件后必须完整重新编译 ### 3. 内存泄漏 **检测工具**: - 控制台命令`OBJ LIST CLASS=YOUR_CLASS` - 使用`UE_MEMORY_STAT`宏统计内存 **预防措施**: ```cpp // 使用TWeakObjectPtr避免强引用 TWeakObjectPtr<AActor> SafePointer; ``` ## 三、高级调试技巧 ### 1. 日志输出 ```cpp UE_LOG(LogTemp, Warning, TEXT("Damage taken: %f"), Damage); ``` 通过控制台命令`Log LogTemp Verbose`显示详细日志[^2] ### 2. 崩溃分析 1. 在`ProjectName\Saved\Crashes`中查找崩溃报告 2. 使用`!analyze -v`命令分析Dump文件 3. 检查调用堆栈中的引擎模块符号 ## 四、性能优化 ### 1. 避免每帧操作 ```cpp // 错误示例 void Tick() { ComplexCalculation(); } // 正确方案 FTimerHandle Timer; GetWorldTimerManager().SetTimer(Timer, this, &MyClass::DelayedCalc, 1.0f, true); ``` ### 2. 资源加载优化 使用异步加载: ```cpp FStreamableManager& Loader = UAssetManager::GetStreamableManager(); Loader.RequestAsyncLoad(AssetPath, FStreamableDelegate::CreateUObject(this, &MyClass::OnLoadComplete)); ``` 注意资源路径格式:`/Game/Path/To/Asset.AssetName`[^1] ## 五、网络同步 ### 1. 属性复制 ```cpp UPROPERTY(Replicated) float ReplicatedHealth; // 需在GetLifetimeReplicatedProps中声明 void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; ``` ### 2. RPC调用 ```cpp UFUNCTION(Server, Reliable) void ServerTakeDamage(float Damage); ``` §§ 相关问题 §§ 1. 如何正确处理UE C++中的垃圾回收? 2. 怎样实现跨模块的代码调用? 3. UE5中Nanite系统对C++编程有什么影响? 4. 如何进行多线程编程而不引起崩溃? 5. 如何通过插件扩展引擎功能? : 文件操作参考 https://wiki.unrealengine.com/File_Management : 渲染问题修复参考Android Vulkan更新日志 [^3]: Object Library修复说明见版本更新文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值