找回密码
 立即注册
查看: 490|回复: 0

Unreal学习1之c++编程

[复制链接]
发表于 2021-1-27 09:05 | 显示全部楼层 |阅读模式
20210108

1. 概要

本文记录的内容是在学习UE4开发时,第一次采用UE4 C++进行编程开发遇到一些问题汇总。记录一下。ps:本文是跟着siki学院课程做的一个示例,哔哩哔哩链接:【SiKi学院Unreal视频教程】Unreal初级课程-3D吃豆人_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili
2. 编程类内容

2.1 查找包含的头文件

UE 中使用很多类的前提必须是先包含头文件,因此UE使用C++编程的第一个技巧是要学会找到相应的类头文件。比方我们要查找UGameplayStatics的头文件,在Google下搜索关键字UGameplayStatics之后,一般在第一项,会看到Unreal Engine Document标定的网页,打开网页中可以看到如下内容:
其中,header指向其头文件地址,因此我们在引用进行include即可(取Classes后面地址即可):
#include "Kismet/GameplayStatics.h"2.2 CreateDefaultSubobject

CreateDefaultSubobject 用来创建静态网格物体,包含UStaticMeshComponent、USphereComponent等。类似代码如下:
this->CollectableMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CollectableMesh"));
this->BaseCollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("BaseCollisionComponent"));
TEXT包含的文字用来标定创建物体的名称。
这块功能在蓝图中就是添加组件功能。我们可以看到蓝图中显示如下:
由于对象CollectableMesh和BaseCollisionComponent是通过c++方式创建,因此我们右键无法进行删除。
2.3 ConstructorHelpers

ConstructorHelpers::FObjectFinder<T> 和 ConstructorHelpers::FClassFinder<T> 是UE4中的c++静态加载功能,相对应的有动态加载功能LoadClass。例如我们静态加载mesh 和 material:
// 静态加载一个圆柱体mesh
static ConstructorHelpers::FObjectFinder<UStaticMesh> Cylinder(TEXT("'/Game/StarterContent/Shapes/Shape_Cylinder'"));
if (Cylinder.Succeeded()) {
    ....
}
// 静态加载一个材质Material
static ConstructorHelpers::FObjectFinder<UMaterial> VulnerableMat(TEXT("'/Game/Materials/M_Enemy_Vulnerable'"));
if (VulnerableMat.Succeeded()) {
    ....
}
其中TEXT表示物件的地址StarterContent 表示引擎自带内容。
引申阅读:[UE4]C++静态加载问题:ConstructorHelpers::FClassFinder()和FObjectFinder()
2.4 UGameplayStatics

UGameplayStatics是一个非常实用的静态类,用来快速获取类对象。例如:
// 获取游戏模式
APacmanGameModeBase* GameMode = Cast<APacmanGameModeBase>(UGameplayStatics::GetGameMode(this));
// 获取角色
Aacmancharacter* Pacman = Cast<APacmancharacter>(UGameplayStatics::GetPlayerPawn(this, 0));
2.5 自定义拓展相关

UPROPERTY用来出翔编辑器设置。原型:UPROPERTY(属性1,属性2...)。类似代码如下:
UPROPERTY(VisibleAnywhere, Category = Collectable)
UStaticMeshComponent* CollectableMesh;
其中, VisibleAnywhere表示编辑器面板中可见,Category表示分类功能。类似功能还有:
EditAnywhere: 表示暴露在编辑器以便随时编辑的变量
BlueprintReadWrite: 支持蓝图对该变量的读写操作等等
引申阅读:UPROPERTY属性修饰符UE4编辑器扩展踩坑血泪史
2.6 C++新特性与UE下C++内容

2.6.1 UE下的内联函数

与通常c++内联函数inline方式不一样,UE下C++内联关键字是FORCEINLINE(手动狗头,为啥要这样写????),示例如下:
FORCEINLINE void SetCurrentHP(float CurrentHP){this->CurrentHP=CurrentHP;}
FORCEINLINE float GetCurrentHP(){return CurrentHP;}
2.6.2 强类型枚举类

在标准C++中,枚举类型并不是类型安全的。枚举类型被视为整数,这使得两种不同的枚举类型之间可以进行比较。例如:
enum Enumeration1
{
    Val1, // 0
    Val2, // 1
    Val3 = 100,
    Val4 /* = 101 */
};
其中Enumeration1.Val4 == 101,结果为true。因此,在C++11中引入一种特别的"枚举类",采用enum class方式。例如:
enum class EGameState : short
{
    EMenu,
    EPlaying,
    EPause,
    EWin,
    EGameOver
};
这里short用来表示类型。
3. UE 操作内容

3.1 设置起始关卡

当我们创建一个新关卡时,如果需要将这个关卡设置为起始关卡,需要在地图和模式中进行相关设置:编辑->项目设置->项目->地图和模式,界面如下所示:
根据需要设定相应关卡即可。
3.2 轴映射和操作映射

