C++ Two spawns on one "SpawnActor"

I can’t figure out why my game seems to spawn two copies of a class using one spawn actor line. The on screen debug message I have in the PKillCam runs twice which leads me to believe that it spawns two copies. Here is thecode I currently have:

GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Pawn, RV_TraceParams);

			if (Hit.GetActor() != NULL)
			{
				APPlayer* Killed = Cast<APPlayer>(Hit.GetActor());
				if (Killed)
				{
					Kill(Killed);
				}
			}

void APPlayer::Kill(APPlayer* Killed)
{
	AActor* Killer = this;
	Killed->Die(Killer);
}

void APPlayer::Die(AActor* Killer)
{
	Destroy();
	GetWorld()->SpawnActor<APKillCam>(GetActorLocation(), GetActorRotation());
}

The line trace is activated in a reliable server function. It also spawns both of them at 0,0,0 instead of the PPlayer actor location. What am I doing wrong?

It may be that the SpawnActor call isn’t spawning twice, but that Die is being called twice, or perhaps Kill is being called twice.
Perhaps you’re having two shots from the same Killer striking the same target before the first shot finished killing the Killed actor.

You could put a boolean flag in your APlayer actor that will be updated much more quickly than performing a full spawn etc.

if (Killed && Killed-bIsAlive)
{
    Killed-bIsAlive = false;
    Kill(Killed);
}

It might be worthwhile placing a log update at the head of your methods to see which ones are being called multiple times:

    if (Hit.GetActor() != NULL)
    {
        UE_LOG(MessageLog, Log, TEXT("Line Trace hit actor --  %s hit actor %s"), GetName(), Hit.GetActor()->GetName());
        ...
    }


void APPlayer::Kill(APPlayer* Killed)
{
    UE_LOG(MessageLog, Log, TEXT("APPLayer::Kill called --  %s killed %s"), GetName(), Killed->GetName());
    ...
}

void APPlayer::Die(AActor* Killer)
{
    UE_LOG(MessageLog, Log, TEXT("APPLayer::Die called --  %s killed by %s"), GetName(), Killer->GetName());
    ...
}

I put multiple onscreen debug messages in various places in the chain;

if (Hit.GetActor() != NULL)
			{
				APPlayer* Killed = Cast<APPlayer>(Hit.GetActor());
				if (Killed)
				{
					Killed->Die(this);
				}
				Explode(Hit.ImpactPoint);
			}
    
void APPlayer::Explode(FVector Location)
{
	GetWorld()->SpawnActor<APExplosion>(Location, FRotator(0, 0, 0));
}

void APPlayer::Die(AActor* Killer)
{
	GEngine->AddOnScreenDebugMessage(-1, 300.0f, FColor::Black, TEXT("121231231231"));
	GetWorld()->SpawnActor<APKillCam>(GetActorLocation(), GetActorRotation());
}

and in the PKillCam class:

void APKillCam::BeginPlay()
{
	Super::BeginPlay();

	GEngine->AddOnScreenDebugMessage(-1, 300.0f, FColor::Orange, TEXT("TEXT"));

}

I’ve found out that it seems to run the begin play once on the actor’s spawn and once when the chain of code is complete. The PExplode class is set up exactly the same as PKillCam; only an AddOnScreenDebugMessage in the begin play, but that only fires once. When I play the game and initiate this chain of code, I get the following print string (1 appears on screen first and 4 appears last.)

1-121231231231
2-TEXT
3-Explosion
4-TEXT

The text “Explosion” is from the PExplosion class.

Can you show me the rest of your hit detection method? After:

if (Hit.GetActor() != NULL)
{
    APPlayer* Killed = Cast<APPlayer>(Hit.GetActor());
    if (Killed)
    {
        Killed->Die(this);
    }
    Explode(Hit.ImpactPoint);
}

Let’s also see if the APKillCam object is running BeginPlay twice, or if two are being spawned. Can you change the logging call in its BeginPlay to:

