Hello,
I’m trying to implement functionality to restart the round. This includes restoring all of the objects in the current level to their original state. I would like to accomplish this without having to call ServerTravel, or anything that requires the engine to reload the entire map. That, in my opinion, is a bad solution.
I’m using version 4.7.6.
Here’s what I have so far:
-In my GameMode header, I keep an array of AActors:
UPROPERTY()
TArray<AActor *> currentRoundActors;
These are the actors that are present at the beginning of the round.
-When the BeginPlay is called, I populate the array:
void ANimModGameMode::BeginPlay()
{
Super::BeginPlay();
for (FActorIterator It(GetWorld()); It; ++It)
{
AActor* A = *It;
if (A && A != this && !A->IsA<AController>() && ShouldReset(A))
{
currentRoundActors.Add(A);
}
}
}
-I have a trigger volume that, when triggered by a certain player, will:
-freeze all of the players in the game.
-start a timer
-When the timer expires, it will call “RestartRound” on the current GameMode:
#define ISSERVER (GEngine->GetNetMode(GetWorld()) < NM_Client)
void ANimModGameMode::RestartRound()
{
if (!ISSERVER)
return;
//Players should already be frozen
//FreezePlayers();
// Reset all actors (except controllers, the GameMode, and any other actors specified by ShouldReset())
//TODO: This doesn't handle actors that are in currentRoundActors that have been destroyed before this point.
//We need to find a way to respawn those, as well.
TArray<AActor *> destroyActors;
TArray<AActor *> respawnActors;
for (FActorIterator It(GetWorld()); It; ++It)
{
AActor* A = *It;
if (A && A != this && !A->IsA<AController>() && ShouldReset(A))
{
if (currentRoundActors.Contains(A))
{
respawnActors.Add(A);
currentRoundActors.Remove(A);
}
else
destroyActors.Add(A);
}
}
//Whatever is left is something that is pending being deleted, or is deleted.
//Respawn what we can.
for (AActor *A : currentRoundActors)
{
if (A == nullptr)
{
destroyActors.Add(A);
continue;
}
if (ShouldReset(A))
{
respawnActors.Add(A);
}
}
//Respawn the ones that we do want.
while (respawnActors.Num() > 0)
{
AActor *A = respawnActors[0];
if (A == nullptr)
{
respawnActors.RemoveAt(0);
continue;
}
//Add a new actor as a copy of the 'reloaded' one, since the reloaded one is disappearing.
FActorSpawnParameters spawnParams;
spawnParams.Template = A;
/*spawnParams.bNoFail = true;
spawnParams.bNoCollisionFail = true;
spawnParams.Name = NAME_None;*/
//A->SetReplicates(true);
FVector orig = A->GetActorLocation();
FRotator rot = A->GetActorRotation();
AActor *newActor = GetWorld()->SpawnActor
(
A->GetClass(),
&orig,
&rot,
spawnParams
);
if (newActor == nullptr)
continue;
//newActor->SetOwner(A->GetOwner());
//Add the new one
currentRoundActors.Add(newActor);
if (currentRoundActors.Contains(A))
currentRoundActors.Remove(A);
//Remove the old one
destroyActors.Add(A);
respawnActors.RemoveAt(0);
}
//Destroy all of the actors we don't want.
while (destroyActors.Num() > 0)
{
AActor *actor = destroyActors[0];
destroyActors.RemoveAt(0);
//This can (theoretically happen) if we added something that has already been deleted.
if (actor == nullptr)
continue;
actor->Destroy(true, true);
actor = nullptr;
}
//GetWorld()->ForceGarbageCollection(true); //...Maybe?
// reset the GameMode...Which does ****-all.
//Reset();
// Notify the level script that the level has been reset
ALevelScriptActor* LevelScript = GetWorld()->GetLevelScriptActor();
if (LevelScript)
{
LevelScript->LevelReset();
}
//'Respawn' the players
for (FConstControllerIterator Iterator = GetWorld()->GetControllerIterator(); Iterator; ++Iterator)
{
AController* Controller = *Iterator;
ANimModPlayerController* PlayerController = Cast<ANimModPlayerController>(Controller);
if (PlayerController)
{
RestartPlayer(PlayerController);
//PlayerController->ClientRestartRound();
//PlayerController->ServerRestartPlayer();
}
else
Controller->Reset();
}
//Unfreeze the players.
UnfreezePlayers();
}
Just in case someone wants to see what my “ShouldReset” looks like:
bool ANimModGameMode::ShouldReset(AActor* ActorToReset)
{
UClass *actorClass = ActorToReset->GetActorClass();
AGameNetworkManager *networkManager = Cast<AGameNetworkManager>(ActorToReset);
//AParticleEventManager *particleEventManager = Cast<AParticleEventManager>(ActorToReset);
if
(
actorClass->IsChildOf(UWorld::StaticClass()) ||
actorClass->IsChildOf(APlayerController::StaticClass()) ||
actorClass->IsChildOf(ACharacter::StaticClass()) ||
actorClass->IsChildOf(AGameMode::StaticClass()) ||
actorClass->IsChildOf(APlayerCameraManager::StaticClass()) ||
actorClass->IsChildOf(APlayerStart::StaticClass()) ||
actorClass->IsChildOf(AHUD::StaticClass()) ||
actorClass->IsChildOf(UGameViewportClient::StaticClass()) ||
actorClass->IsChildOf(AGameSession::StaticClass()) ||
/*actorClass->IsChildOf(AGameNetworkManager::StaticClass()) ||*/
actorClass->IsChildOf(APlayerState::StaticClass()) ||
actorClass->IsChildOf(AWorldSettings::StaticClass()) ||
actorClass->IsChildOf(AGameState::StaticClass()) ||
actorClass->IsChildOf(AVIPTrigger::StaticClass()) ||
actorClass->IsChildOf(ULevel::StaticClass()) ||
actorClass->IsChildOf(ALight::StaticClass()) ||
actorClass->IsChildOf(ALevelScriptActor::StaticClass()) ||
actorClass->IsChildOf(ALightmassImportanceVolume::StaticClass()) ||
actorClass->IsChildOf(APostProcessVolume::StaticClass()) ||
actorClass->IsChildOf(ANavigationData::StaticClass()) ||
actorClass->IsChildOf(AAtmosphericFog::StaticClass()) ||
actorClass->IsChildOf(ASpectatorPawn::StaticClass()) ||
/*#ifdef DEBUG*/
actorClass->IsChildOf(AGameplayDebuggingReplicator::StaticClass()) ||
/*#endif*/
/*particleEventManager != nullptr ||*/
(AActor *)GetWorld()->MyParticleEventManager == ActorToReset ||
networkManager != nullptr
)
return false;
if (currentRoundActors.Contains(ActorToReset) && ActorToReset->IsPendingKill())
return true;
if (ActorToReset->IsRootComponentStatic())
return false; //I...think...
return true;
}
Now, this code works(ish) when I’m testing in with 1 player in a non-dedicated server. If I test with two players in a dedicated server, the objects stay the way they are. Sometimes I can get them to disappear from the clients, altogether, but it’s clear that they respawned on the server, because I “collide with” empty air where the objects are supposed to be.
I also tried using “FReloadObjectArc”. Instead of storing a pointer to the Actors at when BeginPlay was called, I was serializing them in to their own FReloadObjectArc, then trying to overwrite them in “RestartRound”. That didn’t do anything.
I’m close (I think). I just need to know why the objects are respawning on the clients. And why static mesh actors aren’t respawning at all.
Thoughts?