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 UBoxComponent* MyBox;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class UAudioComponent* MyAudio;
};

在MyActor.cpp文件中的构造函数添加各组件的默认值

#include "MyActor.h"

// Sets default values
// 构造函数
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	// 组件默认值
	MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MyCustomScene"));
	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyCustomStaticMesh"));
	MyParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MyCustomParticle"));
	MyBox = CreateDefaultSubobject<UBoxComponent>(TEXT("MyCustomBox"));
	MyAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("MyCustomAudio"));

	// 设置父子级
	RootComponent = MyScene;// 将MyMesh设置为根组件
	MyMesh->SetupAttachment(MyScene);// 将MyMesh放到根组件下面
	MyParticle->SetupAttachment(MyScene);
	MyBox->SetupAttachment(MyScene);
	MyAudio->SetupAttachment(MyBox);
}

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

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

编译运行,在UE中创建基于MyActor的蓝图类,命名为BP_MyActor:
UEC++50
打开蓝图类,即可查看设置效果:
UEC++51

虚幻C++静态加载类和资源

静态加载:分为静态加载资源和静态加载类两种,在代码编译时加载资源,静态加载必须写在构造函数中

静态加载资源

  1. 在UE中添加初学者内容包:
    UEC++52

  2. 在MyActor.cpp中添加静态加载资源代码:

AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	// 组件默认值
	MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MyCustomScene"));
	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyCustomStaticMesh"));
	MyParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MyCustomParticle"));
	MyBox = CreateDefaultSubobject<UBoxComponent>(TEXT("MyCustomBox"));
	MyAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("MyCustomAudio"));

	// 设置父子级
	RootComponent = MyScene;// 将MyMesh设置为根组件
	MyMesh->SetupAttachment(MyScene);// 将MyMesh放到根组件下面
	MyParticle->SetupAttachment(MyScene);
	MyBox->SetupAttachment(MyScene);
	MyAudio->SetupAttachment(MyBox);

	// 静态加载资源
	static ConstructorHelpers::FObjectFinder<UStaticMesh>TempStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone'"));
	// 将临时静态模型设置为自己的模型   TempStaticMesh.Object获取对象资源的引用
	MyMesh->SetStaticMesh(TempStaticMesh.Object);
	static ConstructorHelpers::FObjectFinder<UParticleSystem>TempParticSystem(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'"));
	MyParticle->SetTemplate(TempParticSystem.Object);
	static ConstructorHelpers::FObjectFinder<USoundWave>TempSound(TEXT("/Script/Engine.SoundWave'/Game/StarterContent/Audio/Collapse01.Collapse01'"));
	MyAudio->SetSound(TempSound.Object);
}
  1. 编译运行,打开BP_MyActor查看效果:
    UEC++53
    UEC++54
    UEC++55
    静态加载类
  2. 在MyActor.h中声明一个MyActor
#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 UBoxComponent* MyBox;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class UAudioComponent* MyAudio;

    // 声明MyActor
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MyClass")
		TSubclassOf<AActor> MyActor;

};
  1. 在MyActor.cpp文件的构造函数中添加静态加载类的代码,并在BeginPlay函数中添加打印,静态加载类时,要在引用最后面加上_C,否则编译会报错
#include "MyActor.h"

// Sets default values
// 构造函数
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	// 组件默认值
	MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MyCustomScene"));
	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyCustomStaticMesh"));
	MyParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MyCustomParticle"));
	MyBox = CreateDefaultSubobject<UBoxComponent>(TEXT("MyCustomBox"));
	MyAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("MyCustomAudio"));

	// 设置父子级
	RootComponent = MyScene;// 将MyMesh设置为根组件
	MyMesh->SetupAttachment(MyScene);// 将MyMesh放到根组件下面
	MyParticle->SetupAttachment(MyScene);
	MyBox->SetupAttachment(MyScene);
	MyAudio->SetupAttachment(MyBox);

	// 静态加载资源
	static ConstructorHelpers::FObjectFinder<UStaticMesh>TempStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone'"));
	// 将临时静态模型设置为自己的模型   TempStaticMesh.Object获取对象资源的引用
	MyMesh->SetStaticMesh(TempStaticMesh.Object);
	static ConstructorHelpers::FObjectFinder<UParticleSystem>TempParticSystem(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'"));
	MyParticle->SetTemplate(TempParticSystem.Object);
	static ConstructorHelpers::FObjectFinder<USoundWave>TempSound(TEXT("/Script/Engine.SoundWave'/Game/StarterContent/Audio/Collapse01.Collapse01'"));
	MyAudio->SetSound(TempSound.Object);

	// 静态加载类
	static ConstructorHelpers::FClassFinder<AActor> TempMyActor(TEXT("/Script/Engine.Blueprint'/Game/StarterContent/Blueprints/Blueprint_CeilingLight.Blueprint_CeilingLight_C'"));
	// 设置自己的类
	MyActor = TempMyActor.Class;
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	// 打印查看Actor是否设置成功
	if (MyActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("MyActor is %s"), *MyActor->GetName());
	}
}

编译运行,在UE中,将MyActor拖拽到场景中,并运行
UEC++56

虚幻C++动态加载类和资源

动态加载:在游戏运行时加载资源

在MyActor.cpp的begin函数中添加动态加载的代码:注意加载类的时候引用后面要加_C

void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	// 打印查看Actor是否设置成功
	if (MyActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("MyActor is %s"), *MyActor->GetName());
	}

	// 动态加载资源
	UStaticMesh* MyTempStaticMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube'"));
	if (MyTempStaticMesh)
	{
		MyMesh->SetStaticMesh(MyTempStaticMesh);
	}
	// 动态加载类   LoadClass<AActor>:加载AActor模板类   this:在哪个类里
	UClass* MyTempClass = LoadClass<AActor>(this, TEXT("/Script/Engine.Blueprint'/Game/StarterContent/Blueprints/Blueprint_WallSconce.Blueprint_WallSconce_C'"));
	if (MyTempClass)
	{
		// SpawnActor出生,SpawnActor<AActor>:产生一个Actor类,FVector:类产生的位置  ZeroVector:(0,0,0)
		// FRotator::ZeroRotator  类出生时的旋转   ZeroRotator:旋转为0    缩放默认值为(1,1,1)
		AActor* SpawnActor = GetWorld()->SpawnActor<AActor>(MyTempClass, FVector::ZeroVector,FRotator::ZeroRotator);
	}
}

运行之后Actor从锥型变为长方体
UEC++57

虚幻C++创建摄像机摇臂和相机,并且设置旋转位移缩放

在MyPawn.h中添加摄像机摇臂和相机的头文件,并声明对应的变量

#pragma once

#include "CoreMinimal.h"
// Object的头文件
#include "MyObject.h"
// 游戏实例的头文件
#include "MyGameInstance.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.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;

// 声明摇臂和相机变量
public:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USceneComponent* MyRoot;
	// 摄像机摇臂组件
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 相机组件
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
};

在MyPawn.cpp文件的构造函数中初始化摄像头摇臂和相机,并设置其父子集关系及碰撞,在BeginPlay函数中设置旋转的位移和缩放

#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;
	// 摄像头摇臂和相机初始化
	MyRoot = CreateDefaultSubobject<USceneComponent>(TEXT("MyRootComponent"));
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));
	// 设置父子级
	RootComponent = MyRoot;
	MySpringArm->SetupAttachment(MyRoot);
	MyCamera->SetupAttachment(MySpringArm);
	// 设置MySpringArm的碰撞
	MySpringArm->bDoCollisionTest = false;// 不作为碰撞体
}

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

	// 设置旋转的位移和缩放
	FVector MyLocation = FVector(0, 0, 0);
	FRotator MyRotation = FRotator(-50, 0, 0);
	FVector MyScale = FVector(1, 1, 1);
	SetActorLocation(MyLocation);
	SetActorRotation(MyRotation);
	SetActorScale3D(MyScale);
}

虚幻C++按键映射绑定以及使用鼠标滑轮控制镜头缩放远近

在UE的编辑—项目设置—输入—绑定—操作映射中加入两个按键映射
UEC++58
MyPlayerController.cpp文件中:

#include "MyPawn.h"
#include "MyPlayerController.h"

void AMyPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();// 继承自父类
	// 重写按键输入绑定函数的实现
	// InputComponent:输入组件,也是输入的变量   BindAction(按键映射名称,按下的方式,对象在哪个类中,绑定的函数):绑定按键映射
	InputComponent->BindAction("WheelUp",IE_Pressed,this,&AMyPlayerController::WheelUpFunction);
	InputComponent->BindAction("WheelDown", IE_Pressed, this, &AMyPlayerController::WheelDownFunction);
}

void AMyPlayerController::WheelUpFunction()
{
	// 如果获得了Pawn
	if (GetPawn())
	{
		// 用getPawn转化为Pawn
		AMyPawn* myCameraPawn = Cast<AMyPawn>(GetPawn());
		if (myCameraPawn)
		{
			myCameraPawn->Zoom(1, 10);
		}
	}
}

void AMyPlayerController::WheelDownFunction()
{
	// 用getPawn转化为Pawn
	AMyPawn* myCameraPawn = Cast<AMyPawn>(GetPawn());
	if (myCameraPawn)
	{
		myCameraPawn->Zoom(0, 10);
	}
}

MyPlayerController.h文件中:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class UESTUDY_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	// 重写按键输入绑定函数
	virtual void SetupInputComponent();
	// 滑轮向上时绑定的函数
	void WheelUpFunction();
	// 滑轮向下时绑定的函数
	void WheelDownFunction();
};

MyPawn.h文件中:声明Zoom函数

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

// 声明摇臂和相机变量
public:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USceneComponent* MyRoot;
	// 摄像机摇臂组件
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 相机组件
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 控制相机摇臂的缩放  鼠标滑轮移动镜头缩放
	void Zoom(bool Direction, float ZoomSpeed);
};

MyPawn.cpp文件中:实现Zoom函数

// 鼠标滑轮移动镜头缩放函数的实现
void AMyPawn::Zoom(bool Direction, float ZoomSpeed)
{
	if (Direction)// 滚轮向上滑动
	{
		if (MySpringArm->TargetArmLength >= 300 && MySpringArm->TargetArmLength <= 5000)
		{
			MySpringArm->TargetArmLength += (ZoomSpeed * 2);
			// 打印摄像机摇臂的长度
			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("SpringArmLength is %f"), MySpringArm->TargetArmLength));
		}
	}
	else 
	{
		if (MySpringArm->TargetArmLength > 300 && MySpringArm->TargetArmLength <= 5000)
		{
			MySpringArm->TargetArmLength -= (ZoomSpeed * 2);
			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("SpringArmLength is %f"), MySpringArm->TargetArmLength));
		}
	}
}

虚幻C++AddActorLocalOffset和AddActorWorldOffset

AddActorLocalOffset:添加Actor的本地坐标
AddActorWorldOffset:添加Actor的世界坐标

在MyActor.cpp中添加对应代码:

void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 偏移量
	FVector MyOffSet = FVector(1, 0, 0);
	// 返回的碰撞参数:如碰撞位置,碰撞的Actor和坐标之类的信息
	FHitResult HitResult;
	
	// 改变本地坐标,X轴每帧增加1
	// 参数:偏移量,是否产生碰撞,返回的碰撞参数
	AddActorLocalOffset(MyOffSet,false,&HitResult);
	// 改变世界坐标,X轴每帧增加1
	// AddActorWorldOffset(MyOffSet, false, &HitResult);
}

分别注释代码,编译运行,在UE中添加MyActor并旋转一定角度后运行,发现改变本地坐标时,物体沿自己的x轴移动,改变世界坐标时,物体沿正上方移动

虚幻C++的代理绑定

BeginOverlap和EndOverlap的代理绑定

在MyActor.cpp文件的BeginPlay中添加代码:

void AMyActor::BeginPlay()
{
	Super::BeginPlay();

	// 利用碰撞box绑定BeginOverlap   参数:对象,绑定的函数
	MyBox->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::BeginOverlapFunction);
	// 利用碰撞box绑定EndOverlap   参数:对象,绑定的函数
	MyBox->OnComponentEndOverlap.AddDynamic(this, &AMyActor::EndOverlapFunction);
}

// 利用碰撞box绑定BeginOverlap  绑定的函数的实现
void AMyActor::BeginOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("BeginOverLap is seccess"));
}

// 利用碰撞box绑定EndOverlap  绑定的函数的实现
void AMyActor::EndOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("EndOverLap is seccess"));
}

在MyActor.h文件中添加代码:

UCLASS()
class UESTUDY_API AMyActor : public AActor
{
	GENERATED_BODY()
public:
	// 利用碰撞box绑定BeginOverlap   绑定的函数的声明   两个物体开始重叠时
	// DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams( FComponentBeginOverlapSignature, UPrimitiveComponent, OnComponentBeginOverlap,
	// UPrimitiveComponent*, OverlappedComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, 
	// int32, OtherBodyIndex, bool, bFromSweep, const FHitResult &, SweepResult);
	UFUNCTION()
		// 复制原函数的后6个参数,并删掉类型和参数名中间的逗号
		void BeginOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	// 利用碰撞box绑定EndOverlap   绑定的函数的声明    两个物体结束重叠时
	//DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams( FComponentEndOverlapSignature, UPrimitiveComponent,OnComponentEndOverlap, 
	// UPrimitiveComponent*, OverlappedComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, int32, OtherBodyIndex);
	UFUNCTION()
		// 复制原函数的4个参数,并删掉类型和参数名中间的逗号
		void EndOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};

编译运行,打开UE,在UE中添加第三人称模板
UEC++59
将GameMode改为None,将第三人称人物拖动到场景中:
UEC++60

