本文へスキップ

LÖVE(Love2D)スプライトアトラスとQuad · 範囲の手書き不要

LÖVEにはアトラス形式がないため、フレームの範囲はふつう手で書きます。この書き出しはそれをLuaのQuadテーブルとして書き、アニメーションを描いて進めるnerulio_atlas.luaも付け、LÖVE 11.5でフレームごとに実行しました。

LÖVEにはアトラスのファイル形式がありません。love.graphics.newImageでPNGを読み込み、フレームごとにlove.graphics.newQuad(x, y, w, h, ページ幅, ページ高さ)でQuadを作り、love.graphics.draw(image, quad, x, y, r, sx, sy, ox, oy)で描きます。Nerulioはフレームをパックしてこれらの数値をLuaのテーブル(矩形、トリムのオフセット、ピボット、秒単位の長さを持つアニメーション)として書き、Quadを作ってアニメーションを進める小さなヘルパーnerulio_atlas.luaを添えます。LÖVE 11.5がこのヘルパーで全フレームとアニメーションを描画しました。

パックするフレーム画像、GIF、.asepriteファイルをドロップ
空のStudioを開く
ブラウザ内で処理し、ファイルはアップロードされません。
Studio のパック&書き出し:各フレームの枠が表示されたアトラスページ、使用率、書き出し先
CC0アセット(OpenGameArt)を開いた実際のStudio画面です。
書き出し先
  • LÖVE検証済み· LÖVE 11.5
  • Godot 4検証済み· Godot 4.7.2
  • Unity 6検証済み· Unity 6000.5.3f1
  • Phaser 3 / 4検証済み· Phaser 3.90 + 4.2
  • PixiJS 8検証済み· PixiJS 8.21
  • Aseprite JSON検証済み· Phaser load.aseprite · PixiJS 8.21
  • .aseprite検証済み· Aseprite 1.3.18
  • Spine / libGDX検証済み· spine-canvas 4.2
  • Starling / Sparrow XML検証済み· Phaser 4.2
  • Defoldビルド確認· Defold bob.jar 1.13.1
  • GameMaker未検証
  • CSS sprites検証済み· Chromium
  • GIF / APNGデコード確認· Pillow

Quad、原点、トリムしたフレーム

Quadは画像の矩形にすぎません。描くと矩形の左上がx, yに置かれます。love.graphics.drawの最後の引数で原点のオフセットox, oyを渡すと変わり、これでフレームを足元や中心を基準に描けます。拡大や回転もこの点を中心に行われます。

トリムしたフレームは見えるピクセルだけを保存しているので、原点をトリム分だけ補正する必要があります。原点 = ピボット − トリムのオフセット。書き出したテーブルでは、px, pyがフレーム全体のキャンバス上のピボット、ox, oyが保存ピクセルのキャンバス上の開始位置です。ヘルパーは原点(px − ox, py − oy)で描きます。

LÖVEプリセットは押し出し1px、間隔2pxでパックし、回転しません。そのためどのQuadも正立していて、周りに縁のピクセルの複製があります。ヘルパーは画像を読み込む前にlove.graphics.setDefaultFilter("nearest", "nearest")を呼ぶので、4倍でもドット絵がくっきりしたままです。

Quad
x、y、幅、高さと画像の大きさからlove.graphics.newQuadで作る、画像の矩形。
drawのox, oy
love.graphics.drawの原点オフセット。x, yに来る点。
テーブルのox, oy
トリムしたフレームの保存ピクセルが、元のキャンバス上で始まる位置。
durations
アニメーションのフレームごとの秒数。タグのミリ秒から変換した値。

例:テーブルから画面までの1フレーム

run.lua   (ページ run.png 64 × 37:押し出し1、間隔2、回転なし)
["run_0"] = { page = 1, x = 1, y = 20, w = 18, h = 15, ox = 10, oy = 10, sw = 40, sh = 29, px = 20, py = 29 }
["run"]   = { loop = true, frames = { "run_0", …, "run_5" }, durations = { 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 } }

quad   = love.graphics.newQuad(1, 20, 18, 15, 64, 37)
原点   = (px − ox, py − oy) = (20 − 10, 29 − 10) = (10, 19)
love.graphics.draw(image, quad, 200, 200, 0, 4, 4, 10, 19)
       → フレームの足元が (200, 200) に来て、4倍で描かれる

ページ上の間隔:run_5はx = 1–21、その押し出し列はx = 22、
間隔x = 23–24、run_3の押し出し列x = 25、run_3はx = 26から

