您的位置:首页 > 编程语言 > C语言/C++

Epic 官方视频教程《 Battery Collector》源码+超详细注释【C++】【UE4】

2017-10-20 00:06 2226 查看
视频教程链接:

优酷链接

油管链接 【需要梯子】

首先来看效果图:

1. 电池随机从天空掉落,玩家按C键收集电池的能量(闪电粒子效果)来补充血条(每秒都会自动衰减)



2.玩家的颜色随着血条的减少,逐渐变黑



3.当血条为空时,玩家(黑色的那一坨)死亡,游戏结束;如果玩家提前集满血条则获胜。



以下是完整的源代码,并配套详细解释:


Pickup.h

[cpp] view
plain copy

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once// 防止多次引用头文件

#include "GameFramework/Actor.h"

#include "Pickup.generated.h" // 必须是最后 include 的头文件,它是UHT(Unreal Header Tool)根据你声明的宏自动生成的

// 该宏将类暴露给 Unreal 的反射系统,允许我们在运行时检查和迭代对象的属性(比如 GC 中的对对象引用计数的管理)

// 1. 意味着当你创建一个对象,UE4会帮助你进行内存管理(智能指针来自动对垃圾内存进行回收),但前提是遵循了 UE4 的构造/销毁规范(比如自己手动 new 出的对象就不能被 UE4 回收);

// 2. 默认使得该类可以被编辑器和蓝图访问;

// 3. 如果将 Bluerpintable 改为 Blueprinttype,那么该类在蓝图中就只能作为 variable 访问了。(还有 NotBlueprintType 类型;Blueprintable 和 BlueprintType 兼容)

UCLASS(Blueprintable)

class BATTERYCOLLECTOR_API APickup : public AActor // BATTERYCOLLECTOR_API也是由UHT创建的宏,确保该类正确输出到 DLL 中

{

GENERATED_BODY()// 导入一些 UE 系统需要的自动生成的方法。与 GENERATED_CLASS_BODY 的区别 http://blog.csdn.net/a359877454/article/details/52511893
// 为类声明一个 Log Category

DECLARE_LOG_CATEGORY_CLASS(Pickup, Log, All);

public:

// Sets default values for this actor's properties

APickup();

// Called when the game starts or when spawned

virtual void BeginPlay() override;

// Called every frame

virtual void Tick( float DeltaSeconds ) override;

// 返回 pickup 模型(注意,内联方法和UFUNCTION不兼容)

FORCEINLINE class UStaticMeshComponent* GetMesh() const { return PickupMesh; }

// BlueprintPure / BlueprintCallable 表示两者都可以从蓝图端被调用,

// BlueprintPure 意味着该方法不会修改成员数据,且只要 output pin 被使用其就会执行(它也没有exec input pin);

// 但 BlueprintCallable 的执行需要连接exec input pin ,然后结果由 output pin 输出。

UFUNCTION(BlueprintPure, Category = "Pickup")

bool IsActive();

// 安全地修改 bIsActive 成员【UFUNCTION 的好处:1.通过添加一些额外的代码,使其可以被蓝图重写;2.在游戏运行时,可以通过命令行来调用,便于调试】

UFUNCTION(BlueprintCallable, Category = "Pickup")

void SetActive(bool NewPickupState);

// 当 pickup 被收集时需要调用的方法

// BlueprintNativeEvent:表示该方法既在 C++ 中定义一些行为,也可以被蓝图中定义一些行为 (C++方法为蓝图同名方法的父方法)

// 注意和 BlueprintImplementableEvents 的区别(既可以通知蓝图有C++层的事件发生,还可以为它额外提供一些信息)

UFUNCTION(BlueprintNativeEvent)

void WasCollected();

virtual void WasCollected_Implementation(); // ❤

protected:

// pickup 是否被激活

bool bIsActive;

private:

// 关卡中的可拾取物(静态模型)—— pickup

// 1. VisibleAnywhere:表示 PickupMesh 属性在 Class Defaults 和它的实例(将蓝图拖动到关卡中) 中都是可见的,但不可编辑【 参见 search “放大镜”右侧的 "Open Selection in Property Matrix" 图标】

// http://blog.csdn.net/xi_niuniu/article/details/54409648
//

// 2. 如果没有 AllowPrivateAccess 的话,BlueprintReadOnly 在 private 下就会编译失败(通常是 public),其作用使得 PickMesh 在蓝图编辑器中可以 Get 到

// 3. BlueprintReadOnly :表示在蓝图下只能 Get,不能 Set; BlueprintDefaultsOnly: 表示在运行前蓝图中的默认值是可以修改的,但运行中蓝图就不能修改它

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pickup", meta = (AllowPrivateAccess = "true"))

class UStaticMeshComponent* PickupMesh;// Actor subobject

};