修改键盘控制玩家为玩家0
UEC++61
在BP_MyActor的蓝图类中修改box的缩放大小为(5,5,5),点击运行,控制玩家靠近Actor,观察输出:在玩家刚开始与物体重叠时,输出BeginOcerlap is seccess,当玩家与物体结束重叠时,输出EndOverLap is seccess
UEC++62

Hit事件的代理绑定

在MyActor.cpp文件的BeginPlay中添加代码:

void AMyActor::BeginPlay()
{
	Super::BeginPlay();

	// 利用碰撞box绑定Hit  参数:对象,绑定的代理函数
	MyBox->OnComponentHit.AddDynamic(this, &AMyActor::HitFunction);
}

// 利用碰撞box绑定Hit  绑定的函数的实现
void AMyActor::HitFunction(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Hit is seccess"));
}

在MyActor.h文件中添加代码:

UCLASS()
class UESTUDY_API AMyActor : public AActor
{
	GENERATED_BODY()
public:
	// 利用碰撞box绑定Hit  绑定的函数的声明
	// DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams(FComponentHitSignature, UPrimitiveComponent, OnComponentHit, 
	// UPrimitiveComponent*, HitComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, FVector, NormalImpulse, const FHitResult&, Hit);
	UFUNCTION()
		// 复制原函数的5个参数,并删掉类型和参数名中间的逗号
		void HitFunction(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
};

编译运行,打开UE,将GameMode改为None,将第三人称人物拖动到场景中,修改键盘控制玩家为玩家0,打开BP_MyActor蓝图类,修改以下位置的参数:将碰撞预设设置为Custom(自定义),将被Pawn碰到的碰撞改为Block(阻挡),勾选模拟生成命中事件(这样才会产生Hit事件)
UEC++63
把BP_MyActor拖动到场景中,将box大小改为(5,5,5),移动人物靠近Actor观察输出:两个物体表面靠近,即发生Hit事件
UEC++64

虚幻C++碰撞设置

在MyActor.cpp文件的构造函数中设置碰撞:

// 构造函数
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	// 碰撞设置  SetCollisionEnabled:设置碰撞输入
	MyBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);// 此组件没有碰撞效果
	MyBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);// 此组件只有查询碰撞   只有事件检测和重叠
	MyBox->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);// 此组件只有物理碰撞   刚体和约束等碰撞
	MyBox->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);// 此组件既有查询碰撞,也有物理碰撞
	MyBox->SetCollisionEnabled(ECollisionEnabled::ProbeOnly);//此组件只有探测碰撞
	MyBox->SetCollisionEnabled(ECollisionEnabled::QueryAndProbe); // 此组件既有查询碰撞,也有探测碰撞
	// 碰撞设置  SetCollisionObjectType:设置碰撞对象类型
	MyBox->SetCollisionObjectType(ECC_WorldStatic);// 世界的静态对象
	MyBox->SetCollisionObjectType(ECC_WorldDynamic);// 世界的动态对象
	MyBox->SetCollisionObjectType(ECC_Pawn);
	MyBox->SetCollisionObjectType(ECC_PhysicsBody);
	MyBox->SetCollisionObjectType(ECC_Vehicle);// 载具
	MyBox->SetCollisionObjectType(ECC_Destructible);// 可破碎物体
	// 碰撞设置  碰撞响应
	// SetCollisionResponseToAllChannels:对所有通道进行设置
	// SetCollisionResponseToChannel:对单个通道进行设置
	MyBox->SetCollisionResponseToAllChannels(ECR_Block);// 将所有的通道碰撞响应设置为Block(阻挡)
	MyBox->SetCollisionResponseToAllChannels(ECR_Overlap);// 将所有的通道碰撞响应设置为Overlap(重叠)
	MyBox->SetCollisionResponseToAllChannels(ECR_Ignore);// 将所有的通道碰撞响应设置为Ignore(忽略)
	MyBox->SetCollisionResponseToChannel(ECC_Pawn,ECR_Overlap);// 将Pawn的碰撞响应设置为Overlap
	MyBox->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);// 将世界静态的碰撞响应设置为Block
	MyBox->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Ignore);// 将世界动态的碰撞响应设置为Ignore
}

编译运行,打开UE,重新生成BP_Actor蓝图类,查看碰撞设置:collision Enabled(碰撞已启用)设置为了最后设置的QueryAndProbe,Object Type设置成了最后设置的Destructible(可破碎物体)…
UEC++65

虚幻C++粒子特效的激活和失效

在MyActor.cpp中:

#include "MyActor.h"

// Sets default values
// 构造函数
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	// 组件默认值
	MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MyCustomScene"));
	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyCustomStaticMesh"));
	MyParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MyCustomParticle"));
	MyBox = CreateDefaultSubobject<UBoxComponent>(TEXT("MyCustomBox"));
	MyAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("MyCustomAudio"));

	// 设置父子级
	RootComponent = MyScene;// 将MyMesh设置为根组件
	MyMesh->SetupAttachment(MyScene);// 将MyMesh放到根组件下面
	MyParticle->SetupAttachment(MyScene);
	MyBox->SetupAttachment(MyScene);
	MyAudio->SetupAttachment(MyBox);

	// 静态加载资源
	static ConstructorHelpers::FObjectFinder<UStaticMesh>TempStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone'"));
	// 将临时静态模型设置为自己的模型   TempStaticMesh.Object获取对象资源的引用
	MyMesh->SetStaticMesh(TempStaticMesh.Object);
	static ConstructorHelpers::FObjectFinder<UParticleSystem>TempParticSystem(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'"));
	MyParticle->SetTemplate(TempParticSystem.Object);
	static ConstructorHelpers::FObjectFinder<USoundWave>TempSound(TEXT("/Script/Engine.SoundWave'/Game/StarterContent/Audio/Collapse01.Collapse01'"));
	MyAudio->SetSound(TempSound.Object);

	// 静态加载类
	static ConstructorHelpers::FClassFinder<AActor> TempMyActor(TEXT("/Script/Engine.Blueprint'/Game/StarterContent/Blueprints/Blueprint_CeilingLight.Blueprint_CeilingLight_C'"));
	// 设置自己的类
	MyActor = TempMyActor.Class;

	// 碰撞设置  SetCollisionEnabled:设置碰撞输入
	//MyBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);// 此组件没有碰撞效果
	MyBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);// 此组件只有查询碰撞   只有事件检测和重叠
	//MyBox->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);// 此组件只有物理碰撞   刚体和约束等碰撞
	//MyBox->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);// 此组件既有查询碰撞,也有物理碰撞
	//MyBox->SetCollisionEnabled(ECollisionEnabled::ProbeOnly);//此组件只有探测碰撞
	//MyBox->SetCollisionEnabled(ECollisionEnabled::QueryAndProbe); // 此组件既有查询碰撞,也有探测碰撞
	
	// 碰撞设置  SetCollisionObjectType:设置碰撞对象类型
	//MyBox->SetCollisionObjectType(ECC_WorldStatic);// 世界的静态对象
	MyBox->SetCollisionObjectType(ECC_WorldDynamic);// 世界的动态对象
	//MyBox->SetCollisionObjectType(ECC_Pawn);
	//MyBox->SetCollisionObjectType(ECC_PhysicsBody);
	//MyBox->SetCollisionObjectType(ECC_Vehicle);// 载具
	//MyBox->SetCollisionObjectType(ECC_Destructible);// 可破碎物体
	
	// 碰撞设置  碰撞响应
	// SetCollisionResponseToAllChannels:对所有通道进行设置
	// SetCollisionResponseToChannel:对单个通道进行设置
	//MyBox->SetCollisionResponseToAllChannels(ECR_Block);// 将所有的通道碰撞响应设置为Block(阻挡)
	MyBox->SetCollisionResponseToAllChannels(ECR_Overlap);// 将所有的通道碰撞响应设置为Overlap(重叠)
	//MyBox->SetCollisionResponseToAllChannels(ECR_Ignore);// 将所有的通道碰撞响应设置为Ignore(忽略)
	//MyBox->SetCollisionResponseToChannel(ECC_Pawn,ECR_Overlap);// 将Pawn的碰撞响应设置为Overlap
	//MyBox->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);// 将世界静态的碰撞响应设置为Block
	//MyBox->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Ignore);// 将世界动态的碰撞响应设置为Ignore

	// 设置Box大小
	MyBox->SetBoxExtent(FVector(64, 64, 64));
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	// 游戏开始时,粒子系统默认失效
	if(MyParticle)
	{
		MyParticle->Deactivate();
	}

	// 利用碰撞box绑定BeginOverlap   参数:对象,绑定的函数
	MyBox->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::BeginOverlapFunction);
	// 利用碰撞box绑定EndOverlap   参数:对象,绑定的函数
	MyBox->OnComponentEndOverlap.AddDynamic(this, &AMyActor::EndOverlapFunction);
	// 利用碰撞box绑定Hit  参数:对象,绑定的代理函数
	MyBox->OnComponentHit.AddDynamic(this, &AMyActor::HitFunction);
}

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// 利用碰撞box绑定BeginOverlap  绑定的函数的实现
void AMyActor::BeginOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// 碰撞时粒子生效
	MyParticle->Activate();
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("BeginOverLap is seccess"));
}

// 利用碰撞box绑定EndOverlap  绑定的函数的实现
void AMyActor::EndOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	// 离开时粒子失效
	MyParticle->Deactivate();
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("EndOverLap is seccess"));
}

编译运行,在UE中右键MyActor创建蓝图类BP_MyActor,查看设置,Box范围设置结果
UEC++66
碰撞设置结果:
UEC++67
将Actor和第三方人物都拖拽到世界中,将GameMode改为None,修改键盘控制玩家为玩家0,运行项目,控制玩家碰撞Actor,玩家碰撞Actor时即显示一次粒子特效
UEC++68

虚幻C++创建UserWidget并且绑定Button点击事件

  1. 在UE中创建C++类UserWidget:
    UEC++69
  2. 在VS的MyUserWidget.cpp中添加:
#include "MyUserWidget.h"

bool UMyUserWidget::Initialize()
{
	if (!Super::Initialize())// 是否继承了父类组件,继承了返回为真,否则为假
	{
		return false;
	}
	// 创建代理绑定
	// 按钮点击事件  参数:对象,绑定函数,
	ButtonStart->OnClicked.AddDynamic(this, &UMyUserWidget::Start);
	ButtonQuit->OnClicked.AddDynamic(this, &UMyUserWidget::Quit);
	return true;
}

void UMyUserWidget::Start()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Start"));
}

void UMyUserWidget::Quit()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Quit"));
}
  1. 在VS的MyUserWidget.h中添加:
 #pragma once

#include "CoreMinimal.h"
// 按钮Button的头文件
#include "Components/Button.h"
#include "Blueprint/UserWidget.h"
#include "MyUserWidget.generated.h"

UCLASS()
class UESTUDY_API UMyUserWidget : public UUserWidget
{
	GENERATED_BODY()
	
public:// 声明变量
	UPROPERTY(meta = (BindWidget))
		UButton* ButtonStart;
	UPROPERTY(meta = (BindWidget))
		UButton* ButtonQuit;

	// 初始化函数  编译时调用
	virtual bool Initialize() override;

	// 按钮点击事件绑定的函数的声明
	UFUNCTION()
		void Start();
	UFUNCTION()
		void Quit();
};
  1. 编译运行,打开UE,在Content(内容)文件夹中创建Widget Blueprint,命名为UMG_Widget:
    UEC++70
  2. 在控件蓝图中添加Canvas Panel(画布面板)和两个Button(按钮),按钮的名字要与C++中创建的名字一致,否则编译会报错
    UEC++71
  3. 编译后,切换至图表面板,将类设置中的类选项的父类设置为MyUserWidget,编译保存
    UEC++72
  4. 复制UMG_Widget的引用:/Script/UMGEditor.WidgetBlueprint’/Game/UMG_Widget.UMG_Widget’

创建Widget并且将其添加到屏幕中:

  1. 在MyPlayerController.h中声明重写BeginPlay函数
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class UESTUDY_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	// 重写BeginPlay函数
	virtual void BeginPlay()override;
};
  1. 在MyPlayerController.cpp中,添加对应的头文件,及相应代码,记得要在UserWidget的引用的末尾加上_C,否则编译运行时会报错
#include "MyPlayerController.h"
#include "MyPawn.h"
// 用户面板的头文件
#include "Blueprint/UserWidget.h"

void AMyPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();// 继承自父类
}

void AMyPlayerController::BeginPlay()
{
	// 创建Widget并且将其添加到屏幕中
	Super::BeginPlay();// 继承自父类
	UClass* widgetClass = LoadClass<UUserWidget>(NULL, TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Widget.UMG_Widget_C'"));

	// 声明一个UserWdget类型的变量
	UUserWidget* MyWidgetClass = nullptr;
	MyWidgetClass = CreateWidget<UUserWidget>(GetWorld(), widgetClass);
	// 将MyWidgetClass添加到屏幕上
	MyWidgetClass->AddToViewport();
}
  1. 编译运行,将GameMode改为MyGameMode,运行即可看到Widget,依次点击按钮,会出现相应的输出:
    UEC++73

虚幻C++进度条Progress的实现

  1. 在MyUserWidget.h文件中添加进度条的实现代码:
#pragma once

#include "CoreMinimal.h"
// 按钮Button的头文件
#include "Components/Button.h"
#include "Blueprint/UserWidget.h"
#include "MyUserWidget.generated.h"

UCLASS()
class UESTUDY_API UMyUserWidget : public UUserWidget
{
	GENERATED_BODY()
	
public:// 声明变量
	UPROPERTY(meta = (BindWidget))
		UButton* ButtonStart;
	UPROPERTY(meta = (BindWidget))
		UButton* ButtonQuit;

	// 进度条的实现
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myHealth")
		float CurrentHealth = 100.0f;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myHealth")
		float MaxHealth = 100.0f;
	// 更新血量值的函数的声明
	void UpdateHealth();

	// 初始化函数  编译时调用
	virtual bool Initialize() override;

	// 按钮点击事件绑定的函数的声明
	UFUNCTION()
		void Start();
	UFUNCTION()
		void Quit();
};
  1. 在MyUserWidget.cpp文件中添加对应的实现:
#include "MyUserWidget.h"

// 更新血量值的函数的实现
void UMyUserWidget::UpdateHealth()
{
	// 判断当前血量值
	if(CurrentHealth <=0)// 血量值小于0,游戏结束
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Game Over"));
	}
	else
	{
		CurrentHealth -= 10;
	}
}

