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

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

视频教程链接:

优酷链接

油管链接 【需要梯子】

首先来看效果图:

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

image.gif

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

20170313151907526.gif

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

20170313152132528.gif

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

Pickup.h

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

image.png

ThirdPersonCharacter 的 Construction Scirpt

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


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


image.png

image.png

ThirdPersonCharacter


image.png

BatteryHUD

image.png

UE_LOG 的使用方法

字符串处理

为什么要用 TSubclassOf

 

(完)

目录
相关文章
|
2天前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
20 2
|
2月前
|
存储 算法 数据可视化
【C++】C++旅游管理系统(源码+论文)【独一无二】
【C++】C++旅游管理系统(源码+论文)【独一无二】
|
2月前
|
存储 数据可视化 C++
【C++】C++ 职工信息管理系统(源码)【独一无二】
【C++】C++ 职工信息管理系统(源码)【独一无二】
|
2月前
|
存储 数据可视化 C++
【C++】C++-机房收费管理系统(源码+注释)【独一无二】
【C++】C++-机房收费管理系统(源码+注释)【独一无二】
|
2月前
|
数据可视化 C++
【C++】C++商店销售管理系统(源码+论文)【独一无二】
【C++】C++商店销售管理系统(源码+论文)【独一无二】
|
2月前
|
C++
【C++】C++书店管理系统(源码+论文)【独一无二】
【C++】C++书店管理系统(源码+论文)【独一无二】
|
2月前
|
存储 C++ 索引
【C++ 】C++ 停车场收费系统(源码)【独一无二】
【C++ 】C++ 停车场收费系统(源码)【独一无二】
|
2月前
|
存储 数据可视化 C++
【C++】C++-学生考试题库管理系统(源码)
本系统设计了一个选题管理流程,包括读取题目信息、随机抽取题目、保存及查询选题结果等功能。使用 `readProjects` 从文件读取题目信息,`drawProject` 随机抽取未选中的题目,`saveSelection` 保存选题结果至文件,`querySelection` 查询并显示所有选题结果。主函数提供菜单界面,支持学生信息输入、抽题及结果查询。关注【测试开发自动化】公众号,回复“题库”获取源码。
17 0
|
2月前
|
存储 数据可视化 C++
【C++】C++-学生考试题库管理系统(源码)【独一无二】
【C++】C++-学生考试题库管理系统(源码)【独一无二】
|
2月前
|
算法 数据可视化 C++
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】