Choose your operating system:
Windows
macOS
Linux
这就是你将在本分段结束时看到的内容。
目标
本分段旨在向你展示如何实现第一人称射击游戏的发射物。
目的
完成本教程的此分段,你能够:
将发射物添加到游戏
实现射击
设置发射物碰撞和生命周期
使发射物与世界交互
将十字准星添加到视口
步骤
3.1 - 将发射物添加到游戏
3.2 - 实现射击
3.3 - 设置发射物碰撞和生命周期
3.4 - 让发射物与世界交互
3.5 - 将十字准星添加到视口
3.1 - 将发射物添加到游戏
你已经设置好角色,现在可以实现武器发射物了。你将通过编程实现简单的手榴弹状发射物,它将从屏幕中心射击并飞行,直到与世界中的物体碰撞。在此步骤中,你将添加输入并为发射物创建新的代码类。
添加射击操作映射
在 编辑(Edit) 菜单中,点击 项目设置(Project Settings)。
在 项目设置(Project Settings) 选项卡左侧的 引擎(Engine) 标题栏下,点击 输入(Input)。
在 绑定(Bindings) 中,点击 操作映射(Action Mappings) 旁边的+号。
点击 操作映射(Action Mappings) 左侧的 箭头。
在显示的文本输入框中输入"Fire",然后点击文本框左侧的箭头,展开操作绑定选项。
在下拉菜单中,从 鼠标(Mouse) 下拉列表中选择 鼠标左键(Left Mouse Button)。
现在输入设置界面应如下图所示:
关闭 项目设置(Project Settings) 菜单。
添加发射物类
在文件(File)菜单中,选择 新建C++类...(New C++ Class...),以选择新的父类。
以上操作将打开 选择父类(Choose Parent Class) 菜单。向下滚动,选择 Actor 作为父类,然后点击 下一步(Next)。
将新类命名为"FPSProjectile",然后点击 创建类(Create Class)。
添加USphere组件
在 解决方案浏览器(Solution Explorer) 中找到
FPSProjectile
类头文件,并打开FPSProjectile.h
。添加SphereComponent头文件:
#include "Components/SphereComponent.h"
在
FPSProjectile
接口中添加USphereComponent
的引用。// 球体碰撞组件。 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent;
现在
FPSProjectile.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 为此Actor的属性设置默认值 AFPSProjectile(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick( float DeltaTime ) override; // 球体碰撞组件 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; };
在 解决方案浏览器(Solution Explorer) 中找到
FPSProjectile
类CPP文件,并打开FPSProjectile.cpp
。将以下代码添加到
FPSProjectile.cpp
中的AFPSProjectile
构造函数中(在PrimaryActorTick.bcanEverTick
之后):if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if(!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; }
你把
CollisionComponent
设为RootComponent
,模拟器将驱动此组件。现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if(!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); }
添加发射物移动组件
在 解决方案浏览器(Solution Explorer) 中找到
FPSProjectile
类头文件,并打开FPSProjectile.h
。添加ProjectileMovementComponent头文件。
#include "GameFramework/ProjectileMovementComponent.h"
将以下代码添加到
FPSProjectile.h
:// 发射物移动组件。 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent;
现在
FPSProjectile.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 为此Actor的属性设置默认值 AFPSProjectile(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick( float DeltaTime ) override; // 球体碰撞组件。 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 发射物移动组件。 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; };
从 解决方案浏览器(Solution Explorer) 打开
FPSProjectile.cpp
。将以下代码行添加到
FPSProjectile.cpp
中的FPSProjectile
构造函数中:if(!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; }
现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if(!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if(!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); }
设置发射物的初始速度
在 解决方案浏览器(Solution Explorer) 中打开
FPSProjectile.h
。在FPSProjectile.h中添加以下函数声明:
// 初始化射击方向上发射物速度的函数。 void FireInDirection(const FVector& ShootDirection);
此函数将负责发射发射物。
现在
FPSProjectile.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 为此Actor的属性设置默认值 AFPSProjectile(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick( float DeltaTime ) override; // 球体碰撞组件。 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 发射物移动组件。 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 初始化射击方向上发射物速度的函数。 void FireInDirection(const FVector& ShootDirection); };
在 解决方案浏览器(Solution Explorer) 中打开
FPSProjectile.cpp
。将以下函数定义添加到
FPSProjectile.cpp
:// 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
你只需提供发射方向,因为发射物的速度由
ProjectileMovementComponent
定义。现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if(!RootComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if(!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } // 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
绑定发射输入操作
在 解决方案浏览器(Solution Explorer) 中打开
FPSCharacter.h
。在
FPSCharacter.h
中添加以下函数声明:// 处理发射物射击的函数。 UFUNCTION() void Fire();
现在
FPSCharacter.h
的内容应如下所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "FPSCharacter.generated.h" UCLASS() class FPSPROJECT_API AFPSCharacter : public ACharacter { GENERATED_BODY() public: // 为此角色的属性设置默认值 AFPSCharacter(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick( float DeltaTime ) override; // 被调用,将功能与输入绑定 virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // 处理用于前后移动的输入。 UFUNCTION() void MoveForward(float Value); // 处理用于左右移动的输入。 UFUNCTION() void MoveRight(float Value); // 按下键时,设置跳跃标记。 UFUNCTION() void StartJump(); // 释放键时,清除跳跃标记。 UFUNCTION() void StopJump(); // 处理发射物射击的函数。 UFUNCTION() void Fire(); // FPS摄像机 UPROPERTY(VisibleAnywhere) UCameraComponent* FPSCameraComponent; // 第一人称网格体(手臂),仅对所属玩家可见。 UPROPERTY(VisibleDefaultsOnly, Category = Mesh) USkeletalMeshComponent* FPSMesh; };
在 解决方案浏览器(Solution Explorer) 中找到
FPSCharacter
CPP文件,并打开FPSCharacter.cpp
。要绑定发射函数,请将以下代码添加到
FPSCharacter.cpp
中的SetupPlayerInputComponent
函数中:PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
现在,将以下函数定义添加到
FPSCharacter.cpp
:void AFPSCharacter::Fire() { }
现在
FPSCharacter.cpp
的内容应如下所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSCharacter.h" // 设置默认值 AFPSCharacter::AFPSCharacter() { // 将此角色设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; // 创建第一人称摄像机组件。 FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera")); check(FPSCameraComponent != nullptr); // 将摄像机组件附加到我们的胶囊体组件。 FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent())); // 将摄像机置于略高于眼睛上方的位置。 FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight)); // 启用Pawn控制摄像机旋转。 FPSCameraComponent->bUsePawnControlRotation = true; // 为所属玩家创建第一人称网格体组件。 FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh")); check(FPSMesh != nullptr); // 仅所属玩家可以看见此网格体。 FPSMesh->SetOnlyOwnerSee(true); // 将 FPS 网格体附加到 FPS 摄像机。 FPSMesh->SetupAttachment(FPSCameraComponent); // 禁用某些环境阴影以便实现只有单个网格体的感觉。 FPSMesh->bCastDynamicShadow = false; FPSMesh->CastShadow = false; // 所属玩家看不到常规(第三人称)全身网格体。 GetMesh()->SetOwnerNoSee(true); } // 当游戏开始或重生(Spawn)时被调用 void AFPSCharacter::BeginPlay() { Super::BeginPlay(); if (GEngine) { // 显示调试消息五秒。 // -1"键"值参数可以防止更新或刷新消息。 GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter.")); } } // 每一帧都被调用 void AFPSCharacter::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } // 被调用,将功能与输入绑定 void AFPSCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // 设置"移动"绑定。 PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight); // 设置"观看"绑定。 PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput); PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput); // 设置"操作"绑定。 PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump); PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump); PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire); } void AFPSCharacter::MoveForward(float Value) { // 找出"前进"方向,并记录玩家想向该方向移动。 FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X); AddMovementInput(Direction, Value); } void AFPSCharacter::MoveRight(float Value) { // 找出"右侧"方向,并记录玩家想向该方向移动。 FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y); AddMovementInput(Direction, Value); } void AFPSCharacter::StartJump() { bPressedJump = true; } void AFPSCharacter::StopJump() { bPressedJump = false; } void AFPSCharacter::Fire() { }
定义发射物的生成位置
生成
FPSProjectile
actor并实现OnFire
函数时需要考虑两点,即:发射物的生成位置。
发射物对应的类(让
FPSCharacter
及其派生蓝图知道要生成哪种发射物)。
你将使用一个摄像机空间中的偏移向量来确定发射物的生成位置。设置该参数为可编辑参数,这样你就可以在
BP_FPSCharacter
蓝图中对其进行设置和调整。最终,你可以基于这些数据计算发射物的初始位置。在 解决方案浏览器(Solution Explorer) 中打开
FPSCharacter.h
。将以下代码添加到
FPSCharacter.h
:// 枪口相对于摄像机位置的偏移。 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) FVector MuzzleOffset;
EditAnywhere
值让你可以在蓝图编辑器的默认(Defaults)模式下或在任何角色实例的细节(Details)选项卡中更改枪口偏移值。BlueprintReadWrite
选项值让你可以在蓝图中获取和设置枪口偏移值。将以下代码添加到
FPSCharacter.h
中的受保护访问说明符下:// 要生成的发射物类。 UPROPERTY(EditDefaultsOnly, Category = Projectile) TSubclassOf<class AFPSProjectile> ProjectileClass;
EditDefaultsOnly
意味着你只能将发射物类设置为蓝图上的默认值,而不是每个蓝图实例上的默认值。现在
FPSCharacter.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "FPSCharacter.generated.h" UCLASS() class FPSPROJECT_API AFPSCharacter : public ACharacter { GENERATED_BODY() public: // 为此角色的属性设置默认值 AFPSCharacter(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; // 要生成的发射物类。 UPROPERTY(EditDefaultsOnly, Category = Projectile) TSubclassOf<class AFPSProjectile> ProjectileClass; public: // 每一帧都被调用 virtual void Tick( float DeltaTime ) override; // 被调用,将功能与输入绑定 virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // 处理用于前后移动的输入。 UFUNCTION() void MoveForward(float Value); // 处理用于左右移动的输入。 UFUNCTION() void MoveRight(float Value); // 按下键时,设置跳跃标记。 UFUNCTION() void StartJump(); // 释放键时,清除跳跃标记。 UFUNCTION() void StopJump(); // 发射发射物的函数。 UFUNCTION() void Fire(); // FPS摄像机 UPROPERTY(VisibleAnywhere) UCameraComponent* FPSCameraComponent; // 第一人称网格体(手臂),仅对所属玩家可见。 UPROPERTY(VisibleDefaultsOnly, Category = Mesh) USkeletalMeshComponent* FPSMesh; // 枪口相对于摄像机位置的偏移。 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) FVector MuzzleOffset; };
编译并检查代码
现在我们来编译并检查新实现的发射物代码。
在Visual Studio中保存所有头文件和实现文件。
在 解决方案浏览器(Solution Explorer) 中找到 FPSProject。
右键点击 FPSProject 并选择 构建(Build),编译你的项目。
3.2 - 实现射击
了解如何实现第一人称射击角色的射击动作。
实现发射函数
将以下代码行添加到FPSCharacter.h:
#include "FPSProjectile.h"
将以下
发射
函数定义添加到FPSCharacter.cpp:void AFPSCharacter::Fire() { // 试图发射发射物。 if (ProjectileClass) { // 获取摄像机变换。 FVector CameraLocation; FRotator CameraRotation; GetActorEyesViewPoint(CameraLocation, CameraRotation); // 设置MuzzleOffset,在略靠近摄像机前生成发射物。 MuzzleOffset.Set(100.0f, 0.0f, 0.0f); // 将MuzzleOffset从摄像机空间变换到世界空间。 FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset); // 使目标方向略向上倾斜。 FRotator MuzzleRotation = CameraRotation; MuzzleRotation.Pitch += 10.0f; UWorld* World = GetWorld(); if (World) { FActorSpawnParameters SpawnParams; SpawnParams.Owner = this; SpawnParams.Instigator = GetInstigator(); // 在枪口位置生成发射物。 AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams); if (Projectile) { // 设置发射物的初始轨迹。 FVector LaunchDirection = MuzzleRotation.Vector(); Projectile->FireInDirection(LaunchDirection); } } } }
现在
FPSCharacter.h
的内容应如下所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Camera/CameraComponent.h" #include "Components/CapsultComponent.h" #include "FPSProjectile.h" #include "FPSCharacter.generated.h" UCLASS() class FPSPROJECT_API AFPSCharacter : public ACharacter { GENERATED_BODY() public: // 为此角色的属性设置默认值 AFPSCharacter(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; // 要生成的发射物类。 UPROPERTY(EditAnywhere, Category = Projectile) TSubclassOf<class AFPSProjectile> ProjectileClass; public: // 每一帧都被调用 virtual void Tick(float DeltaTime) override; // 被调用,将功能与输入绑定 virtual void SetupPlayerInputComponent(class UIComponent* PlayerInputComponent) override; // 处理用于前后移动的输入。 UFUNCTION() void MoveForward(float Value); // 处理用于左右移动的输入。 UFUNCTION() void MoveRight(float Value); // 按下键时,设置跳跃标记。 UFUNCTION() void StartJump(); // 释放键时,清除跳跃标记。 UFUNCTION() void StopJump(); // 发射发射物的函数。 UFUNCTION() void Fire(); // FPS摄像机 UPROPERTY(VisibleAnywhere) UCameraComponent* FPSCameraComponent; // 第一人称网格体(手臂),仅对所属玩家可见。 UPROPERTY(VisibleDefaultsOnly, Category = Mesh) USkeletalMeshComponent* FPSMesh; // 枪口相对于摄像机位置的偏移。 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) FVector MuzzleOffset; };
现在
FPSCharacter.cpp
的内容应如下所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSCharacter.h" // 设置默认值 AFPSCharacter::AFPSCharacter() { // 将此角色设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; // 创建第一人称摄像机组件。 FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera")); check(FPSCameraComponent != nullptr); // 将摄像机组件附加到我们的胶囊体组件。 FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent())); // 将摄像机置于略高于眼睛上方的位置。 FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight)); // 启用Pawn控制摄像机旋转。 FPSCameraComponent->bUsePawnControlRotation = true; // 为所属玩家创建第一人称网格体组件。 FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh")); check(FPSMesh != nullptr); // 仅所属玩家可以看见此网格体。 FPSMesh->SetOnlyOwnerSee(true); // 将 FPS 网格体附加到 FPS 摄像机。 FPSMesh->SetupAttachment(FPSCameraComponent); // 禁用某些环境阴影以便实现只有单个网格体的感觉。 FPSMesh->bCastDynamicShadow = false; FPSMesh->CastShadow = false; // 所属玩家看不到常规(第三人称)全身网格体。 GetMesh()->SetOwnerNoSee(true); } // 当游戏开始或重生(Spawn)时被调用 void AFPSCharacter::BeginPlay() { Super::BeginPlay(); if (GEngine) { // 显示调试消息五秒。 // -1"键"值参数可以防止更新或刷新消息。 GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter.")); } } // 每一帧都被调用 void AFPSCharacter::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } // 被调用,将功能与输入绑定 void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // 设置"移动"绑定。 PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight); // 设置"观看"绑定。 PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput); PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput); // 设置"操作"绑定。 PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump); PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump); PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire); } void AFPSCharacter::MoveForward(float Value) { // 找出"前进"方向,并记录玩家想向该方向移动。 FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X); AddMovementInput(Direction, Value); } void AFPSCharacter::MoveRight(float Value) { // 找出"右侧"方向,并记录玩家想向该方向移动。 FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y); AddMovementInput(Direction, Value); } void AFPSCharacter::StartJump() { bPressedJump = true; } void AFPSCharacter::StopJump() { bPressedJump = false; } void AFPSCharacter::Fire() { // 试图发射发射物。 if (ProjectileClass) { // 获取摄像机变换。 FVector CameraLocation; FRotator CameraRotation; GetActorEyesViewPoint(CameraLocation, CameraRotation); // 设置MuzzleOffset,在略靠近摄像机前生成发射物。 MuzzleOffset.Set(100.0f, 0.0f, 0.0f); // 将MuzzleOffset从摄像机空间变换到世界空间。 FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset); // 使目标方向略向上倾斜。 FRotator MuzzleRotation = CameraRotation; MuzzleRotation.Pitch += 10.0f; UWorld* World = GetWorld(); if (World) { FActorSpawnParameters SpawnParams; SpawnParams.Owner = this; SpawnParams.Instigator = GetInstigator(); // 在枪口位置生成发射物。 AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams); if (Projectile) { // 设置发射物的初始轨迹。 FVector LaunchDirection = MuzzleRotation.Vector(); Projectile->FireInDirection(LaunchDirection); } } } }
在 Visual Studio 中保存
FPSCharacter.h
和FPSCharacter.cpp
。在 解决方案浏览器(Solution Explorer) 中找到 FPSProject。
右键点击 FPSProject 并选择 构建(Build),编译你的项目。
导入发射物网格体
在继续之前,请通过以下链接下载并提取示例网格体: "发射物网格体"
右键点击内容浏览器的文件框,打开 导入资产(Import Asset) 对话框
点击 ‘导入/游戏...(Import to /Game...)',打开 导入(Import) 对话框。
找到并选择 Sphere.fbx 网格体文件。
点击 打开(Open),开始将网格体导入到你的项目中。
内容浏览器(Content Browser) 中将显示 FBX导入选项(FBX Import Options) 对话框。点击 全部导入(Import All),将你的网格体添加到项目。
忽略以下有关平滑组的错误:
此网格体仍展示为第一人称网格体设置,它将与你在后面段中设置的动画一起使用。
点击 保存(Save) 按钮,保存已导入的静态网格体。
添加发射物网格体
将以下代码添加到FPSProjectile.h:
// 发射物网格体 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent;
将以下代码添加到FPSProjectile.cpp中的构造函数中:
if(!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("[ADD STATIC MESH ASSET REFERENCE]")); if(Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } }
打开内容浏览器,右键点击球体静态网格体,选择 复制引用(Copy Reference):
返回到FPSProjectile.cpp中的ProjectileMeshComponent代码段,并用复制的引用替换[添加静态网格体资产引用(ADD STATIC MESH ASSET REFERENCE)]。你添加的代码段应类似于以下内容:
if(!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if(Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } }
资产引用路径可能会有所不同,具体取决于你在内容浏览器中保存球体网格体时所选择的位置。此外,当你粘贴复制的资产引用时,该引用在资产的引用路径之前包含资产类型名称。在我们的示例中,你将看到StaticMesh'/Game/Sphere.Sphere'。请确保从引用路径中删除资产类型名称(例如StaticMesh)。
现在
FPSProjectile.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 为此Actor的属性设置默认值 AFPSProjectile(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick(float DeltaTime) override; // 球体碰撞组件 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 发射物移动组件 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 发射物网格体 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; // 初始化射击方向上发射物速度的函数。 void FireInDirection(const FVector& ShootDirection); };
现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if (!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } } } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
保存并构建代码,然后继续下一步,在下一步中你将设置网格体的材质和缩放。
添加发射物的材质
将以下代码添加到FPSProjectile.h:
// 发射物材质 UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance;
将以下代码添加到FPSProjectile.cpp中的构造函数中:
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("[ADD MATERIAL ASSET REFERENCE]")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent);
在 内容浏览器(Content Browser) 中右键点击并选择 材质(Material)。
将新材质命名为"SphereMaterial"。
设置新材质的节点图表,其属性值类似下图:
底色(Base Color):Constant2VectorNode设置为(1,0,0)
高光度(Specular):常量节点设置为0.5 自发光颜色:常量节点设置为0.05
自发光颜色:常量节点设置为0.05
在此步骤中,你将创建一个基础材质资产。如果想要学习如何制作更复杂的材质,请阅读"如何使用和制作材质"。
设置新材质的节点图表后,点击保存(Save),并打开内容浏览器。
右键点击球体材质(Sphere material),并选择复制引用(Copy Reference)。
返回到FPSProjectile.cpp中的ProjectileMeshComponent代码段,用复制的引用替换[添加材质资产引用(ADD MATERIAL ASSET REFERENCE)]。你添加的代码段应类似于以下内容:
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent);
资产引用路径可能会有所不同,具体取决于你在内容浏览器中保存球体材质时所选择的位置。此外,当你粘贴复制的资产引用时,该引用在资产的引用路径之前包含资产类型名称。在我们的例子中,你将看到Material'/Game/Sphere.Sphere'。请确保从引用路径中删除资产的类型名称(例如Material)。
现在
FPSProjectile.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 为此Actor的属性设置默认值 AFPSProjectile(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick(float DeltaTime) override; // 球体碰撞组件 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 发射物移动组件 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 发射物网格体 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; // 发射物材质 UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance; // 初始化射击方向上发射物速度的函数。 void FireInDirection(const FVector& ShootDirection); };
现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if (!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
前往内容浏览器中的蓝图文件夹,并打开BP_FPSCharacter文件。
打开完整的编辑器(如有必要),然后导航到细节(Detail)面板。
找到发射物(Projectile)标头,然后在发射物类(Projectile Class)旁边的下拉列表中,选择FPSProjectile。
构建FPSProject,然后以PIE模式运行游戏,以确认在场景中生成了静态网格体和材质。
发射发射物时,你会在世界大纲视图中看到发射物的数量不断增加,因为它们没有已定义的生命周期。
在下一分段中,我们将展示如何定义发射物的初始生命周期。
3.3 - 设置发射物的碰撞和生命周期
目前,我们的发射物:
永远存在(永远不会从场景大纲中消失)
不会与世界中的其他对象碰撞
在此步骤中,我们将设置发射物的碰撞和生命周期。
限制发射物的生命周期
打开FPSProjectile.cpp。
将以下代码添加到FPSProjectile构造函数中,以设置发射物的生命周期:
// 3 秒后删除发射物。 InitialLifeSpan = 3.0f;
现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if (!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } // 3 秒后删除发射物。 InitialLifeSpan = 3.0f; } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
保存并编译FPSProject。
若要确认发射物是否在三秒后被销毁,请在PIE模式下运行游戏。
从世界大纲中可以看到,每个生成的发射物将在三秒后从场景中消失。
编辑发射物的碰撞设置
虚幻引擎自带了数个预设碰撞通道;不过,引擎也支持游戏项目使用自定义通道。
要创建自定义碰撞通道,打开项目设置(Project Settings),在引擎(Engine) - 碰撞(Collision)中,展开预设(Preset)。
在对象通道(Object Channels)中,选择 新建对象通道...(New Object Channel...),创建新碰撞通道。将新碰撞通道命名为"Projectile",确保将默认响应(Default Response)设置为阻止(Block),然后点击接受(Accept)。
在预设(Preset)中选择 新建...(New...),将新配置文件命名为"Projectile"。参考以下图片来设置你的碰撞预设。然后点击"接受(Accept)"。
此碰撞配置文件将发射物设定为将被静态Actor、动态Actor、模拟物理Actor、载具和可破坏Actor阻挡。此外,此碰撞配置文件设定发射物与Pawn重叠。
使用新碰撞通道的设置
打开FPSProjectile.cpp。
在FPSProjectile构造函数中,将以下代码行添加到CreateDefaultSubobject
下方 // 将球体的碰撞配置文件名称设置为"Projectile"。 CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 将球体的碰撞配置文件名称设置为"Projectile"。 CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile")); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if (!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } // 3 秒后删除发射物。 InitialLifeSpan = 3.0f; } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
保存并编译FPSProject。
3.4 - 让发射物与世界交互
现在我们可以检测到发射物的碰撞交互了,不仅如此,我们还能决定如何响应这些碰撞。在此步骤中,我们将向 FPSProjectile
中添加 OnHit
函数,该函数将响应碰撞事件。
使发射物对碰撞做出响应
打开
FPSProjectile.h
。将以下代码添加到
FPSProjectile.h
中:// 当发射物击中物体时会调用的函数。 UFUNCTION() void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
现在
FPSProjectile.h
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 为此Actor的属性设置默认值 AFPSProjectile(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; public: // 每一帧都被调用 virtual void Tick(float DeltaTime) override; // 当发射物击中物体时会调用的函数。 UFUNCTION() void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit); // 球体碰撞组件 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 发射物移动组件 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 发射物网格体 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; // 发射物材质 UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance; // 初始化射击方向上发射物速度的函数。 void FireInDirection(const FVector& ShootDirection); };
在 解决方案浏览器(Solution Explorer) 中找到
FPSProjectile
类CPP文件,并打开FPSProjectile.cpp
,添加以下代码:// 当发射物击中物体时会调用的函数。 void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit) { if (OtherActor != this && OtherComponent->IsSimulatingPhysics()) { OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint); } Destroy(); }
在
FPSProjectile
构造函数中,将以下代码行添加到BodyInstance.SetCollisionProfileName
下方:// 组件击中某物时调用的事件。 CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
现在
FPSProjectile.cpp
的内容应如下图所示://版权所有Epic Games, Inc。保留所有权利。 #include "FPSProjectile.h" // 设置默认值 AFPSProjectile::AFPSProjectile() { // 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 用球体进行简单的碰撞展示。 CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 将球体的碰撞配置文件名称设置为"Projectile"。 CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile")); // 组件击中某物时调用的事件。 CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit); // 设置球体的碰撞半径。 CollisionComponent->InitSphereRadius(15.0f); // 将根组件设置为碰撞组件。 RootComponent = CollisionComponent; } if (!ProjectileMovementComponent) { // 使用此组件驱动发射物的移动。 ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent); ProjectileMovementComponent->InitialSpeed = 3000.0f; ProjectileMovementComponent->MaxSpeed = 3000.0f; ProjectileMovementComponent->bRotationFollowsVelocity = true; ProjectileMovementComponent->bShouldBounce = true; ProjectileMovementComponent->Bounciness = 0.3f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } // 3 秒后删除发射物。 InitialLifeSpan = 3.0f; } // 当游戏开始或重生(Spawn)时被调用 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 每一帧都被调用 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 当发射物击中物体时会调用的函数。 void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit) { if (OtherActor != this && OtherComponent->IsSimulatingPhysics()) { OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint); } Destroy(); } // 初始化射击方向上发射物速度的函数。 void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
测试发射物碰撞
构建完成后,回到虚幻编辑器并打开FPSProject。
选择地面StaticMesh.
复制粘贴地面网格体。
确保已解锁比例锁定(缩放(Scale)行旁边的锁定图标),将地面网格体副本(Floor2)的缩放值设为{0.2, 0.2, 3.0}。
将地面网格体副本放在{320, 0, 170}位置。
向下滚动至物理(Physics)段,并选中模拟物理(Simulate Physics)复选框。
点击查看大图。
保存地图。
在关卡编辑器工具栏中点击运行(Play In)。
要确认发射物与立方体碰撞,请点击鼠标左键发射发射物,并使立方体在关卡内移动。
[
恭喜,你的发射物已完成!
按退出键(Escape)或在关卡编辑器中点击停止(Stop),退出在编辑器中运行(Play in Editor)(PIE)模式。
3.5 - 将十字准星添加到视口
在此步骤中,我们会把十字准星HUD元素添加到游戏,这样我们就可以实现发射物的瞄准功能。
导入十字准星资产
在开始之前,请通过以下链接下载并提取示例图像:
右键点击内容浏览器的文件框,打开 导入资产(Import Asset) 对话框
点击 ‘导入/游戏...(Import to /Game...)',打开 导入(Import) 对话框。
找到并选择 crosshair.TGA 图像文件。
点击 打开(Open),开始将图像文件导入项目。
点击 保存(Save) 按钮,保存导入的图像。
添加新的HUD类
在文件(File)菜单中,选择 新建C++类...(New C++ Class...),以选择新的父类。
以上操作将打开 选择父类(Choose Parent Class) 菜单。向下滚动,选择 HUD 作为父类,然后点击 下一步(Next)。
将新类命名为"FPSHUD",然后点击 创建类(Create Class)。
在 解决方案浏览器(Solution Explorer) 中找到
FPSHUD
类头文件,并打开FPSHUD.h
,添加以下受保护的变量:protected: // 将被绘制在屏幕中心。 UPROPERTY(EditDefaultsOnly) UTexture2D* CrosshairTexture;
在
FPSHUD.h
中添加以下函数声明:public: // HUD绘制的主要调用。 virtual void DrawHUD() override;
将以下头文件添加到FPSHUD.h中:
#include "Engine/Canvas.h"
FPSHUD.h文件内容应如下所示:
//版权所有Epic Games, Inc。保留所有权利。 #pragma once #include "CoreMinimal.h" #include "GameFramework/HUD.h" #include "Engine/Canvas.h" #include "FPSHUD.generated.h" /** * */ UCLASS() class FPSPROJECT_API AFPSHUD : public AHUD { GENERATED_BODY() public: // HUD绘制的主要调用。 virtual void DrawHUD() override; protected: // 将被绘制在屏幕中心。 UPROPERTY(EditDefaultsOnly) UTexture2D* CrosshairTexture; };
现在我们在
FPSHUD.cpp
中实现DrawHUD
函数:void AFPSHUD::DrawHUD() { Super::DrawHUD(); if (CrosshairTexture) { // 找出我们的画布的中心点。 FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f); // 偏移纹理大小的一半,以便纹理中心与画布中心对齐。 FVector2D CrossHairDrawPosition(Center.X - (CrosshairTexture->GetSurfaceWidth() * 0.5f), Center.Y - (CrosshairTexture->GetSurfaceHeight() * 0.5f)); // 在中心点绘制十字准星。 FCanvasTileItem TileItem(CrossHairDrawPosition, CrosshairTexture->Resource, FLinearColor::White); TileItem.BlendMode = SE_BLEND_Translucent; Canvas->DrawItem(TileItem); } }
在Visual Studio中保存
FPSHUD.h
和FPSHUD.cpp
。在 解决方案浏览器(Solution Explorer) 中找到 FPSProject。
右键点击 FPSProject 并选择 构建(Build),编译你的项目。
扩展CPP HUD类到蓝图
现在可以扩展CPP HUD类到蓝图了。如果你需要复习一下相关内容,请前往我们的C++和蓝图参考页面,了解更多有关扩展 C++类到蓝图的信息。
右键点击
FPSHUD
类,打开 C++类操作(C++ Class Actions) 菜单。点击 基于FPSHUD创建蓝图类(Create Blueprint class based on FPSHUD),打开 添加蓝图类(Add Blueprint Class) 对话框菜单。
将新的蓝图类命名为"BP_FPSHUD",选择蓝图(Blueprints)文件夹,然后点击 创建蓝图类(Create Blueprint Class) 按钮。
现在,在蓝图(Blueprints)文件夹内,你应该有一个新创建的
BP_FPSHUD
蓝图类。请确保在关闭蓝图编辑器之前保存你的
BP_FPSHUD
蓝图。
设置默认的HUD类
在 编辑(Edit) 菜单中,点击 项目设置(Project Settings)。
在 项目设置(Project Settings) 选项卡左侧的 项目(Project) 标题栏下,点击 地图和模式(Maps & Modes)。
在 默认HUD(Default HUD) 下拉菜单中,选择 BP_FPSHUD。
关闭 项目设置(Project Settings) 菜单。
返回并打开
BP_FPSHUD
蓝图编辑器。现在,点击位于蓝图编辑器的
FPSHUD
分段的下拉菜单,选择十字准星纹理。最后,在关闭蓝图编辑器之前保存
BP_FPSHUD
蓝图。
验证你的HUD
在关卡编辑器工具栏中点击 运行(Play) 按钮。现在,你应该可以使用新添加的十字准星进行发射物的瞄准操作。
在关卡编辑器中点击 停止(Stop) 按钮,退出在编辑器中运行(Play in Editor)(PIE)模式。
已完成分段代码
FPSProjectile.h
//版权所有Epic Games, Inc。保留所有权利。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "FPSProjectile.generated.h"
UCLASS()
class FPSPROJECT_API AFPSProjectile : public AActor
{
GENERATED_BODY()
public:
// 为此Actor的属性设置默认值
AFPSProjectile();
protected:
// 当游戏开始或重生(Spawn)时被调用
virtual void BeginPlay() override;
public:
// 每一帧都被调用
virtual void Tick(float DeltaTime) override;
// 当发射物击中物体时会调用的函数。
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
// 球体碰撞组件
UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
USphereComponent* CollisionComponent;
// 发射物移动组件
UPROPERTY(VisibleAnywhere, Category = Movement)
UProjectileMovementComponent* ProjectileMovementComponent;
// 发射物网格体
UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
UStaticMeshComponent* ProjectileMeshComponent;
// 发射物材质
UPROPERTY(VisibleDefaultsOnly, Category = Movement)
UMaterialInstanceDynamic* ProjectileMaterialInstance;
// 初始化射击方向上发射物速度的函数。
void FireInDirection(const FVector& ShootDirection);
};
FPSProjectile.cpp
//版权所有Epic Games, Inc。保留所有权利。
#include "FPSProjectile.h"
// 设置默认值
AFPSProjectile::AFPSProjectile()
{
// 将此actor设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。
PrimaryActorTick.bCanEverTick = true;
if (!RootComponent)
{
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
}
if (!CollisionComponent)
{
// 用球体进行简单的碰撞展示。
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
// 将球体的碰撞配置文件名称设置为"Projectile"。
CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
// 组件击中某物时调用的事件。
CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
// 设置球体的碰撞半径。
CollisionComponent->InitSphereRadius(15.0f);
// 将根组件设置为碰撞组件。
RootComponent = CollisionComponent;
}
if (!ProjectileMovementComponent)
{
// 使用此组件驱动发射物的移动。
ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
ProjectileMovementComponent->InitialSpeed = 3000.0f;
ProjectileMovementComponent->MaxSpeed = 3000.0f;
ProjectileMovementComponent->bRotationFollowsVelocity = true;
ProjectileMovementComponent->bShouldBounce = true;
ProjectileMovementComponent->Bounciness = 0.3f;
ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
}
if (!ProjectileMeshComponent)
{
ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
if (Mesh.Succeeded())
{
ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
}
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
if (Material.Succeeded())
{
ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
}
ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
ProjectileMeshComponent->SetupAttachment(RootComponent);
}
// 3 秒后删除发射物。
InitialLifeSpan = 3.0f;
}
// 当游戏开始或重生(Spawn)时被调用
void AFPSProjectile::BeginPlay()
{
Super::BeginPlay();
}
// 每一帧都被调用
void AFPSProjectile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
if (OtherActor != nullptr && OtherActor != this && OtherComponent != nullptr && OtherComponent->IsSimulatingPhysics())
{
OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
}
Destroy();
}
// 初始化射击方向上发射物速度的函数。
void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
{
ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
}
FPSCharacter.h
//版权所有Epic Games, Inc。保留所有权利。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "FPSProjectile.h"
#include "FPSCharacter.generated.h"
UCLASS()
class FPSPROJECT_API AFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
// 为此角色的属性设置默认值
AFPSCharacter();
protected:
// 当游戏开始或重生(Spawn)时被调用
virtual void BeginPlay() override;
// 要生成的发射物类。
UPROPERTY(EditAnywhere, Category = Projectile)
TSubclassOf<class AFPSProjectile> ProjectileClass;
public:
// 每一帧都被调用
virtual void Tick(float DeltaTime) override;
// 被调用,将功能与输入绑定
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// 处理用于前后移动的输入。
UFUNCTION()
void MoveForward(float Value);
// 处理用于左右移动的输入。
UFUNCTION()
void MoveRight(float Value);
// 按下键时,设置跳跃标记。
UFUNCTION()
void StartJump();
// 释放键时,清除跳跃标记。
UFUNCTION()
void StopJump();
// 发射发射物的函数。
UFUNCTION()
void Fire();
// FPS摄像机
UPROPERTY(VisibleAnywhere)
UCameraComponent* FPSCameraComponent;
// 第一人称网格体(手臂),仅对所属玩家可见。
UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
USkeletalMeshComponent* FPSMesh;
// 枪口相对于摄像机位置的偏移。
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
FVector MuzzleOffset;
};
FPSCharacter.cpp
//版权所有Epic Games, Inc。保留所有权利。
#include "FPSCharacter.h"
// 设置默认值
AFPSCharacter::AFPSCharacter()
{
// 将此角色设置为每帧调用Tick()。 如果不需要此特性,可以关闭以提升性能。
PrimaryActorTick.bCanEverTick = true;
// 创建第一人称摄像机组件。
FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
check(FPSCameraComponent != nullptr);
// 将摄像机组件附加到我们的胶囊体组件。
FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));
// 将摄像机置于略高于眼睛上方的位置。
FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
// 启用Pawn控制摄像机旋转。
FPSCameraComponent->bUsePawnControlRotation = true;
// 为所属玩家创建第一人称网格体组件。
FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
check(FPSMesh != nullptr);
// 仅所属玩家可以看见此网格体。
FPSMesh->SetOnlyOwnerSee(true);
//将 FPS 网格体连接到 FPS 摄像机。
FPSMesh->SetupAttachment(FPSCameraComponent);
// 禁用某些环境阴影,实现只有单个网格的感觉。
FPSMesh->bCastDynamicShadow = false;
FPSMesh->CastShadow = false;
// 所属玩家看不到常规(第三人称)全身网格体。
GetMesh()->SetOwnerNoSee(true);
}
// 当游戏开始或重生(Spawn)时被调用
void AFPSCharacter::BeginPlay()
{
Super::BeginPlay();
if (GEngine)
{
// 显示调试消息五秒。
// -1"键"值参数可以防止更新或刷新消息。
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter."));
}
}
// 每一帧都被调用
void AFPSCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// 被调用,将功能与输入绑定
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 设置"移动"绑定。
PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
// 设置"观看"绑定。
PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
// 设置"操作"绑定。
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
}
void AFPSCharacter::MoveForward(float Value)
{
// 找出"前进"方向,并记录玩家想向该方向移动。
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
void AFPSCharacter::MoveRight(float Value)
{
// 找出"右侧"方向,并记录玩家想向该方向移动。
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
void AFPSCharacter::StartJump()
{
bPressedJump = true;
}
void AFPSCharacter::StopJump()
{
bPressedJump = false;
}
void AFPSCharacter::Fire()
{
// 试图发射发射物。
if (ProjectileClass)
{
// 获取摄像机变换。
FVector CameraLocation;
FRotator CameraRotation;
GetActorEyesViewPoint(CameraLocation, CameraRotation);
// 设置MuzzleOffset,在略靠近摄像机前生成发射物。
MuzzleOffset.Set(100.0f, 0.0f, 0.0f);
// 将MuzzleOffset从摄像机空间变换到世界空间。
FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
// 使目标方向略向上倾斜。
FRotator MuzzleRotation = CameraRotation;
MuzzleRotation.Pitch += 10.0f;
UWorld* World = GetWorld();
if (World)
{
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
// 在枪口位置生成发射物。
AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
if (Projectile)
{
// 设置发射物的初始轨迹。
FVector LaunchDirection = MuzzleRotation.Vector();
Projectile->FireInDirection(LaunchDirection);
}
}
}
}
FPSHUD.h
//版权所有Epic Games, Inc。保留所有权利。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "Engine/Canvas.h"
#include "FPSHUD.generated.h"
/**
*
*/
UCLASS()
class FPSPROJECT_API AFPSHUD : public AHUD
{
GENERATED_BODY()
public:
// HUD绘制的主要调用。
virtual void DrawHUD() override;
protected:
// 将被绘制在屏幕中心。
UPROPERTY(EditDefaultsOnly)
UTexture2D* CrosshairTexture;
};
FPSHUD.cpp
//版权所有Epic Games, Inc。保留所有权利。
#include "FPSHUD.h"
void AFPSHUD::DrawHUD()
{
Super::DrawHUD();
if (CrosshairTexture)
{
// 找出我们的画布的中心点。
FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f);
// 偏移纹理大小的一半,以便纹理中心与画布中心对齐。
FVector2D CrossHairDrawPosition(Center.X - (CrosshairTexture->GetSurfaceWidth() * 0.5f), Center.Y - (CrosshairTexture->GetSurfaceHeight() * 0.5f));
// 在中心点绘制十字准星。
FCanvasTileItem TileItem(CrossHairDrawPosition, CrosshairTexture->Resource, FLinearColor::White);
TileItem.BlendMode = SE_BLEND_Translucent;
Canvas->DrawItem(TileItem);
}
}
祝贺你!你已经学会了如何:
✓ 将发射物添加到游戏
✓ 实现射击
✓ 设置发射物的碰撞和生命周期
✓ 让发射物与世界交互
✓ 将十字准星添加到视口
现在,你可以准备在下一分段中学习如何为角色添加动画。