0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

DxLibでエフェクトを簡単にしたい

どうもこんにちはカレンダー主催者です。4日の遅刻ですさーせんm(_ _)m
最近C#のほうでやってるもんで、C++の書き方といい、エラーといいで悩まされてました。
本題はいります

基本設計

DXライブラリとともによく出てくるライブラリ「Siv3D」(C++/DirectX 12)には、Effectクラスが存在します。そちらは、こちらを参照してください。
今回は、これぐらい気軽にエフェクトを扱えるようにしたいと思います。言語はC++です。Cでやる場合は頑張れ。

設計としては、

エフェクト管理にエフェクトを追加

メインループ内でエフェクトが有効な間、実行する

こんな感じです。

ちなみにDxLibじゃなくても動きます。

実装

突貫でやったんでヘッダーにコード直書きです。

管理クラス

Effect.h
#include <vector>
#include <functional>

class Effect {
private:
    std::vector<std::function<bool(int)>> EffectList;
    std::vector<bool> Valid, Finished;
    std::vector<int> Counter;

public:
    /// <summary>
    /// エフェクトを有効にする
    /// </summary>
    /// <param name="UpdateFunction">エフェクト実体</param>
    /// <returns>エフェクトのインデックス</returns>
    int Add(std::function<bool(int)> UpdateFunction) {
        EffectList.push_back(UpdateFunction);
        Counter.push_back(0);
        Valid.push_back(true);
        Finished.push_back(false);
        return EffectList.size() - 1;
    }

    /// <summary>
    /// エフェクトの一時停止
    /// </summary>
    /// <param name="index">一時停止するエフェクトのインデックス</param>
    void Pause(int index) {
        if (index >= EffectList.size() || Finished[(int)index]) return;
        Valid[(int)index] = false;
    }

    /// <summary>
    /// 一時停止したエフェクトの再開
    /// </summary>
    /// <param name="index">再開するエフェクトのインデックス</param>
    void Resume(int index) {
        if (index >= EffectList.size() || Finished[(int)index]) return;
        Valid[(int)index] = true;
    }

    /// <summary>
    /// エフェクトの停止
    /// </summary>
    /// <param name="index">停止するエフェクトのインデックス</param>
    void Stop(int index) {
        if (index >= EffectList.size() || Finished[(int)index]) return;
        Valid[(int)index] = false;
        Finished[(int)index] = true;
    }

    /// <summary>
    /// 停止されているエフェクトを一番最初から再開する
    /// </summary>
    /// <param name="index">再開するエフェクトのインデックス</param>
    void Restart(int index) {
        Counter[(int)index] = 0;
        Valid[(int)index] = true;
        Finished[(int)index] = false;
    }

    /// <summary>
    /// <para>エフェクトを処理する</para>
    /// <para>メインループ内で呼ぶ</para>
    /// </summary>
    void Update() {
        if (EffectList.size() == 0) {
            return;
        }

        for (int i = 0; EffectList.size() > i; i++) {
            if (!Valid[i]) {
                continue;
            }
            bool r = EffectList[i](Counter[i]); //trueのときに終了する
            Counter[i]++;
            if (r) {
                Finished[i] = true;
                Valid[i] = false;
            }
        }
    }
};
maincpp
#include "DxLib.h"
#include "Effect.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (DxLib_Init() == -1){
        return -1;
    }
    ChangeWindowMode(TRUE);
    SetDrawScreen(DX_SCREEN_BACK);

    Effect effect;

    while (ScreenFlip() == 0 && ClearDrawScreen() == 0 && ProcessMessage() == 0) {
        effect.Add([](int t) { //エフェクトの実体が定義された関数を渡す ラムダ式で直接実装することも可能
                DrawPixel(t, t, 0xffffff);
                return false;//終了条件
            });
        effect.Update();
    }

    DxLib_End();

    return 0;
}

画面に(0, 0)から斜め45度に線が入っていくと思います。

応用

画像を一定時間ごとに点滅させてみましょう。

実装

class TextureFlash {
    int Time, Interval;
    int CenterPosX, CenterPosY;
    std::vector<byte> FlashPattern;
    double Rate, Angle;
    int texHandle;

public:

    TextureFlash(int CenterPosX, int CenterPosY, std::string texture, int Count, int Interval, std::vector<byte> FlashAlphaPattern, double Rate = 1.0, double Angle = 0) {
        Time = Count; //実行時間
        this->Interval = Interval; //点滅の間隔
        this->CenterPosX = CenterPosX; // 画像の中心座標
        this->CenterPosY = CenterPosY; // 画像の中心座標
        this->Rate = Rate; // 画像の拡大比率
        this->Angle = Angle; // 画像の回転角度
        texHandle = LoadGraph(texture.c_str());
        FlashPattern = FlashAlphaPattern;
    }


    bool Update(int Counter) {
        SetDrawBlendMode(DX_BLENDMODE_ALPHA, FlashPattern[(Counter / Interval) % FlashPattern.size()]);
        DrawRotaGraph(CenterPosX, CenterPosY, Rate, Angle, texHandle, true);
        if (Time == 0) return false; // Time == 0のとき、常に実行
        return Counter >= Time;
    }

};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    if (DxLib_Init() == -1)
        return -1;
    ChangeWindowMode(TRUE);
    SetDrawScreen(DX_SCREEN_BACK);

    Effect effect;
    TextureFlash flash(320, 240, "Grapic.png", 0, 60, { 255, 0 }); // 透明度を0%と100%で切り替える

    effect.Add([&](int t) { return flash.Update(t); });

    while (ScreenFlip() == 0 && ClearDrawScreen() == 0 && ProcessMessage() == 0) {
        effect.Update();
    }

    DxLib_End();

    return 0;
}

これで画像が、画面の中央で一定間隔で無限に点滅してるかと思います。

実行結果

GIFのいい感じの取り方わからなかったんでGyazoに上げてます。
サンプル映像

最後に

今回は画像の点滅を実装しましたが、フェードイン/アウトや、一定時間ごとに画像の切り替えでアニメーションを実装できたりと、できることの幅は広いです。様々なエフェクトやアニメーションを実装してみてください。


最後にC#版も載せておきます

using System.Collections.Generic;
using System;

namespace DxLibEffect
    public class EffectAdmin
    {
        private List<Func<int, bool>>  EffectList = new List<Func<int, bool>>();
        private List<bool> Valid = new List<bool>();
        private List<bool> Finished = new List<bool>();
        public uint EffectCount { get { return (uint)EffectList.Count; } }

        private List<int> Counter = new List<int>();

        public uint Fire(Func<int, bool> UpdateFunc)
        {
            EffectList.Add(UpdateFunc);
            Counter.Add(0);
            Valid.Add(true);
            Finished.Add(false);
            return (uint)EffectList.Count - 1;
        }

        public void Pause(uint index)
        {
            if (index >= EffectCount || Finished[(int)index]) return;
            Valid[(int)index] = false;
        }

        public void Resume(uint index)
        {
            if (index >= EffectCount || Finished[(int)index]) return;
            Valid[(int)index] = true;
        }

        public void Stop(uint index)
        {
            if (index >= EffectCount || Finished[(int)index]) return;
            Valid[(int)index] = false;
            Finished[(int)index] = true;
        }

        public void Restart(uint index)
        {
            Counter[(int)index]= 0;
            Valid[(int)index] = true;
            Finished[(int)index] = false;
        }

        public void Update()
        {
            if(EffectList == null)
            {
                return;
            }
            for(var i = 0; EffectList.Count > i; i++)
            {
                if (!Valid[i])
                {
                    continue;
                }
                bool r = EffectList[i](Counter[i]);
                Counter[i]++;
                if (r)
                {
                    Finished[i] = true;
                    Valid[i] = false;
                }
            }
        }
    }
}

正直C#が使いやすくてですね、それでしばらくC++を触ってなかったわけです。

参考

std::functionでのメンバ関数キャプチャの罠
Siv3d エフェクト

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?