Character Blueprint vs. Player Controller

Hi, so I wanted to ask a quick question. Should I be using a Player Controller for my multiplayer game? I never understood what PC’s did and wanted someone to weigh in.

Thanks!

There will always be a player controller for each player. It’s how the engine identifies which player runs which character, and which remote computer a particular command is coming in from. You can’t avoid this – it’s created for you.

That being said, you could probably build a multiplayer game that used the pawn objects for input decoding and replication, but you’d have to build a bunch of work that the PlayerController already does related to tying particular sets of inputs to particular Pawns and forwarding them to other players – it’s a lot of work (and possible bugs!) that the engine already solves for you.

This is for networked games: Yes, you are much better off understanding and using the Player Controller construct.

For multiplayer split-screen games, it’s not as necessary – you could just wire all the inputs for the different local playercontrollers into each separate Pawn. That could work. I wouldn’t, but it could be done.

5 Likes

Do you know like what work the Player Controller could do for me?

The answer to that question is, everything. It can handle input management and complex logic that is associated with the player and not the pawn. For example, if you’re making a deathmatch style game, you would assume the player pawn will die often meaning needing to be destroyed in the logic and then recreated. If you’re doing all your player logic in a pawn class or actor class then you’re essentially reinitializing all of the player logic each time a pawn is destroyed.

The thing about the player controller is that every player that is connected to your MP session has an associated player controller that isn’t ever destroyed. If you implement your player logic in the PC then you can avoid odd bugs that might arise when doing multiplayer stuff that is generally networking logic related. Implementing your logic in the controller means that you can then simply create a new pawn for that controller to possess and continue on without having to reimplement the players logic.

Doing it either way will work just fine, but I’d posit that best practice is to implement your player logic in the PC to meet the Single Responsibility Principle and it ends up being more clean that way. For example if you want to add more versions of avatars players could use you dont have to implement logic for them just create a new pawn class with a different mesh and animations and you’re set.

Obviously this all depends on the type of game you’re making but I’d say most games would benefit from using the PC rather than Pawn or Actor class for player input handling.

Also the PC has a lot of replication procedures inherently associated with them making designing MP worlds in UE more streamline.

Hope that helps.

2 Likes

Makes so much sense now. One last thing, should I do everything in my PC or should I do some stuff in the Pawn class (like hit events, attacking a player, etc.)?

2 Likes

So taking the example where you might have different avatars, you would want hit and collision events to be associated with that particular actor or pawn so as not to be forced to reimplement the same logic over and over again. You can implement stats that might be universal to the particular player in the PC and then emit events up from the actor or pawn as needed. If the stat is related to that particular avatar only then it would be find to simply implement that in the pawn or actor.

For example you might have a brawler character that has higher melee or a sniper character that has less recoil, those would be specific to that type so would make since to be implemented in the actor or pawn. It is possible to do all that in the PC but for expandability and maintainability trying to separate the responsibilities as best you can will make for a better development experience. Especially if you plan to do skinning that doesn’t change much other than the look of a character, you’d have a base pawn or actor class and then slap a new mesh and collision onto that without having to reimplement new logic.

1 Like

How would I do like movements with the PlayerController?

There are many ways to do it. But the way I do it is by creating a reference to the pawn or actor in the PC at creation so i can access the movement component and just pass all the inputs that way. That reference also allows you access to other aspects of the Actor or pawn as well.

In the Construction Script you create the reference?

You can do it there or onbeginplay, the main thing is to make sure that both the controller and the instance of the actor or pawn exists when you try to obtain a reference. When that happens will depend on your game logic. Since it’s an MP game likely your playercontroller will exist long before the actual actor or pawn does so getting a reference shouldn’t be a problem. How and when you get it will be an implementation detail you’ll want to work out that works best for you.

Alright, how would I get the pawn/actor its controlling?

1 Like

I dont know how to in Blueprints, or at least I haven’t tried, I primarily work in C++ Here’s an example Controller Header from something I was working on.

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/Pawn.h"
#include <MxO/MxOCharacter.h>
#include "MxOPlayerController.generated.h"

UCLASS()
class MXO_API AMxOPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	AMxOPlayerController();

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Debug, meta = (AllowPrivateAccess = "true"))
		bool debug;

