0
votes

I am having a problem with my RTS game where my enemy units will not attack any base buildings I create after the level starts. They go to attack every other building that was there when the level starts, but none of the built ones.

There is a list being set up of the closest targets to units and they will go to attack their closest target, but any newly instantiated buildings or units, don't get attacked for some reason.

The function DecideWhatToDo() is called on all units in the WorldObject script when they are not doing anything. It then calls FindNearbyObjects() from the WorkManager script.

Everything works up until new units and buildings are created, has anyone experienced this kind of problem before?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using RTS;

public class WorldObject : MonoBehaviour {

    BoxCollider boxCollider;
    bool isDead;
    bool isSinking;

    public string objectName;
    public Texture2D buildImage;
    public int cost, sellValue, maxHitPoints;
    public float hitPoints;
    public virtual bool IsActive { get { return true; } }
    public float weaponRange = 10.0f;
    public float weaponRechargeTime = 1.0f;
    public float weaponAimSpeed = 1.0f;
    public AudioClip attackSound, selectSound, useWeaponSound;
    public float attackVolume = 1.0f, selectVolume = 1.0f, useWeaponVolume = 1.0f;
    public int ObjectId { get; set; }
    public float detectionRange = 20.0f;
    public GameObject explosionPrefab, splat;

    protected NavMeshAgent agent;
    protected AudioElement audioElement;
    protected Animator anim;
    protected List<WorldObject> nearbyObjects;
    protected Rect playingArea = new Rect(0.0f, 0.0f, 0.0f, 0.0f);
    protected Player player;
    protected string[] actions = { };
    protected bool currentlySelected = false;
    protected Bounds selectionBounds;
    protected GUIStyle healthStyle = new GUIStyle();
    protected float healthPercentage = 1.0f;
    protected WorldObject target = null;
    protected bool attacking = false;
    protected bool movingIntoPosition = false;
    protected bool aiming = false;

    private List<Material> oldMaterials = new List<Material>();
    private float currentWeaponChargeTime;
    //we want to restrict how many decisions are made to help with game performance
    //the default time at the moment is a tenth of a second
    private float timeSinceLastDecision = 0.0f, timeBetweenDecisions = 0.1f;

    protected virtual void Awake()
    {
        anim = GetComponent<Animator>();
        boxCollider = GetComponent<BoxCollider>();
        selectionBounds = ResourceManager.InvalidBounds;
        CalculateBounds();
    }

    protected virtual void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        SetPlayer();
        if (player) SetTeamColor();
        InitialiseAudio();
    }

    protected virtual void Update()
    {
        if (isSinking)
        {
            this.transform.Translate(-Vector3.up * 2.5f * Time.deltaTime);
        }
        if (ShouldMakeDecision()) DecideWhatToDo();
        currentWeaponChargeTime += Time.deltaTime;
        if (attacking && !movingIntoPosition && !aiming)
        {

            PerformAttack();
        }
    }

    /**
 * A child class should only determine other conditions under which a decision should
 * not be made. This could be 'harvesting' for a harvester, for example. Alternatively,
 * an object that never has to make decisions could just return false.
 */
    protected virtual bool ShouldMakeDecision()
    {
        if (!attacking && !movingIntoPosition && !aiming)
        {
            //we are not doing anything at the moment
            if (timeSinceLastDecision > timeBetweenDecisions)
            {
                timeSinceLastDecision = 0.0f;
                Debug.Log("");
                return true;
            }
            timeSinceLastDecision += Time.deltaTime;
        }


        return false;
    }

    protected virtual void DecideWhatToDo()
    {
        //determine what should be done by the world object at the current point in time
        Vector3 currentPosition = transform.position;
        nearbyObjects = WorkManager.FindNearbyObjects(currentPosition, detectionRange);

        if (CanAttack())
        {
            List<WorldObject> enemyObjects = new List<WorldObject>();
            foreach (WorldObject nearbyObject in nearbyObjects)
            {
                Resource resource = nearbyObject.GetComponent<Resource>();
                if (resource) continue;
                if (nearbyObject.GetPlayer() != player) enemyObjects.Add(nearbyObject);
            }
            WorldObject closestObject = WorkManager.FindNearestWorldObjectInListToPosition(enemyObjects, currentPosition);
            if (closestObject)
            {
                attacking = true;
                //agent.isStopped = true;
                BeginAttack(closestObject);
            }
        }
    }

    public Player GetPlayer()
    {
        return player;
    }

    protected virtual void OnGUI()
    {
        if (currentlySelected && !ResourceManager.MenuOpen) DrawSelection();
    }

    protected virtual void InitialiseAudio()
    {
        List<AudioClip> sounds = new List<AudioClip>();
        List<float> volumes = new List<float>();
        if (attackVolume < 0.0f) attackVolume = 0.0f;
        if (attackVolume > 1.0f) attackVolume = 1.0f;
        sounds.Add(attackSound);
        volumes.Add(attackVolume);
        if (selectVolume < 0.0f) selectVolume = 0.0f;
        if (selectVolume > 1.0f) selectVolume = 1.0f;
        sounds.Add(selectSound);
        volumes.Add(selectVolume);
        if (useWeaponVolume < 0.0f) useWeaponVolume = 0.0f;
        if (useWeaponVolume > 1.0f) useWeaponVolume = 1.0f;
        sounds.Add(useWeaponSound);
        volumes.Add(useWeaponVolume);
        audioElement = new AudioElement(sounds, volumes, objectName + ObjectId, this.transform);
    }

    public void SetPlayer()
    {
        player = transform.root.GetComponentInChildren<Player>();
    }

    public bool IsOwnedBy(Player owner)
    {
        if (player && player.Equals(owner))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public void CalculateBounds()
    {
        selectionBounds = new Bounds(transform.position, Vector3.zero);
        foreach (Renderer r in GetComponentsInChildren<Renderer>())
        {
            selectionBounds.Encapsulate(r.bounds);
        }
    }

    //!!!!!MULTI SELECTION!!!!!

    public virtual void SetSelection(bool selected, Rect playingArea)
    {
        currentlySelected = selected;
        if (selected)
        {
            if (audioElement != null) audioElement.Play(selectSound);
            this.playingArea = playingArea;
        }
        CalculateBounds();
    }

    //!!!!!MULTI SELECTION!!!!!

    public Bounds GetSelectionBounds()
    {
        return selectionBounds;
    }

    public string[] GetActions()
    {
        return actions;
    }

    public void SetColliders(bool enabled)
    {
        Collider[] colliders = GetComponentsInChildren<Collider>();
        foreach (Collider collider in colliders) collider.enabled = enabled;
    }

    public void SetTransparentMaterial(Material material, bool storeExistingMaterial)
    {
        if (storeExistingMaterial) oldMaterials.Clear();
        Renderer[] renderers = GetComponentsInChildren<Renderer>();
        foreach (Renderer renderer in renderers)
        {
            if (storeExistingMaterial) oldMaterials.Add(renderer.material);
            renderer.material = material;
        }
    }

    public void RestoreMaterials()
    {
        Renderer[] renderers = GetComponentsInChildren<Renderer>();
        if (oldMaterials.Count == renderers.Length)
        {
            for (int i = 0; i < renderers.Length; i++)
            {
                renderers[i].material = oldMaterials[i];
            }
        }
    }

    public void SetPlayingArea(Rect playingArea)
    {
        this.playingArea = playingArea;
    }

    public virtual void SetHoverState(GameObject hoverObject)
    {
        //only handle input if owned by a human player and currently selected
        if (player && player.human && currentlySelected)
        {
            //something other than the ground is being hovered over
            if (hoverObject.name != "Ground")
            {
                Player owner = hoverObject.transform.root.GetComponent<Player>();
                Unit unit = hoverObject.transform.parent.GetComponent<Unit>();
                Building building = hoverObject.transform.parent.GetComponent<Building>();
                if (owner)
                { //the object is owned by a player
                    if (owner.username == player.username) player.hud.SetCursorState(CursorState.Select);
                    else if (CanAttack()) player.hud.SetCursorState(CursorState.Attack);
                    else player.hud.SetCursorState(CursorState.Select);
                }
                else if (unit || building && CanAttack()) player.hud.SetCursorState(CursorState.Attack);
                else player.hud.SetCursorState(CursorState.Select);
            }
        }
    }

    public virtual bool CanAttack()
    {
        //default behaviour needs to be overidden by children
        return false;
    }

    public virtual void PerformAction(string actionToPerform)
    {
        //it is up to children with specific actions to determine what to do with each of those actions
    }

    public virtual void MouseClick(GameObject hitObject, Vector3 hitPoint, Player controller)
    {

        //only handle input if currently selected
        if (currentlySelected && hitObject && hitObject.name != "Ground")
        {
            WorldObject worldObject = hitObject.transform.parent.GetComponent<WorldObject>();
            //clicked on another selectable object
            if (worldObject)
            {
                Resource resource = hitObject.transform.parent.GetComponent<Resource>();
                if (resource && resource.isEmpty()) return;
                Player owner = hitObject.transform.root.GetComponent<Player>();
                if (owner)
                { //the object is controlled by a player
                    if (player && player.human)
                    { //this object is controlled by a human player
                      //start attack if object is not owned by the same player and this object can attack, else select
                        if (player.username != owner.username && CanAttack())
                        {
                            BeginAttack(worldObject);
                        }
                        else ChangeSelection(worldObject, controller);
                    }
                    else ChangeSelection(worldObject, controller);
                }
                else ChangeSelection(worldObject, controller);
            }
        }
    }

    protected virtual void BeginAttack(WorldObject target)
    {
        //if (audioElement != null) audioElement.Play(attackSound);
        this.target = target;
        if (TargetInRange())
        {
            anim.SetBool("Attacking", true);
            attacking = true;
            PerformAttack();
        }
        else AdjustPosition();
    }

    protected void SetTeamColor()
    {
        TeamColor[] teamColors = GetComponentsInChildren<TeamColor>();
        foreach (TeamColor teamColor in teamColors) teamColor.GetComponent<Renderer>().material.color = player.teamColor;
    }

    protected virtual void DrawSelectionBox(Rect selectBox)
    {
        GUI.Box(selectBox, "");
        CalculateCurrentHealth(0.35f, 0.65f);
        DrawHealthBar(selectBox, "");
    }

    protected virtual void CalculateCurrentHealth(float lowSplit, float highSplit)
    {
        healthPercentage = (float)hitPoints / (float)maxHitPoints;
        if (healthPercentage > highSplit) healthStyle.normal.background = ResourceManager.HealthyTexture;
        else if (healthPercentage > lowSplit) healthStyle.normal.background = ResourceManager.DamagedTexture;
        else healthStyle.normal.background = ResourceManager.CriticalTexture;
    }

    protected void DrawHealthBar(Rect selectBox, string label)
    {
        healthStyle.padding.top = -20;
        healthStyle.fontStyle = FontStyle.Bold;
        GUI.Label(new Rect(selectBox.x, selectBox.y - 7, selectBox.width * healthPercentage, 5), label, healthStyle);
    }

    protected virtual void AimAtTarget()
    {
        aiming = true;

        //this behaviour needs to be specified by a specific object
    }

    private void ChangeSelection(WorldObject worldObject, Player controller)
    {
        //this should be called by the following line, but there is an outside chance it will not
        SetSelection(false, playingArea);
        if (controller.SelectedObject) controller.SelectedObject.SetSelection(false, playingArea);
        controller.SelectedObject = worldObject;
        worldObject.SetSelection(true, controller.hud.GetPlayingArea());
    }

    private void DrawSelection()
    {
        GUI.skin = ResourceManager.SelectBoxSkin;
        Rect selectBox = WorkManager.CalculateSelectionBox(selectionBounds, playingArea);
        //Draw the selection box around the currently selected object, within the bounds of the playing area
        GUI.BeginGroup(playingArea);
        DrawSelectionBox(selectBox);
        GUI.EndGroup();
    }

    private bool TargetInRange()
    {
        Vector3 targetLocation = target.transform.position;
        Vector3 direction = targetLocation - transform.position;
        if (direction.sqrMagnitude < weaponRange * weaponRange)
        {
            return true;
        }
        return false;
    }

    private void AdjustPosition()
    {

        Unit self = this as Unit;
        if (self)
        {
            movingIntoPosition = true;
            Vector3 attackPosition = FindNearestAttackPosition();
            self.StartMove(attackPosition);
            attacking = true;
        }
        else
        {

            attacking = false;
        }
    }

    private Vector3 FindNearestAttackPosition()
    {
        Vector3 targetLocation = target.transform.position;
        Vector3 direction = targetLocation - transform.position;
        float targetDistance = direction.magnitude;
        float distanceToTravel = targetDistance - (0.9f * weaponRange);
        return Vector3.Lerp(transform.position, targetLocation, distanceToTravel / targetDistance);
    }

    private void PerformAttack()
    {
        if (!target)
        {
            attacking = false;
            anim.SetBool("Attacking", false);
            anim.SetBool("IsRunning", false);
            return;
        }
        if (!TargetInRange())
        {
            AdjustPosition();
        }
        else if (!TargetInFrontOfWeapon())
        {
            AimAtTarget();
        }
        else if (ReadyToFire())
        {
            //attacking = true;
            UseWeapon();
        }
        //if (TargetInRange() && (attacking = true))
        //{
        //    AdjustPosition();
        //}
    }

    private bool TargetInFrontOfWeapon()
    {
        Vector3 targetLocation = target.transform.position;
        Vector3 direction = targetLocation - transform.position;
        if (direction.normalized == transform.forward.normalized) return true;
        else return false;
    }

    private bool ReadyToFire()
    {
        if (currentWeaponChargeTime >= weaponRechargeTime)
        {

            return true;
        }
        return false;
    }

    protected virtual void UseWeapon()
    {

        if (audioElement != null && Time.timeScale > 0) audioElement.Play(useWeaponSound);
        currentWeaponChargeTime = 0.0f;
        //this behaviour needs to be specified by a specific object
    }

    public void TakeDamage(float damage)
    {
        //GameObject.Instantiate(impactVisual, target.transform.position, Quaternion.identity);
        hitPoints -= damage;
        if (hitPoints <= 0)
        {
            Instantiate(explosionPrefab, transform.position + new Vector3(0, 5, 0), Quaternion.identity);

            Instantiate(splat, transform.position, Quaternion.identity);

            Destroy(gameObject);
        }
    }
}

using UnityEngine;
using System.Collections.Generic;

namespace RTS
{
    public static class WorkManager
    {


        public static Rect CalculateSelectionBox(Bounds selectionBounds, Rect playingArea)
        {
            //shorthand for the coordinates of the centre of the selection bounds
            float cx = selectionBounds.center.x;
            float cy = selectionBounds.center.y;
            float cz = selectionBounds.center.z;
            //shorthand for the coordinates of the extents of the selection bounds
            float ex = selectionBounds.extents.x;
            float ey = selectionBounds.extents.y;
            float ez = selectionBounds.extents.z;

            //Determine the screen coordinates for the corners of the selection bounds
            List<Vector3> corners = new List<Vector3>();
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx + ex, cy + ey, cz + ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx + ex, cy + ey, cz - ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx + ex, cy - ey, cz + ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx - ex, cy + ey, cz + ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx + ex, cy - ey, cz - ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx - ex, cy - ey, cz + ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx - ex, cy + ey, cz - ez)));
            corners.Add(Camera.main.WorldToScreenPoint(new Vector3(cx - ex, cy - ey, cz - ez)));

            //Determine the bounds on screen for the selection bounds
            Bounds screenBounds = new Bounds(corners[0], Vector3.zero);
            for (int i = 1; i < corners.Count; i++)
            {
                screenBounds.Encapsulate(corners[i]);
            }

            //Screen coordinates start in the bottom left corner, rather than the top left corner
            //this correction is needed to make sure the selection box is drawn in the correct place
            float selectBoxTop = playingArea.height - (screenBounds.center.y + screenBounds.extents.y);
            float selectBoxLeft = screenBounds.center.x - screenBounds.extents.x;
            float selectBoxWidth = 2 * screenBounds.extents.x;
            float selectBoxHeight = 2 * screenBounds.extents.y;

            return new Rect(selectBoxLeft, selectBoxTop, selectBoxWidth, selectBoxHeight);
        }

        public static GameObject FindHitObject(Vector3 origin)
        {
            Ray ray = Camera.main.ScreenPointToRay(origin);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, ResourceManager.RayCastLimit)) return hit.collider.gameObject;
            return null;
        }

        public static Vector3 FindHitPoint(Vector3 origin)
        {
            Ray ray = Camera.main.ScreenPointToRay(origin);
            RaycastHit hit;
            Debug.DrawRay(ray.origin, ray.direction * ResourceManager.RayCastLimit, Color.yellow);
            if (Physics.Raycast(ray, out hit, ResourceManager.RayCastLimit)) return hit.point;
            return ResourceManager.InvalidPosition;
        }

        public static List<WorldObject> FindNearbyObjects(Vector3 position, float range)
        {
            Collider[] hitColliders = Physics.OverlapSphere(position, range);
            HashSet<int> nearbyObjectIds = new HashSet<int>();
            List<WorldObject> nearbyObjects = new List<WorldObject>();
            for (int i = 0; i < hitColliders.Length; i++)
            {
                Transform parent = hitColliders[i].transform.parent;
                if (parent)
                {
                    WorldObject parentObject = parent.GetComponent<WorldObject>();
                    if (parentObject && !nearbyObjectIds.Contains(parentObject.ObjectId))
                    {

                            nearbyObjectIds.Add (parentObject.ObjectId);
                            nearbyObjects.Add (parentObject);

                    }
                }
            }
            return nearbyObjects;
        }

        public static WorldObject FindNearestWorldObjectInListToPosition(List<WorldObject> objects, Vector3 position)
        {
            if (objects == null || objects.Count == 0) return null;
            WorldObject nearestObject = objects[0];
            float sqrDistanceToNearestObject = Vector3.SqrMagnitude(position - nearestObject.transform.position);
            for (int i = 1; i < objects.Count; i++)
            {
                float sqrDistanceToObject = Vector3.SqrMagnitude(position - objects[i].transform.position);
                if (sqrDistanceToObject < sqrDistanceToNearestObject)
                {
                    sqrDistanceToNearestObject = sqrDistanceToObject;
                    nearestObject = objects[i];
                }
            }
            return nearestObject;
        }
    }
}
1
enemy not attacking base buildings that are built UNITY not be a downy, but this is actually not a programming question, its a business logic question, the problem you are having has nothing to do with enemies and building, it comes down to programming. I just say this as the title has 0 benefit to future readers. tip is if you cant explain in code terms whats is going wrong, then you probably haven't explored your problem enough and break pointed enough. Just saying - TheGeneral
How do you create new buildings? How buildings and WorldObjects are related? - Mikko Koivisto

1 Answers

0
votes

The first thing you need to check is if inside

FindNearbyObjects()

The variable

hitColliders = Physics.OverlapSphere(position, range);

Changes it's size when you instantiate new buildings. In case that is not the case, it may be because you are not adding colliders to the buildings when you instantiate them.

However, I think there are better ways for you to detect buildings without using this Physics.OverlapSphere(), which may be expensive in performance. If I were you I will would give a special tag to the buildings (the original and the instantiated ones) and I will use that in my logic to detect them. So they will be potential targets in your method:

DecideWhatToDo()

Here you can read about how to tag the GameObjects:

https://docs.unity3d.com/Manual/Tags.html