• Runtimes
  • How spine web player switch animations with diffrent sizes?

Davide 谢谢您,抱歉回复晚了,因为我又遇到新的问题。我之前没有发现上面我的例子中,动画之间已经能保持角色的大小一致了,只要设置了全局的 x, y,width,height。

现在不断调整这些值,能够把所有动画完整显示。

Related Discussions
...

Davide 我遇到了另外一个问题,希望您有时间帮忙看看~

我使用的 viewport 设置展示所有的动画,动画包含:

代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>spine</title>
    <script src="https://unpkg.com/@esotericsoftware/spine-player@4.2.*/dist/iife/spine-player.js"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/@esotericsoftware/spine-player@4.2.*/dist/spine-player.css"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
        background-color: bisque;
      }
      .grid {
        position: relative;
        left: 150px;
        top: 400px;
        display: flex;
        flex-wrap: wrap;
        width: 600px;
        height: 60px;
      }
      .item {
        width: 80px;
        height: 60px;
        box-sizing: border-box;
        border: 1px solid green;
      }
      .container {
        position: absolute;
        left: -30px;
        top: -10px;
        width: 300px;
        height: 200px
      }
      .btn {
        position: absolute;
        left: 200px;
        top: 600px;
        width: 100px;
        height: 40px;;
      }
    </style>
  </head>
  <body>
    <div class="grid">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div id="container" class="container"></div>
    </div>
    <div>
      <button type="button" class="btn" onclick="handleMove()">Move</button>
    </div>
    <script>
      const coords = [
        {
          x: -30,
          y: -10,
          direction: 'right',
        },
        {
          x: 50,
          y: -10,
          direction: 'front',
        },
        {
          x: 130,
          y: -10,
          direction: 'right',
        },
        {
          x: 210,
          y: -10,
          direction: 'right',
        },
        {
          x: 290,
          y: -10,
          direction: 'right',
        },
        {
          x: 370,
          y: -10,
          direction: 'right',
        },
        {
          x: 370,
          y: -70,
          direction: 'front',
        },
      ]
      const player = new spine.SpinePlayer("container", {
        skeleton: "./spine/yuyan/yuyan.json",
        atlas: "./spine/yuyan/yuyan.atlas",
        alpha: true,
        backgroundColor: "#00000000",
        showControls: false,
        viewport: {
          debugRender: true,
          padTop: 0,
          padLeft: 0,
          padBottom: 0,
          padRight: 0,
          x: -300,
          y: -200,
          width: 600,
          height: 400,
        },
        success: (player) => {
          player.animationState?.setEmptyAnimation(0, 0)
          player.animationState?.addListener({
            complete: function (entry) {
              if (entry.animation.name.includes('move')) {
                remainingSteps--
                curPos++
                if (remainingSteps > 0) {
                  const animation = coords[curPos].direction + '_move'
                  handlePosAction(curPos, animation)
                } else {
                  const animation = coords[curPos].direction + '_idle'
                  handlePosAction(curPos, animation)
                }
              }
            },
          });
        },
      });

      let curPos = 0
      let remainingSteps = 0

      function setPosition(pos) {
        container.style.left = pos.x + "px";
        container.style.top = pos.y + "px";
      }

      function playAnimation(animation) {
        if (player) {
          player.animationState.clearTracks();
          let loop = false
          if (animation.includes('idle')) {
            loop = true
          } else {
            loop = false
          }
          player.setAnimation(animation, loop)
          player.play();          
        }
      }

      function handlePosAction(pos, animation) {
        setTimeout(() => {
          setPosition(coords[pos])
          playAnimation(animation)
        }, 0)
      }

      function handleMove() {
        remainingSteps = 1
        handlePosAction(curPos, 'right_move')
      }

      window.onload = () => {
        const animation = coords[curPos].direction + '_idle'
        handlePosAction(curPos, animation)
      }
    </script>
  </body>
</html>

希望不会有太多的代码干扰问题。当我点击按钮时,container 根据坐标设置位置,以及根据坐标 play 相应的动画。从坐标 0 -> 1,正常 right_move,当 1->2时,front_idle 显示为:

角色不能显示,这是什么原因呢?

