从今天开始,我们一起来学习一下,如何使用C++将一个不带有任何初学者内容的空模板,从无到有的创建一个简答却完整的FPS项目,通过这几篇文章的学习,我们大致了解到UE4 C++编程的流程,能够成功创建一个新的游戏模式,创建出第一人称的角色,他能够在场景中漫游并且向周围射击,完成一个整个工程。
第一部分:
(一)创建项目,新建游戏模式
1.新建一个C++的空项目,没有初学者内容,取名MyFPSProject。
2.点击创建后,将会在VS打开项目。首先我们来看一下VS的目录,系统自动帮我们建立了MyFPSProject的类和 MyFPSProjectGameMode的游戏模式类。等下我们会用到它。
如上图,点击运行,此时VS将会以调试的方式打开UE4编辑器,打开我们的工程,大概看一下内容浏览器,如果看过之前我的那翻译的那篇官方文档,就应该明白C++项目中,会有C++Classes,而之后我们创建的C++代码,都会出现在这里。
3.新建一个Maps文件夹,保存当前map为:MyFPSProjectMap
4.执行 编辑--项目设置:更改编辑器默认场景和游戏模式:分别为刚刚保存的MyFPSProjectMap,和之前在VS工程里面看到的引擎为我们创建的空的游戏模式MyFPSProjectGameMode。
5.然后简单介绍一下游戏模式,游戏模式包含了游戏本身的定义,包括游戏规则啊,胜利的条件等等,同时,他也指定了一些默认的游戏类(gameplay framework)类让我们使用,包含Pawn,PlayerController,和HUD。在我们创建我们的FPS角色之前,我们首先需要创建一个游戏模式引用角色。这里,我们只要在引擎为我们自动生成的游戏模式中添加内容即可。
为了保证游戏模式已经更换成我们自己的,我们先试着输出一个测试信息,打开VS工程中的MyFPSProjectGameMode.h,他默认应该是这样
#pragma once
#include "GameFramework/GameMode.h"
#include "MyFPSProjectGameMode.generated.h"
/**
*
*/
UCLASS()
class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
{
GENERATED_BOD
};
大概看一下这段代码,有两处可能要稍微解释一下
1.代码里面两个宏命令,UCLASS和GENERATED_BOD,只要是我们使用UE4 的C++类向导添加的自定义类,它头上都会有UCLASS这个宏,他是为了让引擎知道他的存在,这样我们写的类就能被包含在序列化等等其他引擎功能中,同时,等下我们就会发现,在自定义类的头部有UFUNCTION()宏命令,也是同样的作用。对于properties 还有一个UPROPERTY宏,这些说明符可以再 ObjectBase.h找到。GENERATED_BOD这里先不做解释。
2.实际的游戏模式类名为“AMyFPSProjectGameMode”,前缀'A'表示这个类最终继承自Actor。这里在之前的那篇翻译的文章中也有提到。
6.接着,我们要重写一个函数 virtual void StartPlay() override:
MyFPSProjectGameMode.h
#pragma once
#include "GameFramework/GameMode.h"
#include "MyFPSProjectGameMode.generated.h"
/**
*
*/
UCLASS()
class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
{
GENERATED_BODY()
virtual void StartPlay() override;
};
MyFPSProjectGameMode.cpp
#include "MyFPSProject.h"
#include "MyFPSProjectGameMode.h"
#include "Engine.h"
void AMyFPSProjectGameMode::StartPlay()
{
Super::BeginPlay();
StartMatch();
if (GEngine){
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("HELLO WORLD"));
}
}
注意,在cpp文件中添加了头文件“Engine.h”。这段代码很简单,就是为了显示一行调试信息。接下来,我们要进入到编辑器中测试一下,这里需要注意,因为我们在项目代码中添加了函数,所以要想让我们的代码有效,需要执行下面步骤:
1.关闭编辑器
2.VS编译代码
3.重新VS F5调试打开编辑器
4.打开相应项目相应地图。
OK,以后只要在代码中增加了函数,就需要这样执行。
以上两个信息,分别说明了我们的游戏模式已经创建成功,并且正在运行。
(二)创建游戏角色,引用到游戏模式中
引擎有一个内置的类叫做DefaultPawn,他是一个Pawn物体,有一些简单的漫游的功能。我们希望创建一个类似人类走在地上的角色,因此我们需要创建自己的Pawn。引擎专门有这样一个类,名字叫做Character,它继承自Pawn,但是已经包含一些移动的功能了,比如走,跑,和跳。我们将使用Character类来作为我们FPS角色的父类。
1.在UE4中选择文件--添加代码到项目,父类选择Character,命名MyFPSCharacter。这里就用到之前说到的UE4的C++类向导了,官方这样描述:使用C++类向导添加类,也可是手动在VS里面添加.h .cpp文件来创建一个新的类。但是C++类向导填写标题和源模板建立虚幻的特定的宏,简化了工艺,所以推荐使用类向导。
接下来看一下引擎自动生成的代码:
MyFPSCharacter.h
#pragma once
#include "GameFramework/Character.h"
#include "MyFPSCharacter.generated.h"
UCLASS()
class MYFPSPROJECT_API AMyFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMyFPSCharacter();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
};
MyFPSCharacter.cpp
#include "MyFPSProject.h"
#include "MyFPSCharacter.h"
// 构造函数
AMyFPSCharacter::AMyFPSCharacter()
{
// 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;
}
// 当产生式会被调用
void AMyFPSCharacter::BeginPlay()
{
Super::BeginPlay();
}
// 每一帧都会调用
void AMyFPSCharacter::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
// 绑定用户输入
void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
}
2.接下来,我们要做的就是把我们创建的这个空角色应用到刚刚的游戏模式之中。首先为了等下验证游戏角色引用成功,同样,我们写一句测试的代码,在BeginPlaye()函数里面,如下:
// 当产生式会被调用
void AMyFPSCharacter::BeginPlay()
{
Super::BeginPlay();
if (GEngine){
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We are using FPSCharacter!"));
}
}
3.接着,我们就要在游戏模式里面做修改了。我们的目的是
修改游戏模式里面的游戏角色。要实现这个功能,我们要在游戏模式的构造函数中修改。打开游戏模式的cpp文件,添加如下的构造函数:
首先添加角色的头文件:
#include "MyFPSCharacter.h"
构造函数如下
AMyFPSProjectGameMode::AMyFPSProjectGameMode(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
{
DefaultPawnClass = AMyFPSCharacter::StaticClass();
}
这里要注意,为了能让这个构造函数运行,游戏模式.h里面的GENERATED_BODY()宏命令要改成GENERATED_UCLASS_BODY()
#pragma once
#include "GameFramework/GameMode.h"
#include "MyFPSProjectGameMode.generated.h"
/**
*
*/
UCLASS()
class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
{
GENERATED_UCLASS_BODY()
virtual void StartPlay() override;
};
4.好了,这时候可以测试了,还是要注意之前提到的,因为我们添加了函数,要关闭编辑器,编译代码,重启编辑器打开项目。
(三)绑定输入,使得角色动起来!
首先我们绑定事件到WASD这几个键上。
1. 创建映射:编辑--项目设置
观察上图,就会发现这里有两种映射,一种是虚拟轴映射,一种是动作映射,其实和Unity一样,我也框出了二者的不同,我们的动作和摄像机的控制是使用的坐标轴映射,这样可以实现他们处理持续不间断输入功能,此外,动作映射为非连续性的事件处理输入信息,比如等下我们要添加的Jump跳跃的动作映射。
上面的填表很简单,具体步骤不做赘述,唯一一点要注意的是,两个相反的方向值为-1.
2.添加函数:我们要为角色添加连个函数:MoveFoward()和MoveRight()两个函数,来获取角色的方向,并在相应方向上添加位移。
MyFPSCharacter.h
#pragma once
#include "GameFramework/Character.h"
#include "MyFPSCharacter.generated.h"
UCLASS()
class MYFPSPROJECT_API AMyFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMyFPSCharacter();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
UFUNCTION()
void MoveForward(float Val);
UFUNCTION()
void MoveRight(float Val);
};
MyFPSCharacter.cpp
void AMyFPSCharacter::MoveForward(float Value)
{
if ((Controller != NULL) && (Value != 0.0f))
{
// 找到前进的方向
FRotator Rotation = Controller->GetControlRotation();
// 限制前进或者降落时的角度
if (CharacterMovement->IsMovingOnGround() || CharacterMovement->IsFalling())
{
Rotation.Pitch = 0.0f;
}
// 添加位移
FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void AMyFPSCharacter::MoveRight(float Value)
{
if ((Controller != NULL) && (Value != 0.0f))
{
// 找到前进的方向
FRotator Rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
// 添加位移
AddMovementInput(Direction, Value);
}
}
这两个函数道理都是一样的,在经典的FPS游戏中,这里的向前和向右都是基于相机的实际方向的。所以我们首先必须从PlayerController获得当前相机的方向。同样,还有一个问题,即使是当我们向上或向下看的时候,也需要沿着地面移动,所以MoveForward函数要忽略相机角度的最高点并且限制我们的输入在XY平面。
简单来说,就是都需要这么两步
(1)获取方向:先找到前进方向,然后对方向做处理
FRotator Rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
(2)在对应方向上添加位移:
AddMovementInput(Direction, Value);
3.绑定映射和函数。
接下来我们需要把刚刚创建好的映射和添加的函数绑定,很简单,只需要在角色的SetupPlayerInputComponent设置用户输入组件的函数里面添加两行即可。
// 绑定用户输入
void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
//轴名字,xx,函数名字
InputComponent->BindAxis("MoveForward", this, &AMyFPSCharacter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AMyFPSCharacter::MoveRight);
}
4.OK,完成了这三步,就可以测试了,注意之前说的,我们添加了函数,所以进入游戏检测需要关闭编辑器,编译代码,重启编辑器,打开工程。没有问题!
5.继续处理输入,接下来,我们为鼠标创建映射,用鼠标的移动来控制视角,即控制摄像机的旋转。同样,还是上面的三步:
(1)创建映射,注意Y这里是-1
(2)添加函数,这里有一点要注意,Character 类已经为我们定义了两个非常重要的函数:AddYawInput()和AddPitchInput(),也就是说我们不用自己再添加了。如果我们想要添加更多功能,比如控制鼠标灵敏度或者坐标轴的倒置,我们需要在把他们传递给这两个函数之前,自己去写函数来调整他们的值。但是,在这个案例中,我们直接把我们的输入绑定给他们。好的,这一步跳过!
(3)绑定函数和映射
// 绑定用户输入
void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
//轴名字,xx,函数名字
InputComponent->BindAxis("MoveForward", this, &AMyFPSCharacter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AMyFPSCharacter::MoveRight);
InputComponent->BindAxis("Turn", this, &AMyFPSCharacter::AddControllerYawInput);
InputComponent->BindAxis("LookUp", this, &AMyFPSCharacter::AddControllerPitchInput);
}
(4)可以测试啦!这里还有一点要注意,想清楚,我们这次只是在函数中添加了几行代码,并没有像之前,添加了函数,所以我们并不需要像以前一样,为了让新添的代码工作,关闭编辑器,VS编译,调试打开编辑器,打开工程这么麻烦,而是直接是用编辑器的代码热更新!
右下角会有编译状态的提示。好的编译成功后,进入游戏测试,没问题!
6.再添加一个功能吧:角色可以跳跃!处理过程还是一样:
(1)添加映射
(2)添加函数
这里有一点要说明,不知道大家是否还记得之前的那篇翻译的官方文章,里面有提到Character类的优势,它里面其实已经为我们添加好了 一些函数,比如跳啊,走啊,跑啊之类, 查看Character.h文件,我们会发现里面内置了跳跃的函数,它绑定了bPressedJump 变量。所以我们 仅仅需要当跳跃事件被触发时设置标志位1,当跳跃键被释放时,设置为0.我们需要两个函数来实现这些。
UFUNCTION()
void OnStartJump();
UFUNCTION()
void OnStopJump();
void AMyFPSCharacter::OnStartJump()
{
bPressedJump = true;
}
void AMyFPSCharacter::OnStopJump()
{
bPressedJump = false;
}
(3)绑定映射和函数
InputComponent->BindAction("Jump", IE_Pressed, this, &AMyFPSCharacter::OnStartJump);
InputComponent->BindAction("Jump", IE_Released, this, &AMyFPSCharacter::OnStopJump);
(4)测试吧!没得问题!
(四)总结一下今天的内容
今天的内容就到这里,不急着更新,先一起总结一下今天做了哪些事情吧:
1.使用空模板,创建空白C++工程。
2.创建了一个简单的游戏模式,到目前为止这个游戏模式仅仅只完成了引用游戏角色的任务。
3.创建了一个简单的角色,他可以完成一些动作,但是还没有网格。这部分内容会在下一篇文章中说明。
4.处理了输入。输入处理很简单,就是简单的三步:
(1)创建映射
(2)创建函数
(3)绑定映射和函数
我们总共用UE4的类向导创建了两个类:MyFPSGameMode和MyFPSCharacter,这里还没有涉及到蓝图的内容,C++和蓝图将会在下一篇文章 中做总结,并且为角色添加网格等更多功能。
最后说明,博客的主人俺只是一个大三的学生,这些是我花费了自己大量的课余时间整理的,制作分享,不会考虑任何商业因素,如果大家喜欢,欢迎大家支持,如果文中有错误,还望大家指出,我们一同学习一同进步。如需转载,请注明作者,注明CSDN本文链接