操作映射针对按下和松开2个处理,轴映射针对持续的输入(类似遥感的输入),需要在输入中进行设置:编辑->项目设置->引擎->输入,界面如下:
设置好之后,需要在C++层面进行配置:
void APacmancharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    PlayerInputComponent->BindAxis("MoveX", this, &APacmancharacter::MoveXAxis);
    PlayerInputComponent->BindAxis("MoveY", this, &APacmancharacter::MoveYAxis);

    PlayerInputComponent->BindAction("NewGame", IE_Pressed, this, &APacmancharacter::NewGame);
    PlayerInputComponent->BindAction("ReStart", IE_Pressed, this, &APacmancharacter::ReStart);
    PlayerInputComponent->BindAction("Pause", IE_Pressed, this, &APacmancharacter::Pause);
}

void APacmancharacter::MoveXAxis(float AxixValue)
{
    ...
}

void APacmancharacter::ReStart()
{
    ...
}
其中函数SetupPlayerInputComponent是用来绑定输入和执行函数。
PS:在蓝图中如何设置呢?
3.3 碰撞通道

默认情况下,在UE中设置的Actor对象都会互相碰撞(添加了碰撞胶囊体之后);另一个方面只有设置了正确的碰撞检测类型,才能处理好碰撞信息。因此,我们需要在碰撞中进行设置:编辑->项目设置->引擎->碰撞界面如下:
这里有一点需要注意下:Preset显示内容中,并不会有一个下拉控件,因此有时候这个界面偏小时,会将部分内容遮挡住,这时需要我们将界面拉大,才能看到剩下内容。
首先,在Object Channels中,点击新建Object通道,可以新建一个通道。这里,我们新建了一个Enemy,默认类型会Block的通道。
然后,在Preset中,新建一个碰撞类型。这里,我们同样添加了一个Enemy,设置如下:
其中,WoldStatic表示一个静态对象,比方墙、建筑等;Pawn表示主角,设置为重叠类型,表示会跟主角进行碰撞检测。
最后,将关卡的特定对象进行碰撞预设设定,如下所示:
剩下的工作,我们就是在C++层面进行碰撞检测了。
// .h 文件
UFUNCTION()
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);

// .cpp 文件
AEnemyCharacter::AEnemyCharacter()
{
    // 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;
    SetActorEnableCollision(true);
}

// Called when the game starts or when spawned
void AEnemyCharacter::BeginPlay()
{
    Super::BeginPlay();
    GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &AEnemyCharacter::OnCollision);
}
void APacmancharacter::OnColliOnsion(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    if (OtherActor->IsA(ACollectables::StaticClass())) {
        ACollectables* collectable = Cast<ACollectables>(OtherActor);
       ...
    }
}

默认情况下时不开启物理碰撞,因此首先需要启用碰撞功能;然后在BeginPlay中,我们需要对碰撞执行函数进行绑定,OnComponentBeginOverlap表示开始重叠时处理。
这里,需要注意一点,OnColliOnsion函数需要按照这个标准来写,具体原因还不了解。
延申阅读:UE4 Collision 文档UE4物理精粹Part 3:Collision
3.4 导航设置

UE4中对导航功能制作的非常完善,用起来非常方便。选中:放置actor->体积->导航网格体边界体积拖到场景中,进行一个简单的位置和大小配置,然后在编辑按下P键,既可看到类似如下界面:
默认情况下导航配置一般都会有些问题,因此我们需要根据项目进行简单配置:编辑->项目设置->引擎->导航网络体中进行设置,这里我们修改了默认单元大小和单元高度,如下所示:
最后,我们需要在C++层面进行导航工作:
#include "NavigationSystem/Public/NavigationSystem.h"
...
void AEnemyAIController::SearchNewPoint()
{
    // 获取当前关卡导航系统
    UNavigationSystemV1* Navmesh = UNavigationSystemV1::GetCurrent(this);
    if (Navmesh) {
        const float SearchRad = 10000.0f;
        FNavLocation RandomPt;
        // 随机寻找要给可以到达点
        const bool bFound = Navmesh->GetRandomReachablePointInRadius(Bot->GetActorLocation(), SearchRad, RandomPt);
        if (bFound) {
            ....
        }
    }
}
为了能够编译成功,需要在工程名.Build.cs 文件中进行修改:在PublicDependencyModuleNames 中,添加NavigationSystem
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem" });
Build.cs 是UE 构建相关内容,延申阅读:[[UE]理解UnrealBuildTool](http://blog.coolcoding.cn/?p=1485)理解UnrealBuildTool
4. 编译内容

4.1 莫名其妙闪退

在调试过程中遇到好几次编译闪退问题,类似报错Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x00000000,不管是重启UE、还是重启电脑都没有用。后来,将新增的h和cpp文件删掉之后,再次添加就莫名奇妙的好了。
这个纯属经验之谈。
4.2 编译乱码

在UE下或者VS下编译时,遇到乱码问题,其解决方案只有将VS的语言包设置成纯English,其他方案都无法通过。打开Visual Studio Installer进行更改。
5. 总结

在纯粹C++下进行编程,门槛会比采用蓝图开发难度大很多,这里有很大一部分原因是由于C++编程能力限制吧。不过用蓝图进行数值计算和逻辑处理也很麻烦,需要连接太多节点。另一方面,初学者刚刚使用蓝图时,对于节点理解不透彻,经常会有一些灵魂提问:这是啥,这是啥,这TM又是啥?。。。哈哈。综合考虑还是蓝图配好C++方式最好。当然实际项目,肯定要引入lua内容。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-20 13:35 , Processed in 0.103356 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表