見出し画像

[Siv3D] 簡易的なスプライトシートアニメーションクラス


はじめに

Siv3Dには、アニメーション関連の機能が標準で存在しません。そこで、スプライトシートを使った簡易的なアニメーションクラスを作成しました。

スプライトシート?

スプライトシートとは、複数の画像を1枚の画像にまとめ、タイル状に配置した画像のことです。1枚にまとめることで同じ役割を持つ複数の画像を管理しやすくなるほか、個別に管理するよりも画像の容量を抑制する効果もあるため、ビデオゲーム制作においては古くから用いられている手法の1つです。

ITMedia

Asepriteなどで簡単に作成することができます。動きごとに画像を並べ、切り出して使用します。

スプライトシートの例

Asepriteを使ったスプライトシートの作成は、以下に詳しく書いてあります。


https://scrapbox.io/tt-memo/%E3%82%B9%E3%83%97%E3%83%A9%E3%82%A4%E3%83%88%E3%82%B7%E3%83%BC%E3%83%88%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%97_%2F_Aseprite

クラスの概要

スプライトシートからアニメーションを読み込み、管理するためのクラスです。スプライトシートから読み込んだアニメーションは、フレームごとの矩形領域として管理されます。

アニメーションは、特定の名前で識別され、各アニメーションは複数のフレームから構成されます。指定された速度で更新され、指定された位置と拡大率で描画されます。

本クラスのオブジェクトは、複数のアニメーションを持つことができます。それぞれのアニメーションは、それぞれ更新レートを設定できます。

また、アニメーションの切り替えやリセットが可能です。

メソッド一覧

  • void addAnimation(const String &animationName, int32 row, int32 frameCount, double rate = 0.5, bool loop = true): 新しいアニメーションを追加するメソッドです。アニメーション名、スプライトシート上の行、フレーム数、更新レートを指定します。デフォルトでは、ループするようになっています。

  • void updateAnimation(): アニメーションの更新を行うメソッドです。経過時間を管理し、指定された更新レートでフレームを切り替えます。

  • void draw(double scale = 1.0, Vec2 pos = Vec2{0, 0}) const: アニメーションを描画するメソッドです。指定した拡大率と位置でアニメーションを描画します。

  • void switchAnimation(const String &animationName): 現在のアニメーションを切り替えるメソッドです。指定されたアニメーション名のアニメーションに切り替えます。

  • void resetAnimation(): 現在のアニメーションのフレームをリセットするメソッドです。再生中のアニメーションのフレームを最初の状態に戻します。

メンバー変数一覧

  • std::map<String, Array<Rect>> animations: アニメーションを管理するためのマップです。キーにアニメーション名、値にそのアニメーションの各フレームの矩形領域を含む配列が格納されます。

  • std::map<String, int32> animationIndices: 各アニメーションの現在のフレームのインデックスを管理するマップです。キーにアニメーション名、値に現在のフレームのインデックスが格納されます。

  • std::map<String, double> animationRates: 各アニメーションの更新レート(フレームの切り替え速度)を管理するマップです。キーにアニメーション名、値に更新レートが格納されます。

  • std::map<String, double> animationTimers: 各アニメーションの経過時間を管理するマップです。キーにアニメーション名、値に経過時間が格納されます。

  • String currentAnimation: 現在再生中のアニメーションの名前を示す文字列です。

コード

#include <Siv3D.hpp> // Siv3D v0.6.13

class AnimatedObject
{
private:
    Texture spriteSheet;                      // スプライトシート
    Vec2 frameSize;                           // 一つのフレームの大きさ
    std::map<String, Array<Rect>> animations; // アニメーションを管理するマップ
    std::map<String, int32> animationIndices; // アニメーションの現在のインデックスを管理するマップ
    std::map<String, double> animationRates;  // アニメーションの更新レートを管理するマップ
    std::map<String, double> animationTimers; // アニメーションの経過時間を管理するマップ
    std::map<String, bool> loopFlags;         // アニメーションのループフラグを管理するマップ
    String currentAnimation;                  // 現在のアニメーションの名前

public:
    // コンストラクタ
    // @param spriteSheetPath スプライトシートのファイルパス
    // @param frameSize 一つのフレームの大きさ
    AnimatedObject(const String &spriteSheetPath, const Vec2 &frameSize)
        : spriteSheet(Texture{spriteSheetPath}), frameSize(frameSize) {}

