视频教程链接:
油管链接 【需要梯子】
首先来看效果图:
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 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
ThirdPersonCharacter 的 Construction Scirpt
注意:该材质是动态材质实例(Dynamic Material Instance),可以实时改变
Construction Script 是一种当绑定对象上的属性发生变化就会执行的脚本(不包括关卡中的移动),和 C++ 中的构造函数有点相似
ThirdPersonCharacter
BatteryHUD
附
(完)