bool UMyUserWidget::Initialize()
{
	if (!Super::Initialize())// 是否继承了父类组件,继承了返回为真,否则为假
	{
		return false;
	}
	// 创建代理绑定
	// 按钮点击事件  参数:对象,绑定函数,
	ButtonStart->OnClicked.AddDynamic(this, &UMyUserWidget::Start);
	ButtonQuit->OnClicked.AddDynamic(this, &UMyUserWidget::Quit);
	return true;
}

void UMyUserWidget::Start()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Start"));
	// 调用掉血函数
	UpdateHealth();
}

void UMyUserWidget::Quit()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Quit"));
}
  1. 编译运行,在UE的UMG_Widget蓝图中,添加Progress Bar(进度条):
    UEC++74
  2. 创建绑定
    UEC++75
  3. 用当前血量值除以最大血量值以获得进度条的百分比,编译保存
    UEC++76
  4. 将GameMode设置为MyGameMode,运行,点击Start按钮,观察输出和进度条变化:
    UEC++77
    UEC++78

虚幻C++的代理

代理机制:代理也叫做委托,其作用就是提供一种消息机制,我们知道消息的传递需要发送方和接收方,而代理的过程也可分为这两大部分,我们可以换个名字分别叫做:出发点和执行点,这就是代理的主要部分,记住这两个点就能记住代理的原理。其实代理的方法和软件模式中的观察者模式是统一原理

代理:分为单播代理、多播代理和动态多播代理

单播代理的实现

  1. 在UE中创建一个C++类Actor用来绑定代理,命名为MyDelegateActor
  2. MyDelegateActor.cpp文件
#include "MyDelegateActor.h"

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

	// 绑定 参数:对象,绑定的函数
	NoParamDelegate.BindUObject(this, &AMyDelegateActor::NoParamDelegateFunction);
	OneParamDelegate.BindUObject(this, &AMyDelegateActor::OneParamDelegateFunction);
	TwoParamDelegate.BindUObject(this, &AMyDelegateActor::TwoParamDelegateFunction);
	ThreeParamDelegate.BindUObject(this, &AMyDelegateActor::ThreeParamDelegateFunction);
	RetvalDelegate.BindUObject(this, &AMyDelegateActor::RetvalDelegateFunction);
}

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

	// 进行代理的执行
	NoParamDelegate.ExecuteIfBound();// 判断是否绑定,绑定了就执行
	OneParamDelegate.ExecuteIfBound("OneParamDelegate");
	TwoParamDelegate.ExecuteIfBound("TwoParamDelegate", 10);
	ThreeParamDelegate.ExecuteIfBound("ThreeParamDelegate", 10, 5.0);
	FString strValue = RetvalDelegate.Execute();
}

// Called every frame
void AMyDelegateActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AMyDelegateActor::NoParamDelegateFunction()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, TEXT("NoParamDelegate"));
}

void AMyDelegateActor::OneParamDelegateFunction(FString str)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, PFString::Printf(TEXT("%s"), *str));
}

void AMyDelegateActor::TwoParamDelegateFunction(FString str, int32 value)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("%s %d"), *str, value));
}

void AMyDelegateActor::ThreeParamDelegateFunction(FString str, int32 value, float value1)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("%s %d %f"), *str, value, value1));
}

FString AMyDelegateActor::RetvalDelegateFunction()
{
	FString str = FString::Printf(TEXT("RetvalDelegate"));
	return str;
}
  1. MyDelegateActor.h文件
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyDelegateActor.generated.h"

// 声明单播代理
// 无参单播代理   NoParamDelegate:代理名称
DECLARE_DELEGATE(NoParamDelegate);
// 有一个参数的单播代理   FString:参数类型Clare
DECLARE_DELEGATE_OneParam(OneParamDelegate, FString);
// 有两个参数的单播代理   FString, int32:参数类型
DECLARE_DELEGATE_TwoParams(TwoParamDelegate, FString, int32);
// 有三个参数的单播代理   FString, int32, float:参数类型
DECLARE_DELEGATE_ThreeParams(ThreeParamDelegate, FString, int32, float);
// 带有返回参数的单播代理   FString:返回参数    RetvalDelegate:代理名称
DECLARE_DELEGATE_RetVal(FString, RetvalDelegate);

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

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

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

	// 声明代理名称变量
	NoParamDelegate NoParamDelegate;
	OneParamDelegate OneParamDelegate;
	TwoParamDelegate TwoParamDelegate;
	ThreeParamDelegate ThreeParamDelegate;
	RetvalDelegate RetvalDelegate;

	// 声明绑定函数
	void NoParamDelegateFunction();
	void OneParamDelegateFunction(FString str);
	void TwoParamDelegateFunction(FString str, int32 value);
	void ThreeParamDelegateFunction(FString str, int32 value, float value1);
	FString RetvalDelegateFunction();
};
  1. 编译运行,打开UE,把MyDelegateActor拖入场景中,点击运行
    UEC++79

多播代理的实现

  1. MyDelegateActor.cpp文件:
#include "MyDelegateActor.h"

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

	// 多播代理的绑定   多播代理可以绑定多个函数
	OneParamMultiDelegate.AddUObject(this, &AMyDelegateActor::MultiDelegateFunction1);
	OneParamMultiDelegate.AddUObject(this, &AMyDelegateActor::MultiDelegateFunction2);
	OneParamMultiDelegate.AddUObject(this, &AMyDelegateActor::MultiDelegateFunction3);
}

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

	// 进行多播代理的执行
	OneParamMultiDelegate.Broadcast("OneParamMultiDelegate");
}

// Called every frame
void AMyDelegateActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 多播代理绑定函数的实现
void AMyDelegateActor::MultiDelegateFunction1(FString str)
{
	FString TempStr = str.Append("1");
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("%s"), *TempStr));
}

void AMyDelegateActor::MultiDelegateFunction2(FString str)
{
	FString TempStr = str.Append("2");
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("%s"), *TempStr));
}

void AMyDelegateActor::MultiDelegateFunction3(FString str)
{
	FString TempStr = str.Append("3");
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("%s"), *TempStr));
}
  1. MyDelegateActor.h文件
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyDelegateActor.generated.h"

// 声明多播代理
// 有一个参数的多播代理   OneParamMultiDelegate:代理名称  FString:参数类型
DECLARE_MULTICAST_DELEGATE_OneParam(OneParamMultiDelegate, FString);

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

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	// 声明多播代理变量名称
	OneParamMultiDelegate OneParamMultiDelegate;

	// 声明多播代理绑定函数
	UFUNCTION()
		void MultiDelegateFunction1(FString str);
	UFUNCTION()
		void MultiDelegateFunction2(FString str);
	UFUNCTION()
		void MultiDelegateFunction3(FString str);
};

  1. 编译运行,打开UE,把MyDelegateActor拖入场景中,点击运行:
    UEC++80

动态多播代理的实现

  1. MyDelegateActor.cpp文件:
#include "MyDelegateActor.h"

// Sets default values
AMyDelegateActor::AMyDelegateActor()
{
 	// Set this actor 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 AMyDelegateActor::BeginPlay()
{
	Super::BeginPlay();

	// 执行动态多播代理  绑定在蓝图中实现
	DynamicMultiDelegate.Broadcast("DynamicMultiDelegate");
}

// Called every frame
void AMyDelegateActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
  1. MyDelegateActor.h文件
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyDelegateActor.generated.h"

// 声明动态多播代理  和动态多播代理的区别在于动态多播代理可以暴露给蓝图,在蓝图中进行事件的绑定  param:参数名称
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDynamicMultiDelegate, FString, param);

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

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	// 声明动态多播代理变量 
	UPROPERTY(BlueprintAssignable)   // 将动态多播代理可以暴露给蓝图,在蓝图中进行事件的绑定
		FDynamicMultiDelegate DynamicMultiDelegate;
};

  1. 编译运行,打开UE,创建基于MyDelegateActord的蓝图类,命名为BP_MyDelegateActor,在蓝图中进行事件的绑定:
    UEC++81
    UEC++82
  2. 编译保存,将BP_MyDelegateActor拖动到场景中,运行:
    UEC++83

虚幻C++子弹类型设置

  1. 打开UE,创建C++类Actor,命名为MyBullet
  2. MyBullet.h文件:
#pragma once

#include "CoreMinimal.h"
// staticMesh头文件
#include "Components/StaticMeshComponent.h"
// 球型碰撞头文件
#include "Components/SphereComponent.h"
// 运动组件头文件
#include "GameFramework/ProjectileMovementComponent.h"
#include "GameFramework/Actor.h"
#include "MyBullet.generated.h"

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

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

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

public:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "myComponent")
		UStaticMeshComponent* MyBullet;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "myComponent")
		USphereComponent* MySphere;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "myComponent")
		UProjectileMovementComponent* MyProjectile;
};
  1. MyBullet.cpp文件:
#include "MyBullet.h"

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

	// 初始化
	MyBullet = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyBulletComponent"));
	MySphere = CreateDefaultSubobject<USphereComponent>(TEXT("MySphereComponent"));
	MyProjectile= CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("MyPronjectileComponent"));

	// 静态加载,设置模型
	static ConstructorHelpers::FObjectFinder<UStaticMesh>TempStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone'"));
	MyBullet->SetStaticMesh(TempStaticMesh.Object);
	MyBullet->SetRelativeScale3D(FVector(0.4, 0.4, 0.4));
	// 设置父子级
	RootComponent = MyBullet;
	MySphere->SetupAttachment(MyBullet);
	MyProjectile->SetUpdatedComponent(MyBullet);
	MyProjectile->InitialSpeed = 1200.0f;
	MyProjectile->MaxSpeed = 2400.0f;
	MyProjectile->bRotationFollowsVelocity = true;
	MyProjectile->bIsHomingProjectile = true;
	MyProjectile->ProjectileGravityScale = 1.5f;
}

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

// Called every frame
void AMyBullet::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
  1. 编译运行,打开UE,创建基于MyBullet的蓝图类,命名为BP_MyBullet,在蓝图中查看设置:
    UEC++84
    UEC++85

虚幻C++创建Character添加增强输入

  1. 在UE中新建C++类Character,命名为MyCharacter

  2. 在VS的UEStudy.build.cs文件中,添加增强输入模块:
    UEC++86

  3. 在MyCharacter.h中:

#pragma once

#include "CoreMinimal.h"
// 输入映射 value值
#include "InputActionValue.h"
//增强输入头文件
#include "EnhancedInputComponent.h"
// 增强输入子系统
#include "EnhancedInputSubsystems.h"
// 控制器头文件
#include "GameFramework/Controller.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.h"
// 运动组件头文件
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()
class UESTUDY_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 声明相机变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 声明输入映射相关变量
	// 输入映射上下文
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputMappingContext* DefaultMappingContext;
	// 移动映射输入变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* MoveAction;
	// 旋转映射变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* LookAction;

	// 鼠标按下移动时的代理绑定函数
	// FInputActionValue:鼠标按下和键盘按下时会有值输入
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
};
  1. 在MyCharacter.cpp中:
#include "MyCharacter.h"

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

	// 初始化
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));

	// 设置摄像机摇臂长度
	MySpringArm->TargetArmLength = 400.0f;

	// 设置根组件  将相机设置为摄像机摇臂的组件
	MyCamera->SetupAttachment(MySpringArm);
	// 将摄像机摇臂设置为默认根组件的组件
	MySpringArm->SetupAttachment(RootComponent);

	// 让控制器的旋转不影响角色的转动,只影响摄像机的转动
	bUseControllerRotationPitch = false;// 设置Pitch旋转
	bUseControllerRotationRoll = false;// 设置Roll旋转
	bUseControllerRotationYaw = false;// 设置Yaw旋转

	// 让角色面朝加速度的方向
	GetCharacterMovement()->bOrientRotationToMovement = true;
	// 使用Pawn控制器的旋转
	MySpringArm->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayController = Cast<APlayerController>(Controller))// 判断控制器是否合法
	{
		// 判断玩家子系统   获取增强输入的本地玩家子系统  将本地控制器转化为增强输入本地玩家子系统
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayController->GetLocalPlayer()))
		{
			// 转化成功  添加映射输入上下文   0为优先级,数值越大,优先级越高
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
// 输入映射
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 将玩家输入映射,转化为增强输入玩家映射  判断是否有输入映射
	// CastChecked<UEnhancedInputComponent>(PlayerInputComponent):将PlayerInputComponent转化为UEnhancedInputComponent
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定映射  MoveAction:映射变量,ETriggerEvent::Trigger:方式,this:对象,&AMyCharacter::Move绑定的函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
	}
}

