Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

A.I Dodging incoming projectiles

Discussion in 'Game Design' started by SparrowsNest, Apr 18, 2019.

  1. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,630
    <TextWall>

    Hey guys, I have a game where tank-like robots form teams and battle each other in an arena, think kinda robot wars but with automatic rifles and etc.
    (I think that's enough to go on, please say if you need to hear more)

    Incase the title didn't make it clear, all I'm talking about is how the AI will control this, I don't want to change the mechanics, I don't know if this is truely the place for this post, but I don't have a "how" question, I have a "what" question.

    My current dilemma is about the ballistic weapons, or more precisely - other robots that are fired on by them.
    my code for aiming the ballistic weapons (mortar & howitzer) takes into account the target velocity, time to target and a few more details to get a better estimate of where to aim to hit the target (and I must say I'm very surprised by how accurate it is given I only do 1 iteration to get each value), ofc the player does this by himself if so he chooses, I only give him a line renderer displaying the predicted path (it has deviation tied to weapon quality).

    V this part is not implemented yet and is basically the question V

    now, the way to dodge an incoming projectile is obvious if we know it doesn't take
    change in velocity - change you velocity. (I'd have to keep track of the change over time and a whole bunch of other S*** I ain't got time for to do that)
    we can do that by either:

    speeding up - the closer we are to the top speed the less effective this is, my robots doesn't have any fuel efficiency curve that goes with speed or anything like that, so they are mostly moving at top speed or are accelerating to it unless they are turning

    slowing down/reversing - but what if we wanna keep going forward, what if stopping in place isn't the best idea? some one can see this as a ramming opportunity - might be fun if the player is the one doing the ramming
    (you can control individual tanks), but I'd consider this "dumb" AI.

    turning - turn a little to the left, move out of the hazard zone, turn back right for course correction to keep heading where you want, I dunno, seems like the ideal solution to me, please poke holes in this one at will.

    use a special tank-jet pack to hop over the projectile and crush the enemy tank - jk, lol nothing else, if you have another idea please suggest so, there aren't any more controls really.. the turret can technically shoot the projectile mid-air, but that's a ridiculously tough shot to make unless I "aim bot" the robots, heh.

    okay, so the way I'm going to alert the tanks that they are begin fired on by a mortar is by placing "Hazard" object where the projectile is gonna hit,
    (that is probably just a trigger and maybe a mini script, i dunno it's not real yet, maybe check distances to avoid the collider overhead, can still do both circles and boxes), it's gonna work with stuff like pitfalls (actual pits that you fall though), saws, flames, other stuff that are, you know.. hazardous.

    how should the tanks behave when in a (incoming projectile) hazard zone? just tell it "rotate an arbitrary number of angles in an arbitrary direction" and after the danger has passed just let the normal AI calculations do the course correction?
    find the closest "exit point" from the hazard zone, given the current position velocity and heading and go that way?

    the way the tanks will react to static hazards the are always there, or atleast for a DOT effect that's toggling on and off or even the pits that can open and close is just plop the position into the already-implemented (and working) obstacle avoidance with a high weight, maybe I should just use that then?

    I should also mention this all takes places in a circular arena with a uniform height.

    so.. yeah, kinda overcooked my head today on this one, what are your thoughts?
    sorry if I've left something vital out, I've kinda overcooked my brains today, heh

    </TextWall>
     
  2. YBtheS

    YBtheS

    Joined:
    Feb 22, 2016
    Posts:
    122
    Perhaps the AI behaves randomly (or weighted in one way or another). Just so that it doesn't become too predictable when an AI turns 30 degrees to the left every time it gets shot at. And also perhaps a random (or weighted) reaction time so the AI might still get hit if it reacts too late or perhaps clears the danger easily if it reacts early to make things interesting.
     
    SparrowsNest likes this.
  3. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,630
    yeah that was my idea behind the "rotate an arbitrary number of angles in an arbitrary direction", I definitely don't want my AI to be predictable in this fashion, he does need to be predictable about what the player sets up as his values for targeting and etc., but that's another story.

    There is actually already a reaction time in the form of 2 layers from the AI output to the tanks controls, the tank tries to follow a vector as his fwd(while the AI is in control) and it's magnitude as the speed, the AI changes that by changing another vector that is then interpolated with the former to get a new heading, hope this makes sense, don't really know the name for it, it's all very hacky really, haha.
    I implemented this so that the tanks won't "wiggle their tails"(IE rapidly turn left and right around the wanted vector because there was a feedback loop thingy with that), but taking control of the lerping value to give each tank a different and a maybe a little random/changing over time reaction time would be sweet, thanks.
     
    YBtheS likes this.
  4. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    62
    I am a bit confused. Why are you dealing with the "dodging" of the bullet rather than the "accuracy" of the shooter?
    It would seem easier to add a fudge factor of random range deviation of + or - 10° per shot.
     
  5. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,630
    Weapon deviation is also implemented for everything based on weapon level/"grade", but doesn't really matter who's or what's shooting, the important thing is that there is a projectile coming his way and he's not doing anything about it while there's viable options to dodge.

    I don't want to make them impossible to hit, i want them to try and do something about it, try to react atleast, they theoretically would be able to dodge a projectile with enough flight time to impact if they have enough speed to get out of the way, what else that would do is maybe avoid a direct impact (this occurs even with high deviation weapons and noise, sometimes the values just align even if i'm aiming a bit off), minimize slpash damage recived.
     
  6. newjerseyrunner

    newjerseyrunner

    Joined:
    Jul 20, 2017
    Posts:
    600
    It takes relatively little math to determine where the projectile will land. I think it would be more efficient to calculate the landing spot of the projectile at the same time it’s being fired, then do a sphere collision detection from that location. Send a message to anything within a certain radius that a shot is incoming and to take action or not depending on rng. That way your enemies don’t have to detect anything which seems very computationally expensive.
     
  7. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,630
    Well yeah, I already have the point of impact and all, this is my ballistics class incase anyone is intersted:
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4.     public static class Ballistics {
    5.  
    6.         // Note, doesn't take drag into account.
    7.  
    8.         /// <summary>
    9.         /// Calculate the lanch angle.
    10.         /// </summary>
    11.         /// <returns>Angle to be fired on.</returns>
    12.         /// <param name="start">The muzzle.</param>
    13.         /// <param name="end">Wanted hit point.</param>
    14.         /// <param name="muzzleVelocity">Muzzle velocity.</param>
    15.         public static bool CalculateTrajectory(Vector3 start, Vector3 end, float muzzleVelocity, out float angle){//, out float highAngle){
    16.  
    17.             Vector3 dir = end - start;
    18.             float vSqr = muzzleVelocity * muzzleVelocity;
    19.             float y = dir.y;
    20.             dir.y = 0.0f;
    21.             float x = dir.sqrMagnitude;
    22.             float g = -Physics.gravity.y;
    23.  
    24.             float uRoot = vSqr * vSqr - g * (g * (x) + (2.0f * y * vSqr));
    25.  
    26.  
    27.             if (uRoot < 0.0f) {
    28.  
    29.                 //target out of range.
    30.                 angle = -45.0f;
    31.                 //highAngle = -45.0f;
    32.                 return false;
    33.             }
    34.  
    35.     //        float r = Mathf.Sqrt (uRoot);
    36.     //        float bottom = g * Mathf.Sqrt (x);
    37.  
    38.             angle = -Mathf.Atan2 (g * Mathf.Sqrt (x), vSqr + Mathf.Sqrt (uRoot)) * Mathf.Rad2Deg;
    39.             //highAngle = -Mathf.Atan2 (bottom, vSqr - r) * Mathf.Rad2Deg;
    40.             return true;
    41.  
    42.         }
    43.  
    44.         /// <summary>
    45.         /// Gets the ballistic path.
    46.         /// </summary>
    47.         /// <returns>The ballistic path.</returns>
    48.         /// <param name="startPos">Start position.</param>
    49.         /// <param name="forward">Forward direction.</param>
    50.         /// <param name="velocity">Velocity.</param>
    51.         /// <param name="timeResolution">Time from frame to frame.</param>
    52.         /// <param name="maxTime">Max time to simulate, will be clamped to reach height 0 (aprox.).</param>
    53.  
    54.         public static Vector3[] GetBallisticPath(Vector3 startPos, Vector3 forward, float velocity, float timeResolution, float maxTime = Mathf.Infinity){
    55.  
    56.             maxTime = Mathf.Min (maxTime, Ballistics.GetTimeOfFlight (velocity, Vector3.Angle (forward, Vector3.up) * Mathf.Deg2Rad, startPos.y));
    57.             Vector3[] positions = new Vector3[Mathf.CeilToInt(maxTime / timeResolution)];
    58.             Vector3 velVector = forward * velocity;
    59.             int index = 0;
    60.             Vector3 curPosition = startPos;
    61.             for (float t = 0.0f; t < maxTime; t += timeResolution) {
    62.              
    63.                 if (index >= positions.Length)
    64.                     break;//rounding error using certain values for maxTime and timeResolution
    65.              
    66.                 positions [index] = curPosition;
    67.                 curPosition += velVector * timeResolution;
    68.                 velVector += Physics.gravity * timeResolution;
    69.                 index++;
    70.             }
    71.             return positions;
    72.         }
    73.          
    74.         /// <summary>
    75.         /// Checks the ballistic path for collisions.
    76.         /// </summary>
    77.         /// <returns><c>false</c>, if ballistic path was blocked by an object on the Layermask, <c>true</c> otherwise.</returns>
    78.         /// <param name="arc">Arc.</param>
    79.         /// <param name="lm">Anything in this layer will block the path.</param>
    80.         public static bool CheckBallisticPath(Vector3[] arc, LayerMask lm){
    81.          
    82.             RaycastHit hit;
    83.             for (int i = 1; i < arc.Length; i++) {
    84.  
    85.                 if (Physics.Raycast (arc [i - 1], arc [i] - arc [i - 1], out hit, (arc [i] - arc [i - 1]).magnitude) && GameMaster.IsInLayerMask(hit.transform.gameObject.layer, lm))
    86.                     return false;
    87.  
    88.     //            if (Physics.Raycast (arc [i - 1], arc [i] - arc [i - 1], out hit, (arc [i] - arc [i - 1]).magnitude) && GameMaster.IsInLayerMask(hit.transform.gameObject.layer, lm)) {
    89.     //                Debug.DrawRay (arc [i - 1], arc [i] - arc [i - 1], Color.red, 10f);
    90.     //                return false;
    91.     //            } else {
    92.     //                Debug.DrawRay (arc [i - 1], arc [i] - arc [i - 1], Color.green, 10f);
    93.     //            }
    94.             }
    95.             return true;
    96.         }
    97.  
    98.         public static Vector3 GetHitPosition(Vector3 startPos, Vector3 forward, float velocity){
    99.  
    100.             Vector3[] path = GetBallisticPath (startPos, forward, velocity, .35f);
    101.             RaycastHit hit;
    102.             for (int i = 1; i < path.Length; i++) {
    103.  
    104.                 //Debug.DrawRay (path [i - 1], path [i] - path [i - 1], Color.red, 10f);
    105.                 if (Physics.Raycast (path [i - 1], path [i] - path [i - 1], out hit, (path [i] - path [i - 1]).magnitude)) {
    106.                     return hit.point;
    107.                 }
    108.             }
    109.  
    110.             return Vector3.zero;
    111.         }
    112.          
    113.  
    114.         public static float CalculateMaxRange(float muzzleVelocity){
    115.             return (muzzleVelocity * muzzleVelocity) / -Physics.gravity.y;
    116.         }
    117.  
    118.         public static float GetTimeOfFlight(float vel, float angle, float height){
    119.  
    120.             return (2.0f * vel * Mathf.Sin (angle)) / -Physics.gravity.y;
    121.         }
    122.          
    123.     }

    That's pretty much what I had in mind, currently before a ballistic weapon fires he checks the hit point for any friendly units to see if he can fire or should he change targets. (I check for x1.3 the actual range of the splash damage)

    What i though about doing is if there's no allies in range, when I fire i spawn a "hazard" object as i've suggested at the point, this will have a radius a bit bigger then the projectiles explosion radius (x1.3) that what I had in mind for is toggling a bool like "inHazardZone" with a vector 3 indicating said hazard location, the tank just need to know to run away

    Do you mean something like this? (about the part in bold about the rng)
    Code (CSharp):
    1. bool Avoid(){
    2.  
    3.    if(Random.value > .5f)
    4.       return true;
    5.    else
    6.       return false;
    7.  
    8. }
    I dunno about that.. i think every tank should react, whether they get hit or not depends on stuff like the tank stats (turn speed, acceleration speed, etc.) and ofc extra time to impact = "worse" tanks can dodge.

    one thing i'll point out, if a tank is going max speed at a straight line, the mortar will fire with hes speed in mind, if he does nothing and keeps going that speed the impact is gonna happen directly on him (if we don't take the weapon deviation into account, but that will still keep him in the middle of the danger zone), the arena radius is 150, the maximum time of flight for a projectile is 5 and a bit seconds if fired at 45 degrees at the maximum muzzle velocity possible (I just checked) the average is 2, maybe 3 seconds of reaction time, its on the edge for dodge imo, slow, heavy, sluggish tanks won't be able to dodge but will attempt, while quick and light tanks will be able to at the very least minimize damage taken.
     
  8. artwhaley

    artwhaley

    Joined:
    Aug 11, 2014
    Posts:
    4
    If your robot knows the estimated point and time of impact, first have it project it's position at the time of impact. If that's going to be outside the kill radius, do nothing. (d=vt)

    If inside the kill radius, apply maximum turning power to try to move directly away from the blast radius. Don't overthink it - you wouldn't if you saw a grenade coming at you - just turn away as hard as possible - aiming for path that's directly away from the blast.

    Then tell them what to do if they end up outside the radius before the blast goes off - if they immediately resume course, they may drive right back into the blast zone... so you'll either want some logic for that, or if it its with your concept, some sort of 'cover' animation might be a good fit. If they get outside the kill radius they could 'hunker down' in some way - retracting appendages, lowering wheel suspension, etc - something to indicate that it's going to be close but I think I'm fine. This also has the benefit, depending on the timings you use, of creating a stun effect that may be interesting! If they manage to avoid the blast they still end up immobile for a fraction of a second that gives you a chance to shoot at them?

    If it's possible to be in the kill radius of two impending explosions at once... then you have to decide if they're going to be SMARTER and try to avoid the overlapping kill radius, or dumb and just respond to the first trigger, which creates an interesting way to kill them if you can flush a fast mover from one place into range of the next round...
     
    angrypenguin and SparrowsNest like this.
  9. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,630
    Thank you very much for your input my friend. ;)

    I though of this, but I have no idea how to account for future change in the direction, i ofc could calculate it given a straight line, but it's very unlikely to happen.

    how i though about handling it is just telling the tank the explosion radius is bigger than it actually is, so even if they stray back a little it won't be a problem.

    Oh wow, this is a great idea for some sort of active ability for the slower tanks, I definitely want to implement this if i'll have the proper animation resources for this.
    IE able to get my blender S*** together, because i'm a programmer and it's my first solo indie game, but unless I go crazy with what I want I think this is actually achievable (for me, that is).

    Dodging from all of them using a weighted direction to give an offset against the strongest one, can work with any number of explosions, the weight is given by two values, one is damage(distance from center with the damage falloff) and the second is time to impact, giving a priority to a sooner explosion.

    the only draw back i can think of is that there's always atleast 1 "balance point" where i will get a zero vector for the direction, but it's an actual point (not a region) in space and the tanks move via slowly changing velocity vector, so they'll realistically never get stuck there as far as i can tell.
     
  10. TenKHoursDev

    TenKHoursDev

    Joined:
    Nov 9, 2014
    Posts:
    980
    Do what any sane tank commander would do when faced with impending rocket impact: choose a direction (preferably forward) and run like hell! Rotating a tank takes valuable time. You have a choice: an omniscient AI that has an exact idea of where the shell will land, or a realistic AI that doesn't know that detail and so makes every effort to get the heck out of dodge. In WW2 the best chance you had as an infantry to survive artillery was 1. prepare by digging a "foxhole", and 2. get to one of these foxholes once you hear the distant whistle of incoming arty.

    The first option (an omniscient AI) has value in being hard to actually hit but deterministic (you can assume the enemy tank will dodge the shot with almost 100% certainty). The other has the advantage of being easily scared off, but could potentially run into the killzone (or away from it). This may give the player the appearance of "dumb" ai.

    You as the developer must decide what features you'd like. Who knows maybe the tanks don't dodge the shell. Instead they "pop smoke" or use their explosive reactive armor, or activate their point blank defense shield (like Israels Iron Dome or vehicle solution of the same function).
     
    SparrowsNest likes this.
  11. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,630
    My implemented solution does actually know where it will land but still doesn't do the perfect solution and actually runs inside the "kill zone" on occasion as you've said.

    I actually made the armor into 2 parts, one plate armor (against normal firepower) and non-explosive-reactive-armor (to avoid making more hit effects, haha)

    about the last part, I do want to have some active abilities like @artwhaley suggested with the "hunkering down", i though about doing something like Iron Dome but it's still under consideration.

    the armor degrades so the ideal will be to not get hit at all, but as a last resort, yeah.

    Thanks for the input mate. ;)
     
    TenKHoursDev likes this.
  12. TenKHoursDev

    TenKHoursDev

    Joined:
    Nov 9, 2014
    Posts:
    980
    Regarding iron dome, I don't think you actually need to simulate shoot-down. Just play some explosion effect and re-pool the projectile once it hits a trigger field in close proximity to the tank. It will happen so fast the player won't know the difference, but they will know the projectile was shot down by the tanks proximity defenses.