This is a personal project. After playing Expedition 33, I wanted to experiment with its combat system. Everything was made from scratch (except for the models, which are Megascans). (Work in progress)
void ABattleManager::AddCharactersToSpeedArray(TArray Players, TArray Enemies)
{
for (int32 i = 0; i < Players.Num(); i++)
{
AExpedition101Character* player = Players[i];
float tempSpeed = player->PlayerBaseSpeed * player->PlayerSpeedMultiplier;
if (PlayersSpeedArray.Num() == 0)
{
PlayersSpeedArray.Add(player);
}
else
{
bool bAdded = false;
for (int32 j = 0; j < PlayersSpeedArray.Num(); j++)
{
float currentSpeed = PlayersSpeedArray[j]->PlayerBaseSpeed * PlayersSpeedArray[j]->PlayerSpeedMultiplier;
if (tempSpeed > currentSpeed)
{
bAdded = true;
PlayersSpeedArray.Insert(player, j);
break;
}
}
if (!bAdded)
{
PlayersSpeedArray.Add(player);
}
}
}
for (int32 i = 0; i < Enemies.Num(); i++)
{
AEnemybase* enemy = Enemies[i];
float tempSpeed = enemy->EnemyBaseSpeed * enemy->EnemySpeedMultiplier;
if (EnemiesSpeedArray.Num() == 0)
{
EnemiesSpeedArray.Add(enemy);
}
else
{
bool bAdded = false;
for (int32 j = 0; j < EnemiesSpeedArray.Num(); j++)
{
float currentSpeed = EnemiesSpeedArray[j]->EnemyBaseSpeed * EnemiesSpeedArray[j]->EnemySpeedMultiplier;
if (tempSpeed > currentSpeed)
{
bAdded = true;
EnemiesSpeedArray.Insert(enemy, j);
break;
}
}
if (!bAdded)
{
EnemiesSpeedArray.Add(enemy);
}
}
}
}
Turn order management is handled through two dedicated functions in the battle manager, both adaptable to encounters involving one or multiple enemies and players. The first, AddCharactersToSpeedArray(...), computes each character’s combat priority and organizes them into two arrays, one for enemies and one for players, both sorted in descending order of priority. These arrays are then used by the subsequent function to determine the turns order.
TArray ABattleManager::CalculateTurns(TArray Turns)
{
for (int32 k = 0; k < 4; k++)
{
for (int32 i = 0; i < PlayersSpeedArray.Num(); i++)
{
AExpedition101Character* player = PlayersSpeedArray[i];
AEnemybase* enemy = EnemiesSpeedArray[i];
float enemySpeed = enemy->EnemyBaseSpeed * enemy->EnemySpeedMultiplier;
float playerSpeed = player->PlayerBaseSpeed * player->PlayerSpeedMultiplier;
FTurns Turn;
if (enemySpeed > playerSpeed)
{
int32 numTurns = round(enemySpeed/playerSpeed);
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, *FString::FromInt(numTurns));
for (int32 j = 0; j < numTurns; j++)
{
Turn.Enemy = EnemiesSpeedArray[i];
Turn.bIsPlayer = false;
Turn.Player = nullptr;
Turns.Add(Turn);
}
Turn.Enemy = nullptr;
Turn.bIsPlayer = true;
Turn.Player = PlayersSpeedArray[i];
Turns.Add(Turn);
}
else
{
int32 numTurns = round(playerSpeed/enemySpeed);
for (int32 j = 0; j < numTurns; j++)
{
Turn.Enemy = nullptr;
Turn.bIsPlayer = true;
Turn.Player = PlayersSpeedArray[i];
Turns.Add(Turn);
}
Turn.Enemy = EnemiesSpeedArray[i];
Turn.bIsPlayer = false;
Turn.Player = nullptr;
Turns.Add(Turn);
}
}
}
return Turns;
}
This is the second function, it receives the two arrays as input from the previous one and compiles a third array that determines the exact turn order, adding each character in sequence. It also calculates how many consecutive turns a character can take based on their speed relative to the second-fastest. I decided to use arrays rather than other data structures since the turn array is directly passed to the Widget Blueprint for on-screen visualization. Using alternative structures would have required conversion, eliminating the benefits of using them in the firs place.