RPG-style hero collector pack opening card carousel in Phaser 3.60 using Tween addCounter, pathFollower, Ellipse and renderTexture

This script is adapted from the RPG card carousel example in the Phaser 3 Feb 2020 backer scripts.

That example used the same ellipse concept to position the cards with a second set of blurred images, and required user input to flip the cards which then proceed on straight trajectories between points on the ellipse.

This one uses pathFollower to create a smoother transition between points following the curve, and renderTexture to enable more complicated card face construction (though I only leave it as a playing card face in the example).

The order of the tweens was something it took me days to figure out. We start with a counter tween, which I couldn’t figure out how to change from decimals to integers. As a kludge, we count from 0 to the deck size minus 1, but onUpdate listen for when the decimal reaches the next integer. At that point we pause the counter, flip the first card in a yoyoing tween then rotate the cards and resume the counter. Once the rotations are done, the CONTINUE text appears.

function create() {
  const tobedealt = 10, cardwidth = 73, cardheight = 98;
  
  const ellipse = new Phaser.Curves.Ellipse({
        x: 400,
        y: 270,
        rotation: 90,
        xRadius: 100,
        yRadius: 250
    });
  let points = ellipse.getPoints(tobedealt);
  if (360 % tobedealt == 0)
    points.pop();

  let showcard = [];
  points.forEach((p, i) => {
    showcard.push(this.add.renderTexture(-1000, -1000, cardwidth, cardheight));
    showcard[i].draw(this.add.sprite(0, 0, 'fronts', tobedealt - i - 1)
                     .setOrigin(0).setVisible(false).setDisplaySize(cardwidth, cardheight));
  });

  const minY = ellipse.y-ellipse.xRadius;
  const maxY = ellipse.y+ellipse.xRadius;
  const scaleRange = 0.3 / (maxY - minY);

  const path = new Phaser.Curves.Path();
  path.add(ellipse);

  let dealtcard = [];
  points.forEach((p, i, arr) => {
    var pointinpoint = points[Object.keys(points).length - i];
    if (i == 0)
      pointinpoint = points[0];
    dealtcard.push(this.add.follower(path, pointinpoint.x, pointinpoint.y, 'fronts',i+26)
      .setDisplaySize(cardwidth, cardheight).setOrigin(0.5, 1)
      .setDepth(p.y)
      .setScale(0.7 + scaleRange * (pointinpoint.y - minY))
      );
  });


  let contbutt = this.add.text(400, 420, "CONTINUE").setOrigin(0.5).setVisible(false);

  let iter = 0;
  const cardgap = 1 / tobedealt, initialdelay = 2000, spintime = 300, rotatetime = 1000;
  const durationtime = tobedealt * (spintime*2 + rotatetime);

  let cardcarousel = this.tweens.addCounter({
    from: 0,
    to: tobedealt - 1,
    delay: initialdelay,
    duration: durationtime,
    callbackScope: this,
    onUpdate: (tween) => {
      if (tween.getValue() >= iter)
      {
        cardcarousel.pause();
        let cardtoflip = iter;
        this.tweens.add({
          targets: dealtcard[cardtoflip],
          scaleX: 0,
          duration: spintime,
          ease:'Quad.easeOut',
          yoyo: true,
          callbackScope: this,
          onYoyo: () =>  {
            dealtcard[cardtoflip].setTexture(showcard[tobedealt-cardtoflip-1].texture);
          },
          onComplete: () =>  {
            if (iter < tobedealt)
              dealtcard.forEach((thiscard, cpos) => {
                let startpathpoint = cardgap * (iter - cpos - 1);
                let endpathpoint = startpathpoint + cardgap;
                if (startpathpoint >= 1)
                {
                  startpathpoint -= 1;
                  endpathpoint -= 1;
                }
                else if (startpathpoint < 0)
                {
                  startpathpoint += 1;
                  endpathpoint += 1;
                }
                thiscard.startFollow({
                  duration: rotatetime,
                  repeat: 0,
                  rotateToPath: false,
                  positionOnPath: true,
                  from: startpathpoint,
                  to: endpathpoint,
                  ease:'Quad.easeOut',
                  onUpdate: () => {
                    thiscard.setDepth(thiscard.y);
                    thiscard.setScale(0.7 + scaleRange * (thiscard.y - minY));
                  } 
                });
              })
            else
              contbutt.setVisible(true);
          }
        });
        iter++;
        cardcarousel.resume();
      }
    }
  });
}
Exit mobile version