diff --git a/game/src/game/seaquest/seaquest_state.rs b/game/src/game/seaquest/seaquest_state.rs
index 5f8bbd51f5231a35002a9f7e06ca184051d76fd1..a007683a048545a6645243d79b86a85f6c0edf57 100644
--- a/game/src/game/seaquest/seaquest_state.rs
+++ b/game/src/game/seaquest/seaquest_state.rs
@@ -96,6 +96,7 @@ pub struct BackgroundTheme
     pub background_animation_id: AnimationId,
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub struct AssetTheme
 {
     pub theme_name: String,
@@ -143,6 +144,27 @@ pub struct BoundingCircle
     pub radius: f32,
 }
 
+#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
+pub struct ExplosionState
+{
+    pub position: Vector2<f32>,
+    pub animation_instance_id: AnimationInstanceId,
+    pub active: bool,
+}
+
+impl ExplosionState
+{
+    pub fn adjusted_position(
+        &self,
+        sprite_manager: &SpriteManager,
+        sprite_state_manager: &SpriteStateManager,
+    ) -> Vector2<f32>
+    {
+        let origin = sprite_manager.get_animation_instance_origin(sprite_state_manager, self.animation_instance_id);
+        self.position - origin
+    }
+}
+
 #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
 pub struct BulletState
 {
@@ -566,7 +588,7 @@ enum Sprites
     Shark,
     Sub,
     Bullet,
-    Bullet2,
+    Explosion,
     Background,
 }
 
@@ -575,6 +597,7 @@ pub struct GameState
 {
     pub players: Vec<PlayerState>,
     pub player_bullets: Vec<Vec<BulletState>>,
+    pub bullet_explosions: Vec<ExplosionState>,
     // pub time_multiplier: f32,
     // pub camera: Camera,
     // pub proj: Matrix4<f32>,
@@ -619,6 +642,7 @@ impl Default for GameState
             level: 1,
             players,
             player_bullets: Vec::new(),
+            bullet_explosions: Vec::new(),
             patrol_sub: PatrolSub::new(&game_defines),
             enemy_groups: EnemyGroups {
                 groups: Vec::new(),
@@ -1018,7 +1042,7 @@ impl GameSystem
             .animation_mapping
             .get(&("bullet".into(), "idle".into()))
             .expect("Failed to find bullet idle animation");
-        self.sprite_builders[Sprites::Bullet2 as usize].animation_id = *asset_theme
+        self.sprite_builders[Sprites::Explosion as usize].animation_id = *asset_theme
             .animation_mapping
             .get(&("bullet".into(), "collision".into()))
             .expect("Failed to find bullet collision animation");
@@ -1109,6 +1133,7 @@ impl GameSystem
         Ok(())
     }
 
+    // TODO: Return a list of collision pairs and handle collision responses outside of this function
     pub fn handle_collision(&mut self, game_state: &mut game::GameState, sprite_manager: &SpriteManager)
     {
         let mut player_collisions = HashSet::new();
@@ -1191,6 +1216,37 @@ impl GameSystem
 
                             if bullet_bounding_box.intersects(&enemy_bounding_box)
                             {
+                                // TODO: Move these lookups that don't change out of the funcion
+                                let theme = &self.asset_themes[game_state.game_state.current_theme];
+                                let animation_id = *theme
+                                    .animation_mapping
+                                    .get(&("bullet".to_string(), "collision".to_string()))
+                                    .expect("Failed to find bullet collision animation");
+
+                                let explosion_builder =
+                                    SpriteAnimationBuilder::new(animation_id, PlaybackState::Playing)
+                                        .billboard_mode(BillboardMode::XYAxis)
+                                        .playback_type(PlaybackLoopingMode::Once);
+
+                                let animation_instance_id = sprite_manager
+                                    .create_animation_instance(&mut game_state.sprite_state, &explosion_builder);
+
+                                let sprite_size = sprite_manager.get_animation_size(animation_id);
+                                let offset = if player.direction == Direction::Right
+                                {
+                                    Vector2::new(sprite_size.x * 0.5, sprite_size.y * -0.5)
+                                }
+                                else
+                                {
+                                    Vector2::new(sprite_size.x * -0.5, sprite_size.y * -0.5)
+                                };
+
+                                game_state.game_state.bullet_explosions.push(ExplosionState {
+                                    position: bullet.position + offset,
+                                    animation_instance_id,
+                                    active: true,
+                                });
+
                                 let lunge_multiplier = if g.lunging
                                 {
                                     self.game_defines.enemy_group_defines.lunging_points_multiplier
@@ -1315,6 +1371,17 @@ impl GameSystem
         sprite_manager.update_sprite_animations(&mut game_state.sprite_state, dt, &mut sprite_updates);
         game_state.sprite_state.update_animation_palette_sets(sprite_manager);
 
+        for instance_id in sprite_updates.completed_animations
+        {
+            sprite_manager.free_animation_instance(&mut game_state.sprite_state, instance_id);
+            game_state.game_state.bullet_explosions.iter_mut().for_each(|b| {
+                if b.animation_instance_id == instance_id
+                {
+                    b.active = false
+                }
+            });
+        }
+
         input_states.player_input.iter().nth(0).map(|action_states| {
             action_states.input.new_theme.map(|theme| {
                 game_state.game_state.current_theme = theme as usize;
@@ -1555,6 +1622,8 @@ impl GameSystem
             player_bullets.retain(|b| b.active);
         }
 
+        game_state.game_state.bullet_explosions.retain(|b| b.active);
+
         for enemy_group in &game_state.game_state.enemy_groups.groups
         {
             for animation_instance_id in &enemy_group.animation_instance_ids
@@ -1670,10 +1739,17 @@ impl GameSystem
                                        current_state: &SpriteStateManager,
                                        previous_state: &SpriteStateManager|
          -> SpriteInstance {
-            let exists = previous_state
-                .animation_instances
-                .get(animation_instance_id.0 as usize)
-                .is_some();
+            let previous_animation_instance = previous_state.animation_instances.get(animation_instance_id.0 as usize);
+            let current_animation_instance = current_state.animation_instances.get(animation_instance_id.0 as usize);
+            let exists = if let (Some(prev), Some(cur)) = (previous_animation_instance, current_animation_instance)
+            {
+                prev.sprite_instance_id == cur.sprite_instance_id && prev.animation_id == cur.animation_id
+            }
+            else
+            {
+                false
+            };
+
             let state = if exists && frame_percentage < 0.5 { previous_state } else { current_state };
 
             let mut sprite = get_sprite_instance(animation_instance_id, sprite_manager, state);
@@ -1770,6 +1846,23 @@ impl GameSystem
             sprite_instances.push(sprite_instance);
         }
 
+        for explosion in &game_state.bullet_explosions
+        {
+            let sprite_instance_id =
+                sprite_manager.get_sprite_instance_id(&sprite_state_manager, explosion.animation_instance_id);
+            let mut sprite_instance = sprite_state_manager.get_sprite_instance(sprite_instance_id).clone();
+            let dimensions = sprite_manager.get_sprite_size(sprite_instance.sprite_id);
+            let origin = get_sprite_origin(
+                explosion.animation_instance_id,
+                sprite_manager,
+                sprite_state_manager,
+                &dimensions,
+            );
+            let position = explosion.position - origin;
+            sprite_instance.position_mut(position.x, position.y);
+            sprite_instances.push(sprite_instance.clone());
+        }
+
         for (player, player_prev) in game_state
             .players
             .iter()
diff --git a/game/src/sprites/sprite_manager.rs b/game/src/sprites/sprite_manager.rs
index 23891f818604cb0ad1985e7a22ce4bb87a7d9d3b..2b8dd8d25437e7ece32a3f0997ec736f1f68b237 100644
--- a/game/src/sprites/sprite_manager.rs
+++ b/game/src/sprites/sprite_manager.rs
@@ -378,6 +378,12 @@ impl SpriteManager
         sprite_state_manager.animation_instances[animation_instance_id].sprite_instance_id
     }
 
+    pub fn get_sprite_size(&self, sprite_id: SpriteId) -> Vector2<f32>
+    {
+        let sprite = &self.sprites[sprite_id.0 as usize];
+        Vector2::new(sprite.width, sprite.height)
+    }
+
     pub fn get_animation_instance_origin(
         &self,
         sprite_state_manager: &SpriteStateManager,
@@ -419,6 +425,13 @@ impl SpriteManager
         offset
     }
 
+    pub fn get_animation_size(&self, animation_id: AnimationId) -> Vector2<f32>
+    {
+        let animation = &self.animations[animation_id.0 as usize];
+        let frame = &animation.frames[0];
+        Vector2::new(frame.width, frame.height)
+    }
+
     pub fn load_sprite_parses(
         &mut self,
         vulkan: &VulkanAppResources,