丧心病狂虚幻C++

  • 天 地 玄 黄 宇 宙 洪 荒
  • 虚幻C++绝对难得毫无廉耻

🔥虚幻物体类型结构

  • 虚幻C++类是一个代码预置,没有外部调整界面,一切设置都由代码完成,是不可视化的
  • 通过虚幻C++类派生的蓝图类,其中的蓝图成员仍然能接收其虚幻C++类修改或变动
  • 通过这样生成的蓝图类,属于虚幻C++类的变种,在变种中可以可视化地分别设置其组件及成员的数据
  • 自定义类物体拥有三种类型:
    • 代码原型:通过C++写成的类,需要在里面定义好其拥有的组件及其父子关系,以及定义默认参数,以及写好功能函数与所有“一般化”功能
    • 蓝图类:通过C++类派生出来的预制体类型,可以派生多个,拥有所有其C++原型对其暴露的参数与函数,拥有为其单个预制体而服务的蓝图脚本区(必须继承自UObject或其派生)以及可视化三维视口区(必须继承自AActor或其派生),拥有“特殊化”的配置能力
    • 实例物体:通过代码原型蓝图类在关卡中实例化出的单个物体(必须继承自AActor或其派生),能够在游戏进程中运行线程周期函数

🔥新建代码原型

  • 示例以AActor类为基类

🚩头文件介绍

GENERATED_BODY() UE默认代码

BeginPlay() 初始化函数

Tick(DeltaTime) 运行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Actor_1类头文件.h --------------------------------------
#pragma once//此指令保证本头文件只被编译一次

#include "CoreMinimal.h"//此引用包含所有UE4新增的数据类型
#include "GameFramework/Actor.h"//此引用包含所有UE4封装好的代码初始继承类
//此引用包含UE4在创立本类时生成的反射机制,注意:此引用必须在所有引用的下面
#include "Actor_1.generated.h"

UCLASS()//此宏用于本类被UE4识别
//class 项目名 类名 : 基类名
class HOW2PLAYWITHUE4_1_API AActor_1 : public AActor//继承自AActor类
{
GENERATED_BODY()//此宏表示UE4替开发者生成的代码

public:
//在此加入游戏物体的初始化参数
AActor_1();//类构造函数声明

protected:
virtual void BeginPlay() override;//游戏初始函数声明

public:
virtual void Tick(float DeltaTime) override;//游戏运行函数声明

};

🚩源文件介绍

PrimaryActorTick.bCanEverTick 是否每帧调用运行函数标识符

Super::BeginPlay() / Tick(DeltaTime) 父类初始化与运行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Actor_1类源文件.cpp --------------------------------------
#include "Actor_1.h"//包含本类的头文件

AActor_1::AActor_1()//构造函数
{
//保证游戏运行时每帧运行Tick函数,设为false可以省资源
PrimaryActorTick.bCanEverTick = true;
}

void AActor_1::BeginPlay()//游戏初始函数实现
{
//Super是父类的类型别名,表示子类重写父类虚函数的多态
Super::BeginPlay();//在运行子类的初始函数之前,需要调用一遍父类的初始函数
}

void AActor_1::Tick(float DeltaTime)//游戏运行函数实现
{
Super::Tick(DeltaTime);//在运行子类的运行函数之前,需要调用一遍父类的运行函数
}

🚩类命名规则

  • 继承或派生自Actor首字母为A,如APawn
  • 继承或派生自Object首字母为U,如USceneComponent
  • 继承或派生自SWidget首字母为S,如SButton
  • 继承或派生自Enums首字母为E,如EInputEvent
  • 继承或派生自Template首字母为T,如TArray
  • 其他闲散类首字母均为F,如FVector

🔥参数属性说明符

🚩宏UCLASS()

  • 使类便于虚幻引擎识别并操控
1
2
3
// 头文件内 -----------------------------------
UCLASS()
class 项目名 类名 : public 基类名{}

🚩宏UFUNCTION()

  • 使成员函数便于虚幻引擎识别并操控

UFUNCTION(BlueprintCallable) 函数:蓝图可调用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 头文件内 -----------------------------------
public:
UFUNCTION()
void 函数名();
// ****************************
//BlueprintCallable表示能在蓝图编辑器中调用
UFUNCTION(BlueprintCallable)
void 函数名();
// ****************************
//BlueprintNativeEvent表示能在蓝图类中重写
UFUNCTION(BlueprintNativeEvent)
void 函数名();
void 函数名_Implementation();

🚩宏UPROPERTY()

  • 使成员属性便于虚幻引擎识别并操控
  • 官网

UPROPERTY(VisibleAnywhere) 属性:所有环境可见

UPROPERTY(EditAnywhere) 属性:所有环境可编辑

UPROPERTY(VisibleDefaultsOnly) 属性:仅蓝图类可见

UPROPERTY(EditDefaultsOnly) 属性:仅蓝图类可编辑

UPROPERTY(VisibleInstanceOnly) 属性:仅实例可见

UPROPERTY(EditInstanceOnly) 属性:仅实例可编辑

UPROPERTY(BlueprintReadOnly) 属性:蓝图类与蓝图只读

UPROPERTY(BlueprintReadWrite) 属性:蓝图类与蓝图可读写

UPROPERTY(EditAnywhere, Category=”Category_1|SubCategory”) 属性:设置组件内分栏

