トップ

DXライブラリにSpineを組み込む

2025-03-24

こんにすやぁ…… 最近息絶え絶えなふるおろです。

今回は、WindowsアプリケーションにSpineを組み込む際のノウハウの共有になります。

きっかけ

トップページなどのうごイラで使用しているSpine ですが、Webであったり、アプリケーションだったり、作成したスプライトを幅広に利用できるように多くの公式ランタイムが用意されています。

そういえば、Windowsアプリケーションへの組み込みはやったことがないな……🤔と思っていたところ、Qiita におもしろい記事があがっていたので、C++でゲームプログラミングをする場合にしばしば用いられるDXライブラリ へSpineを組み込んでみました1, 2, 3

DXライブラリとSpineを繋げる根幹部分は元記事にて既に解説されていますので、記事中に載せられているサンプルコードをもう少しだけ使い勝手が良くなるよう、関数などをラッピングしたものを実装してみました。 具体的には、アニメーションのセットや変更、表示位置(座標)や大きさの変更、スキンのセットになります。

この記事に書いたソースコード等はGitHubのリポジトリ に置いてあります。 ぷよぐやみんぐ(C++)が分かる人はDLして好きに活用してみてね。

実装

spine.hDxSpineというクラスを宣言しました。 spine.hをインクルードしてDxSpineインスタンスを作成しておけば、基本的なSpineスプライトの操作はできるようになっている……と思います。

また、DXライブラリの関数の使用感に近いようなSpineスプライト用のロード関数LoadSpineJson, LoadSpineBinaryを用意しました。

各メソッドの引数の解説などはここでは省略します(詳細はリポジトリ見てね)。

spine.h
1#pragma once
2
3#include <string>
4#include <memory>
5#include <vector>
6#include <DxLib.h>
7#include "dxlib_spine_c.h"
8#include "spine_loader_c.h"
9
10class DxSpine {
11public:
12    // コンストラクタ
13    DxSpine() {
14        atlas = nullptr;
15        skeletonData = nullptr;
16        spineDrawable = nullptr;
17        pos = { 0.0f };
18        scale = { 1.0f };
19        active = true;
20        visible = true;
21    };
22    // デストラクタ
23    ~DxSpine() {};
24    // アトラスデータ
25    std::shared_ptr<spAtlas> atlas;
26    // スケルトンデータ
27    std::shared_ptr<spSkeletonData> skeletonData;
28    // アニメーション名の配列
29    std::vector<std::string> animationNames;
30    // spine drawable
31    std::shared_ptr<DxLibSpineDrawer> spineDrawable;
32    // 更新・描画がアクティブか
33    bool active;
34    // 可視状態か
35    bool visible;
36    // Spineの座標を変更する
37    DxSpine& setPosition(float x, float y);
38    // Spineの座標を取得する
39    void getPosition(float* x, float* y) const;
40    // 大きさを変更する
41    DxSpine& setScale(float scaleX, float scaleY);
42    // 大きさを変更する
43    DxSpine& setScale(float scale);
44    // アニメーションをセットする
45    spTrackEntry* setAnimation(int trackIndex, const char* animationName, float mixDuration = 0.f, bool loop = true);
46    // アニメーションを追加する
47    spTrackEntry* addAnimation(int trackIndex, const char* animationName, float mixDuration = 0.f, bool loop = true, float delay = 0.f);
48    // 空のアニメーションをセットする
49    spTrackEntry* setEmptyAnimation(int trackIndex, float mixDuration = 0.f) const;
50    // 空のアニメーションを追加する
51    spTrackEntry* addEmptyAnimation(int trackIndex, float mixDuration = 0.f, float delay = 0.f) const;
52    // 現在のアニメーションの状態を取得する
53    spTrackEntry* getCurrentAnimation(int trackIndex) const;
54    // スキンをセットする
55    DxSpine& setSkin(const char* skinName);
56    // スキンをセットする
57    DxSpine& setSkin(const std::vector<std::string> skinNames);
58    // 状態を更新する
59    DxSpine& update();
60    // 描画する
61    DxSpine& draw(float fDepth = 1.0f);
62private:
63    // 座標
64    VECTOR pos;
65    // スケール
66    VECTOR scale;
67};
68
69// Spineをロードする(JSON)
70int LoadSpineJson(const char* atlasPath, const char* jsonPath, DxSpine* spine);
71
72// Spineをロードする(バイナリ)
73int LoadSpineBinary(const char* atlasPath, const char* skelPath, DxSpine* spine);

