RPC to spawn weapon | ControllerIterator

EDIT: UE 4.7!

Hey guys,

i’m just digging into UE4 C++ and started myself a small project to learn a tiny bit of everything for the start.

What am i trying to do?

I want to let the GameMode (Serverside) spawn a weapon for every connected player.

My GameMode.h:



#pragma once
#include "GameFramework/GameMode.h"
#include "SmallCoopTDGameMode.generated.h"

UCLASS(Blueprintable)
class ASmallCoopTDGameMode : public AGameMode
{
	GENERATED_BODY()

	/** Max Players to be waited for before starting the match **/
	int32 MaxNumberOfPlayers;
	/** Difficulty, regarding the strength of the enemies **/
	int32 Difficulty;



public:
	ASmallCoopTDGameMode(const FObjectInitializer& ObjectInitializer);

	//void SetStartWeapons();

	UFUNCTION(reliable, server, WithValidation)
	void ServerSetStartWeapons();
	
	bool ServerSetStartWeapons_Validate();
	void ServerSetStartWeapons_Implementation();

	virtual void BeginPlay() override;
};


And my GameMode.cpp:




#include "SmallCoopTD.h"
#include "SmallCoopTDGameMode.h"
#include "SmallCoopTDPlayerController.h"
#include "SmallCoopTDCharacter.h"
#include "UnrealNetwork.h"
#include "TestWeapon1.h"

ASmallCoopTDGameMode::ASmallCoopTDGameMode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
	// use our custom PlayerController class
	PlayerControllerClass = ASmallCoopTDPlayerController::StaticClass();

	// set default pawn class to our Blueprinted character
	/*static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/TopDown/Blueprints/TopDownCharacter"));
	if (PlayerPawnBPClass.Class != NULL)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}*/

	DefaultPawnClass = ASmallCoopTDCharacter::StaticClass();
}


bool ASmallCoopTDGameMode::ServerSetStartWeapons_Validate()
{
	return true;
}

void ASmallCoopTDGameMode::ServerSetStartWeapons_Implementation()
{
	AGameState* TempGameState = GetGameState<AGameState>(); //Ignore this. Was a test for getting the Controller with the PlayerStateOwners etc.
	ASmallCoopTDCharacter* TempCharacter;
	int32 i = 3; // DEBUG
	for (FConstControllerIterator It = GetWorld()->GetControllerIterator(); It; It++)
	{
		GEngine->AddOnScreenDebugMessage(i, 20.0f, FColor::Green, FString::FromInt(i)); // DEBUG
		i++; // DEBUG
		ASmallCoopTDPlayerController* PC = Cast<ASmallCoopTDPlayerController>(*It);
		if (PC)
		{
			TempCharacter = Cast<ASmallCoopTDCharacter>(PC->GetPawn());
			if (TempCharacter)
			{
				FActorSpawnParameters SpawnParams;
				SpawnParams.Instigator = Instigator;
				SpawnParams.Owner = PC;
				SpawnParams.Name = "MyTestWeapon";
				SpawnParams.bNoCollisionFail = true;
				ATestWeapon1* TempWeapon = GetWorld()->SpawnActor<ATestWeapon1>(ATestWeapon1::StaticClass(), FVector(0, 0, 500), FRotator(0, 0, 0), SpawnParams);
				//TempWeapon->SetActorLocation(FVector(0, 0, 500));
				TempWeapon->AttachRootComponentTo(TempCharacter->GetMesh(), FName("WeaponSocket"), EAttachLocation::SnapToTarget, false);
				TempCharacter->SetCurrentWeapon(TempWeapon);

				GEngine->AddOnScreenDebugMessage(13, 20.0f, FColor::Green, PC->GetName()); // DEBUG
			}
		}
	}
}

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

	if (Role == ROLE_Authority)
	{
		ServerSetStartWeapons();
	}
}


What does it do by now?

It spawns a weapon for the Server only. The Weapon is also only visible for the server, so i guess he is the only one spawning here at all.

Replication and RPCs still giving me headache x). I guess i need a multicast function, but can someone point me into the right direction for the start? Also the ControllerIterator only does all the things ONE time, although there are more Controllers (or at least there should be more, since only the server should call this function).

I hope someone can give me a bit of an insight on what i am doing terribly wrong here.