#pragma region Class Declarations
	class AMxOCharacter* Player;
	class ACharacter* PlayerCharacter;
	class APawn* PlayerPawn;
	class UCharacterMovementComponent* PlayerMovement;
	class USpringArmComponent* CameraBoom;
#pragma endregion

#pragma region Camera Variables
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
		float CameraArmMin;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
		float CameraArmMax;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
		float CameraArmDefault;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
		float zoomRate;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
		float zoomInterp;

	bool CanMoveCamera;
#pragma endregion

#pragma region Mouse Variables
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Mouse, meta = (AllowPrivateAccess = "true"))
		float mouseSensitivity;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Mouse, meta = (AllowPrivateAccess = "true"))
		bool LeftMouseClickPressed;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Mouse, meta = (AllowPrivateAccess = "true"))
		bool RightMouseClickPressed;
#pragma endregion

#pragma region Movement Variables
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float RotationRate;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float dJumpZVelocity;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float SuperJumpZVelocity;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float AirControl;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float WalkSpeed;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float WalkSpeedCrouched;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float BackupSpeed;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		bool MovingForward;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		bool MovingBackward;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		bool Strafing;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		bool Rotating;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float ForwardValue;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float StrafingValue;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
		float RotationValue;
#pragma endregion

protected:
#pragma region Interface Overrides
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaSeconds) override;
	virtual void SetupInputComponent() override;
#pragma region

#pragma region Camera Controls
	virtual void CalcCamera(float DeltaSeconds, FMinimalViewInfo& OutResult) override;
	void YawCamera(float DeltaSeconds);
	void PitchCamera(float DeltaSeconds);
	void RotateCameraToCharacter();
#pragma endregion

#pragma region Mouse Wheel
	void MouseWheelUp();
	void MouseWheelDown();
#pragma endregion

#pragma region Mouse Buttons
	void LeftMousePressed();
	void LeftMouseReleased();
	void RightMousePressed();
	void RightMouseReleased();
#pragma endregion

#pragma region Movement Methods
	void MoveForward(float Value);
	void Rotate(float Value);
	void Strafe(float Value);
	void RotateCharacterToCamera();
#pragma endregion

#pragma region Jumping Methods
	void Jump();
	void StopJumping();
	void SuperJump();
	void StopSuperJumping();
#pragma endregion

};

And in the CPP

#include "MxOPlayerController.h"
#include "Kismet/KismetMathLibrary.h"

#pragma region Constructor
AMxOPlayerController::AMxOPlayerController()
{
  PrimaryActorTick.bCanEverTick = true;
  debug = true;

	// Initialize Camera Variables
	CameraArmMin = 100.f;
	CameraArmMax = 1000.f;
    CameraArmDefault = 300.f;
	zoomRate = 100.f;
	zoomInterp = 20.f;

	CanMoveCamera = false;

	mouseSensitivity = 500.f;

	RotationRate = 2.f;
	dJumpZVelocity = 600.f;
	SuperJumpZVelocity = 5600.f;
	AirControl = 0.f;
	WalkSpeed = 600.f;
	WalkSpeedCrouched = 300.f;
	BackupSpeed = 300.f;

	MovingBackward = false;

	// Disable automatic camera management
	bAutoManageActiveCameraTarget = false;
}
#pragma endregion

#pragma region Begin Play
void AMxOPlayerController::BeginPlay()
{
  Super::BeginPlay();

	// Set camera view target to this object
	SetViewTarget(this);

  if (GetCharacter() != nullptr)
  {
		Player = Cast<AMxOCharacter>(GetCharacter());
		PlayerCharacter = Cast<ACharacter>(GetCharacter());
		PlayerPawn = Cast<APawn>(GetCharacter());
                PlayerMovement = Cast<UCharacterMovementComponent>(GetCharacter()->GetCharacterMovement());
  }

	if (Player != nullptr)
	{
		Player->bUseControllerRotationPitch = false;
		Player->bUseControllerRotationYaw = false;
		Player->bUseControllerRotationRoll = false;
		CameraBoom = Player->GetCameraBoom();
		CameraBoom->TargetArmLength = CameraArmDefault;
	}

	if (PlayerMovement != nullptr)
	{
		PlayerMovement->bOrientRotationToMovement = false; // Character moves in the direction of input...	
		PlayerMovement->JumpZVelocity = dJumpZVelocity;
		PlayerMovement->AirControl = AirControl;
		PlayerMovement->MaxWalkSpeed = WalkSpeed;
		PlayerMovement->MaxWalkSpeedCrouched = WalkSpeedCrouched;
	}
  if (debug) UE_LOG(LogTemp, Warning, TEXT("Player: %s"), *Player->GetName());
}
#pragma endregion

