• 0

  • 点赞

  • 收藏

【UE4】C++基础【01】——默认代码扒拉拉(初学者向)

Doctor异乡人。

关注计算机

7个月前

本篇很 Water,因为我很 Vegetable,难免有Error。

别人的永远是别人的,整理理解成自己的才永远是自己的,查阅复习也方便。

【C++基础】:

X Tesla:【UE4】通俗易懂 用蓝图来学习 C++ 基础知识

【导图】:

一、基础注意事项

【1.1】项目/引擎/插件 目录

  • Game——项目文件
    • Content (/Game/)
    • C++ Classes(/Script/)
  • Engine——引擎文件
    • Engine Content
    • Engine C++ Classes
  • Plugins——第三方插件
    • Plugins Content(不一定有)
    • Plugins C++ Classes
  • 创建的代码位置

【1.2】虚幻代码采用 Pascal 命名法

  • 每个单词首字母大写,如Health、UPrimitiveComponent
  • 变量名称前有一个额外的大写字母用于区分不同的类型和普通的变量名
    • T表示模板类 Template——TArray、TMap、TSet
    • U表示类继承自UObject (可能是Unreal Object的简称吧)
    • A表示类继承自AActor
    • S表示类继承自SWidget
    • I表示抽象接口类
    • b表示布尔值 bool
    • F表示为其他不满足上述规则的类,如FVector、FColor、FString、FName 等等。

二、头文件代码详解

编程快速入门

【ShitActor.h】头文件代码:

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

#pragma once

#include "CoreMinimal.h" 
#include "GameFramework/Actor.h"
#include "ShitActor.generated.h" // 为了标记一个包含反射类型的头文件,需要添加这个特殊的include,这时UHT知道需要处理这个文件。

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

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

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

};

【2.1】#include“CoreMinimal.h"

  • 文件路径——Engine/Source/Runtime/Core/Public/CoreMinimal.h
  • 内容—— 包含一套来自UE4 核心编程的普遍存在类型,全部都是 #include"XXX.h"
    • 字符串String相关,如FString、FName等
    • 数学Math相关,如FVector、FColor等
    • 容器Container相关,如TSet、TArray等
    • 日志Logging相关
    • 代理Delegates相关
    • 等等

【2.2】#include “GameFramework/Actor.h"

  • 文件路径——Engine/Source/Runtime/Engine/GameFramework/Actor.h
  • 内容——GameFramework 游戏框架中包含一系列常用的类,也就是我们在Content Browser当中右键创建BP时候的模板蓝图,如GameMode、Actor、Character那些。
  • 包含原因——
    • 因为是基于Actor类生成的源代码,所以默认包含Actor的头文件。(要继承皇族血统,所以基因染色体来一份)即因为要 class :public AActor 继承父类Actor,所以要#include”GameFramework/Actor.h"

【2.3】#include"xxxx.generated.h" 位于项目内部。

  • 文件路径
    • 头文件Game/Intermediate/Build/Win64/UE4Editor/Inc/Game/xxx.generated.h
    • 源文件——Game/Intermediate/Build/Win64/UE4Editor/Inc/Game/xxx.gen.cpp
  • 内容——虚幻所生成的所有反射数据都会放入到该文件当中
  • 注意事项——#include“generated.h"必须在所有#include宏语句最下部,否则会报错

【反射】:

  • 作用——反射是指在运行状态下,任意一个实体类都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性
  • 含义——这种动态获取信息以及动态调用对象方法的功能称为语言的反射
  • 实现——C++本身并不支持反射。虚幻在C++上搭建了自己的一套反射机制。虚幻使用反射可以实现
    • 序列化Serialization
    • Editor中的细节面板
    • GC垃圾回收
    • 网络复制
    • 蓝图/C++ 通信和相互调用
    • 等功能。

【2.4】UClass

  • 作用——标识这个类能够被UnrealHeaderTool识别。

【Unreal Header Tool】:

  • 路径——Engine/Source/Programs/UnrealHeaderTool
  • 作用——收集信息,所有的U标记都会被收集处理。
  • 工作原理——UCLASS UFUNCTION UPROPERTY 这些宏就是参与这个阶段,当UHT 处理代码时,遇到这些宏标记,就知道接下来的代码需要进行处理。
  • 处理结果——生成 xxx.generated.h,注入内容。被定义为当前文件相对于工程根目录的路径,保证唯一。

