目录
1.新建项目
新建BatteryMan类
需要继承自Character
2 功能实现
2.1 在BattetyMan.h中添加如下头文件:
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "Blueprint/UserWidget.h"
需要注意的是,这些头文件需要添加在#include "BatteryMan.generated.h" 之前,否则会编译出错。
Camera代表玩家的视角,可以理解为玩家如何看待这个世界,CameraComponent则提供关于相机的属性信息。
Components是组件的意思,CapsuleComponent是一个通常用于简单碰撞的胶囊。
StaticMeshComponent是静态网格组件,是由一组静态的多边形组成的几何体。
InputComponent用于实现一个用于输入绑定的Actor组件。
CharacterMovementComponent处理相关角色所有者的运动逻辑。
Controller是Pawn的灵魂,用来控制Pawn的行为。
SpringArmComponent将子类与父代保持在一个固定的距离上,如果中间有遮挡,它将切换为子类,而在没有碰撞时则弹回。
UserWidget是用户界面UI
SprintArmComponent可能难以理解,如下图所示,当没有遮挡的时候镜头是这样:
当人物被遮挡的时候,镜头会自动切到人物上:
2.2 添加属性
在添加属性之前首先介绍一下UPROPERTY:
声明属性时,属性说明符 可被添加到声明,以控制属性与引擎和编辑器诸多方面的相处方式。
使用方式:
UPROPERTY(属性标签, 属性元标签,类别)
属性常见的标签有:
VisibleAnywhere:说明此属性在所有属性窗口中可见,但无法被编辑。此说明符与"Edit"说明符不兼容。
BlueprintReadOnly:此属性可由蓝图读取,但不能被修改。
EditAnywhere:说明此属性可通过属性窗口在原型和实例上进行编辑。此说明符与所有"可见"说明符不可同时用。
更多详细的属性参考官方文档:https://docs.unrealengine.com/zh-CN/ProgrammingAndScripting/GameplayArchitecture/Properties/Specifiers/index.html
了解了UPROPERTY属性后我们在头文件中添加如下属性:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
UCameraComponent* FollowCamera;
SpringArmComponent将子类与父代保持在一个固定的距离上,如果中间有遮挡,它将切换为子类,而在没有碰撞时则弹回。
UCameraComponent是摄像机组件,将其挂载到SpringArmComponent之下,这样就可以实现当发生遮挡,视角切换的功能。
在UE中新建文件夹BluePrints,并在BluePrints中右键新建BatteryMan的蓝图类
双击打开刚刚新建的蓝图类,选择Mesh的Skeleten
旋转任务,将任务朝向对准为Arrow的方向
让后调整人物的位置,以及胶囊体的大小,把人物放到胶囊体内:
为了让这个蓝图类更具有通用性,我们在BatteryMan的构造函数BatteryMan::ABatteryMan()中设置胶囊体的大小:
GetCapsuleComponent()->InitCapsuleSize(42.0f, 96.0f);
在旋转的时候我们希望镜头旋转,而不是人物旋转,所以这里需要把用户的旋转功能关闭:
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
在构造函数ABatteryMan::ABatteryMan()中将摄像机组件初始化
GetCharacterMovement()->bOrientRotationToMovement = true; //如果为真,则使用相机方向旋转角色,使用RotationRate作为角色旋转变化的速率。
GetCharacterMovement()->RotationRate = FRotator(0.0f, 600.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 900.0f;
GetCharacterMovement()->AirControl = 0.0f;//在空中控制角色的灵敏度
//创建USpringArmComponent组件
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f;//300 cm
CameraBoom->bUsePawnControlRotation = true;//运动时控制视角
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
//附加到CameraBoom上
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
编译后在蓝图中会出现如下组件:
右键新建GameMode C++类
新建该类的蓝图,这里蓝图类的名称为BatteryMan_GameMode_BP:
接下来进行如下设置,设置游戏开始控制的角色蓝图类以及游戏操作模式的蓝图类:
设置完以后运行游戏,会出现如下:
添加 bDead属性,并在构造函数中设置为false,表示目标是否死亡。
bool bDead;
2.3 添加移动的函数
void MoveForward(float Axis);
void MoveRight(float Axis);
void ABatteryMan::MoveForward(float Axis)
{
if (!bDead)
{
const FRotator rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, rotation.Yaw, 0);//Z轴旋转的角度
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Axis);
}
}
void ABatteryMan::MoveRight(float Axis)
{
if (!bDead)
{
const FRotator rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, rotation.Yaw, 0);
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Axis);
}
}
在BatteryMan::SetupPlayerInputComponent函数中绑定输入动作:
// Called to bind functionality to input
void ABatteryMan::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAxis("MoveForward", this, &ABatteryMan::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ABatteryMan::MoveRight);
}
实现运动:
void ABatteryMan::MoveForward(float Axis)
{
if (!bDead)
{
const FRotator rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(rotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Axis);
}
}
void ABatteryMan::MoveRight(float Axis)
{
//UE_LOG(LogTemp, Warning, TEXT("MoveRight %f"),Axis);
if (!bDead)
{
const FRotator rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(rotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Axis);
}
}
添加加速功能,在input中 添加输入:
在BatteryMan.h中添加
void Accelerate();
void DeAccelerate();
并在ABatteryMan::SetupPlayerInputComponent绑定事件:
PlayerInputComponent->BindAction("Accelerate", IE_Pressed, this, &ABatteryMan::Accelerate);
PlayerInputComponent->BindAction("Accelerate", IE_Released, this, &ABatteryMan::DeAccelerate);
实现函数:
void ABatteryMan::Accelerate()
{
GetCharacterMovement()->MinAnalogWalkSpeed = 6000;
}
void ABatteryMan::DeAccelerate()
{
GetCharacterMovement()->MinAnalogWalkSpeed = 0;
}
2.4 添加可以捡起的小球
右键添加蓝图类,基类为Actor,并取名为PickbleItem_BP
打开PickbleItem_BP并添加球体Sphere,与球体碰撞体,调整好大小并设置球体的Tag为Recharge,用于在代码中判断是否触碰到小球:
另外一个需要设置的地方是把小球的Simulate Pysics属性勾上
在BatteryMan.h中添加
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
float Power;//表示当前的电量
UPROPERTY(EditAnywhere)
float Power_Threshold;//表示每隔一定时间减少多少电量
继续添加处理碰撞的UFUNCTION函数
//HitComp:碰撞到了什么组件
//OtherActor:碰撞到的Actor
UFUNCTION()
void OnBeginOverlap(class UPrimitiveComponent* HitComp,
class AActor* OtherActor, class UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
设置碰撞响应
在CPP中实现碰撞函数:
void ABatteryMan::OnBeginOverlap(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
UE_LOG(LogTemp, Warning, TEXT("Collided with"));
if (OtherActor->ActorHasTag("Recharge"))
{
Power += 10.0f;
if (Power > 100.0f)
Power = 100.0f;
OtherActor->Destroy();
}
}
在开始游戏时绑定碰撞函数:
void ABatteryMan::BeginPlay()
{
Super::BeginPlay();
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &ABatteryMan::OnBeginOverlap);
}
2.5 UI的编写
右键新建界面蓝图类,并命名为Player_Power_UI
打开ui并添加Progress Bar
在BatteryMan.h中添加Widget属性
UPROPERTY(EditAnywhere, Category = "UI HUD")
TSubclassOf<UUserWidget> Player_Power_Widget_Class;
UUserWidget* Player_Power_Widget;
在Battery_Collector.Build.cs添加UMG支持
编译后,在BatteryMan可以看到UI_HUB的属性,将类设置为刚刚新建的蓝图类UI:
在BatteryMan的BeginPlay中添加如下代码:
if (Player_Power_Widget_Class != nullptr) {
Player_Power_Widget = CreateWidget(GetWorld(), Player_Power_Widget_Class);
Player_Power_Widget->AddToViewport();
}
在UI界面中绑定ProgressBar的槽函数
并做如下的操作:
Tick是每一帧都会执行的函数,在Tick函数中对Power进行递减:
如果Power小于等于0则游戏结束,并重新开始游戏,添加头文件,并在Tick函数中判断是否死亡:
#include "BatteryMan.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet/GameplayStatics.h"
此外还需要对角色做如下设置,使得在SimulatePhysics为True的时候角色倒下:
首先新建Physics文件夹,并在该文件夹内新建Physics Asset蓝图类,并命名为MyRagDollFemale
选择:
在BatteryMan_BP中对该类进行设置:
添加重新开始游戏的方法RestartGame
void ABatteryMan::RestartGame()
{
UGameplayStatics::OpenLevel(this, FName(*GetWorld()->GetName()),false);
}
补充Tick()函数:
2.6 自动生成小球
在BatteryMan_GameMode.h中添加如下函数:
UCLASS()
class CUETEST_API ABatteryMan_GameMode : public AGameMode
{
GENERATED_BODY()
ABatteryMan_GameMode();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
UPROPERTY(EditAnywhere)
TSubclassOf<APawn> PlayerRecharge;
float SPawn_Z = 500.0f;
UPROPERTY(EditAnywhere)
float SPawn_X_Min;
UPROPERTY(EditAnywhere)
float SPawn_X_Max;
UPROPERTY(EditAnywhere)
float SPawn_Y_Min;
UPROPERTY(EditAnywhere)
float SPawn_Y_Max;
void SpawnPlaerRecharge();
};
通过俯视图得到各个方向的最大值最小值,随便拖一个组件上去,然后拖动查看位置:
在GameMode蓝图类中做如下设置:
在BatteryMan_GameMode_BP中对PlayerRecharge进行初始化
BatteryMan_GameMode.cpp中添加实现:
// Fill out your copyright notice in the Description page of Project Settings.
#include "BatteryMan_GameMode.h"
#include "GameFramework/Actor.h"
ABatteryMan_GameMode::ABatteryMan_GameMode()
{
PrimaryActorTick.bCanEverTick = true;
}
void ABatteryMan_GameMode::BeginPlay()
{
Super::BeginPlay();
FTimerHandle UnsedHandle;
GetWorldTimerManager().SetTimer(UnsedHandle, this, &ABatteryMan_GameMode::SpawnPlaerRecharge, FMath::RandRange(1,3), true);
}
void ABatteryMan_GameMode::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
}
void ABatteryMan_GameMode::SpawnPlaerRecharge()
{
float RandX = FMath::RandRange(SPawn_X_Min, SPawn_X_Max);
float RandY = FMath::RandRange(SPawn_Y_Min, SPawn_Y_Max);
FVector SpawnPosition = FVector(RandX, RandY, SPawn_Z);
FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
GetWorld()->SpawnActor(PlayerRecharge,&SpawnPosition,&SpawnRotation);
}
至此,游戏已经完成
3 详细代码
BatteryMan.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "Blueprint/UserWidget.h"
#include "BatteryMan.generated.h"
UCLASS()
class CUETEST_API ABatteryMan : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ABatteryMan();
void MoveForward(float Axis);
void MoveRight(float Axis);
void Accelerate();
void DeAccelerate();
bool bDead;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
UCameraComponent* FollowCamera;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
float Power;//表示当前的电量
UPROPERTY(EditAnywhere)
float Power_Threshold;//表示吃一个小球涨多少电量
//HitComp:碰撞到了什么组件
//OtherActor:碰撞到的Actor
UFUNCTION()
void OnBeginOverlap(class UPrimitiveComponent* HitComp,
class AActor* OtherActor, class UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UPROPERTY(EditAnywhere, Category = "UI HUD")
TSubclassOf<UUserWidget> Player_Power_Widget_Class;
UUserWidget* Player_Power_Widget;
void RestartGame();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
BatteryMan.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BatteryMan.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet/GameplayStatics.h"
// Sets default values
ABatteryMan::ABatteryMan()
{
// 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;
GetCapsuleComponent()->InitCapsuleSize(42.0f, 96.0f);
//bUseControllerRotationPitch = false;
//bUseControllerRotationYaw = false;
//bUseControllerRotationRoll = false;
GetCharacterMovement()->bOrientRotationToMovement = true; //如果为真,则使用相机方向旋转角色,使用RotationRate作为角色旋转变化的速率。
GetCharacterMovement()->RotationRate = FRotator(0.0f, 600.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 900.0f;
GetCharacterMovement()->AirControl = 0.0f;//在空中控制角色的灵敏度
//创建USpringArmComponent组件
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f;//300 cm
CameraBoom->bUsePawnControlRotation = true;//运动时控制视角
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
//附加到CameraBoom上
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
bDead = false;
Power = 100;
Power_Threshold = 1;
}
// Called when the game starts or when spawned
void ABatteryMan::BeginPlay()
{
Super::BeginPlay();
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &ABatteryMan::OnBeginOverlap);
if (Player_Power_Widget_Class != nullptr) {
Player_Power_Widget = CreateWidget(GetWorld(), Player_Power_Widget_Class);
Player_Power_Widget->AddToViewport();
}
}
void ABatteryMan::RestartGame()
{
UGameplayStatics::OpenLevel(this, FName(*GetWorld()->GetName()),false);
}
// Called every frame
void ABatteryMan::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
Power -= DeltaTime * Power_Threshold;
if (Power <= 0) {
bDead = true;
//游戏结束
GetMesh()->SetSimulatePhysics(true);
FTimerHandle UnsedHandle;
GetWorldTimerManager().SetTimer(UnsedHandle, this, &ABatteryMan::RestartGame, 3.0f, false);
}
}
// Called to bind functionality to input
void ABatteryMan::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAction("Accelerate", IE_Pressed, this, &ABatteryMan::Accelerate);
PlayerInputComponent->BindAction("Accelerate", IE_Released, this, &ABatteryMan::DeAccelerate);
PlayerInputComponent->BindAxis("MoveForward", this, &ABatteryMan::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ABatteryMan::MoveRight);
}
void ABatteryMan::OnBeginOverlap(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
UE_LOG(LogTemp, Warning, TEXT("Collided with"));
if (OtherActor->ActorHasTag("Recharge"))
{
Power += 10.0f;
if (Power > 100.0f)
Power = 100.0f;
OtherActor->Destroy();
}
}
void ABatteryMan::MoveForward(float Axis)
{
if (!bDead)
{
const FRotator rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(rotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Axis);
}
}
void ABatteryMan::MoveRight(float Axis)
{
//UE_LOG(LogTemp, Warning, TEXT("MoveRight %f"),Axis);
if (!bDead)
{
const FRotator rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(rotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Axis);
}
}
void ABatteryMan::Accelerate()
{
GetCharacterMovement()->MinAnalogWalkSpeed = 6000;
}
void ABatteryMan::DeAccelerate()
{
GetCharacterMovement()->MinAnalogWalkSpeed = 0;
}
BatteryMan_GameMode.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "BatteryMan_GameMode.generated.h"
/**
*
*/
UCLASS()
class CUETEST_API ABatteryMan_GameMode : public AGameMode
{
GENERATED_BODY()
ABatteryMan_GameMode();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
UPROPERTY(EditAnywhere)
TSubclassOf<APawn> PlayerRecharge;
float SPawn_Z = 500.0f;
UPROPERTY(EditAnywhere)
float SPawn_X_Min;
UPROPERTY(EditAnywhere)
float SPawn_X_Max;
UPROPERTY(EditAnywhere)
float SPawn_Y_Min;
UPROPERTY(EditAnywhere)
float SPawn_Y_Max;
void SpawnPlaerRecharge();
};
BatteryMan_GameMode.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BatteryMan_GameMode.h"
#include "GameFramework/Actor.h"
ABatteryMan_GameMode::ABatteryMan_GameMode()
{
PrimaryActorTick.bCanEverTick = true;
}
void ABatteryMan_GameMode::BeginPlay()
{
Super::BeginPlay();
FTimerHandle UnsedHandle;
GetWorldTimerManager().SetTimer(UnsedHandle, this, &ABatteryMan_GameMode::SpawnPlaerRecharge, FMath::RandRange(1,3), true);
}
void ABatteryMan_GameMode::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
}
void ABatteryMan_GameMode::SpawnPlaerRecharge()
{
float RandX = FMath::RandRange(SPawn_X_Min, SPawn_X_Max);
float RandY = FMath::RandRange(SPawn_Y_Min, SPawn_Y_Max);
FVector SpawnPosition = FVector(RandX, RandY, SPawn_Z);
FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
GetWorld()->SpawnActor(PlayerRecharge,&SpawnPosition,&SpawnRotation);
}
CUETest.Build.cs
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class CUETest : ModuleRules
{
public CUETest(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay","UMG" });
}
}