一、添加主界面和准星
添加射击游戏的瞄准准星,方法有同样可以只通过蓝图去创建使用,此次会同介绍动画蓝图一样,通过UEC++的方式去创建Widget类,将Widget类中的变量和蓝图中的UserWidget变量绑定在一起进行使用。
不过在此之前,需要先创建一个MainUI作为游戏主界面,之后再创建一个CorssHairUI作为准星的界面,将准星的界面添加到主界面上。
1.创建主界面并修改类设置
创建HUD类
这个HUD作为Gameplay中的一员,在项目设置中的HUD一项,会改为我自己创建的HUD类。
创建MainUI
基于MainUI创建控件蓝图
2.将主界面生成在游戏中
HUD类创建了,MainUI类也创建好了,现在要在HUD中将主界面创建出来。
ShootingGameHUD.h
private:
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=UI,meta=(AllowPrivateAccess="true"))
TSubclassOf<UUserWidget> MainUIAsset;
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=UI,meta=(AllowPrivateAccess="true"))
UMainUI* MainUI;
public:
FORCEINLINE UMainUI* GetMainUI() const {return MainUI;}
ShootingGameHUD.cpp
AShootingGameHUD::AShootingGameHUD()
{
static ConstructorHelpers::FClassFinder<UUserWidget> UIAsset(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/Blueprints/UI/WBP_MainUI.WBP_MainUI_C'"));
if (UIAsset.Succeeded())
{
MainUIAsset = UIAsset.Class;
}
}
void AShootingGameHUD::BeginPlay()
{
Super::BeginPlay();
if (MainUIAsset)
{
MainUI = CreateWidget<UMainUI>(GetWorld(),MainUIAsset);
if (MainUI)
{
MainUI->AddToViewport();
}
}
}
这里我使用的是在构造函数中通过文件路径静态寻找类的方式,也可以通过在蓝图中赋值的方式创建,这个时候要将声明MainUIAsset的属性宏的VisibleAnywhere修改成EditAnywhere,之后在BP_ShootingGameHUD中为其赋值。
FClassFinder其后的文件路径在引擎中选中要赋值的类,右键->复制引用。
添加UMG的模块引用
在项目名.build.cs文件下,要添加UMG模块,不然的话会导致编译不通过。
3.创建准星并修改类设置
考虑到后续会修改准星的实现类型,这里我并没有创建单独的一个准星的Widget类,只是创建了一个UserWidget蓝图,去添加准星的图片,再将这个Widget放到MainUI中。
准星添加到MainUI中
编译如果没问题的话,运行,准星就会显示到界面中,如果觉得角色位置不好,可以自行调整。
4.对应代码
ShootingGameHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "ShootingGameHUD.generated.h"
class UMainUI;
class UUserWidget;
/**
* HUD
*/
UCLASS()
class SHOOTINGGAME_API AShootingGameHUD : public AHUD
{
GENERATED_BODY()
public:
AShootingGameHUD();
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=UI,meta=(AllowPrivateAccess="true"))
TSubclassOf<UUserWidget> MainUIAsset;
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=UI,meta=(AllowPrivateAccess="true"))
UMainUI* MainUI;
public:
FORCEINLINE UMainUI* GetMainUI() const {return MainUI;}
};
ShootingGameHUD.cpp
#include "Gameplay/ShootingGameHUD.h"
#include "Blueprint/UserWidget.h"
#include "UI/MainUI.h"
AShootingGameHUD::AShootingGameHUD()
{
static ConstructorHelpers::FClassFinder<UUserWidget> UIAsset(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/Blueprints/UI/WBP_MainUI.WBP_MainUI_C'"));
if (UIAsset.Succeeded())
{
MainUIAsset = UIAsset.Class;
}
}
void AShootingGameHUD::BeginPlay()
{
Super::BeginPlay();
if (MainUIAsset)
{
MainUI = CreateWidget<UMainUI>(GetWorld(),MainUIAsset);
if (MainUI)
{
MainUI->AddToViewport();
}
}
}
二、开火射击
目前使用到的动画是步枪的动画,射击方式为可以点按开火,可以长按连续开火。
1.开火的输入映射
项目设置中添加开火的输入映射。
2.开火射击的实现思路和代码
思路:
上述思路提到过的变量和方法:
这里是放到了BaseCharacter.h中
/*
* 射击
*/
void FireButtonPressed();//开火按下
void FireButtonReleased();//开火松开
void StartFireTimer();//开始射击的定时器
UFUNCTION()
void AutoFireReset();//连续开火重置
virtual void FireWeapon();//开火
先声明了一个开火按下和开火松开的方法,其中有一个bFireButtonPressed变量判断是否在开火,按下的时候设置为true,松开的时候设置为false。
按下的时候同时也会去调用StartFireTimer,这里使用定时器,在这个方法里面有一个bShouldFire变量控制是否能够开发,一开始设置为true,是的话bShouldFire设置为false,执行FireWeapon,FireWeapon会做一些攻击射线检测、音效、特效等工作;然后执行定时器,定时调用是否能够重置AutoFireReset;而在AutoFireReset中,bShouldFire设置为true,可以重新开火,如果仍然是鼠标按下的状态,就会返回去继续执行StartFireTimer,如果不是就会把定时器清除,不再继续执行。
说明:AutoFireReset前加了一个方法宏标记,是因为这个方法是要在定时器中引用,只有用FUNCTION()标记了引擎才能正确调用。FireWeapon前加了一个virtual是把这个方法声明为虚函数,这样可以在子类中去重写,去实现不同子类不同的射击表现,加不加virtual可根据自己的需求去调整。
/*
* 是否可以开火
*/
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
bool bShouldFire;
/**
* 开火按钮是否按下
*/
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
bool bFireButtonPressed;
/**
* 自动开火重置的定时器句柄
*/
FTimerHandle FireAutoResetTimerHandle;
BaseCharacter.cpp
void ABaseCharacter::FireButtonPressed()
{
bFireButtonPressed = true;
StartFireTimer();
}
void ABaseCharacter::FireButtonReleased()
{
bFireButtonPressed = false;
}
void ABaseCharacter::StartFireTimer()
{
if (bShouldFire)
{
bShouldFire = false;
FireWeapon();
GetWorldTimerManager().SetTimer(FireAutoResetTimerHandle,this,&ABaseCharacter::AutoFireReset,0.1f,false);
}
}
void ABaseCharacter::AutoFireReset()
{
bShouldFire = true;
if (bFireButtonPressed)
{
StartFireTimer();
}
else
{
GetWorldTimerManager().ClearTimer(FireAutoResetTimerHandle);
}
}
void ABaseCharacter::FireWeapon()
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1,5.0f,FColor::Green,TEXT("Fire!"));
}
}
void ABaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward",this,&ABaseCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight",this,&ABaseCharacter::MoveRight);
PlayerInputComponent->BindAxis("Turn",this,&ABaseCharacter::Turn);
PlayerInputComponent->BindAxis("LookUp",this,&ABaseCharacter::LookUp);
PlayerInputComponent->BindAction("Jump",IE_Pressed,this,&ABaseCharacter::Jump);
PlayerInputComponent->BindAction("Jump",IE_Released,this,&ABaseCharacter::StopJumping);
//开火
PlayerInputComponent->BindAction("FireButton",IE_Pressed,this,&ABaseCharacter::FireButtonPressed);
PlayerInputComponent->BindAction("FireButton",IE_Released,this,&ABaseCharacter::FireButtonReleased);
}
记得要在SetupPlayerInputComponent去绑定开火的映射。
在StartFireTimer中SetTimer的参数,0.1f是一直按下的时候定时器会每隔0.1秒执行一次,这个可以根据自己的需求更改,也可以声明为一个变量,使用属性宏标记EditAnywhere,后续可以在引擎中直接修改。
在FireWeapon中暂时使用打印的方式,编译运行之后,如果我每隔一秒去点击鼠标左键,共按下三次,引擎的打印会出现三个Fire!的消息,如果一直按下鼠标左键,在没有子弹数量限制的情况下,会一直打印Fire!的消息。
![](https://img-blog.csdnimg.cn/direct/158008fcf7764101b21a1c700c32b90b.png)
![](https://img-blog.csdnimg.cn/direct/72b93cd2c2e942f0baaf1fde4e51d997.png)
3.对应代码
3.1 BaseCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BaseCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
UCLASS()
class SHOOTINGGAME_API ABaseCharacter : public ACharacter
{
GENERATED_BODY()
public:
ABaseCharacter();
protected:
virtual void BeginPlay() override;
/*
* 角色移动和视角查看
*/
void MoveForward(float Value);
void MoveRight(float Value);
void Turn(float Value);
void LookUp(float Value);
/*
* 射击
*/
void FireButtonPressed();//开火按下
void FireButtonReleased();//开火松开
void StartFireTimer();//开始射击的定时器
UFUNCTION()
void AutoFireReset();//连续开火重置
virtual void FireWeapon();//开火
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
/*
* 摄像机臂
*/
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Components,meta=(AllowPrivateAccess="true"))
USpringArmComponent* CameraBoom;
/*
* 摄像机
*/
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Components,meta=(AllowPrivateAccess="true"))
UCameraComponent* FollowCamera;
/*
* 是否可以开火
*/
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
bool bShouldFire;
/**
* 开火按钮是否按下
*/
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
bool bFireButtonPressed;
/**
* 自动开火重置的定时器句柄
*/
FTimerHandle FireAutoResetTimerHandle;
};
3.2 BaseCharacter.cpp
#include "ShootingGame/Public/Gameplay/BaseCharacter.h"
#include"Camera/CameraComponent.h"
#include"GameFramework/SpringArmComponent.h"
ABaseCharacter::ABaseCharacter()
{
PrimaryActorTick.bCanEverTick = true;
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom);
bShouldFire = true;
bFireButtonPressed = false;
}
void ABaseCharacter::BeginPlay()
{
Super::BeginPlay();
}
void ABaseCharacter::MoveForward(float Value)
{
if (Controller && Value != 0.0f)
{
const FRotator ControlRotation = Controller->GetControlRotation();
const FRotator NewRotation = FRotator(0.0f,ControlRotation.Yaw,0.0f);
const FVector Dir = FRotationMatrix(NewRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Dir,Value);
}
}
void ABaseCharacter::MoveRight(float Value)
{
if (Controller && Value != 0.0f)
{
const FRotator ControlRotation = Controller->GetControlRotation();
const FRotator NewRotation = FRotator(0.0f,ControlRotation.Yaw,0.0f);
const FVector Dir = FRotationMatrix(NewRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Dir,Value);
}
}
void ABaseCharacter::Turn(float Value)
{
AddControllerYawInput(Value*0.25f);
}
void ABaseCharacter::LookUp(float Value)
{
const FRotator ControlRotation = Controller->GetControlRotation();
const float PitchValue = ControlRotation.Pitch;
//限制上下视角
if (PitchValue >= 30.0f && PitchValue < 180.0f && Value < 0.0f)
{
return;
}
if (PitchValue > 180.0f && PitchValue <= 280.0f && Value > 0.0f)
{
return;
}
AddControllerPitchInput(Value*0.25f);
}
void ABaseCharacter::FireButtonPressed()
{
bFireButtonPressed = true;
StartFireTimer();
}
void ABaseCharacter::FireButtonReleased()
{
bFireButtonPressed = false;
}
void ABaseCharacter::StartFireTimer()
{
if (bShouldFire)
{
bShouldFire = false;
FireWeapon();
GetWorldTimerManager().SetTimer(FireAutoResetTimerHandle,this,&ABaseCharacter::AutoFireReset,0.1f,false);
}
}
void ABaseCharacter::AutoFireReset()
{
bShouldFire = true;
if (bFireButtonPressed)
{
StartFireTimer();
}
else
{
GetWorldTimerManager().ClearTimer(FireAutoResetTimerHandle);
}
}
void ABaseCharacter::FireWeapon()
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1,5.0f,FColor::Green,TEXT("Fire!"));
}
}
void ABaseCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ABaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward",this,&ABaseCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight",this,&ABaseCharacter::MoveRight);
PlayerInputComponent->BindAxis("Turn",this,&ABaseCharacter::Turn);
PlayerInputComponent->BindAxis("LookUp",this,&ABaseCharacter::LookUp);
PlayerInputComponent->BindAction("Jump",IE_Pressed,this,&ABaseCharacter::Jump);
PlayerInputComponent->BindAction("Jump",IE_Released,this,&ABaseCharacter::StopJumping);
//开火
PlayerInputComponent->BindAction("FireButton",IE_Pressed,this,&ABaseCharacter::FireButtonPressed);
PlayerInputComponent->BindAction("FireButton",IE_Released,this,&ABaseCharacter::FireButtonReleased);
}