this is my first post and I really did a lot of research on the topic before posting here. But now I found several possible solutions and I'm just not sure what is the proper way.
Problem
We are currently developing a local coop game that is a mix of an action game for one player and a puzzle game for the second one. Our actual problem is the input that is currently handled by a player controller that casts on every action mapping delegate received to determine if the possessed Pawn is from type PlayerA or PlayerB to then call the right function.
Let me give you a specific example:
We have two action bindings for the Button B (XBox Controller) currently called "B"
When the delegate is called we cast the possessedPlayer to check whether it shall call PlayerA->Jump or PlayerB->Blink
I'm totally unhappy with the situation that I have to cast everytime we receive an Input just to check wether I have possessed action/puzzle char
Solutions
1) Create 2 PlayerControllers and swap them by using GameMode::SwapPlayerController(Old, New)
I found this function during research but I'm not happy with creating a Player with PlayerControllerA to immediately switch it to use PlayerControllerB,
2) Shift responsibility to the Charatcer class
Another thought would be to delegate the decision making to the Character class by passing down an enum value like E_BUTTON_B from the PlayerController to the Character. We could write a macro that creates this enum based on our input mappings and then delegate the decision to a generic Character->ProcessInput(EnumValue) function. I'm also not quite happy because then the PC does not make so much sense to me.
3) Cache the possessed Pawn to avoid the cast
Another idea would have been to cache the type of the possessed Character whenever the PC possesses a Pawn. This would get rid of the cast per input delegate call.
I would be very glad for advide and any hint how you folks solve such issues in your games. Our main goals are separation of concern, good maintainability and input rebinding.
Cheers and have a nice day,
Parzival
You can create structure like this (written in C++, but this approach could be easily implemented in Blueprints as well):
class MyGameBasePlayerController : public PlayerController
{
// TODO methods common for both controllers (for example Escape key handling)
}
Now specific controllers:
class PuzzlePlayerControler : public MyGameBasePlayerController
{
UPROPERTY(BlueprintReadWrite, Transient, Category = "MyCat"
PuzzleCharacter* puzzleChar;
// TODO handle specific actions for Puzzle chararacter and control handling.
// TODO override base methods / input bindings if necesary
virtual void Possess(APawn* pawn) override;
}
Same goes for ActionPlayerCharacter
.
You need to decide, how your characters will differ. If they are same, you can use something like MyGameCharacter : public PlayerCharacter
(or you can use broader class PlayerPawn
) and your PlayerControllers
will call appropriate methods. Then your stored reference to MyGameCharacter
can be stored in MyGameBasePlayerController
.
Or you can use inheritance as well, so you will have PuzzleCharacter : public MyGameCharacter
and ActionCharacter : MyGameCharacter
with some common base implementation and extending methods or use virtual method overriding.
Depending on your final structure, you can use overriden Possess
method (UE Docs) to get actual pawn instance, cast it to appropriate class (for example PuzzleCharacter
or PuzlePawn
in PuzzlePlayerController
) and store reference for later usage. Be aware that in this case you should implement Unpossess
as well, so you can clear your stored reference and behave correctly in any given situation.