Thursday, October 1, 2015

Tutorial: Projectile Weapon: Part 1 Revision 2

This tutorial will be more lengthy, i will cover both raycasting, projectile spawning, ejection, bullet count, cinematics(sound, light), aiming down iron sights/scope.

Now, as always lets get some assets before we begin programming. Go to the asset store find a weapon that is free. There are many to choose from.

https://www.assetstore.unity3d.com/en/#!/content/21025

Now, we need some sound. freeSFX.co.uk has a decent selection. Pick whichever you want. Currently we don't care about the accuracy of the sound to the weapon, next we need the sound of a cartridge being ejected. freeSFX does not have any good ones, but Freesound.org does. Search for "bullet shell":
http://www.freesfx.co.uk/sfx/gun
--M1 Garand semi automatic rifle, .30 caliber, single shot (distant perspective)

Bullet shell sound from Freesound.org

We need magazine removal and insertion sound:
http://www.freesfx.co.uk/sfx/Magazine
--Machine gun, .308 caliber, remove magazine clip, full, DPMS LR-308, AR-10
--Machine gun, .308 caliber, insert magazine clip, empty, DPMS LR-308, AR-10


So, if you look at your weapon, you should notice a few things:
1) Muzzle(where the bullet flies from):
---We will need to add the fire sound and muzzle flash here.

2) Ejection port(where the spent cartridge ejects from):
---We need to add the bullet shell sound here.

3) Magazine(self explanatory):
---We will need to add a sound and an animation here.

4) Trigger
--This will play a sound when you run out of bullets.



Create a new scene and import your fire arm. Next we need to create a EnmptyGame object. You'll want to position the EmptyGame Object in front of the muzzle. It may be difficult to see, so add Mesh Renderer and Mesh Filter component. Under Mesh Filter search and find Sphere. It may be large in comparison to your weapon so scale it down till it looks right. Now rename the Empty Game Object as: GameObject:MuzzelFlash.
 

This game object will serve as the object for the muzzle flash, and when we do the projectile version it will be the spawn point for bullets.

Next we will repeat this for the ejection report. So make a new Empty Game Object, add the Filter and Renderer and position it over the ejection port. Make sure to name the Empty Game Object: EmptyObject:EjectionPort. Here we ill play a sound for the bullet casing and spawning a bullet casing.

Next make two more Empty Game Objects one for the trigger to play a sound when the magazine is empty and one for the magazine when the player reloads. As always make sure to name them appropriately. Below is my set up model:


Next parent all the GameObjects to the weapon object:


Finally, add an AudioSource component to your model. Make sure that Loop and Play On Awake are unchecked.

Here i will cover the basics of a firearm, Raycast shooting, bullet count and display, and zoom. What follows after will be the script we will write appended for different needs. First thing add an audio source to the weapon model and un-check Loop and Play On Wake, this is where you will place you shooting sound. Lets make a new C# script and name it Firearm_Standard.cs

Raycasting is used in most games. Even when you see bullets fly those are for cinematic. They dont do anything other than give you the impression that its real. In reality, the damage your weapon does is like laser tag. When you press the fire button a ray is projected from your weapon. The ray is a class that has its own function to check if it hit anything. If it does it applies damage.

We need to declare some intrinsic classes and then some variables:

 Ray hitRay = new Ray ();
RaycastHit hit = new RaycastHit();

public float baseFOV;
Vector3 raycastLookAtPoint;
public Transform raycastSpawnPoint;

public float lastShot = -10.0f;
public float fireRate = 0.15f;

public int weaponDamage = 50;
public int bulletsInMag = 30;

Now under Update() we have to link the Ray to the camera so that the view port on the camera is used as the origin of the ray.
hitray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));

Next we need to take user input and set the condition that the weapon will fire when a button is pressed and the raycast is deployed but only when there are bullets in the weapon and the last shot has fired. Here we will also add bullet count and conditions for automatic fire. Interestingly if we dont have the conditions for auto fire we will still have auto fire, but it will be too rapid, unrealistic, noisy as the sound will mesh together, and if you are using projectiles there will be too many projectiles causing lag and in instances of collision the impact with objects will not be registered.


