Three.js + Theatre.js WebGL动画制作简明教程

news2025/1/21 11:57:10

在这个教程中,我们将介绍 Theatre.js 的基础知识并探索如何制作令人惊叹的动画序列。 我们将演示如何为 Three.js 立方体制作动画、集成引人注目的视觉效果、修改颜色、试验 HTML 元素以及以特定时间间隔将动画与声音播放同步。

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景

1、Theatre.js安装和设置

首先,我们需要一个带有 Three.js 的入门模板和一个基本场景。 Theater.js 有两个基本包:

  • @theatre/studio 是我们用来创建动画的编辑器 GUI
  • @theatre/core 播放我们创建的动画。

我们可以像这样添加 Theater.js 包:

# with npm
npm install --save @theatre/core @theatre/studio
# with yarn
yarn add @theatre/core @theatre/studio

或者下载这个入门模板,其中包含所有必需的依赖项,然后运行以下命令:

# Install the dependencies 
yarn install
# Start the server
yarn run dev.

2、创建立方体和地板

入门模板为我们提供了一个简单的立方体、地板、一些照明和轨道控件。

// Cube
  const geometry = new THREE.BoxGeometry(10, 10, 10);
  const material = new THREE.MeshPhongMaterial({ color: 0x049ef4 });
  const box = new THREE.Mesh(geometry, material);
  box.castShadow = true;
  box.receiveShadow = true;
  scene.add(box);

// Floor
  const floorGeometry = new THREE.CylinderGeometry(30, 30, 300, 30);
  const floorMaterial = new THREE.MeshPhongMaterial({ color: 0xf0f0f0 });
  const floor = new THREE.Mesh(floorGeometry, floorMaterial);
  floor.position.set(0, -150, 0);
  floor.receiveShadow = true;
  scene.add(floor);

// Lights
  const ambLight = new THREE.AmbientLight(0xfefefe, 0.2);
  const dirLight = new THREE.DirectionalLight(0xfefefe, 1);
  dirLight.castShadow = true;
  dirLight.shadow.mapSize.width = 1024;
  dirLight.shadow.mapSize.height = 1024;
  dirLight.shadow.camera.far = 100;
  dirLight.shadow.camera.near = 1;
  dirLight.shadow.camera.top = 40;
  dirLight.shadow.camera.right = 40;
  dirLight.shadow.camera.bottom = -40;
  dirLight.shadow.camera.left = -40;
  dirLight.position.set(20, 30, 20);
  scene.add(ambLight, dirLight);

// OrbitControls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableZoom = true;
  controls.enableDamping = true;
  controls.autoRotate = false;
  controls.dampingFactor = 0.1;
  controls.minDistance = 2.4;
  controls.target.set(0, 20, 0);

3、导入 Theater 并创建项目

我们需要从 @theatre/core 导入 { getProject, types }。 完成后,我们还需要从 @theatre/studio 导入并初始化 studio。

Theatre.js 中的项目就像一个保存的文件。 项目存储在 localStorage 中,因此如果关闭并重新打开浏览器,你不会丢失进度。 创建一个新的剧院项目并为其命名。
然后我们创建一个新工作表。 工作表包含所有可以设置动画的对象。

import { getProject, types } from '@theatre/core';
import studio from '@theatre/studio';
studio.initialize();

// Create a project for the animation
const project = getProject('TheatreTutorial_1');

// Create a sheet
const sheet = project.sheet('AnimationScene');

4、物体和属性

每个需要动画的对象都有一个相应的 Theater Sheet 对象。 这些工作表对象包含属性(Props),可以对它们进行动画处理以在场景中创建运动和其他动态效果。

让我们创建一个新的 boxObject 并将其命名为 Box。

const boxObj = sheet.object('Box', {});

属性对应于可以动画化的对象的特定特征,可以有不同的类型,可以使用 import {types} from ‘@theatre/core’ 导入。

我们将添加一些道具。 让我们从旋转开始,创建一个复合类型的属性,并添加数值类型的xR、yR和zR,值:0,范围:[-Math.PI,Math.PI]。

