<div class="taro-pet-dialog" id="taro-pet-dialog"></div>
.taro-pet-dialog {
opacity: 0;
user-select: none;
position: absolute;
left: 68px;
bottom: 245px;
display: flex;
gap: 4px;
flex-direction: column;
align-items: center;
justify-content: center;
width: 250px;
min-height: 50px;
padding: 10px 21px 10px 35px;
transition: all 0.5s ease-in-out;
}
.taro-pet-dialog p {
font-size: 15px;
font-family: "楷体";
font-weight: 800;
opacity: 0.9;
}
export const voice_config = [
{
dialog: "card_green.png",
color: "#33812a",
voice: "1.mp3",
element: `
<p>就让身为引导者的我</p>
<p>全力支持主人吧 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "2.mp3",
element: `
<p>主人,肚子饿不饿?</p>
<p>任何事都要先</p>
<p>填饱肚子再说 ♪ ♪ ♪</p>
`,
}
...其他配置
]
// 音频的配置
let voice_config = null;
// 词板的dom
let dialog_dom = null;
// 获取配置
const module = await import(`../modules/${current_module}/${current_module}.js`);
init_config = module.init_config;
action_config = module.action_config;
voice_config = module.voice_config;
// 赋值音频dom,词板dom
audio_dom = document.getElementById("taro-pet-audio");
dialog_dom = document.getElementById("taro-pet-dialog");
// 左键点击播放语音
anim.on("click", (event) => {
if (drag_static === false && talk_static === false) {
talk_static = true;
// 随机获取音频的index
const index = Math.floor(Math.random() * voice_config.length);
const voice_data = voice_config[index];
// 设置音频文件的 URL
audio_dom.src = `${file_prefix}/${current_module}/voice/${voice_data.voice}`;
// 设置词板
dialog_dom.style.color = voice_data.color;
// 这里必须要一同设置repeat和size等,不然背景图会变形
dialog_dom.style.background = `url("./public/images/${voice_data.dialog}") no-repeat center / 100% 100%`;
dialog_dom.style.opacity = 1;
dialog_dom.innerHTML = voice_data.element;
const onAudioEnded = () => {
talk_static = false;
dialog_dom.style.opacity = 0;
// 移除事件监听器
audio_dom.removeEventListener("ended", onAudioEnded);
};
audio_dom.addEventListener("ended", onAudioEnded);
audio_dom.play();
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TaroPet</title>
<link rel="stylesheet" href="./public/css/index.css" />
</head>
<body>
<div id="taro-pet" class="taro-pet">
<canvas id="taro-canvas"></canvas>
<canvas id="shadow-canvas"></canvas>
</div>
<div class="taro-pet-dialog" id="taro-pet-dialog"></div>
<audio id="taro-pet-audio" src="" controls="" preload=""></audio>
<script src="./public/js/pixi.min.js"></script>
<script src="./_renderer/renderer.js" type="module"></script>
</body>
</html>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html,
body {
width: 100vw;
height: 100vh;
overflow: hidden;
}
.taro-pet {
width: 100vw;
height: 100vh;
transform: scaleX(-1);
}
.taro-pet-dialog {
opacity: 0;
user-select: none;
position: absolute;
left: 68px;
bottom: 245px;
display: flex;
gap: 4px;
flex-direction: column;
align-items: center;
justify-content: center;
width: 250px;
min-height: 50px;
padding: 10px 21px 10px 35px;
transition: all 0.5s ease-in-out;
}
.taro-pet-dialog p {
font-size: 15px;
font-family: "楷体";
font-weight: 800;
opacity: 0.9;
}
#taro-pet-audio {
display: none;
}
#shadow-canvas {
position: absolute;
z-index: -1;
opacity: 0;
}
export const init_config = [
{
index: 0,
name: "normal",
frames: 60,
texture: [],
object: {
loop: true,
},
},
{
index: 1,
name: "drap",
frames: 54,
texture: [],
object: {
loop: true,
},
},
];
export const action_config = [
{
index: 0,
name: "dear_jump",
frames: 66,
texture: [],
object: {
loop: false,
},
},
{
index: 1,
name: "dear_smile",
frames: 64,
texture: [],
object: {
loop: false,
},
},
{
index: 2,
name: "high_touch",
frames: 51,
texture: [],
object: {
loop: false,
},
},
{
index: 3,
name: "joy_long",
frames: 108,
texture: [],
object: {
loop: false,
},
},
{
index: 4,
name: "joy_short",
frames: 54,
texture: [],
object: {
loop: false,
},
},
{
index: 5,
name: "salute_smile",
frames: 94,
texture: [],
object: {
loop: false,
},
},
{
index: 6,
name: "stand_by",
frames: 54,
texture: [],
object: {
loop: false,
},
},
{
index: 7,
name: "weapon_joy",
frames: 107,
texture: [],
object: {
loop: false,
endTime: 800,
},
},
{
index: 8,
name: "weapon_skill",
frames: 80,
texture: [],
object: {
loop: false,
},
},
{
index: 9,
name: "weapon_ub",
frames: 247,
texture: [],
object: {
loop: false,
},
},
{
index: 10,
name: "cheer_joy",
frames: 54,
texture: [],
object: {
loop: false,
},
},
{
index: 11,
name: "mana_jump",
frames: 31,
texture: [],
object: {
loop: false,
},
},
];
export const voice_config = [
{
dialog: "card_green.png",
color: "#33812a",
voice: "1.mp3",
element: `
<p>就让身为引导者的我</p>
<p>全力支持主人吧 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "2.mp3",
element: `
<p>主人,肚子饿不饿?</p>
<p>任何事都要先</p>
<p>填饱肚子再说 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "3.mp3",
element: `
<p>看来是累了呢</p>
<p>管理健康也是我的职责</p>
<p>要是有什么吩咐</p>
<p>请尽管说 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "4.mp3",
element: `
<p>能够服侍主人这样</p>
<p>温柔的人</p>
<p>我真是个幸福的人 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "5.mp3",
element: `
<p>请让我像主人的夫人一样</p>
<p>来服侍主人吧 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "6.mp3",
element: `
<p>为了与主人相遇做准备</p>
<p>我从小时候起</p>
<p>就和精灵交谈</p>
<p>请尽管使唤我吧 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "7.mp3",
element: `
<p>被主人注视着</p>
<p>精灵们好像也很高兴 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "8.mp3",
element: `
<p>我会与大自然一同</p>
<p>助主人一臂之力 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "9.mp3",
element: `
<p>我从年幼时起</p>
<p>就一直期待着</p>
<p>能够服侍主人 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "10.mp3",
element: `
<p>有什么事就请呼唤我</p>
<p>我会全心全意地</p>
<p>回应主人的要求 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "11.mp3",
element: `
<p>是与主人的情谊</p>
<p>催生出了这般的力量吧</p>
<p>主人……从今往后</p>
<p>也请一直让我陪伴在您身边</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "12.mp3",
element: `
<p>我的感情</p>
<p>得到了精灵们的回应……</p>
<p>有了这股力量</p>
<p>今后就能更好地服侍主人了</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "13.mp3",
element: `
<p>多么轻柔的微风…</p>
<p>呵呵……简直就像是精灵们</p>
<p>在为我和主人</p>
<p>送来祝福呢 ♪ ♪ ♪</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "14.mp3",
element: `
<p>花瓣宛如新娘的捧花</p>
<p>我和主人也……哈!</p>
<p>我、我竟然,</p>
<p>会去想象这么逾矩的事情</p>
`,
},
{
dialog: "card_green.png",
color: "#33812a",
voice: "15.mp3",
element: `
<p>有了这股力量</p>
<p>就能更加周到地照顾主人了</p>
<p>自早至晚</p>
<p>全都交给我吧 ♪ ♪ ♪</p>
`,
},
];
// 导入方法
import { get_pixel_color_func, body_area_func } from "../utils/anim.js";
// 当前使用的模型
let current_module = "kkr";
// 初始配置和动作配置
let init_config = null;
let action_config = null;
let voice_config = null;
// 当前的动画
let anim_current = null;
// 初始动画
let anim_normal = null;
// 拖拽动画
let anim_drap = null;
// 存储的随机动作索引,用来给定时器随机
let anim_action_random_index = [];
let anim_action_random_cache = [];
// 动画的定时器
let timer = null;
// 拖拽状态
let drag_static = false;
// 语音状态
let talk_static = false;
// 动画的舞台
let app = null;
// 音频的dom
let audio_dom = null;
// 词板的dom
let dialog_dom = null;
// 动画的canvas
let anim_canvas = null;
// 虚拟的canvas
let shadow_canvas = null;
// 获取窗口的大小
const bower_width = window.innerWidth;
const bower_height = window.innerHeight;
// 页面加载完成执行
window.addEventListener("load", async (event) => {
// 获取配置
const module = await import(`../modules/${current_module}/${current_module}.js`);
init_config = module.init_config;
action_config = module.action_config;
voice_config = module.voice_config;
// 赋值canvas
anim_canvas = document.getElementById("taro-canvas");
shadow_canvas = document.getElementById("shadow-canvas");
shadow_canvas.width = bower_width;
shadow_canvas.height = bower_height;
// 赋值音频dom,词板dom
audio_dom = document.getElementById("taro-pet-audio");
dialog_dom = document.getElementById("taro-pet-dialog");
// 动画舞台配置
app = new PIXI.Application({
view: anim_canvas,
width: bower_width,
height: bower_height,
backgroundAlpha: 0,
resolution: 1,
});
// 添加给div-taropet元素
document.getElementById("taro-pet").appendChild(app.view);
// 先把初始的动画加载完成
anim_normal = await create_anim_func(init_config[0], 0);
anim_normal.play();
app.stage.addChild(anim_normal);
// 赋值给当前动画
anim_current = anim_normal;
// 开始初始化其他的动画
anim_drap = await create_anim_func(init_config.find((obj) => obj.name === "drap"), 0);
// 将动作的配置转换成随机索引赋值 [0,1,2,3,4]
anim_action_random_index = Array.from(
action_config.map((obj, index) => {
obj.index = index;
return obj;
}),
({ index }) => index
);
// 开启定时器
setIntervalTimer();
});
// 创建动画的方法 obj-配置对象, type-是否初始化0/1
const create_anim_func = async (obj, type) => {
// 存放文件前缀, 文件格式(png,jpg)
const file_prefix = "./modules";
const file_format = ".png";
const { name, frames, object } = obj;
const texture_array = [];
// 通过帧数循环获取贴图
for (let i = 0; i < frames; i++) {
const num = `000${i}`.slice(-3);
// texture_name ./modules/kkr/normal/001.png
const texture_name = type === 0 ? `${file_prefix}/${current_module}/${name}/${num}${file_format}` : `${file_prefix}/${current_module}/action/${name}/${num}${file_format}`;
const texture = await PIXI.Texture.from(texture_name);
texture_array.push(texture);
}
// 生成动画,配置动画属性
const anim = new PIXI.AnimatedSprite(texture_array);
anim.name = name;
anim.animationSpeed = 0.5;
anim.loop = object.loop;
// 设置交互模式
anim.eventMode = "dynamic";
// 鼠标移动事件
anim.on("mousemove", (event) => {
const global_position = event.data.global;
const local_position = anim.toLocal(global_position);
// 当前这一帧的动画贴图
const anim_img = anim.texture.baseTexture.resource.source;
if (drag_static) {
// 这个时候在拖拽,什么都不做
} else {
body_area_func(get_pixel_color_func(local_position.x, local_position.y, anim_img), local_position)
}
});
// 左键点击播放语音
anim.on("click", (event) => {
if (drag_static === false && talk_static === false) {
talk_static = true;
// 随机获取音频的index
const index = Math.floor(Math.random() * voice_config.length);
const voice_data = voice_config[index];
// 设置音频文件的 URL
audio_dom.src = `${file_prefix}/${current_module}/voice/${voice_data.voice}`;
// 设置词板
dialog_dom.style.color = voice_data.color;
// 这里必须要一同设置repeat和size等,不然背景图会变形
dialog_dom.style.background = `url("./public/images/${voice_data.dialog}") no-repeat center / 100% 100%`;
dialog_dom.style.opacity = 1;
dialog_dom.innerHTML = voice_data.element;
const onAudioEnded = () => {
talk_static = false;
dialog_dom.style.opacity = 0;
// 移除事件监听器
audio_dom.removeEventListener("ended", onAudioEnded);
};
audio_dom.addEventListener("ended", onAudioEnded);
audio_dom.play();
}
});
// 鼠标点击右键拖拽
anim.on("rightclick", (event) => {
const global_position = event.data.global;
const local_position = anim.toLocal(global_position);
if (drag_static === false) {
// 如果没在拖拽状态,右键后进入推拽状态,传给主进程点击的位置
window.mouseAPI.mouseDrapStart({
x: local_position.x,
y: local_position.y,
drap: true,
});
// 开启拖拽状态进入拖拽动画
drag_static = true;
change_anim_func(anim_current, anim_drap, 0);
} else {
// 再次点击脱离拖拽状态
window.mouseAPI.mouseDrapEnd({
drap: false,
});
// 取消拖拽状态进入普通动画
drag_static = false;
change_anim_func(anim_drap, anim_normal, 0);
}
});
if (object.loop === false) {
anim.onComplete = () => {
// 完成动作后500毫秒后进入普通动画
change_anim_func(anim, anim_normal, object?.endTime ?? 100);
};
}
if (type === 1) {
// 缓存随机动作,这样下次不需要再次生成
anim_action_random_cache.push(anim);
// 给生成动画时间
setTimeout(() => {
// 生成动画后1秒后进入动作动画
if (drag_static === false) {
change_anim_func(anim_normal, anim, 0);
}
}, 1000);
} else {
// 如果是初始动画的话就返回动画
return anim;
}
};
// 进入动画,可以用来切换动画(切换回normal或进入drap)
const change_anim_func = (from_anim, to_anim, time) => {
from_anim.stop();
setTimeout(() => {
app.stage.removeChild(from_anim);
to_anim.gotoAndPlay(0);
app.stage.addChild(to_anim);
// 替换当前动画
anim_current = to_anim;
}, time);
};
// 设置定时器,用来一定时间播放一次随机动作
const setIntervalTimer = () => {
timer = setInterval(() => {
if (drag_static === false && talk_static === false) {
// 随机获取动作的index
const index = Math.floor(Math.random() * anim_action_random_index.length);
// 通过index获取动作的配置
const action = action_config[anim_action_random_index[index]];
// 如果有缓存的动作就不需要生成了
const cacheAction = anim_action_random_cache.find((obj) => obj.name === action.name);
if (cacheAction) {
change_anim_func(anim_normal, cacheAction, 0);
} else {
create_anim_func(action, 1);
}
} else {
// 说明是在拖拽,什么都不做
}
}, 1000 * 60 * 5);
};
此页面不支持夜间模式!
已进入夜间模式!
已进入普通模式!
搜索框不允许为空
签到成功!经验+5!芋圆币+2!
签到失败!今日已签到!
需要登录社区账号才可以进入!