Download

Multiplayer RPC Routing through UObjects

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.

@TheJamsh

I’ve no real business in replying to this, as my C++ doesn’t even remember to add a semicolon at the end. However, what you’re describing, sounds a bit like a remote-control drone-system, or a handheld controller that has a web-interface for Joe public to remotely fly etc. You mentioned RPC’s, but simplifying things to single-player for a sec.

Gamemode could create the weapon and expose a crude ‘remote control framework’ for players to control simultaneously, without players owning or possessing anything. Basically any player who can access Gamemode can control the weapon. That said, I’ve only done this once before using UDK, so maybe as a frame of reference that’s too limited. But I was working on a multiplayer game where players could take over / or hack each others Cicada drones, and pilot them remotely using just a render-texture-cam to provide feedback… So in short, no possession / no super-character / no proxy-pawn etc…

But now that I think of it… You probably didn’t mention something so obvious, because it would do away with Server-authority and allow for rampant cheating… :stuck_out_tongue:

In single player or even split-screen this would be extremely easy and wouldn’t require any special workarounds, everything would just work.

Server Authority isn’t the problem - players can’t call an RPC on anything they don’t own (by default). I managed to get around the “Ownership” limitation, but the next barrier is that the player seemingly can’t call an RPC on an Object that isn’t in their “Outer” chain. I’m looking for a way to workaround that without changing engine code - but it doesn’t seem feasible.

The Actor Proxy system I’ve built in the meantime works just fine - but I don’t want to use actors because of all the extra overhead they create. Guess I’m stuck with it for now though.

Thanks for the short schooling James! Quick question… Can the server poll comms the client sends, parse and act on it? So the client isn’t calling an RPC on something it doesn’t own, its merely sending XML / SQL / proprietary messages that the server can choose to parse and act on, if it wants, independent of the client. Though I suppose, properly formed or spoofed messages could game the system and let imposters cheat…

Why not make “Seat” be sub-object of Player and make Artillery reference that instead of owning the Object?

That’s an interesting idea, I could probably make that work quite easily but I’d prefer to keep the seats as being “Owned” by the item the player is sitting in, rather than having to create new seat objects when a player gets in/out of a given seat.

For now I’ve stuck with the Actor system, since it doesn’t look like I can get around this without source changes which are out of scope for this project. God knows what kind of problems it would cause anyway.

Seats as actors is how many previous Unreal Engine games have handled things like this in the past (including UT’s vehicles).

Maybe a dumb question, but could you not change the owner of the seat at runtime? That would essentially amount to your “Occupant” system, but in a way that the engine understands (the notion of owner).

With UObject the problem isn’t the ‘Owner’ it’s the ‘Outer’ (UObject has no concept of Owner natively, that’s an Actor thing).

The Outer is the object it was created “in” and is relevant for garbage collection and suchlike. I’d likely introduce a whole host of other problems if I tried to mess with that (I’m not even sure it can be changed).

Avoiding Actors would just make the system a lot less heavy and cleaner. In a game with a lot of players that can make a big impact.