同样,让我们添加位置和比例属性。 向其中添加一个 nudgeMultiplier 可以让我们进行更精细的控制。

const boxObj = sheet.object('Box', {
    rotation: types.compound({
      xR: types.number(0, { range: [-Math.PI, Math.PI] }),
      yR: types.number(0, { range: [-Math.PI, Math.PI] }),
      zR: types.number(0, { range: [-Math.PI, Math.PI] }),
    }),
    position: types.compound({
      x: types.number(0, { nudgeMultiplier: 0.1 }),
      y: types.number(0, { nudgeMultiplier: 0.1 }),
      z: types.number(0, { nudgeMultiplier: 0.1 }),
    }),
    scale: types.compound({
      xS: types.number(1, { nudgeMultiplier: 0.1 }),
      yS: types.number(1, { nudgeMultiplier: 0.1 }),
      zS: types.number(1, { nudgeMultiplier: 0.1 }),
    }),
});

现在我们可以看到工作表下有一个新的 Box 对象。
在这里插入图片描述

5、为立方体设置动画

是时候为我们的立方体制作动画了。 我们需要一种根据 boxObj 属性的值来旋转立方体网格的方法。 这可以通过使用 onValuesChange() 钩子监听 boxObj 的变化来完成。

boxObj.onValuesChange((values) => {
    const { xR, yR, zR } = values.rotation;
    box.rotation.set(xR, yR, zR);
    const { x, y, z } = values.position;
    box.position.set(x, y, z);
    const { xS, yS, zS } = values.scale;
    box.scale.set(xS, yS, zS);
});

移动滑块现在会实时影响我们的立方体。

在这里插入图片描述

6、添加关键帧

让我们添加一些关键帧。 可以右键单击任何属性,然后单击“序列”或“序列全部”。
在这里插入图片描述

这将打开带有属性的序列编辑器(Sequence Editor)。 我们可以调整序列时间线的大小、放大或缩小,并使用蓝色指针擦洗序列。

拖动以移动指针,然后单击黄色按钮添加关键帧。

在这里插入图片描述

让我们将序列时间线的大小调整为 2 秒多一点。 然后添加关键帧来为立方体的 y 位置设置动画。 同样,让我们对比例进行排序并向其添加关键帧。 你可以使用这些值或自己尝试一下,直到你觉得合适为止。
在这里插入图片描述

然后按空格键播放序列:
在这里插入图片描述

7、图编辑器

单击序列编辑器中每个属性旁边的按钮将打开图编辑器(Graph Editor)或多轨曲线编辑器(multi-track curve editor)。 当我们想要通过手动编辑一个或多个轨道的速度曲线来精细化动画时,这会派上用场。

在这里插入图片描述

单击关键帧之间的链接可显示可使用的默认缓动曲线列表。

8、修改颜色

让我们继续看看如何使用 Theater.js 修改颜色。 让我们创建一个新对象并将其命名为 Colors。 背景颜色是 types.rgba()。 同样,我们还为地板颜色和盒子颜色创建属性:

const colorObj = sheet.object('Colors',{
    backgroundColor: types.rgba(),
    floorColor: types.rgba(),
    boxColor: types.rgba(),
})

在这里插入图片描述

在 onValuesChange() 钩子中,我们可以设置 scene.background 或底层 HTML 元素的背景颜色。 使用 setRGB(),我们设置地板和盒子材质的颜色。 单击并拖动颜色选择器以更改颜色。

colorObj.onValuesChange((values)=>{
    // scene.background = new THREE.Color(values.backgroundColor.toString());
    // @ts-ignore
    document.body.style.backgroundColor = values.backgroundColor;
    floorMaterial.color.setRGB(values.floorColor.r,values.floorColor.g,values.floorColor.b)
    boxMaterial.color.setRGB(values.boxColor.r,values.boxColor.g,values.boxColor.b)
})

在这里插入图片描述

如果立方体在拉伸时能发光那就太好了。 让我们创建一个新的Theatre对象: boxEffects。

const boxEffectsObj = sheet.object('Effects',{
    boxGlow:types.rgba(),
})
boxEffectsObj.onValuesChange((values)=>{
    boxMaterial.emissive.setRGB(values.boxGlow.r,values.boxGlow.g,values.boxGlow.b);
})