如果注释掉:player.animationState.clearTracks(); 更够显示出动画,但是动画会回到初始 pose。

    zangbianxuegu Unfortunately, Davide is currently on vacation so it will take some time for him to respond.

    By the way, the code you showed us is too long and probably even contains lines that are not directly related to the problem you are experiencing, so we would appreciate it if you could show us the code with a minimum of content. Instead, if you have no problem sharing the full code, it would be faster for us to check if you could email us a minimal project file that can reproduce the problem: contact@esotericsoftware.com
    Please include the URL of this forum thread in the email so we know the context. Then we can take a look at what's wrong.

      zangbianxuegu Thanks for submitting your project file! I understand your problem. You want to do something like root movement. I'm sure there will be advice from another staff member later on how to achieve this, but I just want to say that the project you sent me did not match the version of the runtime with the version of the skeleton data, so I would recommend fixing that. (The project you sent us was using spine-pixi 4.2 for the runtime, but the skeleton data was exported from 4.1.16.)

      The major and minor version for the Spine editor used to export JSON or binary data must always match the Spine Runtimes version. Somehow it seems to have worked this time, but there may be problems in the future, so please upgrade your editor version and export the skeleton data again.

        Misaki 感谢这么快回复!我的真实的项目中使用的是"@esotericsoftware/spine-player": "^4.2.58",,邮件中使用CDN链接只是为了演示。我可以将项目中使用的 spine-player 降为 4.1.16 来解决这个问题吗?

          zangbianxuegu Sorry for the mistake, indeed what you are using is spine-player!

          Note that only major and minor versions need to be matched with the editor version. There is no need to have matching patch versions, so you can use the latest 4.1 runtime by writing as follows

          <script src="https://unpkg.com/@esotericsoftware/spine-player@4.1.*/dist/iife/spine-player.js"></script>
          <link rel="stylesheet" href="https://unpkg.com/@esotericsoftware/spine-player@4.1.*/dist/spine-player.css">

          However, if you are already using the newer runtime, it is better to upgrade the editor version than to downgrade the runtime version. If you cannot use the Spine Editor yourself, ask the person who created the animation to upgrade the Editor version and re-export the skeleton for you.

            Misaki 谢谢,如果使用:

            <script src="https://unpkg.com/@esotericsoftware/spine-player@4.1.*/dist/iife/spine-player.js"></script>
            <link rel="stylesheet" href="https://unpkg.com/@esotericsoftware/spine-player@4.1.*/dist/spine-player.css">

            会有同样的问题,你可以直接在 email 的代码中修改,查看。

            另外,我也在要求动画师升级editor到 4.2.x,导出文件,我再尝试一下。

              zangbianxuegu Sorry for the confusion, but the display problem itself has nothing to do with version mismatch. I'm sure another staff member can answer you about that, so please wait a while for a more detailed answer!

                Misaki 刚刚尝试了一下动画师用 4.2.x 版本导出的文件。问题还是存在。

                我明白这个问题还需要另外的解释,只是告知一下。另外,也邮件发送了最新的文件。

                Misaki hello,早上好!虽然知道需要一点时间,但我真的需要尽快知道解决方法,麻烦您帮我问一下大概的时间!谢谢您!

                  zangbianxuegu Sorry to keep you waiting. I have already asked Mario to review the project you sent us yesterday and this thread, but he has not had enough time to do so as he is providing very extensive support. I think he might respond today if he finds the time, but I can't make any promises. Please wait as I am sure he will see it in order.

                  我发现 setToSetupPose 可以解决问题:

                  player.animationState?.setEmptyAnimation(0, 0)
                  player.skeleton?.setToSetupPose()

                  但是也仍然有问题,会有两个动画之间的闪动

                  Your code does many things that can not work. Let's start with:

                      function setPosition(pos) {
                        container.style.left = pos.x + "px";
                        container.style.top = pos.y + "px";
                      }
                  
                      function playAnimation(animation) {
                        if (player) {
                          player.animationState?.clearTracks();
                          let loop = false;
                          if (animation.includes("idle")) {
                            loop = true;
                          } else {
                            loop = false;
                          }
                          player.setAnimation(animation, loop);
                          player.play();
                        }
                      }
                  
                      function handlePosAction(pos, animation) {
                        setTimeout(() => {
                          setPosition(coords[pos]);
                          playAnimation(animation);
                        }, 0);
                      }
                  
                      function handleMove() {
                        remainingSteps = 1;
                        handlePosAction(curPos, "right_move");
                      }
                  
                      window.onload = () => {
                        const animation = coords[curPos].direction + "_idle";
                        handlePosAction(curPos, animation);
                      };

                  When window.onload() is called, you call handlePosAction() which in turn calls playAnimation() in a setTimeout() handler. Depending on various server/browser related factors, the player and skeleton may not have been loaded at that time, resulting in an error, e.g.

                  Uncaught TypeError: Cannot read properties of null (reading 'data')
                      at SpinePlayer.setViewport (Player.ts:724:40)
                      at SpinePlayer.setAnimation (Player.ts:711:20)
                      at playAnimation ((index):167:16)
                      at (index):175:9

                  On my machine, I do not see the skeleton when I load the page because of that.

                  You need to move the logic from window.onload() into the success handler of the Spine player. This also allows us to remove the setTimeout() call in handlePosAction().

                  You also queue an empty animation. The complete listener will be called for that empty animation as well. Empty animations do not have their animation field set, so the handler code in complete fails. I added a check.

                  The default mix duration is set to something != 0. This means that when switching from one animation to the next, there will be interpolation. Your skeleton is extremely complicated and when previewing it in Spine in the preview pane, switching between animations, one can see that the transitions between animations are broken. This is not something the Spine player can fix. That is something you need to fix in your Spine project in the Spine Editor. @Misaki can possibly help you with that. In any case, I've set the default mix duration to 0.

                  Finally, in handlePosAction() you call setPosition() before playAnimation(). setPosition() will immediately move the <div> containing the <canvas>, while the player hasn't rendered a new frame with the new animation yet. This results in a noticable flicker. It can be fixed by first playing the new animation, and then moving the <div> in the next frame.

                  Here's the entire fixed code:

                  <!DOCTYPE html>
                  <html lang="en">
                  
                  <head>
                    <meta charset="UTF-8" />
                    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                    <title>spine</title>
                    <script src="https://unpkg.com/@esotericsoftware/spine-player@4.2.*/dist/iife/spine-player.js"></script>
                    <link rel="stylesheet" href="https://unpkg.com/@esotericsoftware/spine-player@4.2.*/dist/spine-player.css" />
                    <style>
                      body {
                        margin: 0;
                        padding: 0;
                        background-color: bisque;
                      }
                  
                      .grid {
                        position: relative;
                        left: 150px;
                        top: 400px;
                        display: flex;
                        flex-wrap: wrap;
                        width: 600px;
                        height: 60px;
                      }
                  
                      .item {
                        width: 80px;
                        height: 60px;
                        box-sizing: border-box;
                        border: 1px solid green;
                      }
                  
                      .container {
                        position: absolute;
                        left: -30px;
                        top: -10px;
                        width: 300px;
                        height: 200px;
                      }
                  
                      .btn {
                        position: absolute;
                        left: 200px;
                        top: 600px;
                        width: 100px;
                        height: 40px;
                      }
                    </style>
                  </head>
                  
                  <body>
                    <div class="grid">
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div class="item"></div>
                      <div id="container" class="container"></div>
                    </div>
                    <div>
                      <button type="button" class="btn" onclick="handleMove()">Move</button>
                    </div>
                    <script>
                      const coords = [
                        {
                          x: -30,
                          y: -10,
                          direction: "right",
                        },
                        {
                          x: 50,
                          y: -10,
                          direction: "front",
                        },
                        {
                          x: 130,
                          y: -10,
                          direction: "right",
                        },
                        {
                          x: 210,
                          y: -10,
                          direction: "right",
                        },
                        {
                          x: 290,
                          y: -10,
                          direction: "right",
                        },
                        {
                          x: 370,
                          y: -10,
                          direction: "right",
                        },
                        {
                          x: 370,
                          y: -70,
                          direction: "front",
                        },
                      ];
                      const player = new spine.SpinePlayer("container", {
                        skeleton: "./spine/yuyan.json",
                        atlas: "./spine/yuyan.atlas",
                        alpha: true,
                        backgroundColor: "#00000000",
                        showControls: false,
                        viewport: {
                          debugRender: true,
                          padTop: 0,
                          padLeft: 0,
                          padBottom: 0,
                          padRight: 0,
                          x: -300,
                          y: -200,
                          width: 600,
                          height: 400,
                        },
                        success: (player) => {
                          const animation = coords[curPos].direction + "_idle";
                          handlePosAction(curPos, animation);
                          player.animationState.data.defaultMix = 0;
                  
                          player.animationState?.addListener({
                            complete: function (entry) {
                              if (!entry.animation) return; // empty animation, do nothing
                              if (entry.animation.name.includes("move")) {
                                remainingSteps--;
                                curPos++;
                                if (remainingSteps > 0) {
                                  const animation = coords[curPos].direction + "_move";
                                  handlePosAction(curPos, animation);
                                } else {
                                  const animation = coords[curPos].direction + "_idle";
                                  handlePosAction(curPos, animation);
                                }
                              }
                            },
                          });
                        },
                      });
                  
                      let curPos = 0;
                      let remainingSteps = 0;
                  
                      function setPosition(pos) {
                        container.style.left = pos.x + "px";
                        container.style.top = pos.y + "px";
                      }
                  
                      function playAnimation(animation) {
                        if (player) {
                          if (animation.includes("idle")) {
                            loop = true;
                          } else {
                            loop = false;
                          }
                          player.setAnimation(animation, loop);
                          player.play();
                        }
                      }
                  
                      function handlePosAction(pos, animation) {
                        playAnimation(animation);
                        requestAnimationFrame(() => setPosition(coords[pos]));
                      }
                  
                      function handleMove() {
                        remainingSteps = 1;
                        handlePosAction(curPos, "right_move");
                      }
                    </script>
                  </body>
                  
                  </html>

                  Now, this is clearly going to be some kind of game. I would STRONGLY suggest to not use Spine player for games. Please use spine-phaser or spine-pixi.

                    Mario 非常感谢!可惜现在才知道最后一点 :