3
\$\begingroup\$

I have a character building constructs all over a pretty large map. My game blends elements of RTS and fast paced action in a room-based environment where players can only see one room at a time.

I would like to be able to do physics simulation, projectiles and high frame rates in the room the player is in, while also maintaining a significant number of AIs (a few thousand, maybe) wrecking all the buildings that the player has created in other rooms. I want their behavior to be approximately consistent with what they'd do in front of the player.

I have a few ideas I could use feedback on, but I'm mostly wondering what creative approaches are out there and where I can read about this kind of architectural problem (must be common for complicated RTS games)

My ideas:

  • Avoid particles, projectiles, turn off anything that's un-needed for non-visual simulation
  • Switch all offscreen objects to "offscreenSim" versions of expensive components or somehow switch from running their Update() methods to running a SlowUpdate() method
  • SlowUpdate would run in another thread and do the same thing as Update, except it would be called less often with much longer deltaTimes. Maybe some semaphore would throttle them to share a fixed total pool of worktime allowance per second.
    • i.e. the more objects in the simulation, the longer between SlowUpdates.
    • They'd also probably need to use path-intersection based triggers instead of collisions, since they'd be at risk of skipping through normal sized colliders.
  • Or.... Maybe I could recreate a completely simplified graph representation of rooms and their inhabitants and just say "there's a path for enemies to go between A->B->C, there's a turret in B, here's a formula for enemy damage/turret and turret damage/enemy."
    • This approach seems like it could save a lot of performance, but making two simulations deterministically consistent, plus translating between both representations sounds like a major headache (though maybe players wouldn't notice inconsistency)
  • Separate the world into clusters of rooms and leave clusters the player isn't in totally inactive. When a player goes between rooms, run the entire simulation of the cluster they're entering up to that moment in time at 10x speed while showing them a loading screen (I'd worry about keeping them on a loading screen for too long)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ The offscreenSim/SlowUpdate thread using a simplified graph representation is effectively how we solved this problem in Starlink: Battle for Atlas, to have the Legion and friendly factions continue growing/battling on other planets/areas outside the loading ring. We deliberately broke with 1:1 fidelity, allowing the game to run slower in the off-screen parts (so the player had more time to respond to a distress call from over the horizon). \$\endgroup\$
    – DMGregory
    Commented Dec 7, 2021 at 12:47
  • 1
    \$\begingroup\$ Wow! So validating to hear that my whiteboarding led to something similar to what was used in an impressive game. Thank you for sharing your thoughts @DMGregory, and good call about actually slowing down to make life more manageable to the player. It's all letting them have fun conquering the crises, after all. Any resources you found helpful while designing this? Did anyone on your team publish any posts/articles about the tradeoff decisions in your architecture? \$\endgroup\$
    – pixelpax
    Commented Dec 7, 2021 at 20:30

1 Answer 1

0
\$\begingroup\$

What @DMGeorgory said shows that your first three ideas are in fact the complete way. now in case you were also looking for a time-affordable way, you could record every important variable, make a simple graph with maybe length of 4 ( or less ) , then create the next value in a way that makes sense to the record. if for example the array val_rec[] is the record for float val, you can get the next value for val like below:

float maxRand = 4+3+2+1; // because record is length of 4
float rand = Random(0, maxRand);
float next;
if(rand < 4)
{
    rand -= 4;
    next = Random(-val_rec[3]/10, val_rec[3]); // the [3] means 4th element, considering that is the latest record, so it has more chance than other records
}
else if(rand < 3)
{
    rand -= 3;
    next = Random(-val_rec[2]/10, val_rec[2]); // the second latest record
}
else if(rand < 2)
{
    rand -= 2;
    next = Random(-val_rec[1]/10, val_rec[1]); // the third latest record
}
else
{
    next = Random(-val_rec[0]/10, val_rec[0]); // the eldest recodrd. least worthy 
}
val = next;

and of course you could check different values for your own system and see what works best :D

cheers.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .