1. We've introduced thread tags, search within a thread and similar thread search. Read more here.
    Dismiss Notice
  2. Learn how you'll soon be able to publish your games to China in four simple steps with Xiaomi. Sign up now for early access.
    Dismiss Notice
  3. Get further faster with the Unity Plus Accelerator Pack, free for new Unity Plus subscribers for a limited time. Click here for more details.
    Dismiss Notice
  4. We've released our first Timeline Experimental Preview, our new tool for creating cutscenes and more! To check it out click here.
    Dismiss Notice
  5. Unity 5.5 is now released.
    Dismiss Notice
  6. Check out all the fixes for 5.5 in patch releases 1 & 2.
    Dismiss Notice
  7. Unity 5.6 beta is now available for download.
    Dismiss Notice

AI Influence Maps

Discussion in 'Scripting' started by AnomalusUndrdog, Jul 28, 2012.

  1. AnomalusUndrdog

    AnomalusUndrdog

    Joined:
    Jul 3, 2009
    Posts:
    1,319
    From http://aigamedev.com/open/tutorial/influence-map-mechanics/
    http://gameschoolgems.blogspot.com/2009/12/influence-maps-i.html
    http://gameschoolgems.blogspot.com/2010/03/influence-maps-ii-practical.html

    [​IMG]
    This is my attempt at implementing influence maps in Unity. Its on an MIT license. I'll be working more on this as I go. Any expert help to guide me is appreciated.
    Git repository: https://bitbucket.org/AnomalousUnderdog/influencemapsunity3d
     
    laurentlavigne likes this.
  2. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    can i make a few suggestions/corrections?

    when you are getting the neighbors you forgot to get two diagonals, you get the diagonals for (x-1,y-1) and (x+1,y+1) but not for (x-1,y+1) and (x+1,y-1).

    after doing that it gets a bit better but, i noticed how, if you test using only the mouse, the shape is all weird and fades into the lower left corner... this is because you didn't put any second buffer for the calculations of influences, so basically you are calculating and changing the values at the same time, which means the values depend on the order of the calculation, this is why the influences fades into that corner. setting up a second vector of values to store the calculations temporarily and only after all is done switch the values of the buffers corrects this

    after this all is fine, except now the fade has this square looking shape, which is to be expected since you are considering all neighbors to be at the same distance, which isn't true since diagonals are a bit further, so adding a third value of distance where diagonals have a value of 14 and axis a value of 10 (you could change it to float and have it 1.4142 and 1.0 respectively) and calculating the exponential value considering the distance gives us this nice sphery/stary looking shape

    thx for your initial project :]

    $influencemap.png
     
    Last edited: Nov 22, 2012
    laurentlavigne likes this.
  3. AnomalusUndrdog

    AnomalusUndrdog

    Joined:
    Jul 3, 2009
    Posts:
    1,319
    Haha, wow, I actually have no idea how to do what you just said. Is it ok if you share your corrections?
     
  4. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    xD, ok i'll try to explain in more detail now

    this is what you have right now:
    map1vz.png

    notice how the diffusion of the map is all weird, it's strong on some diagonals and it seems to fade primarily into the lower left corner

    on closer inspection i noticed how you forgot to find the neighbors for two of diagonals

    your code:
    Code (csharp):
    1.         // diagonals
    2.         if (x > 0  y > 0)
    3.         {
    4.             retVal.Add(new Vector2I(x-1, y-1));
    5.         }
    6.         if (x < _influences.GetLength(0)-1  y < _influences.GetLength(1)-1)
    7.         {
    8.             retVal.Add(new Vector2I(x+1, y+1));
    9.         }
    so the first thing i did was to correct this by adding the missing diagonals

    my change:
    Code (csharp):
    1.         // diagonals
    2.         if (x > 0  y > 0)
    3.         {
    4.             retVal.Add(new Vector2I(x-1, y-1));
    5.         }
    6.         if (x < _influences.GetLength(0)-1  y < _influences.GetLength(1)-1)
    7.         {
    8.             retVal.Add(new Vector2I(x+1, y+1));
    9.         }
    10.         if (x > 0  y < _influences.GetLength(1)-1)
    11.         {
    12.             retVal.Add(new Vector2I(x-1, y+1));
    13.         }
    14.         if (x < _influences.GetLength(0)-1  y > 0)
    15.         {
    16.             retVal.Add(new Vector2I(x+1, y-1));
    17.         }
    this is what you get:
    map2gu.png

    now you can see the strength of the diagonals is more even out, BUT it has a weird shape doesn't it? if you look at the center of the diffusion you can notice it better, the values still look like they are being pushed to the lower left corner... this happens because you are not double buffering the map, what i mean is that you calculate the influence for a certain cell and you change it's value right away, so the next cell in line will now consider some of its neighbors with old values and some with new values, mixing up everything and giving odd results, influence calculation should not depend on which order do you read and write from the matrix, this is when you need a second matrix (double buffer technique) one to read from and another to write to

    so i created a second matrix with the name _influenceCalc and here is the main trick:
    Code (csharp):
    1.     public void Propagate()
    2.     {
    3.         UpdatePropagators();
    4.  
    5.         for (int xIdx = 0; xIdx < _influences.GetLength(0); ++xIdx)
    6.         {
    7.             for (int yIdx = 0; yIdx < _influences.GetLength(1); ++yIdx)
    8.             {
    9.                 //Debug.Log("at " + xIdx + ", " + yIdx);
    10.                 float maxInf = 0.0f;
    11.                 float minInf = 0.0f;
    12.                 Vector2I[] neighbors = GetNeighbors(xIdx, yIdx);
    13.                 foreach (Vector2I n in neighbors)
    14.                 {
    15.                     //Debug.Log(n.x + " " + n.y);
    16.                     float inf = _influencesCalc[n.x, n.y] * Mathf.Exp(-Decay); //* Decay;
    17.                     maxInf = Mathf.Max(inf, maxInf);
    18.                     minInf = Mathf.Min(inf, minInf);
    19.                 }
    20.            
    21.                 if (Mathf.Abs(minInf) > maxInf)
    22.                 {
    23.                     _influences[xIdx, yIdx] = Mathf.Lerp(_influencesCalc[xIdx, yIdx], minInf, Momentum);
    24.                 }
    25.                 else
    26.                 {
    27.                     _influences[xIdx, yIdx] = Mathf.Lerp(_influencesCalc[xIdx, yIdx], maxInf, Momentum);
    28.                 }
    29.             }
    30.         }
    31.    
    32.         for (int xIdx = 0; xIdx < _influences.GetLength(0); ++xIdx)
    33.         {
    34.             for (int yIdx = 0; yIdx < _influences.GetLength(1); ++yIdx)
    35.             {
    36.                 _influencesCalc[xIdx, yIdx] = _influences[xIdx, yIdx];
    37.             }
    38.         }
    39.     }
    see how i save the information on _influence BUT i use _influenceCalc to get the values, and obviously _influenceCalc must be updated at some point, i do it with a new cycle at the end... this technique has it's costs obviously but look how the map looks now:
    map3y.png

    much more even on all sides :] now the map is working just fine, there is just one small problem, if you use a map like this to decide how a character should move by looking at the values of adjacent cells you'll see it preferring walking horizontally or vertically because with this map the higher value cells is ALWAYS at perpendicular position EXCEPT when the character is sitting exactly on the diagonal of the map, where the higher value cell is ALWAYS at the diagonals... so basically the diagonals of the map function like magnets meaning the a character would always walk towards the diagonals first and only then walk to the center of the map diagonally :p

    this happens because currently the map is considering all neighbors being at the same distance of any given cell, which is not true because diagonal cells are a bit more distant, 1.4142 times more to be more exact (squareroot of 2).

    there are two ways of solving this (3 actually), the first one is to completely ignore diagonals altogether, if you comment out the code to get the diagonal neighbors now all neighbors (only 4 of them) do have the same distance and this is what you get:
    map4h.png

    BUT, this isn't a really good solution, simply because the only thing it did was shifting the relevance back to the perpendiculars inverting the behavior of the map, this is why now it looks like a 4 point star, or a rotated cube, this is to be expected though, and should be used if you only care about perpendicular movement

    another solution would be to diffuse the values instead of interpolating them, which would give us a nice sphere effect on the map (like a soft airbrush from photoshop), but this solution actually has the same problem as the previous solution, because while visually it looks better the values tell us a different story, where the higher value cell is ALWAYS diagonally adjacent to the cell

    the solution i prefer is to give each neighbor cell it's rightful distance value so when you calculate the exponential of the decay it depends on the distance, i did that but introducing a new value of your Vector2I structure that holds either 1 or 1.4142
    Code (csharp):
    1. public struct Vector2I
    2. {
    3.     public int x;
    4.     public int y;
    5.     public float d;
    6.  
    7.     public Vector2I(int nx, int ny, float nd)
    8.     {
    9.         x = nx;
    10.         y = ny;
    11.         d = nd;
    12.     }
    13. }
    Code (csharp):
    1. float inf = _influencesCalc[n.x, n.y] * Mathf.Exp(-Decay * n.d); //* Decay;
    Code (csharp):
    1.     Vector2I[] GetNeighbors(int x, int y)
    2.     {
    3.         List<Vector2I> retVal = new List<Vector2I>();
    4.    
    5.         if (x > 0)
    6.         {
    7.             retVal.Add(new Vector2I(x-1, y, 1));
    8.         }
    9.         if (x < _influences.GetLength(0)-1)
    10.         {
    11.             retVal.Add(new Vector2I(x+1, y, 1));
    12.         }
    13.    
    14.         if (y > 0)
    15.         {
    16.             retVal.Add(new Vector2I(x, y-1, 1));
    17.         }
    18.         if (y < _influences.GetLength(1)-1)
    19.         {
    20.             retVal.Add(new Vector2I(x, y+1, 1));
    21.         }
    22.    
    23.         // diagonals
    24.         if (x > 0  y > 0)
    25.         {
    26.             retVal.Add(new Vector2I(x-1, y-1, 1.4142f));
    27.         }
    28.         if (x < _influences.GetLength(0)-1  y < _influences.GetLength(1)-1)
    29.         {
    30.             retVal.Add(new Vector2I(x+1, y+1, 1.4142f));
    31.         }
    32.         if (x > 0  y < _influences.GetLength(1)-1)
    33.         {
    34.             retVal.Add(new Vector2I(x-1, y+1, 1.4142f));
    35.         }
    36.         if (x < _influences.GetLength(0)-1  y > 0)
    37.         {
    38.             retVal.Add(new Vector2I(x+1, y-1, 1.4142f));
    39.         }
    40.         return retVal.ToArray();
    41.     }
    and this is how it looks :]
    map5m.png

    notice how it looks like a 8 point start, which actually share the same problems as previous solutions, BUT since now both the diagonals AND the perpendiculars are "pulling" the values the map works just fine for small to medium size maps like this one, the bigger the map the more you notice the same problems on the outer parts of the map, which is not that bad if you plan on using it for a RTS game for instance

    i hope it helped, most of the code is here, but if you wish i can share the package, i still advise you to try doing it yourself though

    cheers :]
     
    Last edited: Dec 12, 2016
    laurentlavigne likes this.
  5. AnomalusUndrdog

    AnomalusUndrdog

    Joined:
    Jul 3, 2009
    Posts:
    1,319
    Awesome, thanks a lot for the explanations! One thing I guessed at was to change both _influences and _influencesCalc when setting influence in InfluenceMap.SetInfluence()

    Code (csharp):
    1.  
    2.     public void SetInfluence(int x, int y, float value)
    3.     {
    4.         if (x < Width  y < Height)
    5.         {
    6.             _influences[x, y] = value;
    7.             _influencesBuffer[x, y] = value;
    8.         }
    9.     }
    10.  
    11.     public void SetInfluence(Vector2I pos, float value)
    12.     {
    13.         if (pos.x < Width  pos.y < Height)
    14.         {
    15.             _influences[pos.x, pos.y] = value;
    16.             _influencesBuffer[pos.x, pos.y] = value;
    17.         }
    18.     }
    19.  
     
    laurentlavigne likes this.
  6. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    you guessed it right, i didn't paste all the changes, some minor ones i left out

    influence maps are pretty cool, in some cases you can totally replace any pathfinding system with it, being a worthy trade for scenarios with multiple characters, specially if they are cooperating with each other

    i'm actually now working of top of these scripts to create a system that dynamically blocks and unblocks some cells, i managed to create walls for now and improved the code by switching buffers instead of copying them (ie: frame 1 you do the calculations with _influenceBuffer and save it on _influence, frame 2 you do the opposite, and so on), it's for a very custom made scenario but if it turns out good enough for general purposes i'll gladly share it
     
    laurentlavigne likes this.
  7. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    This is great! A friend was pushing me to try influence map to do the AI instead of BT and it works really well. For a larger environment it might be necessary to use a shader to compute the map gaussian blur but in the meantime I tweaked it a bit to remove GC spikes and sped it up 2x.

    Thanks for writing Unity API free code :) The 4x 100x100 maps I use for testing AI used to spike at 70ms each (their update are staggered) and now that the call to Propagae is threaded they don't even show up on the profiler - I love you ThreadNinja :)

    Code (CSharp):
    1. /*
    2. Copyright (C) 2012 Anomalous Underdog
    3.  
    4. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    5.  
    6. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    7.  
    8. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    9. */
    10.  
    11. using UnityEngine;
    12. using System.Collections;
    13. using System.Collections.Generic;
    14.  
    15. public struct Vector2I
    16. {
    17.     public int x;
    18.     public int y;
    19.     public float d;
    20.  
    21.     public Vector2I(int nx, int ny)
    22.     {
    23.         x = nx;
    24.         y = ny;
    25.         d = 1;
    26.     }
    27.  
    28.     public Vector2I(int nx, int ny, float nd)
    29.     {
    30.         x = nx;
    31.         y = ny;
    32.         d = nd;
    33.     }
    34. }
    35.  
    36. public class InfluenceMap : GridData
    37. {
    38.     List<IPropagator> _propagators = new List<IPropagator>();
    39.  
    40.     float[,] _influences;
    41.     float[,] _influencesBuffer;
    42.     public float Decay { get; set; }
    43.     public float Momentum { get; set; }
    44.     public int Width { get{ return _influences.GetLength(0); } }
    45.     public int Height { get{ return _influences.GetLength(1); } }
    46.     public float GetValue(int x, int y)
    47.     {
    48.         return _influences[x, y];
    49.     }
    50.  
    51.     public InfluenceMap(int size, float decay, float momentum)
    52.     {
    53.         _influences = new float[size, size];
    54.         _influencesBuffer = new float[size, size];
    55.         Decay = decay;
    56.         Momentum = momentum;
    57.     }
    58.  
    59.     public InfluenceMap(int width, int height, float decay, float momentum)
    60.     {
    61.         _influences = new float[width, height];
    62.         _influencesBuffer = new float[width, height];
    63.         Decay = decay;
    64.         Momentum = momentum;
    65.     }
    66.  
    67.     public void SetInfluence(int x, int y, float value)
    68.     {
    69.         if (x < Width && y < Height)
    70.         {
    71.             _influences[x, y] = value;
    72.             _influencesBuffer[x, y] = value;
    73.         }
    74.     }
    75.  
    76.     public void SetInfluence(Vector2I pos, float value)
    77.     {
    78.         if (pos.x < Width && pos.y < Height)
    79.         {
    80.             _influences[pos.x, pos.y] = value;
    81.             _influencesBuffer[pos.x, pos.y] = value;
    82.         }
    83.     }
    84.  
    85.     public void RegisterPropagator(IPropagator p)
    86.     {
    87.         _propagators.Add(p);
    88.     }
    89.  
    90.     public void Propagate()
    91.     {
    92.         UpdatePropagators();
    93.         UpdatePropagation();
    94.         UpdateInfluenceBuffer();
    95.     }
    96.  
    97.     void UpdatePropagators()
    98.     {
    99.         foreach (IPropagator p in _propagators)
    100.         {
    101.             SetInfluence(p.GridPosition, p.Value);
    102.         }
    103.     }
    104.  
    105.     void UpdatePropagation()
    106.     {
    107.         for (int xIdx = 0; xIdx < Width; ++xIdx)
    108.         {
    109.             for (int yIdx = 0; yIdx < Height; ++yIdx)
    110.             {
    111.                 //Debug.Log("at " + xIdx + ", " + yIdx);
    112.                 float maxInf = 0.0f;
    113.                 float minInf = 0.0f;
    114.                 GetNeighbors(ref retVal, xIdx, yIdx);
    115.                 for (int i=0;i<8;i++)
    116.                 {
    117.                     Vector2I n = retVal [i];
    118.                     if (n.d!=0) {
    119.                         //Debug.Log(n.x + " " + n.y);
    120.                         float inf = _influencesBuffer [n.x, n.y] * Mathf.Exp (-Decay * n.d); //* Decay;
    121.                         maxInf = Mathf.Max (inf, maxInf);
    122.                         minInf = Mathf.Min (inf, minInf);
    123.                     }
    124.                 }
    125.          
    126.                 if (Mathf.Abs(minInf) > maxInf)
    127.                 {
    128.                     _influences[xIdx, yIdx] = Mathf.Lerp(_influencesBuffer[xIdx, yIdx], minInf, Momentum);
    129.                 }
    130.                 else
    131.                 {
    132.                     _influences[xIdx, yIdx] = Mathf.Lerp(_influencesBuffer[xIdx, yIdx], maxInf, Momentum);
    133.                 }
    134.             }
    135.         }
    136.     }
    137.  
    138.     void UpdateInfluenceBuffer()
    139.     {
    140.         for (int xIdx = 0; xIdx < _influences.GetLength(0); ++xIdx)
    141.         {
    142.             for (int yIdx = 0; yIdx < _influences.GetLength(1); ++yIdx)
    143.             {
    144.                 _influencesBuffer[xIdx, yIdx] = _influences[xIdx, yIdx];
    145.             }
    146.         }
    147.     }
    148.  
    149.     Vector2I[] retVal = new Vector2I[8];
    150.     void InitVector2IArray(ref Vector2I[] array)
    151.     {
    152.         for (int i = 0; i < array.Length; i++)
    153.             array [i] = new Vector2I (0, 0, 0);
    154.     }
    155.  
    156.     void GetNeighbors(ref Vector2I[] array, int x, int y)
    157.     {
    158.         InitVector2IArray(ref retVal);
    159.         if (x > 0) {
    160.             retVal [0] = new Vector2I (x - 1, y);
    161.         }
    162.         if (x < _influences.GetLength (0) - 1) {
    163.             retVal [1] = new Vector2I (x + 1, y);
    164.         }
    165.         if (y > 0) {
    166.             retVal [2] = new Vector2I (x, y - 1);
    167.         }
    168.         if (y < _influences.GetLength (1) - 1) {
    169.             retVal [3] = new Vector2I (x, y + 1);
    170.         }
    171.          
    172.         // diagonals
    173.  
    174.         // as long as not in bottom-left
    175.         if (x > 0 && y > 0)
    176.         {
    177.             retVal[4] = new Vector2I(x-1, y-1, 1.4142f);
    178.         }
    179.         if (x < _influences.GetLength(0)-1 && y < _influences.GetLength(1)-1)
    180.         {
    181.             retVal[5] = new Vector2I(x+1, y+1, 1.4142f);
    182.         }
    183.         if (x > 0 && y < _influences.GetLength(1)-1)
    184.         {
    185.             retVal[6] = new Vector2I(x-1, y+1, 1.4142f);
    186.         }
    187.         if (x < _influences.GetLength(0)-1 && y > 0)
    188.         {
    189.             retVal[7] = new Vector2I(x+1, y-1, 1.4142f);
    190.         }
    191.     }
    192. }
    193.  
     
    Last edited: Dec 9, 2016
    kebrus likes this.
  8. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    This is obviously some pretty old stuff and I sure didn't have in mind GC at the time, I just wanted something that it would work. Even the images are broken, maybe I can fetch the old ones and fix my post. How did you got here btw? (just curious)

    I highly recommend you to do proper diffusion instead of the star shaped one, I ended up using it for some simple mob AI in a square grid but with proper diffusion you can use it with a graph system and place it coarsely in a weird level design. This, in conjunction with other systems like path-finding or raycasting does wonders.
     
  9. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    Oldies but goodies, it must have been a time where the "upload file" button of this forum didn't exist. I found it thought google, only 2 forum thread on the subject.

    This diffusion is 8 direction, which is your bit on top of Anomalus' excellent stub. Seems to behave well without any funky corners unless I crank up one influence way up.

    I'd like to see what you made with it, it might not be the proper AI for me. I don't see need for a graph system, are you using it for a very large level where a giant grid would choke? Pathfinding - do you A* through the vector field? I get good results with following only the local maxima but I haven't added static obstacles yet.
     
  10. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    It was a 4yr old mobile hack and slash game. It ran on almost any device at that time and it used 2D sprites in a 3D gameplay. The maps were very small with a few props and hazards and the AI was competent enough to avoid them and surround the player, I think the max number of enemies was about 15 to 20 in the screen (about 25 draw calls). We scraped all path-finding because the maps were sufficient and only used raycasts for targeting and what not. So it scaled well regardless of the number of baddies. I know for sure most of the systems could handle more than just 20, it was just a design decision.

    Influence maps are very situational but I'm not really sure why aren't they more popular. It scales really well and adds an extra layer of information that you can use for a lot of things, in my case it completely replaced the path-finding.
     
  11. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    Did you floodfill the grid using manhattan distance to form a potential field? I see that this present implementation tends to get stuck Pathdfinding with potential field usually get stuck in corners, how did you avoid that without A*?
    Also did you end up calculating derived maps such as vulnerability and high level decisions?

     
  12. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    I didn't. Because when the level started had a counter I used that time to have it filled properly. You could pre-bake it or have it update on load.

    Because I used heavy time slicing on the diffusion part if something wildly different was happening on one edge of the map it would take a while for the "signal" to reach the other end but that didn't bother me much, the creepers weren't super fast and even if they were following the wrong data at one point it made it look like they were searching for their last know position which in my case was a nice side effect, might not suit yours tho. I do remember that the worst case scenario were maze-like levels because the signal would take ages to travel and by the time it reached the end it would cause some weird behaviors like the creepers suddenly going back and forth like they don't know where to go. But besides this case which could be fixed by have it update faster I didn't have any path-finding issues at all.

    The only problem I remember having was walls. I can't remember completely what I did for static objects but it was something in the lines of deleting the value in a final buffer so that calculations weren't affected by the missing values? not sure if it makes sense but basically I had this problem where if not done properly creeps would either be attracted to walls or avoid them but I know I got it fixed completely at the end, just can't remember exactly how.

    It was a simple game so there wasn't much of high levels decisions to be made, the creeps were avoiding each other and some hazards in the scene (but not completely to make it more natural). Some creeps were supposed to help other and I was going to use some group data to help in the decision making but they never left design so I can't help you much on that besides giving you my opinion.
     
  13. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    Just after I posted those questions, I went ahead and added obstacles, what I did then is zero out a neighbor that's in the wall and the diffusion gradient takes care of the pathfinding vector flow style, I like it and like you I find the erratic behavior very satisfying, like you say, as if the creature searches for a target while the diffusion reaches them. I just ran into my first thread artifacts, entire regions of the map pulsing, so I'm going to revise the map diffusion code and instead of the very inefficient calculate each map one at a time, do everything in one pass.

    On a side note it's funny how much easier creating fun behavior is, also zero bug which I have been told for a long time is the nice benefit of data oriented programming, makes me want to throw OOP out of the window.

    Did you override motion in the vicinity of a target? You mentioned raycast...

    For those interested, the code change:
    Code (CSharp):
    1. void GetNeighbors(ref Vector2I[] array, int x, int y)
    2.     {
    3.         InitVector2IArray(ref retVal);
    4.         if (x > 0) {
    5.             retVal [0] = new Vector2I (x - 1, y);
    6.         }
    7.         if (x < Width - 1) {
    8.             retVal [1] = new Vector2I (x + 1, y);
    9.         }
    10.         if (y > 0) {
    11.             retVal [2] = new Vector2I (x, y - 1);
    12.         }
    13.         if (y < Height - 1) {
    14.             retVal [3] = new Vector2I (x, y + 1);
    15.         }
    16.        
    17.         // diagonals
    18.  
    19.         if (x > 0 && y > 0)
    20.         {
    21.             retVal[4] = new Vector2I(x-1, y-1, 1.4142f);
    22.         }
    23.         if (x < Width-1 && y < Height-1)
    24.         {
    25.             retVal[5] = new Vector2I(x+1, y+1, 1.4142f);
    26.         }
    27.         if (x > 0 && y < Height-1)
    28.         {
    29.             retVal[6] = new Vector2I(x-1, y+1, 1.4142f);
    30.         }
    31.         if (x < Width-1 && y > 0)
    32.         {
    33.             retVal[7] = new Vector2I(x+1, y-1, 1.4142f);
    34.         }
    35.  
    36.         // zero out if they're inside an obstacle
    37.         for (int i=0; i<8;i++){
    38.             if (IsInsideObstacle (retVal [i]))
    39.                 retVal [i] = new Vector2I (0, 0, 0);
    40.         }
    41.     }
    42.  
    43.     bool IsInsideObstacle (Vector2I pos){
    44.         for (int i=0;i<_obstacles.Count; i++)
    45.             if (_obstaclesBounds[i].Contains (pos))
    46.                 return true;
    47.         return false;
    48.     }


    Screen Shot 2016-12-10 at 10.10.41 PM.png
    The big blue capsules avoid the combat zones sneaking around to the red cube
     
    Last edited: Dec 11, 2016
    AntonioModer likes this.
  14. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    49
    Is there a place where I can find a complete tutorial for a 3D influence map? I can find a lot of sites that explain the theory but none of them actually get into how to code it.
     
  15. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    This is it, download the project on the first post and apply the theory by adding maps.
     
  16. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    Yeah, pretty much, it had more conditions but the major factor was how close it was, I used raycasting because my creeps were throwing stuff and I wanted them to "see" the target so I reused ray-casting for both. I would probably do it differently today, it was a waste of resources because I never did anything meaningful with the rays that I couldn't do without them.

    [edit] got the old images restored for future reference
     
    Last edited: Dec 12, 2016
  17. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    49
    This works with multiple floors in a level?
     
  18. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    Seems that a state machine or a bt needs to take over in-range. I was trying to do all the decision making as a flocking mechanic by adding vector direction from multiple maps and, no luck.

    It's a 2D grid, you can have multiple 2D grids but connecting the floors together won't be as trivial, doable though.
     
  19. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    388
    If you implement it as a graph and not a grid you can have any shape you want. But this solution is only a 2D grid
     
  20. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    888
    Quick optimization : if you use navmesh agents instead of character controler you get a 5x speedup, plus quasi free collision avoidance so there is one less map lookup you need (maybe, sorta)

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.AI;
    4.  
    5. public class Mover : MonoBehaviour
    6. {
    7.     NavMeshAgent agent;
    8.  
    9.     [SerializeField]
    10.     float _speed=2f;
    11.  
    12.     public static int pathfindingIterationsPerFrame;
    13.        
    14.     // Use this for initialization
    15.     void Start()
    16.     {
    17.         //navmesh performance and init
    18.         pathfindingIterationsPerFrame = pathfindingIterationsPerFrame+10;
    19.         NavMesh.pathfindingIterationsPerFrame = pathfindingIterationsPerFrame;
    20.         agent = GetComponent<NavMeshAgent> ();
    21.         if (agent)
    22.             agent.velocity = velocity;
    23.        
    24.         character = GetComponent<CharacterController> ();
    25.         delayUpdateDirection += Random.value*delayUpdateDirection;
    26.     }
    27.  
    28.     [SerializeField]
    29.     float delayUpdateDirection=.1f;
    30.     float timer;
    31.  
    32.     void Update()
    33.     {
    34.         if (Time.time > timer) {
    35.             direction = Vector3.zero;
    36.             timer = Time.time + delayUpdateDirection;
    37.             var directions = GetComponents<IDirection> ();
    38.             foreach (var d in directions)
    39.                 direction += d.GetDirection ();
    40.             if (agent && agent.enabled)
    41.                 agent.SetDestination (transform.position+ velocity);
    42.         }
    43.  
    44.         velocity = direction;
    45.         velocity.Normalize();
    46.         velocity *= _speed;
    47.         velocity.y = 0;
    48.  
    49. //        transform.position += _velocity * Time.deltaTime;
    50.         if (character&& character.enabled)
    51.             character.SimpleMove(velocity);
    52.     }
    53. }
     
    kebrus likes this.