Skip to main content

Collectibles

In this asset, collectibles are networked, uncontrolled scene objects with a component deriving from Collectible attached to them. The owner of collectibles is the server, which is also responsible for synchronizing their current state to newly joined or already connected players in a match. Because of this, each Collectible item has a managing ObjectSpawner as a parent in the scene. Also they feature two different types, one that consumes them immediately and one that allows player to carry the item around.

Spawner

The ObjectSpawner component is responsible for the actual spawning instruction of a Collectible in the scene, that lets you define the prefab, exact position and respawn delay. Since it will spawn the Collectible as a networked scene object, the ObjectSpawner game object also needs to have a NetworkObject / PhotonView component attached. After the ObjectSpawner spawned a object, it assigns a reference to itself on the Collectible, so we can later recognize at any point to which ObjectSpawner an item belongs to. This is very important, since the Collectible will inform the ObjectSpawner of various events involving state changes, i.e. whether it was just consumed or picked up and should be respawned soon!

The reference from a Collectible to its parent ObjectSpawner is saved using its NetworkObjectId in a NetworkVariable directly on the Collectible. We do it that way because when networked objects are spawned for other clients, they exist without clear allocation. This means we cannot just send the ObjectSpawner reference over because this reference does not exist on target clients - they would have their own reference, not matching with the server's. By using and syncing the NetworkObjectId across all clients, which is a globally unique ID and actually the same on all clients, we can instead go from the Collectible's NetworkObject component and get its corresponding ObjectSpawner parent.

Prefabs for all types of collectibles are located in the project under TanksMultiplayer > Prefabs. Just like bullets, collectibles have their own Pool in the game scene making use of our PoolManager in their spawn behavior.

Use Type

A Collectible item for consumption is typically an object that applies to a player in order to boost their effectivity in the game, like powerups. For this type, the Collectible script is working like a template for different powerups, such as for the PowerupBullet or PowerupHealth scripts. The item can be in one out of two possible states as listed in the table below.

Collectibles010

StateDescription
ActiveVisible in the scene and can be collected by colliding with any player. Calls Apply() on the colliding player to apply the effect.
InactiveCurrently invisible in the scene because it has been consumed recently. The server is running a respawn timer to reactivate it soon.

If you would like to create new powerups, simply extend your script from Collectible and override its Apply() method. This method is responsible for what to do when a player collects the powerup, so all of your consumption logic should go in here.

Pickup Type

A Collectible pickup item describes an object that resides at a default position, but can picked up and then carried around by a player. As a use-case, you could think of a "Rambo" item that either makes a player invisible or overpowered as long as it is attached to that player. In the Capture The Flag mode, this type of Collectible is used for the flags. However an extension was made on the class to let it be assigned to a team, namely the CollectibleTeam component, adding the Team Index variable. The item can be in one of several different states as listed in the table below.

Collectibles020

StateDescription
DefaultActive at the default position and can be collected by colliding with any player. Calls Apply() to check the condition whether the object can be picked up by this player. Then calls Pickup() to attach the object to that player.
CarriedCurrently attached to a player. In the event that player is killed, Drop() is called, making the object available on the dropped position for other players to pick up. The item can also be taken to a destination zone (more on this below), e.g. adding points to a team and then calling Return() to reset the state to Default.
DroppedThe item was dropped by a player and is currently not located at its default position. It can be picked up by other players, otherwise Return() is called by the server after a delay, in order to reset it to its Default state.

For the sake of covering several different network approaches in this asset, a new type of synchronization is used for the pickup/drop/return of a CollectibleTeam.

For synchronizing important variables of a CollectibleTeam, we could use multiple single NetworkVariables or NetworkLists. But, we can also define a class or struct with custom values and synchronize that. The CollectibleState struct, located in the ObjectSpawner script at the bottom, is used to define a Collectible's network ID, parent ID and position. In order to synchronize it across the network, the struct needs to derive from INetworkSerializable. To allow synchronization of multiple struct entries, we therefore use a NetworkList< CollectibleState >. The implementation is defined in the GameManager, with a callback subscription for changed entries invoking OnCollectibleStateChanged().

With our implementation for CollectibleTeam change events, we would not want to store an infinite amount of changed entries every time a player picks up or drops the Collectible though – just the most recent action. For this, we are searching for an existing entry about it first and then modify that or remove all others. Note that the implementation as a whole is basically just a sample for demonstrating a different networked approach. Since we only store the most recent change, the same functionality could also be achieved easily by storing the necessary variables on the Collectible itself, or letting the clients request them from the server via a single RPC call.

Collectible Zone

With the networked functionality for pickups above, we still need a way to detect if a player has scored by getting the enemy flag to its own base. In the Capture The Flag mode, we have added the CollectibleZone component for this reason.

Collectibles030

The CollectibleZone component should include a BoxCollider for detecting player or Collectible collisions. It is attached to a game object in the scene, defining the "Home Base" for one team. If an ObjectSpawner is assigned to the Require Object variable, that specific object needs to be present at its home base before the player can score. In the Capture The Flag mode, this means that e.g. Team = 0 (Red Team) can only score by returning the blue flag, if the required object (their own - red flag) is at their base. With the Require Object unassigned, players could also score when someone else has taken their own flag.