【2.5】项目名称大写_API AShitActor:public AActor

  • 项目名称大写_API—— ModuleName_ API,可以理解为一个命名空间,避免类名冲突。
  • public 继承,加A前缀区分Actor。

【2.6】GENERATED_BODY()

  • 意义——相当于一个宏
  • 作用——因为引擎为当前创建类所生成的代码太多,所以这里用宏简洁写出。编译的时候会把生成的代码替换到当前GENERATED_BODY()位置。

#include"xxx.generated.h"的目的就是为了这个宏。


【2.7】函数

public:	
	// Sets default values for this actor's properties
	AShitActor();

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

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

};
  • AShitActor()——默认构造函数,每次创建类的新对象时执行(即拖拽到场景当中变对象执行)
  • virtual void ——虚函数,虚函数是在 基类(Parent Class)中使用 关键字 virtual声明的函数。
    • 想要被子类更改功能的函数,都被声明为虚函数。并让子类重写它们。
    • 如果没有声明为虚函数,说明这些函数的功能是不能被子类改动的。
    • (就类似unity不能改源码,unreal git clone 后可以让我们查看并重写它们)
  • override——重写虚函数。
  • BeginPlay()——程序一开始就执行,跟Niagara ParticleSpawn一样
  • Tick()——程序每帧执行,跟Niagara ParticleUpdate一样

三、Cpp文件

【代码】:

// Fill out your copyright notice in the Description page of Project Settings.
#include "ShitActor.h"

// Sets default values
AShitActor::AShitActor()
{
 	// 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 AShitActor::BeginPlay()
{
	Super::BeginPlay();
	
}

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

}


【3.1】PrimaryActorTick.bCanEverTick(Tick)

  • PrimaryActorTick.bCanEverTick=true;
    • PrimaryActorTick——将此Actor设为逐帧调用Tick(),如果不需要这个功能,可以关闭提高性能。
    • 理解——其实就相当于把蓝图这根Tick线断掉,Tick有实现函数,而这个就是判断是否连线。另外,看这个bCanEverTick,以b开头,就是一个boolean布尔变量,来true false的。

【3.2】Super::BeginPlay(Super::Tick)

  • 注意事项——必须放在子类的构造方法中(成员方法不可以),即void AShitActor::BeginPlay() 同类名构造方法中。
  • 作用——调用这个函数之前会先调用父类里面的函数

四、实战FloatingActor


【FloatingActor源码】:

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


#include "ShitActor.h"

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

	ShitMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	ShitMesh->SetupAttachment(RootComponent);


	static ConstructorHelpers::FObjectFinder<UStaticMesh>CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
		if (CubeVisualAsset.Succeeded())
		{
			ShitMesh->SetStaticMesh(CubeVisualAsset.Object);
			ShitMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
		}
}

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

// Called every frame
void AShitActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime); //DeltaTime 单帧,要实时渲染每帧时长不一

	FVector S_Loc = GetActorLocation();
	FRotator S_Rot = GetActorRotation();
	float RunningTime = GetGameTimeSinceCreation(); // 项目一旦 Alt+P 开始就开始计时,从0.0s 开始
	float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));  // -0.01 ~ 0.01
	S_Loc.Z += DeltaHeight * 20.0f;  // -0.2~0.2,每秒大概-20~20cm 高低运动
	float DeltaRotation = DeltaTime * 20.0f;     //每帧大概转0.2度,平均0.01~0.02s/帧,空场景FPS在大约120左右,所以也就是每秒大约转20度左右
	S_Rot.Yaw += DeltaRotation;                  //汽车车展360度转就是Yaw,不断叠加
	SetActorLocationAndRotation(S_Loc, S_Rot);  // 设置位置和旋转,跟蓝图一样。

	FString DebugMsg = TEXT("RunningTime:  ")+ FString::SanitizeFloat(RunningTime);  // 调试 RunningTime
	GEngine->AddOnScreenDebugMessage(1, 0.0f, FColor::Blue, DebugMsg);

	FString DebugMsg2 = TEXT("DeltaTime:  ") + FString::SanitizeFloat(DeltaTime);
	GEngine->AddOnScreenDebugMessage(2, 0.0f, FColor::Green, DebugMsg2);

	FString DebugMsg3 = TEXT("DeltaHeight:  ") + FString::SanitizeFloat(DeltaHeight);
	GEngine->AddOnScreenDebugMessage(3, 0.0f, FColor::Blue, DebugMsg3);

	FString DebugMsg4 = TEXT("S_Loc.Z:  ") + FString::SanitizeFloat(S_Loc.Z);
	GEngine->AddOnScreenDebugMessage(4, 0.0f, FColor::Green, DebugMsg4);

	FString DebugMsg5 = TEXT("S_Rot.Yaw:  ") + FString::SanitizeFloat(S_Rot.Yaw);
	GEngine->AddOnScreenDebugMessage(5, 0.0f, FColor::Blue, DebugMsg5);
	
}

