1
\$\begingroup\$

I am looking for ways to improve my traffic AI for endless racing game.

Currently, I have hard coded situations where randomly cars will be spawned ahead of user bike depending on position of player.

The thing most bothering me is how to provide user challenging situations where he will feel fun to play. Good example of such AI can be found here, https://www.youtube.com/watch?v=CJozc6PA9lI

What are the ways you guys would go on implementing Traffic AI for endless racing? What's your suggestion on same.

\$\endgroup\$
5
\$\begingroup\$

I've never really thought about it since these type of games don't amuse me much. This is, essentially, "Frogger", except that you are travelling parallel to the enemies instead of perpendicular to them. With that in mind and having watched the YouTube you linked, here's what appears to be happening:

  1. Every X seconds, spawn a group of Y cars
  2. Y appears to begin at 2 and increment by 1 or 2 each "level"
  3. Speed increases each "level"
  4. X slightly decreases with each "level"
  5. After a few groups of 5, it appears to change to more frequent groups of 3
  6. The composition of each group is psuedo-random

A minimal set of behaviors:

  1. Each type/color of car has a specific behavior.
    Examples include:
    • Right/Center/Left lane only
    • Start left, move and stay right
    • Start left, move right, move center
    • Random lane change either way
  2. Cars may not drive through other cars

Combined with a bit of randomness, these deceptively simple rules should make a good game.

Optional behavior examples:

  1. Some vehicle types speed up or slow down when near player
  2. Some vehicle types prefer to change lanes abruptly when near player
  3. Cars moving faster than player, passing them from behind

For spawning (psuedo-code):

ArrayList enemies;
ArrayList expiredEnemies;
int groupSizes[maxLevel] = { 2, 3, 5 };
void Update(float deltaTime) //Game
{
   if (enemies.Count == 0)
      SpawnAGroup(groupSizes[currentLevel]);
   expiredEnemies.Clear();
   foreach (enemy in enemies)
     enemy.Update(deltaTime);

   //Can't delete enemies from enemies while iterating it so we cleanup afterwards
   foreach (enemy in expiredEnemies)
     enemies.remove(enemy);
}

float velocity;
void Update(float deltaTime) //Enemy
{
   bool laneChangeEnabled = true;
   if ((player.lane == this.lane) //Same lane as player
   {
      if (this.y > player.y) //player is behind this enemy
        if (this.y - player.y < carLength) //closer than carLength
          velocity = 1.1; //Speed up a little
   }
   else //Different lane than player
   {
      if (this.y > player.y) //player is behind this enemy
        if (this.y - player.y < carLength) //closer than carLength
        {
           laneChangeEnabled = false;
           velocity = 0.9; //Slow down
        }
   }

   if (laneChangeEnabled)
   {
     int random = GenerateRandomIntInclusive(1, 100);
     if (random > 95) //"5%" chance to change lanes
     {
        int direction = GenerateRandomIntInclusive(0, 1); //0 or 1
        direction = (direction * 2) - 1; //Scale to (0 or 2) then move to (-1 or 1)
        if (player.lane != this.lane + direction) //If player isn't in target lane
           position.x += direction;
        else //Player IS in target lane
           if (player.y < this.y - carlength) //Is player more than one car length behind?
              position.x += direction;

        //Off-roading not allowed
        position.x = Math.Clamp(position.x, leftMostX, rightMostX);
     }
   }

   position.y += velocity * deltaTime; //Always go forward

   if (position.y < 0) //Off-screen
   {
      expiredEnemies.add(this);
   }
}

If this is your road:

enter image description here

This code tries to spawn numCars enemies, giving up if it takes too long

ArrayList<int2> newCars;
void SpawnAGroup(int numCars)
{
   newCars.Clear();
   int tryCount;
   int2 testPosition;
   for (i = 0, i < numCars; i++)
   {
     tryCount = 0;
     testPosition.x = GenerateRandomIntInclusive(1, 3);
     testPosition.y = 6 + GenerateRandomIntInclusive(1, 3);
     while (!isValid(testPosition))
     {
        if (tryCount++ == 10) //Don't try forever
          break;
        testPosition.x = GenerateRandomIntInclusive(1, 3);
        testPosition.y = 6 + GenerateRandomIntInclusive(1, 3);
     }
     if (tryCount < 11) //If giving up, tryCount == 10++
     {
        //Spawn new car i
        enemies.Add(new Enemy(RandomEnemyType(), testPosition));
        newCars.Add(testPosition); //For isValid()
     }
   }
}

bool isValid(int2 position)
{
   if (newCars.Contains(position)) return false;
   ... //Now all the "rules" go here
   //if (currentLevel == 1) //Can also apply certain rules to specific levels
   return true;
}

Verify the system is working before adding a bunch of rules. The only rule I've included is that we will only spawn one car per square. The rules should be very simple (a pile of if's is fine) but it shouldn't take an enormous amount of them. Once it's working, another very important rule is to not spawn more than 2 enemies for any given Y (road block).

I ran a few "simulations" (tic-tac-toe):

enter image description here

It seems:

  • We need to spread cars evenly on the X (top "too easy")
  • We can't fill an entire row (top "too hard")
  • At least one stationary car is preferred (bottom "too easy")
  • All lane-changers should not lane-change in the same direction (bottom "too easy")

And, for every X cars spawned (3-ish):

  • At least one lane-changer is preferred (bottom "ok" fixes bottom "too hard")
  • At least one bi-directional lane-changer is preferred (top "ok")
  • Absolute maximum of 2 bi-directional lane-changers (top "unreliable")
  • Too many bi-directional lane-changers becomes dependent on "pushing" cars out of the way via the enemy speed-up/slow-down mechanism (bottom "unreliable")

Important note:

All of this relies on "random" numbers which aren't actually random. If you do not change the "seed", the game will appear to be random the first time you run it, but every time after will be an exact re-play of the same "map", so to speak. You should definitely generate a new seed at game start and then, again, every X seconds, or each level; not too often.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.