转自:http://blog.csdn.net/u011707076/article/details/44180951

从今天开始,我们一起来学习一下,如何使用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,他默认应该是这样

  1. #pragma once
  2. #include "GameFramework/GameMode.h"
  3. #include "MyFPSProjectGameMode.generated.h"
  4. /**
  5. *
  6. */
  7. UCLASS()
  8. class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
  9. {
  10. GENERATED_BOD
  11. };

大概看一下这段代码,有两处可能要稍微解释一下

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

  1. #pragma once
  2. #include "GameFramework/GameMode.h"
  3. #include "MyFPSProjectGameMode.generated.h"
  4. /**
  5. *
  6. */
  7. UCLASS()
  8. class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
  9. {
  10. GENERATED_BODY()
  11. virtual void StartPlay() override;
  12. };

MyFPSProjectGameMode.cpp

  1. #include "MyFPSProject.h"
  2. #include "MyFPSProjectGameMode.h"
  3. #include "Engine.h"
  4. void AMyFPSProjectGameMode::StartPlay()
  5. {
  6. Super::BeginPlay();
  7. StartMatch();
  8. if (GEngine){
  9. GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("HELLO WORLD"));
  10. }
  11. }

注意,在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

  1. #pragma once
  2. #include "GameFramework/Character.h"
  3. #include "MyFPSCharacter.generated.h"
  4. UCLASS()
  5. class MYFPSPROJECT_API AMyFPSCharacter : public ACharacter
  6. {
  7. GENERATED_BODY()
  8. public:
  9. // Sets default values for this character's properties
  10. AMyFPSCharacter();
  11. // Called when the game starts or when spawned
  12. virtual void BeginPlay() override;
  13. // Called every frame
  14. virtual void Tick( float DeltaSeconds ) override;
  15. // Called to bind functionality to input
  16. virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
  17. };

MyFPSCharacter.cpp

  1. #include "MyFPSProject.h"
  2. #include "MyFPSCharacter.h"
  3. // 构造函数
  4. AMyFPSCharacter::AMyFPSCharacter()
  5. {
  6. // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  7. PrimaryActorTick.bCanEverTick = true;
  8. }
  9. // 当产生式会被调用
  10. void AMyFPSCharacter::BeginPlay()
  11. {
  12. Super::BeginPlay();
  13. }
  14. // 每一帧都会调用
  15. void AMyFPSCharacter::Tick( float DeltaTime )
  16. {
  17. Super::Tick( DeltaTime );
  18. }
  19. // 绑定用户输入
  20. void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
  21. {
  22. Super::SetupPlayerInputComponent(InputComponent);
  23. }

2.接下来,我们要做的就是把我们创建的这个空角色应用到刚刚的游戏模式之中。首先为了等下验证游戏角色引用成功,同样,我们写一句测试的代码,在BeginPlaye()函数里面,如下:

  1. // 当产生式会被调用
  2. void AMyFPSCharacter::BeginPlay()
  3. {
  4. Super::BeginPlay();
  5. if (GEngine){
  6. GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We are using FPSCharacter!"));
  7. }
  8. }

3.接着,我们就要在游戏模式里面做修改了。我们的目的是修改游戏模式里面的游戏角色。要实现这个功能,我们要在游戏模式的构造函数中修改。打开游戏模式的cpp文件,添加如下的构造函数:

首先添加角色的头文件:

  1. #include "MyFPSCharacter.h"

构造函数如下

  1. AMyFPSProjectGameMode::AMyFPSProjectGameMode(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
  2. {
  3. DefaultPawnClass = AMyFPSCharacter::StaticClass();
  4. }

这里要注意,为了能让这个构造函数运行,游戏模式.h里面的GENERATED_BODY()宏命令要改成GENERATED_UCLASS_BODY()

  1. #pragma once
  2. #include "GameFramework/GameMode.h"
  3. #include "MyFPSProjectGameMode.generated.h"
  4. /**
  5. *
  6. */
  7. UCLASS()
  8. class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
  9. {
  10. GENERATED_UCLASS_BODY()
  11. virtual void StartPlay() override;
  12. };

4.好了,这时候可以测试了,还是要注意之前提到的,因为我们添加了函数,要关闭编辑器,编译代码,重启编辑器打开项目。

(三)绑定输入,使得角色动起来!

       首先我们绑定事件到WASD这几个键上。

1. 创建映射:编辑--项目设置

观察上图,就会发现这里有两种映射,一种是虚拟轴映射,一种是动作映射,其实和Unity一样,我也框出了二者的不同,我们的动作和摄像机的控制是使用的坐标轴映射,这样可以实现他们处理持续不间断输入功能,此外,动作映射为非连续性的事件处理输入信息,比如等下我们要添加的Jump跳跃的动作映射。

上面的填表很简单,具体步骤不做赘述,唯一一点要注意的是,两个相反的方向值为-1.

2.添加函数:我们要为角色添加连个函数:MoveFoward()和MoveRight()两个函数,来获取角色的方向,并在相应方向上添加位移。

MyFPSCharacter.h

  1. #pragma once
  2. #include "GameFramework/Character.h"
  3. #include "MyFPSCharacter.generated.h"
  4. UCLASS()
  5. class MYFPSPROJECT_API AMyFPSCharacter : public ACharacter
  6. {
  7. GENERATED_BODY()
  8. public:
  9. // Sets default values for this character's properties
  10. AMyFPSCharacter();
  11. // Called when the game starts or when spawned
  12. virtual void BeginPlay() override;
  13. // Called every frame
  14. virtual void Tick( float DeltaSeconds ) override;
  15. // Called to bind functionality to input
  16. virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
  17. UFUNCTION()
  18. void MoveForward(float Val);
  19. UFUNCTION()
  20. void MoveRight(float Val);
  21. };

MyFPSCharacter.cpp

  1. void AMyFPSCharacter::MoveForward(float Value)
  2. {
  3. if ((Controller != NULL) && (Value != 0.0f))
  4. {
  5. // 找到前进的方向
  6. FRotator Rotation = Controller->GetControlRotation();
  7. // 限制前进或者降落时的角度
  8. if (CharacterMovement->IsMovingOnGround() || CharacterMovement->IsFalling())
  9. {
  10. Rotation.Pitch = 0.0f;
  11. }
  12. // 添加位移
  13. FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
  14. AddMovementInput(Direction, Value);
  15. }
  16. }
  17. void AMyFPSCharacter::MoveRight(float Value)
  18. {
  19. if ((Controller != NULL) && (Value != 0.0f))
  20. {
  21. // 找到前进的方向
  22. FRotator Rotation = Controller->GetControlRotation();
  23. const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
  24. // 添加位移
  25. AddMovementInput(Direction, Value);
  26. }
  27. }

这两个函数道理都是一样的,在经典的FPS游戏中,这里的向前和向右都是基于相机的实际方向的。所以我们首先必须从PlayerController获得当前相机的方向。同样,还有一个问题,即使是当我们向上或向下看的时候,也需要沿着地面移动,所以MoveForward函数要忽略相机角度的最高点并且限制我们的输入在XY平面。

简单来说,就是都需要这么两步

(1)获取方向:先找到前进方向,然后对方向做处理

FRotator Rotation = Controller->GetControlRotation();
  const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);

(2)在对应方向上添加位移:

AddMovementInput(Direction, Value);

3.绑定映射和函数。

接下来我们需要把刚刚创建好的映射和添加的函数绑定,很简单,只需要在角色的SetupPlayerInputComponent设置用户输入组件的函数里面添加两行即可。

  1. // 绑定用户输入
  2. void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
  3. {
  4. Super::SetupPlayerInputComponent(InputComponent);
  5. //轴名字,xx,函数名字
  6. InputComponent->BindAxis("MoveForward", this, &AMyFPSCharacter::MoveForward);
  7. InputComponent->BindAxis("MoveRight", this, &AMyFPSCharacter::MoveRight);
  8. }

4.OK,完成了这三步,就可以测试了,注意之前说的,我们添加了函数,所以进入游戏检测需要关闭编辑器,编译代码,重启编辑器,打开工程。没有问题!

5.继续处理输入,接下来,我们为鼠标创建映射,用鼠标的移动来控制视角,即控制摄像机的旋转。同样,还是上面的三步:

(1)创建映射,注意Y这里是-1