// 移动
void AMyCharacter::Move(const FInputActionValue& Value)
{
	// 用2D变量来控制移动   Value.Get<FVector2D>():获得输入的2D向量坐标
	FVector2D MovementVector = Value.Get<FVector2D>();
	// 判断控制器是否合法
	if (Controller != nullptr) 
	{
		// 通过变量的旋转获得他的前向向量和右向向量,来控制前后左右移动
		// Controller->GetControlRotation():获得控制器的旋转
		const FRotator Rotation = Controller->GetControlRotation();
		// 获取Yaw方向上的旋转
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		// 获得前向向量(X轴方向)
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		// 获得右向向量(Y轴方向)
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// 在键盘按下WASD时,添加输入
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 旋转
void AMyCharacter::Look(const FInputActionValue& Value)
{
	// 获得鼠标X和Y轴的向量
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	// 判断控制器是否合法,即是否有输入
	if (Controller != nullptr)
	{
		// 获得X轴方向的向量值
		AddControllerYawInput(LookAxisVector.X);
		// 获得Y轴方向的向量值
		AddControllerPitchInput(LookAxisVector.Y);
	}
}
  1. 编译运行,打开UE,创建基于MyCharacter的蓝图类,命名为BP_MyCharacter,打开蓝图,为网格体添加变换、网格体和动画类,编译保存
    UEC++87
  2. 在类设置中,为几个映射添加默认值:
    UEC++88
    这几个是第三人称里自带的,也可以自己新建:
    UEC++89
  3. 将BP_MyCharacter拖入场景当中,在细节面板中将自动控制玩家改为玩家0,将世界设置中的GameMode设置为默认,运行:
    UEC++90

虚幻C++创建Interface接口

  1. 在UE中添加Unreal Interface,命名为MyInterface:
    UEC++91
  2. 在MyInterface.h文件中添加函数:
#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "MyInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UMyInterface : public UInterface
{
	GENERATED_BODY()
};

class UESTUDY_API IMyInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	// 必须声明为虚函数,这样在继承时,才可以将其进行重写、覆盖
	virtual void Attack() {};
	virtual void CalculateHealth() {};
};
  1. 在MyCharacter中使用接口:MyCharacter.cpp
#include "MyCharacter.h"

// Sets default values
AMyCharacter::AMyCharacter()
{
 	// Set this character 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 AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	// 调用接口函数
	Attack();
	CalculateHealth();
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

void AMyCharacter::Attack()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Attack"));
}

void AMyCharacter::CalculateHealth()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("CalculateHealth"));
}
  1. 在MyCharacter.h中
#pragma once

// 接口
#include "MyInterface.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter,public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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;

	// 重写两个接口函数
	virtual void Attack()override;
	virtual void CalculateHealth()override;
};
  1. 编译运行,打开UE,将BP_MyCharacter拖入场景中,运行
    UEC++92

虚幻C++创建TimeHandle定时器

  1. 在MyCharacter.h文件中:
#pragma once

#include "CoreMinimal.h"
// 定时器头文件
#include "TimerManager.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter,public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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;

	// 声明一个定时器变量
	FTimerHandle Time;
	// 声明一个函数用来方便定时器打印内容
	void PrintF();
};
  1. 在MyCharacter.cpp中:
#include "MyCharacter.h"

// Sets default values
AMyCharacter::AMyCharacter()
{
 	// Set this character 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 AMyCharacter::BeginPlay()
{
	Super::BeginPlay();

	// 设置定时器   参数:时间句柄,对象,绑定的函数,计时间隔,是否循环
	GetWorld()->GetTimerManager().SetTimer(Time, this, &AMyCharacter::PrintF, 1.0, true);

	// 清除定时器    加上此段以后,就没有输出了
	if (Time.IsValid()) // 时间合法的话
	{
		GetWorld()->GetTimerManager().ClearTimer(Time);
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 定时器相关
void AMyCharacter::PrintF()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Time"));
}
  1. 编译运行,打开UE,将BP_MyCharacter拖入场景中,运行
    UEC++93

虚幻C++创建3DWidget并渲染到屏幕上

  1. 在UE中创建C++类UserWidget,命名为MyHealthWidget
  2. 在Content中, 创建一个控件蓝图,命名为UMG_Health
    UEC++94
  3. 在UMG_Health中添加,canvas Pannel,并将填充屏幕改为自定义,将长款设为400和20

UEC++95
UEC++96
4. 再添加一个进度条,将锚点改为平铺,且偏移都设置为0:
UEC++97
UEC++98
5. 将类默认设置改为MyHealthWidget
UEC++99
6. 给进度条创建绑定,编译保存,复制其引用:/Script/UMGEditor.WidgetBlueprint’/Game/UMG_Health.UMG_Health’
UEC++100
UEC++101
7. 在MyCharacter.h文件中:

#pragma once

#include "CoreMinimal.h"
// WidgetCompontent头文件
#include "Components/WIdgetComponent.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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;

	// 声明一个Widget类型变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UWidgetComponent* MyWidgetHealth;
};
  1. 在MyCharacter.cpp文件中:
#include "MyCharacter.h"

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

	// 初始化控件
	MyWidgetHealth = CreateDefaultSubobject<UWidgetComponent>(TEXT("MyWidgetComponent"));
	// 将控件设置为默认根组件的组件
	MyWidgetHealth->SetupAttachment(RootComponent);
	static ConstructorHelpers::FClassFinder<UUserWidget>WidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Health.UMG_Health_C'"));
	MyWidgetHealth->SetWidgetClass(WidgetClass.Class);
	MyWidgetHealth->SetRelativeLocation(FVector(0, 0, 100));
	// 设置其渲染方式,渲染到屏幕上或渲染到世界当中
	MyWidgetHealth->SetWidgetSpace(EWidgetSpace::Screen);
	// 设置位置大小
	MyWidgetHealth->SetDrawSize(FVector2D(400, 20));
}
  1. 编译运行,打开UE,查看BP_Character,
    UEC++102

虚幻C++创建Apply Damage并且接受伤害TakeDamage

  1. 在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"
// ApplyDamage所在的函数库头文件
#include "Kismet/GameplayStatics.h"
#include "MyCharacter.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 UBoxComponent* MyBox;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		class UAudioComponent* MyAudio;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MyClass")
		TSubclassOf<AActor> MyActor;

	// 利用碰撞box绑定BeginOverlap   绑定的函数的声明   两个物体开始重叠时执行
	UFUNCTION()
		// 复制原函数的后6个参数,并删掉类型和参数名中间的逗号
		void BeginOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
  1. 在MyActor.cpp中
#include "MyActor.h"

// Sets default values
// 构造函数
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	// 组件默认值
	MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MyCustomScene"));
	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyCustomStaticMesh"));
	MyParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MyCustomParticle"));
	MyBox = CreateDefaultSubobject<UBoxComponent>(TEXT("MyCustomBox"));
	MyAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("MyCustomAudio"));

	// 设置父子级
	RootComponent = MyScene;// 将MyMesh设置为根组件
	MyMesh->SetupAttachment(MyScene);// 将MyMesh放到根组件下面
	MyParticle->SetupAttachment(MyScene);
	MyBox->SetupAttachment(MyScene);
	MyAudio->SetupAttachment(MyBox);

	// 静态加载资源
	static ConstructorHelpers::FObjectFinder<UStaticMesh>TempStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cone.Shape_Cone'"));
	// 将临时静态模型设置为自己的模型   TempStaticMesh.Object获取对象资源的引用
	MyMesh->SetStaticMesh(TempStaticMesh.Object);
	static ConstructorHelpers::FObjectFinder<UParticleSystem>TempParticSystem(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'"));
	MyParticle->SetTemplate(TempParticSystem.Object);
	static ConstructorHelpers::FObjectFinder<USoundWave>TempSound(TEXT("/Script/Engine.SoundWave'/Game/StarterContent/Audio/Collapse01.Collapse01'"));
	MyAudio->SetSound(TempSound.Object);

	// 静态加载类
	static ConstructorHelpers::FClassFinder<AActor> TempMyActor(TEXT("/Script/Engine.Blueprint'/Game/StarterContent/Blueprints/Blueprint_CeilingLight.Blueprint_CeilingLight_C'"));
	// 设置自己的类
	MyActor = TempMyActor.Class;

	// 碰撞设置  SetCollisionEnabled:设置碰撞输入
	MyBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);// 此组件只有查询碰撞   只有事件检测和重叠
	
	// 碰撞设置  SetCollisionObjectType:设置碰撞对象类型
	MyBox->SetCollisionObjectType(ECC_WorldDynamic);// 世界的动态对象
	
	// 碰撞设置  碰撞响应
	MyBox->SetCollisionResponseToAllChannels(ECR_Overlap);// 将所有的通道碰撞响应设置为Overlap(重叠)

	// 设置Box大小
	MyBox->SetBoxExtent(FVector(64, 64, 64));
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	// 游戏开始时,粒子系统默认失效
	if(MyParticle)
	{
		MyParticle->Deactivate();
	}

	// 动态加载资源
	UStaticMesh* MyTempStaticMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube'"));
	if (MyTempStaticMesh)
	{
		MyMesh->SetStaticMesh(MyTempStaticMesh);
	}
	// 动态加载类   LoadClass<AActor>:加载AActor模板类   this:在哪个类里
	UClass* MyTempClass = LoadClass<AActor>(this, TEXT("/Script/Engine.Blueprint'/Game/StarterContent/Blueprints/Blueprint_WallSconce.Blueprint_WallSconce_C'"));
	if (MyTempClass)
	{
		// SpawnActor出生,SpawnActor<AActor>:产生一个Actor类,FVector:类产生的位置  ZeroVector:(0,0,0)
		// FRotator::ZeroRotator  类出生时的旋转   ZeroRotator:旋转为0    缩放默认值为(1,1,1)
		AActor* SpawnActor = GetWorld()->SpawnActor<AActor>(MyTempClass, FVector::ZeroVector,FRotator::ZeroRotator);
	}

	// 利用碰撞box绑定BeginOverlap   参数:对象,绑定的函数
	MyBox->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::BeginOverlapFunction);
}

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// 利用碰撞box绑定BeginOverlap  绑定的函数的实现
void AMyActor::BeginOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// 当other actor碰撞到角色时,角色受到伤害
	AMyCharacter* MyCharacter = Cast<AMyCharacter>(OtherActor);
	if (MyCharacter)// 判断MyCharacter是否合法
	{
		// 碰到的时第三人称的时候,才受到伤害
		// ApplyDamage( 受伤害的对象,伤害值,控制器,this指针,伤害类型)
		UGameplayStatics::ApplyDamage(MyCharacter, 5.0f, nullptr, this, UDamageType::StaticClass());
	}

	// 碰撞时粒子生效
	MyParticle->Activate();
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("BeginOverLap is seccess"));
}
  1. 在MyCharacter.h中
#pragma once

#include "CoreMinimal.h"
// 输入映射 value值
#include "InputActionValue.h"
//增强输入头文件
#include "EnhancedInputComponent.h"
// 增强输入子系统
#include "EnhancedInputSubsystems.h"
// 控制器头文件
#include "GameFramework/Controller.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.h"
// 运动组件头文件
#include "GameFramework/CharacterMovementComponent.h"
// 接口
#include "MyInterface.h"
// 定时器头文件
#include "TimerManager.h"
// WidgetCompontent头文件
#include "Components/WidgetComponent.h"
#include "MyHealthWidget.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 声明相机变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 声明输入映射相关变量
	// 输入映射上下文
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputMappingContext* DefaultMappingContext;
	// 移动映射输入变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* MoveAction;
	// 旋转映射变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* LookAction;

	// 鼠标按下移动时的代理绑定函数
	// FInputActionValue:鼠标按下和键盘按下时会有值输入
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);

	// 重写两个接口函数
	virtual void Attack()override;
	virtual void CalculateHealth()override;

	// 声明一个定时器变量
	FTimerHandle Time;
	// 声明一个函数用来方便定时器打印内容
	void PrintF();

	// 声明一个Widget类型变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UWidgetComponent* MyWidgetHealth;

	// 重写受到伤害的函数
	// 参数:接收到的伤害值,
	virtual float TakeDamage(float DamageAmount,struct FDamageEvent const &DamageEvent,class AController* EnvenInstigator,AActor* DamageCauser)override;
};
  1. 在MyCharacter.cpp中
#include "MyCharacter.h"

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

	// 初始化
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));

	// 初始化控件
	MyWidgetHealth = CreateDefaultSubobject<UWidgetComponent>(TEXT("MyWidgetComponent"));
	// 将控件设置为默认根组件的组件
	MyWidgetHealth->SetupAttachment(RootComponent);
	static ConstructorHelpers::FClassFinder<UUserWidget>WidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Health.UMG_Health_C'"));
	MyWidgetHealth->SetWidgetClass(WidgetClass.Class);
	MyWidgetHealth->SetRelativeLocation(FVector(0, 0, 100));
	// 设置其渲染方式,渲染到屏幕上或渲染到世界当中
	MyWidgetHealth->SetWidgetSpace(EWidgetSpace::Screen);
	// 设置位置大小
	MyWidgetHealth->SetDrawSize(FVector2D(400, 20));

	// 设置摄像机摇臂长度
	MySpringArm->TargetArmLength = 400.0f;

	// 设置根组件  将相机设置为摄像机摇臂的组件
	MyCamera->SetupAttachment(MySpringArm);
	// 将摄像机摇臂设置为默认根组件的组件
	MySpringArm->SetupAttachment(RootComponent);

	// 让控制器的旋转不影响角色的转动,只影响摄像机的转动
	bUseControllerRotationPitch = false;// 设置Pitch旋转
	bUseControllerRotationRoll = false;// 设置Roll旋转
	bUseControllerRotationYaw = false;// 设置Yaw旋转

	// 让角色面朝加速度的方向
	GetCharacterMovement()->bOrientRotationToMovement = true;
	// 使用Pawn控制器的旋转
	MySpringArm->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayController = Cast<APlayerController>(Controller))// 判断控制器是否合法
	{
		// 判断玩家子系统   获取增强输入的本地玩家子系统  将本地控制器转化为增强输入本地玩家子系统
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayController->GetLocalPlayer()))
		{
			// 转化成功  添加映射输入上下文   0为优先级,数值越大,优先级越高
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}

	// 调用接口函数
	Attack();
	CalculateHealth();

	// 设置定时器   参数:时间句柄,对象,绑定的函数,计时间隔,是否循环
	GetWorld()->GetTimerManager().SetTimer(Time, this, &AMyCharacter::PrintF, 1.0, true);
	
	// 清除定时器    加上此段以后,就没有输出了
	if (Time.IsValid()) // 时间合法的话
	{
		GetWorld()->GetTimerManager().ClearTimer(Time);
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
// 输入映射
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 将玩家输入映射,转化为增强输入玩家映射  判断是否有输入映射
	// CastChecked<UEnhancedInputComponent>(PlayerInputComponent):将PlayerInputComponent转化为UEnhancedInputComponent
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定映射  MoveAction:映射变量,ETriggerEvent::Trigger:方式,this:对象,&AMyCharacter::Move绑定的函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
	}
}

