一.首先GameMode里有Actor数组,组装直线路,和左右路
#include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "RunGANGameMode.generated.h" UCLASS(minimalapi) class ARunGANGameMode : public AGameModeBase { GENERATED_BODY() UPROPERTY() TArray<TSubclassOf<class AActor>> StraitArray; UPROPERTY() TArray<TSubclassOf<class AActor>> LeftRightArray; UPROPERTY() TSubclassOf<AActor> NewRoad; UPROPERTY() FTransform NextTransforms; public: ARunGANGameMode(); TSubclassOf<AActor> RandomInputFloor(); virtual void BeginPlay(); //添加我们地板 void AddFloor(); };
UPROPERTY()不会被GC掉
TSubclassOf
在Unreal中经常会有在蓝图或者C++中声明某些UClass,便于后续用来创建对应的对象,但是如果只是填了一个UClass指针的话,这并不好用。这是因为UClass*可以指代任意UObject,所以在蓝图或者编辑器中选择对应的UClass的时候不好选择,因为基本上所有的UClass都会出现在下拉框内。Unreal也提出对应的解决方案,那就是TSubclassOf,它能够避免能上面说的问题,TSubclassOf能够约束下拉框中只会出现继承于T的类或者T本身,并且C++层面也能实现类型安全,如果给TSubclassOf对象赋值一个类型不兼容的UClass,则会得到编译错误。
二.在GameMode构造函数时,可以使用动态加载 Actor到路面类型。静态加载主角到PlayerPawnBPClass类中,再设置到Gamemode默认的Pawn里(DefaultPawnClass)。
#include "RunGANGameMode.h" #include "RunGANCharacter.h" #include "UObject/ConstructorHelpers.h" #include "Actor/CollisionInteraction/RunRoad.h" ARunGANGameMode::ARunGANGameMode() { // set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter")); if (PlayerPawnBPClass.Class != NULL) { DefaultPawnClass = PlayerPawnBPClass.Class; } StraitArray.Add(LoadClass<AActor>(NULL,TEXT("/Script/Engine.Blueprint'/Game/Environment/BP/BP_StraightRoad_1.BP_StraightRoad_1_C'"))); StraitArray.Add(LoadClass<AActor>(NULL, TEXT("/Script/Engine.Blueprint'/Game/Environment/BP/BP_StraightRoad_2.BP_StraightRoad_2_C'"))); StraitArray.Add(LoadClass<AActor>(NULL, TEXT("/Script/Engine.Blueprint'/Game/Environment/BP/BP_StraightRoad_3.BP_StraightRoad_3_C'"))); StraitArray.Add(LoadClass<AActor>(NULL, TEXT("/Script/Engine.Blueprint'/Game/Environment/BP/BP_StraightRoad_4.BP_StraightRoad_4_C'"))); LeftRightArray.Add(LoadClass<AActor>(NULL, TEXT("/Script/Engine.Blueprint'/Game/Environment/BP/BP_TurnLeftRoad.BP_TurnLeftRoad_C'"))); LeftRightArray.Add(LoadClass<AActor>(NULL, TEXT("/Script/Engine.Blueprint'/Game/Environment/BP/BP_TurnRightRoad_2.BP_TurnRightRoad_2_C'"))); }
ConstructorHelpers::FClassFinder()和FObjectFinder()
静态加载指的是在构造函数中完成的加载方式,这种方式的弊端明显,就是需要写死路径,一旦改变路径读取失败很容易造成程序崩溃。
LoadObject<UClass>。
也就是说LoadObject和LoadClass函数都用于在运行时加载UObject派生的对象,但是它们的用途和返回值有所不同。
`LoadObject`用于加载单个对象,可以是任何UObject派生类的实例,包括UClass、UTexture、UMaterial等。它会返回加载的对象,或者在加载失败时返回nullptr
同时,如果使用LoadClass()方法,路径名也必须带_C后缀(LoadObject不需要带_C后缀),例如,蓝图路径是:Blueprint'/Game/Blueprints/Test', 加后缀以后,则是:Blueprint'/Game/Blueprints/Test_C'
三.通过FMath::RandRange随机数从不同的类型中生成下个路面。并在一开始设置生成一个路面的位置。
TSubclassOf<AActor> ARunGANGameMode::RandomInputFloor() { int32 i = FMath::RandRange(1,100); if (i <= 80) { int32 Index = FMath::RandRange(0,StraitArray.Num()-1); return StraitArray[Index]; } else { int32 Index = FMath::RandRange(0, LeftRightArray.Num() - 1); return LeftRightArray[Index]; } return TSubclassOf<AActor>(); } void ARunGANGameMode::BeginPlay() { Super::BeginPlay(); if (GetWorld()) { NextTransforms = GetWorld()->GetFirstPlayerController()->GetPawn()->GetTransform(); FVector InLocation = NextTransforms.GetLocation(); InLocation.Y += 100.f; InLocation.Z -= 200.f; NextTransforms.SetLocation(InLocation); for (int32 i = 0; i < 9; i++) { AddFloor(); } } }
四.添加地板逻辑,拼贴点位。
//添加我们的地板 void ARunGANGameMode::AddFloor() { NewRoad = RandomInputFloor(); if (NewRoad != NULL) { FVector const MyLocation = NextTransforms.GetLocation(); FRotator MyRotation(NextTransforms.Rotator()); if (ARunRoad* MyRunRoad = GetWorld()->SpawnActor<ARunRoad>(NewRoad, MyLocation, MyRotation)) { MyRunRoad->SetActorScale3D(FVector(10.f)); NextTransforms = MyRunRoad->GetAttackToTransform(MyLocation); } } }
五.AddFloor里使用的GetAttackToTransform是使用每个,路面自己对准的下一个路面的位置。
FTransform ARunRoad::GetAttackToTransform(const FVector& MyLocation) { FTransform Transform; Transform.SetLocation(SpawnPointMiddle->GetComponentToWorld().GetLocation()); Transform.SetRotation(SpawnPointMiddle->GetComponentQuat()); return Transform; }
六.通过跑步碰撞,判断。开启角色可以转弯的功能,
#include "TurnBox.h" #include "Components/BoxComponent.h" #include "../../RunGANCharacter.h" // Sets default values ATurnBox::ATurnBox() { // 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; Box = CreateDefaultSubobject<UBoxComponent>(TEXT("TurnBox")); RootComponent = Box; } // Called when the game starts or when spawned void ATurnBox::BeginPlay() { Super::BeginPlay(); Box->OnComponentBeginOverlap.AddDynamic(this,&ATurnBox::CharacterOverlapStart); Box->OnComponentEndOverlap.AddDynamic(this, &ATurnBox::CharacterOverlapEnd); } // Called every frame void ATurnBox::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void ATurnBox::CharacterOverlapStart(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (ARunGANCharacter* InCharacter = Cast<ARunGANCharacter>(OtherActor)) { InCharacter->bTurn = true; } } void ATurnBox::CharacterOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (ARunGANCharacter* InCharacter = Cast<ARunGANCharacter>(OtherActor)) { InCharacter->bTurn = false; } }
TurnBox,位置不对。可以用蓝图具象化调整。
碰撞显示
BP_TurnBox 的位置就是Arrow的位置,记住Dlay延迟一下,预防Scale位置会有问题偏差。