Android 接入指南
本文档面向需要将 NovaMark 运行时集成到 Android 应用的开发者。
前置理解
在开始之前,请确保已阅读 运行时、宿主与平台接入,理解以下核心概念:
- 离散状态机:NovaMark VM 不自行推进时间,而是等待宿主调用
- 宿主职责:Android 端负责 UI 渲染、动画、存档管理、平台适配
- 状态驱动:渲染器只消费运行时状态,不参与剧情逻辑
架构选型
NovaMark 在 Android 平台支持两种主要接入方式:
方案一:WebView + WASM
将 NovaMark 编译为 WebAssembly 模块,在 Android WebView 中运行。
适用场景:
- 已有 Web 渲染器,希望快速复用
- 团队熟悉 Web 前端技术栈
- 需要热更新能力
接入要点:
- 使用 Emscripten 将 NovaMark 编译为
.wasm文件 - 将 Web 渲染器模板(HTML/CSS/JS)打包进 Android 资源
- WebView 通过 JavaScript 桥接访问原生能力(存档、文件访问等)
注意事项:
- 游戏包(
.nvmp)需要通过桥接从原生层传入 WASM - 存档操作需要桥接到 Android 原生文件系统
- 音频播放建议使用原生 MediaPlayer 而非 WebAudio
方案二:原生渲染
通过 JNI 调用 NovaMark C API,自行实现 Android 原生 UI。
适用场景:
- 追求最优性能
- 需要深度集成 Android 系统能力
- 希望渲染完全可控
接入要点:
- 使用 NDK 编译 NovaMark 为共享库(
.so) - 通过 JNI 封装 C API 供 Kotlin/Java 调用
- 自行实现 UI 渲染层(可使用 Jetpack Compose 或传统 View 系统)
注意事项:
- 需要理解
NovaState结构并映射到 UI 组件 - 需要自行处理动画、过渡效果
- 需要管理线程模型(VM 在后台线程,UI 在主线程)
选型建议
| 维度 | WebView + WASM | 原生渲染 |
|---|---|---|
| 开发成本 | 低(复用 Web 模板) | 高(需自建渲染层) |
| 运行性能 | 中等 | 最优 |
| 热更新 | 支持 | 不支持 |
| 原生能力集成 | 需桥接 | 直接访问 |
| UI 一致性 | 与 Web 端一致 | 可完全定制 |
接入步骤
以下以原生渲染方案为例,说明基本接入流程。
第一步:编译 NovaMark 共享库
- 配置 NDK 环境
- 使用 CMake 交叉编译 NovaMark 为
libnova.so - 在
build.gradle中配置 NDK 构建参数
第二步:封装 JNI 接口
将 C API 封装为 Kotlin/Java 可调用的接口。核心操作包括:
生命周期管理:
- 创建 VM 实例
- 加载游戏包(
.nvmp文件) - 销毁 VM 实例
剧情推进:
advance()- 推进到下一个状态点choose(choiceId)- 选择选项
状态读取:
- 获取
NovaState结构 - 解析对话、选项、背景、立绘等字段
第三步:实现 UI 渲染层
根据 NovaState 结构设计 Android UI:
对话显示:
- 读取
dialogue.speaker显示角色名 - 读取
dialogue.text显示对话内容 - 实现打字机效果(由宿主控制,非引擎行为)
选项渲染:
- 读取
choices数组 - 动态生成选项按钮
- 点击后调用
choose()
背景与立绘:
- 读取
bg字段获取背景图路径 - 读取
sprites数组获取立绘信息 - 自行决定过渡动画的实现方式
第四步:实现事件循环
建立 VM 执行与 UI 更新的交互模式:
用户操作 → 调用 advance()/choose() → VM 更新状态 → 读取 NovaState → 刷新 UI → 等待下一次用户操作状态驱动渲染
NovaMark 采用状态驱动模型,Android 宿主需要理解如何消费运行时状态。
NovaState 结构概览
运行时状态包含以下主要字段:
| 字段 | 说明 | 用途 |
|---|---|---|
dialogue | 当前对话 | 显示角色名和文本 |
choices | 可选选项列表 | 渲染选择按钮 |
bg | 背景图路径 | 加载并显示背景 |
sprites | 立绘列表 | 显示角色立绘 |
bgm | 背景音乐路径 | 播放 BGM |
sfx | 音效路径 | 播放音效 |
variables | 变量表 | 显示状态、解锁条件等 |
inventory | 物品背包 | 显示背包界面 |
textConfig | 文本配置 | 对话框样式、字体等 |
渲染提示字段
以下字段由脚本提供,但由宿主解释和执行:
transition- 过渡效果提示(淡入淡出、滑动等)position- 位置提示(左、中、右等)opacity- 透明度提示loop- 循环提示(用于 BGM 等)volume- 音量提示
这些字段表达的是创作者意图,Android 宿主可以:
- 完全遵循提示实现
- 部分采用,部分使用平台默认值
- 忽略某些提示(如低端设备降级渲染)
状态更新时机
VM 状态仅在以下时机发生变化:
- 调用
advance()后 - 调用
choose()后 - 加载存档后
宿主无需轮询状态,只需在调用 VM 方法后读取新状态即可。
输入处理
NovaMark 的核心输入只有两种:advance() 和 choose()。
advance 触发时机
用户执行以下操作时,宿主应调用 advance():
- 点击对话框空白区域
- 点击"继续"按钮
- 按下确认键
choose 触发时机
当 NovaState.choices 不为空时:
- 显示选项列表
- 用户点击某个选项后,调用
choose(choiceId) choiceId从choices数组中获取
线程模型
建议的线程处理方式:
主线程(UI 线程):
- 渲染界面
- 接收用户输入
- 更新 UI 组件
后台线程:
- 调用 VM 方法(advance/choose)
- 读取 NovaState
- 解析资源路径使用 LiveData、Flow 或 Handler 在线程间传递状态更新。
存档管理
NovaMark 将存档分为两层,Android 宿主需要分别处理。
二进制存档(正式环境)
使用 C API 的快照功能:
nova_save_snapshot_file()- 保存二进制快照nova_load_snapshot_file()- 加载二进制快照
存档文件位置建议:
- 内部存储:
getFilesDir()下的saves/目录 - 支持多存档槽位
存档时机:
- 用户手动保存
- 自动存档(场景切换时)
- 关键选择前
JSON 快照(调试与测试)
使用 nova_export_runtime_state_json() 获取 JSON 格式状态:
用途:
- 开发阶段查看完整状态
- 调试工具展示
- 自动化测试验证
不建议用于正式存档,因为:
- 文件较大
- 解析速度较慢
存档界面职责
存档界面的实现完全由宿主负责:
- 存档槽位布局
- 存档缩略图(宿主自行截图保存)
- 存档时间戳
- 存档元数据(如当前场景名)
NovaMark 引擎仅提供状态快照的保存与恢复功能。
调试建议
日志与错误处理
- 在 JNI 层捕获 C API 错误,通过
Log.e()输出 - 使用
nova_get_error()获取详细错误信息 - 区分 VM 错误(如脚本逻辑)和宿主错误(如资源加载失败)
资源调试
如果资源加载失败,检查:
.nvmp包是否正确打包资源- 资源路径是否正确传递给 Android 资源系统
- 文件权限是否正确
状态验证
在开发阶段,可以:
- 使用 JSON 快照导出完整状态
- 对比期望状态与实际状态
- 验证变量、背包、场景是否正确
性能调优
- 图片资源使用合适的采样率
- BGM 使用流式播放而非一次性加载
- 立绘使用图集减少绘制调用
- 避免每帧重复读取
NovaState,仅在状态变化后读取
接入检查清单
完成接入后,确认以下功能正常:
- 加载
.nvmp游戏包 - 显示对话内容(角色名 + 文本)
- 显示选项并响应选择
- 显示背景图
- 显示立绘
- 播放 BGM 和音效
- 保存与加载存档
- 变量读取(用于 UI 显示)
- 背包物品显示