// 移动
void AMyCharacter::Move(const FInputActionValue& Value)
{
	// 用2D变量来控制移动   Value.Get<FVector2D>():获得输入的2D向量坐标
	FVector2D MovementVector = Value.Get<FVector2D>();
	// 判断控制器是否合法
	if (Controller != nullptr) 
	{
		// 通过变量的旋转获得他的前向向量和右向向量,来控制前后左右移动
		// Controller->GetControlRotation():获得控制器的旋转
		const FRotator Rotation = Controller->GetControlRotation();
		// 获取Yaw方向上的旋转
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		// 获得前向向量(X轴方向)
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		// 获得右向向量(Y轴方向)
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// 在键盘按下WASD时,添加输入
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 旋转
void AMyCharacter::Look(const FInputActionValue& Value)
{
	// 获得鼠标X和Y轴的向量
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	// 判断控制器是否合法,即是否有输入
	if (Controller != nullptr)
	{
		// 获得X轴方向的向量值
		AddControllerYawInput(LookAxisVector.X);
		// 获得Y轴方向的向量值
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

// 接口相关
void AMyCharacter::Attack()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Attack"));
}

void AMyCharacter::CalculateHealth()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("CalculateHealth"));
}

// 定时器相关
void AMyCharacter::PrintF()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Time"));
}

// 受伤减血
float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EnvenInstigator, AActor* DamageCauser)
{
	UMyHealthWidget* MyWidget = Cast<UMyHealthWidget>(MyWidgetHealth->GetUserWidgetObject());
	// 判断MyWidget是否合法
	if (MyWidget)
	{
		// 判断血量值
		if (MyWidget->CurrentHealth <= 0)
		{
			// 游戏结束
			return 0;
		}
		MyWidget->CurrentHealth -= 5.0f;
	}
	return 0.0f;
}
  1. 编译运行,打开UE,将Pawn的自动控制玩家设置为玩家0,运行,移动玩家碰撞Actor,每碰撞一次,血条减少5:
    UEC++103

虚幻C++创建Timeline时间轴

  1. 打开UE,创建C++类Actor,命名为MyTimelineActor
  2. 在MyTimelineActor.h中:
#pragma once

#include "CoreMinimal.h"
// Timeline头文件
#include "Components/TimelineComponent.h"
#include "GameFramework/Actor.h"
#include "MyTimelineActor.generated.h"

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

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

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

	// 用于设置浮点曲线的变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myCurve")
		UCurveFloat* MyCurveFloat;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "mySceneComponent")
		UTimelineComponent* MyTimeline;

	// 声明两个代理,用于timeline时绑定
	// 时间轴一开始时进行的绑定
	FOnTimelineFloat TimelineDelegate;
	// 时间轴完成时进行的绑定
	FOnTimelineEvent TimelineFinishedDelegate;

	// 用于timeline代理绑定的函数
	// 时间轴开始时绑定
	UFUNCTION()
		void TimelineStart(float value);
	// 时间轴结束时绑定
	UFUNCTION()
		void TimelinFinished();
};
  1. 在MyTimelineActor.cpp中
#include "MyTimelineActor.h"

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

	// 初始化
	MyTimeline = CreateDefaultSubobject<UTimelineComponent>(TEXT("MyTimeline"));
}

// Called when the game starts or when spawned
void AMyTimelineActor::BeginPlay()
{
	Super::BeginPlay();
	
	//  代理绑定逻辑
	TimelineDelegate.BindUFunction(this, TEXT("TimelineStart"));
	TimelineFinishedDelegate.BindUFunction(this, TEXT("TimelinFinished"));
	// 设置浮点曲线
	MyTimeline->AddInterpFloat(MyCurveFloat, TimelineDelegate);
	// 设置是否循环
	MyTimeline->SetLooping(false);
	// 设置播放
	MyTimeline->PlayFromStart();// 从一开始就播放
	MyTimeline->Play();// 从当前位置播放
	// 设置完成时绑定的代理
	MyTimeline->SetTimelineFinishedFunc(TimelineFinishedDelegate);
}

// Called every frame
void AMyTimelineActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 时间轴开始时绑定
void AMyTimelineActor::TimelineStart(float value)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("TimelinePlay"));
}

// 时间轴结束时绑定
void AMyTimelineActor::TimelinFinished()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("TimelineFinished"));
}
  1. 编译运行,打开UE,在UE中创建浮点曲线,命名为MyFloatCurve,双击打开,添加两个关键帧,一个数值为(0,0),一个数值为(1,1)
    UEC++104
    UEC++105
    UEC++106
  2. 创建基于MyTimelineActor的蓝图类,命名为BP_MyTimelineActor,双击打开,指定其浮点曲线为刚创建的MyFloatCurve,编译保存
    UEC++107
  3. 把BP_MyTimelineActor拖动到场景中,点击运行
    UEC++108

用TimeLine实现开关门

  1. 在MyTimelineActor.h中:
#pragma once

#include "CoreMinimal.h"
// Timeline头文件
#include "Components/TimelineComponent.h"
// 碰撞头文件
#include "Components/BoxComponent.h"
#include "MyCharacter.h"
#include "GameFramework/Actor.h"
#include "MyTimelineActor.generated.h"

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

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

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

	// 用于设置浮点曲线的变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "myCurve")
		UCurveFloat* MyCurveFloat;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "mySceneComponent")
		UTimelineComponent* MyTimeline;

	// 声明两个代理,用于timeline时绑定
	// 时间轴一开始时进行的绑定
	FOnTimelineFloat TimelineDelegate;
	// 时间轴完成时进行的绑定
	FOnTimelineEvent TimelineFinishedDelegate;

	// 用于timeline代理绑定的函数
	// 时间轴开始时绑定
	UFUNCTION()
		void TimelineStart(float value);
	// 时间轴结束时绑定
	UFUNCTION()
		void TimelinFinished();

	// 用Timeline实现开关门
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "mySceneComponent")
		USceneComponent* MyScene;
	// 声明一个模型变量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "mySceneComponent")
		UStaticMeshComponent* MyStaticMesh;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MySceneComponent")
		UBoxComponent* MyBox;

	// 利用碰撞box绑定BeginOverlap   绑定的函数的声明   两个物体开始重叠时执行
	UFUNCTION()
		// 复制原函数的后6个参数,并删掉类型和参数名中间的逗号
		void BeginOverlapFunction(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
	// 利用碰撞box绑定EndOverlap   绑定的函数的声明    两个物体结束重叠时执行
	UFUNCTION()
		// 复制原函数的4个参数,并删掉类型和参数名中间的逗号
		void EndOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};

  1. 在MyTimelineActor.cpp中
#include "MyTimelineActor.h"

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

	// 初始化
	MyTimeline = CreateDefaultSubobject<UTimelineComponent>(TEXT("MyTimeline"));
	MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MySceneComponent"));
	MyStaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyStaticMeshComponent"));
	MyBox = CreateDefaultSubobject<UBoxComponent>(TEXT("MyBoxComponent"));
	// 加载模型
	static ConstructorHelpers::FObjectFinder<UStaticMesh>TempStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Architecture/Wall_400x400.Wall_400x400'"));
	if (TempStaticMesh.Succeeded())// 成功加载模型
	{
		MyStaticMesh->SetStaticMesh(TempStaticMesh.Object);
	}
	// 设置根组件
	RootComponent = MyScene;
	MyStaticMesh->SetupAttachment(MyScene);
	MyBox->SetupAttachment(MyScene);
	// 设置大小
	MyBox->SetBoxExtent(FVector(200, 100, 100));
	// 设置相对位置
	MyBox->SetRelativeLocation(FVector(200, 0, 0));
}

// Called when the game starts or when spawned
void AMyTimelineActor::BeginPlay()
{
	Super::BeginPlay();
	
	//  代理绑定逻辑
	TimelineDelegate.BindUFunction(this, TEXT("TimelineStart"));
	TimelineFinishedDelegate.BindUFunction(this, TEXT("TimelinFinished"));
	// 设置浮点曲线
	MyTimeline->AddInterpFloat(MyCurveFloat, TimelineDelegate);
	// 设置是否循环
	//MyTimeline->SetLooping(false);
	 设置播放
	//MyTimeline->PlayFromStart();// 从一开始就播放
	//MyTimeline->Play();// 从当前位置播放
	// 设置完成时绑定的代理
	MyTimeline->SetTimelineFinishedFunc(TimelineFinishedDelegate);

	// 利用碰撞box绑定BeginOverlap   参数:对象,绑定的函数
	MyBox->OnComponentBeginOverlap.AddDynamic(this, &AMyTimelineActor::BeginOverlapFunction);
	// 利用碰撞box绑定EndOverlap   参数:对象,绑定的函数
	MyBox->OnComponentEndOverlap.AddDynamic(this, &AMyTimelineActor::EndOverlapFunction);
}

// Called every frame
void AMyTimelineActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 时间轴开始时绑定
void AMyTimelineActor::TimelineStart(float value)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("TimelinePlay"));
	// 在Z轴上缓慢旋转90°
	float YawRotation = FMath::Lerp(0, 90, value);
	MyStaticMesh->SetRelativeRotation(FRotator(0, YawRotation, 0));
}

// 时间轴结束时绑定
void AMyTimelineActor::TimelinFinished()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("TimelineFinished"));
}

void AMyTimelineActor::BeginOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// 判断是否是这个角色碰到了碰撞体
	AMyCharacter* TempCharacter = Cast<AMyCharacter>(OtherActor);
	if (TempCharacter)
	{
		MyTimeline->PlayFromStart();
	}
}

void AMyTimelineActor::EndOverlapFunction(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	// 判断是否是这个角色碰到了碰撞体
	AMyCharacter* TempCharacter = Cast<AMyCharacter>(OtherActor);
	if (TempCharacter)
	{
		MyTimeline->ReverseFromEnd();// 从后往前播放
	}
}
  1. 编译运行,将BP_MyTimeLineActor和人物拖入场景中,将Pawn的自动控制玩家设置为玩家0,运行,移动玩家靠近门,玩家靠近门打开,离开门则关闭

虚幻C++射线检测LinetranceByChannel和LineTanceByObject

根据通道进行检测

  1. 在MyCharacter.cpp中:
#include "MyCharacter.h"

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

	// 初始化
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));

	// 初始化控件
	MyWidgetHealth = CreateDefaultSubobject<UWidgetComponent>(TEXT("MyWidgetComponent"));
	// 将控件设置为默认根组件的组件
	MyWidgetHealth->SetupAttachment(RootComponent);
	static ConstructorHelpers::FClassFinder<UUserWidget>WidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Health.UMG_Health_C'"));
	MyWidgetHealth->SetWidgetClass(WidgetClass.Class);
	MyWidgetHealth->SetRelativeLocation(FVector(0, 0, 100));
	// 设置其渲染方式,渲染到屏幕上或渲染到世界当中
	MyWidgetHealth->SetWidgetSpace(EWidgetSpace::Screen);
	// 设置位置大小
	MyWidgetHealth->SetDrawSize(FVector2D(400, 20));

	// 设置摄像机摇臂长度
	MySpringArm->TargetArmLength = 400.0f;

	// 设置根组件  将相机设置为摄像机摇臂的组件
	MyCamera->SetupAttachment(MySpringArm);
	// 将摄像机摇臂设置为默认根组件的组件
	MySpringArm->SetupAttachment(RootComponent);

	// 让控制器的旋转不影响角色的转动,只影响摄像机的转动
	bUseControllerRotationPitch = false;// 设置Pitch旋转
	bUseControllerRotationRoll = false;// 设置Roll旋转
	bUseControllerRotationYaw = false;// 设置Yaw旋转

	// 让角色面朝加速度的方向
	GetCharacterMovement()->bOrientRotationToMovement = true;
	// 使用Pawn控制器的旋转
	MySpringArm->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayController = Cast<APlayerController>(Controller))// 判断控制器是否合法
	{
		// 判断玩家子系统   获取增强输入的本地玩家子系统  将本地控制器转化为增强输入本地玩家子系统
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayController->GetLocalPlayer()))
		{
			// 转化成功  添加映射输入上下文   0为优先级,数值越大,优先级越高
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}

	// 调用接口函数
	Attack();
	CalculateHealth();

	// 设置定时器   参数:时间句柄,对象,绑定的函数,计时间隔,是否循环
	GetWorld()->GetTimerManager().SetTimer(Time, this, &AMyCharacter::PrintF, 1.0, true);
	
	// 清除定时器    加上此段以后,就没有输出了
	if (Time.IsValid()) // 时间合法的话
	{
		GetWorld()->GetTimerManager().ClearTimer(Time);
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 执行射线检测
	StartLocation = MyCamera->GetComponentLocation();
	ForwardVector = MyCamera->GetForwardVector();
	EndLocation = StartLocation + ForwardVector * 9999;
	// 用bool值接收射线检测的返回值  ECC_Visibility:碰撞类型
	// 根据通道查询检测
	bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_Visibility);
	if (bHit)//判断是否检测到了东西
	{
		AActor* HitActor = HitResult.GetActor();
		// 击中这个点的位置
		FVector ImpactPoint = HitResult.ImpactPoint;
		// 击中的位置
		FVector HitLocation = HitResult.Location;
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("%s"),*HitActor->GetName()));
	}
}