if(Input.GetButton(KeyCode.Mouse0))//Check for mouse button pressed
{
         if(bulletsInMag > 0)//now we are checking if there is enough bullets
         {
                 if(Time.time > fireRate + lastShot)
                 {
                     if (Physics.Raycast(hitray, out hit, 1000.0f))//if the ray has hit something at 1000 units
{
raycastLookAtPoint = hit.point;
raycastSpawnPoint.transform.LookAt(raycastLookAtPoint);
                      }
                                   
                         GetComponent<AudioSource>().Play();
                         lastShot = Time.time;

                         bulletsInMag--;
                     }
          }
}

Now we have to cover the zoom feature, this wont be looking down rails, but more like games in the 90s like GoldenEye, just a zoom feature. Under Start() we have to initialize the above variable: baseFOV = Camera.main.fieldOfView;

if(Input.GetButton ("Fire2"))
{
        Camera.main.fieldOfView = 25;
}
else
{
  Camera.main.fieldOfView = baseFOV;
}

You can experiment with Camera.main.fieldOfView = 25; by changing the value to zoom more or less in/out.

Projectile:

Now if you want to do projectile instead of raycast, we will first delete all the lines of code dealing with raycast. It would be best to start another script: Firearm_Standard_Projectile.cs

Next we need a bullet model if you have any assets you can use those, if not, a sphere works well or a capsule.

In place of the raycast we will actually instantiate a the bullet model and add force to it. First we need to declare a variable for the bullet model: public Rigidbody caliber556mm, next we need one for the location where the bullet will spawn from public Transform bulletSpawnPointBarrel, and finally the speed at which we will propel the bullet public float caliber556mmSpeed.

if(Input.GetKey(KeyCode.Mouse0))//If user has pressed the fire button
{
if(bulletsInMag > 0)
{
if(Time.time > fireRate + lastShot)
{

Rigidbody instance =  (Rigidbody)Instantiate(
                        caliber556mm,
                        bulletSpawnPointBarrel.position, 
                        bulletSpawnPointBarrel.rotation);
instance.AddForce(transform.forward * caliber556mmSpeed);

GetComponent<AudioSource>().Play();//play the shooting sound

lastShot = Time.time;

bulletsInMag--;//Deduct bullets from the magazine

Destroy(instance.gameObject, 1);//
   }
  }
}

So what we are doing is creating a temporary variable for the Rigidbody(because unity does not allow direct manifestation of the Rigidbody because when we delete the Rigidbody  we would delete the entire object from Unity. But by using the temporary variable we delete the variable not the model and then we create another and delete and so on and so forth.), and spawning it from the spawn point, then we AddForce() to the Rigidbody using the caliber556mmSpeed.  After about a second of our human time the temporary variable is deleted saving on memory.

Where as the raycast applies damage directly, the bullet model will need a script to be able to apply damage(we will cover that later). With that said make a new script and call it: Bullet_Dynamics.cs

In your Bullet_Dynamics script we will just use the intrinsic function OnCollisionEnter() and in it type: Destroy(gameObject);
This is a self referring statement since it is encapsulated with in this script on the bullet object.

Now add this script to your bullet model, also add a Rigidbody component to you model, change the values of the rigidbody to Mass: 0.09, Drag: 0, Angular Drag: 0.05. Its up to you if you wish to check the box for gravity, though i do for realism. Play around with the speed of the bullet under Firearm_Standard.cs and the gravity check box under Rigidbody.




Now make a new script and call it: Muzzle_Flash.
The script will basically work by taking a player input and playing a particle for like a fifth of a second. With that said we will need a variable for the particle that will serve as the muzzle flash, and another game object to clone the muzzle flash:

GameObject instaParticle;
public GameObject ParticlePrefabObject;

The instaParticle is a temporary variable to house the actual particle object. ParticlePrefabObject is the actual variable for the muzzle flash. Next we will nee a variable that will set the limit to how long the particle will play, this is because particles are animated:

public float aliveTime = 0.05f;

We need to link the Firearm_Standard or Firearm_Standard_Projectile script to the muzzle flash script to keep track of the bullets so the flash can be played if there are bullets to fire. To do this we need to make a public variable of that script: public Firearm_Standard bulletCounter

Now we need to develop out Update(). First thing we need to do is check if there are bullets in the weapon, if there are not then we cant play the flash, second we have to check for user input when the player fires. Unity has functions designed to get player input, and by default the "Fire" button is default Mouse1.  

if(bulletCounter.bulletsInMag > 0)
{
if(Input.GetButton("Fire1"))
{
     instaParticle = (GameObject)Instantiate 
                               (ParticlePrefabObject, transform.position, transform.rotation);
}
}