保存された断片は自分の押し出しの帯の1px内側にあり、次の断片とは少なくとも4px(1 + 2 + 1)離れています。そのため小数の位置や倍率で描いても、縁では自分の色を読みます。

できること

  • 既定で1pxの縁の拡張入りアトラスPNG(非整数位置でのにじみを防止)。
  • ピボット・アニメーション・フレームごとの長さ入りのLua Quadテーブル。
  • nerulio_atlas.luaヘルパーと、全アニメーションをNearestで描くmain.lua。
  • この書き出し先は回転しないので、すべてのQuadはまっすぐです。

使い方

  1. フレーム・GIF・.aseprite、またはシート(適用まで)を入れる。
  2. アニメーションとピボットを設定。
  3. パック&書き出しでLÖVEを選ぶ。
  4. ファイルをmain.luaの横に置き、nerulio_atlasをrequire。

出力されるファイル

ファイル用途
run.luaimages、pages(w, h)、frames(x, y, w, h、トリムのox/oy、キャンバスのsw/sh、ピボットpx/py)、order、animations(ループ、フレーム、秒単位の長さ)。
nerulio_atlas.luaAtlas.load(path)が画像とQuadを作り、atlas:draw(key, x, y, sx, sy)がピボット基準で描き、atlas:play(name)がupdate(dt)とdrawを持つ小さなプレーヤーを返す。
main.lua最初のアニメーションを4倍で再生する例(フォルダーでlove .)。
run.png全フレームの周りに1pxの押し出しがあるページ。

LÖVE 11で使う

  1. run.png、run.lua、nerulio_atlas.luaをゲームのフォルダーに入れます。main.luaの隣か、パスをAtlas.loadに渡すサブフォルダーに置きます。
  2. love.loadでAtlas = require("nerulio_atlas")、atlas = Atlas.load("run.lua")、anim = atlas:play("run")。
  3. love.update(dt)でanim:update(dt)、love.draw()でanim:draw(x, y, 4)を呼ぶと、ピボットがx, yに来るように4倍で描きます。
  4. ヘルパーなしなら:local data = love.filesystem.load("run.lua")()で読み、フレームごとにlove.graphics.newQuad(f.x, f.y, f.w, f.h, page.w, page.h)を作って、原点f.px − f.ox, f.py − f.oyで描きます。

結果の確認

  • 書き出したフォルダーでlove .を実行すると、main.luaが最初のアニメーションを4倍、ピクセルの縁が四角いまま再生します。
  • run_0とrun_4を同じ点に描きます。トリムのオフセットが(10, 10)と(10, 8)で違っても、足元は一直線にそろうはずです。
  • runの1周は0.6秒です。0.1秒の長さが6つ。

トラブルシューティング

症状考えられる原因確認方法対処
アニメーション中にフレームが上下に跳ねるQuadをトリムとピボットの原点なしで、それぞれの左上基準で描いている2つのフレームを同じ点に描き、Studioのプレビューと比べる原点px − ox, py − oyを渡すか、ヘルパーのdrawを使う
拡大するとピクセルがぼやけるsetDefaultFilter("nearest", "nearest")より前に画像を読み込んだ、または自分の画像のフィルターが線形newImageとの順番でフィルター設定を確認既定のフィルターを先に設定する(ヘルパーはそうしている)か、image:setFilter("nearest", "nearest")を呼ぶ
小数の位置や倍率で、隣のフレームの細い線が見えるQuadの周りに押し出しも間隔もない手作りのシートフレームを半ピクセルずつ動かし、縁を4倍で見るLÖVEプリセット(押し出し1、間隔2)でパックし直す
module 'nerulio_atlas' not foundヘルパーがrequireのパスにないlove.filesystem.getInfo("nerulio_atlas.lua")main.luaの隣に置くか、フォルダー付きでrequireする。例:require("lib.nerulio_atlas")
アニメーションが最後のフレームで止まるタグがループしないため、プレーヤーが最後のフレームを保持しているdata.animations.run.loop書き出し前にタグを無限ループにするか、atlas:play("run")でプレーヤーを作り直す

ほかの方法

方法こんなときに向いています
均一なグリッドとループで計算したQuad全フレームが同じ大きさでトリムがないとき。セルごとにlove.graphics.newQuad(col * w, row * h, w, h, sheetW, sheetH)で、データファイルは不要です。
LuaのJSONライブラリで読むTexturePackerのJSONすでにJSONのアトラスがあるとき。各frameの矩形から同じようにQuadを作りますが、トリムしたフレームは原点にspriteSourceSizeのオフセットを反映する必要があります。
同じフレームを別のエンジンでDefoldアトラス、Phaserのテクスチャアトラス。

エンジン別の書き出しと検証方法

「検証済み」は、実際のCC0アセットから書き出したファイルを本物のエンジン・ツールで読み込み、元データと比較したという意味です。読み込んでいない形式はそのように表示します。

書き出し先ファイル検証
Godot 4.tres SpriteFrames · .tscn AnimatedSprite2D · .png.import検証済み Godot 4.7.2Godotで読み込み、全アニメーションフレームを描画して元ファイルと比較。シーンはNearestフィルターで、回転は使いません。
Unity 6.unity.json · Editor/NerulioSpriteImporter.cs · AnimationClip検証済み Unity 6000.5.3f1バッチモードでスプライト範囲・ピボット・ピクセル・クリップのキーと長さを確認。
Phaser 3 / 4atlas JSON (multiatlas) · .anims.json検証済み Phaser 3.90 + 4.2両バージョンでフレームとアニメーションを描画。
PixiJS 8spritesheet JSON · animations · anchor · meta.scale検証済み PixiJS 8.21回転・複数ページのアトラスも確認。
Aseprite JSONhash / array · frameTags · slices検証済み Phaser load.aseprite · PixiJS 8.21タグの方向と繰り返し回数を保持。
.asepritelayers · tags · durations · slices検証済み Aseprite 1.3.18Asepriteのコマンドラインで開いて描画。
Spine / libGDX.atlas検証済み spine-canvas 4.2Spineランタイムで回転・トリム領域をすべて描画。libGDX本体は未実行。
Starling / Sparrow XMLXML per page検証済み Phaser 4.2Phaser 3.90はトリムされたXMLフレームを誤って描画します(Phaser側の不具合)。「Sparrow XML (Phaser 3)」プリセットはトリムを切り、3.90・4.2とも合格。
LÖVELua quads · nerulio_atlas.lua · main.lua検証済み LÖVE 11.5同梱ヘルパーで全フレームを描画し、全アニメーションを再生。
Defold.atlas · .tilesourceビルド確認 Defold bob.jar 1.13.1Defoldのコマンドラインビルダーでビルドし、生成テクスチャのUVを読み戻して確認。
GameMakerspr_<name>_stripN.png · gamemaker.json未検証GameMakerでは読み込んでいません(ヘッドレス実行不可)。ストリップはGameMakerの取り込み方式どおりに切ります。
CSS sprites.css · preview .html検証済み Chromium同梱スタイルシートで全クラスを描画。
GIF / APNGone file per animationデコード確認 Pillow書き出し後に全フレームと遅延を再デコード。APNGは完全一致。

検証の根拠

書き出し:実在のCC0アセットで実際のエンジンを49回実行し47回合格。失敗2回はPhaser 3.90のトリムXMLの不具合で、Phaser 3プリセットで回避。実在の5フレームセットでパッカー比較(CodeAndWeb無料Webパッカー、GAPTools、free-tex-packer):回転ありで5つすべて最小、回転なしで5つ中4つ。全フレームがピクセル単位で復元できます。

制限

  • ポリゴン(メッシュ)パッキングはありません。検証したエンジンのどれも、書き出しデータからメッシュアトラスを描画しないためです。
  • GameMakerのストリップはGameMaker本体では未検証です。
  • 拡大縮小バリエーション(@0.5x〜@4x)はニアレストネイバーのみです。
  • トリムしたStarling XMLはPhaser 3.90で正しく描画されないため、「Sparrow XML (Phaser 3)」プリセットを使ってください。

バージョンと出典

LÖVE 11.5(ポータブル版)で、書き出したnerulio_atlas.luaをrequireする検査用スクリプトを実行し、全フレームをCanvasに描き、ヘルパーのプレーヤーで全アニメーションを進め、1フレームを4倍で描きました。ピクセルを読み戻して元のフレームと比較しています。関数のシグネチャはLÖVE 11.5のソースに従います。

参照した公式ドキュメント

よくある質問

LÖVEで何を確認しましたか?

LÖVE 11.5が同梱ヘルパーで全フレームを描画し、全アニメーションを進め、4倍でもくっきりでした。

ヘルパーは必須ですか?

いいえ。QuadテーブルはふつうのLuaデータで、ヘルパーは描画と時間管理の一例にすぎません。

無料ですか?アカウントは必要ですか?

このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。

スプライトはアップロードされますか?

いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。

Asepriteやエンジンのプラグインは必要ですか?

いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。