【4.1】:头文件新增 静态网格体组件 指针

	UPROPERTY(VisibleAnywhere)
		UStaticMeshComponent *ShitMesh;
  • UPROPERTY,跟UCLASS一样,用于UnrealHeaderTool收集信息,定义一个变量属性。
    • VisibleAnywhere,编辑器可见(要不然灰色显示不可改动)
  • UStaticMeshComponent * ShitMesh,声明一个静态网格体组件指针

需要注意的是它不是一个变量,它是一个指针,相当于蓝图中添加静态网格体组件变量。我们可以看下区别。

  • 蓝图中的StaticMesh就叫做StaticMeshComponent静态网格体组件,继承自UStaticMeshComponent (Add Component嘛,都以组件形式呈现)
  • 注意AddComponent是直接在蓝图中生成组件,变量是只定义组件,未生成。

【4.2】:源文件新增

ShitMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	ShitMesh->SetupAttachment(RootComponent);
  • ShitMesh=xxx——把地址值给我们在头文件中声明的指针(跟蓝图Get Set一样)
  • CreateDefaultSubobject——创建组件
    • <UStaticMeshComponent> 创建一个静态网格体组件
  • ShitMesh->SetupAttachment(RootComponent)——将网格体附着到根组件上。(附着后是StaticMesh就是根组件,DefaultSceneRoot会消失的)

【4.3】给网格体组件赋值(DefaultValue)

static ConstructorHelpers::FObjectFinder<UStaticMesh>CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"))
  • 静态类中包含一个声明模板结构体
  • ConstructorHelpers 是 ObjectBase.h 中定义的特殊命名空间,是为资源或寻找引用、以及创建并寻找组件的助手模板。即查找资源地址,即从ContentBrowser资源浏览器当中查找资源并加载。
  • TEXT()中存放资源路径,/Game/就是/Content/,编辑器Content,代码Game。
  • 如果资源不存在的情况,那么编辑器就会直接崩溃掉。

如果没有StarterContent的话,我们可以新添加。

我们还可以直接复制粘贴 引用网格体模型 的路径。

  • if Succeeded 防止崩溃——如果路径下有这么个资源的话,就把这个资源给到静态网格体并设置相对位置为 原点位置。
if (CubeVisualAsset.Succeeded())
		{
			ShitMesh->SetStaticMesh(CubeVisualAsset.Object);
			ShitMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
		}

【4.4】Tick每帧执行函数:

这个就近似取sine函数的值了。

float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));  // -0.01 ~ 0.01
// Called every frame
void AShitActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime); //DeltaTime 单帧,要实时渲染每帧时长不一,大概0.01s左右

	FVector S_Loc = GetActorLocation(); // 获得 Actor位置,跟蓝图一样的
	FRotator S_Rot = GetActorRotation(); // 获得Actor 旋转,跟蓝图也是一样 d 
	float RunningTime = GetGameTimeSinceCreation(); // 项目一旦 Alt+P 开始就开始计时,从0.0s 开始
	float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));  // -0.01 ~ 0.01
	S_Loc.Z += DeltaHeight * 20.0f;  // -0.2~0.2,每秒大概-20~20cm 高低运动
	float DeltaRotation = DeltaTime * 20.0f;     //每帧大概转0.2度,平均0.01~0.02s/帧,空场景FPS在大约120左右,所以也就是每秒大约转20度左右
	S_Rot.Yaw += DeltaRotation;                  //汽车车展360度转就是Yaw,不断叠加
	SetActorLocationAndRotation(S_Loc, S_Rot);  // 设置位置和旋转,跟蓝图一样。

}
  • FVector就类似我们在蓝图变量面板里面声明一个Vector变量是一模一样的,Rotator一样。
  • GetGameTimeSinceCreation()——Alt+P 就开始从0.0s开始计时,不断增加。
  • Sin函数 -1~1 变化,Tick差不多。
  • 20.0f 其实就是一个 float factor,就我们蓝图中 加减乘除空出来的那个pin 引脚接口。
    • float DeltaHeight,每帧大约变化-0.01~0.01cm
    • DeltaHeight *20 每帧大约变化 -0.2~0.2cm
    • 每秒, 60fps/120fps,大概变化 -20多cm~20多cm吧。