#pragma region Tick
void AMxOPlayerController::Tick(float DeltaSeconds)
{
  Super::Tick(DeltaSeconds);

	if (CanMoveCamera)
	{
		YawCamera(DeltaSeconds);
		PitchCamera(DeltaSeconds);
	}
}
#pragma endregion

#pragma region Input Component
void AMxOPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	check(InputComponent);

	InputComponent->BindAction("Jump", IE_Pressed, this, &AMxOPlayerController::Jump);
	InputComponent->BindAction("Jump", IE_Released, this, &AMxOPlayerController::StopJumping);
	InputComponent->BindAction("SuperJump", IE_Pressed, this, &AMxOPlayerController::SuperJump);
	InputComponent->BindAction("SuperJump", IE_Released, this, &AMxOPlayerController::StopSuperJumping);

	InputComponent->BindAction("MouseWheelUp", IE_Pressed, this, &AMxOPlayerController::MouseWheelUp);
	InputComponent->BindAction("MouseWheelDown", IE_Pressed, this, &AMxOPlayerController::MouseWheelDown);
	InputComponent->BindAction("LeftMouse", IE_Pressed, this, &AMxOPlayerController::LeftMousePressed);
	InputComponent->BindAction("LeftMouse", IE_Released, this, &AMxOPlayerController::LeftMouseReleased);
	InputComponent->BindAction("RightMouse", IE_Pressed, this, &AMxOPlayerController::RightMousePressed);
	InputComponent->BindAction("RightMouse", IE_Released, this, &AMxOPlayerController::RightMouseReleased);

	InputComponent->BindAxis("MoveForward", this, &AMxOPlayerController::MoveForward);
	InputComponent->BindAxis("Rotate", this, &AMxOPlayerController::Rotate);
	InputComponent->BindAxis("StrafeRight", this, &AMxOPlayerController::Strafe);
}
#pragma endregion

#pragma region Camera Controls
void AMxOPlayerController::CalcCamera(float DeltaSeconds, FMinimalViewInfo& OutResult)
{
	// Player Pivot Point
	FVector PivotPoint = Player->GetActorLocation() + Player->CameraPivotPoint;

	RotateCameraToCharacter();

	OutResult.Location = CameraBoom->GetSocketLocation("SpringEndpoint");

	FRotator LookAtPlayer = UKismetMathLibrary::FindLookAtRotation(OutResult.Location, PivotPoint);
	OutResult.Rotation = LookAtPlayer;
}

void AMxOPlayerController::YawCamera(float DeltaSeconds)
{
	InputComponent->BindAxis("Turn");
	CameraBoom->AddRelativeRotation(FRotator(0, GetInputAxisValue("Turn") * DeltaSeconds * mouseSensitivity, 0));

	RotateCharacterToCamera();
}

void AMxOPlayerController::PitchCamera(float DeltaSeconds)
{
	InputComponent->BindAxis("LookUp");
	CameraBoom->AddRelativeRotation(FRotator(- (GetInputAxisValue("LookUp") * DeltaSeconds * mouseSensitivity), 0, 0));
}

void AMxOPlayerController::RotateCameraToCharacter()
{
	if (MovingForward || MovingBackward)
	{
		if (!RightMouseClickPressed && !LeftMouseClickPressed)
		{
			if (CameraBoom->GetRelativeRotation().Yaw != PlayerCharacter->GetActorRotation().Yaw)
			{
				CameraBoom->SetRelativeRotation(FRotator(0, PlayerCharacter->GetActorRotation().Yaw, 0));
			}
		}
	}
}
#pragma endregion

