Building a Cannonball Shooter Game: Part 4 — Explosives
In today’s blog post, let’s add some explosives to our game.
Disclaimer: This series is inspired by a tutorial from Tarodev. You can check out his excellent tutorial and download the assets here.
Creating an explosive prefab
Asset Prep
I’ll add a bomb GameObject to our Pyramid prefab and create a new prefab from it called PyramidWithBomb.
Ensure that you add a Rigidbody and Box Collider so that we can detect collisions between the bomb and the cannonball.
Note: Make sure you have Use Gravity unchecked so that the bomb won’t fall.
I got the asset from the Unity store, and you can download it here.
To enhance the experience of our game, let’s also add some sound effects and visual effects.
Scripting
I’ll create a new script called Bomb and attach it to the PyramidWithBomb prefab. Here are a couple of variables that we’ll be using:
[Header("Explosion")]
[SerializeField] private GameObject _explosionPrefab;
[SerializeField] private Rigidbody _rb;
[SerializeField] private float _explosionRadius;
[SerializeField] private float _explosionForce;
[SerializeField] float _upwardModifier;
private Vector3 _hitPoint;
[Header("Audio")]
[SerializeField] private AudioSource _audioSource;
[SerializeField] private AudioClip _explosionSound;
We also need to assign these values in the Inspector.
We only want our bomb to interact with the cannonball, so I’ll tag it as Ball and use OnCollisionEnter to detect the collision.
private void OnCollisionEnter(Collision collision)
{
if (collision.transform.CompareTag(("Ball")))
{
CreateExplosion(collision);
Destroy(gameObject, 2f);
}
}
To create an explosion, I’ll use Physics.OverlapSphere to cast a sphere at the collision point between the cannonball and the bomb, and then use the AddExplosionForce method to create an explosion force.
private void CreateExplosion(Collision collision)
{
// Cast a sphere at hit point and add force
ContactPoint contact = collision.contacts[0];
_hitPoint = contact.point;
Collider[] colliders = Physics.OverlapSphere(_hitPoint, _explosionRadius);
foreach (Collider collider in colliders)
{
if (collider.TryGetComponent<Rigidbody>(out Rigidbody rb))
rb.AddExplosionForce(_explosionForce, _hitPoint, _explosionRadius, _upwardModifier, ForceMode.Impulse);
}
// Add SFX and VFX
Instantiate(_explosionPrefab, transform.position, Quaternion.identity);
_audioSource.PlayOneShot(_explosionSound);
}
To enhance the game further, I want to add an impact sound when the cannonball collides with a cube. So in the Cannonball script, I’ll get a reference to the Audio Source component and the impact sound (which you can download for free from websites like pixabay.com or freesound.org).
[Header("Audio")]
[SerializeField] private AudioSource _audioSource;
[SerializeField] private AudioClip _impactSound;
In the OnCollisionEnter method, I’ll add the PlayOneShot method before the CreateSmallPush function.
private void OnCollisionEnter(Collision collision)
{
if (_isGhost) return;
if (collision.transform.CompareTag("Target"))
{
_audioSource.PlayOneShot(_impactSound);
CreateSmallPush(collision);
}
if (collision.transform.CompareTag("Ground"))
Destroy(gameObject, 0.5f);
}
To enhance this even further, I’ll add a sound when a box hits the ground. To make the sound less repetitive, I’ll create an array of sounds and randomly play one of them. Here are the Box script and a screenshot of its fields in the Inspector.
[Header("Audio")]
[SerializeField] private AudioSource _audioSource;
[SerializeField] private AudioClip[] _hitSounds;
private void OnCollisionEnter(Collision collision)
{
if (collision.transform.CompareTag("Ground"))
{
PlayHitSound();
Destroy(gameObject, 0.05f);
}
}
void PlayHitSound()
{
int randomIndex = Random.Range(0, _hitSounds.Length);
_audioSource.PlayOneShot(_hitSounds[randomIndex]);
}
Before testing the result, we need to modify the DisableRenderers method in the Projection script to disable the Box Collider components on the cubes and bomb. This adjustment is necessary to avoid collisions between the cannonball and these objects. Since our trajectory line simulates the cannon firing projectiles continuously, it’s important to ensure these projectiles do not interact with the simulated objects.
private void DisableRenderers(Transform parentObj)
{
// disable renderer component in parent object(level 1)
if (parentObj.TryGetComponent<Renderer>(out Renderer renderer))
renderer.enabled = false;
// disable box collider component in objects of the cubes (Target)
if (parentObj.TryGetComponent<BoxCollider>(out BoxCollider collider) && (parentObj.CompareTag("Target")))
collider.enabled = false;
// disable renderer components in child objects (level 2)
if (parentObj.childCount > 0)
{
foreach (Transform childObj_1 in parentObj.transform)
{
if(childObj_1.TryGetComponent<Renderer>(out Renderer renderer_child_1))
renderer_child_1.enabled = false;
// disable box collider component in objects of the cubes (Target) and bomb.
if (childObj_1.TryGetComponent<BoxCollider>(out BoxCollider collider_child_1) && (childObj_1.CompareTag("Target") || childObj_1.CompareTag("Bomb")))
collider_child_1.enabled = false;
// disable renderer components in child objects (level 3)
if (childObj_1.childCount > 0)
{
foreach (Transform childObj_2 in childObj_1.transform)
{
if(childObj_2.TryGetComponent<Renderer>(out Renderer renderer_child_2))
renderer_child_2.enabled = false;
// disable box collider component in objects of the cubes (Target)
if (childObj_2.TryGetComponent<BoxCollider>(out BoxCollider collider_child_2) && childObj_2.CompareTag("Target"))
collider_child_2.enabled = false;
}
}
}
}
}
And here’s the final result