Fireflies is a project I started working on after finishing school this year. I was getting a bit tired of using third-party middleware, and decided I wanted to try something fresh that had a large focus on optimization.
The gameplay and visual style would be heavily based (as in “I’m not claiming this is original at all”) on an Indie XNA game called Aurora. It’s an RTS simplified down to it’s base essentials- units and unit production
Player start with a base which repeatedly generates units. if two enemy units collide, they are destroyed. Neutral bases around the field can be claimed by “investing” a certain number of units into them. Some bases can also be upgraded to produce additional units by investing units into the base. Invested units are destroyed. Bases lose health when enemy units collide with them, and do not gain any additional health if upgraded.
I’ve never tried making an RTS before, though I do have experience managing large numbers of objects, all potentially interacting with each other.
Collision detection between units is handled by a 50*50 hash table. Wherever possible, memory is reused as opposed to being initialized and deleted repeatedly.
The part of the game that I was really excited to get working on was AI. The most experience I had programming AI was acquired from working on Interstellar Excavation, and that also had a focus on strategically capturing key locations, albeit only with one unit per player.
The approach I’m going to try first with Fireflies is having the AI manage each base individually, coming to the best decision based on the base’s location and nearby idle unit count. To save performance, only one base will be considered per call to the AI Process function each frame.
I created a logic flowchart before working on it to organize my thoughts:
No, obviously there’s a bit missing, like a case for repairing damaged bases or defending ones that are under attack. This is because I wanted to test these key features before committing to the each-base-makes-its-own-decisions model.
A little bit of implemetation and bug fixing later, and this is what the above looks like as a function (At this point, I’d taken to labelling bases as “Lanterns”:
/*
* Processes tha AI for this Frame
*/
void CPlayer::ProcessAI()
{
//Get the Next Lantern in the list
++m_iCurrentLantern;
if(m_iCurrentLantern >= m_pLanternManager->GetLanternTotal())
{
m_iCurrentLantern = 0;
}
CLantern* pLantern = m_pLanternManager->GetLantern(m_iCurrentLantern);
CLantern* pTargetLantern = 0;
//Do I Own This Lantern?
if(pLantern->GetPlayerID() == m_PlayerID)
{
//Does this Lantern have a sufficent number of
//nearby units to attempt an aggressive move?
if(pLantern->GetNearbyFriendlyUnitTotal(m_PlayerID) >= k_iUnitsRequiredForAIAggression)
{
//Are there any nearby unclaimed Lanterns?
pTargetLantern = pLantern->FindNearbyUnclaimedBase(m_PlayerID);
if(pTargetLantern)
{
pLantern->OrderUnitsToTarget(m_PlayerID, pTargetLantern);
//End Turn
return;
}
//Can This Lantern be Upgraded?
if(pLantern->GetLevel() < pLantern->GetMaxLevel())
{
pLantern->OrderUnitsToTarget(m_PlayerID, pLantern);
//End Turn
return;
}
//Are there any nearby enemy Lanterns?
pTargetLantern = pLantern->FindNearbyEnemyBase(m_PlayerID);
if(pTargetLantern)
{
//Do I have Enough nearby Units to take it?
if(pLantern->IsThisTargetAcceptableRisk(pTargetLantern))
{
pLantern->OrderUnitsToTarget(m_PlayerID, pTargetLantern);
//End Turn
return;
}
}
}
else
{
//End Turn
return;
}
}
//or Is It Unclaimed/Hostile?
else if(pLantern->IsAligned() == false || pLantern->GetPlayerID() != m_PlayerID)
{
//Send any nearby Units of mine to start capturing it.
pLantern->OrderUnitsToTarget(m_PlayerID, pLantern);
}
}
Initial results look okay- technical performance is good, but the simplicity of the above chart is reflected in how the AI plays; each player expands naturally until a stalemate forms over capturing the middle base.
At this point, anytime the middle base is beaten back to neutral status, THIS happens. It’s a result of the Unit-requirement for taking neutral bases being significantly lower than attacking an un-upgraded enemy base (100 vs 150).
Eventually, one player is able to overcome the other two, but there’s no strategy involved- it’s just a result of who gets what before all that’s left is the middle base.
So, what I’d like to do now is give the AI some more complicated, interesting options- Determining where the frontline is, reinforcing bases that are under attack, and making more wise decisions about when and where to attack. If you’re interested in seeing more of my source code samples, you can always contact me.