I’m working on a game at the moment whereby we need to have player-controlled objects which multiple users can interact with at the same time. For example, think of a field gun or artillery piece which has both a gunner and a loader.
I don’t want the players that are interacting with the object to have to possess another pawn in order to do so, as this would mean rewriting huge chunks of the project. I’m at a bit of a dilemma here - I don’t want a convoluted system where everything is routed through the character pawn to any possible item they could interact with, but I also can’t use a “Proxy Pawn” that the player can possess while they are in control of the object.
So… I thought I’d found a workaround but I seem to have been fooled at the last minute. What I’ve done is created a new UObject class called “Seat”, which is created by the artillery piece in the constructor as a default sub-object. This is the relevant networking code for that “Seat” class:
//////////////////////
///// Networking /////
//////////////////////
bool USeat::IsSupportedForNetworking() const
{
return true;
}
bool USeat::CallRemoteFunction(UFunction * Function, void * Parms, FOutParmRec * OutParms, FFrame * Stack)
{
AActor* Owner = CurrentOccupant ? CurrentOccupant : Cast<AActor>(GetOuter());
if (!Owner)
{
return false;
}
UNetDriver* NetDriver = Owner->GetNetDriver();
if (!NetDriver)
{
return false;
}
NetDriver->ProcessRemoteFunction(Owner, Function, Parms, OutParms, Stack, this);
return true;
}
int32 USeat::GetFunctionCallspace(UFunction * Function, void * Parameters, FFrame * Stack)
{
AActor* Owner = CurrentOccupant ? CurrentOccupant : Cast<AActor>(GetOuter());
return (Owner ? Owner->GetFunctionCallspace(Function, Parameters, Stack) : FunctionCallspace::Local);
}
void USeat::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(USeat, CurrentOccupant);
DOREPLIFETIME_CONDITION(USeat, SeatOwner, COND_InitialOnly)
}
What you can see is that if the seat has a current “occupant” (the player characters), then I use that occupant to determine the “caller” of the RPC. This all compiles fine and it even sends the RPC (yay!) - but once an occupant tries to call an RPC they are kicked, because the Occupant is not an “Outer” of the UObject. Now obviously, there isn’t really much I can do to change that (perhaps unless I change the “Outer” of the UObject at runtime or use AddToRoot()? That’s one part I didn’t try, but I don’t know if it’s even possible.
So I’m pretty bummed because this was a nice clean system - but I’ve been called out by the networking system at the last minute. The engine kicks the client because the “Seat” is not in the “Outer” chain of the occupant. I’m not really sure how/if I can workaround that?
Note: I’ve since rewritten this system to use an “Actor” instead of a UObject, and I set the “Owner” of the Actor to whoever the Occupant is - which kind of mimics what Pawn Possession does anyway. The thing is, it’s much more messy because I have to spawn actors, and they have additional networking overhead of their own. I’d like to revert to a UObject method if I can, it just feels much cleaner.