UPROPERTY(EditAnywhere, Meta=(Bitmask, BitmaskEnum=”EColorType”)) 属性:枚举下拉菜单

UPROPERTY(EditAnywhere, Meta=(ClampMin = -1, ClampMax = 1, UIMin = -1, UIMax = 1)) 属性:钳制数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 头文件内 -----------------------------------
public:
//UPROPERTY()宏用来给参数添加属性
// ****************************
//VisibleAnywhere表示在编辑器中显示并只读
UPROPERTY(VisibleAnywhere)
int32 num1 = 0;
// ****************************
//EditAnywhere表示在编辑器中显示并可编辑
UPROPERTY(EditAnywhere)
int32 num2 = 0;
// ****************************
//Category赋值表示可在编辑器中显示分栏,以“|”为多级分栏
UPROPERTY(EditAnywhere, Category="Category_1|SubCategory")
int32 num3 = 0;
// ****************************
//VisibleDefaultsOnly表示只在蓝图编辑器中显示并只读
UPROPERTY(VisibleDefaultsOnly)
int32 num4 = 0;
// ****************************
//EditDefaultsOnly表示在蓝图编辑器中显示并可编辑
UPROPERTY(EditDefaultsOnly)
int32 num4 = 0;
// ****************************
//EditInstanceOnly表示只在蓝图编辑器中显示并只读,当实例化到场景中才可编辑
UPROPERTY(EditInstanceOnly)
int32 num4 = 0;
// ****************************
//EditAnywhere不建议赋给指针组件,不然就没有好果汁吃
UPROPERTY(EditAnywhere)
UStaticMeshComponent* mesh;//不建议!!!
//一:如果能够保证该指针能在游戏开始前被赋值,则可以使用EditAnywhere,比如说固有组件
//二:如果该物体是可动态实例化物体,则不建议,因为虚幻检测到空指针会崩溃
// ****************************
//Meta赋值表示设置位掩码
//如果有一个枚举定义名为EColorType,则可以在编辑器内设置选择下拉框
//UENUM() enum class EColorType { RED, GREEN, YELLOW };
UPROPERTY(EditAnywhere,
Meta=(Bitmask, BitmaskEnum="EColorType"))
EColorType color = EColorType::RED;
// ****************************
//Meta同时也可以对数值做限制
UPROPERTY(EditAnywhere,
Meta=(ClampMin = -500, ClampMax = 500, UIMin = -500, UIMax = 500))
FVector vect = FVector(0, 0, 0);

🔥组件

  • 在虚幻里,任何可以拖入场景里的物件都是Actor,并且继承自AActor
  • 组件Component只能依附于Actor才能存在
  • Actor都是以A开头的,Component都是以U开头的
  • Actor的方位矩阵信息记录在AActor里的RootComponent根组件上,默认Nullptr

🚩物体类添加组件

USceneComponent 场景组件类,是所有可放入场景的组件的基类

UStaticMeshComponent 静态网格组件类

UObject::CreateDefaultSubobject<>() 物体添加组件

RootComponent 物体的根组件

USceneComponent::SetupAttachment() 将组件作为其他组件的子集

FindComponentByClass<class …>() 通过组件类名获取物体内组件的模板类

1
2
3
4
5
6
7
8
9
10
11
12
13
// 头文件内 -----------------------------------
public:
UPROPERTY(VisibleAnywhere, Category = "OBJScene")
USceneComponent* objScene;//场景物体组件声明(必须是指针)
UPROPERTY(VisibleAnywhere, Category = "MyMesh")
UStaticMeshComponent* mesh1;//静态网格组件声明(必须是指针)
// 源文件内构造函数内 ---------------------------
//场景物体组件实现
objScene = CreateDefaultSubobject&lt;USceneComponent&gt;(TEXT("objScene"));
RootComponent = objScene;//设置根组件
//静态网格组件实现
mesh1 = CreateDefaultSubobject&lt;UStaticMeshComponent&gt;(TEXT("myMesh1"));
mesh1->SetupAttachment(objScene);//组件层级附加

🚩组件类

1
2
3
4
5
//在组件类中的操作与物体类差不多一致
//不同点是组件类影响其附着的物体需要借助GetOwner()方法
GetOwner()->SetActorLocation(0, 0, 0);
//像组件使用的函数方法能直接使用
SetupAttachment(...);

🔥测试

🚩打印

GEngine->AddOnScreenDebugMessage()

1
2
//在屏幕上打印一行字
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("HelloWorld!"));

UE_LOG()

1
2
3
4
5
6
7
//在日志窗口打印一行字
UE_LOG(LogTemp, Log, TEXT("HelloWorld"));
UE_LOG(LogTemp, Warning, TEXT("HelloWorld"));
UE_LOG(LogTemp, Error, TEXT("HelloWorld"));
int num = 10;
UE_LOG(LogTemp, Log, TEXT("HelloWorld %i"), *num);
//int = %i, float = %f, string = %s, vector = %v

🚩绘图

DrawDebugLine 画线

DrawDebugPoint 画点

DrawDebugDirectionalArrow 画箭头

DrawDebugBox/Frustum/SolidBox/SolidPlane 画方

DrawDebugSphere/Capsule/Cylinder/Cone 画球

DrawDebugCircle/2DDonut 画面

DrawDebugCoordinateSystem 画坐标系

DrawDebugString 写字

DrawDebugCamera 摄像机

DrawDebugMesh 网格

