diff --git a/package.json b/package.json
index 423785b4062a037e3d1e94e9c3467f9ff3b1ea59..e7e8f66aa2ce8788a68016f62f96b0a58ad18c50 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
   "dependencies": {
     "aframe-billboard-component": "^1.0.0",
     "aframe-extras": "^3.12.4",
-    "aframe-input-mapping-component": "https://github.com/johnshaughnessy/aframe-input-mapping-component#c369fed",
+    "aframe-input-mapping-component": "https://github.com/johnshaughnessy/aframe-input-mapping-component#feature/map-to-array",
     "aframe-physics-extras": "https://github.com/infinitelee/aframe-physics-extras#fix/physics-collider-crash",
     "aframe-physics-system": "https://github.com/donmccurdy/aframe-physics-system",
     "aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin",
diff --git a/src/assets/avatars/BotDefault_Avatar.glb b/src/assets/avatars/BotDefault_Avatar.glb
index ccb77bf21362f3a5b2058f59eafd56fa17a55ec4..0784e7354e0fe2e35b3df8329c5587616203b7c3 100644
Binary files a/src/assets/avatars/BotDefault_Avatar.glb and b/src/assets/avatars/BotDefault_Avatar.glb differ
diff --git a/src/assets/avatars/BotDefault_Avatar_Unlit.glb b/src/assets/avatars/BotDefault_Avatar_Unlit.glb
index ba33589d4c42fe6bf9756d5164f331e9830dab91..850f7552c4183553779dfe0776fb92f042b7f8ca 100644
Binary files a/src/assets/avatars/BotDefault_Avatar_Unlit.glb and b/src/assets/avatars/BotDefault_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/Bot_SingleSkin_Rigged.glb b/src/assets/avatars/Bot_SingleSkin_Rigged.glb
deleted file mode 100755
index 468db3fd21422c94a19c67fc3fd5e0d4e7b8e447..0000000000000000000000000000000000000000
Binary files a/src/assets/avatars/Bot_SingleSkin_Rigged.glb and /dev/null differ
diff --git a/src/components/animated-robot-hands.js b/src/components/animated-robot-hands.js
index ef7b429d177864cc4d78a907f333e5e38411d7f9..1b26402848a8cb641560d76307708c4d77d53352 100644
--- a/src/components/animated-robot-hands.js
+++ b/src/components/animated-robot-hands.js
@@ -13,24 +13,25 @@ const POSES = {
 // TODO: When we have analog values of index-finger triggers or middle-finger grips,
 //       it would be nice to animate the hands proportionally to those analog values.
 AFRAME.registerComponent("animated-robot-hands", {
+  dependencies: ["animation-mixer"],
   schema: {
     leftHand: { type: "selector", default: "#player-left-controller" },
     rightHand: { type: "selector", default: "#player-right-controller" }
   },
 
   init: function() {
-    window.hands = this;
     this.playAnimation = this.playAnimation.bind(this);
 
-    // Get the three.js object in the scene graph that has the animation data
-    const root = this.el.querySelector("a-gltf-entity .RootScene").object3D.children[0];
-    this.mixer = new THREE.AnimationMixer(root);
-    this.root = root;
+    this.mixer = this.el.components["animation-mixer"].mixer;
 
-    // Set hands to open pose because the bind pose is funky due
+    const object3DMap = this.el.object3DMap;
+    const rootObj = object3DMap.mesh || object3DMap.scene;
+    this.clipActionObject = rootObj.parent;
+
+    // Set hands to open pose because the bind pose is funky dues
     // to the workaround for FBX2glTF animations.
-    this.openL = this.mixer.clipAction(POSES.open + "_L", root.parent);
-    this.openR = this.mixer.clipAction(POSES.open + "_R", root.parent);
+    this.openL = this.mixer.clipAction(POSES.open + "_L", this.clipActionObject);
+    this.openR = this.mixer.clipAction(POSES.open + "_R", this.clipActionObject);
     this.openL.play();
     this.openR.play();
   },
@@ -45,10 +46,6 @@ AFRAME.registerComponent("animated-robot-hands", {
     this.data.rightHand.removeEventListener("hand-pose", this.playAnimation);
   },
 
-  tick: function(t, dt) {
-    this.mixer.update(dt / 1000);
-  },
-
   // Animate from pose to pose.
   // TODO: Transition from current pose (which may be BETWEEN two other poses)
   //       to the target pose, rather than stopping previous actions altogether.
@@ -81,8 +78,8 @@ AFRAME.registerComponent("animated-robot-hands", {
     //    console.log(
     //      `Animating ${isLeft ? "left" : "right"} hand from ${prevPose} to ${currPose} over ${duration} seconds.`
     //    );
-    const from = mixer.clipAction(prevPose, this.root.parent);
-    const to = mixer.clipAction(currPose, this.root.parent);
+    const from = mixer.clipAction(prevPose, this.clipActionObject);
+    const to = mixer.clipAction(currPose, this.clipActionObject);
     from.fadeOut(duration);
     to.fadeIn(duration);
     to.play();
diff --git a/src/components/animation-mixer.js b/src/components/animation-mixer.js
new file mode 100644
index 0000000000000000000000000000000000000000..791854f1eab2b6f30f20f094b9fb9a35c3ecb9f2
--- /dev/null
+++ b/src/components/animation-mixer.js
@@ -0,0 +1,36 @@
+AFRAME.registerComponent("animation-mixer", {
+  init() {
+    this.mixer = null;
+
+    const object3DMap = this.el.object3DMap;
+    const rootObject3D = object3DMap.mesh || object3DMap.scene;
+
+    if (rootObject3D) {
+      this.setAnimationMixer(rootObject3D);
+    } else {
+      this.onModelLoaded = this.onModelLoaded.bind(this);
+      this.el.addEventListener("model-loaded", this.onModelLoaded);
+    }
+  },
+
+  onModelLoaded(event) {
+    const sceneObject3D = event.detail.model;
+    this.setAnimationMixer(sceneObject3D);
+
+    this.el.removeEventListener(this.onModelLoaded);
+  },
+
+  setAnimationMixer(rootObject3D) {
+    this.mixer = new THREE.AnimationMixer(rootObject3D);
+  },
+
+  tick: function(t, dt) {
+    if (this.mixer) {
+      this.mixer.update(dt / 1000);
+    }
+  },
+
+  destroy() {
+    this.el.removeEventListener(this.onModelLoaded);
+  }
+});
diff --git a/src/components/loop-animation.js b/src/components/loop-animation.js
new file mode 100644
index 0000000000000000000000000000000000000000..09a9e9dafabf8c05c7f13a02a9ecb820018ef573
--- /dev/null
+++ b/src/components/loop-animation.js
@@ -0,0 +1,58 @@
+AFRAME.registerComponent("loop-animation", {
+  dependencies: ["animation-mixer"],
+  schema: {
+    clip: { type: "string", required: true }
+  },
+  init() {
+    const object3DMap = this.el.object3DMap;
+    this.model = object3DMap.mesh || object3DMap.scene;
+
+    if (this.model) {
+      this.mixer = this.el.components["animation-mixer"].mixer;
+    } else {
+      this.onModelLoaded = this.onModelLoaded.bind(this);
+      this.el.addEventListener("model-loaded", this.onModelLoaded);
+    }
+  },
+
+  onModelLoaded(event) {
+    const animationMixerComponent = this.el.components["animation-mixer"];
+    this.model = event.detail.model;
+    this.mixer = animationMixerComponent.mixer;
+
+    this.updateClipState(true);
+
+    this.el.removeEventListener(this.onModelLoaded);
+  },
+
+  update(oldData) {
+    if (oldData.clip !== this.data.clip && this.model) {
+      this.updateClipState(true);
+    }
+  },
+
+  updateClipState(play) {
+    const model = this.model;
+    const clipName = this.data.clip;
+
+    for (const clip of this.model.animations) {
+      if (clip.name === clipName) {
+        const action = this.mixer.clipAction(clip, model.parent);
+
+        if (play) {
+          action.enabled = true;
+          action.setLoop(THREE.LoopRepeat, Infinity).play();
+        } else {
+          action.stop();
+        }
+
+        break;
+      }
+    }
+  },
+
+  destroy() {
+    this.updateClipState(false);
+    this.el.removeEventListener(this.onModelLoaded);
+  }
+});
diff --git a/src/gltf-component-mappings.js b/src/gltf-component-mappings.js
index e73692a8ff9270985f3b533ff8bd1f8fdc0cb835..e4cf086733ad1e0dc547abafe3fb37ed025a5608 100644
--- a/src/gltf-component-mappings.js
+++ b/src/gltf-component-mappings.js
@@ -1,3 +1,4 @@
 import "./elements/a-gltf-entity";
 
 AFRAME.AGLTFEntity.registerComponent("scale-audio-feedback", "scale-audio-feedback");
+AFRAME.AGLTFEntity.registerComponent("loop-animation", "loop-animation");
diff --git a/src/room.html b/src/room.html
index 3e9b26297f6b592f1ec4a26ac6bfeebb9c0ab562..4d21e693f79f705f3958a388184379e777b34bc4 100644
--- a/src/room.html
+++ b/src/room.html
@@ -60,6 +60,10 @@
                     <a-entity class="right-controller"></a-entity>
 
                     <a-gltf-entity src="#bot-skinned-mesh" inflate="true" ik-controller >
+                        <template data-selector=".RootScene">
+                            <a-entity animation-mixer ></a-entity>
+                        </template>
+
                         <template data-selector=".Neck">
                              <a-entity>
                                  <a-entity
@@ -147,7 +151,6 @@
             wasd-to-analog2d
             character-controller="pivot: #player-camera"
             ik-root
-            animated-robot-hands
         >
             <a-entity
                 id="player-camera"
@@ -183,7 +186,11 @@
                 haptic-feedback
             ></a-entity>
 
-            <a-gltf-entity src="#bot-skinned-mesh" inflate="true" ik-controller>
+            <a-gltf-entity src="#bot-skinned-mesh" inflate="true" ik-controller >
+                <template data-selector=".RootScene">
+                    <a-entity animation-mixer animated-robot-hands ></a-entity>
+                </template>
+
                 <template data-selector=".Neck">
                     <a-entity>
                         <a-entity class="nametag" visible="false" text ></a-entity>
diff --git a/src/room.js b/src/room.js
index d19eacda88ff4cb32c104475dc3ddcf769d64a7d..92adbce7b00821fcd5660f3737fe92731336c64f 100644
--- a/src/room.js
+++ b/src/room.js
@@ -13,9 +13,6 @@ import "aframe-input-mapping-component";
 import "aframe-billboard-component";
 import "webrtc-adapter";
 
-import animationMixer from "aframe-extras/src/loaders/animation-mixer";
-AFRAME.registerComponent("animation-mixer", animationMixer);
-
 import { vive_trackpad_dpad4 } from "./behaviours/vive-trackpad-dpad4";
 import { oculus_touch_joystick_dpad4 } from "./behaviours/oculus-touch-joystick-dpad4";
 import { PressedMove } from "./activators/pressedmove";
@@ -40,6 +37,8 @@ import "./components/layers";
 import "./components/spawn-controller";
 import "./components/animated-robot-hands";
 import "./components/hide-when-quality";
+import "./components/animation-mixer";
+import "./components/loop-animation";
 
 import "./systems/personal-space-bubble";
 
diff --git a/yarn.lock b/yarn.lock
index cc36d0be7b75f6a30e22f154be8f3487f3c5a9f5..8fb5a456961d97ae9975a5df0ad769a7b415e695 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -73,9 +73,9 @@ aframe-extras@^3.12.4:
     aframe-physics-system "^1.4.3"
     three-pathfinding "^0.2.2"
 
-"aframe-input-mapping-component@https://github.com/johnshaughnessy/aframe-input-mapping-component#c369fed":
+"aframe-input-mapping-component@https://github.com/johnshaughnessy/aframe-input-mapping-component#feature/map-to-array":
   version "0.1.2"
-  resolved "https://github.com/johnshaughnessy/aframe-input-mapping-component#c369fed313a708fd1e78179cfc9d0ea64433e5f9"
+  resolved "https://github.com/johnshaughnessy/aframe-input-mapping-component#4c7e493ad6c4a25eef27d32551c94d8b78541191"
 
 aframe-lerp-component@^1.1.0:
   version "1.1.0"