AgeOfWar minigame #4

Merged
oleg.petruny merged 7 commits from MinigameAgeOfWar into master 2024-11-22 18:50:52 +00:00
21 changed files with 415 additions and 30 deletions

View File

@ -139,6 +139,8 @@ ManualIPAddress=
+Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.") +Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.")
+Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ") +Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
+Profiles=(Name="Interactable",CollisionEnabled=QueryAndPhysics,bCanModify=True,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Interactable")),HelpMessage="WorldDynamic objects derived from AInteractable.") +Profiles=(Name="Interactable",CollisionEnabled=QueryAndPhysics,bCanModify=True,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Interactable")),HelpMessage="WorldDynamic objects derived from AInteractable.")
+Profiles=(Name="AgeOfWarUnitPlayer",CollisionEnabled=QueryOnly,bCanModify=True,ObjectTypeName="Vehicle",CustomResponses=((Channel="WorldStatic",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Interactable")),HelpMessage="Minigame object")
+Profiles=(Name="AgeOfWarUnitComputer",CollisionEnabled=QueryOnly,bCanModify=True,ObjectTypeName="Destructible",CustomResponses=((Channel="WorldStatic",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore),(Channel="Interactable")),HelpMessage="Minigame object")
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False,Name="Interactable") +DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False,Name="Interactable")
-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") -ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") -ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
@ -150,6 +152,9 @@ ManualIPAddress=
+ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") +ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
+ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") +ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
+ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") +ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
+ProfileRedirects=(OldName="AgeOfWarUnitComputer",NewName="AgeOfWarUnitPlayerZone")
+ProfileRedirects=(OldName="AgeOfWarUnit",NewName="AgeOfWarUnitPlayer")
+ProfileRedirects=(OldName="AgeOfWarUnitPlayerZone",NewName="AgeOfWarUnitComputer")
-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") -CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") -CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") -CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")

Binary file not shown.

Binary file not shown.

View File

@ -39,6 +39,18 @@ void UCommonFunctions::DestroyActorRecursively(AActor* actor)
actor->Destroy(); actor->Destroy();
} }
TArray<int32> UCommonFunctions::GetRandomIntArray(int32 size, int32 min, int32 max)
{
if(size <= 0)
return {};
TArray<int32> arr;
arr.Reserve(size);
for(int32 i = 0; i < size; ++i)
arr.Add(FMath::RandRange(min, max));
return arr;
}
ALevelBase* UCommonFunctions::GetCurrentLevelScript(UObject* obj) ALevelBase* UCommonFunctions::GetCurrentLevelScript(UObject* obj)

View File

@ -21,6 +21,8 @@ public:
UFUNCTION(BlueprintCallable, Category = Actor) UFUNCTION(BlueprintCallable, Category = Actor)
static void DestroyActorRecursively(class AActor* actor); static void DestroyActorRecursively(class AActor* actor);
UFUNCTION(BlueprintPure)
static TArray<int32> GetRandomIntArray(int32 size = 16, int32 min = 0, int32 max = 16);
UFUNCTION(BlueprintCallable, Category = Level) UFUNCTION(BlueprintCallable, Category = Level)

View File

@ -55,25 +55,25 @@ void AInteractable::BeginPlay()
} }
} }
GetComponents(meshes); GetComponents(collisions, true);
for(auto mesh : meshes) for(auto collision : collisions)
{ {
if(activatorTypes) if(activatorTypes)
{ {
mesh->SetCollisionProfileName(TEXT("Interactable")); collision->SetCollisionProfileName(TEXT("Interactable"));
} }
if(activatorTypes & static_cast<uint8>(EActivatorType::Saw)) if(activatorTypes & static_cast<uint8>(EActivatorType::Saw))
{ {
mesh->CustomDepthStencilValue = 128; collision->CustomDepthStencilValue = 128;
mesh->CustomDepthStencilWriteMask = ERendererStencilMask::ERSM_Default; collision->CustomDepthStencilWriteMask = ERendererStencilMask::ERSM_Default;
mesh->SetRenderCustomDepth(true); collision->SetRenderCustomDepth(true);
} }
if(activatorTypes & static_cast<uint8>(EActivatorType::Use)) if(activatorTypes & static_cast<uint8>(EActivatorType::Use))
{ {
mesh->CustomDepthStencilWriteMask = ERendererStencilMask::ERSM_Default; collision->CustomDepthStencilWriteMask = ERendererStencilMask::ERSM_Default;
mesh->SetRenderCustomDepth(true); collision->SetRenderCustomDepth(true);
} }
} }
@ -120,8 +120,8 @@ void AInteractable::_Activate(EActivatorType type)
if(activated & static_cast<uint8>(EActivatorType::Use)) if(activated & static_cast<uint8>(EActivatorType::Use))
{ {
for(auto mesh : meshes) for(auto collision : collisions)
mesh->SetCustomDepthStencilValue(132); collision->SetCustomDepthStencilValue(132);
} }
Activate(type); Activate(type);
@ -155,8 +155,8 @@ void AInteractable::_Deactivate(EActivatorType type)
if(!(activated & static_cast<uint8>(EActivatorType::Use))) if(!(activated & static_cast<uint8>(EActivatorType::Use)))
{ {
for(auto mesh : meshes) for(auto collision : collisions)
mesh->SetCustomDepthStencilValue(0); collision->SetCustomDepthStencilValue(0);
} }
Deactivate(type); Deactivate(type);

