Skip to main content

Collection

Written by: Akram Taghavi-Burris | © Copyright 2024
Status

In many resource collection games, tracking what players gather is essential. Imagine a scenario where a player collects wood, stone, and other resources scattered across a virtual world. Each time they pick up an item, our game should store it in a structured way.

Instead of scattering code throughout our game to handle each resource individually, we'll design and implement a Collection class to handle this task. The Collection class will not only keep a record of every resource gathered but will also manage updates, ensuring our data remains accurate and easily accessible.

Implementation

Our collection should be persistent throughout the game or level and easily record not only the collectable items in the collection but the amount of these items. As such the following will be implemented:

  • Signleton Pattern: Our Collection class will inherit from a Singleton base class, which ensures only one instance is created. With this Singleton approach, we can access our collection from any part of the code without repeatedly instantiating or passing around the Collection instance.

  • Dictionary: The core of our Collection class is the dictionary which is made up of:

    • Key: Each type of collectable resource (CollectableData).
    • Value: The count of units collected for that resource type.

Using a dictionary to manage resources is efficient. When a player gathers a new item, we can check if it already exists in our dictionary. If it does, we update the count; if it doesn't, we add it as a new entry.

With these features in mind, the collection class would be written as such:

Collection.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// Inherits from Singleton pattern to ensure only one instance exists
public class Collection : Singleton<Collection>
{
/// <summary>
/// Dictionary to store each type of collectable item and its count.
/// Key: CollectableData - the type of item collected.
/// Value: int - the quantity of that item collected.
/// </summary>
private Dictionary<CollectableData, int> _collection = new Dictionary<CollectableData, int>();

/// <summary>
/// Adds a specified quantity of a collectable item to the collection.
/// </summary>
/// <param name="collectable">The specific collectable item to add.</param>
/// <param name="count">The quantity of the item to add (default is 1).</param>
public void AddItem(CollectableData collectable, int count = 1)
{
// Check if the collection already contains this type of collectable
if (_collection.ContainsKey(collectable))
{
// If the item already exists, increment its count by the specified amount
_collection[collectable] += count;
}
else
{
// If the item does not exist, add it with the initial count
_collection[collectable] = count;
} // end if(_collection.ContainsKey)

// Log a message when item has been added and show the new count
Debug.Log($"{collectable.CollectableName} was added to the collection, new count {_collection[collectable]}");
} // end AddItem method

}

Integrating the Collection

Now that we have our Collection class to track resources collected, let's set it up in the game scene and use a Collector object to add items to it.

Step 1: Setting up the Collection in the Scene

  1. Create an Empty GameObject: In Unity, create an empty GameObject in your scene. Rename it as "CollectionManager" to make its purpose clear.
  2. Attach the Collection Script: Attach the Collection script to this CollectionManager GameObject. This setup creates a Singleton instance of the Collection class, ensuring that our game has a centralized manager to track all collectable items.

Step 2: Adding to the Collection

In our current game setup, we already have a Collector component that detects and collects items when they enter its range. To start tracking these collectable items in our Collection, we'll implement the AddItem method in the Collector to log each item as it's collected.

Our goal is to make sure each time a collectable is picked up, it's automatically added to the Collection tally, ensuring our game keeps an accurate record of all resources gathered.

To implement this, we'll call the Collection.Instance.AddItem() method whenever a collectable object is detected and collected by the Collector. This way, each collected item will incrementally update the total count of that specific resource type in our Collection. The highlighted lines of code demonstrates how to implement this:

Collector.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(BoxCollider))]
public class Collector : MonoBehaviour
{
// Called when game object initializes
public void Awake()
{
// Get the BoxCollider component
BoxCollider boxCollider = GetComponent<BoxCollider>();

// If the collider is not a trigger, set it as a trigger
if (!boxCollider.isTrigger)
{
boxCollider.isTrigger = true;
Debug.LogWarning("Collider on collector object was not set to a trigger. It has now been set to a trigger");
}
}//end Awake()

// Triggers when an object enters the collider
private void OnTriggerEnter(Collider other)
{
// Check if the other object has a Collectable component
if (other.TryGetComponent<Collectable>(out Collectable collectable))
{
Collect(collectable);
}
}//end OnTriggerEnter

// Collect the collectable
protected virtual void Collect(Collectable collectable)
{
// Process the data for the collectable
ProcessCollectableData(collectable.CollectableData);

// Call the OnCollected method when the collectable is collected
collectable.OnCollected();

//Add collectable to collection
Collection.Instance.AddItem(collectable.CollectableData);
}//end Collect()

// Process the data for the collectable object
protected virtual void ProcessCollectableData(CollectableData collectableData)
{
Debug.Log($"Collected: {collectableData.CollectableName}, Points: {collectableData.PointValue}");
}//end ProcessCollectableData()
}

Extending the Collection Class

Since this Collection class is built on flexible principles, adding functionality becomes easy. Here are a few ideas for extending this class further:

  • RemoveItem Method: We could add a method to decrease or remove items from the collection when they're spent or discarded.
  • GetCount Method: A method to retrieve the count for a specific item could be useful, allowing other parts of the game to check if the player has enough of a resource.
  • UI Integration: The Collection class could integrate with the user interface to display collected items to the player, providing feedback on their progress.

By building on the modular foundation we've established, these extensions are easy to implement and manage without major changes to the codebase.

Summary

In this chapter, we've created a Collection class to manage resources efficiently. Using the Singleton pattern ensures a single instance of the class manages all resources, while the dictionary structure allows us to handle various types of items with ease.

While this implementation covers the essential aspects of managing collectables within our game, we may encounter scenarios where we have a wide variety of different collectable types. In such cases, it could be beneficial to load their data from a CSV file, allowing for easier management and scalability of collectable items. This approach not only simplifies the process of adding new collectables but also makes it easier to modify existing ones without altering the code. In the next chapter, we will explore how to efficiently load data from CSV files into Scriptable Objects, enhancing our game's flexibility and making resource management even more robust.