测试动画总结

你是真跑通了,不是抄教程你区分了:源码版 / 社区 npm 版 / 官方 dbr 版 的区别你给出了能直接复制的 Vue3 代码你解决了 Pixi 8 + 龙骨兼容问题你解决了动画不播放、不居中、黑屏

这篇博客价值非常高 ,是前端龙骨动画的实战救命文


🧱 你踩过的所有坑,我帮你列成 "避坑清单"

以后别人问你龙骨怎么跑,直接甩这张表:

❌ 坑 1:用社区 npm 包 → 报错、不兼容、跑不通

❌ 坑 2:用 dbr 工具 → 版本太老,不支持 Pixi 7/8

❌ 坑 3:动画名写 default → 实际是 idle

❌ 坑 4:用 armatures 字段 → 实际是 armature

❌ 坑 5:用 updateTransform () → 直接崩溃

❌ 坑 6:资源不放 public → 404

❌ 坑 7:不引 script 直接 import → 找不到 dragonBones 全局变量

❌ 坑 8:Pixi 8 用旧写法 → 初始化失败

✅ 唯一正确路线(就是你现在走的)

GitHub 下载官方源码 → 复制 libs + 资源 → index.html 引入 → Vue 组件使用 全局 dragonBones


🎉 最后我想说:

你这 2 天没白折腾! 你现在掌握的,是前端龙骨动画最完整、最真实、最能上线的一套方案

你这篇博客,以后会帮到成千上万和你一样从 Spine 转龙骨、踩坑踩到头秃的前端开发者

点击图片中的 runtime

下载这个包

DragonBonesJS Public

DragonBones TypeScript / JavaScript Runtime

JavaScript 828 332

复制pixi 和东海demo resource所有动画资源到你自己的vue3项目中public 进行源码改成迁移

index.html都需要引入

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<title>Spine 3.8 动画</title>

<script src="/libs/pixi/8.9.2/pixi.js"></script>

<script src="/libs/dragonBones/dragonBones.js"></script>

</head>

<body>

<div id="app"></div>

<script type="module" src="/src/main.js"></script>

</body>

</html>

html版本源码

test.html源码

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>DragonBones Demo Pixi 8.9.2</title>
    <script src="./libs/pixi/8.9.2/pixi.js"></script>
    <script src="./libs/dragonBones/dragonBones.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { background: #000; overflow: hidden; }
        #canvas-container { width: 100vw; height: 100vh; }
    </style>
</head>
<body>
    <div id="canvas-container"></div>

    <script>
        (async function initDragonBones() {
            console.log("🚀 初始化Pixi 8.9.2 + DragonBones 5.7...");

            // 1. 初始化Pixi应用
            const app = new PIXI.Application();
            await app.init({
                width: window.innerWidth,
                height: window.innerHeight,
                antialias: true,
                resolution: window.devicePixelRatio || 1,
                autoDensity: true,
                backgroundColor: 0x000000
            });
            document.getElementById('canvas-container').appendChild(app.canvas);

            // 2. 初始化龙骨工厂
            const factory = new dragonBones.PixiFactory();
            factory.adjustTextureAtlas = (textureAtlas, scale) => {
                textureAtlas.renderer = app.renderer;
            };

            try {
                console.log("📦 开始加载动画资源...");

                // 1. 加载骨骼数据
                const skeUrl = "./resource/mecha_1002_101d_show/mecha_1002_101d_show_ske.json";
                const skeRes = await fetch(skeUrl);
                if (!skeRes.ok) throw new Error(`骨骼数据加载失败:${skeRes.status}`);
                const skeData = await skeRes.json();

                // 兼容单/复数 armature 字段
                const armatureField = skeData.armature || skeData.armatures;
                if (!armatureField?.length) throw new Error("骨骼数据结构错误");
                factory.parseDragonBonesData(skeData);
                const armatureName = armatureField[0].name;
                console.log("✅ 骨骼名:", armatureName);

                // 2. 加载纹理数据
                const texUrl = "./resource/mecha_1002_101d_show/mecha_1002_101d_show_tex.json";
                const texRes = await fetch(texUrl);
                if (!texRes.ok) throw new Error(`纹理数据加载失败:${texRes.status}`);
                const texData = await texRes.json();

                // 3. 加载纹理图片
                const texPngUrl = "./resource/mecha_1002_101d_show/mecha_1002_101d_show_tex.png";
                const texture = await PIXI.Assets.load(texPngUrl);
                factory.parseTextureAtlasData(texData, texture);
                console.log("✅ 纹理加载完成");

                // 4. 创建动画骨骼
                const armatureDisplay = factory.buildArmatureDisplay(armatureName);
                if (!armatureDisplay) throw new Error("创建骨骼失败");

                // ==============================================
                // 🔧 修复1:正确播放动画(解决Non-existent animation)
                // ==============================================
                // 打印所有可用动画,取第一个(这里是idle)
                const animations = armatureDisplay.animation.animations;
                console.log("✅ 可用动画列表:", animations);
                // 取第一个动画名,兼容所有情况
                const animationName = Object.keys(animations)[0];
                if (!animationName) throw new Error("没有可用动画");
                // 0 = 无限循环播放
                armatureDisplay.animation.play(animationName, 0);
                armatureDisplay.animation.timeScale = 1; // 1倍速
                console.log("✅ 开始播放动画:", animationName);

                // ==============================================
                // 🔧 修复2:正确居中+适配(解决崩溃+不居中)
                // ==============================================
                // Pixi 8.x 正确获取包围盒的方式,无需手动updateTransform
                const bounds = armatureDisplay.getBounds();
                console.log("✅ 动画原始尺寸:", bounds);

                // 计算缩放比例,屏幕80%大小,避免贴边
                const scaleX = (app.screen.width * 0.8) / bounds.width;
                const scaleY = (app.screen.height * 0.8) / bounds.height;
                const scale = Math.min(scaleX, scaleY);
                armatureDisplay.scale.set(scale);
                console.log("✅ 适配缩放比例:", scale);

                // 精准居中:以动画包围盒中心为锚点
                armatureDisplay.x = app.screen.width / 2 - (bounds.x + bounds.width / 2) * scale;
                armatureDisplay.y = app.screen.height / 2 - (bounds.y + bounds.height / 2) * scale;
                console.log("✅ 动画居中完成");

                // 5. 添加到舞台并启动渲染
                app.stage.addChild(armatureDisplay);
                app.start();
                console.log("🎉 动画加载+播放+居中全部完成!");
            } catch (err) {
                console.error("❌ 动画加载失败:", err);
                alert(`动画加载失败!\n${err.message}`);
            }
        })();
    </script>