让我们编排其序列,并在前几帧上添加两个 emissive为 #000000 的关键帧,并为压缩状态选择一种颜色。 然后在最后一帧恢复正常。

在这里插入图片描述

9、速度线效果

在这里插入图片描述

要添加卡通速度线 vFx,让我们创建三个立方体并将它们缩放为看起来像线,然后将它们以组的形式添加到场景中。

// Swoosh Effect Objects
const swooshMaterial = new THREE.MeshBasicMaterial({color:0x222222,transparent:true,opacity:1});
const swooshEffect = new THREE.Group();

const swooshBig = new THREE.Mesh(geometry, swooshMaterial );
swooshBig.scale.set(0.02,2,0.02)
swooshBig.position.set(1,0,-2)

const swooshSmall1 = new THREE.Mesh(geometry, swooshMaterial );
swooshSmall1.scale.set(0.02,1,0.02)
swooshSmall1.position.set(1,0,3)

const swooshSmall2 = new THREE.Mesh(geometry, swooshMaterial );
swooshSmall2.scale.set(0.02,1.4,0.02)
swooshSmall2.position.set(-3,0,0)

swooshEffect.add( swooshBig, swooshSmall1, swooshSmall2 );
swooshEffect.position.set(0,20,0)
scene.add(swooshEffect)

让我们向 boxEffect 对象添加更多道具来调整线条的比例、位置和不透明度。 尝试使用该关键帧以获得所需的效果。

const boxEffectsObj = sheet.object('Effects',{
    boxGlow:types.rgba(),
    swooshScale:types.number(1,{nudgeMultiplier:0.01}),
    swooshPosition:types.number(0,{nudgeMultiplier:0.01}),
    swooshOpacity:types.number(1,{nudgeMultiplier:0.01})
})
boxEffectsObj.onValuesChange((values)=>{
    boxMaterial.emissive.setRGB(values.boxGlow.r,values.boxGlow.g,values.boxGlow.b);
    swooshEffect.scale.setY(values.swooshScale);
    swooshEffect.position.setY(values.swooshPosition);
    swooshMaterial.opacity=values.swooshScale;
})

10、漫画文字效果

在这里插入图片描述

是时候来一些动漫文字效果了:“Boink!”

从 THREE 导入 {CSS2DRenderer,CSS2DObject} 并创建一个 textRenderer。 让我们将 style.position 设置为 absolute 并更新 OrbitControls 的 domElement。

创建一个新的 CSS2D 对象,将其添加到场景中,然后添加一个表示该对象的 HTML 元素。 将文本添加到框中,使其跟随屏幕上的框位置。

  <div id="boink">Boink!!</div>
import {CSS2DRenderer,CSS2DObject} from 'three/examples/jsm/renderers/CSS2DRenderer'
let textRenderer = new CSS2DRenderer();
textRenderer.setSize(window.innerWidth,window.innerHeight);
textRenderer.domElement.style.position = 'absolute';
textRenderer.domElement.style.top = "0";
textRenderer.domElement.style.left = "0";
textRenderer.domElement.style.width = "100%";
textRenderer.domElement.style.height = "100%";
textRenderer.domElement.style.zIndex = "2";
document.body.appendChild(textRenderer.domElement)

// OrbitControls
controls = new OrbitControls(camera, textRenderer.domElement);

// Text Effects
const boinkDom = document.getElementById('boink');
const boinkText = new CSS2DObject(boinkDom);
boinkText.position.set(-25,0,0)
box.add(boinkText);

// add this to your render()/tick() function
// textRenderer.render(scene, camera);

创建一个新的 theatre.js 对象: textEffectObj,其中包含不透明度、文本和比例的属性。

使用 onValuesChange(),更新HTML 元素的 innerText。 Theater.js 的一个有趣之处是:它也可以用于修改文本和动画文本。 对所有属性进行排序并添加关键帧,使文本在盒子弹起时弹出。

const textEffectObj = sheet.object('text',{
    opacity:1,
    text:"",
    scale: 1
});

