この付録では、スクリプトを作成するときに迷いやすい問題と、その対処方法を説明します。
1. 個別にscaleした文字の送りを整える
1.1 どのようなときに必要か
OnLayoutで文字ごとにscaleやstretchを変更し、変更後の大きさに合わせて文字を 並べ直したいときに必要です。
1.2 起きる問題
文字の送りは、OnLayoutで個別の変形を加える前に決まります。そのため、文字ごとに scaleやstretchを変更しただけでは、次の問題が起きることがあります。
- 大きくした文字が隣の文字と重なります。
- 小さくした文字の左右に余分な間隔が残ります。
- 文字の基準位置がベースラインからずれます。
これは、文字がgeometry.bounds_centerを中心に拡大・縮小される一方で、元の送りが そのまま使われるためです。
1.3 対処方法
文字のサイズ、回転、pivotを変更したあと、OnLayoutの最後に mt.layout.reflowを呼び出してください。
function OnLayout(ctx)
for index = 1, ctx.char_count do
local character = ctx.chars[index]
character.scale = 0.8 + index * 0.05
end
-- 変形後の文字サイズに合わせて送りを整える
mt.layout.reflow(ctx)
end文字間に追加の余白が必要な場合は、第2引数に間隔を指定します。対象文字や固定する文字を 限定する場合は、configを指定します。引数の詳細は mt.layout.reflowを参照してください。
1.4 仕組み
mt.layout.reflowは、横書きと縦書きの組版原点を使い、シェーピング、kerning、tracking、 marginsによる間隔を保ったまま、現在のscale、stretch、rotation、pivotを反映して文字を 並べ直します。個々のインク輪郭の間隔を均一にする機能ではありません。
pivotが0.5でscaleだけを変更する場合、自然座標上の点Pは次式で移動します。 Cはgeometry.bounds_center、sはscale×stretch、dはoffset-0.5です。
P' = C + s × (P - C) + d
d = P'(目標) - C - s × (P - C)この式をスクリプト内で直接計算する必要はありません。mt.layout.reflowは、rotation、pivot、 縦書き、非正方形canvasを含む完全な2D文字変換を考慮して組版原点を整列します。
2. グラデーションの範囲と向きを調整する
2.1 どのようなときに必要か
start_position、end_position、angleを変更したときに、グラデーションの開始位置、 終了位置、色の向きが想定と合わない場合に確認してください。
2.2 起きる問題
start_positionとend_positionは、canvas全体を基準とする座標ではありません。 グラデーションのangleも、単純にバウンディングボックスの幅や高さだけを使って 計算されるわけではありません。
これらをcanvas座標として扱うと、テキストを移動・回転・拡大したときや、斜め方向の グラデーションを設定したときに、色の切り替わる位置が想定からずれて見えます。
2.3 対処方法
start_positionとend_positionは、変形後のテキスト全体をangle方向に見た範囲に 対する比率として指定してください。
- テキストの端から端までグラデーションを適用する場合は、
start_position = 0.0、end_position = 1.0にします。 - テキストの内側で色を切り替える場合は、
start_positionを大きくし、end_positionを小さくします。標準設定の0.2と0.8では、両端から20%内側が グラデーション区間になります。 angle = 0では左から右、angle = 90では上から下、angle = -90または270では下から上へ色が変化します。
start_position側にはfill色、end_position側にはend_colorが割り当てられます。 angleの向きは座標の定義と同じです。
2.4 基準となる範囲
グラデーションは、OnLayoutで文字やパーツへscale、rotation、offsetなどを適用した 後のテキスト全体のバウンディングボックスを基準とします。
- 2D描画時: 変形後の全文字・パーツのパスを囲むcanvas上の2Dバウンディングボックスです。
- 3D投影時: カメラ投影の影響を受けない3Dレイアウト平面上のバウンディングボックスです。 キャラクターごとに投影座標が計算されるため、カメラが動いてもグラデーションの位置は 文字に対してずれません。
2.5 基準線の計算方法
グラデーションの0.0〜1.0の基準線は、バウンディングボックスをangle方向へ投影したときの 最近端と最遠端を結ぶ線分です。斜め方向では、矩形の幅や高さではなく、指定方向へ最も 突き出た位置と、その反対側の位置が境界になります。
バウンディングボックスの中心をC、angleの方向ベクトルをD、4つの頂点をP_iとすると、 各頂点の射影値は次式で求められます。
proj_i = (P_i - C) · D射影値の最小値をminProj、最大値をmaxProjとすると、基準線の開始点bbP0と 終了点bbP1は次のようになります。
bbP0 = C + D * minProj
bbP1 = C + D * maxProjstart_positionをt_s、end_positionをt_eとすると、実際の開始点P0と終了点P1は 基準線上の次の位置になります。
P0 = bbP0 + (bbP1 - bbP0) * t_s
P1 = bbP0 + (bbP1 - bbP0) * t_e3. コールバック間で値を受け渡す
3.1 どのようなときに必要か
OnPreLayoutで上書きする前の値を、同じフレームのOnLayoutでも使いたい場合に必要です。 たとえば、ホストのScaleを文字サイズではなくアニメーションの移動量を決める係数として使う場合が 該当します。
3.2 起きる問題
OnPreLayoutでctx.global.scaleを1.0に変更すると、OnLayoutから参照できるのは 変更後の値です。上書き前のホスト値は復元できないため、その値を使った計算ができなくなります。
一方、OnPreLayoutで上書きしていない値は、OnLayoutのctx.globalからそのまま読めます。 たとえばtrackingやline_spacingは、退避せずに使う場所で直接参照してください。
3.3 対処方法
上書き前の値を関数の外側で宣言したlocal変数へ保存し、OnLayoutから参照してください。 Luaの実行環境はインスタンスごとに1つ作成され、スクリプトが再読み込みされるまで維持されるため、 同じ変数をコールバック間で参照できます。
local scaleKnob = 1.0
function OnPreLayout(ctx)
-- ホストのScaleを退避してから、文字サイズへの影響を打ち消す
scaleKnob = math.max(ctx.global.scale, 0.05)
ctx.global.scale = 1.0
end
function OnLayout(ctx)
local travel = 0.07 * scaleKnob
for index = 1, ctx.char_count do
ctx.chars[index].offset_y = ctx.chars[index].offset_y + travel
end
endこの受け渡しは、上書き前の値が必要な場合だけ使ってください。不要な値まで保存すると、 スクリプトの状態が増え、処理の流れが分かりにくくなります。
3.4 フレーム間の状態保持には使わない
関数の外側で宣言した変数は、コールバック間だけでなくフレーム間でも値を保持します。しかし、 ホストはシーク、レンダリング順序の変化、並列描画などにより、フレームを時間順に評価するとは 限りません。
前フレームの値を使った差分計算や累積を行うと、再生時とレンダリング時で結果が変わることが あります。各フレームの結果は、そのフレームのctx.timeから一意に決まるようにしてください。 関数の外側では、定数やコールバック間で同一フレーム内に受け渡す値だけを保持します。