// Called to bind functionality to input
// 输入映射
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 将玩家输入映射,转化为增强输入玩家映射  判断是否有输入映射
	// CastChecked<UEnhancedInputComponent>(PlayerInputComponent):将PlayerInputComponent转化为UEnhancedInputComponent
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定映射  MoveAction:映射变量,ETriggerEvent::Trigger:方式,this:对象,&AMyCharacter::Move绑定的函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
	}
}

// 移动
void AMyCharacter::Move(const FInputActionValue& Value)
{
	// 用2D变量来控制移动   Value.Get<FVector2D>():获得输入的2D向量坐标
	FVector2D MovementVector = Value.Get<FVector2D>();
	// 判断控制器是否合法
	if (Controller != nullptr) 
	{
		// 通过变量的旋转获得他的前向向量和右向向量,来控制前后左右移动
		// Controller->GetControlRotation():获得控制器的旋转
		const FRotator Rotation = Controller->GetControlRotation();
		// 获取Yaw方向上的旋转
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		// 获得前向向量(X轴方向)
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		// 获得右向向量(Y轴方向)
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// 在键盘按下WASD时,添加输入
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 旋转
void AMyCharacter::Look(const FInputActionValue& Value)
{
	// 获得鼠标X和Y轴的向量
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	// 判断控制器是否合法,即是否有输入
	if (Controller != nullptr)
	{
		// 获得X轴方向的向量值
		AddControllerYawInput(LookAxisVector.X);
		// 获得Y轴方向的向量值
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

// 接口相关
void AMyCharacter::Attack()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Attack"));
}

void AMyCharacter::CalculateHealth()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("CalculateHealth"));
}

// 定时器相关
void AMyCharacter::PrintF()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Time"));
}


float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EnvenInstigator, AActor* DamageCauser)
{
	UMyHealthWidget* MyWidget = Cast<UMyHealthWidget>(MyWidgetHealth->GetUserWidgetObject());
	// 判断MyWidget是否合法
	if (MyWidget)
	{
		// 判断血量值
		if (MyWidget->CurrentHealth <= 0)
		{
			// 游戏结束
			return 0;
		}
		MyWidget->CurrentHealth -= 5.0f;
	}
	return 0.0f;
}
  1. 在MyCharacter.h中:
#pragma once

#include "CoreMinimal.h"
// 输入映射 value值
#include "InputActionValue.h"
//增强输入头文件
#include "EnhancedInputComponent.h"
// 增强输入子系统
#include "EnhancedInputSubsystems.h"
// 控制器头文件
#include "GameFramework/Controller.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.h"
// 运动组件头文件
#include "GameFramework/CharacterMovementComponent.h"
// 接口
#include "MyInterface.h"
// 定时器头文件
#include "TimerManager.h"
// WidgetCompontent头文件
#include "Components/WidgetComponent.h"
#include "MyHealthWidget.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 声明相机变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 声明输入映射相关变量
	// 输入映射上下文
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputMappingContext* DefaultMappingContext;
	// 移动映射输入变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* MoveAction;
	// 旋转映射变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* LookAction;

	// 鼠标按下移动时的代理绑定函数
	// FInputActionValue:鼠标按下和键盘按下时会有值输入
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);

	// 重写两个接口函数
	virtual void Attack()override;
	virtual void CalculateHealth()override;

	// 声明一个定时器变量
	FTimerHandle Time;
	// 声明一个函数用来方便定时器打印内容
	void PrintF();

	// 声明一个Widget类型变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UWidgetComponent* MyWidgetHealth;

	// 重写受到伤害的函数
	// 参数:接收到的伤害值,
	virtual float TakeDamage(float DamageAmount,struct FDamageEvent const &DamageEvent,class AController* EnvenInstigator,AActor* DamageCauser)override;

	// 射线检测起始位置
	FVector StartLocation;
	// 射线检测向前向量
	FVector ForwardVector;
	// 射线检测的最终位置
	FVector EndLocation;
	// 结构体,射线检测击中物体后返回的一些数据
	FHitResult HitResult;
};
  1. 编译运行,打开UE,在Content里新建一个蓝图类Actor,命名为BP_HitActor,将Pawn的自动控制玩家设置为玩家0,将BP_HitActor和人物都拖拽到场景中,运行,移动人物,靠近P_HitActor,发现P_HitActor和地面都能检测出来
    UEC++109

根据对象查询检测

  1. 在MyCharacter.cpp中:
#include "MyCharacter.h"

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

	// 初始化
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));

	// 初始化控件
	MyWidgetHealth = CreateDefaultSubobject<UWidgetComponent>(TEXT("MyWidgetComponent"));
	// 将控件设置为默认根组件的组件
	MyWidgetHealth->SetupAttachment(RootComponent);
	static ConstructorHelpers::FClassFinder<UUserWidget>WidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Health.UMG_Health_C'"));
	MyWidgetHealth->SetWidgetClass(WidgetClass.Class);
	MyWidgetHealth->SetRelativeLocation(FVector(0, 0, 100));
	// 设置其渲染方式,渲染到屏幕上或渲染到世界当中
	MyWidgetHealth->SetWidgetSpace(EWidgetSpace::Screen);
	// 设置位置大小
	MyWidgetHealth->SetDrawSize(FVector2D(400, 20));

	// 设置摄像机摇臂长度
	MySpringArm->TargetArmLength = 400.0f;

	// 设置根组件  将相机设置为摄像机摇臂的组件
	MyCamera->SetupAttachment(MySpringArm);
	// 将摄像机摇臂设置为默认根组件的组件
	MySpringArm->SetupAttachment(RootComponent);

	// 让控制器的旋转不影响角色的转动,只影响摄像机的转动
	bUseControllerRotationPitch = false;// 设置Pitch旋转
	bUseControllerRotationRoll = false;// 设置Roll旋转
	bUseControllerRotationYaw = false;// 设置Yaw旋转

	// 让角色面朝加速度的方向
	GetCharacterMovement()->bOrientRotationToMovement = true;
	// 使用Pawn控制器的旋转
	MySpringArm->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayController = Cast<APlayerController>(Controller))// 判断控制器是否合法
	{
		// 判断玩家子系统   获取增强输入的本地玩家子系统  将本地控制器转化为增强输入本地玩家子系统
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayController->GetLocalPlayer()))
		{
			// 转化成功  添加映射输入上下文   0为优先级,数值越大,优先级越高
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}

	// 调用接口函数
	Attack();
	CalculateHealth();

	// 设置定时器   参数:时间句柄,对象,绑定的函数,计时间隔,是否循环
	GetWorld()->GetTimerManager().SetTimer(Time, this, &AMyCharacter::PrintF, 1.0, true);
	
	// 清除定时器    加上此段以后,就没有输出了
	if (Time.IsValid()) // 时间合法的话
	{
		GetWorld()->GetTimerManager().ClearTimer(Time);
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 执行射线检测
	StartLocation = MyCamera->GetComponentLocation();
	ForwardVector = MyCamera->GetForwardVector();
	EndLocation = StartLocation + ForwardVector * 9999;
	
	// 根据对象查询检测   参数:检测到后返回的结果,射线检测的起始点,射线检测的终止点,通道名称
	FCollisionObjectQueryParams objectType;// 用于添加通道
	objectType.AddObjectTypesToQuery(ECC_WorldDynamic);// 添加通道ECC_WorldDynamic,可继续添加多个
	bool bHit2 = GetWorld()->LineTraceSingleByObjectType(HitResult, StartLocation, EndLocation, objectType);
	if (bHit2)//判断是否检测到了东西
	{
		AActor* HitActor2 = HitResult.GetActor();
		// 击中这个点的位置
		FVector ImpactPoint2 = HitResult.ImpactPoint;
		// 击中的位置
		FVector HitLocation2 = HitResult.Location;
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("%s"), *HitActor2->GetName()));
	}
}

// Called to bind functionality to input
// 输入映射
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 将玩家输入映射,转化为增强输入玩家映射  判断是否有输入映射
	// CastChecked<UEnhancedInputComponent>(PlayerInputComponent):将PlayerInputComponent转化为UEnhancedInputComponent
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定映射  MoveAction:映射变量,ETriggerEvent::Trigger:方式,this:对象,&AMyCharacter::Move绑定的函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
	}
}

// 移动
void AMyCharacter::Move(const FInputActionValue& Value)
{
	// 用2D变量来控制移动   Value.Get<FVector2D>():获得输入的2D向量坐标
	FVector2D MovementVector = Value.Get<FVector2D>();
	// 判断控制器是否合法
	if (Controller != nullptr) 
	{
		// 通过变量的旋转获得他的前向向量和右向向量,来控制前后左右移动
		// Controller->GetControlRotation():获得控制器的旋转
		const FRotator Rotation = Controller->GetControlRotation();
		// 获取Yaw方向上的旋转
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		// 获得前向向量(X轴方向)
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		// 获得右向向量(Y轴方向)
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// 在键盘按下WASD时,添加输入
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 旋转
void AMyCharacter::Look(const FInputActionValue& Value)
{
	// 获得鼠标X和Y轴的向量
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	// 判断控制器是否合法,即是否有输入
	if (Controller != nullptr)
	{
		// 获得X轴方向的向量值
		AddControllerYawInput(LookAxisVector.X);
		// 获得Y轴方向的向量值
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

// 接口相关
void AMyCharacter::Attack()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Attack"));
}

void AMyCharacter::CalculateHealth()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("CalculateHealth"));
}

// 定时器相关
void AMyCharacter::PrintF()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Time"));
}


float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EnvenInstigator, AActor* DamageCauser)
{
	UMyHealthWidget* MyWidget = Cast<UMyHealthWidget>(MyWidgetHealth->GetUserWidgetObject());
	// 判断MyWidget是否合法
	if (MyWidget)
	{
		// 判断血量值
		if (MyWidget->CurrentHealth <= 0)
		{
			// 游戏结束
			return 0;
		}
		MyWidget->CurrentHealth -= 5.0f;
	}
	return 0.0f;
}
  1. 在MyCharacter.h中:
#pragma once

#include "CoreMinimal.h"
// 输入映射 value值
#include "InputActionValue.h"
//增强输入头文件
#include "EnhancedInputComponent.h"
// 增强输入子系统
#include "EnhancedInputSubsystems.h"
// 控制器头文件
#include "GameFramework/Controller.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.h"
// 运动组件头文件
#include "GameFramework/CharacterMovementComponent.h"
// 接口
#include "MyInterface.h"
// 定时器头文件
#include "TimerManager.h"
// WidgetCompontent头文件
#include "Components/WidgetComponent.h"
#include "MyHealthWidget.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 声明相机变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 声明输入映射相关变量
	// 输入映射上下文
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputMappingContext* DefaultMappingContext;
	// 移动映射输入变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* MoveAction;
	// 旋转映射变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* LookAction;

	// 鼠标按下移动时的代理绑定函数
	// FInputActionValue:鼠标按下和键盘按下时会有值输入
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);

	// 重写两个接口函数
	virtual void Attack()override;
	virtual void CalculateHealth()override;

	// 声明一个定时器变量
	FTimerHandle Time;
	// 声明一个函数用来方便定时器打印内容
	void PrintF();

	// 声明一个Widget类型变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UWidgetComponent* MyWidgetHealth;

	// 重写受到伤害的函数
	// 参数:接收到的伤害值,
	virtual float TakeDamage(float DamageAmount,struct FDamageEvent const &DamageEvent,class AController* EnvenInstigator,AActor* DamageCauser)override;

	// 射线检测起始位置
	FVector StartLocation;
	// 射线检测向前向量
	FVector ForwardVector;
	// 射线检测的最终位置
	FVector EndLocation;
	// 结构体,射线检测击中物体后返回的一些数据
	FHitResult HitResult;
};
  1. 编译运行,打开UE,在Content里新建一个蓝图类Actor,命名为BP_HitActor2,在其中加一个Cube,类型为WorldDynamic,将Pawn的自动控制玩家设置为玩家0,将BP_HitActor2和人物都拖拽到场景中,运行,移动人物,靠近BP_HitActor2,只能检测到指定的对象类型
    UEC++110

多射线通道检测

  1. 在MyCharacter.cpp中:
#include "MyCharacter.h"

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

	// 初始化
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));

	// 初始化控件
	MyWidgetHealth = CreateDefaultSubobject<UWidgetComponent>(TEXT("MyWidgetComponent"));
	// 将控件设置为默认根组件的组件
	MyWidgetHealth->SetupAttachment(RootComponent);
	static ConstructorHelpers::FClassFinder<UUserWidget>WidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Health.UMG_Health_C'"));
	MyWidgetHealth->SetWidgetClass(WidgetClass.Class);
	MyWidgetHealth->SetRelativeLocation(FVector(0, 0, 100));
	// 设置其渲染方式,渲染到屏幕上或渲染到世界当中
	MyWidgetHealth->SetWidgetSpace(EWidgetSpace::Screen);
	// 设置位置大小
	MyWidgetHealth->SetDrawSize(FVector2D(400, 20));

	// 设置摄像机摇臂长度
	MySpringArm->TargetArmLength = 400.0f;

	// 设置根组件  将相机设置为摄像机摇臂的组件
	MyCamera->SetupAttachment(MySpringArm);
	// 将摄像机摇臂设置为默认根组件的组件
	MySpringArm->SetupAttachment(RootComponent);

	// 让控制器的旋转不影响角色的转动,只影响摄像机的转动
	bUseControllerRotationPitch = false;// 设置Pitch旋转
	bUseControllerRotationRoll = false;// 设置Roll旋转
	bUseControllerRotationYaw = false;// 设置Yaw旋转

	// 让角色面朝加速度的方向
	GetCharacterMovement()->bOrientRotationToMovement = true;
	// 使用Pawn控制器的旋转
	MySpringArm->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayController = Cast<APlayerController>(Controller))// 判断控制器是否合法
	{
		// 判断玩家子系统   获取增强输入的本地玩家子系统  将本地控制器转化为增强输入本地玩家子系统
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayController->GetLocalPlayer()))
		{
			// 转化成功  添加映射输入上下文   0为优先级,数值越大,优先级越高
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}

	// 调用接口函数
	Attack();
	CalculateHealth();

	// 设置定时器   参数:时间句柄,对象,绑定的函数,计时间隔,是否循环
	GetWorld()->GetTimerManager().SetTimer(Time, this, &AMyCharacter::PrintF, 1.0, true);
	
	// 清除定时器    加上此段以后,就没有输出了
	if (Time.IsValid()) // 时间合法的话
	{
		GetWorld()->GetTimerManager().ClearTimer(Time);
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 执行射线检测
	StartLocation = MyCamera->GetComponentLocation();
	ForwardVector = MyCamera->GetForwardVector();
	EndLocation = StartLocation + ForwardVector * 9999;
	
	// 多射线通道检测   参数:返回的结果,射线检测的起始点,射线检测的终止点,通道检测的对象
	bool HitMulti = GetWorld()->LineTraceMultiByChannel(HitResults, StartLocation, EndLocation, ECC_Visibility);
	if (HitMulti)//判断是否检测到了东西
	{
		for (int32 i = 0; i < HitResults.Num(); i++)
		{
			AActor* HitMultiActor = HitResults[i].GetActor();
			FVector HitLocation = HitResults[i].Location;
			FVector HitImpactPoint2 = HitResults[i].ImpactPoint;

			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("%s"), *HitMultiActor->GetName()));
		}
	}
}

// Called to bind functionality to input
// 输入映射
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 将玩家输入映射,转化为增强输入玩家映射  判断是否有输入映射
	// CastChecked<UEnhancedInputComponent>(PlayerInputComponent):将PlayerInputComponent转化为UEnhancedInputComponent
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定映射  MoveAction:映射变量,ETriggerEvent::Trigger:方式,this:对象,&AMyCharacter::Move绑定的函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
	}
}

// 移动
void AMyCharacter::Move(const FInputActionValue& Value)
{
	// 用2D变量来控制移动   Value.Get<FVector2D>():获得输入的2D向量坐标
	FVector2D MovementVector = Value.Get<FVector2D>();
	// 判断控制器是否合法
	if (Controller != nullptr) 
	{
		// 通过变量的旋转获得他的前向向量和右向向量,来控制前后左右移动
		// Controller->GetControlRotation():获得控制器的旋转
		const FRotator Rotation = Controller->GetControlRotation();
		// 获取Yaw方向上的旋转
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		// 获得前向向量(X轴方向)
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		// 获得右向向量(Y轴方向)
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// 在键盘按下WASD时,添加输入
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 旋转
void AMyCharacter::Look(const FInputActionValue& Value)
{
	// 获得鼠标X和Y轴的向量
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	// 判断控制器是否合法,即是否有输入
	if (Controller != nullptr)
	{
		// 获得X轴方向的向量值
		AddControllerYawInput(LookAxisVector.X);
		// 获得Y轴方向的向量值
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

// 接口相关
void AMyCharacter::Attack()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Attack"));
}

void AMyCharacter::CalculateHealth()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("CalculateHealth"));
}

// 定时器相关
void AMyCharacter::PrintF()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Time"));
}


float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EnvenInstigator, AActor* DamageCauser)
{
	UMyHealthWidget* MyWidget = Cast<UMyHealthWidget>(MyWidgetHealth->GetUserWidgetObject());
	// 判断MyWidget是否合法
	if (MyWidget)
	{
		// 判断血量值
		if (MyWidget->CurrentHealth <= 0)
		{
			// 游戏结束
			return 0;
		}
		MyWidget->CurrentHealth -= 5.0f;
	}
	return 0.0f;
}
  1. 在MyCharacter.h中:
#pragma once

#include "CoreMinimal.h"
// 输入映射 value值
#include "InputActionValue.h"
//增强输入头文件
#include "EnhancedInputComponent.h"
// 增强输入子系统
#include "EnhancedInputSubsystems.h"
// 控制器头文件
#include "GameFramework/Controller.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.h"
// 运动组件头文件
#include "GameFramework/CharacterMovementComponent.h"
// 接口
#include "MyInterface.h"
// 定时器头文件
#include "TimerManager.h"
// WidgetCompontent头文件
#include "Components/WidgetComponent.h"
#include "MyHealthWidget.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 声明相机变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 声明输入映射相关变量
	// 输入映射上下文
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputMappingContext* DefaultMappingContext;
	// 移动映射输入变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* MoveAction;
	// 旋转映射变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* LookAction;

	// 鼠标按下移动时的代理绑定函数
	// FInputActionValue:鼠标按下和键盘按下时会有值输入
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);

	// 重写两个接口函数
	virtual void Attack()override;
	virtual void CalculateHealth()override;

	// 声明一个定时器变量
	FTimerHandle Time;
	// 声明一个函数用来方便定时器打印内容
	void PrintF();

	// 声明一个Widget类型变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UWidgetComponent* MyWidgetHealth;

	// 重写受到伤害的函数
	// 参数:接收到的伤害值,
	virtual float TakeDamage(float DamageAmount,struct FDamageEvent const &DamageEvent,class AController* EnvenInstigator,AActor* DamageCauser)override;

	// 射线检测起始位置
	FVector StartLocation;
	// 射线检测向前向量
	FVector ForwardVector;
	// 射线检测的最终位置
	FVector EndLocation;
	// 结构体,射线检测击中物体后返回的一些数据
	FHitResult HitResult;
	// 接收多射线反射返回的数组
	TArray<FHitResult> HitResults;
};
  1. 编译运行,打开UE,在Content里新建一个蓝图类Actor,命名为BP_HitActor,在其中加一个Cube,类型为WorldDynamic,将Pawn的自动控制玩家设置为玩家0,将BP_HitActor和人物都拖拽到场景中,运行,移动人物,靠近BP_HitActor:
    UEC++111

多射线对象检测

  1. 在MyCharacter.cpp中:
#include "MyCharacter.h"

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

	// 初始化
	MySpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("MySpringArmComponent"));
	MyCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("MyCameraComponent"));

	// 初始化控件
	MyWidgetHealth = CreateDefaultSubobject<UWidgetComponent>(TEXT("MyWidgetComponent"));
	// 将控件设置为默认根组件的组件
	MyWidgetHealth->SetupAttachment(RootComponent);
	static ConstructorHelpers::FClassFinder<UUserWidget>WidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG_Health.UMG_Health_C'"));
	MyWidgetHealth->SetWidgetClass(WidgetClass.Class);
	MyWidgetHealth->SetRelativeLocation(FVector(0, 0, 100));
	// 设置其渲染方式,渲染到屏幕上或渲染到世界当中
	MyWidgetHealth->SetWidgetSpace(EWidgetSpace::Screen);
	// 设置位置大小
	MyWidgetHealth->SetDrawSize(FVector2D(400, 20));

	// 设置摄像机摇臂长度
	MySpringArm->TargetArmLength = 400.0f;

	// 设置根组件  将相机设置为摄像机摇臂的组件
	MyCamera->SetupAttachment(MySpringArm);
	// 将摄像机摇臂设置为默认根组件的组件
	MySpringArm->SetupAttachment(RootComponent);

	// 让控制器的旋转不影响角色的转动,只影响摄像机的转动
	bUseControllerRotationPitch = false;// 设置Pitch旋转
	bUseControllerRotationRoll = false;// 设置Roll旋转
	bUseControllerRotationYaw = false;// 设置Yaw旋转

	// 让角色面朝加速度的方向
	GetCharacterMovement()->bOrientRotationToMovement = true;
	// 使用Pawn控制器的旋转
	MySpringArm->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayController = Cast<APlayerController>(Controller))// 判断控制器是否合法
	{
		// 判断玩家子系统   获取增强输入的本地玩家子系统  将本地控制器转化为增强输入本地玩家子系统
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayController->GetLocalPlayer()))
		{
			// 转化成功  添加映射输入上下文   0为优先级,数值越大,优先级越高
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}

	// 调用接口函数
	Attack();
	CalculateHealth();

	// 设置定时器   参数:时间句柄,对象,绑定的函数,计时间隔,是否循环
	GetWorld()->GetTimerManager().SetTimer(Time, this, &AMyCharacter::PrintF, 1.0, true);
	
	// 清除定时器    加上此段以后,就没有输出了
	if (Time.IsValid()) // 时间合法的话
	{
		GetWorld()->GetTimerManager().ClearTimer(Time);
	}
}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 执行射线检测
	StartLocation = MyCamera->GetComponentLocation();
	ForwardVector = MyCamera->GetForwardVector();
	EndLocation = StartLocation + ForwardVector * 9999;
	// 多射线对象查询检测
	FCollisionObjectQueryParams objectType;// 用于添加通道
	objectType.AddObjectTypesToQuery(ECC_WorldDynamic);// 添加通道ECC_WorldDynamic,可继续添加多个
	objectType.AddObjectTypesToQuery(ECC_WorldStatic);
	
	bool HitMulti2 = GetWorld()->LineTraceMultiByObjectType(HitResults, StartLocation, EndLocation, objectType);
	if (HitMulti2)//判断是否检测到了东西
	{
		for (int32 i = 0; i < HitResults.Num(); i++)
		{
			AActor* HitMultiActor2 = HitResults[i].GetActor();
			FVector HitLocation2 = HitResults[i].Location;
			FVector HitImpactPoint2 = HitResults[i].ImpactPoint;

			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("%s"), *HitMultiActor2->GetName()));
		}
	}
}

// Called to bind functionality to input
// 输入映射
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 将玩家输入映射,转化为增强输入玩家映射  判断是否有输入映射
	// CastChecked<UEnhancedInputComponent>(PlayerInputComponent):将PlayerInputComponent转化为UEnhancedInputComponent
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定映射  MoveAction:映射变量,ETriggerEvent::Trigger:方式,this:对象,&AMyCharacter::Move绑定的函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
	}
}

// 移动
void AMyCharacter::Move(const FInputActionValue& Value)
{
	// 用2D变量来控制移动   Value.Get<FVector2D>():获得输入的2D向量坐标
	FVector2D MovementVector = Value.Get<FVector2D>();
	// 判断控制器是否合法
	if (Controller != nullptr) 
	{
		// 通过变量的旋转获得他的前向向量和右向向量,来控制前后左右移动
		// Controller->GetControlRotation():获得控制器的旋转
		const FRotator Rotation = Controller->GetControlRotation();
		// 获取Yaw方向上的旋转
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		// 获得前向向量(X轴方向)
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		// 获得右向向量(Y轴方向)
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// 在键盘按下WASD时,添加输入
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 旋转
void AMyCharacter::Look(const FInputActionValue& Value)
{
	// 获得鼠标X和Y轴的向量
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	// 判断控制器是否合法,即是否有输入
	if (Controller != nullptr)
	{
		// 获得X轴方向的向量值
		AddControllerYawInput(LookAxisVector.X);
		// 获得Y轴方向的向量值
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

// 接口相关
void AMyCharacter::Attack()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Attack"));
}

void AMyCharacter::CalculateHealth()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("CalculateHealth"));
}

// 定时器相关
void AMyCharacter::PrintF()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Time"));
}


float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EnvenInstigator, AActor* DamageCauser)
{
	UMyHealthWidget* MyWidget = Cast<UMyHealthWidget>(MyWidgetHealth->GetUserWidgetObject());
	// 判断MyWidget是否合法
	if (MyWidget)
	{
		// 判断血量值
		if (MyWidget->CurrentHealth <= 0)
		{
			// 游戏结束
			return 0;
		}
		MyWidget->CurrentHealth -= 5.0f;
	}
	return 0.0f;
}
  1. 在MyCharacter.h中:
#pragma once