1
2
//绘图调试需要使用DrawDebugHelpers类
#include "DrawDebugHelpers.h"

🔥基本数值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int32//32位整型
int64//64位整型
float//单精度浮点
double//双精度浮点
bool//布尔是非值
TArray//动态数组
Set//集
Map//映射
FVector//三维向量
FVector2D//二维向量
FRotator//三维旋矩
FQuat//四元数
FTransform//方位量
FColor//颜色量
FString//字符串
FName//微字符串,不区分大小写
FText//长字符串

🔥类型转换

🚩转字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ----------------------------------------
//int
//int32/int64转FString
int32 i32 = 0;
int64 i64 = 0;
FString s = FString::FromInt(i32);
FString s = FString::FromInt(i64);
//FString转int32/int64
FString s = TEXT("");
int32 i = FCString::Atoi(s);
int64 i = FCString::Atoi64(s);
// ----------------------------------------
//float
//float/double转FString
float f = 0.0f;
double d = 0.0;
FString s = FString::SanitizeFloat(f);
FString s = FString::SanitizeFloat(d);
//FString转float/double
FString s = TEXT("");
float f = FCString::Atof(f);
double f = FCString::Atod(f);
// ----------------------------------------
//FName和FText
//FName/FText转FString
FName n = TEXT("");
FText t = TEXT("");
FString s = n.ToString();
FString s = t.ToString();
//FString转FName/FText
FString s = TEXT("");
FName n = FName(*s);
FText t = FText::FromString(s);
//FName转FText(FText不允许转FName)
FName n = TEXT("");
FText t = FText::FromName(n);

🚩整型与浮点

1
2
3
4
5
6
7
//有两种转换方法:
//构造转换
int32 i = 0;
float f = float(i);
//强转
int32 i = 0;
float f = (float)i;

🚩保留小数

1
2
3
//使用int()来强制转换会彻底丢掉小数位,所以要加上0.5,来保证其第一位小数大于5时会进1
float f = 3.14159;
FString s = FString::SanitizeFloat(int(f * 100 + 0.5) / 100.0);

🔥实例化

🚩ConstructorHelpers类

  • ConstructorHelpers类用于在C++中获取资源文件

FClassFinder寻找类

1
2
3
4
5
6
7
8
9
10
11
12
13
// ---------------------------
// 头文件
#include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h"
TSubclassOf&lt;AActor&gt; actorToSpawn;//创建UClass*实例
// ---------------------------
// 源文件
// 一:必须在构造函数内定义
// 二:获取类地址时,掐头去尾,还要在后面添加“_C”来表示获取的是个类
static ConstructorHelpers::FClassFinder&lt;AActor&gt; actorResource(TEXT("/Game/类地址_C"));
if (actorResource.Succeeded())//如果正确获取类
{
actorToSpawn = actorResource.Class;//将资源类赋予给UClass*实例
}

FObjectFinder寻找资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ---------------------------
// 头文件
#include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h"
public:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* mesh;
// ---------------------------
// 源文件
// 必须在构造函数内定义
static ConstructorHelpers::FObjectFinder&lt;UStaticMesh&gt; meshResource(TEXT("/Game/资源地址"));
if (meshResource.Succeeded())//如果正确获取资源
{
mesh->SetStaticMesh(meshResource.Object);//将资源赋予给UStaticMeshComponent*实例
}

🚩类实例化

UWorld 场景类

TSubclassOf<class …>() 表示UClass类型的模板类(强制选择该类下的派生)

FActorSpawnParameters 物体生成参数类型

UWorld->SpawnActor<>() 生成实例模板函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TSubclassOf&lt;class AActor&gt; ActorClass;//生成物体类
FActorSpawnParameters SpawnParams;//物体生成参数
//
AActor* actor = World->SpawnActor&lt;AActor&gt;(//SpawnActor&lt;生成物体类>
ActorClass, //生成物体是什么类
FVector(0, 0, 0), //生成位置
FRotator(0, 0, 0), //生成方向旋转
SpawnParams);//生成参数
//---------精简版:
//头文件
UPROPERTY(EditDefaultsOnly)
TSubclassOf&lt;class AActor&gt; ActorClass;//此变量可在蓝图类中快速赋值
//
AActor* actor = GetWorld()->SpawnActor&lt;AActor&gt;(
TSubclassOf&lt;class AActor&gt;(), FVector(0, 0, 0), FRotator(0, 0, 0));

🔥时间

🚩游戏时间

1
float time = GetWorld()->GetTimeSeconds();//获取游戏时间

🔥数学

🚩数值映射

1
2
3
4
5
6
//本函数功能与FMath::GetMappedRangeValue()功能一致,但是更简单
//将数值val,从min1至max1的范围,映射至min2至max2的范围
inline float Mapping(float val, float min1, float max1, float min2, float max2)//MAPPING变量映射
{
return ((val - min1) * (max2 - min2) + min2 * (max1 - min1)) / (max1 - min1);
}

🚩UE坐标轴

  • 虚幻采用的是左手坐标系
  • X为前方向,Y为右方向,Z为上方向
  • XYZ旋转对应欧拉角为RollPitchYaw
  • 在此需要强调一下:
    • SceneComponent提供的Detail面板中Rotation的顺序为Roll(X)Pitch(Y)Yaw(Z)
    • 而FRotator结构体的构造方法顺序为Pitch(Y)Yaw(Z)Roll(X)

