Game Mode Multiple Spawn Points
Here’s some code that works as long as you have multiple player starts in your level!
//your game mode.h
//I am overriding Game Mode virtual functions, you dont have to call them, Game Mode class calls them whenever a player joins.
/** select best spawn point for player */
virtual AActor* ChoosePlayerStart(AController* Player) override;
/** always */
virtual bool PlayerCanRestart(APlayerController* Player) override;
/** check if player can use spawnpoint */
virtual bool IsSpawnpointAllowed(APlayerStart* SpawnPoint, AController* Player) const;
/** check if player should use spawnpoint */
virtual bool IsSpawnpointPreferred(APlayerStart* SpawnPoint, AController* Player) const;
//your game mode.cpp
//adjust to your preferences, could make into variable to be tweaked in BP
#define TOO_CLOSE_RADIUS 128
bool AGalaxyGameMode::PlayerCanRestart(APlayerController* Player)
{
return true;
}
/** Choosing Start Point */
AActor* AGalaxyGameMode::ChoosePlayerStart(AController* Player)
{
//If PIE, dont do this, so "play from here" works
if(GetWorld()->WorldType == EWorldType::PIE)
{
return Super::ChoosePlayerStart(Player);
//~~~~~~~~~~~~~~~~~~~~~~~
}
static TArray<APlayerStart*> PreferredSpawns;
static TArray<APlayerStart*> FallbackSpawns;
PreferredSpawns.Reset();
FallbackSpawns.Reset();
for (int32 i = 0; i < PlayerStarts.Num(); i++)
{
APlayerStart* TestSpawn = PlayerStarts*;
if (IsSpawnpointAllowed(TestSpawn, Player))
{
if (IsSpawnpointPreferred(TestSpawn, Player))
{
PreferredSpawns.Add(TestSpawn);
}
else
{
FallbackSpawns.Add(TestSpawn);
}
}
}
APlayerStart* BestStart = NULL;
if (PreferredSpawns.Num() > 0)
{
BestStart = PreferredSpawns[FMath::RandHelper(PreferredSpawns.Num())];
}
else if (FallbackSpawns.Num() > 0)
{
BestStart = FallbackSpawns[FMath::RandHelper(FallbackSpawns.Num())];
}
return BestStart ? BestStart : Super::ChoosePlayerStart(Player);
}
bool AGalaxyGameMode::IsSpawnpointAllowed(APlayerStart* SpawnPoint, AController* Player) const
{
if(!SpawnPoint) return false;
//~~~~~~~~~~~~~~~
//Character Proximity?
for (TActorIterator<ACharacter> Itr(GetWorld()); Itr; ++Itr)
{
//Check if there's any player within TOO_CLOSE_RADIUS
if ((SpawnPoint->GetActorLocation() - Itr->GetActorLocation()).SizeSquared() < TOO_CLOSE_RADIUS *TOO_CLOSE_RADIUS)
{
return false;
}
}
//~~~
//Test if there is any World Geometry in the way of the spawn Point!
FHitResult Hit;
return UVictoryCore::VTrace(SpawnPoint,
SpawnPoint->GetActorLocation() + FVector(0,0,256),
SpawnPoint->GetActorLocation() - FVector(0,0,256),
Hit
);
}
bool AGalaxyGameMode::IsSpawnpointPreferred(APlayerStart* SpawnPoint, AController* Player) const
{
if(!SpawnPoint) return false;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Last Resort Spawn Point!
if(SpawnPoint->GetName().Contains("LastResortSpawnPoint")) return false;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ACharacter* MyPawn = Player ? Cast<ACharacter>(Player->GetPawn()) : NULL;
if (MyPawn)
{
const FVector MyLocation = MyPawn->GetActorLocation();
for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
{
ACharacter* OtherPawn = Cast<ACharacter>(*It);
if (OtherPawn && OtherPawn != MyPawn)
{
const float CombinedHeight = (MyPawn->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() + OtherPawn->GetCapsuleComponent()->GetScaledCapsuleHalfHeight()) * 2.0f;
const float CombinedRadius = MyPawn->GetCapsuleComponent()->GetScaledCapsuleRadius() + OtherPawn->GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector OtherLocation = OtherPawn->GetActorLocation();
// check if player start overlaps this pawn
if (FMath::Abs(MyLocation.Z - OtherLocation.Z) < CombinedHeight && (MyLocation - OtherLocation).Size2D() < CombinedRadius)
{
return false;
}
//Check if there's any player within TOO_CLOSE_RADIUS
if(SpawnPoint)
{
if ((SpawnPoint->GetActorLocation() - OtherLocation).SizeSquared() < TOO_CLOSE_RADIUS * TOO_CLOSE_RADIUS)
{
return false;
}
}
}
}
}
return true;
}
**For You To Do**
You can replace my custom C++ library function, UVictoryCore::VTrace(), with GetWorld()->LineTraceSingle
**
What This Does**
Basically when a player wants to spawn, server or as many clients as you want, the code checks all the player starts and finds one that is not occupied, by verifying there is no colliding geometry in way, and also making sure there are no characters that are with TOO_CLOSE_RADIUS of the spawn point
You can tweak the TOO_CLOSE_RADIUS part or remove it as you see fit.
You could also use the Character’s Scaled Capsule Radius
float CapsuleRadius = Character->GetCapsuleComponent()->GetScaledCapsuleRadius();
//Remember to use **CapsuleRadius * CapsuleRadius** since it is a Size Squared check!
**Not in PIE**
Notice I skip my code if PIE is being used, so that "play from here" works
You should test using Standalone or running the game from commandline to see my code working correctly.
Enjoy!
Rama