Pickup.cpp

[cpp] view
plain copy

// Fill out your copyright notice in the Description page of Project Settings.

#include "BatteryCollector.h" // 切记放在第一个

#include "Pickup.h"

DEFINE_LOG_CATEGORY_CLASS(APickup, Pickup)

//#define _DEBUG_ 1

// Sets default values

APickup::APickup()

{

// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.

PrimaryActorTick.bCanEverTick = false; // true

// 所有 pickup 默认为 true

bIsActive = true;

// 创建一个静态模型

PickupMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PickupMesh"));// subobject的名字为“PickupMesh”

RootComponent = PickupMesh;

}

// Called when the game starts or when spawned

void APickup::BeginPlay()

{

Super::BeginPlay();

}

// Called every frame

void APickup::Tick( float DeltaTime )

{

Super::Tick( DeltaTime );

}

bool APickup::IsActive()

{

return bIsActive;

}

void APickup::SetActive(bool NewPickupState)

{

bIsActive = NewPickupState;

}

void APickup::WasCollected_Implementation()

{

#ifdef _DEBUG_

FString PickupDebugString = GetName();

UE_LOG(Pickup, Warning, TEXT("You have collected %s"), *PickupDebugString);

#endif

}


BatteryPick.h

[cpp] view
plain copy

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Pickup.h"

#include "BatteryPickup.generated.h"

/**

*

*/

UCLASS()

class BATTERYCOLLECTOR_API ABatteryPickup : public APickup

{

GENERATED_BODY()

public:

ABatteryPickup();

// BlueprintNativeEvent

void WasCollected_Implementation() override;

// 获取 battery power(注意:此方法蓝图不可调用)

float GetPower();

protected:

// BlueprintProtected:表示只有继承了这个类的蓝图才可以修改这个变量

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

float BatteryPower;

};


BatteryPick.cpp

[cpp] view
plain copy

// Fill out your copyright notice in the Description page of Project Settings.

#include "BatteryCollector.h" // 切记放在第一个

#include "BatteryPickup.h"

ABatteryPickup::ABatteryPickup()

{

// 记得为 SM_Batter_Medium 设置 Collision

GetMesh()->SetSimulatePhysics(true);

BatteryPower = 150.f;

}

void ABatteryPickup::WasCollected_Implementation()

{

// 调用父类的方法

Super::WasCollected_Implementation();

// 销毁 battery

Destroy();// 相关方法:SetLifeSpan,允许在destroy方法调用之后,坚挺若干时间

}

float ABatteryPickup::GetPower()

{

return BatteryPower;

}


SpawnVolume.h

[cpp] view
plain copy

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Actor.h"

#include "SpawnVolume.generated.h"

UCLASS()

class BATTERYCOLLECTOR_API ASpawnVolume : public AActor

