Theseus 模组文档
API 参考 / 只读视图、场景、音频与文本

组合用法示例


下面的示例只使用前文出现过的真实类型与方法:从 GameplayContext 读谱面信息与准度、用 TextField 做输入、经 SceneHost 打开/关闭模组场景、在结算覆盖渲染参数中读成绩快照与布局原语。示例为纯只读用法,不修改任何游戏状态。

java
package mymod.example;

import com.seecmd.mod.api.ModContext;
import com.seecmd.mod.api.capability.SceneHost;
import com.seecmd.mod.api.context.ChartView;
import com.seecmd.mod.api.context.GameplayContext;
import com.seecmd.mod.api.context.LaneView;
import com.seecmd.mod.api.context.NoteView;
import com.seecmd.mod.api.context.ResultLayout;
import com.seecmd.mod.api.context.ResultSnapshot;
import com.seecmd.mod.api.event.gameplay.ChartLoadEvent;
import com.seecmd.mod.api.render.RenderCanvas;
import com.seecmd.mod.api.scene.InputEvent;
import com.seecmd.mod.api.scene.InputType;
import com.seecmd.mod.api.scene.Scene;
import com.seecmd.mod.api.text.TextField;

import java.nio.file.Path;
import java.util.List;

/** 只读用法示例:谱面读取 + 准度读取 + 模组场景 + 文本输入。 */
public final class ReadOnlyExampleScene implements Scene {

    private final TextField query = new TextField(32, false);
    private String title = "";
    private Path chartPath;

    @Override
    public String id() {
        return "mymod:readonly-example";
    }

    @Override
    public void onEnter() {
        query.reset("");
        query.reportImeAnchor(40, 652);   // 自绘场景可手动上报;用 render 时会自动上报
    }

    @Override
    public void update(double dt) {
        // 逻辑更新放在这里,不要依赖 render 的调用频率
    }

    @Override
    public void render(RenderCanvas canvas) {
        // 标准输入框外观;focused = true 时组件自动上报 IME 锚点
        query.render(canvas, 20, 320, 300, 32, true);
    }

    @Override
    public boolean handleInput(InputEvent event) {
        if (event.type() != InputType.KEY_PRESSED && event.type() != InputType.TEXT_INPUT) {
            return false;   // 其余事件类型组件一律不消费
        }
        return query.handleInput(event); // 仅把输入转发给聚焦组件
    }

    /** 谱面加载完成:读谱面元数据(不要缓存宿主返回的列表作为实时数据)。 */
    public void onChartLoad(ChartLoadEvent event) {
        GameplayContext ctx = event.context();
        ChartView chart = ctx.chartView();
        title = chart.title();
        chartPath = chart.chartPath();        // 可能为 null,需判空
        int bpm = chart.bpm();
        int laneCount = chart.laneCount();
        long durationMs = chart.durationMs();
        NoteView next = ctx.nextNote();       // 比 pendingNotes() 轻量
        List<LaneView> lanes = ctx.lanes();   // 快照列表,不要每帧取用
    }

    /** 判定后读取进度与准度(两种进度口径不可混用)。 */
    public void onJudged(GameplayContext ctx) {
        double byNotes = ctx.progress();            // 已判音符数 / 总音符数
        double byMusic = ctx.musicPositionRatio();  // 音乐时间线位置
        double acc = ctx.accuracy();                // 0-1,未判定不计入
        int combo = ctx.combo();                    // 当前连击
        int best = ctx.maxCombo();                  // 历史最大连击
        int remaining = ctx.remainingNoteCount();   // total - judged
        if (ctx.isRewinding()) {
            // 倒带重演窗口内 musicTimeMs() 非单调,累积型状态需按 τ 重建
        }
    }

    /** 结算界面覆盖渲染中可直接读取的快照与定位原语。 */
    public void onResultFrame(ResultSnapshot snapshot, ResultLayout layout) {
        double accPercent = snapshot.accuracy();     // 0-100,与结算界面口径一致
        List<Integer> builtin = snapshot.builtinCounts(); // PERFECT/GREAT/MISS
        int windowX = layout.windowX();
        int canvasWidth = layout.canvasWidth();
    }

    /** 打开本场景(压入场景栈,当前场景挂起)。 */
    public void open(ModContext mod) {
        SceneHost host = mod.capabilities().get(SceneHost.class);
        host.push(this);
        String currentSceneId = host.current().id();   // 模组场景返回声明 id
    }

    /** 关闭本场景(弹出当前场景,返回上一个场景)。 */
    public void close(ModContext mod) {
        mod.capabilities().get(SceneHost.class).pop();
    }
}