View File

@ -58,7 +58,7 @@ protected:
TMap<EActivatorType, class UInteractableModificator*> modificators; TMap<EActivatorType, class UInteractableModificator*> modificators;
class APlayerBase* player = nullptr; class APlayerBase* player = nullptr;
TArray<UMeshComponent*> meshes; TArray<UPrimitiveComponent*> collisions;
}; };

View File

@ -3,11 +3,34 @@
#include "AgeOfWarManager.h" #include "AgeOfWarManager.h"
#include "Camera/CameraComponent.h"
#include "Components/ChildActorComponent.h"
#include "AgeOfWarUnit.h"
#include "MainGameModeBase.h" #include "MainGameModeBase.h"
#include "PlayerBase.h" #include "PlayerBase.h"
TMap<UClass*, FAgeOfWarUnitStats> AAgeOfWarManager::unitStats = {};
const FAgeOfWarUnitStats& AAgeOfWarManager::UnitGetStats(TSubclassOf<AAgeOfWarUnit> unitClass)
{
check(unitStats.Contains(*unitClass));
//if(!unitStats.Contains(*unitClass))
//{
// static FAgeOfWarUnitStats empty;
// return empty;
//}
return unitStats[unitClass];
}
AAgeOfWarManager::AAgeOfWarManager() AAgeOfWarManager::AAgeOfWarManager()
{} {
auto root = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
camera->SetupAttachment(root);
}
void AAgeOfWarManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate) void AAgeOfWarManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate)
{ {
@ -17,11 +40,58 @@ void AAgeOfWarManager::Start(APlayerBase* playerPawn, FMinigameEndCallback deleg
AMinigame::Start(playerPawn, delegate); AMinigame::Start(playerPawn, delegate);
player->LockPlayer(FPlayerLock::All()); player->LockPlayer(FPlayerLock::All());
player->SwitchToView(this);
FillUnitStats();
TArray<UChildActorComponent*> units;
GetComponents<UChildActorComponent>(units);
for(auto& unit : units)
{
if(auto proxy = Cast<AAgeOfWarUnit>(unit->GetChildActor()))
{
proxy->manager = this;
proxy->UpdateStats();
}
}
} }
void AAgeOfWarManager::End() void AAgeOfWarManager::End()
{ {
for(auto& unit : spawnedUnits)
unit->Destroy();
spawnedUnits.Empty();
unitStats.Empty();
player->ReturnPlayerView();
player->UnlockPlayer(FPlayerLock::All()); player->UnlockPlayer(FPlayerLock::All());
AMinigame::End(); AMinigame::End();
} }
void AAgeOfWarManager::UnitKill(AAgeOfWarUnit* unit)
{
OnUnitKill(unit);
spawnedUnits.Remove(unit);
}
void AAgeOfWarManager::AddUnitStats(TSubclassOf<AAgeOfWarUnit> unitClass, FAgeOfWarUnitStats stats)
{
unitStats.Add(unitClass, stats);
}
FAgeOfWarUnitStats AAgeOfWarManager::GetUnitStats(TSubclassOf<AAgeOfWarUnit> unitClass)
{
return UnitGetStats(unitClass);
}
void AAgeOfWarManager::SpawnUnit(TSubclassOf<AAgeOfWarUnit> unitClass, FTransform transform, bool computer)
{
FActorSpawnParameters spawnParams{};
spawnParams.Owner = this;
auto unit = GetWorld()->SpawnActor<AAgeOfWarUnit>(*unitClass, transform, spawnParams);
unit->team = computer ? UnitTeam::AI : UnitTeam::Player;
unit->manager = this;
unit->UpdateStats();
spawnedUnits.Add(unit);
}

View File

@ -12,8 +12,32 @@ class AAgeOfWarManager : public AMinigame
GENERATED_BODY() GENERATED_BODY()
public: public:
static const struct FAgeOfWarUnitStats& UnitGetStats(TSubclassOf<AAgeOfWarUnit> unitClass);
AAgeOfWarManager(); AAgeOfWarManager();
virtual void Start(class APlayerBase* playerPawn, FMinigameEndCallback delegate) override; virtual void Start(class APlayerBase* playerPawn, FMinigameEndCallback delegate) override;
virtual void End() override; virtual void End() override;
void UnitKill(class AAgeOfWarUnit* unit);
UFUNCTION(BlueprintImplementableEvent)
void OnUnitKill(class AAgeOfWarUnit* unit);
protected:
UFUNCTION(BlueprintImplementableEvent)
void FillUnitStats();
UFUNCTION(BlueprintCallable)
static void AddUnitStats(TSubclassOf<AAgeOfWarUnit> unitClass, struct FAgeOfWarUnitStats stats);
UFUNCTION(BlueprintPure)
static struct FAgeOfWarUnitStats GetUnitStats(TSubclassOf<AAgeOfWarUnit> unitClass);
UFUNCTION(BlueprintCallable)
void SpawnUnit(TSubclassOf<AAgeOfWarUnit> unitClass, FTransform transform, bool computer = false);
static TMap<UClass*, struct FAgeOfWarUnitStats> unitStats;
UPROPERTY(EditAnywhere)
class UCameraComponent* camera;
TSet<AAgeOfWarUnit*> spawnedUnits;
}; };