#pragma region Mouse Wheel
void AMxOPlayerController::MouseWheelUp()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("MW_U"));
	if ((Player != nullptr))
	{
		if (CameraBoom->TargetArmLength >= CameraArmMin)
		{
			if (CameraBoom->TargetArmLength < CameraArmMin)
			{
				CameraBoom->TargetArmLength = CameraArmMin;
			}
			else
			{
				CameraBoom->TargetArmLength = FMath::FInterpTo(
					CameraBoom->TargetArmLength,
					FMath::Clamp(
						CameraBoom->TargetArmLength - zoomRate,
						CameraArmMin,
						CameraArmMax),
					GetWorld()->DeltaTimeSeconds,
					zoomInterp
				);
				if (debug) UE_LOG(LogTemp, Warning, TEXT("Target Arm: %f"), CameraBoom->TargetArmLength);
			}
		}
	}
}

void AMxOPlayerController::MouseWheelDown()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("MW_D"));
	if ((Player != nullptr))
	{
		if (CameraBoom->TargetArmLength <= CameraArmMax)
		{
			if (CameraBoom->TargetArmLength > CameraArmMax)
			{
				CameraBoom->TargetArmLength = CameraArmMax;
			}
			else
			{
				CameraBoom->TargetArmLength = FMath::FInterpTo(
					CameraBoom->TargetArmLength,
					FMath::Clamp(
						CameraBoom->TargetArmLength + zoomRate,
						CameraArmMin,
						CameraArmMax),
					GetWorld()->DeltaTimeSeconds,
					zoomInterp
				);
				if (debug) UE_LOG(LogTemp, Warning, TEXT("Target Arm: %f"), CameraBoom->TargetArmLength);
			}
		}
	}
}
#pragma endregion

#pragma region Mouse Buttons
void AMxOPlayerController::LeftMousePressed()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("LM_P"));
	CanMoveCamera = true;
	LeftMouseClickPressed = true;
}

void AMxOPlayerController::LeftMouseReleased()
{
	LeftMouseClickPressed = false;
	CanMoveCamera = false;
	if (debug) UE_LOG(LogTemp, Warning, TEXT("LM_R"));
}

void AMxOPlayerController::RightMousePressed()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("RM_P"));
	CanMoveCamera = true;
	RightMouseClickPressed = true;
}

void AMxOPlayerController::RightMouseReleased()
{
	RightMouseClickPressed = false;
	CanMoveCamera = false;
	if (debug) UE_LOG(LogTemp, Warning, TEXT("RM_R"));
}
#pragma endregion

#pragma region Movement
void AMxOPlayerController::MoveForward(float Value)
{
	Value = FMath::Clamp(Value, -1.f, 1.f);
	if (!PlayerMovement->IsFalling())
	{
		const FVector Forward = Player->GetActorForwardVector();
		if (Value < 0.f)
		{
			if (debug) UE_LOG(LogTemp, Warning, TEXT("BackingUp: %f"), Value);
			ForwardValue = Value;
			MovingBackward = true;
			MovingForward = false;
			PlayerCharacter->GetCharacterMovement()->MaxWalkSpeed = 300.f;
			PlayerPawn->AddMovementInput(Forward, Value * BackupSpeed);
		} else if (Value > 0.f) {
			if (debug) UE_LOG(LogTemp, Warning, TEXT("MovingForward: %f"), Value);
			ForwardValue = Value;
			MovingBackward = false;
			MovingForward = true;
			PlayerCharacter->GetCharacterMovement()->MaxWalkSpeed = 600.f;
			PlayerPawn->AddMovementInput(Forward, Value);
		} else {
			ForwardValue = 0.f;
			MovingForward = false;
			MovingBackward = false;
		}
	}
}

void AMxOPlayerController::Rotate(float Value)
{
	Value = FMath::Clamp(Value, -1.f, 1.f);
	if (RightMouseClickPressed)
	{
		if (Value && !PlayerMovement->IsFalling())
		{
			Strafe(Value);
		}
	} else {
		if (Value && !PlayerMovement->IsFalling())
		{
			if (debug) UE_LOG(LogTemp, Warning, TEXT("Rotating Character"));
			RotationValue = Value;
			Rotating = true;
			const FRotator NewRotation = FRotator(0, Value * RotationRate, 0);
			PlayerCharacter->AddActorLocalRotation(NewRotation);
		}
		else {
			RotationValue = 0.f;
			Rotating = false;
		}
	}
}