{

GENERATED_BODY()

DECLARE_LOG_CATEGORY_CLASS(SpawnVolume, Log, All); // 为类声明一个 Log Category

public:

// Sets default values for this actor's properties

ASpawnVolume();

// Called when the game starts or when spawned

virtual void BeginPlay() override;

// Called every frame

virtual void Tick( float DeltaSeconds ) override;

FORCEINLINE class UBoxComponent* GetWhereToSpawn() const { return WhereToSpawn; }

// 返回一个 BoxComponent 范围内的随机点

// 注意:它不会改变 SpawnVolume,也不会改变 SpawnVolume 的行为

UFUNCTION(BlueprintPure, Category = "Spawning")

FVector GetRandomPointInVolume();

// 是否继续产生电池

UFUNCTION(BlueprintCallable, Category = "Spawning")

void SetSpawningActive(bool bShouldSpawn);

protected:

// 产生的 pickup,同时限制了蓝图上显示的类型必须是继承自 Pickup 类

UPROPERTY(EditAnywhere, Category = "Spawning")

TSubclassOf<class APickup> WhatToSpawn;// 具体可以参考 <UE4 Scripting with C++ Cookbook> p49 (NewObject<>, ConstructObject<>, ConditionalBeginDestroy)

// 如果是自定义的C++类指针,且非UObject的派生类(已有引用计数),那么可以使用TSharedPtr, TWeakPtr, TAutoPtr(非线程安全)TScopedPointer 来自动管理内存

// 计时器的句柄,可以用它来 cancel 定时器

FTimerHandle SpawnTimer;

// 最小延迟

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")

float SpawnDelayRangeLow;

// 最大延迟

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")

float SpawnDelayRangeHigh;

private:

// Box 组件,指定 Pickup 在哪里产生

// VisibleAnywhere

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning", meta = (AllowPrivateAccess = "true"))

class UBoxComponent* WhereToSpawn;

void SpawnPickup();

// 真实的延迟

float SpawnDelay;

};


SpawnVolume.cpp

[cpp] view
plain copy

// Fill out your copyright notice in the Description page of Project Settings.

#include "BatteryCollector.h" // 切记放在第一个

#include "EngineGlobals.h"

#include "Runtime/Engine/Classes/Engine/Engine.h"

//#include "UnrealMathUtility.h"

#include "Kismet/KismetMathLibrary.h"

#include "Pickup.h"

#include "SpawnVolume.h"

DEFINE_LOG_CATEGORY_CLASS(ASpawnVolume, SpawnVolume)

//#define _DEBUG_ 1

// Sets default values

ASpawnVolume::ASpawnVolume()

{

// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.

PrimaryActorTick.bCanEverTick = false;

WhereToSpawn = CreateDefaultSubobject<UBoxComponent>(TEXT("WhereToSpawn"));

RootComponent = WhereToSpawn;

SpawnDelayRangeLow = 1.f;

SpawnDelayRangeHigh = 4.5f;

}

// Called when the game starts or when spawned

void ASpawnVolume::BeginPlay()

{

Super::BeginPlay();

}

// Called every frame

void ASpawnVolume::Tick( float DeltaTime )

{

Super::Tick( DeltaTime );

}

FVector ASpawnVolume::GetRandomPointInVolume()

{

FVector SpawnOrigin = WhereToSpawn->Bounds.Origin;

FVector SpawnExtend = WhereToSpawn->Bounds.BoxExtent;

// ❤

//FBox box = FBox::BuildAABB(SpawnOrigin, SpawnExtend);

//FVector SpawnRand = FMath::RandPointInBox(box);

FVector SpawnRand = UKismetMathLibrary::RandomPointInBoundingBox(SpawnOrigin, SpawnExtend);

#ifdef _DEBUG_

// Output Log

//UE_LOG(SpawnVolume,

// Warning,

// TEXT("SpawnRand is (%3.2f, %3.2f, %3.2f)"),

// SpawnRand.X, SpawnRand.Y, SpawnRand.Z);

// Screen Log

GEngine->AddOnScreenDebugMessage(-1,

5.f,

FColor::Yellow,

FString::Printf(TEXT("SpawnRand: x: %f, y: %f, z: %f"),

SpawnRand.X, SpawnRand.Y, SpawnRand.Z));

#endif

return SpawnRand;

}