GEngine->AddOnScreenDebugMessage(-1, 300.0f, FColor::Orange, (TEXT("APKillCam :: %s"),  *GetName()));

GetName() will retrieve a unique identifier for the actor.

I added a GetName to the PKillCam BeginPlay; I’ve also removed all PrintStrings outside of the explosion and Killcam. This is the new output when I activate a KillCam Spawn twice as seen during runtime:

PKillCam_1
PKillCam_1
Explosion
PKillCam_0
PKillCam_0
Explosion

So only one is being spawned and the begin play is being called twice. Why would this be happening? The Explosion and KillCam are only different in that Explosion is an actor and KillCam is a pawn.

Could it be that your GameMode is collecting the Pawn as a player and attempting to invoke BeingPlay on it as well? Perhaps your PlayerController?
Is there a default PlayerController type associated with the APKillCam that may be calling BeginPlay?

I’m throwing stuff at the wall at this point.

Honestly, I have no idea why this is happening; I put this in the KillCam’s tick:

if (MyKiller != NULL)
	{
		GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Emerald, MyKiller->GetName());
		GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, this->GetName());
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Purple, TEXT("Yes"));
		GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Yellow, this->GetName());
	}

and it outputs both parts of the if/else statement. The spawn code remained the same:

KCam = GetWorld()->SpawnActor<APKillCam>(Camera->GetComponentLocation(), GetActorRotation());
			KCam->MyKiller = Killer;  

This really shouldn’t be happening. I’m starting to think that it’s a glitch in the engine, but it still works fine in my other classes. Just the subclasses of APawn are having this issue.

It outputs BOTH parts of an if/else?

If the APKillCam isn’t too tied into other parts of your code, I would suggest copying the header and source into a backup directory, and then removing them from your project, then save it.

Exit Visual Studio, and delete the “.vs”, “Binaries”, “Derived Data Cache”, “Intermediate”, and “Saved” directories from your project directory.

Right-click on your .uproject file and Generate Visual Studio Project Files.

Then open your solution in VS and compile. If it works properly, there’s probably a duplicate of your class source in your project somewhere, search your directories for it and get rid of it.

If it doesn’t compile, comment out whatever you need to to get it to compile successfully and then try running your game again.

After that works clean, exit the editor and open up your VS solution again; then copy your backed up files into your source directory where they should be. Add the Existing Files to your project, then exit VS again, delete the directories again, generate your project files again, and then open up VS and compile.

Uncomment anything you commented out to get it to compile before.

Compile.

Cross your fingers, and run the game again.


It’s really weird behavior. I haven’t noticed any problems with classes derived from APawn in my project with the current release version.

I created a new project with only the following code in my player code:

//ServerFunctions

bool APPlayer::ServerFire_Validate()
{
	return true;
}

void APPlayer::ServerFire_Implementation()
{
	Fire();
}


//Functions

void APPlayer::Fire()
{
	if (Role < ROLE_Authority)
	{
		ServerFire();
	}
	else
	{
		FCollisionQueryParams TraceParams = FCollisionQueryParams(FName(TEXT("Trace")), true, this);
		TraceParams.bTraceComplex = false;
		TraceParams.bTraceAsyncScene = true;
		TraceParams.bReturnPhysicalMaterial = false;
		FHitResult Hit(ForceInit);

		FVector Start = Camera->GetComponentLocation();
		FVector End = ((Camera->GetForwardVector() * 10000) + Start);

		GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Pawn, TraceParams);

		if (Hit.GetActor() != NULL)
		{
			APPlayer* ShotActor = Cast<APPlayer>(Hit.GetActor());
			if (ShotActor)
			{
				ShotActor->Die(this);
			}
		}
	}
}

void APPlayer::Die(APPlayer* Killer)
{
	GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Cyan, Killer->GetName());
	KCam = GetWorld()->SpawnActor<APKillCam>(GetActorLocation(), GetActorRotation());
	KCam->Killer = Killer;
}