🚩FMath类常用函数

1
2
3
4
5
6
7
8
9
10
11
FMath::Clamp()//钳制
FMath::DegreesToRadians()//角转弧
FMath::RadiansToDegrees()//弧转角
FMath::DivideAndRoundUp()//除后五入
FMath::GetMappedRangeValue()//数值映射(参数需要传入FVector2D)
FMath::Lerp/LerpStable()//插值运算
FMath::PointDistToLine/SphereDistToLine()//到线段之间的直线距离
FMath::SmoothStep()//平滑阶梯
FMath::Square/Pow()//指数
FMath::Log2/Loge/LogX()//对数
FMath::Sin/Cos/Tan/ASin/ACos/ATan()//三角函数

🔥C++通信

🚩C++与类实例通信

  • 虚幻场景中一切实例都属于类(包括C++实例和蓝图实例)
  • 与静态实例通信时:
    • 使用暴露和细节面板拖拽的方法,且确保没有空指针
  • 与动态实例通信时:
    • 使用查找法,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "Kismet/GameplayStatics.h"
TArray&lt;AActor*&gt; Actors;//存储场景中所有物体
//通过标签查找物体
UGameplayStatics::GetAllActorsWithTag(
GetWorld(), TEXT("myTag"), Actors);
//通过类查找物体
UGameplayStatics::GetAllActorsOfClass(
GetWorld(), TSubclassOf&lt;AActor&gt;(), Actors);
//通过标签和类查找物体
UGameplayStatics::GetAllActorsOfClassWithTag(
GetWorld(), TSubclassOf&lt;AActor&gt;(), TEXT("myTag"), Actors);
//遍历返回到的物体数组
for (AActor* Actor: Actors)
{
if (Cast&lt;APawn&gt;(Actor))//Cast用于监测并过滤掉与APawn类无关的类型
{
//...
}
}

🚩C++与派生蓝图类通信

  • 派生蓝图类获取C++成员变量,见下图
1
2
UPROPERTY(BlueprintReadWrite, Category = "Props")
float myFloat;
  • 派生蓝图类获取C++成员方法,见下图
1
2
UFUNCTION(BlueprintCallable, Category = "Functions")
void MyFunc();//别忘了实现
  • 派生蓝图类重写C++类方法(C++调用其派生蓝图类的方法)
1
2
3
4
5
6
7
8
9
10
11
// 头文件内 -----------------------------------
UFUNCTION(BlueprintNativeEvent, Category = "Game")//设置反射蓝图可重写
void MyEventFunction();//主方法
void MyEventFunction_Implementation();//主方法实现体(一个字都不能少)
// 源文件内 -----------------------------------
void 类名::MyEventFunction_Implementation() {}//主方法实现体实现
//
void 类名::BeginPlay()
{
MyEventFunction();//在代码内调用此方法,这时如果其派生蓝图类中已有重写,则调用重写方法
}

🚩C++与材质通信

🚩C++蓝图脚本库

  • 创建C++类继承自UBlueprintFunctionLibrary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 头文件内 -----------------------------------
UCLASS()
class PROJECTFORBLUEPRINT_API UMyBPLib : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
//在下面写功能性方法
UFUNCTION(BlueprintCallable, Category = "Functions")
static void MyBPFunction();//必须静态
};
// 源文件内 -----------------------------------
void UMyBPLib::MyBPFunction()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, TEXT("Hello"));
}

🔥GamePlay基础框架

🚩游戏模式GameMode

  • 游戏模式GameMode用于构建实现游戏和规则
  • 游戏模式类AGameModeBase的作用是存储一些与游戏规则相关的配置
  • 项目设置中的游戏规则用于默认,世界设置中用于重载

🚩基础游戏框架

  • 游戏模式GameModeAGameModeBase
    • 游戏状态GameStateAGameStateBase
    • 玩家控制器playerControllerAPlayerController
      • 玩家相机控制器playerCameraControllerAPlayerCameraManager
    • 玩家状态PlayerStateAPlayerState
    • 用户界面HUDAHUD
    • 默认玩家DefaultPawnAPawn

🚩生成体Pawn

  • Pawn是所有玩家与AI生成体的基类
  • DefaultPawn指默认玩家
  • 默认玩家需要通过控制器进行控制,才能接收输入

🚩GameplayStatics类

1
2
3
4
#include "Kismet/GameplayStatics.h"//游戏信息类
// ---------------------------------
APlayerController* PlayerController =
UGameplayStatics::GetPlayerController(GWorld, 0);//此类可以方便获取游戏信息

🔥界面UMG

🚩界面绘制

  • 通常不使用代码的方式来添加游戏UI,而是使用编辑器

UTexture2D 2D贴图类

AHUD::DrawHUD() 绘制界面函数

FCanvasItem 界面物体基类

AHUD::Canvas.DrawItem() 将界面物体绘制到界面上函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// HUD派生头文件内 -----------------------------------
UPROPERTY(EditDefaultsOnly)
UTexture2D* tex;//声明贴图文件
virtual void DrawHUD() override;//界面绘制函数
// HUD派生源文件内 -----------------------------------
void AFPSHUD::DrawHUD()
{
Super::DrawHUD();//父类调用
if (tex)
{
FVector2D Center(//画布中心点
Canvas->ClipX * 0.5f,
Canvas->ClipY * 0.5f);
//
FVector2D Pos(//图像对齐,默认以图像左上角为原点生成
Center.X - (tex->GetSurfaceWidth() * 0.5f),
Center.Y - (tex->GetSurfaceHeight() * 0.5f));
//
FCanvasTileItem TileItem(//绘制界面
Pos, //位置
tex->Resource, //资源
FLinearColor::White);//颜色覆盖
//
TileItem.BlendMode = SE_BLEND_Translucent;//混合模式
Canvas->DrawItem(TileItem);//绘制到Canvas上
}
}

🚩界面装配

  • 界面装配使用蓝图
  • 创建UMG可视化UI生成器并制作UI
  • 在HUD类里使用节点BeginPlay->CreateWidget->AddToViewport进行装配

🚩界面绑定

  • 使用蓝图来绑定
  • 在UI绑定函数节点里使用GetAllActorsOfClass->Get(0)->CastTo->获取C++暴露的成员变量或方法

🔥标签Tag

🚩标签设置

  • 标签有两种:
    • 物体Actor标签
    • 组件Component标签
  • 设置物体Actor标签:
    • 所有场景物体Actor自身self都有Actor模块,在Actor模块最下方设置标签
  • 设置组件Component标签:
    • 所有组件Component都有Tags模块,可以直接设置标签

🚩标签判断

  • 物体Actor
1
2
3
4
5
6
7
8
AActor* act;//需要判断标签的物体
for (FName tag : act->Tags)
{
if (tag == FName(TEXT("tagged")))
{
UE_LOG(LogTemp, Warning, TEXT("is tagged"));
}
}
  • 组件Component
1
2
3
4
5
6
7
8
USceneComponent* comp;//需要判断标签的组件
for (FName tag : comp->ComponentTags)
{
if (tag == FName(TEXT("tagged")))
{
UE_LOG(LogTemp, Warning, TEXT("is tagged"));
}
}

🔥输入映射

  • 虚幻的输入系统由ActionMappingAxisMapping组成
  • ActionMapping包含所有瞬间输入
  • AxisMapping包含所有持续输入
  • SpeechMapping是新加的语音识别映射
  • 所有输入映射需要先在项目设置里设置好,并设置缩放权衡值(Scale)
  • 依此输入系统,自定义类必须继承自APawn类,因为需要重写的绑定函数是APawn类成员
  • 工程设置Project Settings输入Input中的第一栏是绑定Bindings设置

🚩绑定函数

SetupPlayerInputComponent() 玩家输入绑定函数

UInputComponent::BindAxis() 轴输入绑定函数

UInputComponent::BindAction() 动作输入绑定函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// --------------------- 类APlayerManager头文件
//若要进行输入绑定,则需要声明实现“SetupPlayerInputComponent”函数
virtual void SetupPlayerInputComponent
(class UInputComponent* InputCom) override;//重写绑定函数
void MoveForward(float value);//轴传递需要传一个参,用于接收数据,动作传递不需要
// --------------------- 类APlayerManager源文件
//具体实现需要使用UInputComponent::BindAxis/BindAction函数
void APlayerManager::SetupPlayerInputComponent(UInputComponent* InputCom)
{
Super::SetupPlayerInputComponent(InputCom);
InputCom->BindAxis
("MoveForward", //在项目设置的Input里设置的值的名称
this, //本类实例指针
&APlayerManager::MoveForward);//执行函数地址:&类名::函数名
//
InputCom->BindAction
("Jump", //在项目设置的Input里设置的值的名称
EInputEvent::IE_Pressed, //触发事件类型:按下、松开等
this, //本类实例指针
&APlayerManager::StartJump);//执行函数地址:&类名::函数名
}
void APlayerManager::MoveForward(float value)
{
//...
}

🚩输入绑定案例

头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PlayerManager.generated.h"

UCLASS()
class TESTFORUE5_1_API APlayerManager : public ACharacter
{
GENERATED_BODY()
public:
APlayerManager();//构造函数
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;

//SetupPlayerInputComponent输入绑定函数
virtual void SetupPlayerInputComponent
(class UInputComponent* PlayerInputComponent) override;
//
UFUNCTION()
void MoveForward(float Value);//前后移动
UFUNCTION()
void MoveRight(float Value);//左右移动
UFUNCTION()
void StartJump();//按下键时,设置跳跃标记
UFUNCTION()
void StopJump();//释放键时,清除跳跃标记
};

源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "PlayerManager.h"

