Today’s development focused on refining the inventory system. We have successfully implemented the ability to pick up, hold, and drop items seamlessly. This brings us one step closer to a fully functional and dynamic inventory experience.
With these new mechanics in place, players can now interact with pickups, store them in their inventory, and drop them at will. The system ensures smooth transitions between these states, making item management more intuitive and responsive.
Looking ahead, we will continue to optimize item interactions, improve UI feedback, and refine network synchronization to enhance the overall gameplay experience.

Here the full Class
PlayerInventory.cs
using Cockfite.Enums;
using Cockfite.Interfaces;
using Cockfite.Management;
using Cockfite.Weapons;
using Fusion;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Cockfite.Player.AvatarManagement;
using System.Collections;
using Cockfite.Interactions;
namespace Cockfite.Player.Inventory
{
public class PlayerInventory : NetworkBehaviour
{
#region Fields & Properties #########################################################################
// private
private IEngine m_engine => Engine.Instance;
[SerializeField] private Weapon[] m_weaponPrefabs;
[SerializeField] private GameObject[] m_dummyPrefabs;
[SerializeField] private NetworkObject m_dummyInventoryItem;
[Networked, Capacity(5)] private NetworkArray<NetworkObject> m_itemSlots {get;}
private sbyte m_localActiveSlot = 0;
private bool m_isSlotSwitchInProg = false;
private Dictionary<int, GameObject> tempDummys = new Dictionary<int, GameObject>();
// protected
[Networked] protected sbyte m_activeSlot { get; set; } = -1;
// properties
public NetworkArray<NetworkObject> ItemSlots => m_itemSlots;
public sbyte ActiveSlot { get => m_activeSlot; set => m_activeSlot = value; }
public sbyte LocalActiveSlot
{
get => m_localActiveSlot; set
{
RPC_ChangeActiveInventorySlot(value);
}
}
#endregion
#region NetworkBehaviours ###########################################################################
public override void Spawned()
{
if (!HasStateAuthority)
return;
m_activeSlot = 0;
m_itemSlots.Set(0, m_dummyInventoryItem.GetComponent<NetworkObject>());
m_itemSlots.Set(1, m_dummyInventoryItem.GetComponent<NetworkObject>());
m_itemSlots.Set(2, m_dummyInventoryItem.GetComponent<NetworkObject>());
m_itemSlots.Set(3, m_dummyInventoryItem.GetComponent<NetworkObject>());
m_itemSlots.Set(4, m_dummyInventoryItem.GetComponent<NetworkObject>());
}
public override void FixedUpdateNetwork()
{
if (!HasStateAuthority)
return;
foreach (var item in m_itemSlots)
{
if(item != null)
{
var iitem = item.GetComponent<IInventoryItem>();
if(iitem.InventoyItemType == InventoyItemType.Weapon)
{
if (iitem.InventoryItemSlot == m_activeSlot && !iitem.IsArmed)
iitem.ArmItem();
else if(iitem.InventoryItemSlot != m_activeSlot && iitem.IsArmed)
iitem.UnarmItem();
}
}
}
m_activeSlot = m_localActiveSlot;
}
#endregion
#region Private Methodes ######################################################################
[Rpc(sources: RpcSources.All, targets: RpcTargets.All, Channel = RpcChannel.Reliable)]
private void RPC_ChangeActiveInventorySlot(sbyte _slot)
{
m_localActiveSlot = _slot;
}
[Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority, Channel = RpcChannel.Reliable)]
private void RPC_DropItemsInSlot(sbyte _slotIndex, int localDummyId)
{
if (!HasStateAuthority)
return;
NetworkObject item = m_itemSlots[_slotIndex];
m_itemSlots.Set(_slotIndex, m_dummyInventoryItem);
IInventoryItem iitem = item.GetComponent<IInventoryItem>();
iitem.Uninitalize();
Vector3 spawnpos = GetDropPosition(iitem.DropOffsetSettings);
NetworkObject staticPickup = Runner.Spawn(iitem.PickupPrefab, spawnpos, Quaternion.Euler(Vector3.zero), PlayerRef.None, BeforePickupSpawned);
void BeforePickupSpawned(NetworkRunner runner, NetworkObject obj)
{
obj.GetComponent<IInventoryItem>().DropPickup(iitem.CurrentAmmo);
}
RPC_MergeDummyWithRealItem(item.InputAuthority, localDummyId);
/*StartCoroutine(WaitForSpawned(staticPickup, item.InputAuthority)); now i thing it works without this delay
IEnumerator WaitForSpawned(NetworkObject _staticPickup, PlayerRef _pref)
{
while (_staticPickup == null || !_staticPickup.IsValid)
{
yield return null;
}
yield return new WaitForSeconds(.3f);
RPC_MergeDummyWithRealItem(_pref, localDummyId);
}*/
Runner.Despawn(item);
}
[Rpc(RpcSources.StateAuthority, RpcTargets.All, Channel = RpcChannel.Reliable)]
private void RPC_MergeDummyWithRealItem([RpcTarget] PlayerRef player, int localDummyId)
{
GameObject localDummyDrop = FindObjectByInstanceID(localDummyId);
if (localDummyDrop != null)
{
Destroy(localDummyDrop);
}
}
[Rpc(RpcSources.All, RpcTargets.StateAuthority, Channel = RpcChannel.Reliable)]
private void RPC_SwitchInventorySlot(sbyte _slotA, sbyte _slotB)
{
m_isSlotSwitchInProg = true;
IInventoryItem itemA = m_itemSlots[_slotA]?.GetComponent<IInventoryItem>();
IInventoryItem itemB = m_itemSlots[_slotB]?.GetComponent<IInventoryItem>();
if (itemA != null && itemB != null)
{
itemA.InventoryItemSlot = _slotB;
itemB.InventoryItemSlot = _slotA;
}
// switch places in list
var temp = m_itemSlots.Get(_slotA);
m_itemSlots.Set(_slotA, m_itemSlots.Get(_slotB));
m_itemSlots.Set(_slotB, temp);
UpdateSlot(_slotA, m_itemSlots.Get(_slotA));
UpdateSlot(_slotB, m_itemSlots.Get(_slotB));
m_activeSlot = _slotB;
m_localActiveSlot = _slotB;
m_isSlotSwitchInProg = false;
}
private Vector3 GetDropPosition(DropOffsetSettings settings)
{
// Compute drop direction relative to the player's current rotation
Vector3 dropDirection = transform.rotation * Quaternion.Euler(0, settings.DropOffsetAngle, 0) * -Vector3.forward;
// Calculate final drop position
Vector3 spawnPosition = transform.position + dropDirection * settings.DropOffsetRadius;
spawnPosition.y += settings.DropOffsetHeight; // Adjust height
return spawnPosition;
}
private GameObject FindObjectByInstanceID(int instanceId)
{
if (tempDummys.TryGetValue(instanceId, out GameObject obj))
{
tempDummys.Remove(instanceId);
return obj;
}
return null;
}
// Updates the corresponding Networked slot based on index
private void UpdateSlot(int index, NetworkObject item)
{
if (index < m_itemSlots.Length)
{
m_itemSlots.Set(index, item);
}
}
#endregion
#region Public Methodes #####################################################################
public void SwitchInventorySlot(sbyte _slotA, sbyte _slotB)
{
RPC_SwitchInventorySlot(_slotA, _slotB);
}
// this method is called only on host.
public bool AddItemToInventory(IInventoryItem _inventoryItem)
{
short rest = -1;
bool hasAmmoAdded = false;
for (int i = 0; i < m_itemSlots.Length; i++)
{
var iitem = m_itemSlots[i].GetComponent<IInventoryItem>();
if (iitem.ItemId.Equals(_inventoryItem.ItemId)) // we have already a item of this type
{
switch (iitem.InventoyItemType)
{
case InventoyItemType.Free:
break;
case InventoyItemType.Weapon:
IInventoryItem inventoryItem = iitem;
var item = Runner.FindObject(inventoryItem.ItemNetworkId); //inventoryItem as NetworkBehaviour;
if (item.gameObject.GetComponent<Weapon>().AddAmmo(_inventoryItem.CurrentAmmo, out short _rest))
{
hasAmmoAdded = true;
rest = _rest;
if (_rest == 0)
return true;
else
break;
}
else
{
if(_rest > 0)
rest = _rest;
break;
}
case InventoyItemType.FirearmWeapon:
break;
case InventoyItemType.Shield:
break;
case InventoyItemType.Ammo:
break;
default:
break;
}
}
}
// If no matching item exists, search for an empty slot
for (int i = 0; i < m_itemSlots.Length; i++)
{
var iitem = m_itemSlots[i].GetComponent<IInventoryItem>();
if (string.IsNullOrEmpty(iitem.ItemId.ToString())) // we have a free slot
{
switch (_inventoryItem.InventoyItemType)
{
case InventoyItemType.Free:
break;
case InventoyItemType.Weapon:
var spawn = m_weaponPrefabs.First(w => w.ItemId.Equals(_inventoryItem.ItemId));
var weapon = Runner.Spawn(spawn, null, null, GetComponent<CF_Avatar>().PlayerRef, (_rnr, _no) =>
{
_no.gameObject.GetComponent<IInventoryItem>()
.Initalize(GetComponent<CF_Avatar>().GetSpawnPointTransform(_inventoryItem.SpawnTransform),
(sbyte)i,
GetComponent<CF_Avatar>().PlayerRef,
(rest == -1 && !hasAmmoAdded) ? _inventoryItem.CurrentAmmo : rest);
});
UpdateSlot(i, m_itemSlots.Set(i, weapon.GetComponent<NetworkObject>()));
break;
case InventoyItemType.FirearmWeapon:
break;
case InventoyItemType.Shield:
break;
case InventoyItemType.Ammo:
break;
default:
break;
}
return true;
}
}
// if ammo has addet return true and remove the weapon
if(hasAmmoAdded)
return true;
// we can eather take ammo nor the weapon
return false;
}
public void DropItemsInSlot(sbyte _slotIndex)
{
NetworkObject item = m_itemSlots[_slotIndex];
var iitem = item.GetComponent<IInventoryItem>();
Vector3 spawnpos = GetDropPosition(iitem.DropOffsetSettings);
GameObject localDummyDrop = Instantiate(
iitem.PickupPrefabDummy,
spawnpos,
Quaternion.identity
);
tempDummys.Add(localDummyDrop.GetInstanceID(), localDummyDrop);
iitem.UninitalizeLocal();
m_engine.GUI_Inventory.InventorySlots[_slotIndex].ItemIcon.sprite = m_dummyInventoryItem.GetComponent<IInventoryItem>().InventoryIcon;
m_engine.GUI_Inventory.InventorySlots[_slotIndex].ItemCount.text = "0";
RPC_DropItemsInSlot(_slotIndex, localDummyDrop.GetInstanceID());
}
#endregion
}
}