(2)添加函数,这里有一点要注意,Character 类已经为我们定义了两个非常重要的函数:AddYawInput()和AddPitchInput(),也就是说我们不用自己再添加了。如果我们想要添加更多功能,比如控制鼠标灵敏度或者坐标轴的倒置,我们需要在把他们传递给这两个函数之前,自己去写函数来调整他们的值。但是,在这个案例中,我们直接把我们的输入绑定给他们。好的,这一步跳过!

(3)绑定函数和映射

  1. // 绑定用户输入
  2. void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
  3. {
  4. Super::SetupPlayerInputComponent(InputComponent);
  5. //轴名字,xx,函数名字
  6. InputComponent->BindAxis("MoveForward", this, &AMyFPSCharacter::MoveForward);
  7. InputComponent->BindAxis("MoveRight", this, &AMyFPSCharacter::MoveRight);
  8. InputComponent->BindAxis("Turn", this, &AMyFPSCharacter::AddControllerYawInput);
  9. InputComponent->BindAxis("LookUp", this, &AMyFPSCharacter::AddControllerPitchInput);
  10. }

(4)可以测试啦!这里还有一点要注意,想清楚,我们这次只是在函数中添加了几行代码,并没有像之前,添加了函数,所以我们并不需要像以前一样,为了让新添的代码工作,关闭编辑器,VS编译,调试打开编辑器,打开工程这么麻烦,而是直接是用编辑器的代码热更新!

右下角会有编译状态的提示。好的编译成功后,进入游戏测试,没问题!

6.再添加一个功能吧:角色可以跳跃!处理过程还是一样:

(1)添加映射

(2)添加函数

这里有一点要说明,不知道大家是否还记得之前的那篇翻译的官方文章,里面有提到Character类的优势,它里面其实已经为我们添加好了             一些函数,比如跳啊,走啊,跑啊之类, 查看Character.h文件,我们会发现里面内置了跳跃的函数,它绑定了bPressedJump 变量。所以我们           仅仅需要当跳跃事件被触发时设置标志位1,当跳跃键被释放时,设置为0.我们需要两个函数来实现这些。

  1. UFUNCTION()
  2. void OnStartJump();
  3. UFUNCTION()
  4. void OnStopJump();
  1. void AMyFPSCharacter::OnStartJump()
  2. {
  3. bPressedJump = true;
  4. }
  5. void AMyFPSCharacter::OnStopJump()
  6. {
  7. bPressedJump = false;
  8. }

(3)绑定映射和函数

  1. InputComponent->BindAction("Jump", IE_Pressed, this, &AMyFPSCharacter::OnStartJump);
  1. InputComponent->BindAction("Jump", IE_Released, this, &AMyFPSCharacter::OnStopJump);

(4)测试吧!没得问题!

(四)总结一下今天的内容

      今天的内容就到这里,不急着更新,先一起总结一下今天做了哪些事情吧:

1.使用空模板,创建空白C++工程。

2.创建了一个简单的游戏模式,到目前为止这个游戏模式仅仅只完成了引用游戏角色的任务。

3.创建了一个简单的角色,他可以完成一些动作,但是还没有网格。这部分内容会在下一篇文章中说明。

4.处理了输入。输入处理很简单,就是简单的三步:

(1)创建映射

(2)创建函数

(3)绑定映射和函数

我们总共用UE4的类向导创建了两个类:MyFPSGameMode和MyFPSCharacter,这里还没有涉及到蓝图的内容,C++和蓝图将会在下一篇文章   中做总结,并且为角色添加网格等更多功能。