    // アニメーションを追加するメソッド
    // @param animationName 追加するアニメーションの名前
    // @param row スプライトシート上のアニメーションが配置されている行
    // @param frameCount アニメーションのフレーム数
    // @param rate アニメーションの更新レート(省略可能、デフォルト値は0.5)
    // @param loop アニメーションをループするかどうか(省略可能、デフォルト値は true)
    void addAnimation(const String &animationName, int32 row, int32 frameCount, double rate = 0.5, bool loop = true)
    {
        Array<Rect> frames;
        for (int32 i = 0; i < frameCount; ++i)
        {
            frames.push_back(Rect{static_cast<int32>(frameSize.x * i), static_cast<int32>(frameSize.y * row), static_cast<int32>(frameSize.x), static_cast<int32>(frameSize.y)});
        }
        animations[animationName] = frames;
        animationIndices[animationName] = 0;
        animationRates[animationName] = rate;
        animationTimers[animationName] = 0.0;
        loopFlags[animationName] = loop;

        // 初期状態のアニメーションを設定
        if (currentAnimation.isEmpty())
        {
            currentAnimation = animationName;
        }
    }

    // アニメーションを更新するメソッド
    void updateAnimation()
    {
        animationTimers[currentAnimation] += Scene::DeltaTime();
        if (animationTimers[currentAnimation] > animationRates[currentAnimation])
        {
            if (animationIndices[currentAnimation] < animations[currentAnimation].size() - 1)
            {
                animationIndices[currentAnimation]++;
            }
            else
            {
                if (loopFlags[currentAnimation])
                {
                    animationIndices[currentAnimation] = 0;
                }
            }
            animationTimers[currentAnimation] = 0.0;
        }
    }

    // アニメーションを描画するメソッド
    // @param scale 描画時の拡大率(省略可能、デフォルト値は1.0)
    // @param pos 描画位置(省略可能、デフォルト値は(0, 0))
    void draw(double scale = 1.0, Vec2 pos = Vec2{0, 0}) const
    {
        const Rect &currentFrame = animations.at(currentAnimation)[animationIndices.at(currentAnimation)];
        spriteSheet(currentFrame).scaled(scale).drawAt(pos);
    }

    // アニメーションを切り替えるメソッド
    // @param animationName 切り替えるアニメーションの名前
    void switchAnimation(const String &animationName)
    {
        if (animations.count(animationName) && currentAnimation != animationName)
        {
            resetAnimation();
            currentAnimation = animationName;
        }
    }

    // アニメーションをリセットするメソッド
    void resetAnimation()
    {
        animationIndices[currentAnimation] = 0;
        animationTimers[currentAnimation] = 0.0;
    }
};

使用例

#include <Siv3D.hpp>

void Main()
{
    // スプライトシートのファイルパス
    const String spriteSheetPath = U"assets/spritesheet.png";

    // スプライトシート上の1フレームの大きさ
    const Vec2 frameSize(64, 64);

    // AnimatedObject クラスのインスタンスを生成
    AnimatedObject animatedObject(spriteSheetPath, frameSize);

    // アニメーションの追加
    animatedObject.addAnimation(U"walk", 0, 4, 0.2);
    animatedObject.addAnimation(U"idle", 1, 3, 0.3);
    animatedObject.addAnimation(U"jump", 2, 2, 0.1);

    // アニメーションの切り替え
    animatedObject.switchAnimation(U"walk");

    // ゲームループ
    while (System::Update())
    {
        // アニメーションの更新
        animatedObject.updateAnimation();

        // アニメーションの描画
        animatedObject.draw(Vec2(100, 100), 1.0);
    }
}

参考


この記事が気に入ったらサポートをしてみませんか?