射击游戏案例(三)

一、添加主界面和准星

添加射击游戏的瞄准准星,方法有同样可以只通过蓝图去创建使用,此次会同介绍动画蓝图一样,通过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!的消息。

点击开火
连续开火

 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);
}

 

  • 37
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡西莫多说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值