0
\$\begingroup\$

I'm trying to make my main character aim at the closest enemy that he can see.

My issue is with the close range criterion taking priority. If the enemy is near the player but the player can't see him, I'd like the player to look for another enemy inside his range.

Screenshot showing ray checks against an enemy behind a wall

As you can see in the image, There is an enemy behind the wall. The player is not aiming at him because he can't see him. So far this is correct.

But the player is also ignoring the enemy in front of him because he's busy rejecting the one behind the wall.

What he should do is aim at the one in front of him, because he can see him and he is inside the player's range.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
public class clickme : MonoBehaviour
{

private NavMeshAgent mAgent;
public float mark, ToPoint;
public bool actives, seeEnemy;
private Transform target;
public float range, enemyDistance; // range is player eyes field.
public float speed = 5.0f;
public Transform aiming_point, playerEye;
private GameObject[] targets;

void Start()
{
    targets = GameObject.FindGameObjectsWithTag("enemy");
    target = targets[0].transform;
    seeEnemy = false;
}

void Update()
{

    if (mAgent.remainingDistance <= mAgent.stoppingDistance)
    {
        actives = false;
        if (actives == false && playerEye == true && target != null)
        {

            if (enemyDistance <= range)
            {
                Vector3 dir = target.position - transform.position;
                Quaternion lookRotation = Quaternion.LookRotation(dir);
                Vector3 rotation = Quaternion.Lerp(transform.rotation, lookRotation, Time.deltaTime * speed).eulerAngles;
                transform.rotation = Quaternion.Euler(0f, rotation.y, 0.0f);
            }

        }
        else  {

        }

    }
    else   {
        actives = true;

    }

    updateTarget();

} // end update

void updateTarget()
{
    GameObject[] enemies = GameObject.FindGameObjectsWithTag("enemy");
    float shortesDistance = Mathf.Infinity;
    GameObject nearestEnemy = null;
    foreach (GameObject enemy in enemies)
    {
        float distanceToEnemy = Vector3.Distance(transform.position, enemy.transform.position);
        if (distanceToEnemy < shortesDistance)
        {
            shortesDistance = distanceToEnemy;
            nearestEnemy = enemy;

            RaycastHit hit; // can I see my closent enemy or not ?
            if (Physics.Linecast(playerEye.position, nearestEnemy.transform.position, out hit))
            {
                Debug.DrawLine(playerEye.position, nearestEnemy.transform.position, Color.blue, range);

                if (hit.collider.tag == "enemy")
                {
                    seeEnemy = true;
                    print(" I can see enemy !");

                }
                else
                {
                    seeEnemy = false;
                    print("No enemy here!");

                }
            }
        }

    } // end foreach


    if (nearestEnemy != null && shortesDistance <= range && seeEnemy == true)
    {
        target = nearestEnemy.transform;
        aiming_point = target.transform.Find("aim");
        enemyDistance = Vector3.Distance(transform.position, target.transform.position);
    }
    else
    {
        target = null;
        aiming_point = null;
        seeEnemy = false;
        enemyDistance = 0;
    }

} // end target



}
\$\endgroup\$
5
\$\begingroup\$

There are a few problems here.

First, don't do this every frame in Update:

GameObject[] enemies = GameObject.FindGameObjectsWithTag("enemy");

The find methods are generally slow. It's OK to use for initialization, but if you just want a list of all enemies - which is likely to be very similar to the list from last frame - then store that list somewhere and update it when you add/kill an enemy.

Or, if you only care about enemies in your vision cone / range, then you should instead use a physics query to find them, like Physics.OverlapSphere or OverlapBox (or their NonAlloc versions for efficiency). You can put all the enemies on a specific physics layer, then filter the physics query to check only that layer, in a bound tightly fitted to your shooting range. This will minimize the number of objects you have to iterate over to just the ones that are potentially interesting.

Now, your main logic problem is just doing your visibility check and target update in the wrong order:

if (distanceToEnemy < shortestDistance)
{
    shortestDistance = distanceToEnemy;
    nearestEnemy = enemy;

    RaycastHit hit;
    if (Physics.Linecast(playerEye.position, nearestEnemy.transform.position, out hit))
    {...

This translates to:

  1. Choose the nearest enemy.

  2. Then, if you can see that enemy...

But what you want is:

  1. Choose the nearest enemy that you can see.

ie. we don't want to consider candidate enemies who are nearby but not visible. So...

if (distanceToEnemy < shortestDistance)
{
    // DO NOT immediately set a new shortestDistance here,
    // because we don't yet know whether this is an enemy we can see.

    RaycastHit hit;
    if (Physics.Linecast(playerEye.position, enemy.transform.position, out hit))
    {
        if(hit.collider.CompareTag("enemy") 
        {
            // NOW we know this is not just a nearby enemy,
            // but a nearby VISIBLE enemy, and thus worth tracking.
            shortestDistance = distanceToEnemy;
            nearestVisibleEnemy = enemy;
            ...
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.