0
\$\begingroup\$

I'm making a 2D game with Unity. I have a patrol enemy that has to shoot bullets. The problem is that the bullets are being spawned from a random point at the middle of the scene, instead of bulletSpawnPos (an empty game object which is a child of the pistol). I also want the enemy to shoot whenever the player is near. But it seems like all the code does is to shoot towards it, no matter if it's near or not. In general I want to solve these two problems that are pretty much related to each other:

  1. Bullets should be spawn frombulletSpawnPos.
  2. Enemy should start shooting whenever the player is near (maybe by triggering something).

Pistol script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Pistol : MonoBehaviour
{
    [SerializeField] GameObject bullet;
    [SerializeField] Transform bulletSpawnPos;
    private bool isShooting;
    [SerializeField] private float shootDistance = 5f;
    [SerializeField] private float shootDelay = .5f;
    private Enemy enemy; 

    void Start()
    {
        enemy = transform.parent.GetComponent<Enemy>();
        InvokeRepeating("Shoot", 1f, shootDelay);
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
    }

    private void FixedUpdate()
    {
        //Detecting hits to the right 
        RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.right, shootDistance);
        //making sure we hit something and it is the player
        if (hit.collider != null && hit.collider.tag.Equals("Player"))
        {
            //We only want to start repeated shooting in case we do not shoot yet
            if (!isShooting) InvokeRepeating("Shoot", 0f, shootDelay);
            isShooting = true;
        }
        else
        {
            //Stopping ongoing shooting
            if (isShooting) CancelInvoke("Shoot");
            isShooting = false;
        }
    }
    void Shoot()
    {
        GameObject b = Instantiate(bullet);
        b.GetComponent<Bullet>().StartShoot(enemy.IsFacingLeft());
        b.transform.position = bulletSpawnPos.transform.position;
    }
}

Bullet script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    [SerializeField] float speed;
    [SerializeField] int damage;
    [SerializeField] GameObject bullet;
    GameObject Enemy;
    float timeToDestroy = 3;
    
     NewPlayer target;
    Vector2 moveDirection;

    void Start()
    {
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
        target = GameObject.FindObjectOfType<NewPlayer>();
        moveDirection = (target.transform.position - transform.position).normalized * speed;
        rb.velocity = new Vector2(moveDirection.x, moveDirection.y);
        Destroy(gameObject, 3f);
    }

    public void StartShoot(bool isFacingLeft)
    {
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
        if (isFacingLeft)
        {
            rb.velocity = new Vector2(-speed, 0);
        }
        else
        {
            rb.velocity = new Vector2(speed, 0);
        }

        Destroy(gameObject, timeToDestroy);
    }

    private void OnTriggerEnter2D(Collider2D collision)
     {
         if (collision.gameObject.name.Equals ("Player"))
         {
             Destroy(gameObject);
         }
     } 
}

Enemy script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : PhysicsObject
{
    [Header("Attributes")]
    [SerializeField] private int attackPower = 10;
    private int direction = 1;
    public int health = 100;
    private int maxHealth = 100;
    [SerializeField] private float maxSpeed;
    [SerializeField] private LayerMask rayCastLayerMask; //Which layer do we want the raycast to interact with?
    [SerializeField] private Vector2 rayCastOffset; //Offset from the center of the raycast origin
    [SerializeField] private float rayCastLength = 2;
    bool isFacingLeft;
    public bool IsFacingLeft()
    {
        return isFacingLeft;
    }

    [Header("References")]
    private RaycastHit2D rightLedgeRaycastHit;
    private RaycastHit2D leftLedgeRaycastHit;
    private RaycastHit2D rightWallRaycastHit;
    private RaycastHit2D leftWallRaycastHit;

    // Update is called once per frame
    void Update()
    {
        targetVelocity = new Vector2(maxSpeed * direction, 0);

        if (targetVelocity.x < -.01)
        {
            transform.localScale = new Vector2(-1, 1);
            isFacingLeft = true;
        }
        else if (targetVelocity.x > .01)
        {
            transform.localScale = new Vector2(1, 1);
            isFacingLeft = false;
        }

        //Check for right ledge!
        rightLedgeRaycastHit = Physics2D.Raycast(new Vector2(transform.position.x + rayCastOffset.x, transform.position.y + rayCastOffset.y), Vector2.down, rayCastLength);
        Debug.DrawRay(new Vector2(transform.position.x + rayCastOffset.x, transform.position.y + rayCastOffset.y), Vector2.down * rayCastLength, Color.blue);
        if (rightLedgeRaycastHit.collider == null) direction = -1;

        //Check for left ledge!
        leftLedgeRaycastHit = Physics2D.Raycast(new Vector2(transform.position.x - rayCastOffset.x, transform.position.y + rayCastOffset.y), Vector2.down, rayCastLength);
        Debug.DrawRay(new Vector2(transform.position.x - rayCastOffset.x, transform.position.y + rayCastOffset.y), Vector2.down * rayCastLength, Color.green);
        if (leftLedgeRaycastHit.collider == null) direction = 1;

        //Check for right wall!
        rightWallRaycastHit = Physics2D.Raycast(new Vector2(transform.position.x, transform.position.y), Vector2.right, rayCastLength, rayCastLayerMask);
        Debug.DrawRay(new Vector2(transform.position.x, transform.position.y), Vector2.right * rayCastLength, Color.red);
        if (rightWallRaycastHit.collider != null) direction = -1;

        //Check for left wall!
        leftWallRaycastHit = Physics2D.Raycast(new Vector2(transform.position.x, transform.position.y), Vector2.left, rayCastLength, rayCastLayerMask);
        Debug.DrawRay(new Vector2(transform.position.x, transform.position.y), Vector2.left * rayCastLength, Color.magenta);
        if (leftWallRaycastHit.collider != null) direction = 1;

        //If health < 0, destroy me
        if (health <= 0)
        {
            Destroy(gameObject);
        }
    }


    //If I collide with the player, hurt the player (health is going to decrease, update the UI)
    void OnCollisionEnter2D(Collision2D col)
    {
        if (col.gameObject == NewPlayer.Instance.gameObject)
        {
            //Hurt the player, then update the UI!
            NewPlayer.Instance.health -= attackPower;
            NewPlayer.Instance.UpdateUI();
        }
    }
}

