コンテンツにスキップ

よくある落とし穴 (TJS2 / エンジン API)

コアデモ (src/core/data/*) を書きながら実際に踏んだものを集めた実践メモです。 どれも「エラーにならず、黙って違う結果になる」たぐいのものなので、 症状から逆引きできるようにしています。


TJS2 言語

文字の取り出しは str[i]str[i, 1] は別物

str[i, 1] はカンマ式 (i, 1) が評価されて 常に str[1] になります。 エラーにならないため、文字列処理の結果が「全部同じ文字」になって初めて気づきます。

var s = "abcdef";
var c1 = s[2];        // "c"      … 正しい
var c2 = s.charAt(2); // "c"      … 範囲外でも例外にならない版
var c3 = s[2, 1];     // "b" (!)  … カンマ式なので常に s[1]

文字列に対する操作

Math.floor() などは実数を返す

整数に丸めたつもりでも型は実数のままなので、文字列連結すると "1.000000000000000" のように出ます。表示や添字に使うなら (int) を付けます。

var n = Math.floor(payload + 0.5);
dm("index = " + n);        // "index = 1.000000000000000"
dm("index = " + (int)n);   // "index = 1"

匿名関数は外側のローカル変数を捕捉しない

TJS2 の関数式はクロージャではありません。 コールバック内から使いたい値は メンバ変数か global に置きます。

function start() {
    var scene = this;                       // これはコールバックから見えない
    var t = new Timer(function() {
        scene.close();                      // → 例外: メンバ "scene" が見つかりません
    }, "");
}
// メンバ変数にするか、global に置く
global.__scene = this;
var t = new Timer(function() { global.__scene.close(); }, "");

非数値文字列の if は常に偽

if("abc") は真になりません ( 数値化されて 0 になります )。 存在判定は typeof!== void で行います。

if (typeof System.appDataPath != "undefined") { ... }   // 正しい
if (obj.name !== void) { ... }                          // 正しい

レイヤ / 描画

Layer.visible の既定は false

生成しただけでは表示されません。 デモで「何も出ない」原因のほとんどがこれです。

hitTypehtMaskhtProvince の 2 値

「矩形全面で当たる」設定は htRect ではなく hitThreshold = 0 です。

drawText の色は 24bit RGB

0xRRGGBB の 6 桁で指定します。 8 桁 ( 0xAARRGGBB ) を渡すとシステムカラー 指定とみなされ、意図せず黒くなります。

Layer.invalidate は子レイヤを無効化しない

子は「親から切り離されるだけ」で生き残り、描画も黙って成功します。 サブツリーごと捨てるなら再帰的に invalidate してください ( demolib の invalidateLayerTree が実装例 )。

operateRect の乗算系は holdAlpha

omMultiplicative 等は転送先のアルファを 0 にしてしまうため、 ltAlpha レイヤに対して使うと真っ黒になります。 転送先を保つなら holdAlpha = true を指定します。

更新矩形は必要最小限に

update() を引数なしで呼ぶ ( = 全面 ) 癖があると、静止画面でも毎フレーム 画面全体を GPU へ転送し続けます。 「消す矩形」「描く矩形」だけを update すれば 転送量は桁で変わります ( 実測はコアデモ perf_stats / 解説は src/core/doc/ScreenTransfer.md )。


ビルドによって有無が変わる API

同じスクリプトを Windows ネイティブ ( WINVER ) ビルドと SDL3 ビルドの双方で 動かすなら、次のものは typeof で存在を確認してから使います。

API 状況
System.appDataPath / personalPath 2026-08-25 に SDL3 / 汎用ビルドへも追加済 ( Windows では WINVER と同じ解決、他 OS では exePath と同じ値 )。それ以前のエンジンの汎用ビルドには無いので typeof 確認。保存先は System.dataPath を使う
System.desktopLeft/Top/Width/Height WINVER 限定。解像度だけなら System.screenWidth/Height
System.urldecode / readRegValue Windows 拡張。SDL3 ビルドには無い
System.setMemoryOverlay / setPadOverlay フラグは全ビルド共通だが、描画するのは OGL 系 / SDL の DrawDevice ( WINVER 既定の D3D11 では出ない )
Layer.imeMode / setAttentionPoint 値は全ビルドで保持されるが、実際に効くのは WINVER
Window.setZoom 2026-08-17 に両ビルドで挙動を統一済 ( 「レイヤサイズ × 倍率」をウィンドウの内側サイズにする )。それ以前の WINVER は倍率を覚えるだけだったので、古いエンジンも対象にするなら Window.setInnerSize を併用する
System.padButtonMapping SDL3 / 汎用ビルド限定。WINVER は XInput ベースで刻印が Xbox 系固定のため設定自体が不要
Dialog 系 / WebServer ビルドオプション ( KRKRZ_USE_ELEMENTS / KRKRZ_REPL_WEB ) 次第

入力

文字入力は onKeyPress、キーは onKeyDown

onKeyDown は仮想キーコード、onKeyPress は文字そのもの ( IME 確定文字を含む ) を扱います。 文字を集めたい処理を onKeyDown で書くと IME 入力が取れません。

Elements パネル表示中はパッド入力がパネルに吸われる

VK_PAD* はパネルのウィジェット操作 ( 十字 = フォーカス移動 / A = 決定 ) に 消費され、ゲーム側の onKeyDown には届きません。 必ずゲームで受けたいボタンは ElementsDialog.registerHotKey で確保すると パネルをバイパスします ( コアデモ pad_advanced に ON/OFF の比較あり )。

アナログスティックは無操作でも 0 にならない

System.getPadAxis は無操作でも ±0.05 程度を返します。 ゲーム側で デッドゾーン処理を入れてください。

Agent.keyPress では文字入力イベントが出ない

注入されるのはキーイベントだけです ( 文字は OS のテキスト入力経路を通るため )。 自動テストで文字を入れるなら Elements の入力欄 + Agent.text を使います。


その他

Clipboard.asText はテキストが無いと void

空文字列ではなく void が返ります。 if (v === void) で判定してください。

JSON の組み立てにプラグインを前提にしない

Scripts.toJSONString / evalJSON は json プラグインの拡張です。 コア機能だけで 完結させたい場合は手組みするか、Dialog.dictToJson ( Elements 有効時 ) を使います。


関連