APlayerManager::APlayerManager()//构造函数
{
PrimaryActorTick.bCanEverTick = true;//是否调用Tick函数
}
void APlayerManager::BeginPlay()//初始化函数
{
Super::BeginPlay();//父类先调用
}
void APlayerManager::Tick(float DeltaTime)//运行函数
{
Super::Tick(DeltaTime);//父类先调用
}
//
void APlayerManager::SetupPlayerInputComponent(UInputComponent* InputCom)//输入绑定函数
{
Super::SetupPlayerInputComponent(InputCom);//父类先调用
//
//轴绑定BindAxis("外部设置值",本类实例指针,执行函数);
// 设置"移动"绑定。
InputCom->BindAxis
("MoveForward", this, &APlayerManager::MoveForward);
InputCom->BindAxis
("MoveRight", this, &APlayerManager::MoveRight);
//
// 设置"视角"绑定。
InputCom->BindAxis
("Turn", this, &APlayerManager::AddControllerYawInput);//父类函数继承
InputCom->BindAxis
("LookUp", this, &APlayerManager::AddControllerPitchInput);//父类函数继承
//
//动作绑定BindAction("外部设置值",触发事件枚举,本类实例指针,执行函数);
// 设置"跳跃"绑定。
InputCom->BindAction
("Jump", EInputEvent::IE_Pressed, this, &APlayerManager::StartJump);
InputCom->BindAction
("Jump", EInputEvent::IE_Released, this, &APlayerManager::StopJump);
}
//
void APlayerManager::MoveForward(float Value)//玩家前后移动
{
FVector Direction = \
FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
void APlayerManager::MoveRight(float Value)//玩家左右移动
{
FVector Direction = \
FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}

void APlayerManager::StartJump()//玩家开始跳跃
{
bPressedJump = true;//注意:该标识符定义于ACharacter类
}
void APlayerManager::StopJump()//玩家结束跳跃
{
bPressedJump = false;//注意:该标识符定义于ACharacter类
}

🔥事件与委托

🚩常用蓝图交互事件

1
2
3
4
5
6
7
8
9
10
11
12
13
//声明动态多投射委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyEvent);//在头文件类的上面注宏
// -----------------------------
// 头文件
public:
UPROPERTY(BlueprintAssignable)//蓝图可指定
FMyEvent OnMyEvent_1;
UPROPERTY(BlueprintAssignable)//蓝图可指定
FMyEvent OnMyEvent_2;
// -----------------------------
// 源文件
OnMyEvent_1.Broadcast();//广播(触发事件)
OnMyEvent_2.Broadcast();//广播(触发事件)
  • 在此物体类蓝图中,可以找到在C++中声明的事件

🔥碰撞

  • AActor是所有物体碰撞事件的父类,此物体类携带Collision模块

  • UPrimitiveComponent是所有组件碰撞事件的父类,此组件类携带Collision模块

🚩碰撞通道

  • 工程设置Project Settings碰撞Collision中的第一栏是物体通道Object Channels设置
  • 点击New Object Channel来新建物体通道
  • 设置物体通道默认值为无视Ignore,在预设中有需要的情况下再做改动

🚩物体碰撞需求

要求一:物理模拟

  • 至少有一方开启物理模拟Simulate Physics

要求二:功能开关

  • 在网格组件中的碰撞模块Collision里进行以下设置
  • 当信息接收方碰撞事件检测Simulate Generates Hit Events开启时,若至少一方物理模拟开启,则能接收碰撞事件
  • 当必须双方覆盖事件检测Generates Overlap Events开启时,才能接收覆盖事件

要求三:碰撞方法

  • 碰撞方法Collision Enabled
    • 此方法的产生碰撞覆盖信息仅对于该物体自身起作用,对于其他物体无效
碰撞方法 产生碰撞覆盖信息 产生物理效果
无碰撞 NoCollision
仅信息 QueryOnly
仅物理 PhysicsOnly
有碰撞 CollisionEnable

要求四:通道设置

  • 碰撞响应Collision Responses:
    • 凡一方对另一方碰撞通道设置无视Ignore,则双方相互穿过,双方不发生任何响应
    • 以上条为基准,凡一方对另一方碰撞通道设置覆盖Overlap,则双方相互穿过,双方发生覆盖响应
    • 只有双方对对方碰撞通道设置碰撞Block,则双方相互碰撞,双方发生碰撞响应
  • 物体类型Object Type中为该物体赋予用户自定义类型,自定义类型会出现在物体响应Object Responses
碰撞响应 B_Ignore B_Overlap B_Block
A_Ignore AB_Ignore AB_Ignore AB_Ignore
A_Overlap AB_Ignore AB_Overlap AB_Overlap
A_Block AB_Ignore AB_Overlap AB_Block
  • 以上是物体碰撞四大要求

🚩碰撞与覆盖检测

物体,函数重写

  • 以下是采用物体类的函数重写法来检测碰撞,缺点是无法定位碰撞组件

  • 物体Actor进入覆盖:AActor::NotifyActorBeginOverlap

1
2
3
4
5
6
7
8
9
10
11
12
13
// 头文件中--------------------------
void NotifyActorBeginOverlap(AActor* Other) override;
// 源文件中--------------------------
// 触发进入检测(物体)函数重写
void 自定义类名::NotifyActorBeginOverlap(AActor* Other)
{
Super::NotifyActorBeginOverlap(Other);
//
if (Cast&lt;AActor&gt;(Other))//Cast用于监测并过滤掉与AActor类无关的类型
{
UE_LOG(LogTemp, Warning, TEXT("---begin %s"), *(Other->GetName()));
}
}
  • 物体Actor离开覆盖:AActor::NotifyActorEndOverlap
1
2
3
4
5
6
7
8
9
10
11
12
13
// 头文件中--------------------------
void NotifyActorEndOverlap(AActor* Other) override;
// 源文件中--------------------------
// 触发离开检测(物体)函数重写
void 自定义类名::NotifyActorEndOverlap(AActor* Other)
{
Super::NotifyActorEndOverlap(Other);
//
if (Cast&lt;AActor&gt;(Other))//Cast用于监测并过滤掉与AActor类无关的类型
{
UE_LOG(LogTemp, Warning, TEXT("---end %s"), *(Other->GetName()));
}
}
  • 物体Actor碰撞:AActor::NotifyHit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 头文件中--------------------------
void NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other,
class UPrimitiveComponent* OtherComp, bool bSelfMoved,
FVector HitLocation, FVector HitNormal,
FVector NormalImpulse, const FHitResult& Hit) override;
// 源文件中--------------------------
// 碰撞(物体)函数重写
void 自定义类名::NotifyHit(UPrimitiveComponent* MyComp, AActor* Other,
UPrimitiveComponent* OtherComp, bool bSelfMoved,
FVector HitLocation, FVector HitNormal,
FVector NormalImpulse, const FHitResult& Hit)
{
Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved,
HitLocation, HitNormal, NormalImpulse, Hit);
//
if (Cast&lt;AActor&gt;(Other))//Cast用于监测并过滤掉与AActor类无关的类型
{
UE_LOG(LogTemp, Warning, TEXT("---hit %s"), *(Other->GetName()));
}
}

