Data Structures and Approach for AoE effects

Started by
11 comments, last by Codman 14 years, 2 months ago
No matter how hard you try, you can't make it good from the beginning. The reason is you have no idea if this will be a problem or not. The chosen solution may be better than a naive solution, but if it ends up adding a layer of complexity in your architecture and there was no bottleneck after all, you lost the time it took to create the solution and the time it will take to maintain the solution. At that point, the good solution was the bad one and the bad one was the good.

Before doing any optimization, make sure you profile the code first, identify real bottlenecks and then optimize. If you already have a naive solution that you know works, you can validate your new solution because you have test data. Without it, your optimized solution can be faster, but return bad data and you will never know until you are deep in complexity. It will also allow you to benchmark the solution. Sometimes, complex solutions cause bottlenecks elsewhere. Will maintaining whatever structure you choose cause more overhead than checking for each unit every frame? You can't know until you do them both in a real scenario.
Developer for Novus Dawn : a [s]Flash[/s] Unity Isometric Tactical RPG - Forums - Facebook - DevLog
Advertisement
Quote:Original post by gexxx
Stuff that happens very often and needs "complex" computations is the first thing to be optimized.

A range calculation is not complex. To see if the distance between two points P1 and P2 is lower then some threshold d you simply have to do the following

v = P2 - P1
if (dot(v, v) < d*d) {
// do something...
}

It is not composed by a lot of operations. The naive method has a better data locality and cache usage pattern then the quadtree method and it is better if the number of units is low. On the other hand, if there are a lot of units, then the quadtree method can be faster. There isn't therefore a method which is better in every case, but it depends on the game.
I don't know if the following approach actually works because I never get to implement it. I just considered it sometime in the past.

You need to UPDATE ALL UNITS every simulation cycle. Auras and AOE effects should be updated before units. The most important thing when doing updates is to keep data structures and order of processing cache-friendly or you'll horribly waste CPU power.

Auras (which applies to fog of war also):
a) Each cell of a grid contains the auras which apply to the cell (eventually splited per "player" and "target type" - offensive/defensive). Most certainly there will be a hard-cap for number of auras that a cell can remember. Here you can pick something to allign nicely in memory in case you want to parse the map-grid at a later time cell-by-cell for any fancy reason you may find.

b) You can check-sum each cell to know if the status of any aura/applied auras has changed since the last iteration. This helps you to cache previous auras on unit side and re-compute only when status change (i.e. an aura fades or changes parameters; HINT: plenty of bugs may happen in this piece of code, but you can dodge hardcoding fixes with elegance and help from design side - just the type of buffs/debuffs and type of targets clear from the start :) ). For the first implementation, go with a non-cached version.

c) When an aura is enabled, use a circle fill algorithm to register your aura among the relevant cells. I believe Warcraft 3 stacks "effects" in pre-designed slots in each cell or uses the maximum one of them. This is a design restriction you have to account for (again, make it clear inside your mind how do you want the mechanics to work before you start coding).

d) When the aura-generator unit moves, use an optimized algorithm to unmark cells "behind unit" and mark the new aquired cells (I can't recommend you right now something, but I read a few of them around the Internet). Instead changing all the cells that the circle covers, you modify only the "new" and "outdated" ones (the bigger the circle is, the less they count from circle area - so you get performance increased returns with higher circle range). DO NOT USE linked lists, use a pre-allocated cache-friendly data structure and hopefully a data-processing oriented code (SIMD) for lighting-speed performance :).

e) When the aura-affected unit moves, compute the index of grid-cells that affect it (note that you may have a different grid with less divisions for implementing mechanics then the one using for movement).

Picking units (with mouse - I wrote it and then I noticed it was about AOE):
Just cache units visible in current view. Because you dont have restrictions regarding the number units that can stack over a cell or their size (warcraft 2 and most of older games had unit equal to a cell and most of 2 stacked units ground + aerial, in Starcraft they did a trick making an unit cover multiple mini-cells), it's a good idea to just have a proxi-bounding-rectangle stored in a cache-friendly data structure and iterate through them. Further optimisation, like selecting the unit under every next click can also be made plus pixel-perfect selection, but all these are polish and should not be in the initial implementation.

AOE stuff:
With some changes, code from auras should apply here as well. You will not need the "moving circle" filling part and there might be additional design restrictions (i.e. dmg cap for aoe effects). If you want dmg-cap you may register units to dmg source when you update them (as you did when checking auras for their cell) and apply the dmg after making further calculations (like dividing the entire dmg to number of registered units or other design requirements).


I hope this will answer your question and come again if you need to find out something new. :)
-----------------------------How to create atmosphere? Bring in EMOTIONS!

This topic is closed to new replies.

Advertisement