void ASpawnVolume::SpawnPickup()

{

if (WhatToSpawn != NULL)

{

UWorld* const World = GetWorld();// 当前的 UWorld 实例

if (World)

{

FActorSpawnParameters SpawnParams;

SpawnParams.Owner = this;

SpawnParams.Instigator = Instigator;

// 随机 pickup 的位置

FVector SpawnLocation = GetRandomPointInVolume();

// 随机 pickup 的方向

FRotator SpawnRotation;

SpawnRotation.Pitch = FMath::FRand() * 360.f; // 绕 Y 轴旋转 Right Axis

SpawnRotation.Yaw = FMath::FRand() * 360.f; // 绕 Z 轴旋转 Up Axis

SpawnRotation.Roll = FMath::FRand() * 360.f; // 绕 X 轴旋转 Forward Axis

// 生产 pickup

APickup* const SpawnedPickup = World->SpawnActor<APickup>(WhatToSpawn, SpawnLocation, SpawnRotation, SpawnParams);

// 重新随机一个延时

SpawnDelay = FMath::FRandRange(SpawnDelayRangeLow, SpawnDelayRangeHigh);

// 绑定一个延时方法到全局计时器(不循环执行)

GetWorldTimerManager().SetTimer(SpawnTimer, this, &ASpawnVolume::SpawnPickup, SpawnDelay, false);

}

}

}

void ASpawnVolume::SetSpawningActive(bool bShouldSpawn)

{

if (bShouldSpawn)

{

// 随机一个延时

SpawnDelay = FMath::FRandRange(SpawnDelayRangeLow, SpawnDelayRangeHigh);

// 绑定一个延时方法到全局定时器(不循环执行),即在 SpawnDelay 秒之后执行 SpawnPickup 方法

GetWorldTimerManager().SetTimer(SpawnTimer, this, &ASpawnVolume::SpawnPickup, SpawnDelay, false);

}

else

{

// 清除定时器

GetWorldTimerManager().ClearTimer(SpawnTimer);

}

}


BatteryCollectorGameMode.h

[cpp] view
plain copy

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/GameModeBase.h"

#include "BatteryCollectorGameMode.generated.h"

// 用于存储 gameplay 当前状态的枚举

UENUM(BlueprintType)

enum class EBatteryPlayState

{

EPlaying,

EGameOver,

EWon,

EUnknow

};

UCLASS(minimalapi)

class ABatteryCollectorGameMode : public AGameModeBase

{

GENERATED_BODY()

// 为类声明一个 Log Category

DECLARE_LOG_CATEGORY_CLASS(BatteryCollectorGameMode, Log, All);

public:

ABatteryCollectorGameMode(); // GameMode类负责定义游戏的规则

// 该方法在构造方法之后,在 tick 方法之前执行,

// 那时所有东西已经注册完毕了。

// 确保执行该方法时 character 已经构建完成

virtual void BeginPlay() override;

virtual void Tick(float DeltaTime) override;

UFUNCTION(BlueprintPure, Category = "Power")

float GetPowerToWin() const;

// 获取当前的游戏状态

UFUNCTION(BlueprintPure, Category = "Power")

EBatteryPlayState GetCurrentState() const;

void SetCurrentState(EBatteryPlayState NewState);

protected:

// character 的 power 的衰减率

//(可以通过设置不同的衰减率,设计不同难度的关卡,只需要切换GameMode即可)

// 该属性只有Class Defulats Detail 窗口可以编辑,拖入关卡的蓝图实例 Detail 窗口无法编辑(如果可拖拽到关卡中)

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

float DecayRate;

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

float PowerToWin;

// 用于 HUD 的 Widget 类(限制必须继承与 UUserWidget)

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

TSubclassOf<class UUserWidget> HUDWidgetClass;

// HUD 实例

UPROPERTY() // 利用 GC 【GetWorld()->ForceGarbageCollection(true) 会强制 GC】

class UUserWidget* CurrentWidget;

private:

// 记录当前游戏的状态

EBatteryPlayState CurrentState;

// 记录关卡中所有的 SpawnActor 【即使不想在蓝图中编辑,也最好声明为 UPROPERTY(),让UE4管理 TArray 的内存】

TArray<class ASpawnVolume*> SpawnVolumeActors;

void HandleNewState(EBatteryPlayState NewState);

};


