Search Unity

Players picking up objects is much harder than it should be

Discussion in 'Netcode for GameObjects' started by brainwipe, Oct 1, 2022.

  1. brainwipe

    brainwipe

    Joined:
    Aug 21, 2017
    Posts:
    78
    Use case:
    • A player is spawned into the environment. It has a "hand" object attached to its camera so that the hand moves with the view of the camera.
    • A box is spawned separately into the environment.
    • Player wants to pick up box, so it sticks to the hand. As the camera moves around, the hand moves, the box moves with it.

    Doing this in NGO is staggeringly hard. Harder in Mirror but should be easier than it is. Parenting in Unity is the standard way of doing things but there are two restrictions in NGO that make this very hard. Firstly, you can only parent
    NetworkObjects
    if there is a chain of
    NetworkObjects
    in each parent to the root. Secondly, you can only have one
    NetworkObject
    on a prefab.

    1. Hand spawned separately, then parent to that.
    After the player spawns, a hand with a
    NetworkObject
    is spawned onto the player and attached. Might require a chain of
    NetworkObjects
    between the new hand to the player root; so you might have to spawn lots of
    NetworkObjects
    in a chain because the prefab can't hold more than one.

    2. Don't parent, connect with spring on server
    When player picks up object, a spring with a short length is connected between hand and box. Causes some laggy rubber banding on client but works for some applications (such as using magic wand to move things).

    3. Don't parent, manually set position of box
    Give ownership to the player holding the box. On
    Update()
    , the owning client sets the position/rotation of box to the same as the hand transform. Picking up/dropping is then an act of giving/taking ownership. I've had issues with this as the Server doesn't respect the Client Network Transform's movement when you give back control.

    Am I missing something? Thanks in advance.
     
    Last edited: Oct 1, 2022
    Vectrex likes this.
  2. TestifyNow

    TestifyNow

    Joined:
    Feb 9, 2016
    Posts:
    24
    Hey, I have been struggling with the same thing for a while. The way I have it setup in my game currently, which works as expected, is that I don't give back ownership to the server. It seemed glitched and was freezing stuff in the air. However, you can change ownership to the host/other client, and it works as expected, at least in my case, which mind you, might be different than yours. (don't use RemoveOwnership, only ChangeOwnership). Again, sorry if this doesn't help your case.

    Other than that, I completely agree, I went through the same steps as you did, and I almost gave up when I noticed the fifty shades of restrictions that are required in this networking. Most of the stuff is harder than it needs to be, and on top of that, the bugs in the networking are very common. The longer you work on your game, the more you realise how buggy it is. I have the luck of porting a really small simple game.

    I actually came to this forum right now to post 2 more bugs that I found, namely, when ownership is changed from client to host, the host gets 'onlostownership' message for some F***ing reason, even though he gained it
    On top of that, Why is there ownership on networkobject AND on networkbehaviours? Even if there is a reason for that, there's a bug that when player disconnects, all the objects owned by the client give back ownership to the server, but only partially, which generates even more issues...

    The most basic stuff that should be working, don't.
    I must say, for something that comes out of preview, and has been in development for years, I am severely disappointed.
    No clue how this engine plans to compete in the long run, when releasing half-assed features all around. (yes, I am mad)
     
    brainwipe likes this.
  3. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,882
    Maybe this requires a change in mindset? I haven't done this pick-up and carry kind of thing but from my understanding of Netcode the way I would implement it is as follows:

    • Item spawns on the server on the ground, clients see it, all hurry towards it ...
    • a client walks over item, sends pickup RPC to server
    • Client immediately adds this item to his hand but as a non-networked version of that item !! At the same time it toggles visibility of the server-side item on the ground to be hidden (avoid duplication).
    • Server responds: yes, you can carry that item. Client takes note of that and ... actually does nothing. The server actually sends out this RPC to all clients stating "that item was picked up by ..." thus causing all other clients to make the server-side item invisible on the ground while showing the player carrying the item, also using a non-networked local version of that item in the client's hands.
      • However, if server were to respond: sorry, someone else picked it up a split second before you did ... client would have to remove that item from his hands and return to the empty-hands state. May look odd for the client but that's just the way it is and rarely happens anyway ...
    • Sometime later ...
    • Client drops item ... sends drop RPC to server
    • Client immediately drops the LOCAL item visually locally, it may instantly appear on the ground or it may be rolling around with physics, doesn't matter much.
    • Server responds: yes, I determined you were carrying that item and were allowed to drop it, so all is fine. This goes to all clients.
    • Client responds by unhiding the server-side version of the item and hiding or removing his local version. Ideally the client may lerp position & rotation of LOCAL item until it is close-enough or some time has passed - that way the motion looks smooth but sometimes, due to lag, items may seem to fly around illogically - again, that's just something that happens in online games.

    In essence, you do not need to constantly (de)construct a hierarchy of networked objects for things like carrying but rather follow the logic that the server dictates and emulate it locally.

    You can skip the "smoothing" part and make your code simpler by having the client wait for the pickup response from the server before showing the item in the hands of the player, and likewise you do the same for dropping. That will cause the player to walk over an item but not have it carry it for one round-trip time - this may be 50 ms (hardly noticable) or 500 ms (very noticable but still playable for slow games). Same for dropping.

    I'd advise to take the simpler route first, and then see if any of these delays cause unacceptable issues, at which point you may try to add local interpolation to what you expect is going to happen without having received the authority to do so, and being prepared to be told by the server "nah, you shouldn't have" and have to revert it.

    The Netcode docs glance over this subject here and there but since this is out of the scope of their documentation, they just assume this to be something multiplayer developers know (or need to learn).

    Now you know. ;)
     
  4. brainwipe

    brainwipe

    Joined:
    Aug 21, 2017
    Posts:
    78
    Thanks for the feedback - I was aware of the "do it all without networking" option - which is how you need to do it with Mirror.

    Photon Pun 2 lets you simply parent the object. I'd be using it but I don't need matchmaking.

    I think CodeSmile's complex list of "how to do it" and "assume you know" proves my point. NGO should have a solution for this, it's much harder than it needs to be.
     
    Vectrex and TestifyNow like this.
  5. TestifyNow

    TestifyNow

    Joined:
    Feb 9, 2016
    Posts:
    24
    That's the thing, many people seem to miss the point. It's not about whether or not this is possible to be done. With enough 'hacks' pretty much anything can be done. The point is that the networking and especially such basic features, should be easily accomplishable.
     
  6. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,882
    I have this already in my "todo" list. Something like "Item Pickup for Netcode" that is just a minimal framework to make this generally work. Because I think that's super-useful AND I'm interested in doing that because I like the challenge, I see a "system" that I like developing, and I might need it myself.

    The thing is, I understand Unity or any large framework developer on this. They provide the low-level. What you're asking for is the convenience high-level features. But in order to get there, the low-level has first to be established, stable, reliable, fully functional, integrated, battle-tested, etc etc etc. ... so for Unity to get around to the high-level we'd have to wait for at least Netcode 2.0 I suppose.

    At the same time devs can put the convenience on top and proven convenience assets get bought out by Unity, like ProBuilder or Rival or NGUI or plenty of others. ;)
     
  7. CreativeChris

    CreativeChris

    Unity Technologies

    Joined:
    Jun 7, 2010
    Posts:
    457
    Hi all,

    We have some planned improvements coming which will address some of this.
    Check out the PR here.

    We will also have a Parenting example in Boss Room soon.

    Thanks,
    Chris
     
  8. brainwipe

    brainwipe

    Joined:
    Aug 21, 2017
    Posts:
    78
    Thanks for the tip, Chris!

    I've been through the PR and code and while the fixes are welcome, it partially deals with the 3rd option (from the top post) - that the client's movement was not being honored by the server. That solution is actually a work around for a larger problem:

    Most developers are going to expect that parenting will work as the non-multiplayer parenting does. While it is fine to have restrictions, there appear to be two separate restrictions that together make "Player Picks Up Object" exceptionally hard.

    Those are:
    1. You can only parent
      NetworkObjects 
      if there is a chain of
      NetworkObjects 
      in each parent to the root.
    2. You can only have one
      NetworkObject
      on a prefab.
    Given that players are Prefabs, that makes Player Picks Up Object as a use case exceptionally complex to build.
     
    Vectrex likes this.
  9. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,882
    Hmmm I don't immediately see why.

    Player (networked)
    -- Attached Items (networked, empty in prefab)
    -- -- Item 1 (networked item that was picked up)
    -- -- Item 2 ( " )
    -- -- Item 3 ( " )

    Unless ... you have a rigged character model and want to attach the item all the way down (up) to the middle-fingernail bone?

    In that case I'd have a special "Attached Items" empty game object that follows the position of the bone in question so you still only need the root and one (or more) child container game objects for carrying/attaching items.
     
  10. brainwipe

    brainwipe

    Joined:
    Aug 21, 2017
    Posts:
    78
    For my example, I have the "hand" attached to the camera, which is only one below the player object. So I have to spawn and parent two things, one of which is the active camera.

    Player (networked, prefab)
    -- Camera (networked, spawned in after the player has spawned and then attached to the player)
    -- -- Hand (Attached item) (networked, spawned in after the Camera has spawned and attached to the Camera)
    -- -- -- Box (networked, spawned in separately or in scene)

    That's the simplest FPS controller you can have and yet it's still spawning in two extra Networked Objects rather than having a prefab with multiple network objects.

    Also:

    How does the follow work? The client doesn't have the ability to move it and if you move it on the server, then it lags. You can't have both server and client controlling it because when you enable/disable the network transform then the item lag jumps. You can't smooth that because the host doesn't have the server position and vice versa.

    Thank you for joining in with the solutions, it's much appreciated. Something might come out in the wash but right now it looks like a use case that Unity has just not prioritised and we're really just trying to work around two restrictions that make things unacceptably complex. All that spawning and attaching code, making sure the order is correct and things are linked together rather than just this:

    _ = NetworkObject.TrySetParent(player.Hand);


    And I mean just that. For scene objects, that's all I have to do. But as a player is a prefab, I have to jump through these ridiculous hoops.
     
    Nyphur likes this.
  11. CreativeChris

    CreativeChris

    Unity Technologies

    Joined:
    Jun 7, 2010
    Posts:
    457
    I just wanted to share the example of a Pickup and Drop action we created in Boss Room.

    PickUp
    Drop
     

    Attached Files:

  12. brainwipe

    brainwipe

    Joined:
    Aug 21, 2017
    Posts:
    78
    That's really helpful, thank you. Without seeing the player hierarchy, it's going to be difficult to understand the full setup but I'm going to download Boss Room and step through.

    It's interesting that the parent is being set and then a constraint is being added to the hand joint. Given that this is all action code, it's impossible to know if this is server, client or both. I would hope it's both else you will get odd lag artifacts.

    Many thanks for coming back for the update.
     
    Last edited: Oct 11, 2022
    CreativeChris likes this.
  13. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    258
    As a semi-relative side note on some of this discussion:
    You can have a complex parent-child hierarchies with nested NetworkBehaviours under a single NetworkObject. NGO v1.2.0 (now) properly synchronizes NetworkTransforms within complex parent-child hierarchies (Just thought that was worth mentioning).

    Regarding parenting, if you want a "bare-bones" example to look at, in the testproject within the NGO repository you can find several additional samples under Assets\Tests\Manual. I put together manual tests for visual testing prior to writing integration tests.

    One of those manual tests you may (or may not) find useful to look over is:
    Assets\Tests\Manual\InSceneObjectParentingTests
    Within that manual test folder you will find ChildObjectScript.cs which provides a very basic example.

    I was using this for testing of the WorldPositionStays parameter, but it could also be used as a "bare-bone" parenting reference.

    Regarding dropping an item you picked up by using null, there are really two ways I would recommend doing this:
    Use NetworkObject.TryRemoveParent: This is a helper method really to automatically handle the casting of a null value to NetworkObject:
    Code (CSharp):
    1. public bool TryRemoveParent(bool worldPositionStays = true)
    2. {
    3.   return TrySetParent((NetworkObject)null, worldPositionStays);
    4. }
    The second way would be calling TrySetParent just like it does in the TryRemoveParent script snippet above.

    If you are not concerned about world or local space relative parenting (i.e. "ok" with it to default to world space relative), then you should be able to just directly set the transform of the GameObject to null...otherwise using TrySetParent and TryRemoveParent allows NGO to know how to synchronize the parenting action (world or local space relative).

    Advanced Parenting:
    One way to handle parenting and be free of the NetworkObject parenting restrictions once parented, is to do the NGO parenting first and then apply normal GameObject parenting once parented.

    For Example:
    You have a player and item you want to pickup, but you want to be able to parent under a GameObject (with no NetworkObject component) that is nested deep within a model (common issue one might run into).
    upload_2022-12-14_7-49-58.png
    Assume the RootPlayer and ItemToPickup both have NetworkObject components and any of their respective children could have
    NetworkBehaviour
    components (but no NetworkObject components).

    Step 1: Setup a NetworkBehaviour similar to the ChildObjectScript.cs with the only difference is you have a public property that points to the "ItemVisuals" GameObject and have a way to know about the "RightHand". There are several ways to handle this, one way is to use an interface and make one of the components of the RootPlayer implement it so you have a method to be able to easily obtain the target visual parenting node.
    Code (CSharp):
    1. public interface PlayerVisualNodeHandler
    2. {
    3.     GameObject GetRightHand();
    4. }
    Something along those lines...

    Step 2: Do the initial parenting using NGO so this parenting action is synchronized with clients and
    OnNetworkObjectParentChanged
    is invoked (both server and client sides)
    upload_2022-12-14_7-52-31.png

    Step 3: Within
    OnNetworkObjectParentChanged
    you can then do normal "Unity style" (like you would in a single player game) parenting of the "root visual" under the player's "hand"
    GameObject
    . Both the server and client sides would invoke this portion of the script.
    The end result would look like this:
    upload_2022-12-14_7-53-23.png
    Since the ItemToPickup children's NetworkBehaviour components are already registered with the ItemToPickup NetworkObject component, NGO will already know how to route messages for RPCs and handle NetworkVariable synchronization. Where the ItemToPickup's (non-NetworkObject) children exist after it is spawned shouldn't matter once it is spawned and parented under something.

    When the player drops the item you can follow the same steps but in reverse:
    Step1: Migrate ItemVisuals back under ItemToPickup.
    Step2: Remove the parent of ItemToPickup using TryRemoveParent (or the like).

    This approach does require a few more steps than one might have to run through than when making a single player game, but then again it is a bit more complex because there is the added complexity of synchronizing clients with specific states/actions.

    However, I felt it might be useful to provide you with this alternate (albeit a bit more complex) approach.
     
    Nyphur likes this.
  14. glowkeeper

    glowkeeper

    Joined:
    May 21, 2020
    Posts:
    4
    It's still horrendously complex now. And the docs just don't cut it...
     
  15. Nyphur

    Nyphur

    Joined:
    Jan 29, 2016
    Posts:
    98
    This question comes up a lot, and these old posts get dragged up because they show up on google. In case anyone's finding this via google, I posted an outline of another solution here with explanations as to why we have to do each step: https://forum.unity.com/threads/player-hierarchical-networkobjects.1207012/#post-8951985

    Agree though that it's still much too difficult to implement a simple pick up system, it's among the most common use cases in a game but there's no standardised solution built into NGO and the official samples do it incorrectly. Tempted to release a third party asset with all the additional systems we've had to build on top of NGO.