(As of writing this post was made on 2024/01/13, but I’m having trouble deploying this page with a date set)

It has been a while! Sadly I’ve kind of neglected this website a bit, but unfortunately a part of that is just that there hasn’t been a huge amount of interesting stuff that I’m able to share. Since my last update I got a job in industry at Zero Latency and officially shipped my first game last year. I’ve learned an enormous amount in my time there and have become much more confident with my abilities in Unreal (although there’s never a lack of new things to learn)

In the meantime, I’ve started working on a couple of new projects which I might talk more about in the future, but also Timesplitters: Rewind is still quietly moving along. I didn’t do much for the project in 2023, but years prior I was fairly heavily involved in bug fixing still which is not really interesting/worth talking about (and anything worthwhile would require paragraphs of explanation on why things are set up the way they are)

But finally, I have something to write about! My first major task for this year is going to be redoing the arcade AI for TS:R. I’m going to be writing these as a sort of more stream of consciousness style blog post for how I work and break down what I’m doing somewhat and issues I have along the way (which is all part of the process). I am still fairly new to AI in Unreal, having only really read through the docs and made a fairly simple AI for another project so this will be something of a learning exercise for me too. I’ll aim to wrap up this series with a much more concise technical breakdown for people who are more interested in the final product vs the “how I got here”.

History

But first, a bit of history: the AI made for the game when the project was handed over worked fine-ish for story but has always had some issues with arcade modes (though it’s been a while since I heard the QA feedback on it and have since forgotten about what the issues are). The original single behaviour tree is also very messy and hard to maintain:

Old behaviour tree zoomed out.
Old behaviour tree zoomed in left.
Old behaviour tree zoomed in right.

There’s logic in all sorts of places trying to do specific things for specific game modes, so even without diving into the BTDs or BTTs here, it’s challenging to figure out the flow of logic and a lot of branches should never be getting run when it’s game mode specific logic.

At some point in the last couple of years, we got a programmer on the team who had some more experience with AI to help fix some of the issues, and they opted to rewrite the entire thing. At some point after getting combat mostly working for Deathmatch and Team Deathmatch, they disappeared from the server never to be heard from again.

Since then, someone else who didn’t understand how AI worked in Unreal stepped in to try and patch the holes left by the last person to get it working for the remaining game modes, which leaves the Behaviour Tree in its current state:

Newer behaviour tree.

This is much cleaner, but regrettably works even worse and on closer inspection there’s a lot of strange nodes in places. There are frequent issues with bots getting stuck or doing silly things and they are frequently mentioned by QA to be one of the major things that need fixing for the game to release.

So here I am, a relative novice with Unreal AI repeating history by making the AI again from scratch. Hopefully it will be a slight improvement this time.

Research

So before I get started, I need to actually figure out what my end goal is. Going through some video footage of the original games (mainly 2 and 3) I found the following:

  • Bots will aggro you if shot if they’re not currently already under fire
  • They will ONLY punch the player if there is no weapons available on the map
  • If in the middle of a shootout, they will grab health/better weapons/powerups if they’re close by

For the most part, the rest of the logic seemed pretty standard shooter AI logic, but it’s something I will need to periodically go back and check against the original to make sure it’s sensible.

Next, I wrote down all the game modes we have available and wrote out which modes would need some sort of unique logic to handle special rulesets:

  • Simple modes (kill other players)

    • Deathmatch
    • Team Deathmatch
    • Elimination
    • Team Elimination
    • Shrink
    • Leech
    • Regeneration
    • Vampire
  • Simple mode with simple additional special condition/target

    • Bag Tag (take objective)
    • Thief (pickup coins)
    • Gladiator (only target gladiator)
    • Escort (target escort)
    • Monkey Assistant (monkeys are valid targets unless they’re yours)
  • Special modes (will likely require more complex trees/logic)

    • Virus (non-combat with tagging)
    • Flame Tag (non-combat with tagging)
    • Capture the Bag (capture/return bag, maybe attacking/defending bots?)
    • Knockout (grabbing bags and returning to base)
    • Zones (capturing zones, otherwise normal)
    • Assault (attack/defend base)

Whew, this will probably be a bit of work. Also last stand isn’t considered here because it uses separate unique AI from story to allow for things like zombies and splitters behaving as they would there.

Getting Started

The task ahead is fairly significant, so for the moment I’m going to focus just on getting the combat working and correct, then I can start to specialise the logic a bit more.

Current combat logic branch

I roughed out a tree with my desired behaviour just adding some comments in places where I didn’t have the logic setup yet, and slowly built it out to the current iteration while adding and fixing some BTDs along the way:

The way I roughed out the logic was:

  • If the bot has a valid target
    • If they have enough ammo to shoot the player, they’ll move within the range to shoot them, then start firing.
    • If they don’t have enough ammo, they’ll switch to the next best weapon.
    • If there is no next best weapon with ammo, they’ll check if they can punch the player, then chase the player to punch them
    • If they have no ammo and can’t punch the player they’ll run away (just a wait node for now for testing purposes and later this will likely contain other behaviour)

This seemed sensible to me, but there were a number of problems with this setup in no particular order:

  • BTD_WithinFiringRange is not enough to satisfy shooting checks. Currently all this checks is if another player is inside of the maximum weapon range with a distance check. The original graph had BTD_CanSeeEnemy but that was doing a box trace by channel inside of it, and had some other strange logic going on. To make this work I’m going to need some kind of validation to check the AI has line of sight to the current target since distance checks aren’t enough.
  • The Move To on node 21 (to my knowledge) can’t have a dynamic acceptable radius set, which means that the bot will move up to the acceptable radius and attempt to fire (which is usually immediately next to the target). The acceptable range needs to be per weapon dependent because a baseball bat and a sniper rifle have very different max ranges.
  • Looking at the debug when the BT is running, once the bot ran out of ammo they would frequently flick between the BTD_HasAmmoAvailable and BTT_EquipBestAvailableWeapon which seems not optimal
  • The “duration” based system of shooting is a bit strange. Currently the tree will lock the bot into BTT_FireWeapon for 3 seconds each time while also running the BTD_DodgeAnimScope (which plays a dodge animation). It might be better to run the dodge behaviour as a parallel against the other firing types, which I’ll look at doing when I make more improvements to that system.

These were the immediately obvious issues once I started testing, though I’m sure more will crop up later.

Next Tasks

Planning out the next phase of work, I set myself up with the following tasks for fixing the outstanding issues:

  • Set up a service for validating if there is line of sight to the player
  • Set up a more unified system for fire/alt fire/throw grenade
  • Potentially make a blackboard key for current ammo available so I can check against that instead of running a BTD to switch weapon (not 100% sure how this will work yet though)
  • Initialise the game mode as a blackboard key somewhere to make BTD_CanPunch just be a check against a blackboard condition. This will also be more valuable in future when we have game mode specific logic.

The one other major thing I’d like to do is add an AI sense for when the bot is damaged. I’d like to make it so that a bot will immediately switch from their current target to an aggressor, provided that they are not already under attack from someone else.