The weapon system in Captain's Hold is a system that I'm particularly proud of. Of course, I don't think it's a perfect system, and there are certainly better methods for achieving the functionality that I wanted in the end product. However, the gun wound up feeling fairly fun in the showcase build, and I'm happy with what I came up with.
We were initially supposed to have a variety of gun types, which all interacted with the environment and enemies in different ways. Of course, we didn't have time to model, create effects for, and balance all of the weapon types, so unfortunately we wound up with a single weapon. Despite the visual setback, I think the system that was created for weapon iteration is still very useful for creating new weapons, and I can likely implement a shotgun and sniper type weapon with very little effort.
There are two features that I'd like to highlight with this weapon system that I'm happy with, and I'd really like it if someone made a useful blog post about this before starting this project. I made a real cool IK Recoil system using Unity's experimental animation API, but right now I'm just going to detail the projectile collision system because that seems more useful for Unity programmers looking for solutions.
Unity is pretty awful at detecting collisions between colliders moving at high speeds. When you're making a sci-fi shooter that features slower-moving projectiles, raycasting is a less desirable option as you want the collision of the laser beam to match up perfectly with the slow-moving projectile.
So I went ahead and did the classic "Baby's first Unity Projectile", which goes something like:
GameObject proj = Instantiate(Prefab.gameObject, Barrel.position, Barrel.rotation);
proj.GetComponent<Rigidbody>().AddForce(ProjForce);
This caused a huge problem. If I added too much force to the projectile, the rigidbody would clip through solid objects and I'd get really inconsistent collision, resulting in very frustrating gameplay. One method that seemed to alleviate this issue, was enabling continuous collision, and reducing the mass of the projectile's rigidbody to something like 0.01. I no longer had crazy force values like 500000, and for the most part this was serviceable. HOWEVER, after testing this a bit more, I realized that this simple solution didn't actually work, and I was still getting projectiles that just ignored collisions at certain angles and distances.
The real solution was to use Raycasting. Pretty obvious in hindsight.
The first step was to remove the collider from the projectile object completely.
Second step is to Raycast in your aim direection, and save out a RaycastHit variable on the Fire Weapon event.
Send the RaycastHit over to the newly spawned projectile, and have the projectile wait until it's close to the hit position to play hit effects and deal damage to whatever enemy you hit with the Raycast.
(Side note: If you're making a third person game, make sure you're doing a raycast from the camera's forward AS WELL AS the player's weapon position to the camera's hit position. This allows you to avoid weird clipping issues when your player is close to walls or other large obstacles. I give an example of this below)
Weapon.cs
//Raycast from Camera
if (Physics.Raycast(Camera.main.transform.position, Direction.normalized,
out CamHit, AimDistance, AimLayer))
{
//Raycast from Player
if (Physics.Raycast(Barrel.position, (CamHit.point - Barrel.position).normalized,
out RayCastHit, AimDistance, AimLayer))
{
//Save RayCastHit;
}
}
...
//Spawn Projectile
GameObject proj = Instantiate(Prefab.gameObject, Barrel.position, Barrel.rotation);
//If the raycast hit an enemy (or anything we care about) save out a reference to it
proj.GetComponent<Projectile>().EnemyRef = RayCastHit.collider.GetComponentInParent<Enemy>();
Projectile.cs
void Update()
{void Update()
{
//Check the distance between the start position and current position of your projectile
CurrentDistance = (StartPoint - ProjectileObject.transform.position);
/
if (CurrentDistance.magnitude >= RaycastDistance)
{
if (EnemyRef) //If the raycast hit an enemy
{
EnemyRef.TakeDamage(Damage);
}
Explode(); //Spawn effects and kill the gameobject
}
}
//Check the distance between the start position and current position of your projectile
CurrentDistance = (StartPoint - ProjectileObject.transform.position);
/
if (CurrentDistance.magnitude >= RaycastDistance)
{
if (EnemyRef) //If the raycast hit an enemy
{
EnemyRef.TakeDamage(Damage);
}
Explode(); //Spawn effects and kill the gameobject
}
}
Obviously checking the distance of the projectile to it's target in Update is not the most efficient way of doing this, but it's a relatively simple solution that I didn't think would work at first. It turned out to make everything involving projectiles in the game much more consistent.
Comments