ワールドのアプリ(U#)

U# で書くアプリ(native)の作り方です。見本は時計アプリ(Packages/vrc.ulexite.app.clock)。データで書くアプリは4章を見てください。

Chapter 3

時計アプリで詰まりやすいところ(実行中に変えるラベルの扱い、アプリ内のダイアログ、feed の読み方、K.Set のフィールド名)を先に説明します。

パッケージの形

Packages/vrc.ulexite.app.<id>/
  package.json                  VPM の情報(vpmDependencies に vrc.ulexite.tv)
  ulexite-app.json              マニフェスト(kind: "native"、profiles: ["tv"])
  Runtime/
    Ulexite.App.<Id>.asmdef     参照:Ulexite(本体)
    UlexiteApp<Id>.cs           UlexiteApp を継承(アプリの本体)
    Ulexite.App.<Id>.asset      U# のプログラムアセット(エディタが作ります)
  Editor/
    Ulexite.App.<Id>.Editor.asmdef
    Ulexite<Id>Builder.cs       UlexiteAppBuilder を継承(画面のビルダー)
  Localization/<アプリ>-lang.json  アプリの翻訳表(任意)

画面は Builder で作る

文字と翻訳(実行中に変えるラベルは空文字で作る)

// ビルダー:空文字で作る(あとでコードが入れる)
K.Set(app, "toggleLabel", K.Tmp(K.Rect(rt, "Label", 20, 0, 200, 40), "", 18, K.Text1, false, TextAlignmentOptions.MidlineLeft));

// アプリ:状態に合わせて入れ直す(言語が変わったら _OnLanguageChanged でもう一度)
toggleLabel.text = running ? L("Ulexite.Clock.On") : L("Ulexite.Clock.Off");

フォーカスと操作

返す・受ける何を
GetRowCount()行数(行 0 はナビゲーションバー。アプリの行は 1 から)
GetColCount(row)その行の列数
GetFocusTarget(row, col)フォーカス枠を重ねる相手(ないときは null)
_OnSubmit(row, col)決定
_OnDirection(row, col, dir)方向。自分で処理したら true。false ならランチャーが行・列を動かす
_OnBack()戻る。アプリ内で戻れたら true(ダイアログを閉じるなど)、false ならホームへ

アプリ内のダイアログ(時計の例)

骨組みの例:

const int RowMain = 1, RowMenu = 10, RowList = 11, RowListRows = 8, RowPage = 19;

public override int GetRowCount() => RowPage;          // 閉じていても固定
public override int GetColCount(int row) => row == RowMain ? cards.Length : 1;

public override RectTransform GetFocusTarget(int row, int col)
{
    if (row == RowMain) return Pick(cards, col);
    if (row == RowMenu) return Pick(menuButtons, col);
    if (row >= RowList && row < RowList + RowListRows) return Pick(listButtons, row - RowList);
    if (row == RowPage) return Pick(pageButtons, col);
    return null;
}

public override bool _OnDirection(int row, int col, int dir)
{
    if (row == RowMain && dir == UlexiteLauncher.Down) { OpenMenu(col); return true; }       // 下で開く
    if (row == RowMenu && dir == UlexiteLauncher.Up)
    {
        launcher._SetFocus(RowMain, Mathf.Clamp(edit, 0, cards.Length - 1)); return true;   // 上で戻る
    }
    if (row == RowMenu && dir == UlexiteLauncher.Down)
    {
        if (listOpen) launcher._SetFocus(RowList, 0); return true;                          // 開いていれば一覧へ
    }
    if (row == RowList && dir == UlexiteLauncher.Up)
    {
        CloseList(); launcher._SetFocus(RowMenu, 0); return true;                           // 先頭で上は閉じる
    }
    return false;
}

public override bool _OnBack()
{
    if (launcher.Row >= RowMenu) { CloseAll(); return true; }                               // ダイアログを閉じる
    return false;
}

App Services

アプリは services(UlexiteApp.services)だけを使います。Ulexite TV の内部は直接触りません。

データ(feed)

// ulexite-app.json(時計の例)
"data": [
  { "id": "timezones", "url": "https://thesilver0913.github.io/ulexite-catalog/v1/timezones.json", "every": "24h" }
]

保存・設定・音・通知

同期

はまりどころ