#include "CoreMinimal.h"
// 输入映射 value值
#include "InputActionValue.h"
//增强输入头文件
#include "EnhancedInputComponent.h"
// 增强输入子系统
#include "EnhancedInputSubsystems.h"
// 控制器头文件
#include "GameFramework/Controller.h"
// 摄像机摇臂头文件
#include "GameFramework/SpringArmComponent.h"
// 相机头文件
#include "Camera/CameraComponent.h"
// 运动组件头文件
#include "GameFramework/CharacterMovementComponent.h"
// 接口
#include "MyInterface.h"
// 定时器头文件
#include "TimerManager.h"
// WidgetCompontent头文件
#include "Components/WidgetComponent.h"
#include "MyHealthWidget.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()// ,public IMyInterface:继承自接口
class UESTUDY_API AMyCharacter : public ACharacter, public IMyInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

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(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		USpringArmComponent* MySpringArm;
	// 声明相机变量
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UCameraComponent* MyCamera;
	// 声明输入映射相关变量
	// 输入映射上下文
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputMappingContext* DefaultMappingContext;
	// 移动映射输入变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* MoveAction;
	// 旋转映射变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
		class UInputAction* LookAction;

	// 鼠标按下移动时的代理绑定函数
	// FInputActionValue:鼠标按下和键盘按下时会有值输入
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);

	// 重写两个接口函数
	virtual void Attack()override;
	virtual void CalculateHealth()override;

	// 声明一个定时器变量
	FTimerHandle Time;
	// 声明一个函数用来方便定时器打印内容
	void PrintF();

	// 声明一个Widget类型变量
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "mySceneComponent")
		UWidgetComponent* MyWidgetHealth;

	// 重写受到伤害的函数
	// 参数:接收到的伤害值,
	virtual float TakeDamage(float DamageAmount,struct FDamageEvent const &DamageEvent,class AController* EnvenInstigator,AActor* DamageCauser)override;

	// 射线检测起始位置
	FVector StartLocation;
	// 射线检测向前向量
	FVector ForwardVector;
	// 射线检测的最终位置
	FVector EndLocation;
	// 结构体,射线检测击中物体后返回的一些数据
	FHitResult HitResult;
	// 接收多射线反射返回的数组
	TArray<FHitResult> HitResults;
};
  1. 编译运行,打开UE,在Content里新建一个蓝图类Actor,命名为BP_HitActor,在其中加一个Cube,类型为WorldDynamic,将Pawn的自动控制玩家设置为玩家0,将BP_HitActor和人物都拖拽到场景中,运行,移动人物,靠近BP_HitActor,可以同时检测到世界动态和世界静态的物体
    UEC++112

虚幻C++软引用

引用:分为软引用和硬引用
软引用:通常是仅储存资源对象的资源路径没有与资源产生耦合关系的引用(软引用加载到内存中,引用对象不会被加载到内存中,只有在需要的时候才会被加载进内存中)。软引用指向的资源未被加载,仅仅是提供了一个路径,通过路径找到对应的资源
硬引用:是拥有资源对象实际成员变量,直接与资源对象产生耦合(硬引用被加载到内存中,则被引用的对象资源也会被加载到内存中)

常用软引用

  1. FSoftObjectPath
  2. FSoftClassPath
  3. TSoftObjectPtr
  4. TSoftClassPtr

软引用的使用

  1. 在UE中创建一个C++类Actor,命名为:MySoftActor
  2. 在MySoftActor.h中
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MySoftActor.generated.h"

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

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

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

	// 软引用  FSoftObjectPath
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Path")
		FSoftObjectPath AssetObjectPath;
	// 软引用  FSoftClassPath
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Path")
		FSoftClassPath AssetClassPath;
	// 软引用  TSoftObjectPtr  可引用Actor对象
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Path")
		TSoftObjectPtr<AActor> AssetObjectPtr;
	// 软引用
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Path")
		TSoftClassPtr<AActor> AssetClassPtr;
};
  1. 编译运行,创建基于MySoftActor的蓝图类,命名为BP_MySoftActor,将其拖入场景中,查看右部细节面板是否设置成功
    UEC++113

虚幻C++同步和异步加载资源

  1. 在以上基础上,在MySoftActor.cpp中添加代码:
#include "MySoftActor.h"
// 资源管理头文件  
#include "Engine/AssetManager.h"

// Sets default values
AMySoftActor::AMySoftActor()
{
 	// Set this actor 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 AMySoftActor::BeginPlay()
{
	Super::BeginPlay();

	// 同步加载资源
	// 用于加载资源路径的变量
	FSoftObjectPath Path1 = TEXT("/Script/Engine.Texture2D'/Game/StarterContent/Textures/T_Brick_Clay_Beveled_D.T_Brick_Clay_Beveled_D'");
	// 智能指针
	TSharedPtr<FStreamableHandle> SyncStreamHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(Path1);
	if (SyncStreamHandle)// 判断是否已经找到了内存
	{
		UTexture2D* Image1 = Cast<UTexture2D>(SyncStreamHandle->GetLoadedAsset());
		if (Image1)// 判断图片是否加载成功
		{
			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("%s"),*Image1->GetName()));
		}
	}
	
	// 异步加载资源
	FSoftObjectPath Path2 = TEXT("/Script/Engine.Texture2D'/Game/StarterContent/Textures/T_Brick_Clay_Beveled_M.T_Brick_Clay_Beveled_M'");
	// 智能指针
	TSharedPtr<FStreamableHandle> ASyncStreamHandle = UAssetManager::GetStreamableManager().RequestSyncLoad(Path2);
	if (ASyncStreamHandle)// 判断是否已经找到了内存
	{
		UTexture2D* Image2 = Cast<UTexture2D>(ASyncStreamHandle->GetLoadedAsset());
		if (Image2)// 判断图片是否加载成功
		{
			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("%s"), *Image2->GetName()));
		}
	}
}

// Called every frame
void AMySoftActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
  1. 编译运行,打开UE,创建基于MySoftActor的蓝图类,将其拖入场景中,运行:
    UEC++114

虚幻C++智能指针

为什么引入智能指针:是为了方便程序员管理堆内存而提出来的,核心思想是:程序员只管分配堆内存,释放内存交给智能指针。UE有自己的一套内存管理系统,但只针对与UObjet,为了方便对非Object对象的内存管理,UE设计了一套自己的智能指针库。包含:共享指针(TSharePtr)、弱指针(TWeakPtr)、以及特有的共享引用(TShareRef)。

使用智能指针的好处

  • std不能全平台通用
  • 允许在所有编译器和平台上更一致的实现
  • 可以和UE其他容器无缝衔接
  • 更好的控制平台细节,包括线程和优化
  • 希望线程安全特性是可选的(为了性能)
  • 添加自己的改进(MakeShareable,assign to nullptr等)
  • 不需要也不希望出现异常
  • 希望对性能有更好的控制(内联、内存、虚拟的使用等)
  • 更容易调试(自由代码注释等)
  • 不需要的时候不要引入新的第三方依赖项

共享指针

  1. 打开UE,创建一个C++类Actor,命名为MySmartPtrActor
  2. MySmartPtrActor.h文件:
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MySmartPtrActor.generated.h"

// 声明一个原生的C++类
class TestA
{
public:
	int a = 0;
	float b = 0;
	TestA()//构造函数
	{
		// 初始化变量
		a = 0;
		b = 0;
	};
	TestA(int a, float b)// 带有两个参数的构造函数
	{
		this->a = a;
		this->b = b;
	};
	~TestA()// 析构函数
	{
		UE_LOG(LogTemp, Warning, TEXT("xigou"));
	};

private:

};


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

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

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

	// 使用原生C++类  测试共享指针
	void TestAFunc();
};
  1. MySmartPtrActor.cpp文件:
#include "MySmartPtrActor.h"

// Sets default values
AMySmartPtrActor::AMySmartPtrActor()
{
 	// Set this actor 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 AMySmartPtrActor::BeginPlay()
{
	Super::BeginPlay();

	// 调用函数
	TestAFunc();
}

// Called every frame
void AMySmartPtrActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 使用原生C++类
void AMySmartPtrActor::TestAFunc()
{
	// 用C++的方式创建一个原生指针
	TestA* ptr1 = new TestA(1, 2.0f);
	// 用共享指针左值初始化
	TSharedPtr<TestA>SharedPtr2(ptr1);// 不推荐使用此方式,容易混淆共享指针和原生指针
	ptr1 = nullptr;
	// 用共享指针右值初始化
	TSharedPtr<TestA>SharedPtr3(new TestA(3, 4.0f));
	// 拷贝构造函数初始化   用共享指针初始化共享指针
	TSharedPtr<TestA>SharedPtr4(SharedPtr2);// 调用拷贝构造函数以初始化共享指针
	// UE的方法初始化共享指针
	TSharedPtr<TestA>SharedPtr5 = nullptr;// 共享指针可以初始化为空
	SharedPtr5 = MakeShareable(new TestA(5, 6.0f));
	// 线程安全的共享指针
	TSharedPtr<TestA, ESPMode::ThreadSafe>SharedPtr6(new TestA(7, 8.0f));

	//共享指针常用的接口
	if (SharedPtr5.IsValid())// 判断共享指针是否指向了一个有效的对象
	{
		// 声明一个共享引用
		TSharedRef<TestA>SharedRef1(new TestA(9, 10.0f));// 共享引用必须执行一个对象,不能为空
		// 将共享指针转化为共享引用   引用计数加一
		SharedRef1 = SharedPtr5.ToSharedRef();
		// 获得共享引用的引用计数
		int32 Count1 = SharedPtr5.GetSharedReferenceCount();
		UE_LOG(LogTemp, Warning, TEXT("Count1 is %d"), Count1);
		if (!SharedPtr5.IsUnique())//判断其是不是唯一的共享指针,也就是引用计数是否为1
		{
			UE_LOG(LogTemp, Warning, TEXT("SharedPtr5 is not Unique"));
		}
		// 解引用,就是返回对象的原生指针   解引用:共享引用的名称.Get()
		SharedPtr5.Get()->a;
		UE_LOG(LogTemp, Warning, TEXT("SharedPtr5a is %d"), SharedPtr5.Get()->a);
		// 重置共享指针,即把共享指针变为null 引用计数会变为0
		SharedPtr5.Reset();
		int32 Count2 = SharedPtr5.GetSharedReferenceCount();
		UE_LOG(LogTemp, Warning, TEXT("Count2 is %d"), Count2);
	}
}
  1. 编译运行,将MySmartPtrActor拖入场景中,运行,查看输出日志:
    UEC++115

共享引用

  1. MySmartPtrActor.h文件:
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MySmartPtrActor.generated.h"

// 声明一个原生的C++类
class TestA
{
public:
	int a = 0;
	float b = 0;
	TestA()//构造函数
	{
		// 初始化变量
		a = 0;
		b = 0;
	};
	TestA(int a, float b)// 带有两个参数的构造函数
	{
		this->a = a;
		this->b = b;
	};
	~TestA()// 析构函数
	{
		UE_LOG(LogTemp, Warning, TEXT("xigou"));
	};

private:

};


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

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

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

	// 测试共享引用
	void TestBFunc();
};
  1. MySmartPtrActor.cpp文件:
#include "MySmartPtrActor.h"

// Sets default values
AMySmartPtrActor::AMySmartPtrActor()
{
 	// Set this actor 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 AMySmartPtrActor::BeginPlay()
{
	Super::BeginPlay();

	// 调用函数
	TestBFunc();
}

// Called every frame
void AMySmartPtrActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 测试共享引用
void AMySmartPtrActor::TestBFunc()
{
	// 共享引用初始化的时候必须指向一个有效的对象
	TSharedRef<TestA> SharedRef2(new TestA(7, 8.0f));
	// 共享引用常用的接口
	if (SharedRef2.IsUnique())//判断其是不是唯一的共享引用
	{
		// 解引用
		SharedRef2->a;// 虽然是共享引用,但是解引用的方式仍然是->
		UE_LOG(LogTemp, Warning, TEXT("SharedRef2a is %d"), SharedRef2->a);
		// 共享引用转化为共享指针
		TSharedPtr<TestA>SharedPtr6;
		SharedPtr6 = SharedRef2;// 可以直接转化
		SharedPtr6.Get()->b;
		UE_LOG(LogTemp, Warning, TEXT("SharedPtr6b is %f"), SharedPtr6.Get()->b);
	}
}
  1. 编译运行,将MySmartPtrActor拖入场景中,运行,查看输出日志:
    UEC++116

弱指针

弱指针的作用

  1. 解决了循环引用,只对引用对象保留引用权,并不参与引用计数。当我们需要操作引用对象时,需要转换成智能指针。
  2. 不能阻止对象被销毁。如果弱指针指向的对象被销毁,弱指针会自动清空,不需要手动向智能指针一样置空

UE查找Actor的方式

  1. 在蓝图中设置tag属性,通过tag UGameplayStatics::GetAllActorsWithTag
#include "Kismet/GameplayStatics.h"
TArray<AActor*> Actors;
UGameplayStatics::GetAllActorsWithTag(GetWorld(), TEXT("actorName"), Actors);
for (AActor* Actor: Actors)
{
}
  1. 通过 UGameplayStatics::GetAllActorsOfClass 查找
#include "Kismet/GameplayStatics.h"
TArray<AActor*> Actors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), Actors);
 
for (AActor* Actor : Actors)
{
}
  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UE4(Unreal Engine 4)是由Epic Games开发的一款领先的游戏开发引擎。对于初学者来说,UE4为其提供了一系列易于理解和学习的教程和资源。 首先,Epic Games官方网站上有专门针对初学者的入门教程和学习路径。你可以通过他们的学习资源,了解UE4的基础知识、工作流程和常用工具等。这些教程包含了视频教程、文档和范例项目等,可以帮助你快速入门。 其次,Epic Games的学习资源库中有大量的免费教学资源和示例项目。这些资源涵盖了不同类型的游戏开发,如虚幻关卡设计、角色和动画、蓝图脚本编程等。你可以通过这些资源学习到UE4的各个方面,提高自己的技能。 此外,还有许多在线教育平台提供关于UE4的教程和课程。例如,Udemy、Coursera和Pluralsight等平台上都有丰富的UE4开发课程。这些课程由经验丰富的教育者和开发者提供,可以根据自己的需求选择适合的课程进行学习。 最后,UE4社区是一个非常有价值的资源,你可以在其中与其他开发者交流和分享经验。在论坛和社交媒体中,你可以提问、寻找答案,还可以参与讨论和分享你自己的项目。社区不仅可以扩展你的知识,还可以帮助你建立人脉并获得反馈。 总之,UE4开发教程推荐的资源很多,包括官方教程、免费资源、在线课程和社区交流等。通过系统学习和实践,你可以逐步掌握UE4的开发技能,并在游戏开发领域中展开你的创作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值