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

来源:互联网 发布:淘宝电商赚钱吗 编辑:程序博客网 时间:2024/06/03 18:00


视频教程链接:

优酷链接

油管链接 【需要梯子】


首先来看效果图:

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



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



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



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


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 CategoryDECLARE_LOG_CATEGORY_CLASS(Pickup, Log, All);public:// Sets default values for this actor's propertiesAPickup();// Called when the game starts or when spawnedvirtual void BeginPlay() override;// Called every framevirtual 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 valuesAPickup::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 默认为 truebIsActive = true;// 创建一个静态模型PickupMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PickupMesh"));// subobject的名字为“PickupMesh”RootComponent = PickupMesh;}// Called when the game starts or when spawnedvoid APickup::BeginPlay(){Super::BeginPlay();}// Called every framevoid 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();// BlueprintNativeEventvoid 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 设置 CollisionGetMesh()->SetSimulatePhysics(true);BatteryPower = 150.f;}void ABatteryPickup::WasCollected_Implementation(){// 调用父类的方法Super::WasCollected_Implementation();// 销毁 batteryDestroy();// 相关方法: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 Categorypublic:// Sets default values for this actor's propertiesASpawnVolume();// Called when the game starts or when spawnedvirtual void BeginPlay() override;// Called every framevirtual 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 在哪里产生// VisibleAnywhereUPROPERTY(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 valuesASpawnVolume::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 spawnedvoid ASpawnVolume::BeginPlay(){Super::BeginPlay();}// Called every framevoid 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 LogGEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::Printf(TEXT("SpawnRand: x: %f, y: %f, z: %f"), SpawnRand.X, SpawnRand.Y, SpawnRand.Z));#endifreturn 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 AxisSpawnRotation.Yaw = FMath::FRand() * 360.f;// 绕 Z 轴旋转 Up AxisSpawnRotation.Roll = FMath::FRand() * 360.f;// 绕 X 轴旋转 Forward Axis// 生产 pickupAPickup* 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 CategoryDECLARE_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_ 1DEFINE_LOG_CATEGORY_CLASS(ABatteryCollectorGameMode, BatteryCollectorGameMode)ABatteryCollectorGameMode::ABatteryCollectorGameMode(){PrimaryActorTick.bCanEverTick = true;// ❤// 将蓝图玩家类设为默认的 pawn class。 类似还有 ContructorHelpers::FObjectFinder,用于 Load Assetstatic 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 ActorTArray<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;}// UMGif(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,并转化为 ABatteryCollectorCharacterABatteryCollectorCharacter* 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){// 禁用部分输入,但不会隐藏玩家和 HUDPlayerController->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 和 SetterUFUNCTION(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 RateThis 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 RateThis 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 interfacevirtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;// End of APawn interface// 按下按键,收集一个在 collection sphere 范围内的 pickupUFUNCTION(BlueprintCallable, Category = "Pickups")void CollectPickups();// character 初始的 powerUPROPERTY(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 当前的 powerUPROPERTY(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"//////////////////////////////////////////////////////////////////////////// ABatteryCollectorCharacterABatteryCollectorCharacter::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;}//////////////////////////////////////////////////////////////////////////// Inputvoid 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

(完)





1 0