UE4编程之C++创建一个FPS工程(一)创建模式&角色&处理输入的更多相关文章

  1. UE4编程之C++创建一个FPS工程(二)角色网格、动画、HUD、子弹类

    转自:http://blog.csdn.net/u011707076/article/details/44243103 紧接上回,本篇文章将和大家一同整理总结UE4关于角色网格.动画.子弹类和HUD的 ...

  2. Eclipse创建一个mybatis工程实现连接数据库查询

    Eclipse上创建第一mybatis工程实现数据库查询 步骤: 1.创建一个java工程 2.创建lib文件夹,加入mybatis核心包.依赖包.数据驱动包.并为jar包添加路径 3.创建resou ...

  3. 创建一个Android工程

    Creating an Android Project 原文演示了怎么通过Android Studio和命令行两种方式来创建一个Android工程. 原文链接:http://developer.and ...

  4. 使用idea创建一个maven工程

    使用idea创建一个maven工程 一.创建maven工程 二.输入工程名 三.指定maven仓库 四.点击finish 五.修改工程结构(file->project structure) 六. ...

  5. 利用Zynq Soc创建一个嵌入式工程

    英文题目:Using the Zynq SoC Processing System,参考自ADI的ug1165文档. 利用Zynq Soc创建一个嵌入式工程,该工程总体上包括五个步骤: 步骤一.新建空 ...

  6. 使用Eclipse EE(汉化版) 创建一个JavaWeb工程

    第一步:打开eclipse ee,单击“文件”-->单击“新建”-->单击“动态Web项目”. 若没找到“动态Web项目”,单击“其他” -->在弹出的窗口中打开“Web”下拉菜单 ...

  7. django学习笔记【001】django版本的确定&创建一个django工程

    2.3 查看当前的django版本 python3. -m django --version 2.3.1 创建一个django工程 django-admin startproject mysite 在 ...

  8. 使用Idea第一次创建一个Mavne工程时没有src目录

    在使用idea创建一个maven工程时没有src目录,可能出现的问题很多,我先把我自己的问题分享上来 因为没有src,可能是因为maven插件还没下载到本地仓库.maven插件的版本和jdk版本冲突或 ...

  9. MySQL如何创建一个好索引?创建索引的5条建议【宇哥带你玩转MySQL 索引篇(三)】

    MySQL如何创建一个好索引?创建索引的5条建议 过滤效率高的放前面 对于一个多列索引,它的存储顺序是先按第一列进行比较,然后是第二列,第三列...这样.查询时,如果第一列能够排除的越多,那么后面列需 ...

随机推荐

  1. [ThinkPHP]MVC模块和URL访问

    ## ThinkPHP 3 MVC模式和URL访问#讲师:赵桐正微博:http://weibo.com/zhaotongzheng 本节课大纲: 一.什么是MVC                 // ...

  2. $.trim()函数

    $.trim(str) 返回:string: 参数str :String类型,需要去除两端空白字符的字符串.如果参数str不是字符串类型,该函数将自动将其转为字符串(一般调用其toString()方法 ...

  3. Oracle体系结构总览(整理)

    先让我们来看一张图  这张就是Oracle 9i的架构全图.看上去,很繁杂.是的,是这样的.现在让我们来梳理一下:一.数据库.表空间.数据文件1.数据库数据库是数据集合.Oracle是一种数据库管理系 ...

  4. Python 进阶(五)定制类

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAugAAAF/CAIAAACUs6uhAAAgAElEQVR4nOzdZXubx7ov8PPV9tlrt0 ...

  5. 调用css时,用link 和 @import url 有什么区别

    加载css link与@import的区别: 其实 link 与 @import 在显示效果上还是有很大区别的,基本上来看 link 的加在会在页面显示之前全部加在完全,而 @import 会是读取完 ...

  6. 模数转换器(ADC)的基本原理【转】

    模数转换器(ADC)的基本原理 模拟信号转换为数字信号,一般分为四个步骤进行,即取样.保持.量化和编码.前两个步骤在取样-保持电路中完成,后两步骤则在ADC中完成. 常用的ADC有积分型.逐次逼近型. ...

  7. FTPS加密上传

    公司要求ftp接口不能以明文方式传输,所以adc系统将增加ftps方式 但是在网找了很多方式都无法实现用了方法一 FtpWebRequest request = (FtpWebRequest)WebR ...

  8. JavaEE基础(二十四)/多线程

    1.多线程(多线程的引入) 1.什么是线程 线程是程序执行的一条路径, 一个进程中可以包含多条线程 多线程并发执行可以提高程序的效率, 可以同时完成多项工作 2.多线程的应用场景 红蜘蛛同时共享屏幕给 ...

  9. CI 配置验证规则

    //判断表单域,提交表单显示对应的错误信息      $this->load->library('form_validation');      $config = array(      ...

  10. 如何在ecshop商品详情页显示供货商信息

    以下范例以ecshop2.7.2原型做为修改: 1.首先需要修改程序文件,将供货商读取出来,然后赋值给模板,   打开文件 /goos.php,   在                   $smar ...