Search code examples
javascriptanimationthree.jsaframe

Looping animations using a counter (A-Frame animation mixer)?


I am trying to find an efficient way to execute animations one after another after playing one animation "X" number of times.

My animations cannot be compiled into one long GTLF/GLB animation due to random animations being selected through arrays.

The issue I am encountering is repeating this code after it is completed.

Here is my current approach:

// Counter (to determine when to execute multiple animations sequentially)
var counter = 0;

// No. counter needs to reach. Between 1 & 3 loops
function randomIntFromInterval(min, max) { 
   return Math.floor(Math.random() * (max - min + 1) + min);
};

var countertrigger = randomIntFromInterval(1,3);

// Default animation for Character
character.setAttribute("animation-mixer", {clip: "animationA"});

character.addEventListener('animation-loop', function () {
  if (character.getAttribute = character.getAttribute("animation-mixer", {clip: "animationA"})){
    counter++;

    if (counter === countertrigger){
      character.setAttribute("animation-mixer", {clip: "animationB"});

      character.addEventListener('animation-finished',function() {
        if (character.getAttribute("animation-mixer").clip === "animationB"){
          character.setAttribute("animation-mixer", {clip: "animationC"});

          character.addEventListener('animation-finished',function() {
            if (character.getAttribute("animation-mixer").clip === "animationC"){
              character.setAttribute("animation-mixer", {clip: "animationA"});

            // Resets idle counter
            counter = 0;

            // Resets/randomises the no. loops before next multiple animations execute  
            countertrigger = randomIntFromInterval(1,3);
            };
          });
        };
      }); 
    };
  };
});

Solution

  • Each time animation-loop is emitted and counter === countertrigger, a new event listener is created for animation-finished, and you probably end up with a cascade of callbacks.

    There are multiple ways of doing this, here's one take:

    • keep some helpers (current counter, current animation)
    • keep the logic in one loop callback - determining what should be in the next loop, by checking the helper values.

    Something like this:


    // idle cycle counter
    var counter = 0;
    
    // No. counter needs to reach. Between 1 & 3 loops
    function randomIntFromInterval(min, max) { 
      return Math.floor(Math.random() * (max - min + 1) + min);
    };
    var countertrigger = randomIntFromInterval(1,3);
    
    // animation helpers
    var animations = ["animationA", "animationB", "animationC"]
    var clipId = 0;
    
    // start the animation
    character.setAttribute("animation-mixer", {clip: animations[clipId});
    
    // upon each animation loop...
    character.addEventListener('animation-loop', function () {
      // check if idle, and should be idle longer
      if (clipId === 0 && counter < countertrigger) {
         counter++;
         return;
      }
      
      // check if this was the last animation
      if (clipId === (animations.length - 1)) {
         // Reset helpers
         clipId = 0;
         counter = 1; // the animation will be played once within this callback
         countertrigger = randomIntFromInterval(1,3);
      } else {
         clipId++
      }
      // play the next clip
      character.setAttribute("animation-mixer", {clip: animations[clipId]});
    }