void AMxOPlayerController::Strafe(float Value)
{
	Value = FMath::Clamp(Value, -1.f, 1.f);
	if (!PlayerMovement->IsFalling())
	{
		const FVector Strafe = Player->GetActorRightVector();
		if (Value) {
			if (debug) UE_LOG(LogTemp, Warning, TEXT("Strafing"));
			Strafing = true;
			StrafingValue = Value;
			if (MovingBackward)
			{
				PlayerPawn->AddMovementInput(Strafe, Value * BackupSpeed);
			}
			else if (!MovingBackward)
			{
				PlayerPawn->AddMovementInput(Strafe, Value);
			}
		} else {
			StrafingValue = 0.f;
			Strafing = false;
		}
	}
}

void AMxOPlayerController::RotateCharacterToCamera()
{
	if (RightMouseClickPressed && !PlayerMovement->IsFalling())
	{
		if (debug) UE_LOG(LogTemp, Warning, TEXT("Rotating to Camera"));
		Rotating = true;
		const FRotator NewRotation = FRotator(0, CameraBoom->GetRelativeRotation().Yaw, 0);
		PlayerCharacter->SetActorRotation(NewRotation);
	}
}
#pragma endregion

#pragma region Jumping
void AMxOPlayerController::Jump()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("Jump"));
	Player->bPressedJump = true;
}

void AMxOPlayerController::StopJumping()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("Jump End"));
	Player->bPressedJump = false;
}

void AMxOPlayerController::SuperJump()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("Super Jump"));
	PlayerMovement->JumpZVelocity = SuperJumpZVelocity;
	Player->bPressedJump = true;
}

void AMxOPlayerController::StopSuperJumping()
{
	if (debug) UE_LOG(LogTemp, Warning, TEXT("Super Jump End"));
	Player->bPressedJump = false;
	PlayerMovement->JumpZVelocity = dJumpZVelocity;
}
#pragma endregion

You’ll notice that in the BeginPlay method I do a bunch of casting to set reference variables for the various things I would need access too for different things like controlling the camera getting access to movement components, Input Component and other things. All controlled in the PC.

Ill give you an example of the Character now so you can see how little is actually implemented there. You’ll notice I was doing some things with abilities that I never actually finished, I think, It’s been a little while. Anyway you’ll mostly notice that other than that there is very little logic implemented in the Character and the logic that is implemented is very specific to the character meaning I could create many different versions of the same type of object with little differences like skin etc.

Header

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "AbilitySystemInterface.h"
#include "MxOPlayerAbilitySystemComponent.h"
#include "MxOAttributeSet.h"
#include "MxOGameplayAbility.h"
#include <GameplayEffectTypes.h>
#include "MxOCharacter.generated.h"

UCLASS(config=Game)
class AMxOCharacter : public ACharacter, public IAbilitySystemInterface
{
	GENERATED_BODY()

public:
	AMxOCharacter();

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Debug, meta = (AllowPrivateAccess = "true"))
		bool debug;

	/** Camera boom positioning the camera behind the character */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera, meta = (AllowPrivateAccess = "true"))
		class USpringArmComponent* CameraBoom;

	/** Returns CameraBoom subobject **/
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
		FVector CameraPivotPoint;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Abilities, meta = (AllowPrivateAccess = "true"))
		class UMxOPlayerAbilitySystemComponent* AbilitySystemComponent;

	UPROPERTY()
		class UMxOAttributeSet* Attributes;

	virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
	virtual void InitializeAttributes();
	virtual void GiveAbilities();
	virtual void PossessedBy(AController* NewController) override;
	virtual void OnRep_PlayerState() override;
	void SetupOnRep_PlayerState();

	/* Effect that initializes our default attributes */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Abilities")
		TSubclassOf<class UGameplayEffect> DefaultAttributeEffect;

	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Abilities")
		TArray<TSubclassOf<class UMxOGameplayAbility>> DefaultAbilities;

protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	virtual void Tick(float DeltaSeconds) override;
	// End of APawn interface

};

CPP

// Copyright Epic Games, Inc. All Rights Reserved.

#include "MxOCharacter.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"