textEffectObj.onValuesChange((values)=>{
    if(!boinkDom)return;
    boinkDom.innerText = values.text;
    boinkDom.style.opacity = ""+values.opacity
    boinkDom.style.fontSize = ""+values.scale+"px";
})

在这里插入图片描述

11、鼠标音效

最后,为了让一切变得栩栩如生,让我们添加声音效果。 我在 Pixabay 上搜索了一些免费的声音并将它们导入到项目中。 然后我使用 Three.js AudioLoader 加载它们。 以下是我向 Three.js 项目添加声音的方法:

// importing my sounds as urls
import swooshSound from '../assets/sounds/whoosh.mp3';
import boinkSound from '../assets/sounds/boink.mp3';
import thudSound from '../assets/sounds/loud-thud-45719.mp3';

const listener = new THREE.AudioListener();
const loader = new THREE.AudioLoader(loadingMgr);
let soundReady = false;
const swoosh = new THREE.Audio(listener)
const boink = new THREE.Audio(listener)
const thud = new THREE.Audio(listener)

setupSounds();

function setupSounds() {
  camera.add(listener);

  audioSetup(swoosh,swooshSound,0.3,loader)
  audioSetup(boink,boinkSound,0.2,loader)
  audioSetup(thud,thudSound,0.5,loader)
}

function audioSetup(sound:THREE.Audio, url:string, volume:number, loader:THREE.AudioLoader){
  loader.load(
    url,
    // onLoad callback
    function ( audioBuffer ) {
      sound.setBuffer( audioBuffer );
      sound.setVolume(volume)
      sound.loop=false;
    },
  );
}

设置完成后,我们可以根据序列中的指针位置继续播放声音。 我们可以通过利用 onChange() 钩子并监视指针位置的变化以在特定时间间隔触发声音播放来实现此目的。

// play the audio based on pointer position
onChange(sheet.sequence.pointer.position, (position) => {
    if(!soundReady)return;
    if(position > 0.79 && position < 0.83){
        if(!thud.isPlaying){
            thud.play();
        }
    }
    else if(position > 1.18 && position < 1.23){
        if(!boink.isPlaying){
            boink.play();
        }
    }
    else if(position > 0.00 && position<0.04){
        if(!swoosh.isPlaying){
            swoosh.playbackRate= 1.7;
            swoosh.play();
        }
    }
})

要为 click添加新的事件侦听器,将 soundReady 设置为 true,并利用 sheet.sequence.play() 播放动画,迭代计数为 Infinity,范围为 0-2。

<style>
.enterSceneContainer{
  z-index: 4;
  position: absolute;
  display: block;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: all 0.5s ease;
}
</style>
<div class="enterSceneContainer" id="tapStart">
    <p>Tap to start</p>
</div>
// Play sequence on click once all assets are loaded
const tapStart = document.getElementById('tapStart');

tapStart.addEventListener(
    'click',
    function () {
        soundReady = true;
        tapStart.style.opacity = "0";
        setTimeout(()=>{
          tapStart.style.display = "none";
        },400)
        sheet.sequence.play({ iterationCount: Infinity, range: [0, 2] });
    }
);

12、色调映射和编码器

为了增强场景的颜色,你可以为渲染器指定不同的 toneMappings和 outputEncodings。

在尝试了各种选项后,我选择将它们设置为这个特定项目的 LinearToneMapping 和 sRGBEncoding。

renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.LinearToneMapping;

要添加雾并将其与场景背景同步,可以在 colorObj.onValuesChange() 钩子中包含相关代码。

scene.fog = new THREE.FogExp2(0xffffff, 0.009);
colorObj.onValuesChange((values)=>{
    // @ts-ignore
    scene.fog.color = new THREE.Color(values.backgroundColor.toString());
    // ... rest of the code here ...
})

13、部署到生产环境

为了完成该项目,我们需要导出动画并将其部署到生产中。 只需单击Studio UI 中的项目名称并选择“导出到 JSON”,然后保存状态即可。

在这里插入图片描述

将保存的状态导入到 main.js 中,并在 getProject 中传递保存的状态。

import projectState from '../assets/Saved_TheatreState.json';

