Sitemap
Press enter or click to view image in full size

Interactable Loot Bins and Item Spawning

4 min readSep 19, 2025

--

Let’s continue this series in game interactions with something everyone loves…treasure chests.

Setup

This sci-fi bin feels appropriate for the scene.

Press enter or click to view image in full size

The loot bin has a lid to animate, a couple of UI objects for player interaction and a spawn point object for the loot spawning position.

The box collider provides hard surface collision, while the isTrigger sphere collider is used to show UI to the player when they are close enough to interact with the object. The Container script is what I will be covering in this article.

The container animation is straight forward, starting closed and then opening when the player interacts. The IsOpen bool will change back and forth causing the lid to open and close.

Press enter or click to view image in full size

The UI image appears when the player is in range, to let them know what to press for interaction.

Press enter or click to view image in full size

If the container is locked, this “You need a key” UI will appear, and then fade out after a few seconds.

Press enter or click to view image in full size

The Container script has a serialized List for loot, making it a designer friendly drag and drop with the loot objects. For this example, I will just be spawning a cube primitive prefab every half a second until the loot list runs dry.

Scripting

If the container is locked, the UI will appear to let the player know they need a key, and then start a coroutine to deactivate it again after a the fade out animation is done playing.

If the container is unlocked, the open animation will play. If the loot hasn’t already been spawned in once, a coroutine will start and spawn in each loot object in the list with a wait time in between.

public class Container : MonoBehaviour, iInteractable
{
[SerializeField] private List<GameObject> _lootList = new List<GameObject>();
[SerializeField] private GameObject _needKeyUI;
private Animator _anim;
[SerializeField] private bool _isUnlocked;
[SerializeField] private Transform _spawnPoint;
[SerializeField] private float _spawnTime = 0.25f;
private WaitForSeconds _deactivateUIwaitForSeconds;
private WaitForSeconds _spawnWaitForSeconds;
private bool _isAnimating = false;
private bool _isCollected = false;

private void Start()
{
_anim = GetComponent<Animator>();

_deactivateUIwaitForSeconds = new WaitForSeconds(5.1f);
_spawnWaitForSeconds = new WaitForSeconds(_spawnTime);

if (_anim == null)
Debug.LogWarning("The animator on the contariner " + gameObject.name + "is NULL.");
}

public void Interact()
{
if (_isUnlocked)
{
//play open animation
if (_anim != null)
_anim.SetBool("IsOpen", true);

if (!_isCollected)
{
_isCollected = true;

//spawn random loot objects in a coroutine
if (_lootList != null)
{
//start coroutine
StartCoroutine(SpawnRoutine());
}
}
}
else
{
//let the player know they need a key
Debug.Log("You need a key!");

if (_needKeyUI != null)
_needKeyUI.SetActive(true);

if (!_isAnimating)
{
_isAnimating = true;
StartCoroutine(DeactivateUIRoutine());
}
}
}

private IEnumerator DeactivateUIRoutine()
{
yield return _deactivateUIwaitForSeconds;

if (_needKeyUI != null)
{
_needKeyUI.SetActive(false);
}

_isAnimating = false;
}

private IEnumerator SpawnRoutine()
{
foreach(GameObject obj in _lootList)
{
yield return _spawnWaitForSeconds;

//spawn item
Instantiate(obj, _spawnPoint.position, Quaternion.identity);
}
}
}

The loot script has a handful of serialized variables a designer can play with to control the force and direction of the spawned loot. This physics based approach will push the loot upwards and forwards from the container, with some left and right randomization.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Loot : MonoBehaviour
{
private Rigidbody _rb;
[SerializeField] private float _ZMinDirectionForce, _ZMaxDirectionForce;
[SerializeField] private float _Yforce;
[SerializeField] private float _Xforce;
[SerializeField] private float _force;

// Start is called before the first frame update
void Start()
{
_rb = GetComponent<Rigidbody>();

if (_rb != null)
{
//add upwards Y force and forwards Z force, with small random +/- X value
float randomZ = Random.Range(_ZMinDirectionForce, _ZMaxDirectionForce);
Vector3 direction = new Vector3(_Xforce, _Yforce, randomZ);
_rb.AddForce(direction * _force, ForceMode.Impulse);
}
}
}

Here is the locked container in action.

Press enter or click to view image in full size

Once the container is unlocked, it starts raining cubes!

Press enter or click to view image in full size

Please join me in my next article where I dive into the loot behavior when the player collects it. Thanks for reading!

--

--

Jared Amlin
Jared Amlin

Written by Jared Amlin

I am an artist and musician, that is currently diving headfirst into game development with C# and Unity3D.

No responses yet