注意蓝图里面的Sine函数跟材质里面的Sine函数周期不一样,一个是2pi,一个是1

  • 高低循环往复运动,用GameTime(数值不断增大)配合Sine,不断地来回-1~1运动
  • 左右旋转运动,用Tick(数值较稳定),配合一个因子乘就可以了,0~360度自动旋转,到360也就是0了,继续重新开始转。

写成蓝图就是这个样子,蓝图得连半天,对于吾等强迫症来说,节点不对齐真的心里跟刀绞了一样,真想哪个剪刀把蓝图线给剪穿。(为什么同样是学ue4,有的人就看不起蓝图?

简化后(跟代码不一样),高低跟着FPS 数值变。可以t.maxFPS 1 检测。


【4.5】打印字符串

  • UE_LOG 是在Console Log 控制台中进行输出的
  • GEngine是在Screen 屏幕上打印信息的。
GEngine->AddOnScreenDebugMessage(1, 0.0f, FColor::Blue, "Shit");

打印字符串到屏幕上。

  • FString 声明字符串变量
  • SanitizeFloat—— - 1.234 will be "1.234" rather than "1.234000"
  • GEngine->AddOnScreenDebugMessage ——相当于蓝图的PrintString
    • (1,2,3,4,5)为标号,注意不能重复,重复的那个屏幕上就会打印不出来
    • 0.0f ——延迟时间 Duration,f float
    • FColor::Blue ——字符串的颜色
    • String变量名或 "ABC.."用双引号来说明字符串
FString DebugMsg = TEXT("RunningTime:  ")+ FString::SanitizeFloat(RunningTime); 
	GEngine->AddOnScreenDebugMessage(1, 0.0f, FColor::Blue, DebugMsg);

	FString DebugMsg2 = TEXT("DeltaTime:  ") + FString::SanitizeFloat(DeltaTime);
	GEngine->AddOnScreenDebugMessage(2, 0.0f, FColor::Green, DebugMsg2);

	FString DebugMsg3 = TEXT("DeltaHeight:  ") + FString::SanitizeFloat(DeltaHeight);
	GEngine->AddOnScreenDebugMessage(3, 0.0f, FColor::Blue, DebugMsg3);

	FString DebugMsg4 = TEXT("S_Loc.Z:  ") + FString::SanitizeFloat(S_Loc.Z);
	GEngine->AddOnScreenDebugMessage(4, 0.0f, FColor::Green, DebugMsg4);

	FString DebugMsg5 = TEXT("S_Rot.Yaw:  ") + FString::SanitizeFloat(S_Rot.Yaw);
	GEngine->AddOnScreenDebugMessage(5, 0.0f, FColor::Blue, DebugMsg5);

最终结果。

五、【Visual Studio】快捷键

  • 右键Peek Definition可以在当前文件内速览定义。(不用新打开定义文件)
  • 我们使用Tab 键可以自动补全代码。
  • Home/End 行首行尾
  • 注释变绿字儿:选定要注释的区域:ctrl+K,然后再ctrl+C。
  • 解注释变代码:选定要注释的区域:ctrl+K,然后再ctrl+U
  • F5 Debugging 是还带启动项目调试的。(修改完代码在解决方案中右键Games Build即可重新编译,第一次编译比较慢,如300多s,后面就快了,4s,5s就好了)
  • 待续
免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

计算机

0

相关文章推荐

未登录头像

暂无评论