组件与物体,委托

  • 以下是采用委托法来检测碰撞,缺点是复杂
  • 组件Component进入覆盖:UPrimitiveComponent::OnComponentBeginOverlap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 头文件中--------------------------
UFUNCTION()
void BoxOverlapBegin(class UPrimitiveComponent* HitComp, class AActor* OtherActor,
class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult);
// 源文件中--------------------------
void 自定义类名::BeginPlay()//委托需要在BeginPlay中设置
{
// -----委托法
FScriptDelegate dele_Begin;//委托体
dele_Begin.BindUFunction(this, "BoxOverlapBegin");//委托体绑定函数
boxCollision->OnComponentBeginOverlap.Add(dele_Begin);//组件事件绑定委托体
// -----动态委托法
boxCollision->OnComponentBeginOverlap.AddDynamic(this, &自定义类名::BoxOverlapBegin);
}
// 触发进入检测(组件)委托
void 自定义类名::BoxOverlapBegin(class UPrimitiveComponent* HitComp, class AActor* OtherActor,
class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor == nullptr) { return; }
UE_LOG(LogTemp, Warning, TEXT("---begin %s"), *OtherActor->GetName());
}
  • 组件Component离开覆盖:UPrimitiveComponent::OnComponentEndOverlap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 头文件中--------------------------
UFUNCTION()
void BoxOverlapEnd(class UPrimitiveComponent* HitComp, class AActor* OtherActor,
class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
// 源文件中--------------------------
void 自定义类名::BeginPlay()//委托需要在BeginPlay中设置
{
// -----委托法
FScriptDelegate dele_End;//委托体
dele_End.BindUFunction(this, "BoxOverlapEnd");//委托体绑定函数
boxCollision->OnComponentEndOverlap.Add(dele_End);//组件事件绑定委托体
// -----动态委托法
boxCollision->OnComponentEndOverlap.AddDynamic(this, &自定义类名::BoxOverlapEnd);
}
// 触发离开检测(组件)委托
void 自定义类名::BoxOverlapEnd(class UPrimitiveComponent* HitComp, class AActor* OtherActor,
class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor == nullptr) { return; }
UE_LOG(LogTemp, Warning, TEXT("---end %s"), *OtherActor->GetName());
}
  • 组件Component碰撞:UPrimitiveComponent::OnComponentHit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 头文件中--------------------------