BatteryCollectorGameMode.cpp

[cpp] view
plain copy

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#include "BatteryCollector.h" // 切记放在第一个

#include "EngineGlobals.h"

#include "Runtime/Engine/Classes/Engine/Engine.h"

#include "Kismet/GameplayStatics.h"

#include "Runtime/UMG/Public/Blueprint/UserWidget.h" // 不是 <Blueprint/UserWidget>

#include "BatteryCollectorGameMode.h"

#include "BatteryCollectorCharacter.h"

#include "SpawnVolume.h"

//#define _DEBUG_ 1

DEFINE_LOG_CATEGORY_CLASS(ABatteryCollectorGameMode, BatteryCollectorGameMode)

ABatteryCollectorGameMode::ABatteryCollectorGameMode()

{

PrimaryActorTick.bCanEverTick = true; // ❤

// 将蓝图玩家类设为默认的 pawn class。 类似还有 ContructorHelpers::FObjectFinder,用于 Load Asset

static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter"));

if (PlayerPawnBPClass.Class != NULL)

{

DefaultPawnClass = PlayerPawnBPClass.Class;

}

DecayRate = .01f; // 0.01f;

}

void ABatteryCollectorGameMode::BeginPlay()

{

Super::BeginPlay();

// 查找所有的 Spawn Volume Actor

TArray<AActor*> FoundActors;

// GetWorld 返回缓存的世界指针;

// 返回指定的类在世界中的所有 actor;

// 该方法会遍历整个关卡,比较耗性能

UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundActors);

for (auto Actor : FoundActors)

{

// 如果转换成功,即该 Actor 是 SpawnVolumeActor 类型

ASpawnVolume* SpawnVolumeActor = Cast<ASpawnVolume>(Actor);

if (SpawnVolumeActor)

{

// 确保不会重复添加

SpawnVolumeActors.AddUnique(SpawnVolumeActor);

}

}

// 初始设置游戏状态

SetCurrentState(EBatteryPlayState::EPlaying);

ABatteryCollectorCharacter* MyCharacter = Cast<ABatteryCollectorCharacter>(UGameplayStatics::GetPlayerPawn(this, 0));

if (MyCharacter)

{

PowerToWin = MyCharacter->GetInitialPower() * 1.25f;

}

// UMG

if(HUDWidgetClass != nullptr)

{

CurrentWidget = CreateWidget<UUserWidget>(GetWorld(), HUDWidgetClass);

if (CurrentWidget != nullptr)

{

CurrentWidget->AddToViewport();

}

}

}

void ABatteryCollectorGameMode::Tick(float DeltaTime)

{

Super::Tick(DeltaTime);

#ifdef _DEBUG_

// Screen Log

//GEngine->AddOnScreenDebugMessage(-1,

// 5.f,

// FColor::Red,

// FString::Printf(TEXT("GameMode: %s"), TEXT("Tick")));

GEngine->AddOnScreenDebugMessage(-1,

5.f,

FColor::Red,

FString::Printf(TEXT("GameMode: Tick")));

#endif

// 获取指定 index 的 player pawn,并转化为 ABatteryCollectorCharacter

ABatteryCollectorCharacter* MyCharacter = Cast<ABatteryCollectorCharacter>(UGameplayStatics::GetPlayerPawn(this, 0));

if (MyCharacter)

{

// 如果玩家的能量已经集齐到一定值则胜利

if (MyCharacter->GetCurrentPower() > PowerToWin)

{

SetCurrentState(EBatteryPlayState::EWon);

}

// 如果玩家能量为正

else if (MyCharacter->GetCurrentPower() > 0.f)

{

// 玩家能量随时间衰减

MyCharacter->UpdatePower(-DeltaTime*DecayRate*(MyCharacter->GetInitialPower()));

}

else

{

SetCurrentState(EBatteryPlayState::EGameOver);

}

}

}

float ABatteryCollectorGameMode::GetPowerToWin() const

{

return PowerToWin;

}

EBatteryPlayState ABatteryCollectorGameMode::GetCurrentState() const

{

return CurrentState;

}

void ABatteryCollectorGameMode::SetCurrentState(EBatteryPlayState NewState)

{

CurrentState = NewState;

HandleNewState(CurrentState);

}

void ABatteryCollectorGameMode::HandleNewState(EBatteryPlayState NewState)

{

switch (NewState)

{

case EBatteryPlayState::EPlaying:

{

for (ASpawnVolume* Volume : SpawnVolumeActors)

{

Volume->SetSpawningActive(true);

}

}

break;

case EBatteryPlayState::EWon:

{

for (ASpawnVolume* Volume : SpawnVolumeActors)

{

Volume->SetSpawningActive(false);

}

}

break;

case EBatteryPlayState::EGameOver:

{

for (ASpawnVolume* Volume : SpawnVolumeActors)

{

Volume->SetSpawningActive(false);

}

APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this, 0);

if (PlayerController)

{

// 禁用部分输入,但不会隐藏玩家和 HUD

PlayerController->SetCinematicMode(true, false, false, true, true);

}

// 加入布娃娃系统

// 必须确保它是 Chacater 而不是 Pawn,因为 Character 具有额外的动作和骨骼模型

ACharacter* MyCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);

if (MyCharacter)

{

// 使得玩家可以像布娃娃一样“摊倒”

//(确保 Mannequin/Character/Mesh/ 目录下的 SK_Mannequin 已创建了 Physics Asset)

// (确保 ThirdPersonCPP/Blueprints/ 目录下的 ThirdPersonCharacter,其中 Mesh 组件的 Collision 被激活 [自定义预设])

MyCharacter->GetMesh()->SetSimulatePhysics(true);

// 禁用跳跃动作

MyCharacter->GetMovementComponent()->MovementState.bCanJump = false;

}

}

break;

default:

case EBatteryPlayState::EUnknow:

{

// 保留

}

break;

}

}


BatteryCollectorCharacter.h

[cpp] view
plain copy

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/Character.h"

#include "BatteryCollectorCharacter.generated.h"

UCLASS(config=Game)

class ABatteryCollectorCharacter : public ACharacter

{

GENERATED_BODY()

/** Camera boom positioning the camera behind the character */

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

class USpringArmComponent* CameraBoom;

/** Follow camera */

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

class UCameraComponent* FollowCamera;

// CollectionSphere 组件

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Power, meta = (AllowPrivateAccess = "true"))

class USphereComponent* CollectionSphere;

public:

ABatteryCollectorCharacter();

/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)

float BaseTurnRate;

/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)

float BaseLookUpRate;

// power 的 Getter 和 Setter

UFUNCTION(BlueprintPure, Category = "Power")

float GetInitialPower();

UFUNCTION(BlueprintPure, Category = "Power")

float GetCurrentPower();

UFUNCTION(BlueprintCallable, Category = "Power")

void UpdatePower(float PowerChange);

protected:

/** Resets HMD orientation in VR. */

void OnResetVR();

/** Called for forwards/backward input */

void MoveForward(float Value);

/** Called for side to side input */

void MoveRight(float Value);

/**

* Called via input to turn at a given rate.

* @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate

*/

void TurnAtRate(float Rate);

/**

* Called via input to turn look up/down at a given rate.

* @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate

*/

void LookUpAtRate(float Rate);

