gganimate 根据时间为多条路径设置动画

gganimate animate multiple paths based on time

我分析了电子游戏《反恐精英》中有关手榴弹投掷的一些数据。下面的示例数据显示我知道手榴弹从哪里投掷、手榴弹在哪里引爆以及何时投掷。

df <- data.frame(pos_x = c(443.6699994744587,459.4566921116250, 443.5131582404877, 565.8823313012402, 725.3048665125078, 437.3428992800084, 475.7286794460795, 591.4138769182258),
             pos_y = c(595.8564633895517, 469.8560006170301, 558.8543552036199, 390.5840189222542, 674.7983854380914, 688.0909476552858, 468.4987145207733, 264.6016042780749), 
             plot_group = c(1, 1, 2, 2, 3, 3, 4, 4),
             round_throw_time = c(31.734375, 31.734375, 24.843750, 24.843750, 35.281250, 35.281250, 30.437500, 30.437500), 
             pos_type = c("Player position", "HE detonate", "Player position", "HE detonate", "Player position", "HE detonate", "Player position", "HE detonate"))

并且使用 ggplot2 我可以绘制手榴弹的静态轨迹,如下所示

但我想制作手榴弹轨迹的动画,并按照 round_throw_time 规定的顺序启动每个轨迹的动画,并从玩家位置移动到引爆位置。 到目前为止,我已经尝试过:

ggplot(df, aes(pos_x, pos_y, group = plot_group)) +
 annotation_custom(grid::rasterGrob(img, width = unit(1,"npc"), height = 
 unit(1,"npc")), 0, w, 0, -h) + 
 scale_x_continuous(expand = c(0,0),limits = c(0,w)) + 
 scale_y_reverse(expand = c(0,0),limits = c(h,0)) + 
 geom_point(color = "red") +
 transition_states(states=pos_type, transition_length = 1, state_length = 1)

但是当谈到添加轨迹线以及如何重置动画而不是让点回到原点时,我有点不知所措。

如有任何提示,我们将不胜感激!

我绘制的图像可以在这里下载 http://simpleradar.com/downloads/infernoV2.zip

首先,这太棒了。

第一种方法使用 shadow_wake,没有其他数据准备

我注释掉了问题中没有定义的部分。我添加了 wrap = F 来使动画在最后重置(而不是倒回),并添加 shadow_wake 来捕捉轨迹。

# The factors of pos_type are backwards (b/c alphabetical), so R thinks the detonation 
#   comes before the player position. Here we reverse that.
df$pos_type <- forcats::fct_rev(df$pos_type)

ggplot(df, aes(pos_x, pos_y, group = plot_group)) +
  # annotation_custom(grid::rasterGrob(img, width = unit(1,"npc"), height = 
  #                                      unit(1,"npc")), 0, w, 0, -h) + 
  scale_x_continuous(expand = c(0,0)) + # ,limits = c(0,w)) +
  scale_y_reverse(expand = c(0,0)) + # ,limits = c(h,0)) +
  geom_point(color = "red") +
  transition_states(states=pos_type, transition_length = 1, state_length = 1, wrap = F) +
  shadow_wake(wake_length = 1)

第二种方法,添加初始位置和geom_segment

如果我们给每一帧一个玩家位置的参考,我们也可以将轨迹添加为一个片段:

df %>%
  # Add reference to first coordinates for each plot_group
  left_join(by = "plot_group",
    df %>% 
      group_by(plot_group) %>%
      filter(pos_type == "Player position") %>%
      mutate(pos_x1 = pos_x, pos_y1 = pos_y) %>%
      select(plot_group, pos_x1, pos_y1)
  ) %>%
ggplot(aes(pos_x, pos_y, group = plot_group)) +
  # annotation_custom(grid::rasterGrob(img, width = unit(1,"npc"), height = 
  #                                      unit(1,"npc")), 0, w, 0, -h) + 
  scale_x_continuous(expand = c(0,0)) + # ,limits = c(0,w)) +
  scale_y_reverse(expand = c(0,0)) + # ,limits = c(h,0)) +
  geom_point(color = "red") +
  geom_segment(color = "gray70", aes(xend = pos_x1, yend  = pos_y1)) +
  transition_states(states=pos_type, transition_length = 1, state_length = 1, wrap = F)

第三种变体,按开始时间显示轨迹

与第二个类似,但我添加了每个轨迹的距离、行进时间和开始时间。我在这里假设爆炸是结束时间,然后回到轨迹开始的时间。

(我第一次尝试 transition_time,但在第一个轨迹之后没有错误行为就无法让它工作。)

# trajectory speed
dist_per_time = 50

df2 <- df %>%
  # Add reference to first coordinates for each plot_group
  left_join(by = "plot_group",
            df %>% 
              group_by(plot_group) %>%
              filter(pos_type == "Player position") %>%
              mutate(pos_x1 = pos_x, pos_y1 = pos_y) %>%
              select(plot_group, pos_x1, pos_y1)
  ) %>%
  left_join(by = c("plot_group", "pos_type"),
    df %>%
      group_by(plot_group) %>%
      mutate(x_d = (range(pos_x)[1] - range(pos_x)[2]),
             y_d = (range(pos_y)[1] - range(pos_y)[2]),
             dist = sqrt(x_d^2 + y_d^2),
             event_time = round_throw_time - if_else(pos_type == "Player position", 
                                                     dist / dist_per_time, 
                                                     0),
             event_time = round(event_time, 1)) %>%
      select(plot_group, pos_type, dist, event_time)
  ) %>%

  ### EDIT - added below to make timing explicit and fix code which 
  #          was broken in current version of gganimate @ 2019-11-15
  #          Thanks @Deep North for tip.
  group_by(plot_group) %>%
  mutate(event_time_per_grp = event_time - first(event_time)) %>%
  ungroup() %>%
  mutate(event_time_cuml = cumsum(event_time))


  ggplot(df2, aes(pos_x, pos_y, group = plot_group)) +
  # annotation_custom(grid::rasterGrob(img, width = unit(1,"npc"), height = 
  #                                      unit(1,"npc")), 0, w, 0, -h) + 
  scale_x_continuous(expand = c(0,0)) + # ,limits = c(0,w)) +
  scale_y_reverse(expand = c(0,0)) + # ,limits = c(h,0)) +
  geom_point(color = "red") +
  geom_segment(color = "gray70", aes(xend = pos_x1, yend  = pos_y1)) +
  transition_reveal(event_time_cuml)  ### EDIT, see above