ボタン状態ジェネレーター · hover・pressed・disabled・focus
1つのボタンから5つの状態。状態ごとに明るさ・コントラスト・彩度・オーバーレイ・オフセット・アウトライン・アルファの操作が順に見え、編集できます。ピクセルの計算なので、元にないものは描きません。
ゲームのボタンには、ふつうnormal・hover・pressed・disabled・focusの5枚の画像が必要です。このジェネレーターは1枚のPNGから、読めて変更できるピクセル操作で5枚を作ります。既定値はhoverが明るさ+10%・彩度+8%、pressedが明るさ−10%と1px下へ、disabledが彩度を完全に落としてアルファ50%、focusが2pxの#3182f6のリングです。states/*.png、パック済みストリップ1枚、使ったすべての値を記録したstates.jsonを書き出します。既にあるピクセルを変えるだけで、新しい絵は描きません。

- Nine-slice測定確認· Pillow
- Bitmap font測定確認· independent .fnt parser
- Button states測定確認· Pillow
- Engine import未検証
1つの状態が計算される流れ
各状態はRGBAピクセルに順に適用する操作の一覧です。アウトラインがあれば先にキャンバスを広げ、明るさは値 × 255をR・G・Bに足し、コントラストは中間のグレー(128)を中心に広げ、彩度は各チャンネルをそのピクセルの輝度(0.2126 R + 0.7152 G + 0.0722 B)から遠ざけたり近づけたりします。続いて任意の色のオーバーレイ、ピクセルの移動、形を正方形に膨らませたアウトラインを描き、最後にアルファを掛けます。完全に透明なピクセルは塗り替えません。
エンジンはこれらの画像を決まった枠で受け取ります。Godot 4のTextureButtonにはtexture_normal、texture_hover、texture_pressed、texture_disabled、texture_focusedがあり、専用のテクスチャがないdisabledのボタンはnormalを表示し、focusedのテクスチャは基本のテクスチャの上に重ねて描かれます。UnityのUI ButtonはTransitionをSprite Swapにすると、Target Graphicの基本スプライトとHighlighted・Pressed・Disabledのスプライトを受け取ります。
ゲームはWebページではありませんが、WCAG 2.2の2つの規定は知っておく価値があります。達成基準1.4.3は無効(disabled)なコンポーネントの一部であるテキストをコントラストの最低基準から除外しているので、薄くしたdisabledは許容されます。2.4.13 Focus Appearance(AAA)は、コンポーネントの外周2 CSS px以上の大きさのフォーカス表示と、フォーカス前後のピクセル間で3:1のコントラストを求めます。既定のリングは2pxで、#3182f6は白に対して3.71:1と測定されます。
- 明るさ
- すべてのチャンネルに足す値。+0.1は+25.5段階です。
- 彩度
- +0.08は各チャンネルを輝度から8%遠ざけ、−1はピクセルをグレーにします。
- オフセット
- 同じキャンバスの中でピクセルを移動。はみ出た分は捨て、入ってくる側は透明です。
- アウトライン
- 不透明な形の周りに付く指定の色・太さのリング。キャンバスがその太さ分広がります。
例:1ピクセルが既定の5状態を通ると
色(200, 120, 40)で塗った40 × 16のボタンです。書き出したPNGに実際に入る値です。
状態 ピクセル (R, G, B, A) サイズ 操作
normal (200, 120, 40, 255) 40 × 16 なし
hover (231, 145, 58, 255) 40 × 16 明るさ +0.1 (+25.5)、彩度 +0.08
pressed (174, 94, 14, 255) 40 × 16 明るさ −0.1、オフセット 0,1 → 0行目が透明に
disabled (131, 131, 131, 128) 40 × 16 彩度 −1、アルファ × 0.5
focus (200, 120, 40, 255) 44 × 20 キャンバス各辺 +2px、2pxのリング (49, 130, 246)
disabledのグレー 0.2126 × 200 + 0.7152 × 120 + 0.0722 × 40 = 131.2 → 131;アルファ 255 × 0.5 = 127.5 → 128
hoverの緑 輝度 156.7 + (145.5 − 156.7) × 1.08 = 144.6 → 145focusの画像はほかより縦横4px大きく、アウトラインが正方形の膨張なので角のピクセルまでリングが覆います。states.jsonに状態ごとのサイズと余白が記録されるので、違いに驚くことはありません。
できること
- 既定値:hoverは明るさ+10%、pressedは−10%と1px下へ、disabledは彩度を落としてアルファ50%、focusは2pxのリング。
- 状態ごとにすべての操作値が見えて編集可能。
- 出力:状態ごとのPNG、パック済みストリップ、範囲と正確な操作値入りのstates.json。
- 透明ピクセルは塗り替えません。
使い方
- ボタンのPNGをドロップ。
- 状態ごとの操作を調整。
- 5つのプレビューを並べて確認。
- ZIPで書き出し。
Godot・Unityで状態を使う
- Godot 4:
TextureButtonを追加し、normal.png・hover.png・pressed.png・disabled.pngをtexture_normal・texture_hover・texture_pressed・texture_disabledに割り当てます。 - Godot 4は
texture_focusedを基本のテクスチャの上に描き、ドキュメントはアウトラインのような一部が透明な画像を勧めています。focus.pngにはボタン全体が入っているので、この枠に使うなら画像エディターでリングの内側のボタンを消し、44 × 20のキャンバスはそのままにします。 - Unity UI:ButtonのTransitionをSprite Swapにし、Target GraphicのImageに
normal.pngを、Highlighted Sprite・Pressed Sprite・Disabled Spriteにhover.png・pressed.png・disabled.pngを入れます。 - ボタンのサイズを変える必要があるなら、状態には境界がないので、割り当てる前に9スライスエディターで各状態のPNGに同じナインスライスの境界を付けてください。
結果の確認
states.jsonのすべてのrectがbutton-states.pngから該当の状態を正確に切り出し、states.hover.opsに使った値が並びます。normal.pngは元画像とバイト一致します(Firefoxでは半透明ピクセルがアルファの丸め1段階ぶん違うことがあります)。- ゲームを実行してボタンにカーソルを乗せ、押し、無効にしてみます。跳ねる箇所がなく、pressedはオフセットの分だけ動くはずです。
トラブルシューティング
| 症状 | 考えられる原因 | 確認方法 | 対処 |
|---|---|---|---|
| pressedの画像のいちばん下の行が消える | 1pxのオフセットが同じキャンバス内でピクセルを動かすため、最後の行がはみ出し0行目が透明になる | pressed.pngの下端を元画像と比べる | 絵の下に透明な行を1つ残すか、オフセットを0にしてエンジン側で文字を動かす |
| フォーカス時にボタンが跳ねる・二重に見える | focus.pngが大きく(40 × 16のボタンなら44 × 20)、Godotはfocusのテクスチャを基本の上に描く | states.json → states.focus.size | アウトラインの太さ分マイナスにずらして中央を合わせるか、上の説明どおりリングだけの画像を使う |
| 暗い背景でdisabledのボタンがほとんど見えない | アルファ0.5で背景が透けている | 白地ではなく実際の背景の上で確認する | disabledのアルファを0.7に上げるか、アルファは1のまま彩度だけ落とす |
| 明るいボタンでhoverの差がほとんどない | 明るさは加算なので、255に近いチャンネルは頭打ちになる | 元画像のピクセル値がおよそ230以上 | hoverには明るさの代わりに色のオーバーレイかコントラストを使う |
ほかの方法
| 方法 | こんなときに向いています |
|---|---|
| UnityのTransition = Color Tint | 状態が色だけ違うとき。画像は増えませんが、色を掛けるだけなのでピクセルを動かしたり(pressedのオフセット)リングを足したりはできません。 |
| お絵描きソフトで状態ごとに手描きする | 形が変わるとき(反転するベベル、変わるアイコン、光)。ピクセルの計算では元にないものは描けません。 |
出力ファイルと検証方法
「測定確認」は、ダウンロードしたファイルをページ外(Pillow・numpy・独立したパーサー)で開き直して性質を測ったという意味です。ゲームエンジンでの読み込みは試していません。
| 書き出し先 | ファイル | 検証 |
|---|---|---|
| Nine-slice | nine-slice.json · previews · SETUP.md | 測定確認 Pillowリサイズした全プレビューで四隅のブロックは拡大されません。Chromiumではバイト一致、Firefoxでは半透明ピクセルがアルファの丸め1段階ぶん変わることがあります。すき間・重なり0。 |
| Bitmap font | font.png · font.fnt (BMFont text) · font.json | 測定確認 independent .fnt parserテスト内で別に書いたパーサーで.fntを読み戻し、ヘッダーと全グリフ範囲が一致。 |
| Button states | states/*.png · strip · states.json | 測定確認 Pillownormalは元と一致(Chromiumではバイト一致、Firefoxでは半透明ピクセルの色がアルファの丸め1段階以内)。states.jsonの各範囲がストリップから該当状態を正確に切り出します。 |
| Engine import | Godot NinePatchRect · Unity Sprite border · BMFont import | 未検証Godot・Unityでは実行していません。 |
検証の根拠
ナインスライスの四隅が100×40・300×80・800×200・420×120で拡大されず、同一列12本から境界6·6·6·6を提案。ボタン状態:normalは元と一致、disabledはアルファ128のグレー、focusリングも正確。.fntのグリフ記録がJSONと一致。.poの欠落グリフを正確に列挙。白地に黒文字で21:1を測定。画像はブラウザのCanvasを通ります。Chromiumはバイトがそのまま、Firefoxは半透明ピクセルの色を乗算済みの値で1段階に丸めます(Kenneyのボタンで測定:アルファ63で最大4段階)。アルファと不透明ピクセルはそのままです。
制限
- 状態ごとのナインスライス境界はありません。9スライスエディターで状態のPNGに付けてください。
- エンジンでは試していません。上の枠の名前はGodot・Unityの公式ドキュメントによるものです。
- カーニングペアなし、フォントは1ページ、MSDFなし。SDF出力はベータです。
- TTFのcmapを解析しないため、欠落グリフのチェックには.fntかラボのフォントが必要です。
- アトラスは1ページで、接している要素は1つとして扱います。
- Godot・Unityでの描画は行っていません。エンジン用の値(patch margin、L・B・R・Tの境界)はドキュメント基準で、エンジンでは未検証です。
バージョンと出典
docs/UI-LAB.mdのためにChromiumで40 × 16のボタンを使って測定しました。normalは元画像とバイト一致、hoverはより明るく、pressedはオフセットされ空いた行は透明、disabledはアルファ128のグレー、focusは#3182f6のリング付きの44 × 20で絵はそのまま、states.jsonの5つの矩形はストリップから各状態を正確に切り出しました。上の例の値は同じモジュールで計算し直しています。Godot・Unityでの読み込みは実行していません。
参照した公式ドキュメント
よくある質問
focusの画像が大きいのはなぜ?
focusリングがアウトラインの太さ分キャンバスを広げます。そのサイズはstates.jsonに記録されます。
normalの状態も変わりますか?
いいえ。normalは元とバイト一致です(確認済み)。
無料ですか?アカウントは必要ですか?
このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。
スプライトはアップロードされますか?
いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。
Asepriteやエンジンのプラグインは必要ですか?
いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。