Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 支持使用本地模型的能力 #172

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
## 使用

如果你是小白,或者只需要最基础的功能,那么只用将这一行代码加入 html 页面的 `head` 或 `body` 中,即可加载看板娘:

```xml
<script src="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script>
```

添加代码的位置取决于你的网站的构建方式。例如,如果你使用的是 [Hexo](https://hexo.io),那么需要在主题的模版文件中添加以上代码。对于用各种模版引擎生成的页面,修改方法类似。
如果网站启用了 PJAX,由于看板娘不必每页刷新,需要注意将该脚本放到 PJAX 刷新区域之外。

Expand All @@ -39,12 +41,15 @@

你可以对照 `autoload.js` 的源码查看可选的配置项目。`autoload.js` 会自动加载三个文件:`waifu.css`,`live2d.min.js` 和 `waifu-tips.js`。`waifu-tips.js` 会创建 `initWidget` 函数,这就是加载看板娘的主函数。`initWidget` 函数接收一个 Object 类型的参数,作为看板娘的配置。以下是配置选项:

| 选项 | 类型 | 默认值 | 说明 |
| - | - | - | - |
| `waifuPath` | `string` | `https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/waifu-tips.json` | 看板娘资源路径,可自行修改 |
| `apiPath` | `string` | `https://live2d.fghrsh.net/api/` | API 路径,可选参数 |
| `cdnPath` | `string` | `https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/` | CDN 路径,可选参数 |
| `tools` | `string[]` | 见 `autoload.js` | 加载的小工具按钮,可选参数 |
| 选项 | 类型 | 默认值 | 说明 |
| --------------- | ---------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `waifuPath` | `string` | `https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/waifu-tips.json` | 看板娘资源路径,可自行修改 |
| `apiPath` | `string` | `https://live2d.fghrsh.net/api/` | API 路径,可选参数 |
| `cdnPath` | `string` | `https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/` | CDN 路径,可选参数 |
| `tools` | `string[]` | 见 `autoload.js` | 加载的小工具按钮,可选参数 |
| `isLocalModel` | `boolearn` | 见 `demo.js` | 是否开启使用本地模型,不声明就是使用远程 |
| `modelsPath` | ` string` | 见 `demo.js` | 本地模型文件夹所在目录,设置本地后必须要设置 |
| `modelListPath` | `string` | 见 `demo.js` | 模型列表,如果不写,就约定为 `modelsPath` 下的 `model_list.json` 文件 |

其中,`apiPath` 和 `cdnPath` 两个参数设置其中一项即可。`apiPath` 是后端 API 的 URL,可以自行搭建,并增加模型(需要修改的内容比较多,此处不再赘述),可以参考 [live2d_api](https://github.com/fghrsh/live2d_api)。而 `cdnPath` 则是通过 jsDelivr 这样的 CDN 服务加载资源,更加稳定。

Expand Down Expand Up @@ -77,10 +82,13 @@ npm run build
### 使用 CDN

要自定义有关内容,可以把这个仓库 Fork 一份,然后把修改后的内容通过 git push 到你的仓库中。这时,使用方法对应地变为

```xml
<script src="https://fastly.jsdelivr.net/gh/username/live2d-widget@latest/autoload.js"></script>
```

将此处的 `username` 替换为你的 GitHub 用户名。为了使 CDN 的内容正常刷新,需要创建新的 git tag 并推送至 GitHub 仓库中,否则此处的 `@latest` 仍然指向更新前的文件。此外 CDN 本身存在缓存,因此改动可能需要一定的时间生效。相关文档:

- [Git Basics - Tagging](https://git-scm.com/book/en/v2/Git-Basics-Tagging)
- [Managing releases in a repository](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository)

Expand All @@ -94,18 +102,24 @@ npm run build

这样,整个项目就可以通过你的域名访问了。不妨试试能否正常地通过浏览器打开 `autoload.js` 和 `live2d.min.js` 等文件,并确认这些文件的内容是完整和正确的。
一切正常的话,接下来修改 `autoload.js` 中的常量 `live2d_path` 为 `live2d-widget` 这一目录的 URL 即可。比如说,如果你能够通过

```
https://example.com/path/to/live2d-widget/live2d.min.js
```

访问到 `live2d.min.js`,那么就把 `live2d_path` 的值修改为

```
https://example.com/path/to/live2d-widget/
```

路径末尾的 `/` 一定要加上。
完成后,在你要添加看板娘的界面加入

```xml
<script src="https://example.com/path/to/live2d-widget/autoload.js"></script>
```

就可以加载了。

## 鸣谢
Expand Down Expand Up @@ -181,9 +195,9 @@ https://community.live2d.com/discussion/140/webgl-developer-licence-and-javascri

## 更新日志

2018年10月31日,由 fghrsh 提供的原 API 停用,请更新至新地址。参考文章:
2018 年 10 月 31 日,由 fghrsh 提供的原 API 停用,请更新至新地址。参考文章:
https://www.fghrsh.net/post/170.html

2020年1月1日起,本项目不再依赖于 jQuery。
2020 年 1 月 1 日起,本项目不再依赖于 jQuery。

2022年11月1日起,本项目不再需要用户单独加载 Font Awesome。
2022 年 11 月 1 日起,本项目不再需要用户单独加载 Font Awesome。
2 changes: 1 addition & 1 deletion demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
</a>
<script src="../autoload.js"></script>
<script src="./demo.js"></script>
</body>
</html>
62 changes: 62 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const live2d_path = "../";

// 封装异步加载资源的方法
function loadExternalResource(url, type) {
return new Promise((resolve, reject) => {
let tag;

if (type === "css") {
tag = document.createElement("link");
tag.rel = "stylesheet";
tag.href = url;
} else if (type === "js") {
tag = document.createElement("script");
tag.src = url;
}
if (tag) {
tag.onload = () => resolve(url);
tag.onerror = () => reject(url);
document.head.appendChild(tag);
}
});
}

// 加载 waifu.css live2d.min.js waifu-tips.js
if (screen.width >= 768) {
Promise.all([
loadExternalResource(live2d_path + "waifu.css", "css"),
loadExternalResource(live2d_path + "live2d.min.js", "js"),
loadExternalResource(live2d_path + "waifu-tips.js", "js"),
]).then(() => {
// 配置选项的具体用法见 README.md
initWidget({
waifuPath: live2d_path + "waifu-tips.json",
isLocalModel: true, // 使用本地模型
modelListPath: live2d_path + "demo/model/model_list.json",
modelsPath: live2d_path + "demo/model",
//apiPath: "https://live2d.fghrsh.net/api/",
// cdnPath: "https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/",
tools: ["hitokoto", "asteroids", "switch-model", "switch-texture", "photo", "info", "quit"],
});
});
}

console.log(`
く__,.ヘヽ. / ,ー、 〉
\ ', !-─‐-i / /´
/`ー' L//`ヽ、
/ /, /| , , ',
イ / /-‐/ i L_ ハ ヽ! i
レ ヘ 7イ`ト レ'ァ-ト、!ハ| |
!,/7 '0' ´0iソ| |
|.从" _ ,,,, / |./ |
レ'| i>.、,,__ _,.イ / .i |
レ'| | / k_7_/レ'ヽ, ハ. |
| |/i 〈|/ i ,.ヘ | i |
.|/ / i: ヘ! \ |
kヽ>、ハ _,.ヘ、 /、!
!'〈//`T´', \ `'7'ーr'
レ'ヽL__|___i,___,ンレ|ノ
ト-,/ |___./
'ー' !_,.:
`);
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import Model from "./model.js";
import showMessage from "./message.js";
import randomSelection from "./utils.js";
import tools from "./tools.js";
import LocalModel from "./localModel.js";

function loadWidget(config) {
const model = new Model(config);
const model = config.isLocalModel ? new LocalModel(config) : new Model(config);
localStorage.removeItem("waifu-display");
sessionStorage.removeItem("waifu-text");
document.body.insertAdjacentHTML("beforeend", `<div id="waifu">
Expand Down Expand Up @@ -131,7 +132,7 @@ function loadWidget(config) {
modelTexturesId = localStorage.getItem("modelTexturesId");
if (modelId === null) {
// 首次访问加载 指定模型 的 指定材质
modelId = 1; // 模型 ID
modelId = 0; // 模型 ID
modelTexturesId = 53; // 材质 ID
}
model.loadModel(modelId, modelTexturesId);
Expand Down
90 changes: 90 additions & 0 deletions src/localModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import showMessage from "./message.js";
import randomSelection, { loadJsonFile } from "./utils.js";

class LocalModel {
constructor(config) {
// modelListPath: 模型列表 json 文件路径(可不填,就是约定 modelsPath 下的 model_list.json);modelsPath:模型的跟路径
const { modelListPath, modelsPath } = config;

if (!modelsPath) {
throw "LocalModel requires modelListPath and modelsPath!";
}
const safeModelsPath = modelsPath.endsWith("/") ? modelsPath : modelsPath + "/";
// 确保路径以 / 结尾
this.modelsPath = safeModelsPath;
this.modelListPath = modelListPath ?? safeModelsPath + "model_list.json";
}

async loadModelList() {
try {
this.modelList = await loadJsonFile(this.modelListPath);
if (!this.modelList.models || !this.modelList.messages) {
throw "Invalid model list format!";
}
} catch (error) {
console.error("Failed to load model list:", error);
throw "Failed to load model list!";
}
}

async loadModel(modelId, modelTexturesId, message) {
// 保存当前模型状态
localStorage.setItem("modelId", modelId);
localStorage.setItem("modelTexturesId", modelTexturesId);

// 显示消息
showMessage(message, 4000, 10);

// 确保模型列表已加载
if (!this.modelList) {
await this.loadModelList();
}
// 获取目标模型,可能是值,也可能是数组
const target = randomSelection(this.modelList.models[modelId]);
// 加载模型
loadlive2d("live2d", `${this.modelsPath}${target}/index.json`);
console.log(`Live2D 模型 ${target} 加载完成`);
}

async loadRandModel() {
const modelId = localStorage.getItem("modelId");
let modelTexturesId = localStorage.getItem("modelTexturesId");

if (!this.modelList) {
await this.loadModelList();
}

const currentModel = this.modelList.models[modelId];

if (Array.isArray(currentModel)) {
// 对于数组类型的模型,随机选择一个不同的贴图
let newTextureId;
do {
newTextureId = Math.floor(Math.random() * currentModel.length);
} while (newTextureId === parseInt(modelTexturesId) && currentModel.length > 1);

if (newTextureId === parseInt(modelTexturesId)) {
showMessage("我还没有其他衣服呢!", 4000, 10);
return;
}

this.loadModel(modelId, newTextureId, "我的新衣服好看嘛?");
} else {
showMessage("我还没有其他衣服呢!", 4000, 10);
}
}

async loadOtherModel() {
let modelId = parseInt(localStorage.getItem("modelId"));

if (!this.modelList) {
await this.loadModelList();
}

// 切换到下一个模型,如果到达末尾则回到开始
const index = ++modelId >= this.modelList.models.length ? 0 : modelId;
this.loadModel(index, 0, this.modelList.messages[index]);
}
}

export default LocalModel;
12 changes: 12 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@ function randomSelection(obj) {
return Array.isArray(obj) ? obj[Math.floor(Math.random() * obj.length)] : obj;
}

/**
* 文件加载器
*/
export async function loadJsonFile(path) {
// 浏览器环境
const response = await fetch(path);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}

export default randomSelection;
Loading