DxSpineインスタンスを使ったサンプルコードは以下になります(一部省略)。 これなら、TypeScript版とほぼ同じ感覚で記述できるね……!

main.cpp
1#include <DxLib.h>
2#include "spine.h"
3
4// プログラムは WinMain から始まります
5int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) {
6    /* DXライブラリ初期化処理 */
7
8    /* Spine Boy */
9    DxSpine spineBoy;
10    LoadSpineBinary("data/spineboy/spineboy.atlas", "data/spineboy/spineboy-pro.skel", &spineBoy); // スプライトをロード
11    spineBoy
12        .setPosition(1920.f / 2.f - 500, 1080.f / 2.f + 450) // 位置をセット
13        .setAnimation(0, "idle"); // アニメーションをセット
14
15    /* Mix and Match */
16    DxSpine mixAndMatch;
17    LoadSpineBinary("data/mix-and-match/mix-and-match.atlas", "data/mix-and-match/mix-and-match-pro.skel", &mixAndMatch); // スプライトをロード
18    mixAndMatch
19        .setSkin("full-skins/girl") // スキンをセット
20        .setPosition(1920.f / 2.f + 500, 1080.f / 2.f + 450) // 位置をセット
21        .setAnimation(0, "idle"); // アニメーションをセット
22
23    // メインループ
24    while (ProcessMessage() == 0) {
25        // 画面クリア
26        ClearDrawScreen();
27
28        /* ループ内処理 */
29
30        // アップデートと描画
31        spineBoy.update().draw();
32        mixAndMatch.update().draw();
33
34        // 裏画面を表画面に反映
35        ScreenFlip();
36    }
37    // ソフトの終了
38    return 0;
39}

実行結果はこんな感じです。 Spine公式にあるSpine BoyとMix-and-matchをサンプルスプライトとしてお借りしました。

webp

おお~ 本当にWinアプリで動いた。感動!

Spineはいいぞ(ソフトの価格は高いけど)。 お絵描きする人は一緒に作って遊んでみない……?みない……?(体験版あるよ)

それはともかく、Spine自体がゲーム向けというのもあり、今回試しに書いてみたコードはかなり実用できそうな気がします。 作ったゲームで立ち絵が動いたらかなりかわいいと思う……

こ、こういうことをするとゲ制したくなるじゃない……!///

できたらやりたい(あ、この先余談です)。

これに尽きるんですが、小さいころみたいに時間がたくさんとれぬ、ぐぬぬ……。 私は超人ではないので、(絵、プログラム、音楽など)を全部一人ではやってる余裕がないです4。 いったい何年かかるんだという話になりますので……(ゲ制はクオリティや面白さ以前に完成させるだけで偉業5)。

完成させるために必要なノウハウはいろいろ持っているけど、素材作りに時間がかかるんですよね。 まあもしあと一人いたら……とは思ったりしました。

脚注

  1. 今どきはUnityを代表とした色んなゲーム制作ツールがありますが、C++で作ったゲームは動作が圧倒的に軽いです(あくまで個人の意見です)。うん、なんというか「もっさり感」がない。私はプログラム書くのは嫌いではないので、そういうのが嫌でなければ、ゲーム制作でC++を使うのは悪くはないんじゃないでしょうか……?

  2. (どうでもいい自分語り) 筆者はC++とDXライブラリを使用したゲーム制作経験があったりしたりなかったりします。まあ、名義が違ったりなので、どんなものを作ったかについてはここでは割愛します (今見返すと黒歴史もありますし)

  3. (どうでもいい自分語り2) わたくし、実は最初に学習したプログラミング言語はC++でした(○学生のころ)。Web系の知識(HTML, CSS, JavaScript (TypeScript)など)は後からだったりします。

  4. そもそも、ゲームは1人でつくるものではありません(声に出して読みたい日本語)。「私は世界より選ばれし才能を持つ勇者だ……!」というなら止めはしませんが……。現実的な解を出すなら、最低でも2人組でやるのがいいと思いまする。

  5. 完成難易度は作るジャンルや2D, 3Dの違いで変わります。個人的な感覚では、3D >>> 2Dで、2Dアクション(メトロイドヴァニア)や2Dシューティング(東方Projectのような弾幕STG)は易しめ、一方、RPGみたいなストーリ性の強いジャンルは2Dであっても(ボリューム的な点で)茨の道な気がします。

戻る