#pragma region Constructor
AMxOCharacter::AMxOCharacter()
{
	PrimaryActorTick.bCanEverTick = true;
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	debug = true;

	CameraPivotPoint = FVector(0, 0, 90);

	// Create a camera boom (pulls in towards the player if there is a collision)
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->bUsePawnControlRotation = false; // Rotate the arm based on the controller
	CameraBoom->SetRelativeLocation(GetActorLocation() + CameraPivotPoint);

	// Ability System
	AbilitySystemComponent = CreateDefaultSubobject<UMxOPlayerAbilitySystemComponent>("AbilitySystemComp");
	AbilitySystemComponent->SetIsReplicated(true);
	AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);

	Attributes = CreateDefaultSubobject<UMxOAttributeSet>("Attributes");
}
#pragma endregion

#pragma region Ability System
UAbilitySystemComponent* AMxOCharacter::GetAbilitySystemComponent() const
{
	return AbilitySystemComponent;
}

void AMxOCharacter::InitializeAttributes()
{
	if (AbilitySystemComponent && DefaultAttributeEffect)
	{
		FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
		EffectContext.AddSourceObject(this);

		FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(DefaultAttributeEffect, 1, EffectContext);
		
		if (SpecHandle.IsValid())
		{
			FActiveGameplayEffectHandle GEHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
		}
	}
}

void AMxOCharacter::GiveAbilities()
{
	if (HasAuthority() && AbilitySystemComponent)
	{
		for (TSubclassOf<UMxOGameplayAbility>& StartupAbility : DefaultAbilities)
		{
			AbilitySystemComponent->GiveAbility(
				FGameplayAbilitySpec(StartupAbility, 1, static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID), this));
		}
	}
}

void AMxOCharacter::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);

	// Init Server Gameplay Abilities
	AbilitySystemComponent->InitAbilityActorInfo(this, this);

	InitializeAttributes();
	GiveAbilities();
}

void AMxOCharacter::OnRep_PlayerState()
{
	Super::OnRep_PlayerState();

	AbilitySystemComponent->InitAbilityActorInfo(this, this);
	InitializeAttributes();

	SetupOnRep_PlayerState();
}

void AMxOCharacter::SetupOnRep_PlayerState()
{
	if (AbilitySystemComponent && InputComponent)
	{
		const FGameplayAbilityInputBinds Binds("Confirm", "Cancel", "EMxOAbilityInputID", static_cast<int32>(EMxOAbilityInputID::Confirm), static_cast<int32>(EMxOAbilityInputID::Cancel));
		AbilitySystemComponent->BindAbilityActivationToInputComponent(InputComponent, Binds);
	}
}
#pragma endregion

#pragma region Player Input Component
void AMxOCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	check(PlayerInputComponent);

	SetupOnRep_PlayerState();
}
#pragma endregion

#pragma region Tick
void AMxOCharacter::Tick(float deltaSeconds)
{
	Super::Tick(deltaSeconds);
}
#pragma endregion

Hope this is useful.

I use blueprints so can’t use that, but I’ll figure it out.

1 Like

Everything you see there can be translated into blueprints since blueprints is simply a visualized version of C++. So use this as a reference for creating a blueprint system. first thing you’ll want to do is look into the begin play aspect. Casting and getting a reference to your Character or Pawn or Actor into the controller at the start. Setting various variables in the constructor script and then branch out from there. The principles are the same in general. Implementation is different though since Blueprints works slightly differently. In any case this would be a good exercise for you to learn how to take something from C++ to Blueprints. :slight_smile:

Good luck bruh.

Well what if the controlled pawn changes? That wont execute the construction script right?

On the server, you will get an OnPossess event. However, on clients, you don’t.

There exists a callback you can override in C++, for OnRep_ControlledPawn (or something similar to that) which is a reasonable thing to hook into; I frequently end up creating my own shallow C++ controller subclass just to expose this callback as a Blueprint event.

Another option is to just call Get Controlled Pawn everywhere you need one, and cast it to the class you need. (Probably wrap this in a function) You don’t really need to know when it changes, most of the time. And generally, when it changes, it’s because of actions you yourself took, so you can poke the controller to re-load the value if it needs to (especially if it’s single-player.)

1 Like