Saturday, January 11, 2025

Shoot! Shoot!

 Implementing the Player Shooting Mechanic

In the world of game development, one of the most satisfying tasks is bringing a simple concept to life and watching it evolve. For our game, we needed to implement a shooting mechanic for the player character, but with a twist: the player can only shoot when they have accumulated enough "madness points." Since the player can only shoot when they have 5 madness points, and they die when they reach 10, the idea is to create a sort of "risk/reward" mechanic where on certain levels it makes sense for the player to engage in riskier behavior in order to access to the ability to shoot. In this post, I'll walk you through the journey of getting the shooting mechanic to work, from initial struggles to the final solution.

The Basics

The goal was simple: the player would shoot a projectile at enemies when they had earned 5 madness points. To achieve this, I used a basic system involving Unity's physics engine. The projectile itself needed to be able to interact with the game world, destroy monsters upon collision, and be destroyed after a set period to avoid cluttering the scene.

Starting Simple: The Projectile Prefab

The first step was creating the projectile. This was a straightforward task – I created a basic sphere mesh, attached a Rigidbody to it (so that it would move based on physics), and gave it a collider for interaction with other objects. The projectile would be instantiated at a specific spawn point in front of the player, and it would move in the direction the player was facing.

However, there was a catch. I wanted to ensure that the player could only shoot when they had accumulated at least 5 madness points. This led me to tweak the shooting mechanic to check the player's madness points before firing.

Checking for Madness Points

I created a script that referenced the MadnessPoints script on the player, checking if the player had accumulated 5 or more points before allowing them to shoot. This was implemented in the Update() method, where I checked if the fire button was pressed and whether the player had enough points.

void Update() { if (Input.GetButtonDown("Fire1")) { if (madnessPoints != null && madnessPoints.madnessPoints >= 5) { Shoot(); } else { Debug.Log("Madness Points insufficient to shoot! Need at least 5."); } } }

With this in place, the shooting mechanic would only be triggered if the player had the required madness points.

The Shooting Function

Once I confirmed that the player had enough madness points, I needed a way to actually fire the projectile. This involved instantiating the projectile prefab at a specific shoot point (right in front of the player). Here's the key part of the code that handled the shooting:

void Shoot() { if (projectilePrefab != null && shootPoint != null) { GameObject projectile = Instantiate(projectilePrefab, shootPoint.position, shootPoint.rotation); Rigidbody rb = projectile.GetComponent<Rigidbody>(); if (rb != null) { Vector3 forwardDirection = transform.forward; forwardDirection.y = 0f; rb.velocity = forwardDirection.normalized * projectileSpeed; } } else { Debug.LogWarning("Projectile Prefab or Shoot Point is not assigned!"); } }

This code spawns the projectile, adds a velocity to it (based on the direction the player is facing), and sends it flying. This mechanic worked well initially, but it wasn't quite finished yet.

Handling Collisions: Destroying the Monster

Now that the projectile was shooting, I needed to ensure that it interacted correctly with the monsters. Specifically, when the projectile collided with a monster, the monster should be destroyed. This meant adding a collision handler to the projectile script. The goal was to detect when the projectile hit an enemy (tagged as "Basic Monster") and destroy it.

Here's the collision detection code that did the trick:

private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Basic Monster")) { Destroy(collision.gameObject); // Destroy the monster Destroy(gameObject); // Destroy the projectile itself } }

This code worked as expected – when the projectile hit a monster, it destroyed both the monster and the projectile. But this was only the beginning.

Adding a Time Limit on the Projectile

I didn’t want the projectiles to just hang around in the scene forever, so I added a time limit. After a brief delay, the projectile would destroy itself, cleaning up the scene. I made this delay configurable in the inspector so I could adjust it as needed.

public float lifetime = 2f; // Time before the projectile is destroyed private void Start() { Destroy(gameObject, lifetime); // Automatically destroy the projectile after a set time }

This ensured that after the projectile fired, it would be cleaned up after 2 seconds, preventing unnecessary clutter in the scene. It was a small detail but helped maintain a clean game environment.

A Final Touch: Adding Sound Effects

Of course, no shooting mechanic is complete without sound. I added a sound effect that would play every time the player fired a projectile. The sound is played using Unity’s built-in AudioSource component, attached to the player.


public AudioClip shootSound; // The sound to play when the player shoots private AudioSource audioSource; void Start() { audioSource = GetComponent<AudioSource>(); } void Shoot() { if (madnessPoints != null && madnessPoints.madnessPoints >= 5) { GameObject projectile = Instantiate(projectilePrefab, shootPoint.position, shootPoint.rotation); Rigidbody rb = projectile.GetComponent<Rigidbody>(); if (rb != null) { Vector3 forwardDirection = transform.forward; forwardDirection.y = 0f; rb.velocity = forwardDirection.normalized * projectileSpeed; } // Play shooting sound if (audioSource != null && shootSound != null) { audioSource.PlayOneShot(shootSound); } } }

Now, every time the player shoots, the sound effect plays, adding some nice audio feedback to the action.

Final Thoughts

After some trial and error, I was able to implement a working shooting mechanic that not only respects the madness points but also includes sound and effective collision handling. By breaking down the task into manageable parts and adding debugging steps along the way, I was able to quickly identify and fix any issues that cropped up. It’s a small feature in the grand scheme of the game, but it’s these little mechanics that bring the world to life.

Next up: refining AI behavior and adding some more exciting features to the game. Stay tuned!

No comments:

Post a Comment

"Hello Dr. Faulken. Would you like to play a game?"

I recently re-watched the classic proto-hacker 80's film "WarGames" starring Mathew Broderick and Ally Sheedy. The famous fina...