Now in the body of the inner if statment we will instantiate the particle. In Unity you cant just bring the particle up. You have to make a game object to hold the particle because the particle is not a game object, but particles are their own class. You are keeping the particle in a container that you can manipulate because the particle class does not have those variables that the game object has.

instaParticle = (GameObject)Instantiate(ParticlePrefabObject, transform.position, transform.rotation);

We are initializing the Game Object with the Particle Object. The transform.position and transform.rotation are the coordinates of the particle in the game world. This is to insure that the Particle Object stays in its location. Finally, when ever dealing with Objects that are used for one purpose or as bullets, enemies etc, its important to delete the object to save on memory.

Destroy (instaParticle, aliveTime);

This line says delete the Particle when the time is 0.05f. You can play around with the aliveTime to make the particle last longer or shorter.

Now attach the Muzzle_Flash.cs script to GameObject:MuzzleFlash. Here you will add the particle of your choosing under Particle Prefab Object field, link the Firearm_Standard to the Bullet Counter field. When you shoot the flash may play up, if so, rotate the GameObject:MuzzleFlash until it looks right.



Next script will be the ejection port. Its pretty similar to the muzzle flash. First we need a model for the shell. if you cant afford one or dont have one a simple cylinder is enough attach a rigidbody component to it.
Mass: 0.01
Drag: 0.2
Angular Drag: 0.05
Play around with the Use Gravity. Make a new script and call it EjectShell.cs

We need two Rigidbody variable ones for the shell the other as a temporary variable to be deleted:
public Rigidbody Cartridge;

private Rigidbody ejectShellTemp;

We also need a Transform for the location from where the shell will eject from:
public Transform Ejector;

And we need a float variable for the speed at which the shell will be ejected:
public float force = 2.34; The value is arbitrary. So, much like the flash script in the Update() we need to check if there are bullets and if the user pressed the fire button, then we create the temp variable with the position and rotation:

if (bulletCounterForShell.bulletsInMag > 0) 
{
if (Input.GetButton("Fire1"))    
{
ejectShellTemp = (Rigidbody)Instantiate 
                                                     (Cartridge, Ejector.position, Ejector.rotation); 
ejectShellTemp.AddForce (transform.forward * force);

GetComponent<AudioSource> ().Play ();
}
Destroy (ejectShellTemp, 0.05f);
}

But unlike the flash script we have two new lines:
ejectShellTemp.AddForce (transform.forward * force);  This line says the rigidbody created, add force to it at the speed stated and move it in a forward direction from its spawn point.

The other line GetConponent<AudioSource>().Play(); just plays the audio component that will be attached to the GameObject:EjectionPort;

Now attach the script to the GameObject:EjectionPort, make sure you have an audio component to that object, Loop and Play On Wake are un-checkd and you have assigned the shell dropping sound.
On this script you have to fill in the fields, The Ejector field is the GameObject:EjectionPort, the Cartridge field is shell model/prefab and Bullet Counter For Shell Field is the Firearm_Standard or Firearm_Standard_Projectile script.

To test this whole scene import the FPS controller provided by unity. Parent your weapon model with all the attachments to the Main Camera that comes with the FPS model. When you play the scene pay attention to the Bullets In Mag field in the Inspector part for the Firearm_Standard/_Projectile script. You should see the bullets deducting with each shot.

Now if you dont like that we can change it to display on screen. In the Firearm_Standard/_Projectile script add the following in the variable declaration section:
public Font displayOnScreen;

And the following you should add outside the Update() section but still inside the class declaration:

void OnGUI()
{

GUIStyle displayFont = new GUIStyle();
displayFont.font = displayOnScreen;//this works, changes the hud to Tinker Font.
displayFont.fontSize = 24;
displayFont.normal.textColor = Color.red;

GUI.Label(new Rect(900,500, 75,35),bulletsInMag.ToString(), displayFont);
}

What the above code is doing is creating a new GUIStyle, the designed of the font is being linked to the public Font displayOnScreen which you will assign your self. Then we are changing the font size and color, and finally we call the GUI.Label function to place the value of bullets at a particular location(900, 500), the .ToString() is a function that takes the integer value and converts it to a string value. Now under the Inspector part of this script assign the font style you like. There are some that are already build into unity.

All .cs Scripts so far.

Part two will include a flashlight, a laser sight and the magazine dynamics.

No comments:

Post a Comment