Selecting Random Enemies
Overview
See LV_SelectRandomActors in the example map for an implementation.
Loot Tables are very effective, but sometimes you may want to associate data with distributions that can't be placed in data tables (e.g., the data was sourced at runtime). For this, we may use the same design as loot tables but associate our data instead of Data Table Row Handles.
Suppose we have a lightning spell which bounces between enemies randomly, with closer enemies more likely to be selected. We can't use a loot table for this because of two reasons:
- The amount of items (enemies) is based on runtime data.
- The weights of each item (enemy) are based on runtime data.
Suppose we are casting this spell with three viable targets for the next bounce:
Table A (Possible Targets) | ||
---|---|---|
Index | Distance | Enemy Reference |
0 | 100 | BP_Enemy_0 |
1 | 400 | BP_Enemy_1 |
2 | 500 | BP_Enemy_2 |
Suppose the 'enemy references' are references to spawned actors in the level. This is the data we need to actually target the enemy.
We need to convert this into a discrete distribution, but we can't use the distance values as weights directly because we want closer enemies to be more likely. Instead, we can subtract the weights from the maximum radius of the spell:
Array B (Target Weights) | ||
---|---|---|
Index | Weight | |
0 | 500 | |
1 | 200 | |
2 | 100 |
Each enemy's weight was set at 600 (maximum range of the spell) - distance to that enemy. We can construct a distribution out of this array:
Distribution C (Unlimited) | ||
---|---|---|
Index | Weight | Probability |
0 | 500 | 62.5% |
1 | 200 | 25% |
2 | 100 | 12.5% |
You could use a discrete distribution, Sum Array, Sum Tree, or Alias Table. The structs will be much faster on performance (albeit less user-friendly).
Finally, we still have an array of actor references which we want to associate to this distribution:
Array D (Actor References) | ||
---|---|---|
Index | Actor Reference | |
0 | BP_Enemy_0 | |
1 | BP_Enemy_1 | |
2 | BP_Enemy_2 |
By 'associate', we mean each item in the distribution has a corresponding item in the array. You could also view the distribution/array as columns in a table, with each index being a row. As such, it's important to ensure the distribution and array are the same size.
When we sample from the distribution, we'll receive the index of the enemy we picked. Then we can select that enemy's reference from the array.
When using this method, we have to ensure the size of the distribution matches the size of the data we're associating (in this case, enemy references). Otherwise the sampled indices may be invalid.