UFUNCTION()
void BoxHit(class UPrimitiveComponent* HitComponent, class AActor* OtherActor,
class UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
// 源文件中--------------------------
void 自定义类名::BeginPlay()//委托需要在BeginPlay中设置
{
// -----委托法
FScriptDelegate dele_Hit;//委托体
dele_Hit.BindUFunction(this, "BoxHit");//委托体绑定函数
boxCollision->OnComponentHit.Add(dele_Hit);//组件事件绑定委托体
// -----动态委托法
boxCollision->OnComponentHit.AddDynamic(this, &自定义类名::BoxHit);
}
// 触发离开检测(组件)委托
void 自定义类名::BoxHit(class UPrimitiveComponent* HitComponent, class AActor* OtherActor,
class UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
if (OtherActor == nullptr) { return; }
UE_LOG(LogTemp, Warning, TEXT("---hit %s"), *OtherActor->GetName());
}
  • 物体Actor进入覆盖:AActor::OnActorBeginOverlap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 头文件中--------------------------
UFUNCTION()
void BoxOverlapBegin(class AActor* OverlappedActor, class AActor* OtherActor);
// 源文件中--------------------------
void 自定义类名::BeginPlay()//委托需要在BeginPlay中设置
{
// -----委托法
FScriptDelegate dele_Begin;//委托体
dele_Begin.BindUFunction(this, "BoxOverlapBegin");//委托体绑定函数
boxCollision->OnActorBeginOverlap.Add(dele_Begin);//组件事件绑定委托体
// -----动态委托法
boxCollision->OnActorBeginOverlap.AddDynamic(this, &自定义类名::BoxOverlapBegin);
}
// 触发离开检测(组件)委托
void 自定义类名::BoxOverlapBegin(class AActor* OverlappedActor, class AActor* OtherActor)
{
if (OtherActor == nullptr) { return; }
UE_LOG(LogTemp, Warning, TEXT("---begin %s"), *OtherActor->GetName());
}
  • 物体Actor离开覆盖:AActor::OnActorEndOverlap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 头文件中--------------------------
UFUNCTION()
void BoxOverlapEnd(class AActor* OverlappedActor, class AActor* OtherActor);
// 源文件中--------------------------
void 自定义类名::BeginPlay()//委托需要在BeginPlay中设置
{
// -----委托法
FScriptDelegate dele_End;//委托体
dele_End.BindUFunction(this, "BoxOverlapEnd");//委托体绑定函数
boxCollision->OnActorEndOverlap.Add(dele_End);//组件事件绑定委托体
// -----动态委托法
boxCollision->OnActorEndOverlap.AddDynamic(this, &自定义类名::BoxOverlapEnd);
}
// 触发离开检测(组件)委托
void 自定义类名::BoxOverlapEnd(class AActor* OverlappedActor, class AActor* OtherActor)
{
if (OtherActor == nullptr) { return; }
UE_LOG(LogTemp, Warning, TEXT("---end %s"), *OtherActor->GetName());
}
  • 物体Actor碰撞:AActor::OnActorHit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 头文件中--------------------------
UFUNCTION()
void BoxHit(class AActor* SelfActor, class AActor* OtherActor,
FVector NormalImpulse, const FHitResult& Hit);
// 源文件中--------------------------
void 自定义类名::BeginPlay()//委托需要在BeginPlay中设置
{
// -----委托法
FScriptDelegate dele_Hit;//委托体
dele_Hit.BindUFunction(this, "BoxHit");//委托体绑定函数
boxCollision->OnActorHit.Add(dele_Hit);//组件事件绑定委托体
// -----动态委托法
boxCollision->OnActorHit.AddDynamic(this, &自定义类名::BoxHit);
}
// 触发离开检测(组件)委托
void 自定义类名::BoxHit(class AActor* OverlappedActor, class AActor* OtherActor)
{
if (OtherActor == nullptr) { return; }
UE_LOG(LogTemp, Warning, TEXT("---hit %s"), *OtherActor->GetName());
}

🚩鼠标进入检测

  • 使用鼠标检测功能,必须设置鼠标可视和允许检测
  • 在创建的玩家控制器里鼠标接口栏MouseInterface中设置

UPrimitiveComponent::OnBeginCursorOver

UPrimitiveComponent::OnEndCursorOver

UPrimitiveComponent::OnClicked

UPrimitiveComponent::OnReleased

UPrimitiveComponent::OnInputTouchBegin

UPrimitiveComponent::OnInputTouchEnd

UPrimitiveComponent::OnInputTouchEnter

UPrimitiveComponent::OnInputTouchLeave

1
//鼠标进入事件可以类似碰撞检测,可以用上述碰撞检测的委托法来写

🔥射线检测

🚩射线通道

  • 工程设置Project Settings碰撞Collision中的第二栏是射线通道Trace Channels设置
  • 点击New Trace Channel来新建射线通道
  • 设置射线通道默认值为无视Ignore,在预设中有需要的情况下再做改动
  • 射线通道与物体通道一样无法直接赋给某个网格物体,需要新建预设,将预设赋给需要被射线检测到的网格体
  • 如果实在不想新建预设,也可以在物体的碰撞预设里选自定义Custom
  • 这里演示如何创建预设:在碰撞Collision中的第三栏预设Preset里新建碰撞预设
  • 填入预设名称,设置允许碰撞,设置物体类型
  • 选中该物体预设允许检测到的射线通道,将其设置为Block碰撞,确认新建预设
  • 将需要接受射线检测的物体网格中的碰撞预设Collision Presets设为刚新建的预设

🚩射线参数

  • 在触发射线检测行为的物体或组件中设置触发事件
  • 射线通道最多有18个(ECC_GameTraceChannel1~18)

FHitResult 碰撞信息接收器

DrawDebugLine 画线测试

GetWorld()->LineTraceSingleByChannel 单线通道射线检测

GetWorld()->LineTraceSingleByObjectType 单线物体类型射线检测

GetWorld()->LineTraceSingleByProfile 单线配置文件射线检测

GetWorld()->LineTraceMultiByChannel 多线通道射线检测

GetWorld()->LineTraceMultiByObjectType 多线物体类型射线检测

GetWorld()->LineTraceMultiByProfile 多线配置文件射线检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "DrawDebugHelpers.h"//测试画线需要此类的引用
// -----------------------------------------------------
FHitResult hit; //碰撞信息接收器,接收碰撞信息的参数
FVector startLocation; //射线起始点位置
FRotator startRotation; //射线起始点旋转方向
//获取玩家当前的位置和朝向,并赋给起始点信息
GetWorld()->GetFirstPlayerController()->GetPlayerViewPoint(startLocation, startRotation);
//通过起始点位置和朝向归一化,算出结束点位置(归一化是一厘米)
FVector endLocation = startLocation + startRotation.Vector() * 100;
//画一条测试线,无关紧要
DrawDebugLine(GetWorld(), startLocation, endLocation, FColor::Blue);
//开始射线检测
GetWorld()->LineTraceSingleByChannel(
hit, //碰撞信息接收器
startLocation, //起始位置
endLocation, //结束位置
ECollisionChannel::ECC_GameTraceChannel1, //指用户创建的第一个射线通道Trace Channels
FCollisionQueryParams(FName(TEXT("")), false, GetOwner()), //碰撞问询参数(可选参数)
FCollisionResponseParams(ECollisionResponse::ECR_Block)); //碰撞响应信息(可选参数)
if (hit.GetActor() != nullptr)//若接收器拥有碰撞信息
{
GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, TEXT("hit"));//输出提示
}