I'll start with an introduction to what I have and a video showing it and then list my problems. The game is as follows, AI-controlled units come at each other from both sides of the screen, no user input, 2D auto battler practically. Component data-driven ECS, I am using Mark Daves Imaps approach. I currently have Occupational Imaps (that tell me what tiles are occupied), Proximity Imaps (that tell me the influence of each unit for both players), and I'll add a threat map as well. Units are varying sizes but mostly between 32x32 - 48x48, tiles are divided into 4x4 and Map size is 800x512. This is happening on a game server so I can't just brute force it and rely on the CPU's raw power.
You can see the gameplay here https://imgur.com/a/yugELUk
My architecture is as follows.
1) "World" class that holds the entire state of the current match inside it, calls updates that update the Grid (Imaps) and EntityManager who calls the systems and the ai and so on.
2) ECS calls AI for each unit to generate an action to perform, eg move, attack, cast a spell
3) Systems then loop through entities and do what they have to do, move it, attack...
My goals for the AI are:
1) Don't overlap with other allies ( I use some steering in the movement system to help with this)
2) Go towards the closest enemy and attack him, if there are other allies there surround him
3) If there are too many allies already just find another target
Now here are my problems. How do I use Imaps in this setup without butchering the performance? As I don't have a well enough understanding of Imaps already which makes it hard to visualize what combinations I can create.
I currently have 1 WorkingMap that I just resize (to save some memory and GC) every time AI needs it, and this is how I use it to achieve the thing in the above video.
// 1) Create working map size of interest map
proximity := 2 * int(movComp.Velocity.Magnitute()) * 5
if proximity == 0 {
proximity = 6 * int(movComp.MovementSpeed)
}
sizeX := grid.MapWidth/ grid.TileSize -1
sizeY := grid.MapHeight/ grid.TileSize -1
wm := g.GetWorkingMap(sizeX, sizeY)
// 2) Add everything up
x, y := grid.GlobalCordToTiled(posComp.Position)
engine.AddMaps(g.GetEnemyProximityImap(entities[index].PlayerTag), wm, x, y, 1) // enemy prox map
engine.AddMaps(g.GetProximityImaps()[entities[index].PlayerTag], wm, x, y, 0.5) // allies prox map
engine.AddMaps(grid.GetProximityTemplate(movComp.MovementSpeed).Imap, wm, x,y, -1) // my prox template
wm.NormalizeAndInvert()
engine.AddMaps(g.GetInterestTemplate(proximity), wm, x, y, 1)
// Currently I do this, but I'd like to have a check on whether or not we can attack
//someone, surround someone and etc.
targetX, targetY := wm.GetLowestValue()
moveTowardsPoint(index, targetX, targetY, w)
Is there a way to use only 1 working map per AI iteration and to check for all of the goals I listed above (doesn't have to be necessarily 1 but something that isn't asking for a new Workingmap for every check)? I have no idea how to approach this kind of AI behavior without a bunch of if-else statements and multiple working maps. I am calling the AI once a tick for each unit as well which is probably way too much already (any advice on how to improve this as well is appreciated).
Another problem is when there are no enemies nearby, it doesn't really know where to go (it should go towards the other end of the screen or enemies center of mass). I went around this by having their working map be the entire screen, which is 200tiles * 128 tiles, but that kinda defeats the purpose of the benefits of smaller working maps.
Hopefully, I presented my problem properly, If you guys need any additional info please tell me.