Devlog 4


Shooting System

Having successfully prototyped turret rotation in the vehicle, I went on to add the shooting mechanic. The initial approach was to instantiate a bullet prefab on the shoot point of the turret, apply forward velocity, and utilize collision detection to deal damage on impact. The mechanic both functionally and visually succeeded, and I was able to successfully integrate the same into our multiplayer architecture using Unity Netcode. But in a team playtest, we discovered that the bullet-based system wasn't compatible with the action-oriented and cooperative nature of our game. Bullets would travel too slowly, would sometimes desync clients, and miss shots due to lag or patchy collision.

To fix this, I swapped out the old bullet shooting system with a RayCast-based shooting mechanic, which was a better fit for our design goals. 

  1. The instant hit detection made shooting feel snappier and more responsive.
  2. The improved network performance since no physics objects needed to be synced or interpolated.
  3. The improved accuracy and consistency across clients are important for a role-based cooperative gameplay.

After the new system was implemented, our group conducted another playtest, and the result improved significantly. Shooting felt satisfying and accurate, and matched the arcade-style flow we envisioned during our planning. The group's feedback motivated me to further improve the system by adjusting hit detection layers, adding firing cooldown through weapon properties, and tying it into our IDamageable-based health implementation for smooth damage calculation over the network. This adjustment not only improved the gameplay experience but also simplified our network logic, enabling us to keep the shooting mechanic fast, fair, and fun for all parties involved.

The Gun script is a general-purpose weapon fire controller. It handles fundamental mechanics like fire rate, ammo, and cooldown management. Instead of handling the actual firing logic, it leaves that to wherever the weapon is being used, like in TurretController. This makes the gun system very modular and scalable, so one Gun component can be reused by many different firing configurations: RayCast weapons. To keep weapon behavior flexible, I used a ScriptableObject called GunStats. The script has all variable stats for a weapon: fire rate, damage, range, and optionally ammunition. The main benefit of GunStats is that it keeps weapon configuration data driven rather than hard-coded. Fire rate or damage can be adjusted by designers or developers without modifying the code. This ScriptableObject allowed me to expand content in the future, for example, new guns or balanced weapons, without altering the core shoot logic.

IShooter is an interface with a single method: Fire(). It's used to give any object (a player, a turret) the ability to shoot, without caring how it's done. The shooting mechanism is replaceable and flexible. Any object that needs the ability to shoot can implement this interface directly, thereby simplifying the codebase and making it more extensible.

The Shooter script is used to bind the turret weapon system to player input within a networked environment. It holds the assigned shooter's clientId and a flag for whether the local player is holding control. Upon the correct player's click to fire, it calls the Gun.Fire() method. This script was required to implement role-based gameplay such that only the assigned shooter of a two-player team can utilize the turret. It also separates input logic from weapon logic, making the system clean and easier to maintain.

TurretController script handles aiming and firing for the turret. It awaits mouse input from the shooter, calculates yaw and pitch deltas, and relays these to the server in the form of ServerRPCs. The server updates synchronized NetworkVariables for turret rotation on all clients in the same way. FireServerRpc() within the script above uses Raycasting to detect hits and deal damage by calling TakeDamage() on any IDamageable object. The approach ensures server-authoritative firing, prevents cheating, and keeps shooting performance smooth on the network. The turret itself was also made modular by decoupling the cannon head and base in such a way that there was horizontal and vertical separation.

IDamageable is an interface that defines a standard damage structure with a TakeDamage(float amount) function and a CurrentHealth property. It allows any object—car, enemy to be damaged in a standardized method. This makes the damage system dynamic and extensible. I do not need to code special logic for each object type. As long as an object has IDamageable implemented, it can get along with the shooting system, which is necessary to make the architecture clean and future-proof.

Leave a comment

Log in with itch.io to leave a comment.