This is the pistol inspector window.

enter image description here

Enemy object structure

\$\endgroup\$
5
  • \$\begingroup\$ Do you get any errors in your console? \$\endgroup\$
    – DMGregory
    Jan 26, 2021 at 23:44
  • \$\begingroup\$ Yes, I get a Null Reference error which is related to b.GetComponent<Bullet ().StartShoot(enemy.IsFacingLeft()); line in pistol script. \$\endgroup\$
    – Ali
    Jan 27, 2021 at 0:12
  • \$\begingroup\$ That's your problem. A null reference exception aborts your script before it can get to the line that moves the bullet to your desired position. Fix that error first, by populating your enemy variable, then ask here if you're still having trouble. \$\endgroup\$
    – DMGregory
    Jan 27, 2021 at 0:14
  • \$\begingroup\$ Okay, so this is the root of the problem. What do you mean by populating the enemy variable? \$\endgroup\$
    – Ali
    Jan 27, 2021 at 0:27
  • \$\begingroup\$ You have a private Enemy enemy; that you have never assigned a value into (this should generate a compiler warning to alert you of this). You need to assign a value to this variable before you can use it. \$\endgroup\$
    – DMGregory
    Jan 27, 2021 at 0:49

1 Answer 1

0
\$\begingroup\$

Assuming you have your whole enemy structured like this:

Enemy Pistol Spawn

Your pistol script is on the Pistol part, you need to access the parent if you want to get it the Enemy with GetComponent. GetComponent works always from the element you are currently on, if it is a child of Enemy, you need to ask the parent for the component. You can as well make the enemy variable public and assign it in the editor.

enemy = transform.parent.GetComponent<Enemy>();

Make sure that your Enemy is at 0/0/0 to avoid weird bugs later if you spawn them.

To address your shooting with a distance check. One option would be to use a Raycast (basically a line that you can check if it hits something). Since you can set how far it goes, you can define a distance check with it quite easily. This is added to your Pistol script. Try to adjust it yourself to make it work when the Enemy is facing left. Make sure that your Player has a collider2D and is tagged as Player.

[SerializeField] private float shootDistance = 5f;
private void FixedUpdate()
{
    //Detecting hits to the right 
    RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.right, shootDistance);
    //making sure we hit something and it is the player
    if (hit.collider != null && hit.collider.tag.Equals("Player"))
    {
        //We only want to start repeated shooting in case we do not shoot yet
        if (!isShooting) InvokeRepeating("Shoot", 0f, shootDelay);
        isShooting = true;
    }
    else
    {
        //Stopping ongoing shooting
        if (isShooting) CancelInvoke("Shoot");
        isShooting = false;
    }
}

The InvokeRepeating is taking care of the delay between each firing. As long as the player stands in front of the Enemy (currently to his right only), he would fire.

\$\endgroup\$
4
  • \$\begingroup\$ Thank you for your answer. This solved the spawn issue. Also, the enemy shooting is working. I just want to address a few things. First, my enemy structure is pretty much like that but the only difference is that the enemy is an empty object which has other body parts as its children. I just wanted to inform you about this in case if it's important (I also added an image of this). \$\endgroup\$
    – Ali
    Jan 27, 2021 at 18:04
  • \$\begingroup\$ Second, I realized that the enemy can shoot in both direction when the game starts but after a few seconds it does what it should for now (just shooting to the right whenever it collides with the player). I just want to know why is this happening? Also, about facing left, should I add a new parameter to the RaycastHit2D? \$\endgroup\$
    – Ali
    Jan 27, 2021 at 18:08
  • \$\begingroup\$ I didn't delete that from start function but I after commenting that line, I realized that the enemy doesn't shoot in both directions anymore. So I can say this fixed the problem (except the fact that it doesn't shoot to the left side). Also, why I would need a Vector2 for that? My game is 2D and the characters can't move up and down. Considering that, do I still need to change that bool? \$\endgroup\$
    – Ali
    Jan 27, 2021 at 19:16
  • \$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$
    – Zibelas
    Jan 27, 2021 at 19:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .