News Forums RAIN General Discussion and Troubleshooting Attack harness multitargeting?

This topic contains 18 replies, has 3 voices, and was last updated by  Sigil 9 months ago.

Viewing 15 posts - 1 through 15 (of 19 total)
  • Author
    Posts
  • #39423

    CrispyArrow
    Participant

    I am making a top-down ish rts style game, in which enemy units (zombies) will go down their respective waypoint route. I am using the Attack Harness script providded by the attack harness example that was posted on this forum, it works great with only 1 target, but multitargeting seems to go completely wrong.

    To be specific, i have attack harness put on gamobjects in my scene called towers (think of a tower defense style game), making the enemy units move to that tower and attack it is no problem. but there is 1 single issue. once the “max attackers” on the attack harness is filled, other enemies that see the tower they will not get closer, but they will attack nontheless (from a distance). obviously this is not what’s intended. What I want the enemy objects to do is to recognize whether the attack harness of the object that they are seeing iss filled. if it’s filled, they should move on until they find another attack harness from another tower. if the attack harness is not filled, then they should go to it and attack it (as it is intended to do)

    Thanks in advance,
    An dif you need to see code, example images, thhe behaviour trees or anything, just let me know

    • This topic was modified 11 months, 1 week ago by  CrispyArrow.
    #39426

    Sigil
    Keymaster

    It’s been awhile since I’ve worked with the attack harness, I think there was both the harness script and an additional custom action that was used to make it work. Could you post both of those here and we can see about modifying it for your needs.

    #39427

    CrispyArrow
    Participant

    It was the attack harness script, the AIchoosePosition custom action and the AIupdatePosition custom action. Here they are:

    AttackHarness.cs

    using UnityEngine;
    using System.Collections.Generic;
    using RAIN.Navigation;
    public class AttackHarness : MonoBehaviour 
    {
    	public const int cnstMaxPossibleAttackers = 30;
    	public string harnessName;
    	public int maxAttackers = 6;
    	public float attackDistance = 1f;
    	public bool rotatesWithObject = false;
    	public bool displayVisualization = false;
    	public Color emptyColor = Color.green;
    	public Color occupiedColor = Color.red;
    	[System.NonSerialized]
    	public GameObject[] attackers = new GameObject[cnstMaxPossibleAttackers];
    	void Start () 
    	{
    		maxAttackers = Mathf.Clamp(maxAttackers, 0, cnstMaxPossibleAttackers);
    		Clear();
    	}
    	void Clear()
    	{
    		for (int i = 0; i < attackers.Length; i++)
    			attackers[i] = null;
    	}
    	public void VacateAttack(GameObject attacker)
    	{
    		for (int i = 0; i < attackers.Length; i++)
    			if (attackers[i] == attacker) 
    				attackers[i] = null;
    	}
    	public bool OccupyClosestAttackSlot(GameObject attacker, out int attackSlot, RAINNavigator navigator = null)
    	{
    		for (int i = 0; i < maxAttackers; i++)
    			if (attackers[i] == attacker)
    			{
    				attackSlot = i;
    				return true;
    			}
    		List<int> openList = new List<int>();
    		for (int i = 0; i < maxAttackers; i++)
    			if (attackers[i] == null)
    				openList.Add(i);
    		float bestDistance = float.MaxValue;
    		int bestSlot = -1;
    		for (int i = 0; i < openList.Count; i++)
    		{
    			Vector3 attackPosition = GetAttackPosition(openList[i]);
                if (navigator != null)
                {
                    if (navigator is BasicNavigator)
                    {
                        if (NavigationManager.Instance.GraphsForPoints(navigator.AI.Kinematic.Position, attackPosition, navigator.AI.Motor.MaxHeightOffset, NavigationManager.GraphType.Navmesh, ((BasicNavigator)navigator).GraphTags).Count == 0)
                            continue;
                    }
                    else
                    {
                        if (!navigator.OnGraph(attackPosition, 0.5f))
                            continue;
                    }
                }
    			float distance = (attacker.transform.position - attackPosition).magnitude;
    			if (distance < bestDistance)
    			{
    				bestDistance = distance;
    				bestSlot = openList[i];
    			}
    		}
    		attackSlot = bestSlot;
    		if (bestSlot < 0)
    			return false;
    		attackers[bestSlot] = attacker;
    		return true;
    	}
    	public bool OccupyAttackSlot(GameObject attacker, int attackSlot)
    	{
    		if ((attackSlot < 0) || (attackSlot >= maxAttackers))
    			return false;
    		if (attackers[attackSlot] == null)
    		{
    			attackers[attackSlot] = attacker;
    			return true;
    		} 
    		else if (attackers[attackSlot] == attacker) 
    		{
    			return true;
    		}
    		return false;
    	}
    	public Vector3 GetAttackPosition(int attackSlot)
    	{
    		if ((attackSlot < 0) || (attackSlot >= maxAttackers))
    		{
    			return gameObject.transform.position + gameObject.transform.forward * attackDistance;
    		}
    		float angle = (360f / maxAttackers) * (float)attackSlot;
    		if (rotatesWithObject)
    			angle += gameObject.transform.rotation.eulerAngles.y;
    		Quaternion saved = gameObject.transform.rotation;
    		gameObject.transform.rotation = Quaternion.AngleAxis(angle, Vector3.up);
    		Vector3 returnValue = gameObject.transform.position + gameObject.transform.forward * attackDistance;
    		gameObject.transform.rotation = saved;
    		return returnValue;
    	}
    	void OnDrawGizmos()
    	{
    		if (!displayVisualization)
    			return;
    		if (maxAttackers < 1)
    			return;
    		for (int i = 0; i < maxAttackers; i++)
    		{
    			if (attackers[i] == null)
    				Gizmos.color = emptyColor;
    			else
    				Gizmos.color = occupiedColor;
    	        Gizmos.matrix = Matrix4x4.identity;
    			Gizmos.DrawWireCube(GetAttackPosition(i), new Vector3(0.2f, 0.2f, 0.2f));		
    		}
    	}
    }

    AIChooseAttackPosition.cs (custom action)

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using RAIN.Core;
    using RAIN.Action;
    using RAIN.Perception.Sensors;
    [RAINAction("Choose Attack Position")]
    public class AIChooseAttackPosition : RAINAction
    {	
        public AIChooseAttackPosition()
        {
            actionName = "AIChooseAttackPosition";
        }
    	public override void Start (AI ai)
    	{	
    		base.Start (ai);
    		AttackHarness meleeHarness = null;
    		AttackHarness waitHarness = null;
    		GameObject attackTarget = ai.WorkingMemory.GetItem<GameObject>("attacktarget");
    		if (attackTarget != null)
    		{
    			AttackHarness[] harnesses = attackTarget.GetComponentsInChildren<AttackHarness>();
    			for (int i = 0; i < harnesses.Length; i++)
    			{
    				if ((harnesses[i] != null) && (harnesses[i].harnessName == "melee"))
    					meleeHarness = harnesses[i];
    				if ((harnesses[i] != null) && (harnesses[i].harnessName == "wait"))
    					waitHarness = harnesses[i];
    			}
    		}		
    		ai.WorkingMemory.SetItem<AttackHarness>("attacktargetharness", meleeHarness);
    		ai.WorkingMemory.SetItem<AttackHarness>("waitharness", waitHarness);
    	}
        public override ActionResult Execute(AI ai)
        {
    		AttackHarness meleeHarness = ai.WorkingMemory.GetItem<AttackHarness>("attacktargetharness");
    		if (meleeHarness != null)
    			meleeHarness.VacateAttack(ai.Body);
    		AttackHarness waitHarness = ai.WorkingMemory.GetItem<AttackHarness>("waitharness");
    		if (waitHarness != null)
    			waitHarness.VacateAttack(ai.Body);
    		int tAttackSlot = -1;
    		bool foundSlot = false;
    		if (meleeHarness != null)
    		{
    			foundSlot = meleeHarness.OccupyClosestAttackSlot(ai.Body, out tAttackSlot);
    			if (foundSlot)
    		        ai.WorkingMemory.SetItem<int>("attacktargetharnessslot", tAttackSlot);
    			else
    				ai.WorkingMemory.SetItem<int>("attacktargetharnessslot", -1);
    		}
    		if (!foundSlot)
    	        ai.WorkingMemory.SetItem<int>("attacktargetharnessslot", -1);
    		if ((!foundSlot) && (waitHarness != null))
    		{
    			tAttackSlot = -1;
    			foundSlot =  waitHarness.OccupyClosestAttackSlot(ai.Body, out tAttackSlot);
    			if (foundSlot)
    		        ai.WorkingMemory.SetItem<int>("waitharnessslot", tAttackSlot);
    			else
    				ai.WorkingMemory.SetItem<int>("waitharnessslot", -1);
    		}
    		else
    		{
    	        ai.WorkingMemory.SetItem<int>("waitharnessslot", -1);
    		}
    		if (!foundSlot)
    			return ActionResult.FAILURE;
            return ActionResult.RUNNING;
        }
    	public override void Stop (AI ai)
    	{
    		AttackHarness harness = ai.WorkingMemory.GetItem<AttackHarness>("attacktargetharness");
    		if (harness != null)
    			harness.VacateAttack(ai.Body);
    		harness = ai.WorkingMemory.GetItem<AttackHarness>("waitharness");
    		if (harness != null)
    			harness.VacateAttack(ai.Body);
    		ai.WorkingMemory.RemoveItem("attacktargetharness");
    		ai.WorkingMemory.RemoveItem("attacktargetharnessslot");
    		ai.WorkingMemory.RemoveItem("waitharness");
    		ai.WorkingMemory.RemoveItem("waitharnessslot");
    		base.Stop (ai);
    	}
    }

    AIUpdateAttackPosition.cs (second custom action)

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using RAIN.Core;
    using RAIN.Action;
    using RAIN.Navigation;
    [RAINAction("Update Attack Position")]
    public class AIUpdateAttackPosition : RAINAction
    {
        public AIUpdateAttackPosition()
        {
            actionName = "AIUpdateAttackPosition";
        }
        public override ActionResult Execute(AI ai)
        {
    		AttackHarness attackHarness = ai.WorkingMemory.GetItem<AttackHarness>("attacktargetharness");
    		AttackHarness waitHarness = ai.WorkingMemory.GetItem<AttackHarness>("waitharness");
    		if ((attackHarness == null) && (waitHarness == null))
    			return ActionResult.FAILURE;
    		AttackHarness harness = null;
    		int slot = -1;
    		if ((attackHarness != null) && (ai.WorkingMemory.ItemExists("attacktargetharnessslot")))
    		{
    			harness = attackHarness;
    			slot = ai.WorkingMemory.GetItem<int>("attacktargetharnessslot");
    		}
    		if ((harness == null) || (slot < 0))
    		{
    			if ((waitHarness != null) && (ai.WorkingMemory.ItemExists("waitharnessslot")))
    			{
    				harness = waitHarness;
    				slot = ai.WorkingMemory.GetItem<int>("waitharnessslot");				
    			}
    		}
    		if ((harness == null) || (slot < 0))
    			return ActionResult.FAILURE;
    		Vector3 attackpos = harness.GetAttackPosition(slot);
    		ai.WorkingMemory.SetItem<Vector3>("attackposition", attackpos);
    		ai.WorkingMemory.SetItem<bool>("run", false);
            if (NavigationManager.Instance.GraphsForPoints(ai.Kinematic.Position, attackpos, ai.Motor.StepUpHeight, NavigationManager.GraphType.Navmesh, ((BasicNavigator)ai.Navigator).GraphTags).Count == 0)
    			return ActionResult.FAILURE;
    		float distance = (attackpos - ai.Body.transform.position).magnitude;
    		if (distance > 2.5f)
    			ai.WorkingMemory.SetItem<bool>("run", true);
    		return ActionResult.SUCCESS;
        }
    }

    I need a way for the object (the mob) to recognize when the harness attack positions are full, and basically ignore the object whose harness is full.
    If you need more info such as the behaviour tree, let me know.

    • This reply was modified 11 months ago by  CrispyArrow.
    #39502

    CrispyArrow
    Participant

    >Bump

    You guys are probably busy, but I would really appreciate an answer

    #39505

    Sigil
    Keymaster

    Sorry about that, seems this fell off my list (I usually keep them marked in my email). I will take a look at it and get back to you shortly.

    #39508

    Sigil
    Keymaster

    So everything looks alright with the code, we just need to make sure the behavior tree is handling it OK (there could still be a bug but it doesn’t seem obvious).

    I assume you don’t have a “wait” harness on your tower, as you want the AI to skip it in the event that they can’t use the “melee” harness. Given this, AIChooseAttackPosition should return failure if the harness is full, allowing you to continue on your way. You would have to account for that failure though.

    Can you give me a screen shot of your behavior tree around the area where the attack harness is being used? I will write up a test tonight to make sure this is working the way I think it should.

    #39518

    CrispyArrow
    Participant

    Thank you for your response so far
    Indeed, my towers don’t have a “wait” harness and only have a melee harness.

    Here’s my behaviour tree as requested:

    null

    Okay so first it asks for certain variables, like whether it has an attack target, whether the minion using this behaviour tree has enough health and then whether the minion using the behaviour tree is stunned.

    Then it will start move and attack, which uses the custom action choose attack position, after which it asks with a constrait if attackSlot is <= 5. if so it will set chasing to truw, give a run animation and move towards it’s target, once it reaches his close enough distance chasing will be set to false and the update attack position custom action will be called.
    once that happens, a constraint wwill ask if chasing is false and if the distance between the minion and it’s target is lower than 4. Then it will set the chasing animation to 0 so it stops using that animation, face the target and then use the attack animation which handles damage.

    If there is more you need to know, I’ll be glad to provide.

    • This reply was modified 10 months, 3 weeks ago by  CrispyArrow.
    • This reply was modified 10 months, 3 weeks ago by  CrispyArrow.
    #39525

    Sigil
    Keymaster

    Ok, so I see a possible problem in the behavior tree that may be causing this behavior. It has to do with the constraint check looking for attackSlot less than 5. In the code you posted it sets the attackSlot to -1 if it can’t find a slot, which would still be true in this case. Given the rest of the tree that I see that means it’d likely have a null target, possibly fail on the move (which is in a repeat until success so it won’t matter), and then attack wherever it happened to be.

    An easy change would be to change the constraint to be attackSlot != -1, but that may not be quite right. I think we can rework your tree to work a little better given what you need. I’m heading out the door right now but I’ll come back on later tonight and see if I can’t post a possible solution for you.

    #39533

    Sigil
    Keymaster

    So I don’t think the parallel with the detect at the very top is going to work quite right for you as you have a more complex condition here (you have to detect and you need to find a good attack position). Let’s try something like this instead:

    root
       sequencer
          parallel (succeed: any, fail: any, tie breaker: fail)
             sequencer (repeat: until success)
                detect
                custom action (class: AIChooseAttackPosition)
             waypoint patrol
                move
          sequencer (repeat: until failure)
             custom action (class: AIUpdateAttackPosition)
             move (move target: attackposition)
             move // this is your face for attack

    So I left a few things out here, where I’d expect you to just keep using the values from your tree. I also left the mecparam nodes out (which don’t need to repeat btw, they always return success unless they don’t have an animator set).

    So essentially this starts with patrolling while detecting enemies and attempting to find an attack position. If it ever succeeds in detecting a target AND choosing an attack position, it will head on to the second part of the main sequencer. In the second part it just repeats forever (I didn’t have a failure case at the moment). It updates the attack position (in case the target moved), moves to that attack position, then faces the target. My assumption is you’d add some kind of mecparam and then wait some amount of time before trying again.

    You could easily add in a check at the end for your health (if the tower is attacking you for instance) or for the tower’s health (if you kill it) and then drop out of the looping sequencer by failing (to either die, or move on to another target).

    Let me know if that makes sense, or if you have any issues.

    #39547

    CrispyArrow
    Participant

    Thank you for your time so far, I’ve updated my behaviour tree, and unfortunately it still works the same. Enemy minions will go to their respective attack harness of their target. If the harness is full, they will simply stand at their max vision range.
    Here’s an image to clarify, along with the behaviour tree of one of the minions that gets stuck:

    What I need is for the minions to somehow change target once they detect that their current target is full.
    I’ve been thinking of changing this through the attack harness, rather than the behaviour tree. I could for example add an if statement to the attack harness that checks the amount of occupied spots. If the spots are all full, I could make an if statement change the entiity of the object whose harness is full. This way, the minions should be able to re-select a new target who has the appropriate target entity. Only problem is, I don’t understand much of the rain AI coding.

    I hope to hear from you soon and let me know if you think changing the entity could work, or even if it’s possible.

    #39552

    CrispyArrow
    Participant

    Looks like my images did not work, sorry for that, here they are:

    View post on imgur.com

    • This reply was modified 10 months, 2 weeks ago by  CrispyArrow.
    • This reply was modified 10 months, 2 weeks ago by  CrispyArrow.
    • This reply was modified 10 months, 2 weeks ago by  CrispyArrow.
    #39567

    Sigil
    Keymaster

    OK, well you’ll need to debug the AI that are getting stuck, as the behavior tree I gave you should work for this. Select one of the stuck AI while the game is running and open the behavior tree editor. Select the “Current AI” in the drop down and you should see the behavior tree for the stuck AI, highlighted with indicators of what is happening that frame.

    So generally you can follow the tree, and you’ll see red for failure, green for success, and yellow for running. Sometimes it can be hard to follow but it should give an indication of what is going wrong.

    Take a screen shot of it highlighted and post it here and we can figure this out.

    #39571

    CrispyArrow
    Participant

    here you go:

    View post on imgur.com

    #39585

    CrispyArrow
    Participant

    bump

    #39591

    Sigil
    Keymaster

    Sorry it took so long to get back.

    So it is important that you actually use the setup I described in my tree in order to get it to work right. I see that you are partially using it, but you still have the constraints and the movement occurring in a different part of the tree, and that won’t work with what I posted. The tree I put together handles your patrolling (or returning to spawn as you have it) as well as the attacking and proper ignoring of the target. You just need to fill in the variables that I didn’t know (like name of patrol route, or target variable, etc).

    You don’t have to get rid of your tree altogether, just try mine out in a new tree and we can work through the issues. Then you can add in your other things, like animation, death, what not.

Viewing 15 posts - 1 through 15 (of 19 total)

You must be logged in to reply to this topic.