- Home /
How do I make it so that my bullet projectile for my gun does damage?
Hi, I am new to both Unity and C#. I used a 2 part tutorial to make a gun script, if needed the channel name is "Dave // Game Development." The tutorial's first episode has a projectile bullet-type script that was exactly what I needed for my game, however, I do not know how to make it so that the bullet actually does damage. I do have a damage script from Brackey's gun tutorial, however, it only works with Raycast shooting scripts, and in this projectile bullet script, Raycasts are not used to deal damage. The thing I need help with making it so that on collision, the bullet either damages a target if it hits a target and destroys itself right after or just destroys itself if it does not hit a target. I do not have any script for the bullet, but if I need to make the script for damaging on collisions on the bullet prefab itself, please let me know, Any help is appreciated! Let me know if you need any clarification. Here is the code:
using UnityEngine;
using TMPro;
public class ProjectileGunScript : MonoBehaviour
{
//bullet
public GameObject bullet;
//bullet force
public float shootForce, upwardForce;
//Gun stats
public int damage;
public float timeBetweenShooting, spread, reloadTime, timeBetweenShots;
public int magazineSize, bulletsPerTap;
public bool allowButtonHold;
int bulletsLeft, bulletsShot;
//Recoil
public Rigidbody playerRb;
public float recoilForce;
//bools
bool shooting, readyToShoot, reloading;
//Reference
public Camera fpsCam;
public Transform attackPoint;
public LayerMask whatIsEnemy;
//Graphics
public GameObject muzzleFlash;
public TextMeshProUGUI ammunitionDisplay;
//bug fixing :D
public bool allowInvoke = true;
private void Awake()
{
//make sure magazine is full
bulletsLeft = magazineSize;
readyToShoot = true;
}
private void Update()
{
MyInput();
//Set ammo display, if it exists :D
if (ammunitionDisplay != null)
ammunitionDisplay.SetText(bulletsLeft / bulletsPerTap + " / " + magazineSize / bulletsPerTap);
}
private void MyInput()
{
//Check if allowed to hold down button and take corresponding input
if (allowButtonHold) shooting = Input.GetKey(KeyCode.Mouse0);
else shooting = Input.GetKeyDown(KeyCode.Mouse0);
//Reloading
if (Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading) Reload();
//Reload automatically when trying to shoot without ammo
if (readyToShoot && shooting && !reloading && bulletsLeft <= 0) Reload();
//Shooting
if (readyToShoot && shooting && !reloading && bulletsLeft > 0)
{
//Set bullets shot to 0
bulletsShot = 0;
Shoot();
}
}
private void Shoot()
{
readyToShoot = false;
//Find the exact hit position using a raycast
Ray ray = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); //Just a ray through the middle of your current view
RaycastHit hit;
//check if ray hits something
Vector3 targetPoint;
if (Physics.Raycast(ray, out hit))
targetPoint = hit.point;
else
targetPoint = ray.GetPoint(75); //Just a point far away from the player
//Calculate direction from attackPoint to targetPoint
Vector3 directionWithoutSpread = targetPoint - attackPoint.position;
//Calculate spread
float x = Random.Range(-spread, spread);
float y = Random.Range(-spread, spread);
//Calculate new direction with spread
Vector3 directionWithSpread = directionWithoutSpread + new Vector3(x, y, 0); //Just add spread to last direction
//Instantiate bullet/projectile
GameObject currentBullet = Instantiate(bullet, attackPoint.position, Quaternion.identity); //store instantiated bullet in currentBullet
//Rotate bullet to shoot direction
currentBullet.transform.forward = directionWithSpread.normalized;
//Add forces to bullet
currentBullet.GetComponent<Rigidbody>().AddForce(directionWithSpread.normalized * shootForce, ForceMode.Impulse);
currentBullet.GetComponent<Rigidbody>().AddForce(fpsCam.transform.up * upwardForce, ForceMode.Impulse);
//Instantiate muzzle flash, if you have one
if (muzzleFlash != null)
Instantiate(muzzleFlash, attackPoint.position, Quaternion.identity);
bulletsLeft--;
bulletsShot++;
//Invoke resetShot function (if not already invoked), with your timeBetweenShooting
if (allowInvoke)
{
Invoke("ResetShot", timeBetweenShooting);
allowInvoke = false;
//Add recoil to player (should only be called once)
playerRb.AddForce(-directionWithSpread.normalized * recoilForce, ForceMode.Impulse);
}
//if more than one bulletsPerTap make sure to repeat shoot function
if (bulletsShot < bulletsPerTap && bulletsLeft > 0)
Invoke("Shoot", timeBetweenShots);
}
private void ResetShot()
{
//Allow shooting and invoking again
readyToShoot = true;
allowInvoke = true;
}
private void Reload()
{
reloading = true;
Invoke("ReloadFinished", reloadTime); //Invoke ReloadFinished function with your reloadTime as delay
}
private void ReloadFinished()
{
//Fill magazine
// bulletsLeft = magazineSize;
// reloading = false;
}
}
Answer by lovewessman · Sep 06, 2021 at 09:43 PM
If I have understood correctly, I think what I would do in this case is the following: First make sure that the target and the bullet prefab both have a collider component, and at least one of them has checked "Is Trigger". Doing this will allow you to use the OnTriggerEnter() functions, which will fire when a collision is detected between two objects. Also, add a script to the target if you haven't already done so that you can use later to take damage from a bullet if a collision occurs.
I would also add a tag to the Target gameobject, for example "Target" is fine. You can add tags to gameobjects by selecting them, looking in the inspector and then select Tag just under the name of the gameobject. There you can make new tags. This is just so that it will be easier to find references to the Target later via code.
Then what I would do is add a script to the bullet prefab that would look something like this:
public class Bullet : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// Check if collision is target via tag - needs target gameObjects to be tagged properly
if (other.CompareTag("Target"))
{
// Target has been hit - use script on target to deal damage to it
float dmg = 10;
other.GetComponent<NameOfTargetScriptHere>().TakeDamage(dmg);
// Damage has been dealt, can destroy self now
Destroy(gameObject);
}
else
{
// If we did collide, but it was something other than a target - destroy self
Destroy(gameObject);
}
}
}
And the Target script could just be something like this:
public class Target : MonoBehaviour
{
private float health = 50;
public void TakeDamage(float dmg)
{
health = health - dmg;
}
}
I think that should work, let me know if it doesn't work for some reason.
It seemed to be working, but I forgot to add a collider, so I added a box collider. There are a few problems. First of all, the way the gun script works is that the bullet prefab already exists as a child object inside of the player (a capsule in this case). The script then clones the bullets from an empty object on the barrel of the gun, which are the bullets that actually get shot out. The real prefab stays in place inside the capsule the whole time. This leads to the next problem, the bullet prefab being inside the player. The prefab itself gets destroyed preventing it from shooting to work, however, I tried other things such as moving the bullet to different locations outside of the player. This does make it so that the bullet doesn't immediately get destroyed. Weirdly, it doesn't consecutively get destroyed, simply sometimes it gets destroyed sometimes it doesn't, it doesn't have to be on a target tag. Re$$anonymous$$der, it is always the physical bullet prefab itself, not the clones that get shot out. it seems that the bullet itself takes the collision, but not the clone. It might be a problem with the projectile script itself, but I am not too sure. Also, thank you for the help. I would have never guessed to have to add a collider first to be able to use Is Trigger/OnTriggerEnter. One more thing, if the bullet is inside the player, and after it immediately gets destroyed, you cannot shoot anymore, no more clones get created, and this error pops up in Console "MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object." This appears when you shoot after the prefab immediately gets destroyed.
Aha! I see. The first thing I would do, is to use make the bullet a prefab that exists alongside your project's assets, by dragging the bullet gameobject into the assets folder, and then use the prefab asset as the reference to the public GameObject bullet
, instead of the bullet gameobject that's already "in the game", childed to the gun. If that makes sense. The reason why that would help you is because right now, since the reference to the bullet you are using is inside the game, with the bullet script attached to it will be destroyed whenever it touches something with a collider. Then next time when you try to instantiate a new bullet, the game tries to use the reference to the bullet but can't find it.
That's why I think using a reference to the bullet as a prefab asset rather than the bullet gameobject would fix this, because that will always exist in your assets folder and can be instantiated as much as you want. I hope that works and if it doesn't I'll keep helping you until it does!
Your answer
Follow this Question
Related Questions
Objects instantiate at prefab location instead of playtime location 1 Answer
Gun Rotation Sway 0 Answers
i need to mix this scripts, gun shoot 1 Answer
when float is less then zero, do action once 1 Answer
How to make my gun shoot down [C#] 0 Answers