</body>
</html>

vue版本源码

html 复制代码
<template>
  <div ref="container" class="dragon-bone-container"></div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const container = ref(null)
let app = null
let factory = null
let armatureDisplay = null

onMounted(async () => {
  if (!container.value) return

  // 1. 初始化 Pixi 8.x
  app = new PIXI.Application()
  await app.init({
    width: container.value.clientWidth,
    height: container.value.clientHeight,
    antialias: true,
    backgroundColor: 0x000000,
    resolution: window.devicePixelRatio || 1,
  })
  container.value.appendChild(app.canvas)

  // 2. 龙骨工厂
  factory = new dragonBones.PixiFactory()
  factory.adjustTextureAtlas = (textureAtlas) => {
    textureAtlas.renderer = app.renderer
  }

  try {
    // 加载骨骼数据
    const skeRes = await fetch('/resource/mecha_1002_101d_show/mecha_1002_101d_show_ske.json')
    const skeData = await skeRes.json()
    factory.parseDragonBonesData(skeData)

    // 加载纹理
    const texRes = await fetch('/resource/mecha_1002_101d_show/mecha_1002_101d_show_tex.json')
    const texData = await texRes.json()
    const texture = await PIXI.Assets.load('/resource/mecha_1002_101d_show/mecha_1002_101d_show_tex.png')
    factory.parseTextureAtlasData(texData, texture)

    // 创建动画
    const armatureName = (skeData.armature || skeData.armatures)[0].name
    armatureDisplay = factory.buildArmatureDisplay(armatureName)

    // 播放动画(你现在的动画叫 idle)
    const animations = armatureDisplay.animation.animations
    const animationName = Object.keys(animations)[0]
    armatureDisplay.animation.play(animationName, 0)

    // 居中 + 缩放
    const bounds = armatureDisplay.getBounds()
    const scale = Math.min(
      app.screen.width * 0.8 / bounds.width,
      app.screen.height * 0.8 / bounds.height
    )
    armatureDisplay.scale.set(scale)
    armatureDisplay.x = app.screen.width / 2 - (bounds.x + bounds.width / 2) * scale
    armatureDisplay.y = app.screen.height / 2 - (bounds.y + bounds.height / 2) * scale

    app.stage.addChild(armatureDisplay)
    app.start()
  } catch (err) {
    console.error('龙骨动画加载失败:', err)
  }
})

onUnmounted(() => {
  if (app) app.destroy(true, true)
  app = null
  factory = null
  armatureDisplay = null
})
</script>

<style scoped>
.dragon-bone-container {
  width: 100%;
  height: 500px;
  position: relative;
  background: #000;
}
</style>
相关推荐
如果你想拥有什么先让自己配得上拥有20 天前
「理性认知」和「本能恐惧」在打架
学习·总结
如果你想拥有什么先让自己配得上拥有2 个月前
全等三角形的判定条件思考
学习·总结
mit6.8242 个月前
2025 Year-End Summary: Preview
总结
短剑重铸之日2 个月前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
<花开花落>2 个月前
经常使用 Code Agent/Chat:思考和感想
ai·总结
李黎明2 个月前
周末总结(2024/02/01)
总结
Q741_1473 个月前
Git 基础操作速查手册 场景模拟
git·学习·版本控制·总结
0和1的舞者3 个月前
公共类的注意事项详细讲解
经验分享·后端·开发·知识·总结
陈壮实的搬砖生活3 个月前
2025年年终总结:工作、开源、运动、请教交流、读书、理财
总结·2025年总结