Search Unity

Showcase I know it's not much but just wanted to show some progress!

Discussion in 'Netcode for GameObjects' started by Serinx, Jan 26, 2023.

  1. Serinx

    Serinx

    Joined:
    Mar 31, 2014
    Posts:
    788
    I'm struggling a bit with getting the hang of transform parenting over the network, but all in all looking pretty decent.
    I had to make a fake local copy of the spear to be able to parent it to the hand bone.
    Picking up objects and then throwing them, all synchronized and being able to put them down/swap for another item (this proved to be surprisingly difficult).

    Also managed to synchronise a takedown/assassination.

     
  2. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    Looks great! :D
     
  3. DarkenSoda

    DarkenSoda

    Joined:
    Apr 3, 2020
    Posts:
    12
    Can you explain how you made the weapon pickup/drop system?
    I've been struggling with making a weapons system that should include multiple weapons each with their own attack style and animation.
    I can't even figure out how to make weapons attach to the player correctly without any delay on server/client sides.
     
  4. Serinx

    Serinx

    Joined:
    Mar 31, 2014
    Posts:
    788
    @DarkenSoda Sorry for the late reply, I've been on a game dev hiatus.

    What part are you struggling with?

    At a high level my system works like this:

    1. host spawns "pickupItems". These aren't the actual items that will be wielded, they're just visual representations.
    2. Player approaches a pickupItem and gets a prompt to pick it up.
    3. If the player presses the button, they play an animation and request the pickupItem from the host.
    4. The host checks that the item hasn't already been picked up by someone else (imagine 2 people try to pickup the same item at the same time), and that the player is actually in range.
    5. If successful, the server tells the clients to instantiate a "heldItem" which is the actual item the player will hold. All the clients do this individually, so the weapons is not actually synced across the network, but it is parented to the hand and moves with the armature.
    6. The "pickupItem" is also set inactive by all clients
    When you drop an item you basically have to do it in reverse! destroy the heldItem, move the pickupItem into the dropped position and set it active again.

    For throwables (like the spear in the video), a projectile is spawned half way through the animation and the heldItem is destroyed.

    Unity doesn't make it easy for us unfortunately!

    If you have any more specific questions let me know.
     
    Ohilo likes this.
  5. DarkenSoda

    DarkenSoda

    Joined:
    Apr 3, 2020
    Posts:
    12
    this is the part I'm struggling with.
    is the item on the ground that the host creates a network object?

    is the request done through a ClientRpc or ServerRpc?
    is the "heldItem" a network object or just a normal gameobject that each player creates for that client that requested to hold it?

    I've tried making NetworkObjects for the "heldItem" and parent them using parent constraint like what Unity did in their demo. but then the item doesn't sync properly.

    also let's say that each "heldItem" is a weapon with different attack systems, am I supposed to put the attack logic on the weapon monobehaviour, or make the Player NetworkBehaviour handle detecting the weapon type and performing it's required attack?
     
  6. Serinx

    Serinx

    Joined:
    Mar 31, 2014
    Posts:
    788
    @DarkenSoda

    The item on the ground is a network object with a very simple script attached to store some information about the type, name, etc and the "isPickedUp" network variable is used to tell other clients that this item has already been picked up:

    Code (CSharp):
    1. {
    2.     public enum PickupType { javelin, branch, rock, bow }
    3.     public class PickupItem : NetworkBehaviour
    4.     {
    5.         public NetworkVariable<bool> isPickedUp;
    6.         public PickupType pickupType;
    7.         public string ItemName;
    8.         public GameObject ItemPrefab;
    9.         public GameObject HeldItemPrefab;
    10.     }
    11. }
    Next I have a "PickupHandler" script on each player with the following functions. "AttachPickupItem" is called from an animation event during the pickup animation, but you could call this directly from a button press for simplicity and testing.
    The server actually determines what item is closest to the player to pick up - so there's a slight chance you could pick up a different item if 2 are really close together.

    Code (CSharp):
    1. public class PickupHandler : NetworkBehaviour
    2.     {
    3.  
    4.         [SerializeField] Transform javelinSocket;
    5.  
    6.         public PickupItem ClosestPickupItem { get; private set; }
    7.  
    8.         List<PickupItem> _pickupItems = new List<PickupItem>();
    9.  
    10.         private GameObject heldItem;
    11.         private NetworkObject lastPickupItemNetObj;
    12.  
    13.         private void OnTriggerEnter(Collider other)
    14.         {
    15.             var pickupItem = other.GetComponent<PickupItem>();
    16.             if (pickupItem != null)
    17.             {
    18.                 if (!_pickupItems.Contains(pickupItem))
    19.                 {
    20.                     _pickupItems.Add(pickupItem);
    21.                     pickupItem.isPickedUp.OnValueChanged += IsPickedUp_OnValueChanged;
    22.                 }
    23.              
    24.                 DetermineClosestPickupItem();
    25.             };
    26.         }
    27.  
    28.         private void IsPickedUp_OnValueChanged(bool previousValue, bool newValue)
    29.         {
    30.             for(int i = 0; i < _pickupItems.Count; i++)
    31.             {
    32.                 var pickupItem = _pickupItems[i];
    33.                 if (pickupItem.isPickedUp.Value)
    34.                 {
    35.                     _pickupItems.Remove(pickupItem);
    36.                     DetermineClosestPickupItem();
    37.                 }
    38.             }
    39.         }
    40.  
    41.         private void OnTriggerExit(Collider other)
    42.         {
    43.             var pickupItem = other.GetComponent<PickupItem>();
    44.             if (pickupItem != null)
    45.             {
    46.                 _pickupItems.Remove(pickupItem);
    47.                 DetermineClosestPickupItem();
    48.             };
    49.         }
    50.  
    51.         private void DetermineClosestPickupItem()
    52.         {
    53.             PickupItem bestTarget = null;
    54.             float closestDistanceSqr = Mathf.Infinity;
    55.             Vector3 currentPosition = transform.position;
    56.             foreach (var potentialTarget in _pickupItems.FindAll(x => x != null))
    57.             {
    58.                 if (potentialTarget.isPickedUp.Value) { continue; }
    59.  
    60.                 Vector3 directionToTarget = potentialTarget.transform.position - currentPosition;
    61.                 float dSqrToTarget = directionToTarget.sqrMagnitude;
    62.                 if (dSqrToTarget < closestDistanceSqr)
    63.                 {
    64.                     closestDistanceSqr = dSqrToTarget;
    65.                     bestTarget = potentialTarget;
    66.                 }
    67.             }
    68.             ClosestPickupItem = bestTarget;
    69.         }
    70.  
    71.         public void AttachPickupItem()
    72.         {
    73.             if (!IsOwner) return;
    74.             AttachPickupItemServerRpc();
    75.         }
    76.  
    77.         [ServerRpc]
    78.         public void AttachPickupItemServerRpc()
    79.         {
    80.             DetermineClosestPickupItem(); //Ensure we find a pickup item
    81.  
    82.             if (ClosestPickupItem != null)
    83.             {
    84.                 _pickupItems.Remove(ClosestPickupItem);
    85.                 DropHeldItem(transform.position, transform.rotation);
    86.  
    87.                 var netObj = ClosestPickupItem.GetComponent<NetworkObject>();
    88.                 ClosestPickupItem.isPickedUp.Value = true;
    89.                 AttachPickupItemClientRpc(netObj.NetworkObjectId);
    90.                 lastPickupItemNetObj = netObj;
    91.             }
    92.         }
    93.  
    94.         [ClientRpc]
    95.         public void AttachPickupItemClientRpc(ulong objectId)
    96.         {
    97.             NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(objectId, out var objToPickup);
    98.             lastPickupItemNetObj = objToPickup;
    99.             var pickup = objToPickup.GetComponent<PickupItem>();
    100.  
    101.             //Destroy the previously held item if there is one
    102.             Destroy(heldItem);
    103.  
    104.             var instantiatedItem = Instantiate(pickup.HeldItemPrefab);
    105.  
    106.             switch (pickup.pickupType)
    107.             {
    108.                 case PickupType.javelin:
    109.                     instantiatedItem.transform.SetParent(javelinSocket);
    110.                     break;
    111.                 default:
    112.                     Debug.LogError("Unimplemented pickup type: " + pickup.pickupType);
    113.                     break;
    114.                     //case PickupType.bow:
    115.                     //    break;
    116.                     //case PickupType.rock:
    117.                     //    break;
    118.                     //case PickupType.branch:
    119.                     //    break;
    120.             }
    121.             instantiatedItem.transform.localPosition = Vector3.zero;
    122.             instantiatedItem.transform.localRotation = Quaternion.identity;
    123.             DetermineClosestPickupItem();
    124.             heldItem = instantiatedItem;
    125.             TogglePickupVisibilityClientRpc();
    126.         }
    127.  
    128.         [ClientRpc]
    129.         public void DestroyHeldItemClientRpc()
    130.         {
    131.             Destroy(heldItem);
    132.             lastPickupItemNetObj = null;
    133.         }
    134.  
    135.         private void DropHeldItem(Vector3 position, Quaternion rotation)
    136.         {
    137.             if (lastPickupItemNetObj == null) { return; }
    138.  
    139.             lastPickupItemNetObj.transform.SetParent(null);
    140.             lastPickupItemNetObj.transform.position = position;
    141.             lastPickupItemNetObj.transform.rotation = rotation;
    142.             lastPickupItemNetObj.GetComponent<PickupItem>().isPickedUp.Value = false;
    143.             TogglePickupVisibilityClientRpc();
    144.         }
    145.  
    146.         [ClientRpc]
    147.         public void TogglePickupVisibilityClientRpc()
    148.         {
    149.             lastPickupItemNetObj.gameObject.SetActive(!lastPickupItemNetObj.gameObject.activeInHierarchy);
    150.         }
    151.  
    152.         public PickupItem GetLastPickupItem()
    153.         {
    154.             return lastPickupItemNetObj?.GetComponent<PickupItem>();
    155.         }
    156.  
    157.         public GameObject GetHeldItem()
    158.         {
    159.             return heldItem;
    160.         }
    161.  
    162.  
    163.     }
    As for the weapon type, I would set a parameter in the animator based on the pickup.pickupType, then play a different animation depending on the weapon type - I haven't done this yet, but I'll let you know if I do. It shouldn't be too hard to figure out if you know about animator parameters and transition conditions.

    Any questions about the code please let me know. Obviously there's some missing parts but it should give you a hint.



    I haven't looked at this in a while but it's good for me to remember where I was up to!
     
    Last edited: Oct 9, 2023
    DarkenSoda and Ohilo like this.
  7. DarkenSoda

    DarkenSoda

    Joined:
    Apr 3, 2020
    Posts:
    12
    I think I finally understand thanks to you.
    The weapon on the ground is a network object and has a reference to a normal gameobject which is the weapon to be attached. This is kinda like using a scriptable object to be honest.
    and the clientRPC ensures that the object is created for the client without delay.
    and to drop the weapon you simply destroy the instantiated weapon gameobject and get the network object you originally pickedup and make it visible on the ground again.

    I'm gonna try this again I think I got it this time.
    Thanks for your reply I appreciate it.
     
    Serinx likes this.
  8. Serinx

    Serinx

    Joined:
    Mar 31, 2014
    Posts:
    788
    @DarkenSoda you’re welcome! Glad I could help someone. I’m not sure if this is the best way of doing things but it works for me and syncs pretty well in my tests. Good luck!
     
    DarkenSoda likes this.
  9. unitymark99

    unitymark99

    Joined:
    Jun 22, 2020
    Posts:
    2
    I think this should go into the Unity Learn guide, please, I beg you! Thx for solution! @Serinx