/** Handler for when a touch input begins. */

void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);

/** Handler for when a touch input stops. */

void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);

// APawn interface

virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

// End of APawn interface

// 按下按键,收集一个在 collection sphere 范围内的 pickup

UFUNCTION(BlueprintCallable, Category = "Pickups")

void CollectPickups();

// character 初始的 power

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

float InitialPower;

// 玩家的速度因子

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

float SpeedFactor;

// 基础速度

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))

float BaseSpeed;

// BlueprintImplementableEvent:定义一个蓝图事件, 意味着我们不用在 C++ 中定义该方法,交给蓝图去实现

UFUNCTION(BlueprintImplementableEvent, Category = "Power")

void PowerChangeEffect();

private:

// character 当前的 power

UPROPERTY(VisibleAnywhere, Category="Power")

float CharacterPower;

public:

/** Returns CameraBoom subobject **/

FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }

/** Returns FollowCamera subobject **/

FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }

// 返回 CollectionSphere 组件

FORCEINLINE class USphereComponent* GetCollectionSphere() const { return CollectionSphere; }

};


BatteryCollectorCharacter.cpp

[cpp] view
plain copy

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#include "BatteryCollector.h" // 切记放在第一个

#include "Kismet/HeadMountedDisplayFunctionLibrary.h"

#include "BatteryCollectorCharacter.h"

#include "Pickup.h"

#include "BatteryPickup.h"

//////////////////////////////////////////////////////////////////////////

// ABatteryCollectorCharacter

ABatteryCollectorCharacter::ABatteryCollectorCharacter()

{

// Set size for collision capsule

GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

// set our turn rates for input

BaseTurnRate = 45.f;

BaseLookUpRate = 45.f;

// Don't rotate when the controller rotates. Let that just affect the camera.

bUseControllerRotationPitch = false;

bUseControllerRotationYaw = false;

bUseControllerRotationRoll = false;

// Configure character movement

GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...

GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate

GetCharacterMovement()->JumpZVelocity = 600.f;

GetCharacterMovement()->AirControl = 0.2f;

// Create a camera boom (pulls in towards the player if there is a collision)

CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));

CameraBoom->SetupAttachment(RootComponent);

CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character

CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

// Create a follow camera

FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));

FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation

FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm

// 创建 collection sphere 组件

CollectionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollectionSphere"));

// 并将其添加到 RootComponent 上

//CollectionSphere->AttachTo(RootComponent); // 过时的方法

// https://forums.unrealengine.com/showthread.php?112644-4-12-Transition-Guide
CollectionSphere->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);

// collection sphere 的半径范围

CollectionSphere->SetSphereRadius(200.f);

// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)

// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)

// 为 power 赋值

InitialPower = 2000.f;

CharacterPower = InitialPower;

SpeedFactor = .75f;

BaseSpeed = 10.f;

}

//////////////////////////////////////////////////////////////////////////

// Input

void ABatteryCollectorCharacter::SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent)

{

// Set up gameplay key bindings

check(PlayerInputComponent);

PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);

PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

// Collect 键盘响应事件的绑定(Action 用于短促的响应,Axis 用于持续的响应)

PlayerInputComponent->BindAction("Collect", IE_Pressed, this, &ABatteryCollectorCharacter::CollectPickups);

PlayerInputComponent->BindAxis("MoveForward", this, &ABatteryCollectorCharacter::MoveForward);

PlayerInputComponent->BindAxis("MoveRight", this, &ABatteryCollectorCharacter::MoveRight);

// We have 2 versions of the rotation bindings to handle different kinds of devices differently

// "turn" handles devices that provide an absolute delta, such as a mouse.

// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick

PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);

PlayerInputComponent->BindAxis("TurnRate", this, &ABatteryCollectorCharacter::TurnAtRate);

PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);

PlayerInputComponent->BindAxis("LookUpRate", this, &ABatteryCollectorCharacter::LookUpAtRate);

