Update 8/7/2015 (yeah…it’s about time)
Since people are still using this apparently and the thread gets pinged once in a while… it is about time to update the code. Since this was my first post and attempt into Unity scripting way back when… the gun was OK (but pretty crap code). Here are some updated scripts (better code, gun still OK)…
I moved from a single script for all guns approach to a more inherited approach. All of my guns now inherit from my base Gun script. Each child is a different kind of gun but in your code you can still grab it using the base Gun class.
As usual… use these as you see fit and how every you want. If you like them or the end up in something cool… post back (or pm/mail) and let me see… would be cool to see it actually being used somewhere.
Link to a newer blah webplayer (built today with these scripts)
https://dl.dropboxusercontent.com/u/1475775/CoD/newer/08-07-2015/gun.html
The Code…
Gun.cs
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Damage
{
public int amount; // how much damage
public DamageType type; //what type of damage
}
public enum DamageType
{
NORMAL,
FIRE,
ICE,
ACID,
ELECTRIC,
POISON
}
public enum WeaponType
{
SEMIAUTO, // burst fire and shotguns fall under this category
FULLAUTO
}
public class Gun : MonoBehaviour {
public string weaponName; // Name of this weapon
public WeaponType typeOfWeapon; // type of weapon, used to determine how the trigger acts
public bool usePooling = false; // do we want to use object pooling or instantiation
public Damage damage = new Damage(); // the damage and type of damage this gun does
public float projectileSpeed = 100.0f; // speed that projectile flies at
public float projectileForce = 10.0f; // force applied to any rigidbodies the projectile hits
public float projectileLifeTime = 5.0f; // how long before the projectile is considered gone and recycleable
public Transform muzzlePoint = null; // the muzzle point for this gun, where you want bullets to be spawned
public int maxPenetration = 1; // maximum amount of hits detected before the bullet is destroyed
public float fireRate = 0.5f; // time betwen shots
public bool infinteAmmo = false; // gun can have infinite ammo if thats what you wish
public int roundsPerClip = 50; // number of bullets in each clip
public int ammoReserve = 200; // number of clips you start with
public int roundsLeft; // bullets in the gun-- current clip
public float reloadTime = 2.5f; // how long it takes to reload in seconds
protected bool isReloading = false; // are we currently reloading
public float baseSpread = 0.2f; // how accurate the weapon starts out... smaller the number the more accurate
public float maxSpread = 4.0f; // maximum inaccuracy for the weapon
public float spreadPerShot = 0.75f; // increase the inaccuracy of bullets for every shot
public float spread = 0.0f; // current spread of the gun
public float decreaseSpreadPerTick = 0.25f; // amount of accuracy regained per frame when the gun isn't being fired
public float spreadDecreaseTicker = 0.5f; // time in seconds to decrease inaccuracy
protected float nextFireTime = 0.0f; // able to fire again on this frame
protected bool spreadDecreasing = false; // is the gun currently decrasing the spread
protected ProjectileInfo bulletInfo = new ProjectileInfo(); // all info about gun that's sent to each projectile
protected virtual void Start()
{
roundsLeft = roundsPerClip; // load gun on startup
}
// all guns handle firing a bit different so give it a blank function that each gun can override
public virtual void Fire()
{
}
// everything fires a single round the same
protected virtual void FireOneShot() {
}
// reload your weapon
protected virtual IEnumerator Reload()
{
if (isReloading)
{
yield break; // if already reloading... exit and wait till reload is finished
}
if (ammoReserve > 0)
{
isReloading = true; // we are now reloading
int roundsNeeded = roundsPerClip - roundsLeft; // how many rounds needed to fill the gun
yield return new WaitForSeconds(reloadTime); // wait for set reload time
if (ammoReserve < roundsNeeded)
{
roundsNeeded = ammoReserve;// if we have less bullets than needed to fill the gun... put all we have in
}
roundsLeft += roundsNeeded; // fill up the gun
}
isReloading = false; // done reloading
}
void DecreaseSpread()
{
// decrease the current spread per tick
spread -= decreaseSpreadPerTick;
// if the current spread is less then the base spread value, set it to the base
if (spread <= baseSpread)
{
spread = baseSpread;
// stop the decrease spread function until we need it again
spreadDecreasing = false;
CancelInvoke("DecreaseSpread");
}
}
// set all bullet info from the gun's info
// if you plan to be able to change weapon stats on the fly
// call this function in the fire function (worst performance but always checkes gun stats before firing)
// or Always call this just after altering a weapon's stats (best performance since its called once when it's needed)
// default right now is it is called once in start
protected void SetupBulletInfo()
{
bulletInfo.owner = transform.root.gameObject; // the Owner of this weapon (GameObject) <- use this for scoreboard and who killed who
bulletInfo.name = name; // Name of this weapon <- for keeping track of weapon kills / whose killed by what
bulletInfo.damage.amount = damage.amount; // amount of damage
bulletInfo.damage.type = damage.type; // type of damage
bulletInfo.force = projectileForce; // weapon force
bulletInfo.maxPenetration = maxPenetration; // max hits
bulletInfo.maxSpread = maxSpread; // max weapon spread
bulletInfo.spread = spread; // current weapon spread value
bulletInfo.speed = projectileSpeed; // projectile speed
bulletInfo.owner = transform.root.gameObject; // this projectile's owner gameobject, useful if you want to know whose killing what for kills/assists or whatever
bulletInfo.usePool = usePooling; // do we use object pooling
bulletInfo.projectileLifeTime = projectileLifeTime; // how long till this bullet just goes away
}
}
Gun_Physical.cs (since physical and raycast guns behave differently)
using UnityEngine;
using System.Collections;
public class Gun_Physical : Gun {
public GameObject projectile = null; // projectile prefab... whatever this gun shoots
protected override void Start()
{
base.Start();
SetupBulletInfo(); // set a majority of the projectile info
}
protected override void FireOneShot () {
if (roundsLeft > 0)
{
Vector3 pos = muzzlePoint.position; // position to spawn bullet is at the muzzle point of the gun
Quaternion rot = muzzlePoint.rotation; // spawn bullet with the muzzle's rotation
bulletInfo.spread = spread; // set this bullet's info to the gun's current spread
GameObject newBullet;
if (usePooling)
{
newBullet = ObjectPool.pool.GetObjectForType(projectile.name, false);
newBullet.transform.position = pos;
newBullet.transform.rotation = rot;
}
else
{
newBullet = Instantiate(projectile, pos, rot) as GameObject; // create a bullet
newBullet.name = projectile.name;
}
newBullet.GetComponent<Projectile>().SetUp(bulletInfo); // send bullet info to spawned projectile
spread += spreadPerShot; // we fired so increase spread
// if the current spread is greater then the weapons max spread, set it to max
if (spread >= maxSpread)
{
spread = maxSpread;
}
// if the spread is not currently decreasing, start it up cause we just fired
if (!spreadDecreasing)
{
InvokeRepeating("DecreaseSpread", spreadDecreaseTicker, spreadDecreaseTicker);
spreadDecreasing = true;
}
// if this gun doesn't have infinite ammo, subtract a round from our clip
if (!infinteAmmo)
{
roundsLeft--;
// if our clip is empty, start to reload
if (roundsLeft <= 0)
{
StartCoroutine(Reload());
}
}
}
}
}
Gun_BurstFire.cs (anything that fires multiple shots per triggerpull)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// component 'Gun_BurstFire'
/// ADD COMPONENT DESCRIPTION HERE
/// </summary>
[AddComponentMenu("Scripts/Gun_BurstFire")]
public class Gun_BurstFire : Gun_Physical
{
public int burstCount = 3;
public float burstLag = 0.1f;
public override void Fire()
{
if (nextFireTime < Time.time)
{
StartCoroutine(BurstFire());
nextFireTime = Time.time + fireRate;
}
}
IEnumerator BurstFire()
{
for (int i = 1; i <= burstCount; i++)
{
FireOneShot();
yield return new WaitForSeconds(burstLag);
}
}
}
Gun_SingleShot.cs
using UnityEngine;
using System.Collections;
public class Gun_SingleShot : Gun_Physical
{
public override void Fire()
{
if (nextFireTime < Time.time)
{
FireOneShot();
nextFireTime = Time.time + fireRate;
}
}
}
Gun_Shotgun.cs
using UnityEngine;
using System.Collections;
/// <summary>
/// component 'Shotgun'
/// ADD COMPONENT DESCRIPTION HERE
/// </summary>
[AddComponentMenu("Scripts/Shotgun")]
public class Gun_Shotgun : Gun_Physical
{
public int pelletCount = 8;
public override void Fire()
{
if (nextFireTime < Time.time)
{
for (int i = 0; i <= pelletCount; i++)
{
FireOneShot();
}
nextFireTime = Time.time + fireRate;
}
}
}
Projectile.cs (base projectile for all others to inherit from)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Base Class for projectiles, contains common elements to all type of projectiles for other projectile classes to be derived from.
/// </summary>
public class Projectile : MonoBehaviour {
protected ProjectileInfo myInfo = new ProjectileInfo();
protected Vector3 velocity;
protected int hitCount = 0;
protected List<Collider> collidersToIgnore = new List<Collider>();
protected List<Collider> backCollidersToIgnore = new List<Collider>();
// This is bullet initialization, It gets called by the weapon that fired this projectile
public virtual void SetUp(ProjectileInfo info)
{
myInfo = info;
hitCount = 0;
velocity = myInfo.speed * transform.forward + transform.TransformDirection(Random.Range(-myInfo.maxSpread, myInfo.maxSpread) * myInfo.spread, Random.Range(-myInfo.maxSpread, myInfo.maxSpread) * myInfo.spread, 1);
collidersToIgnore.Add (myInfo.owner.GetComponent<Collider>());
backCollidersToIgnore.Add (myInfo.owner.GetComponent<Collider>());
Invoke("Recycle", myInfo.projectileLifeTime); // set a life time for this projectile
}
protected virtual void MakeAHole(RaycastHit hit)
{
foreach (Collider c in collidersToIgnore)
{
if (hit.collider == c)
{
return;
}
}
BulletHoleManager.bulletHole.SpawnHole(hit);
collidersToIgnore.Add(hit.collider);
}
protected virtual void MakeABackHole(RaycastHit hit)
{
foreach (Collider c in backCollidersToIgnore)
{
if (hit.collider == c)
{
return;
}
}
BulletHoleManager.bulletHole.SpawnHole(hit);
backCollidersToIgnore.Add(hit.collider);
}
protected virtual void Recycle()
{
//Clear the colliders this bullet ignores
collidersToIgnore.Clear ();
backCollidersToIgnore.Clear ();
// pool or destroy the bullet when it is no longer used.
if (myInfo.usePool)
{
ObjectPool.pool.PoolObject(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
[System.Serializable]
public class ProjectileInfo
{
public GameObject owner;
public string name;
public Damage damage = new Damage();
public float force;
public int maxPenetration;
public float maxSpread;
public float spread;
public float speed;
public bool usePool;
public float projectileLifeTime;
}
RigidProjectile.cs (projectile based on a rigidbody… cause bullet drop is all the rage)
using UnityEngine;
using System.Collections;
public class RigidProjectile : Projectile
{
private Rigidbody myRigid;
public override void SetUp(ProjectileInfo info)
{
base.SetUp(info);
myRigid = GetComponent<Rigidbody>();
myRigid.velocity = velocity;
}
void FixedUpdate()
{
// Debug.DrawLine(transform.position, transform.position + myRigid.velocity / 60, Color.red);
// Debug.DrawLine(transform.position, transform.position - myRigid.velocity / 30, Color.magenta);
RaycastHit hit; // forward hit
RaycastHit hit2; // rear hit
if (Physics.Raycast(transform.position, myRigid.velocity, out hit, (myRigid.velocity.magnitude/Time.deltaTime), ~LayerMask.NameToLayer("Projectiles")))
{
// probably shouldn't do this but best way i can think of to avoid
// multiple hits from same bullet
//myRigid.MovePosition(hit.point); // move the bullet to the impact point
transform.position = hit.point;
if (hit.transform.CompareTag("Ground"))
{// if we hit dirt... kill the bullet since most weapons don't pierce the earth
CancelInvoke("Recycle");
Recycle();
}
Health hitObject = hit.transform.GetComponent<Health>();
if (hitObject)
{
hitObject.Hit(myInfo); // send bullet info to hit object's health component
}
else
{
MakeAHole(hit); // make a hole anywhere except the players
}
hitCount++; // add a hit
if (hitCount > myInfo.maxPenetration)
{
CancelInvoke("Recycle");
Recycle(); // if hit count exceeds max hits.... kill the bullet
}
}
// this shoots a ray out behind the bullet.
// use this to add a bullet hole to the back side of a penetrated wall or whatever
if (Physics.Raycast(transform.position, -myRigid.velocity, out hit2, 2+(myRigid.velocity.magnitude/Time.deltaTime) , ~LayerMask.NameToLayer("Projectiles")))
{
if (hit2.transform.CompareTag("Player"))
{
// do nothing since we probably already penetrated the player
}
else
{
MakeABackHole(hit2);
}
}
}
}
That’s it for the main scripts (there are a few others in the download).
Link to Direct Downloads of everything:
Unity Package File: https://dl.dropboxusercontent.com/u/1475775/CoD/newer/08-07-2015/Novashot_Guns_08072015.unitypackage
Scripts Only Folder: https://dl.dropboxusercontent.com/u/1475775/CoD/newer/08-07-2015/Novashot_Guns_ScriptsOnly_08072015.zip
Entire Project folder: https://dl.dropboxusercontent.com/u/1475775/CoD/newer/08-07-2015/Novashot_Guns_Project_08072015.zip
…Just noticed… as a bonus apparently since I’ve already typed this up and linked everything… you get my Health script also.
The original stuff is still below if need be.
Have fun.
The Original Post
Highlights:
- Multiple Guns in one script
- Tracers
- Bullet Penetration
- bullet randomness, increases over time
- multiple material ready, just needs effects
- Look down sights ready
- Easy
Here’s a gun script that I been working on and it does just about everything. I searched for good gun scripts everywhere and got sick of the search; here is a bit of almost every script I seen all added together. It’s a machine gun, burst / single shot, shotgun, and a grenade/rocket launcher all-in one script (c# only sorry). With a few changes in variables, you can make almost any weapon you wish.
This gun side script supports variable bullet penetration, bullet creates impact effects and bullet holes on both sides of a penetrated wall, and variable tracers. Gun also has variables for random bullet spread with support for a decrease in accuracy during sustained fire from machine guns.
Burst mode can fire any number of rounds per trigger pull and you can set the time between the rounds fired in the burst. Want a single shot? Set the burst to 1 and done.
Shotgun has a variable for the number of pellets fired per shot.
The Launcher supports both rockets and grenades if you’re into noob tubes.
…the scripts have the framework in for multi-player, but hasn’t been fully integrated yet, but should be soon. I’m working with the standard unity master server so my multi-player parts will be geared toward that.
Forgot to mention, use the right mouse button(fire2) to look down the sights of your gun.
Here’s a Web Player Test of the gun Scripts in action.
Player with guns Unity Package
If you like what you see, Use and Update these as you see fit. If you happen to add something cool, let everyone else know. Let me know what you think.
Thanks and have fun.
333849–11731–$novashot_gun_scripts_136.zip (8.79 KB)
337268–11830–$novashot_cod_style_weapons_117.unitypackage (1.58 MB)
333849–149471–Novashot_Guns_ScriptsOnly_08072015.zip (18 KB)
333849–149472–Novashot_Guns_08072015.unitypackage (20.9 KB)