html
<html lang="zh-CN"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>骨骼动画混合演示</title>
<style>
body {
margin: 0;
padding: 0;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
font-family: 'Arial', sans-serif;
overflow: hidden;
}
#container {
position: relative;
width: 100vw;
height: 100vh;
}
#controls {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
z-index: 100;
min-width: 200px;
}
.control-section {
margin-bottom: 15px;
}
.control-section h3 {
color: #fff;
margin: 0 0 10px 0;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
}
button {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
border: none;
padding: 8px 15px;
margin: 3px;
border-radius: 5px;
color: white;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
min-width: 80px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
button.active {
background: linear-gradient(45deg, #f093fb 0%, #f5576c 100%);
box-shadow: 0 0 20px rgba(240, 147, 251, 0.5);
}
.slider-container {
margin: 10px 0;
}
.slider-container label {
color: #fff;
font-size: 12px;
display: block;
margin-bottom: 5px;
}
input[type="range"] {
width: 100%;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.3);
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
cursor: pointer;
}
#info {
position: absolute;
bottom: 20px;
left: 20px;
color: rgba(255, 255, 255, 0.8);
font-size: 12px;
max-width: 300px;
}
</style>
<input type="hidden" id="_o_dbjbempljhcmhlfpfacalomonjpalpko" data-inspect-config="7"><script type="text/javascript" src="chrome-extension://odphnbhiddhdpoccbialllejaajemdio/scripts/inspector.js"></script><link id="fl-highlights-css" rel="stylesheet" type="text/css" href="chrome-extension://dppcnmibpgmagiigcnfdjpnghplibbna/fl-highlights.css"><style id="monica-reading-highlight-style">
.monica-reading-highlight {
animation: fadeInOut 1.5s ease-in-out;
}
@keyframes fadeInOut {
0%, 100% { background-color: transparent; }
30%, 70% { background-color: rgba(2, 118, 255, 0.20); }
}
</style><style data-id="immersive-translate-input-injected-css">.immersive-translate-input {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 2147483647;
display: flex;
justify-content: center;
align-items: center;
}
.immersive-translate-attach-loading::after {
content: " ";
--loading-color: #f78fb6;
width: 6px;
height: 6px;
border-radius: 50%;
display: block;
margin: 12px auto;
position: relative;
color: white;
left: -100px;
box-sizing: border-box;
animation: immersiveTranslateShadowRolling 1.5s linear infinite;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-2000%, -50%);
z-index: 100;
}
.immersive-translate-loading-spinner {
vertical-align: middle !important;
width: 10px !important;
height: 10px !important;
display: inline-block !important;
margin: 0 4px !important;
border: 2px rgba(221, 244, 255, 0.6) solid !important;
border-top: 2px rgba(0, 0, 0, 0.375) solid !important;
border-left: 2px rgba(0, 0, 0, 0.375) solid !important;
border-radius: 50% !important;
padding: 0 !important;
-webkit-animation: immersive-translate-loading-animation 0.6s infinite linear !important;
animation: immersive-translate-loading-animation 0.6s infinite linear !important;
}
@-webkit-keyframes immersive-translate-loading-animation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(359deg);
}
}
@keyframes immersive-translate-loading-animation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
.immersive-translate-input-loading {
--loading-color: #f78fb6;
width: 6px;
height: 6px;
border-radius: 50%;
display: block;
margin: 12px auto;
position: relative;
color: white;
left: -100px;
box-sizing: border-box;
animation: immersiveTranslateShadowRolling 1.5s linear infinite;
}
@keyframes immersiveTranslateShadowRolling {
0% {
box-shadow: 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0),
0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
}
12% {
box-shadow: 100px 0 var(--loading-color), 0px 0 rgba(255, 255, 255, 0),
0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
}
25% {
box-shadow: 110px 0 var(--loading-color), 100px 0 var(--loading-color),
0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
}
36% {
box-shadow: 120px 0 var(--loading-color), 110px 0 var(--loading-color),
100px 0 var(--loading-color), 0px 0 rgba(255, 255, 255, 0);
}
50% {
box-shadow: 130px 0 var(--loading-color), 120px 0 var(--loading-color),
110px 0 var(--loading-color), 100px 0 var(--loading-color);
}
62% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 130px 0 var(--loading-color),
120px 0 var(--loading-color), 110px 0 var(--loading-color);
}
75% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0),
130px 0 var(--loading-color), 120px 0 var(--loading-color);
}
87% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0),
200px 0 rgba(255, 255, 255, 0), 130px 0 var(--loading-color);
}
100% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0),
200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0);
}
}
.immersive-translate-toast {
display: flex;
position: fixed;
z-index: 2147483647;
left: 0;
right: 0;
top: 1%;
width: fit-content;
padding: 12px 20px;
margin: auto;
overflow: auto;
background: #fef6f9;
box-shadow: 0px 4px 10px 0px rgba(0, 10, 30, 0.06);
font-size: 15px;
border-radius: 8px;
color: #333;
}
.immersive-translate-toast-content {
display: flex;
flex-direction: row;
align-items: center;
}
.immersive-translate-toast-hidden {
margin: 0 20px 0 72px;
text-decoration: underline;
cursor: pointer;
}
.immersive-translate-toast-close {
color: #666666;
font-size: 20px;
font-weight: bold;
padding: 0 10px;
cursor: pointer;
}
@media screen and (max-width: 768px) {
.immersive-translate-toast {
top: 0;
padding: 12px 0px 0 10px;
}
.immersive-translate-toast-content {
flex-direction: column;
text-align: center;
}
.immersive-translate-toast-hidden {
margin: 10px auto;
}
}
.immersive-translate-dialog {
position: fixed;
z-index: 2147483647;
left: 0;
top: 0;
display: flex;
width: 300px;
flex-direction: column;
align-items: center;
font-size: 15px;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
height: fit-content;
border-radius: 20px;
background-color: #fff;
}
.immersive-translate-modal {
display: none;
position: fixed;
z-index: 2147483647;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
font-size: 15px;
}
.immersive-translate-modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 40px 24px 24px;
border-radius: 12px;
width: 350px;
font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu",
"Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
position: relative;
}
@media screen and (max-width: 768px) {
.immersive-translate-modal-content {
margin: 25% auto !important;
}
}
@media screen and (max-width: 480px) {
.immersive-translate-modal-content {
width: 80vw !important;
margin: 20vh auto !important;
padding: 20px 12px 12px !important;
}
.immersive-translate-modal-title {
font-size: 14px !important;
}
.immersive-translate-modal-body {
font-size: 13px !important;
max-height: 60vh !important;
}
.immersive-translate-btn {
font-size: 13px !important;
padding: 8px 16px !important;
margin: 0 4px !important;
}
.immersive-translate-modal-footer {
gap: 6px !important;
margin-top: 16px !important;
}
}
.immersive-translate-modal .immersive-translate-modal-content-in-input {
max-width: 500px;
}
.immersive-translate-modal-content-in-input .immersive-translate-modal-body {
text-align: left;
max-height: unset;
}
.immersive-translate-modal-title {
text-align: center;
font-size: 16px;
font-weight: 700;
color: #333333;
}
.immersive-translate-modal-body {
text-align: center;
font-size: 14px;
font-weight: 400;
color: #333333;
margin-top: 24px;
}
@media screen and (max-width: 768px) {
.immersive-translate-modal-body {
max-height: 250px;
overflow-y: auto;
}
}
.immersive-translate-close {
color: #666666;
position: absolute;
right: 16px;
top: 16px;
font-size: 20px;
font-weight: bold;
}
.immersive-translate-close:hover,
.immersive-translate-close:focus {
text-decoration: none;
cursor: pointer;
}
.immersive-translate-modal-footer {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin-top: 24px;
}
.immersive-translate-btn {
width: fit-content;
color: #fff;
background-color: #ea4c89;
border: none;
font-size: 14px;
margin: 0 8px;
padding: 9px 30px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.3s ease;
}
.immersive-translate-btn-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
}
.immersive-translate-btn:hover {
background-color: #f082ac;
}
.immersive-translate-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.immersive-translate-btn:disabled:hover {
background-color: #ea4c89;
}
.immersive-translate-link-btn {
background-color: transparent;
color: #ea4c89;
border: none;
cursor: pointer;
height: 30px;
line-height: 30px;
}
.immersive-translate-cancel-btn {
/* gray color */
background-color: rgb(89, 107, 120);
}
.immersive-translate-cancel-btn:hover {
background-color: hsl(205, 20%, 32%);
}
.immersive-translate-action-btn {
background-color: transparent;
color: #ea4c89;
border: 1px solid #ea4c89;
}
.immersive-translate-btn svg {
margin-right: 5px;
}
.immersive-translate-link {
cursor: pointer;
user-select: none;
-webkit-user-drag: none;
text-decoration: none;
color: #ea4c89;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
}
.immersive-translate-primary-link {
cursor: pointer;
user-select: none;
-webkit-user-drag: none;
text-decoration: none;
color: #ea4c89;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
}
.immersive-translate-modal input[type="radio"] {
margin: 0 6px;
cursor: pointer;
}
.immersive-translate-modal label {
cursor: pointer;
}
.immersive-translate-close-action {
position: absolute;
top: 2px;
right: 0px;
cursor: pointer;
}
.imt-image-status {
background-color: rgba(0, 0, 0, 0.5) !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
border-radius: 16px !important;
}
.imt-image-status img,
.imt-image-status svg,
.imt-img-loading {
width: 28px !important;
height: 28px !important;
margin: 0 0 8px 0 !important;
min-height: 28px !important;
min-width: 28px !important;
position: relative !important;
}
.imt-img-loading {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAMAAACfWMssAAAAtFBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////oK74hAAAAPHRSTlMABBMIDyQXHwyBfFdDMSw+OjXCb+5RG51IvV/k0rOqlGRM6KKMhdvNyZBz9MaupmxpWyj437iYd/yJVNZeuUC7AAACt0lEQVRIx53T2XKiUBCA4QYOiyCbiAsuuGBcYtxiYtT3f6/pbqoYHVFO5r+iivpo6DpAWYpqeoFfr9f90DsYAuRSWkFnPO50OgR9PwiCUFcl2GEcx+N/YBh6pvKaefHlUgZd1zVe0NbYcQjGBfzrPE8Xz8aF+71D8gG6DHFPpc4a7xFiCDuhaWgKgGIJQ3d5IMGDrpS4S5KgpIm+en9f6PlAhKby4JwEIxlYJV9h5k5nee9GoxHJ2IDSNB0dwdad1NAxDJ/uXDHYmebdk4PdbkS58CIVHdYSUHTYYRWOJblWSyu2lmy3KNFVJNBhxcuGW4YBVCbYGRZwIooipHsNqjM4FbgOQqQqSKQQU9V8xmi1QlgHqQQ6DDBvRUVCDirs+EzGDGOQTCATgtYTnbCVLgsVgRE0T1QE0qHCFAht2z6dLvJQs3Lo2FQoDxWNUiBhaP4eRgwNkI+dAjVOA/kUrIDwf3CG8NfNOE0eiFotSuo+rBiq8tD9oY4Qzc6YJw99hl1wzpQvD7ef2M8QgnOGJfJw+EltQc+oX2yn907QB22WZcvlUpd143dqQu+8pCJZuGE4xCuPXJqqcs5sNpsI93Rmzym1k4Npk+oD1SH3/a3LOK/JpUBpWfqNySxWzCfNCUITuDG5dtuphrUJ1myeIE9bIsPiKrfqTai5WZxbhtNphYx6GEIHihyGFTI69lje/rxajdh0s0msZ0zYxyPLhYCb1CyHm9Qsd2H37Y3lugVwL9kNh8Ot8cha6fUNQ8nuXi5z9/ExsAO4zQrb/ev1yrCB7lGyQzgYDGuxq1toDN/JGvN+HyWNHKB7zEoK+PX11e12G431erGYzwmytAWU56fkMHY5JJnDRR2eZji3AwtIcrEV8Cojat/BdQ7XOwGV1e1hDjGGjXbdArm8uJZtCH5MbcctVX8A1WpqumJHwckAAAAASUVORK5CYII=");
background-size: 28px 28px;
animation: image-loading-rotate 1s linear infinite !important;
}
.imt-image-status span {
color: var(--bg-2, #fff) !important;
font-size: 14px !important;
line-height: 14px !important;
font-weight: 500 !important;
font-family: "PingFang SC", Arial, sans-serif !important;
}
.imt-primary-button {
display: flex;
padding: 12px 80px;
justify-content: center;
align-items: center;
gap: 8px;
border-radius: 8px;
background: #ea4c89;
color: #fff;
font-size: 16px;
font-style: normal;
font-weight: 700;
line-height: 24px;
border: none;
cursor: pointer;
}
.imt-retry-text {
color: #999;
text-align: center;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 21px;
cursor: pointer;
}
.imt-action-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.imt-modal-content-text {
text-align: left;
color: #333;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
@keyframes image-loading-rotate {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
</style></head>
<body monica-id="ofpnmcalabcbjgholdjcjblkibolbppb" monica-version="7.9.7">
<div id="container">
<div id="controls">
<div class="control-section">
<h3>动作切换</h3>
<button id="idle" class="active">站立</button>
<button id="walk">走路</button>
<button id="run">奔跑</button>
<button id="jump">跳跃</button>
</div>
<div class="control-section">
<h3>动画设置</h3>
<div class="slider-container">
<label>混合速度: <span id="blendSpeedValue">2.0</span></label>
<input type="range" id="blendSpeed" min="0.5" max="5.0" step="0.1" value="2.0">
</div>
<div class="slider-container">
<label>动画速度: <span id="animSpeedValue">1.0</span></label>
<input type="range" id="animSpeed" min="0.1" max="3.0" step="0.1" value="1.0">
</div>
</div>
<div class="control-section">
<button id="autoMode">自动模式</button>
</div>
</div>
<div id="info">
<strong>骨骼动画混合演示</strong><br>
点击右侧按钮切换动作,观察不同动画之间的平滑过渡。<br>
• 蓝色关节:骨骼系统<br>
• 绿色线条:骨骼连接<br>
• 红色方块:蒙皮网格
</div>
<canvas width="753" height="672" style="display: block; width: 753px; height: 672px;"></canvas></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// 全局变量
let scene, camera, renderer, character;
let currentAction = 'idle';
let isAutoMode = false;
let autoModeTimer = 0;
let blendSpeed = 2.0;
let animationSpeed = 1.0;
// 动画状态
const animations = {
idle: { weight: 1.0, speed: 1.0 },
walk: { weight: 0.0, speed: 1.5 },
run: { weight: 0.0, speed: 2.0 },
jump: { weight: 0.0, speed: 1.2 }
};
// 简化的骨骼系统
class SimpleSkeleton {
constructor() {
this.bones = [];
this.joints = [];
this.meshes = [];
this.time = 0;
this.createSkeleton();
}
createSkeleton() {
// 创建骨骼结构(简化的人形)
const boneStructure = [
{ name: 'root', pos: [0, 0, 0], parent: null },
{ name: 'spine', pos: [0, 1, 0], parent: 'root' },
{ name: 'chest', pos: [0, 2, 0], parent: 'spine' },
{ name: 'neck', pos: [0, 2.8, 0], parent: 'chest' },
{ name: 'head', pos: [0, 3.5, 0], parent: 'neck' },
{ name: 'leftShoulder', pos: [-0.8, 2.5, 0], parent: 'chest' },
{ name: 'leftArm', pos: [-1.5, 2.5, 0], parent: 'leftShoulder' },
{ name: 'leftForearm', pos: [-2.2, 2.5, 0], parent: 'leftArm' },
{ name: 'rightShoulder', pos: [0.8, 2.5, 0], parent: 'chest' },
{ name: 'rightArm', pos: [1.5, 2.5, 0], parent: 'rightShoulder' },
{ name: 'rightForearm', pos: [2.2, 2.5, 0], parent: 'rightArm' },
{ name: 'leftHip', pos: [-0.3, 0, 0], parent: 'root' },
{ name: 'leftThigh', pos: [-0.3, -0.8, 0], parent: 'leftHip' },
{ name: 'leftCalf', pos: [-0.3, -1.6, 0], parent: 'leftThigh' },
{ name: 'leftFoot', pos: [-0.3, -2.2, 0.2], parent: 'leftCalf' },
{ name: 'rightHip', pos: [0.3, 0, 0], parent: 'root' },
{ name: 'rightThigh', pos: [0.3, -0.8, 0], parent: 'rightHip' },
{ name: 'rightCalf', pos: [0.3, -1.6, 0], parent: 'rightThigh' },
{ name: 'rightFoot', pos: [0.3, -2.2, 0.2], parent: 'rightCalf' }
];
// 创建骨骼对象
const boneMap = {};
boneStructure.forEach(bone => {
const boneObject = new THREE.Object3D();
boneObject.position.set(bone.pos[0], bone.pos[1], bone.pos[2]);
boneObject.name = bone.name;
boneMap[bone.name] = boneObject;
this.bones.push(boneObject);
// 创建关节可视化
const jointGeometry = new THREE.SphereGeometry(0.08, 8, 8);
const jointMaterial = new THREE.MeshBasicMaterial({ color: 0x4488ff });
const joint = new THREE.Mesh(jointGeometry, jointMaterial);
boneObject.add(joint);
this.joints.push(joint);
scene.add(boneObject);
});
// 建立父子关系
boneStructure.forEach(bone => {
if (bone.parent) {
const parentBone = boneMap[bone.parent];
const childBone = boneMap[bone.name];
parentBone.add(childBone);
// 创建骨骼连接线
const lineGeometry = new THREE.BufferGeometry();
const lineVertices = new Float32Array([
0, 0, 0,
childBone.position.x - parentBone.position.x,
childBone.position.y - parentBone.position.y,
childBone.position.z - parentBone.position.z
]);
lineGeometry.setAttribute('position', new THREE.BufferAttribute(lineVertices, 3));
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff88 });
const line = new THREE.Line(lineGeometry, lineMaterial);
parentBone.add(line);
}
});
this.boneMap = boneMap;
// 创建简单的蒙皮网格
this.createSkinMesh();
}
createSkinMesh() {
// 身体部位的简单网格
const bodyParts = [
{ name: 'torso', bone: 'chest', size: [0.8, 1.2, 0.4], offset: [0, -0.3, 0] },
{ name: 'head', bone: 'head', size: [0.4, 0.4, 0.4], offset: [0, 0, 0] },
{ name: 'leftArm', bone: 'leftArm', size: [0.6, 0.15, 0.15], offset: [-0.3, 0, 0] },
{ name: 'rightArm', bone: 'rightArm', size: [0.6, 0.15, 0.15], offset: [0.3, 0, 0] },
{ name: 'leftThigh', bone: 'leftThigh', size: [0.2, 0.7, 0.2], offset: [0, -0.35, 0] },
{ name: 'rightThigh', bone: 'rightThigh', size: [0.2, 0.7, 0.2], offset: [0, -0.35, 0] },
{ name: 'leftCalf', bone: 'leftCalf', size: [0.15, 0.6, 0.15], offset: [0, -0.3, 0] },
{ name: 'rightCalf', bone: 'rightCalf', size: [0.15, 0.6, 0.15], offset: [0, -0.3, 0] }
];
bodyParts.forEach(part => {
const geometry = new THREE.BoxGeometry(part.size[0], part.size[1], part.size[2]);
const material = new THREE.MeshPhongMaterial({
color: 0xff4444,
transparent: true,
opacity: 0.7
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(part.offset[0], part.offset[1], part.offset[2]);
if (this.boneMap[part.bone]) {
this.boneMap[part.bone].add(mesh);
this.meshes.push(mesh);
}
});
}
// 动画函数
animate(action, time, weight = 1.0) {
const t = time * animationSpeed;
switch(action) {
case 'idle':
this.animateIdle(t, weight);
break;
case 'walk':
this.animateWalk(t, weight);
break;
case 'run':
this.animateRun(t, weight);
break;
case 'jump':
this.animateJump(t, weight);
break;
}
}
animateIdle(time, weight) {
const breathe = Math.sin(time * 2) * 0.02 * weight;
const sway = Math.sin(time * 0.8) * 0.01 * weight;
if (this.boneMap['chest']) {
this.boneMap['chest'].rotation.z = sway;
this.boneMap['chest'].position.y = 2 + breathe;
}
if (this.boneMap['head']) {
this.boneMap['head'].rotation.x = Math.sin(time * 1.5) * 0.05 * weight;
}
}
animateWalk(time, weight) {
const walkCycle = time * 3;
const armSwing = Math.sin(walkCycle) * 0.3 * weight;
const legSwing = Math.sin(walkCycle) * 0.4 * weight;
const bodyBob = Math.abs(Math.sin(walkCycle * 2)) * 0.05 * weight;
// 手臂摆动
if (this.boneMap['leftArm']) {
this.boneMap['leftArm'].rotation.x = armSwing;
}
if (this.boneMap['rightArm']) {
this.boneMap['rightArm'].rotation.x = -armSwing;
}
// 腿部运动
if (this.boneMap['leftThigh']) {
this.boneMap['leftThigh'].rotation.x = legSwing;
}
if (this.boneMap['rightThigh']) {
this.boneMap['rightThigh'].rotation.x = -legSwing;
}
// 身体上下摆动
if (this.boneMap['root']) {
this.boneMap['root'].position.y = bodyBob;
}
}
animateRun(time, weight) {
const runCycle = time * 6;
const armSwing = Math.sin(runCycle) * 0.6 * weight;
const legSwing = Math.sin(runCycle) * 0.8 * weight;
const bodyBob = Math.abs(Math.sin(runCycle * 2)) * 0.15 * weight;
const bodyLean = Math.sin(runCycle) * 0.1 * weight;
// 更大的手臂摆动
if (this.boneMap['leftArm']) {
this.boneMap['leftArm'].rotation.x = armSwing;
}
if (this.boneMap['rightArm']) {
this.boneMap['rightArm'].rotation.x = -armSwing;
}
// 更大的腿部运动
if (this.boneMap['leftThigh']) {
this.boneMap['leftThigh'].rotation.x = legSwing;
}
if (this.boneMap['rightThigh']) {
this.boneMap['rightThigh'].rotation.x = -legSwing;
}
// 身体前倾和上下摆动
if (this.boneMap['root']) {
this.boneMap['root'].position.y = bodyBob;
}
if (this.boneMap['chest']) {
this.boneMap['chest'].rotation.x = bodyLean * 0.3;
}
}
animateJump(time, weight) {
const jumpPhase = (time % 2) / 2; // 2秒一个循环
let jumpY = 0;
let armPose = 0;
let legBend = 0;
if (jumpPhase < 0.3) { // 准备阶段
const t = jumpPhase / 0.3;
jumpY = -0.2 * t * weight;
legBend = 0.5 * t * weight;
armPose = -0.5 * t * weight;
} else if (jumpPhase < 0.7) { // 跳跃阶段
const t = (jumpPhase - 0.3) / 0.4;
const arc = Math.sin(t * Math.PI);
jumpY = (arc * 1.5 - 0.2) * weight;
legBend = (0.5 - arc * 0.3) * weight;
armPose = (arc * 1.2 - 0.5) * weight;
} else { // 着陆阶段
const t = (jumpPhase - 0.7) / 0.3;
jumpY = -0.1 * (1 - t) * weight;
legBend = 0.2 * (1 - t) * weight;
armPose = 0.7 * (1 - t) * weight;
}
if (this.boneMap['root']) {
this.boneMap['root'].position.y = jumpY;
}
if (this.boneMap['leftArm']) {
this.boneMap['leftArm'].rotation.x = armPose;
}
if (this.boneMap['rightArm']) {
this.boneMap['rightArm'].rotation.x = armPose;
}
if (this.boneMap['leftThigh']) {
this.boneMap['leftThigh'].rotation.x = -legBend;
}
if (this.boneMap['rightThigh']) {
this.boneMap['rightThigh'].rotation.x = -legBend;
}
}
update(deltaTime) {
this.time += deltaTime;
// 更新动画权重(混合)
for (let action in animations) {
const anim = animations[action];
if (action === currentAction) {
anim.weight = Math.min(1.0, anim.weight + deltaTime * blendSpeed);
} else {
anim.weight = Math.max(0.0, anim.weight - deltaTime * blendSpeed);
}
}
// 清除之前的变换
this.resetBones();
// 应用混合动画
for (let action in animations) {
const anim = animations[action];
if (anim.weight > 0.001) {
this.animate(action, this.time * anim.speed, anim.weight);
}
}
}
resetBones() {
// 重置关键骨骼的变换
const resetBones = ['root', 'chest', 'head', 'leftArm', 'rightArm', 'leftThigh', 'rightThigh'];
resetBones.forEach(boneName => {
if (this.boneMap[boneName]) {
this.boneMap[boneName].rotation.set(0, 0, 0);
if (boneName === 'root') {
this.boneMap[boneName].position.set(0, 0, 0);
} else if (boneName === 'chest') {
this.boneMap[boneName].position.set(0, 2, 0);
}
}
});
}
}
// 初始化场景
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x222244);
// 创建摄像机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 2, 5);
camera.lookAt(0, 1, 0);
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('container').appendChild(renderer.domElement);
// 添加灯光
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
scene.add(directionalLight);
// 添加地面
const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x336633 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -2.5;
ground.receiveShadow = true;
scene.add(ground);
// 创建角色
character = new SimpleSkeleton();
// 设置控件
setupControls();
// 开始动画循环
animate();
}
function setupControls() {
// 动作按钮
const actionButtons = ['idle', 'walk', 'run', 'jump'];
actionButtons.forEach(action => {
const button = document.getElementById(action);
button.addEventListener('click', () => {
if (!isAutoMode) {
setAction(action);
updateActiveButton(action);
}
});
});
// 滑块控制
const blendSpeedSlider = document.getElementById('blendSpeed');
const blendSpeedValue = document.getElementById('blendSpeedValue');
blendSpeedSlider.addEventListener('input', (e) => {
blendSpeed = parseFloat(e.target.value);
blendSpeedValue.textContent = blendSpeed.toFixed(1);
});
const animSpeedSlider = document.getElementById('animSpeed');
const animSpeedValue = document.getElementById('animSpeedValue');
animSpeedSlider.addEventListener('input', (e) => {
animationSpeed = parseFloat(e.target.value);
animSpeedValue.textContent = animationSpeed.toFixed(1);
});
// 自动模式
const autoButton = document.getElementById('autoMode');
autoButton.addEventListener('click', () => {
isAutoMode = !isAutoMode;
autoButton.textContent = isAutoMode ? '手动模式' : '自动模式';
autoButton.classList.toggle('active');
if (!isAutoMode) {
updateActiveButton(currentAction);
}
});
}
function setAction(action) {
currentAction = action;
}
function updateActiveButton(action) {
document.querySelectorAll('#controls button').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(action).classList.add('active');
}
let lastTime = 0;
function animate() {
requestAnimationFrame(animate);
const currentTime = performance.now() * 0.001;
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// 自动模式切换动作
if (isAutoMode) {
autoModeTimer += deltaTime;
if (autoModeTimer > 3) { // 每3秒切换一次动作
const actions = ['idle', 'walk', 'run', 'jump'];
const currentIndex = actions.indexOf(currentAction);
const nextAction = actions[(currentIndex + 1) % actions.length];
setAction(nextAction);
autoModeTimer = 0;
}
}
// 更新角色动画
if (character) {
character.update(deltaTime);
}
// 摄像机环绕
const time = currentTime * 0.3;
camera.position.x = Math.cos(time) * 6;
camera.position.z = Math.sin(time) * 6;
camera.position.y = 3;
camera.lookAt(0, 1, 0);
renderer.render(scene, camera);
}
// 窗口大小调整
window.addEventListener('resize', () => {
const container = document.getElementById('container');
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
camera.aspect = containerWidth / containerHeight;
camera.updateProjectionMatrix();
renderer.setSize(containerWidth, containerHeight);
});
// 启动应用
init();
</script>
<div id="monica-content-root" class="monica-widget" style="pointer-events: auto;"></div></body><div id="immersive-translate-popup" style="all: initial"></div></html>

需要全屏演示的同学可以通过**骨骼动画混合演示**查看。