// handle touch devices

PlayerInputComponent->BindTouch(IE_Pressed, this, &ABatteryCollectorCharacter::TouchStarted);

PlayerInputComponent->BindTouch(IE_Released, this, &ABatteryCollectorCharacter::TouchStopped);

// VR headset functionality

PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &ABatteryCollectorCharacter::OnResetVR);

}

void ABatteryCollectorCharacter::OnResetVR()

{

UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();

}

void ABatteryCollectorCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)

{

Jump();

}

void ABatteryCollectorCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)

{

StopJumping();

}

void ABatteryCollectorCharacter::TurnAtRate(float Rate)

{

// calculate delta for this frame from the rate information

AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());

}

void ABatteryCollectorCharacter::LookUpAtRate(float Rate)

{

// calculate delta for this frame from the rate information

AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());

}

void ABatteryCollectorCharacter::MoveForward(float Value)

{

if ((Controller != NULL) && (Value != 0.0f))

{

// find out which way is forward

const FRotator Rotation = Controller->GetControlRotation();

const FRotator YawRotation(0, Rotation.Yaw, 0);

// get forward vector

const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);

AddMovementInput(Direction, Value);

}

}

void ABatteryCollectorCharacter::MoveRight(float Value)

{

if ( (Controller != NULL) && (Value != 0.0f) )

{

// find out which way is right

const FRotator Rotation = Controller->GetControlRotation();

const FRotator YawRotation(0, Rotation.Yaw, 0);

// get right vector

const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

// add movement in that direction

AddMovementInput(Direction, Value);

}

}

void ABatteryCollectorCharacter::CollectPickups()

{

// 遍历所有覆盖区域内的 Actor,将它们存储进数组

TArray<AActor *> CollectedActors;

CollectionSphere->GetOverlappingActors(CollectedActors);

// 记录收集的电池的能量

float CollectedPower = 0.f;

// 遍历数组

for (int32 iCollected = 0; iCollected < CollectedActors.Num(); ++iCollected )

{

// 将 Actor 转化为 APickup

APickup *const TestPickup = Cast<APickup>(CollectedActors[iCollected]);

// 检查转化是否成功,还有 pickup 是否即将被销毁,是否是激活状态

if (TestPickup && !TestPickup->IsPendingKill() && TestPickup->IsActive())

{

// 收集 pickup(注意:此处不是调用的 xxx_Implementation)

TestPickup->WasCollected();

// 检查 pickup 是否是电池

ABatteryPickup* const TestBattery = Cast<ABatteryPickup>(TestPickup);

if (TestBattery)

{

// 累加收集的能量

CollectedPower += TestBattery->GetPower();

}

// 冻结 pickup

TestPickup->SetActive(false);

}

}

if (CollectedPower > 0.f)

{

UpdatePower(CollectedPower);

}

}

float ABatteryCollectorCharacter::GetInitialPower()

{

return InitialPower;

}

float ABatteryCollectorCharacter::GetCurrentPower()

{

return CharacterPower;

}

void ABatteryCollectorCharacter::UpdatePower(float PowerChange)

{

CharacterPower = CharacterPower + PowerChange;

// 所有 Character类都有 GetCharacterMovement 方法

// 根据 power 更新玩家的速度

GetCharacterMovement()->MaxWalkSpeed = BaseSpeed + SpeedFactor * CharacterPower;

// 调用蓝图实现的方法

PowerChangeEffect();

}

以及 蓝图 的设计


Battery_BP




ThirdPersonCharacter 的 Construction Scirpt

注意:该材质是动态材质实例(Dynamic Material Instance),可以实时改变

Construction Script 是一种当绑定对象上的属性发生变化就会执行的脚本(不包括关卡中的移动),和 C++ 中的构造函数有点相似




ThirdPersonCharacter




BatteryHUD





UE_LOG 的使用方法

字符串处理

为什么要用 TSubclassOf

(完)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: