Nuxt.js + PixiJSでスプライトアニメーション作成する際にハマったこと
はじめに
Nuxt、Pixi環境でスプライトアニメーションを実装したのですが、ハマってしまったポイントがありました。それらを共有する目的も込めて記事にします。
開発環境構築
ますは、 npx create-nuxt-app <project-name>
でNuxt環境を作成します。
設定は以下のようにしました。
? Project name: sample-pixi-sprite
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: ESLint, Prettier, StyleLint
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert
selection)
? Continuous integration: None
? Version control system: Git
続いてpixiを追加します。
yarn add pixi.js
yarn add -D @types/pixi.js
また、お好みでpug, scssを使う場合はモジュールを追加します。今記事では使用する方向で進めます。
// sass-loaderは最新バージョンだと動作しない可能性があります
yarn add -D pug pug-loader pug-plain-loader sass sass-loader@^10.1.1
スプライトシート準備
実装に入る前にpixi.jsのSpritesheetの公式ドキュメントにあるspritesheet.jsを使用して、スプライトシートとJSONファイルを作成していきます。使用する画像は尾羽の小屋様からペンギンの素材をお借りしました。
以下のように使う箇所を切り出して0~3の番号で保存します。(元画像が小さいので後でcanvasに表示する際拡大します)
画像を書き出したらspritesheet.jsを使ってスプライトシートとJSONファイルを作成します。
yarn add -D spritesheet-js
インストール後、 package.json
にscriptを付け足しこれを実行することでスプライトシートとjsonファイルが出力されます。
{
"scripts": {
...
// 私の場合はassetsフォルダを作成してその中に0~3.pngを入れました
// 出力先はstaticフォルダを指定します
"spritesheet": "spritesheet-js assets/*.png -p static",
}
}
yarn spritesheet
以上で下準備の完了です。
実装
pages/index.vue
にcanvasを用意し、pixiの環境を作ります。
<template lang="pug">
canvas.canvas(ref="canvas")
</template>
<script lang="ts">
import Vue from 'vue'
import SpriteClass from '~/assets/class/spriteClass'
export default Vue.extend({
name: 'IndexPage',
data() {
return {
sprite: {} as SpriteClass,
}
},
mounted() {
const canvas = this.$refs.canvas as HTMLCanvasElement
this.sprite = new SpriteClass({ canvas })
},
})
</script>
<style lang="scss" scoped>
.canvas {
width: 375px;
height: 375px;
}
</style>
spriteClassは以下のように Applicationを作成した後、jsonファイルを読み込みアニメーションを実行させます。
// こちらは型定義用
import { Application } from 'pixi.js'
type Props = {
canvas: HTMLCanvasElement
}
class SpriteClass {
app: Application
constructor({ canvas }: Props) {
// importから呼び出したものを使用しようとすると
// サーバー側でも実行されてエラーになってしまうので
// require経由で呼び出す
const {
Application,
AnimatedSprite,
} = require('pixi.js')
// Application作成
this.app = new Application({
width: 375,
height: 375,
view: canvas,
resolution: window.devicePixelRatio || 1,
backgroundColor: 0xffffff,
})
// jsonファイルの読み込み
// 作成されたスプライトシートはjsonファイルに紐づいているので読み込む必要なし
this.app.loader.add('penguin', '/spritesheet.json').load(() => {
if (!this.app.loader.resources.penguin.textures) return
const penguinTextures = this.app.loader.resources.penguin.textures
const penguinTextureArray = Object.keys(penguinTextures).map(
(e) => penguinTextures[e]
)
// AnimatedSpriteを作成
const penguinSprite = new AnimatedSprite(penguinTextureArray)
// canvasの中央に配置
penguinSprite.position.set(
375 / 2,
375 / 2
)
// 画像が小さいので5倍に拡大
penguinSprite.scale.set(5)
// 画像の変更基準点を画像の中央に設定
penguinSprite.anchor.set(0.5)
// アニメーションの速度設定
penguinSprite.animationSpeed = 0.03
// アニメーションのループ設定
penguinSprite.loop = true
// pixiのstage(Container)に追加
this.app.stage.addChild(penguinSprite)
// 実行
penguinSprite.play()
})
}
}
export default SpriteClass
以上で完成です
ハマったポイント
本題です。
この実装を行う際に2つ、実装でハマってしまったことがありました。
まず1つ目が、jsonを生成するアプリの選定です。
ドキュメントを見ると Sprite sheets can be packed using tools like TexturePacker, Shoebox or Spritesheet.js. Default anchor points (see PIXI.Texture#defaultAnchor) and grouping of animation sprites are currently only supported by TexturePacker.
とTexturePackerやShoebox、Spritesheet.jsなどのアプリを使えばいいと書いてありますが、初めはこういった仕様に気がつかずGlueを使ったところ、jsonがパースできないというエラーが出ました。
これだけであればすぐに解決できそうなのですが、2つ目の内容が重なることで解決に丸1日を費やす羽目になります。
次に、その2つ目はjsonの読み込み方法です。
初め、nuxt-optimized-imagesを使ってビルド時にwebpの自動生成を行なっていた関係上、画像は基本的にrequireを使って動的に読み込んでいました。
そういった流れでjsonも動的に読み込んでいたのですがnuxtの動的パス生成とpixiのパス読み込みの相性が悪いらしく、こちらもjsonに関する箇所でエラーが出てしまいました。
その結果、原因を予測して直してもエラーが出たままという事態に陥り、かなりの時間を要してしまいました。
総括
pixiのスプライトアニメーションを実装する上での知見が溜まった点は良かったです。
また、複雑なはまり方をしてしまったことは不幸でしたが、こうやって話のネタになるような出来事であったことは確かですので総じて良い経験だったということで。
リポジトリ
Discussion