(: Thanks in advance you awesome people.

If you spawn an actor as a server, it should automatically spawn by replication on all clients if the actor is set to replicate. Check your weapon class constructor and make sure it’s replicated by calling


SetReplicates(true);
this->bAlwaysRelevant = true;


Also, GameMode resides only on the server. That means


ASmallCoopTDGameMode::BeginPlay()

gets called only once on and by the server upon start.

You’re better off by giving the player a weapon when his pawn has spawned.
We do this by spawning the weapon inside our custom ACharacter class like so:


void ASupraPawn::PostInitializeComponents()
{
    Super::PostInitializeComponents();

    SpawnWeapon();
}

void ASupraPawn::SpawnWeapon()
{
    /**
     * You'll only want to spawn the weapon actors as a server.
     * If you don't do this, the actor will spawn on the server, as well as the clients.
     * As a result, the clients will have two weapons: one the server created and one the client created locally.
     */
    if (Role == ROLE_Authority)
    {
        UWorld* World = GetWorld();

        // Give the player his or her weapon.
        FActorSpawnParameters WeaponSpawnParameters;
        WeaponSpawnParameters.Owner = this;

        // Please take note that the location and rotation will be *relative* to the pawn.
        this->Weapon = World->SpawnActor<APawnWeapon>
        (
            this->WeaponClass,
            FVector::ZeroVector,
            FRotator::ZeroRotator,
            WeaponSpawnParameters
        );

        // Attach the weapon to the player such that it follows him around in the world.
        this->Weapon->AttachRootComponentTo(this->Camera);
        GetWeapon()->GetAim.BindDynamic(this, &ASupraPawn::GetAim);
    }
}

If you insist on doing it in the gamemode instead, you can always override the following AGameMode function:


APawn* ATeamGameMode::SpawnDefaultPawnFor(AController* NewPlayer, AActor* StartSpot)

Spawn the weapon there, after


Super::SpawnDefaultPawnFor(NewPlayer, StartSpot);

has returned you a newly spawned pawn.

Hey,

thanks for your answer.


bReplicates = true;

fixed the only ServerSide thingy. Seems like i forgot about that.

But my Iterator is still not working.
I really want to let the Server Spawn these first Weapons through the GameMode, since
i want to change the StarteWeapons with different GameModes.

I know that the Server has knowledge of all PlayerControllers. So why can’t i iterate through them?
In the ShooterGame they do it like this:


for (FConstControllerIterator It = GetWorld()->GetControllerIterator(); It; It++)

But this seems to only access the Servers PlayerController (as a listen host).

How can i access the rest of the PlayerControllers? Is the “BeginPlay()” of the GameMode running before
the Players are connected? Should i use the PostLogin Function for that?

First, you don’t need an RPC for


GetStartWeapons();

You only need to call the function as a server, as the created weapons will be replicated to all clients. Luckily for you, AGameMode already only exists on the server, so you don’t need to do an authority check.

As for BeginPlay(), I’m not sure if you are guaranteed to have all controllers in this function. Like I said before, it might be better to spawn the weapon when a player has actually spawned or at least when his controller has been created (the first seems better, since you’re attaching it to the pawn’s mesh). Not only are you guaranteed to have access to an existing controller, but you will also create a weapon for a player that has joined after the game has already started.

Yeah,

i guess i really need to move this into the BeginPlay of the CharacterClass.
I hoped to pack things like this into the GameMode and use “PostLogin” or something like this.

But this seems to create weired effects >.<.

I will move this into the BeginPlay of the Character and get the WeaponClass from the GameMode there too.

Thanks for your time!

Ok xD

Still have a problem with the replication.

What do i do:

Call a Replicated Function on the BeginPlay() of my Character Class. In this, i spawn the DefaultWeaponClass.

What happens?:

Only the last Character gets a Weapon (4 Clients -> only Client 4 gets a weapon).
If i use a ListenServer, then the Server gets a Weapon too, but only visible for himself.

My Code:

Character Header:



	virtual void BeginPlay() override;


	/** Server Functions **/
	UFUNCTION(reliable, server, WithValidation)
	void SpawnFirstWeapon();

	bool SpawnFirstWeapon_Validate();
	void SpawnFirstWeapon_Implementation();


Character CPP File:



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

	SpawnFirstWeapon();
}	
	

/** Server Functions **/

bool ASmallCoopTDCharacter::SpawnFirstWeapon_Validate()
{
	return true;
}

void ASmallCoopTDCharacter::SpawnFirstWeapon_Implementation()
{
	ASmallCoopTDGameMode* GameMode = Cast<ASmallCoopTDGameMode>(GetWorld()->GetAuthGameMode());
	if (GameMode)
	{
		//GEngine->AddOnScreenDebugMessage(i, 10.f, FColor::Black, TEXT("Spawned Weapon ") + FString::FromInt(i));
		
		FActorSpawnParameters SpawnParams;
		SpawnParams.Instigator = Instigator;
		SpawnParams.Owner = this;
		SpawnParams.Name = "MyTestWeapon";
		SpawnParams.bNoCollisionFail = true;
		ABaseWeaponClass* TempWeapon = GetWorld()->SpawnActor<ABaseWeaponClass>(GameMode->GetDefaultWeaponClass(), FVector(0, 0, 500), FRotator(0, 0, 0), SpawnParams);
		TempWeapon->SetOnlyOwnerSee(false);
		TempWeapon->SetOwnerNoSee(false);
		TempWeapon->AttachRootComponentTo(GetMesh(), FName("WeaponSocket"), EAttachLocation::SnapToTarget, false);
		SetCurrentWeapon(TempWeapon);
	}
}


It stripped the code down to the valid points.

Can someone spot the error? I compared this with a friend of mine, who also spawns the weapons like this, but i can’t find the difference.
The Weapons are set to be replicated (the last client does have a replicated weapon on all other clients) . >.<

Thanks in advance (:

You don’t have to make this a server function


UFUNCTION(reliable, server, WithValidation)

BeginPlay() gets called on the server already (I think?), and your ‘if (GameMode)’ will only be valid on the server, so you already have it covered.
Once the server spawned the weapon, it should be replicated and visible to all other clients. I don’t see any direct issues with the code. Maybe it’s ‘BeginPlay’ that doesn’t get called properly. We use


ACharacter::PostInitializeComponents()

I suspect the problem being somewhere else if this does not fix it.