View File

@ -0,0 +1,158 @@
// Oleg Petruny proprietary.
#include "AgeOfWarUnit.h"
#include "Components/StaticMeshComponent.h"
#include "AgeOfWarManager.h"
#include <Components/BoxComponent.h>
namespace
{
constexpr auto PlayerCollision = TEXT("AgeOfWarUnitPlayer");
constexpr auto ComputerCollision = TEXT("AgeOfWarUnitComputer");
constexpr auto TagNoAuto = TEXT("NoAuto");
}
const FAgeOfWarUnitStats& AAgeOfWarUnit::GetStats()
{
if(!stats)
stats = &AAgeOfWarManager::UnitGetStats(GetClass());
return *stats;
}
void AAgeOfWarUnit::UpdateStats()
{
health = GetStats().health;
attackStartRange = GetStats().attackStartRange;
switch(team)
{
case UnitTeam::AI:
root->SetCollisionProfileName(ComputerCollision);
allyblocker->SetCollisionProfileName(PlayerCollision);
break;
case UnitTeam::Player:
root->SetCollisionProfileName(PlayerCollision);
allyblocker->SetCollisionProfileName(ComputerCollision);
break;
default:
break;
}
allyblocker->SetBoxExtent(root->GetUnscaledBoxExtent(), false);
allyblocker->SetCollisionEnabled(ECollisionEnabled::NoCollision);
OnUpdateStats();
}
void AAgeOfWarUnit::BeginPlay()
{
if(manager)
{
UpdateStats();
}
Super::BeginPlay();
world = GetWorld();
}
void AAgeOfWarUnit::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
}
AAgeOfWarUnit::AAgeOfWarUnit()
{
root = CreateDefaultSubobject<UBoxComponent>(TEXT("Collision"));
allyblocker = CreateDefaultSubobject<UBoxComponent>(TEXT("AllyBlocker"));
allyblocker->SetupAttachment(root);
traceStart = CreateDefaultSubobject<USceneComponent>(TEXT("TraceStart"));
traceStart->SetupAttachment(root);
traceStart->AddRelativeLocation(FVector(0, 0, 200));
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
}
void AAgeOfWarUnit::Tick(float deltaTime)
{
Super::Tick(deltaTime);
if(!manager || GetStats().moveSpeed <= 0)
return;
// try move
FHitResult moveHit;
auto moveStep = GetActorForwardVector() * GetStats().moveSpeed;
this->AddActorWorldOffset(moveStep, true, &moveHit, ETeleportType::None);
if(moveHit.bBlockingHit)
allyblocker->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
else
allyblocker->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// look for units forward
FHitResult hit;
auto startLocation = traceStart->GetComponentLocation();
auto endLocation = startLocation + (GetActorRotation().Vector() * attackStartRange * (moveHit.bBlockingHit ? 5 : 1));
world->LineTraceSingleByChannel(
hit,
startLocation,
endLocation,
ECollisionChannel::ECC_GameTraceChannel1
);
DrawDebugLine(GetWorld(), startLocation, endLocation, FColor::Red, false, deltaTime * 10, 0, 4.0f);
// check if blocked
if(!hit.bBlockingHit)
return;
forwardUnit = Cast<AAgeOfWarUnit>(hit.GetActor());
if(!forwardUnit) // something is wrong, only block with units
{
Destroy();
return;
}
// iterate to attackable unit
for(int i = 1; forwardUnit->team == team && i < GetStats().attackUnitDistance; i += forwardUnit->GetStats().unitSize)
forwardUnit = forwardUnit->forwardUnit;
// if forward unit is in the same team skip attack
if(forwardUnit->team == team)
return;
// attack the forward unit
if(lastAttackTimestamp + GetStats().attackRate - FPlatformTime::Seconds() < 0)
{
lastAttackTimestamp = FPlatformTime::Seconds();
forwardUnit->Damage(GetStats().attackDamage);
}
GEngine->AddOnScreenDebugMessage(4, 5.0f, FColor::Yellow, FString::Printf(TEXT("Time %f"), lastAttackTimestamp + GetStats().attackRate - FPlatformTime::Seconds()));
}
void AAgeOfWarUnit::Damage(const int32 damage)
{
if(killed)
return;
GEngine->AddOnScreenDebugMessage((int32)GetClass()->GetUniqueID(), 5.0f, FColor::Yellow, FString::Printf(TEXT("%d"), health));
health -= damage;
if(health > 0)
return;
killed = true;
if(IsValid(manager))
{
manager->UnitKill(this);
}
else
{
Destroy();
}
}

View File

@ -0,0 +1,99 @@
// Oleg Petruny proprietary.
#pragma once
#include "GameFramework/Actor.h"
#include "AgeOfWarUnit.generated.h"
USTRUCT(BlueprintType)
struct FAgeOfWarUnitStats
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText name = FText::GetEmpty();
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 price = 100;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 health = 5;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float moveSpeed = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 attackDamage = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 attackUnitDistance = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float attackStartRange = 100;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float attackRate = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 unitSize = 1;
};
UENUM(BlueprintType)
enum class UnitTeam : uint8
{
Player,
AI
};
UCLASS(Blueprintable, MinimalAPI, Abstract)
class AAgeOfWarUnit : public AActor
{
GENERATED_BODY()
public:
AAgeOfWarUnit();
virtual void Tick(float deltaTime) override;
UFUNCTION(BlueprintPure)
const FAgeOfWarUnitStats& GetStats();
UFUNCTION(BlueprintCallable)
void UpdateStats();
const FAgeOfWarUnitStats* stats = nullptr;
class AAgeOfWarManager* manager = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UnitTeam team;
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UFUNCTION(BlueprintImplementableEvent)
void OnUpdateStats();
void Damage(const int32 damage);
AAgeOfWarUnit* forwardUnit = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UBoxComponent* root;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UBoxComponent* allyblocker;
UPROPERTY(EditAnywhere)
class USceneComponent* traceStart;
private:
int32 health;
float attackStartRange;
FTimerHandle attackTimer;
double lastAttackTimestamp = 0;
UWorld* world;
bool killed = false;
};

View File

@ -24,13 +24,13 @@ ACrossyRoadManager::ACrossyRoadManager()
auto root = CreateDefaultSubobject<USceneComponent>(TEXT("Scene")); auto root = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
playerPos = CreateDefaultSubobject<USceneComponent>(TEXT("PlayerPos")); playerPos = CreateDefaultSubobject<USceneComponent>(TEXT("PlayerPos"));
playerPos->AttachToComponent(root, FAttachmentTransformRules::KeepRelativeTransform); playerPos->SetupAttachment(root);
camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
camera->AttachToComponent(root, FAttachmentTransformRules::KeepRelativeTransform); camera->SetupAttachment(root);
mannequin = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mannequin")); mannequin = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mannequin"));
mannequin->AttachToComponent(root, FAttachmentTransformRules::KeepRelativeTransform); mannequin->SetupAttachment(root);
} }
void ACrossyRoadManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate) void ACrossyRoadManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate)

View File

@ -24,13 +24,13 @@ ASubwaySurfManager::ASubwaySurfManager()
auto root = CreateDefaultSubobject<USceneComponent>(TEXT("Scene")); auto root = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
playerPos = CreateDefaultSubobject<USceneComponent>(TEXT("PlayerPos")); playerPos = CreateDefaultSubobject<USceneComponent>(TEXT("PlayerPos"));
playerPos->AttachToComponent(root, FAttachmentTransformRules::KeepRelativeTransform); playerPos->SetupAttachment(root);
camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
camera->AttachToComponent(root, FAttachmentTransformRules::KeepRelativeTransform); camera->SetupAttachment(root);
mannequin = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mannequin")); mannequin = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mannequin"));
mannequin->AttachToComponent(root, FAttachmentTransformRules::KeepRelativeTransform); mannequin->SetupAttachment(root);
} }
void ASubwaySurfManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate) void ASubwaySurfManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate)