Snapshot of a Protoss vs Terran Match

It’s been almost a decade since I started data mining professional replays for StarCraft: Brood War. After recently trying out new animation packages in R, I wanted to revisit this data set and see if I could make an interesting visualization using some of these replays with the gganimate and tweenr packages. The result is the video below:

Terran vs Zerg

This blog post walks through the R script I authored to create this video. All of the data and code used to generate this visualization is available on Github. I generated the data set by viewing replays in StarCraft and using BWAPI to output a snapshot of the game state every 5 seconds. It’s also possible to use the data sets available here. The result of this process is that I ended up with CSV files in the following format:

frame,pID,unitID,x,y

0,0,12,3792,864

0,0,19,3744,858

0,0,30,3704,864

0,0,34,3808,834

0,0,64,3768,864

The file specifies the game frame, or tick count, the ID of the player controlling the unit, the in-game identifier for tracking the unit, and the position of the unit. While it would be useful to also include unit types and status variables such as health, this data provides a starting point for visualizing the movement of armies. The data set includes both buildings and mobile units, but excludes neutral units and structures.

# libraries

library(ggplot2)

library(gganimate)

library(ggforce)

library(tweenr)

library(dplyr)

As mentioned, I’m using the gganimate and tweenr libraries to create the visualization. gganimate is useful for creating animated plots in R and tweenr provides methods for interpolating between different snapstops in the animation. ggplot2 is used to create the actual plot, ggforce is used for styling the plot, and dplyr is used for setting up the data.

# video settings

fps <- 24

nframes <- 5000

I defined two parameters for video playback. The first, fps, is the framerate for the output video, and the second, nframes, is the total length of the video. The length of the video will be nframes/fps seconds.

# load the replay data

df <- read.csv("PvT.csv") # lag the x and y coordinates to plot lines of movement

df <- df %>% group_by(unitID) %>%

mutate(x1 = dplyr::lag(x, n = 1, default = NA),

x2 = dplyr::lag(x, n = 2, default = NA),

y1 = dplyr::lag(y, n = 1, default = NA),

y2 = dplyr::lag(y, n = 2, default = NA))

The next step is to load and transform the data. The loaded dataframe includes the x and y position of each unit at a specific game time, but I also wanted to lag these values in order to draw lines between coordinates. I used dplyr and mutate to lag the x and y positions by 5 and 10 seconds.

# set up graphics

df$colour <- ifelse(df$pID == 0, 'RED', 'GREEN')

df$time <- df$frame / fps

df$ease = 'linear'

df$size = 5

df$alpha = 1 # Use tweenr

dt <- tween_elements(df, 'time', 'unitID', 'ease', nframes=nframes)

I then set additional parameters for the plot including the color and size of the units, and the type of interpolation to use for the animation. The last step uses tweenr to build a dataframe that interpolates between the different states in the input dataframe.

# animate with gganimate

p <- ggplot(data = dt) +

geom_point(aes(x=0, y=0, size=10, alpha=0, frame = .frame)) +

geom_segment(aes(x=x1, y=y1, xend=x2, yend=y2, size=1,

alpha=0.5, frame = .frame, color = colour)) +

geom_segment(aes(x=x, y=y, xend=x1, yend=y1, size=1, alpha=1,

frame = .frame, color = colour)) +

geom_point(aes(x=x, y=y, size=size, alpha=alpha, frame = .frame,

color = colour)) +

scale_size(guide = 'none') +

scale_colour_identity() +

scale_alpha(range = c(0, 1), guide = 'none') +

ggforce::theme_no_axes()

The next step is to create a plot of the data, which includes lines and dots. The first geom_point is used to scale the size of the dots and the second geom_point is used to draw the units. The two geom_segment calls are used to draw lines between current and past locations of the units. The older trails are colored a lighter color (alpha = 0.5) than the more recent trails. The remainder of the code block modifies the style of the plot.

# output the video

animation::ani.options(interval = 1/fps)

gganimate(p, "output.mp4", ani.width=640, ani.height=640

,title_frame = FALSE)

The final step is to create a video of the animated plot. I set the video size to 640 by 640 pixels and disabled the frame counter at the top of the video. It’s also possible to output GIFs and web pages. The result of this script is shown in the sample video below. If may take quite a bit of time to render.

Protoss vs Terran

The video shows troop movements for a Protoss (red) vs Terran (green) game. The start of the video shows the scouting phase and initial skirmishes. Later on, you can see where the Terran player lays spider mines throughout the map. The match ends after the Protoss player takes out an expansion and gains control of the middle of the map.