Just a line trace, makes sure the hit actor is of PPlayer, and executes Die on the hit actor. The ‘Killer’ variable in the Die function works perfectly for the debug message within the function, but it still runs this entire piece of code in the PKillCam:

    void APKillCam::Tick( float DeltaTime )
    {
    	Super::Tick( DeltaTime );
    
    	if (Killer != NULL)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, Killer->GetName());
    	}
    	else
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, TEXT("None"));
    	}
    
    }

I’ve also tried removing the KCam->Killer = Killer and at that point the if/else works perfectly in that only the green “None” is printed. While if the Killer is set both are executed.
In addition, I put the if/else statement in the begin play of PKillCam, it still runs twice but always prints two “None”'s whether I feed it a killer or not.

Are you sure it’s running both ends of that if->else on the same tick?
Could it be running the Killer == NULL branch until Killer is populated; or is it consistently pumping out both branches every tick?

Does it still spawn two killcams in the new project?

I had this in the PKillCam:

void APKillCam::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

	counter++;

	if (Killer != NULL)
	{
		GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, FString::SanitizeFloat(counter));
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Green, FString::SanitizeFloat(counter));
	}
	
	GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Purple, GetName());
	
}

This was the result:

112206-searf.png

That looks like two different objects.

It’s alternating the name and the counter. If it was running both branches of the if/else it would look like this:

  • PKillCam_1
  • 269.0 (red)
  • 269.0 (green)
  • PKillCam_1
  • 268.0 (red)
  • 268.0 (green)

But you’re getting:

  • PKillCam_1
  • 269.0 (red)
  • PKillCam_1
  • 269.0 (green)
  • PKillCam_1
  • 268.0 (red)
  • PKillCam_1
  • 268.0 (green)

That looks like two different PKillCam_1 objects ticking, one with the Killer != NULL, and the other with the Killer == NULL.

So where is the second PKillCam coming from, and why does it have the same name?

In your Die method:

void APPlayer::Die(APPlayer* Killer)
 {
     GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Cyan, Killer->GetName());
     KCam = GetWorld()->SpawnActor<APKillCam>(GetActorLocation(), GetActorRotation());
     KCam->Killer = Killer;
 }

Where is KCam being declared and how is it initialized?

Try clearing it before spawning the new actor:

void APPlayer::Die(APPlayer* Killer)
{
    GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Cyan, Killer->GetName());
    if (KCam) {
        KCam->Destroy();
        KCam = NULL;

    }
    KCam = GetWorld()->SpawnActor<APKillCam>(GetActorLocation(), GetActorRotation());
    KCam->Killer = Killer;

}

“I put the if/else statement in the begin play of PKillCam, it still runs twice but always prints two “None”'s whether I feed it a killer or not.”

That’s probably because the BeginPlay is executed before the Killer is assigned. If BeginPlay has already executed for the current level it’s executed for the new actor immediately after spawn.

I tried your KCam->Destroy(); KCam = NULL; It had no affect.
My KCcam is just a
class APKillCam* KCam;
in the APPlayer .h

Have you used UPROPERTY with it (this won’t be the fix, I’m just trying to get a full picture).

Are you initializing the variable as NULL in the constructor, or populating it there?
Is there anywhere else you are spawning APKillCams?

I built the APKillCam class in a new project on my end and have the same spawn code in my character; the only thing you’ve shown here that I didn’t copy are the line trace and the server methods, and I’m getting good results.

Would you try adding an action binding to an unassigned key and creating a new spawning method for APKillCam that isn’t reliant on anything other than responding to the keypress and see if it spawns two actors also? - if it does, my best guess right now is that the APKillCam has a duplicate declaration somewhere in your codebase.

Have you overridden the GameMode player pawn spawning, or its BeginPlay triggers?

I’m an absolute idiot; The code in the KillCam runs separately for each client in game. I’ve been spawning it on the host which created it for all players. It works perfectly if I run the spawn on a client. I cannot believe I didn’t realize that that might be the issue until now.
I feel like I’ve just wasted a week of your time. I sincerely apologize for that.
I understand if you don’t want to help my dumb ■■■ with one last thing, but if you would, the Killer variable is filled with the player who is killed and not the killer I put through it.

    		if (Hit.bBlockingHit)
    		{
    			APPlayer* HitPlayer = Cast<APPlayer>(Hit.GetActor());
    			if (HitPlayer)
    			{
    				HitPlayer->Die(this);
    			}
    		}
    
    void APPlayer::Die_Implementation(APPlayer* Killer)
    {
    	KCam = GetWorld()->SpawnActor<APKillCam>(GetActorLocation(), GetActorRotation());
    	KCam->Killer = Killer;
    }

.h

	UFUNCTION(Reliable, Client)
		void Die(APPlayer* Killer);

No worries. I’m glad you got it sorted out, and hope I was at least somewhat helpful.

As to the new issue; I really don’t know.
In looking back at the original version of the code you posted you had a Kill method that passed a pointer to the Killed actor; perhaps something got crossed in the refactoring of those methods; or perhaps that old Kill method is being called but setting the Killer value instead?

I’d search the player base for all instances of KCam and see if it’s ever being set improperly; and I’d check APKillCam for all instances where Killer is set.

Also, make sure your server methods aren’t getting the Killer and Killed crossed at any point. Perhaps the line trace origin has flipped the starting and ending point pawns? Maybe have a pointer to the FiringPlayer from which the line trace starts, and passing that to HitPlayer->Die, rather than “this”. That line trace happens within a server method, yes?

Perhaps even rename the incoming argument in the Die method from “Killer” to “KillingPlayer”, or changing the APKillCam::Killer to APKillCam::KilledBy or something like that (or change both) to prevent any kind of mistake there.

Again, I’m just throwing ideas out right now. The code you posted looks good; but it’s tough to track down more without seeing greater context.

//.H
UFUNCTION(Reliable, Server, WithValidation)
void ServerFire();

	UFUNCTION(Reliable, Client)
		void SDie(APPlayer* KillingPL);

	void Die(APPlayer* Killer);

//.CPP
void APPlayer::ServerFire_Implementation()
{
	FCollisionQueryParams TraceParams = FCollisionQueryParams(FName(TEXT("Trace")), true, this);
	TraceParams.bTraceComplex = false;
	TraceParams.bTraceAsyncScene = true;
	FHitResult Hit(ForceInit);

	FVector Start = Camera->GetComponentLocation();
	FVector End = ((Camera->GetForwardVector() * TraceDist) + Start);

	GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Pawn, TraceParams);

	if (Hit.bBlockingHit)
	{
		APPlayer* HitPlayer = Cast<APPlayer>(Hit.GetActor());
		if (HitPlayer)
		{
			GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Green, this->GetName());
			HitPlayer->Die(this);
		}
	}
}
void APPlayer::Fire()
{
	ServerFire();
}
void APPlayer::SDie_Implementation(APPlayer* KillingPL)
{
	GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Purple, KillingPL->GetName());
}

void APPlayer::Die(APPlayer* Killer)
{
	GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::White, Killer->GetName());
	SDie(Killer);
}

I’m using this code in a multiplayer setting.
All three messages should be the same, but the SDie message only prints PPlayer_1 (with 2 players)
Other than that, the first two messages work correctly for both host and client (from what I could see anyway)
My assumption is the UFUNCTION (Reliable, Client) forces the code through the client only and this copies the variable’s “this” and replaces it with a reference to the first client it sees that it goes through. Don’t know how to get it to send a reference to another player rather than change it to the client it runs it through. My assumption for the fix would be some way to do a “run on owning client only” function.