project = getProject('TheatreTutorial_1', { state: projectState });

导出动画后,你可以删除 studio import 和 studio.initialize(),因为制作不需要它们。 或者,你可以根据需要有条件地删除或包含它们。

let project;
// Using Vite
if (import.meta.env.DEV) {
    studio.initialize();
    // Create a project from local state
    project = getProject('TheatreTutorial_1');
}
else {
    // Create a project from saved state
    project = getProject('TheatreTutorial_1', { state: projectState });
}

不要忘记查看最终代码。 或者,也可以点击此链接来观看视频教程。


原文链接:Theatre.js动画制作 — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/925248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS选择器讲解!!!

CSS选择器 一. 常用的CSS基本选择器1.标签(元素)选择器2.类选择器3.id选择器4.类选择器和标签选择器的区别5.通配符选择器 二.复合选择器(2种)1.交集选择器2.并集(群组)选择器 三.属性选择器四.关系选择器1.后代选择器2.子代选择器3.相邻兄弟选择器4.通用兄弟选择器 五.伪元素选…

SpringMVC探秘: 实现MVC模式的Web应用

文章目录 1. SpringMVC概述1.1. 什么是SpringMVC&#xff1f;1.1.1. MVC与SpringMVC 1.2. SpringMVC项目的优势 2. SpringMVC项目的创建与使用2.1. 创建SpringMVC项目2.2. 设置路由2.3. 获取参数2.3.1. 获取一个参数2.3.2. 获取多个参数2.3.3. 获取日期参数2.3.4. 参数重命名Re…

C++之fileno用法实例(一百八十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

智慧煤矿:煤矿井下视频监控汇聚/AI智能分析监管预警系统解决方案

一、背景分析 随着科技的不断进步&#xff0c;视频监控技术在各个领域得到了广泛应用&#xff0c;其中包括煤矿行业。智慧煤矿方案通过引入视频监控系统&#xff0c;可以实现对煤矿生产过程的实时监控和管理&#xff0c;提高矿山安全性和生产效率。为解决井下作业距离地面远&a…

Spring之Spring案例分析

Spring案例分析 Spring案例分析 摘要引言词汇解释详细介绍不同领域的案例分析实战项目示例注意事项总结 参考资料 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全》 &#x1f369;惟余辈才疏学浅&#xff0c;临摹之…

APP Store上线问题及解决方案

将iOS应用上线到App Store可能会涉及一些问题&#xff0c;在上线iOS应用之前&#xff0c;确保你充分测试应用&#xff0c;遵循苹果的开发者指南和审核规则&#xff0c;以及关注用户的反馈&#xff0c;这些都能帮助你尽可能地解决问题并提供优秀的用户体验。以下是一些可能的问题…

juc基础(三)

目录 一、读写锁 1、读写锁介绍 2、ReentrantReadWriteLock 3、例子 4、小结 二、阻塞队列 1、BlockingQueue 简介 2、BlockingQueue 核心方法 3、案例 4、常见的 BlockingQueue &#xff08;1&#xff09;ArrayBlockingQueue(常用) &#xff08;2&#xff09;Li…

Prompt本质解密及Evaluation实战(二)

一、LangChain基于evaluation的prompt使用解析 我们来看下LangChain中关于prompt的使用&#xff0c;下面是取自LangChain源码中的一个经典的示例&#xff0c;描述了AI模型被授予访问几种工具来帮助回答用户的问题&#xff0c;其中“tool_descriptions”是至关重要的&#xff0…

React生命周期(新-旧)

文章目录 前言1、生命周期介绍2、钩子函数介绍 生命周期的三个阶段一、生命周期&#xff08;旧&#xff09;1.初始化阶段(挂载阶段)① constructor② componentWillMount③ render④ componentDidMount 2.更新阶段① shouldComponentUpdate② componentWillUpdate③ render④ c…

Spring之Spring性能优化与监控

Spring性能优化与监控 Spring性能优化与监控 摘要引言词汇解释详细介绍什么是缓存&#xff1f;Spring框架中的缓存支持示例代码&#xff1a;注释&#xff1a; 注意事项 通过合理使用缓存&#xff0c;可以显著提升应用程序的响应速度&#xff0c;降低系统负载&#xff0c;提供更…

港联证券|市场生态全面优化 创新成长愈加鲜明

8月24日&#xff0c;创业板变革并试点注册制已高质量运行满三周年。坚持变革定力、坚守板块定位——行至2023年&#xff0c;变革后的创业板商场板块功用日益完备、立异生长特征愈加显着&#xff0c;一批又一批战略性新兴工业和高新技能企业继续出现&#xff0c;先进制作、数字经…

IO线程,文件IO(open),文件(stat)与目录(opendir)属性的读取

一、文件IO 1、文件io通过系统调用来操作文件 系统调用:系统提供给用户的一组API(接口函数) open/read/write/close/lseek... 用户空间进程访问内核的接口 把用户从底层的硬件编程中解放出来 极大的提高了系统的安全性 使用户程序具有可移植性(同一系统下) 是操作系统的一部分…

400电话系统如何进行数据分析和优化?

400电话系统可以通过以下方式进行数据分析和优化&#xff1a; 呼叫记录&#xff1a;400电话系统会记录每一次呼叫的相关信息&#xff0c;包括呼叫时间、呼叫持续时间、呼叫地点等。通过分析呼叫记录&#xff0c;企业可以了解客户的呼叫习惯和行为模式&#xff0c;如高峰时段、呼…

如何自己实现一个丝滑的流程图绘制工具(二) 自定义面板

前言 我需要的自定义面板不是固定在左侧&#xff0c;而是右上角&#xff0c;且只有新增节点的操作。采用css取定位更改。 如何自定义面板内容呢&#xff1f; paltte目录下的两个文件 CustomPalette.js export default class CustomPalette {constructor(bpmnFactory, creat…

开源项目-会议室预约管理系统

哈喽,大家好,今天给大家带来一个开源项目-会议室管理系统。项目基于SpringBoot+VUE开发。 会议室管理系统主要分为 前台会议室预约管理系统 和 会议室后台管理系统 两部分 前台会议室预约管理系统主要有申请会议室,预约进程,查看历史会议三部分 后台管理系统主要有会议室…

【Mybatis源码分析】Mybatis 是如何实现预编译的?

Mybatis 是如何实现预编译的&#xff1f; 一、前言二、源码分析三、总结 一、前言 在介绍 Mybatis 是如何实现预编译之前&#xff0c;需提前知道俩个预备知识&#xff1a; MySQL的运行流程&#xff08;对应的 SQL 会成为一个文本-》查询缓存&#xff08;8.0后没了&#xff09…

车联网技术介绍

上图是目前车联网架构图&#xff0c;基于“云-管-端”的车联网系统架构以支持车联网应用的实现&#xff0c; “云”是指 V2X 基础平台、高基于精度定位平台等基础能力&#xff0c;可实现车辆动态厘米级定位&#xff0c;这将满足现阶段以及未来车联网应用场景的定位精度需求。 “…

【Linux网络】Cookie和session的关系

目录 一、Cookie 和 session 共同之处 二、Cookie 和 session 区别 2.1、cookie 2.2、session 三、cookie的工作原理 四、session的工作原理 一、Cookie 和 session 共同之处 Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式。 二、Cookie 和 session 区别 2.…

【C语言】基础知识杂记(整理自用)

前言 之前一直在学新知识&#xff0c;最近打算复习一下之前学的&#xff0c;所以写了这篇文章&#xff0c;记录一下不熟练的知识点&#xff0c;自用&#xff0c;对大家帮助可能不是很大。 double类型与float类型 编译器默认7.0为double类型 在数据后加一个f&#xff0c;编译…

Springboot+mybatis-plus+dynamic-datasource+Druid 多数据源 分布式事务

Springbootmybatis-plusdynamic-datasourceDruid 多数据源事务&#xff0c;分布式事务 文章目录 Springbootmybatis-plusdynamic-datasourceDruid 多数据源事务&#xff0c;分布式事务0.前言1. 基础介绍ConnectionFactoryAbstractRoutingDataSource 动态路由数据源的抽象类 Dyn…