Player Flow in Team Fortress 2: Part 1
If you just want to skip to the demo (desktop only), scroll to the bottom!
With the success of the heatmaps.tf site among level designers and my general love of playing with data I thought I'd try my hand at creating a flow tracker as part of my final year dissertation - the results below are from the initial experimentation and working prototypes, and they're already looking positive.
An orthographic projection of pl_ym2k15_a2 displaying player flow. Each line represents a different person, coloured by their class.
So, first off I should probably explain what I mean by "flow tracking" - Flow tracking, in this context, means taking regular samples of a player's position and plotting those on a chart. This is useful to level designers because it allows them to very quickly identify routes that are underused, or locations that tend to misdirect players.
As we found out this weekend past, it's also quite useful for tracking down areas where players are exploiting. As you can currently see every data point collected by the system any deviations from the standard paths that player's take is immediately obvious.
An example of an out-of-map exploit in cp_overgrown_b2 - high jumping classes are able to walk the top of a "clip" brush and walk around the map on it.
Collecting the data
Selecting a sampling interval for anything is always difficult - with this experiment I wanted something that was precise enough that you could infer the overall layout of the map without using too much of the network bandwidth of server's providing the data. As a base I started off sampling locations once a second, which proved to be far too infrequent and produced diagrams of reasonbly low quality - sadly I didn't take any images of that, so you'll have to trust me. Eventually I settled on sampling on 3 times a second, however this produces a fair bit of data: around 3MB an hour on a full server. That might not seem like much, but when you want to render the data client side, every little helps in getting it to the client faster.
To rectify this before becoming available to clients, collected data is passed through Simplify.js which uses the Douglas-Peucker algorithm to reduce the number of points in a line, while retaining its overall shape. Typically this reduces the size of the overall line data by up to 60%. Combined with standard GZIP compression this brings the overall payload size down by up to 85%, without affecting the data quality noticeably.
Ideally I would pull the entirety of the player position data from a Source Demo (which is basically a dump of all the network packets sent during a match) and parse it to create incredibly detailed player paths, but sadly no one ever bothered reverse engineering TF2's demo format, such things are possible for Counter Strike: Global Offensive however.
Drawing the data
Currently the data is drawn very crudely, as is the way with such functional prototypes. Initially the data was simply fed into three.js as line geometry, due to the low sampling rate, this produced relatively jagged edges which were visually undesirable. As a quick experiment I decided to push the data through Three.js's spline class to smooth off the corners, and that looked so good that I stuck with it.
Thankfully while I was writing this post I got a few browser crashes while using the tool, so I decided to take a look at why: it turns out that Firefox doesn't much like having every line turned into a 2000 point spline curve on every page load - every refresh was taking up a whopping 900MB of memory and the Garbage Collector couldn't keep up. As a result I decided to vary the amount of spline points used based on the original length of the line, this reduced memory-usage to a much more reasonable 600MB (though even that seems high, so I'll have to keep looking into it).
Interpreting the data
In the current state of the demo, interpreting the data isn't the easiest of affairs, considering the charts turn into a quagmire of tangled spaghetti with more data. Thankfully the tool exposes a way to show only specific classes, which makes the situation slightly easier.
For example below is an image of the flow map for pl_emerge_a2 with the level underlaid showing a route entirely unused by Engineers throughout an entire play test. This could be fixed by additional making the alternative route more obvious, although the author might find that the route used is always preferred due to it's tactical advantage. Other classes used the alternative route throughout the play test, which suggests that Engineers (typically a defensive class) didn't want to advance through such a narrow and heavily contested choke. My theory is that this choke is mainly used by the defending team, and ignored by the attackers, however you can't currently filter by team, so that'll have to wait for another blog post.
Switching the class filter over to Sniper we can easily see various sniper nests in a map. The image below is back on cp_overgrown, with the left hand image showing the sniper nest and the image at right displaying the data I used to infer that it was a sniper nest. There's another sniper nest on the right hand side of the map, which I've also circled - it's quite a bit less used though. These nests are identified by noting the amount of players entering an area versus the relatively low number leaving it.
The flow for this map has attacking players coming from the bottom of the image, attempting to advance into the area in the top of the image.
This quick and functional mock-up shows the immediate practicality of collecting player flow data. In my dissertation I hope to expand on that by providing additional tools for level designers to interpret the data. Finding the number of players that pass through an area by drawing it on the tool and comparing it to another area would save users from having to guess from the tangled spaghetti which route is more popular, and by how much.
There's also some work to be done on the data collection side, currently the functional mock-up collects a lot of repetitive data to ensure that each data point can be appropriately attached to a line and doesn't collect the player's team, so I'll be fixing that over the coming weeks.
I'd also like to draw the underlying level structure underneath the data. As a TF2Maps.net staff member, I probably have access to one of the largest collection of maps ever assembled, at current count I have just shy of 5000 maps weighing in at 90GB, with more available in other archives, extracting and sending basic level geometry data over the network should be fairly trivial, I hope.
The prototype doesn't have a real interface, so you'll have to play around with the URL to make changes to the data displayed, it's not terribly difficult to use though, Here are some instructions:
- Your browser must support WebGL and the PointerLock API. Chrome and Firefox are a good starting place. Soory, it probably doesn't work on Mobile!
- As this demo is only currently collecting from 2 custom map servers, the map list is reasonably restricted. There are no Valve maps available. Good maps to look at are:
- To change map you can append "?map=map name" (e.g. "?map=cp_overgrown_b3").
- To change the classes displayed you can use "&classes=class list" (e.g. "&classes=sniper,soldier")
If you want to use the data, feel free to, it's licensed under CC BY 4.0 as detailed on the heatmaps.tf API documentation page. However, bear in mind that the API is likely to change, and you should save the JSON responses if you wish to keep using them.