Live2D MC · 附属 Mod API 文档
适用:
com.chillet.live2dmcv1.0.0(Minecraft 1.20.1 / Forge 47.4.10+,仅客户端) 接口版本:Live2DMcApi.API_VERSION = "1"这份文档面向其他 mod 的作者:如何读取/控制 Live2D 模型、如何往 Live2D MC 的设置界面里加自己的页签、 如何监听状态变化。全部接口都在
com.chillet.live2dmc.api包里,不需要接触原生库(JNI)。
目录
- 快速开始
Live2DMcApi:模型接口Live2DMcUiApi:往设置界面加页签Live2DMcEvents:状态监听- 控件库速查(
com.chillet.live2dmc.ui) - 线程、时机与限制
- 完整示例:一个小附属 mod
- FAQ / 踩坑
- 版本与兼容承诺
1. 快速开始
1.1 依赖
把 Live2D MC 的构建产物放进你工程的 libs/,只在编译期依赖(运行时由玩家安装):
dependencies {
compileOnly files("libs/live2dmc-1.0.0.jar")
}
如果发布时想让安装器提示玩家先装 Live2D MC,在 mods.toml 里加一条软依赖:
[[dependencies.youraddon]]
modId = "live2dmc"
mandatory = false
versionRange = "[1.0.0,)"
ordering = "AFTER"
side = "CLIENT"
1.2 三条硬规则
- 只在客户端用。API 类都标了
@OnlyIn(Dist.CLIENT);Live2D MC 本身是clientSideOnly, 专用服务器上没有它。你的调用代码要么整个类只在客户端加载,要么用FMLEnvironment.dist == Dist.CLIENT包住(推荐:@Mod.EventBusSubscriber(value = Dist.CLIENT)的独立类)。 - 渲染方法必须发生在渲染线程(
Screen#render、RenderGuiEvent.Post等); 查询/设置配置类方法建议也在主线程调(内部会直接改模型参数)。 - 先判可用性再调:
Live2DMcApi.isAvailable()/hasModel()。 玩家没放模型、或原生库没加载时,render*返回false,其它方法退化成空操作/空表,不要当成异常。
1.3 最小骨架
package com.example.addon;
import com.chillet.live2dmc.api.Live2DMcApi;
import com.chillet.live2dmc.api.Live2DMcEvents;
import com.chillet.live2dmc.api.Live2DMcUiApi;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@Mod.EventBusSubscriber(modid = ExampleAddon.MODID, value = Dist.CLIENT,
bus = Mod.EventBusSubscriber.Bus.MOD)
public final class ExampleClient {
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
if (!Live2DMcApi.API_VERSION.equals("1")) {
return; // 接口大版本不匹配就别注册了
}
Live2DMcUiApi.registerTab("example", "示例", builder -> {
// 见第 3 节
});
Live2DMcEvents.register(new Live2DMcEvents.Listener() {
@Override
public void onModelChanged(String model) {
// 玩家换了模型
}
});
}
}
registerTab/register建议放在FMLClientSetupEvent(MOD 总线)里: 它们只是往内存列表里加条目,越早越好;页签构建回调在每次切到该页签时执行。
2. Live2DMcApi:模型接口
所有方法都是 public static。下表是完整清单。
2.1 状态
| 方法 | 返回 | 说明 |
|---|---|---|
modId() |
String |
本 mod 的 id:"live2dmc" |
isAvailable() |
boolean |
原生库 + GL 辅助上下文就绪。没就绪时所有渲染都返回 false |
hasModel() |
boolean |
当前有已加载的模型(= currentModel() 非空且可用) |
isEnabled() / setEnabled(boolean) |
boolean |
总开关(与设置界面里的「总开关」同一个值)。关掉后 render* 一律返回 false,HUD 也不画 |
statusLine() |
String |
一行状态文案,如 就绪(282x360) / 不可用:…,可直接显示 |
hintLine() |
String |
给玩家的首步提示(如"把模型放进 config/live2dmc/live2d") |
2.2 模型
| 方法 | 说明 |
|---|---|
models() |
已扫描到的模型名列表(模型目录名) |
currentModel() |
当前模型名(目录名);没有时返回 "" |
currentModelLabel() |
当前模型显示名 |
selectModel(String name) |
切换到 models() 里的某个模型;名字不存在返回 false。切换会清空当前表情 |
cycleModel() |
切到下一个模型(同样清空表情) |
rescanModels() |
重新扫描 config/live2dmc/live2d/(玩家刚放进新模型时用) |
openModelFolder() |
调系统文件管理器打开模型目录 |
2.3 表情
表情是叠加机制:可以同时勾选多个,参数逐个合并(同名参数后者覆盖)。取消勾选时会把该表情改过的参数 还原成应用前的值(不是粗暴清零),所以取消后立刻恢复原样,不需要重进游戏。
| 方法 | 说明 |
|---|---|
expressions() |
当前模型可用的表情名(来自 model3.json 的 Expressions;没有时自动扫目录里的 *.exp3.json) |
selectedExpressions() |
当前勾选的表情(顺序 = 叠加顺序) |
setExpressions(List<String>) |
直接设置组合;空表 = 无表情 |
toggleExpression(String name) |
勾选/取消一个 |
clearExpressions() |
清空(并还原参数) |
Live2DMcApi.setExpressions(List.of("Happy1", "Shy")); // 叠加
Live2DMcApi.clearExpressions(); // 全关
2.4 动作
| 方法 | 说明 |
|---|---|
cycleMotion() |
播放下一个动作组(模型没配动作时是空操作) |
currentMotion() |
当前动作组显示名(没有时返回 "—") |
2.5 参数
public record Param(String id, String name, String group, float min, float max) { }
数据来源是模型目录里的 *.cdi3.json;模型没有 cdi3 时退化成一份标准参数表(角度/眼球/呼吸/眨眼等)。
| 方法 | 说明 |
|---|---|
parameters() |
List<Param>:全部可调参数(含分组与范围) |
parameter(String id) |
读取覆盖值;没有覆盖时返回 0(不是模型的实时参数值) |
setParameter(String id, float value) |
写覆盖值:立即作用到模型,盖过跟随鼠标/眨眼/动作,并会持久化到 overrides.json |
hasParameterOverride(String id) |
是否被覆盖过 |
resetParameters() |
清空全部参数覆盖 |
⚠ 注意:
setParameter是持久化覆盖,不是"临时推一下"。如果你只是想每帧驱动参数, 又想尊重用户在参数页里手动改的值,可以:if (!Live2DMcApi.hasParameterOverride("ParamAngleZ")) { Live2DMcApi.setParameter("ParamAngleZ", driven); }或者用完调
resetParameters()(会一起清掉玩家自己的改动,慎用)。
2.6 部件
public record Part(String id, String name) { }
| 方法 | 说明 |
|---|---|
parts() |
部件列表(来自 cdi3) |
isPartVisible(String id) / setPartVisible(String id, boolean visible) |
查询 / 显示隐藏(持久化) |
showAllParts() |
全部显示 |
⚠ 实现方式是"合成表情的
Parts字段"。只有当模型的 drawable 确实挂在部件下时才看得出效果; 部分导出工具不按部件组织 drawable,此时调用不会有可见变化(不算 bug)。
2.7 位置 / 缩放 / 不透明度
模型位置和 HUD 共用同一份配置,改完会持久化。
| 方法 | 说明 |
|---|---|
offsetX() / offsetY() / setOffset(double x, double y) |
模型位置(预览和 HUD 都会偏移) |
scale() / setScale(double) |
缩放(1.0 = 原始大小) |
opacity() / setOpacity(double) |
不透明度 0..1 |
这三个只影响
renderFollowMouse(...)与 HUD;render(...)是固定比例的头像模式,不套用它们。
2.8 HUD(桌面挂件)
| 方法 | 说明 |
|---|---|
isHudEnabled() / setHudEnabled(boolean) |
开关 |
hudAnchorX() / hudAnchorY() / setHudAnchor(double x, double y) |
锚点,0..1 的整屏比例(默认 0.84 / 0.98) |
hudScale() / setHudScale(double) |
HUD 独立缩放 |
| HUD 会画在所有界面之上(物品栏、聊天、设置界面都能看到),打开 Live2D MC 自己的设置界面时自动隐藏。 |
2.9 渲染
// 居中、不看鼠标:画进任意 GUI 矩形
boolean render(GuiGraphics graphics, int x, int y, int w, int h);
// 同上,并按鼠标位置转头
boolean renderFollowMouse(GuiGraphics graphics, int x, int y, int w, int h,
float mouseX, float mouseY);
- 坐标是 GUI 逻辑像素,
(x, y)是矩形左上角,w/h是可用区域;模型按自身比例居中放进去。 render(...)用固定的"装进盒子"缩放(0.92,忽略玩家设置的缩放/偏移),适合当头像/预览;renderFollowMouse(...)会套用玩家在模型页里设置的缩放与偏移,并按鼠标转头,适合当桌面宠物。- 直接画进当前
GuiGraphics(内部会立刻 flush 自己那批几何),所以在 GUI 里任何位置都能用: 自己 Screen 的render、HUD 叠加、物品栏上方等。 - 返回
true表示真的画出来了;false表示没模型 / 原生库不可用 / 总开关关闭,请画自己的占位内容。
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(graphics);
boolean drawn = Live2DMcApi.renderFollowMouse(graphics, 20, 30, 180, 240, mouseX, mouseY);
if (!drawn) {
graphics.drawString(this.font, "(没有模型)", 20, 30, 0xFF888888);
}
super.render(graphics, mouseX, mouseY, partialTick);
}
2.10 主题 / 界面 / 保存
| 方法 | 说明 |
|---|---|
isLightTheme() / setLightTheme(boolean) |
读 / 切深色浅色主题(会写配置;同时影响控件库配色) |
openConsole() |
打开 Live2D MC 的设置界面(当前没有别的界面时才会打开) |
save() |
立即落盘配置与参数/部件覆盖(正常关界面时会自动保存) |
resetDefaults() |
恢复全部出厂设置(缩放/HUD/表情/参数/部件等) |
3. Live2DMcUiApi:往设置界面加页签
3.1 注册
Live2DMcUiApi.registerTab(
"youraddon", // 唯一 id(页脚会显示,方便排查)
"拓展", // 页签标题(自己本地化;建议 ≤4 个汉字)
builder -> { /* 往页面里加控件 */ }
);
| 方法 | 说明 |
|---|---|
registerTab(String id, String title, Consumer<TabBuilder> build) |
注册页签。id 为空或 build 为 null 会被忽略;同 id 只保留第一个(重复注册会记一条 warn) |
tabs() |
已注册页签列表(TabEntry{id, title, build()}) |
openConsole() |
打开设置界面 |
notifyConsoleOpened() / notifyConsoleClosed() |
由设置界面自动调用,一般不要自己调 |
| 页签标题会显示在内置 5 个页签(模型 / HUD / 参数 / 部件 / 表情)右边:太长会被按段宽省略, | |
| 所以标题尽量短;页面内容区仍然是完整宽度。 |
3.2 坐标与构建时机
TabBuilder 给出的坐标是绝对 GUI 坐标(不是相对坐标):
| 方法 | 说明 |
|---|---|
bodyX() / bodyY() |
内容区左上角 |
bodyW() / bodyH() |
内容区宽 / 高 |
add(UiWidget) |
往屏幕加一个控件(按调用顺序绘制,后加的在上面);null 会被忽略 |
Live2DMcUiApi.registerTab("youraddon", "拓展", tab -> {
int x = tab.bodyX(), y = tab.bodyY(), w = tab.bodyW();
tab.add(new UiToggle(() -> "启用功能", MyConfig::enabled, MyConfig::setEnabled)
.bounds(x, y, w / 2 - 2, UiTheme.CONTROL_HEIGHT));
tab.add(new UiSlider(() -> "强度",
MyConfig::strength, MyConfig::setStrength, 0.0D, 1.0D, 0.01D)
.bounds(x, y + 24, w - 12, 18));
});
页面生命周期
- 构建回调在每次切到该页签(以及重新打开设置界面)时执行,用来读取最新状态。
- 控件不跨次保留:状态请存在你自己的字段/配置里,不要在控件对象上存;每次构建重新
new即可。 - 构建抛异常会被 catch 并记一条 warn,你的页签会变成空白页,不会崩游戏。
- 不要自己往屏幕上重复画同一批控件;控件由界面统一渲染、派发鼠标和键盘事件。
3.3 可用控件
全部在 com.chillet.live2dmc.ui,都是 public(详见第 5 节):
UiButton、UiToggle、UiSlider、UiCheckRow、UiTabs、UiTextField、UiScrollArea、
UiModelPreview、UiHudMap、UiDraw、UiTheme、UiText。
所有控件都用 .bounds(x, y, w, h) 定位,返回自身,方便链式。
3.4 滚动列表
UiScrollArea 里的子控件用内容坐标((0, 0) = 滚动区域左上角),滚动/裁剪由它负责:
UiScrollArea area = new UiScrollArea().tag("youraddon").bounds(x, y, w, h);
int rowY = 2;
for (String entry : myEntries) {
area.add(new UiButton(() -> entry, () -> pick(entry))
.bounds(2, rowY, w - 12, 18));
rowY += 20;
}
tab.add(area);
滚轮只在列表区域响应;area.add 可以放任意控件(一排开关、一行行按钮都行),坐标从内容原点算起。
4. Live2DMcEvents:状态监听
public interface Listener {
default void onModelChanged(String model) { }
default void onExpressionsChanged(List<String> selected) { }
default void onConfigSaved() { }
default void onConsoleOpened() { }
default void onConsoleClosed() { }
}
| 方法 | 触发时机 |
|---|---|
register(Listener) / unregister(Listener) |
监听器列表(重复注册会重复回调,请自己保证只注册一次) |
onModelChanged(String model) |
换模型成功(selectModel / cycleModel / 自动切模型) |
onExpressionsChanged(List<String>) |
表情组合变化(setExpressions / toggle / clear) |
onConfigSaved() |
配置写盘(关设置界面、手动 save()) |
onConsoleOpened() / onConsoleClosed() |
设置界面打开 / 关闭 |
Live2DMcEvents.register(new Live2DMcEvents.Listener() {
@Override
public void onExpressionsChanged(List<String> selected) {
LOGGER.info("当前表情:{}", selected);
}
@Override
public void onConsoleClosed() {
MyConfig.save(); // 玩家调完参数 → 保存自己的配置
}
});
- 监听器抛出的异常会被吞掉并记 warn,不会连累 Live2D MC。
- 事件都在客户端主线程触发(
register本身可以在初始化线程调用)。 - 不想再收事件时
unregister(那个实例)。
5. 控件库速查(com.chillet.live2dmc.ui)
所有控件都继承 UiWidget,公共字段/方法:
public int x, y, w, h;
public boolean visible = true; // false 时不渲染、不响应
public boolean enabled = true; // false 时点击无效
<T extends UiWidget> T bounds(int x, int y, int w, int h);
boolean contains(double mx, double my);
String tooltip(); // 悬停提示(默认 null)
UiButton
new UiButton(Supplier<String> label, Runnable action);
new UiButton(Supplier<String> label, Runnable action, Style style); // PRIMARY / WARN / GHOST
UiToggle
new UiToggle(Supplier<String> label,
Supplier<Boolean> getter,
Consumer<Boolean> setter);
UiSlider
new UiSlider(Supplier<String> label, DoubleSupplier getter, DoubleConsumer setter,
double min, double max, double step); // 数值格式默认 "%.2f"
new UiSlider(..., double min, double max, double step, String format);
拖动时按 step 量化;标签右边实时显示数值。
UiCheckRow
new UiCheckRow(Supplier<String> label, BooleanSupplier selected, Runnable onSelect);
UiTabs
new UiTabs(String[] labels, IntSupplier index, IntConsumer onChange);
UiTextField
new UiTextField(String value, int maxLength, String label, String placeholder,
boolean masked, Consumer<String> onChange);
- 点一下聚焦,键盘输入(含中文输入法上屏)由界面转发;
ESC第一次只取消聚焦。 onChange每次改动都会回调。- ⚠ 聚焦是由界面按
widget instanceof UiTextField判定的:输入框必须直接add到页面上, 放进UiScrollArea里会收不到键盘。
UiScrollArea
UiScrollArea area = new UiScrollArea().tag("youraddon");
area.add(child); // 子控件坐标 = 内容坐标
area.clear();
area.contentHeight();
area.scrollBy(int dy);
area.canScroll();
UiDraw(静态绘制工具)
UiDraw.rect(g, x, y, w, h, argb);
UiDraw.rounded(g, x, y, w, h, radius, argb);
UiDraw.roundedGradient(g, x, y, w, h, radius, topArgb, bottomArgb);
UiDraw.card(g, x, y, w, h, radius, borderArgb, topArgb, bottomArgb);
UiDraw.shadow(g, x, y, w, h, radius);
UiDraw.inset(g, x, y, w, h, radius);
UiDraw.circle(g, cx, cy, radius, argb);
UiDraw.line(g, x0, y0, x1, y1, thickness, argb);
UiDraw.hairline(g, x, y, w, h, argb);
UiDraw.text(g, font, s, x, y, argb);
UiDraw.textCentered(g, font, s, centerX, y, argb);
UiDraw.textRight(g, font, s, rightX, y, argb);
UiDraw.ellipsize(font, s, maxWidth);
UiDraw.scissorOn(g, x, y, w, h); / UiDraw.scissorOff(g);
UiDraw.lerpColor(a, b, t); / UiDraw.withAlpha(argb, alpha);
UiTheme
主题色和尺寸都是 public static(UiTheme.setLight(true/false) 切换深/浅色):
UiTheme.ACCENT, UiTheme.WARN, UiTheme.TEXT, UiTheme.TEXT_DIM, UiTheme.TEXT_FAINT,
UiTheme.CONTROL, UiTheme.CONTROL_BORDER, UiTheme.CARD_TOP, UiTheme.INSET,
UiTheme.TAB_TRACK, UiTheme.SLIDER_FILL, ...
UiTheme.PAD // 10
UiTheme.ROW_GAP // 4
UiTheme.CONTROL_HEIGHT // 18
UiTheme.RADIUS_CARD // 10
UiTheme.RADIUS_CONTROL // 6
UiText
UiText.tr("youraddon.key", args); // 取本地化字符串(I18n)
UiText.ct("youraddon.key", args); // 取 Component
6. 线程、时机与限制
| 事项 | 约定 |
|---|---|
| 渲染 | 只能在渲染线程(Screen#render / GUI 叠加事件里)调用 render* |
| 查询/设置 | 客户端主线程调用;不要在网络线程/异步线程里调 |
| 无模型时 | render* 返回 false,models()/expressions()/parameters() 为空表,设置类是空操作 |
| 平台 | 原生库只带 Windows x64;其他平台 isAvailable() == false |
| 主 mod 关闭 | Live2D MC 是 clientSideOnly mod:玩家没装时你的 mod 不应报错(软依赖 + isAvailable() 判断) |
| 不要反射/直连内部类 | 只依赖 com.chillet.live2dmc.api 三个类与 ui 控件库;其余包不保证兼容 |
| 模型本身 | 不随 mod 分发;玩家自备,版权自负 |
7. 完整示例:一个小附属 mod
需求:给 Live2D MC 加一个「跟随血量」页签;玩家换模型时把状态同步到自己的配置;收到含"开心"的聊天就笑。
package com.example.addon;
import com.chillet.live2dmc.api.*;
import com.chillet.live2dmc.ui.*;
import net.minecraft.client.Minecraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import java.util.List;
@Mod.EventBusSubscriber(modid = "youraddon", value = Dist.CLIENT)
public final class ExampleAddon {
private static boolean moodEnabled = true;
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
// 只注册一次:MOD 总线里的 FMLClientSetupEvent
Live2DMcUiApi.registerTab("youraddon_mood", "心情", ExampleAddon::buildMoodTab);
Live2DMcEvents.register(new Live2DMcEvents.Listener() {
@Override
public void onModelChanged(String model) {
moodEnabled = true; // 换模型时重置自己状态
}
});
}
private static void buildMoodTab(Live2DMcUiApi.TabBuilder tab) {
int x = tab.bodyX(), y = tab.bodyY(), w = tab.bodyW(), h = tab.bodyH();
tab.add(new UiToggle(() -> "跟随血量",
() -> moodEnabled,
v -> moodEnabled = v)
.bounds(x, y, w / 2 - 2, UiTheme.CONTROL_HEIGHT));
// 表情快捷按钮:直接调主 mod 的 API
tab.add(new UiButton(() -> "笑一个", () -> {
List<String> names = Live2DMcApi.expressions();
if (names.contains("Happy1")) {
Live2DMcApi.toggleExpression("Happy1");
}
}, UiButton.Style.PRIMARY)
.bounds(x + w / 2 + 2, y, w / 2 - 2, UiTheme.CONTROL_HEIGHT));
// 参数滑条:读写主 mod 的覆盖值
tab.add(new UiSlider(() -> "被驱动的参数(ParamAngleZ)",
() -> Live2DMcApi.parameter("ParamAngleZ"),
v -> Live2DMcApi.setParameter("ParamAngleZ", (float) v),
-30.0D, 30.0D, 0.5D)
.bounds(x, y + 26, w, 18));
// 底部说明
tab.add(new UiButton(() -> "重置全部参数", Live2DMcApi::resetParameters,
UiButton.Style.WARN)
.bounds(x, y + h - 22, w / 3, UiTheme.CONTROL_HEIGHT));
}
@SubscribeEvent
public static void onClientTick(TickEvent.ClientTickEvent event) {
if (event.phase != TickEvent.Phase.END) {
return;
}
if (!moodEnabled || !Live2DMcApi.hasModel()) {
return;
}
var player = Minecraft.getInstance().player;
if (player == null) {
return;
}
// 玩家自己改过就不抢
if (!Live2DMcApi.hasParameterOverride("ParamAngleZ")) {
Live2DMcApi.setParameter("ParamAngleZ", (1.0F - player.getHealth() / 20.0F) * 25.0F);
}
}
}
8. FAQ / 踩坑
Q:我在自己界面里画模型,结果是一片黑/花了?
render* 只能在渲染线程的 GUI 绘制阶段调用(Screen#render),并且要传当前的 GuiGraphics。
不要放到 ClientTickEvent 或网络回调里。
Q:页签没出现在设置界面里?
- 注册时机太晚 —— 必须在界面打开前注册(推荐
FMLClientSetupEvent); id和别的附属 mod 撞了(日志有页签 id 已存在的 warn);- 你的 mod 没加载成功/版本低于 1.0.0。
Q:页签里的控件点不动?
坐标要用绝对屏幕坐标(
bodyX()/bodyY()起算),控件不能超出内容区,也不要被后加的控件完全盖住;visible=false或enabled=false的控件不响应。 Q:UiTextField打不了字? 它必须直接tab.add(...)到页面上(不能塞进UiScrollArea),且要先点一下让它聚焦。 Q:Live2DMcApi.parameter("ParamAngleX")返回 0,但模型明明在动? 它读的是覆盖值,不是模型的实时值;没有覆盖就返回 0。想知道模型有哪些参数、范围多少,用parameters()。 Q:setExpressions没反应? 先确认模型名:expressions()里有没有你传的名字(大小写、空格要一致)。模型没有表情时列表为空。 Q:setPartVisible没反应? 部件开关依赖模型把 drawable 挂在部件下;不少导出工具不这样做,此时没有可见变化。 Q:openConsole()没打开界面? 当前已经有别的界面(背包、聊天等)时它不会强制切换;让玩家先关掉当前界面,或用按键绑定。 Q:我的 mod 在专用服务器上崩了(NoClassDefFoundError)? Live2D MC 是纯客户端 mod。请把调用 API 的类放到@Mod.EventBusSubscriber(value = Dist.CLIENT)的 独立类里,或先用FMLEnvironment.dist == Dist.CLIENT判断。
9. 版本与兼容承诺
- 当前
API_VERSION = "1"。v1 内只增不改:新增方法/控件不会破坏已有调用。 - 发生不兼容变更(删方法、改语义)时会提升
API_VERSION并在这里注明迁移方式。 - 附属 mod 侧建议:
if (!"1".equals(Live2DMcApi.API_VERSION)) {
LOGGER.warn("Live2D MC API 版本不匹配:{}", Live2DMcApi.API_VERSION);
return;
}
- 只有
com.chillet.live2dmc.api.*与com.chillet.live2dmc.ui.*属于公开接口; 其他包(live2d、render、client)是内部实现,随时可能调整。

