Visual Studio メモリリークを探す

やあ

間違ってたりしたら教えていただけると幸いです。

環境

Visual Studio 2022 Community

やる

まずメモリリークを起こす

#include <chrono>
#include <thread>

int main()
{
    while (true)
    {
        int* p = new int(5);
        std::this_thread::sleep_for(std::chrono::milliseconds(30));
    }
    return 0;
}

デバッグを開始する

F5を押すかメニューからデバッグを開始

プロセスメモリが徐々に増加しているのがわかる。

何が原因でメモリリークを起こしているのか調べる

まずスナップショットを有効にする

ループ内やdelete, resetなどの後にブレークポイントを設定する

スナップショットを作成


続行する

次のブレークポイントに差し掛かったところでもう一度スナップショットを作成

なんか知らんけど増えてるのがわかる

数字のところをそれぞれクリック

今回は単純にintの割り当てが増えているのがわかる。

クリックする

こんなかんじで割り当てられた場所がわかる

ちゃんと解放するようにする

#include <chrono>
#include <thread>

int main()
{
    while (true)
    {
        int* p = new int(5);
        std::this_thread::sleep_for(std::chrono::milliseconds(30));
        delete p;
    }
    return 0;
}

おしまい。

Visual Studio exeファイル

やあ

メモ。
間違ってたりしたら教えていただけると幸いです。

環境

Windows10
Visual Studio Community 2022 17.6.5

exeファイルが出力される場所

ソリューションを右クリック

出てきたウィンドウの一番下にあるプロパティをクリック

出力ディレクトリで書いてある場所に出力される

デフォルトだと.slnがあるフォルダの


プラットフォーム : x64 x86

コンフィギュレーション : DebugもしくはRelease

ここに出力される

exeファイルがエラーで開けない!

こんなのが出てきてエラーの原因もわかんない!

そんなときは

空のプロジェクトを用意する。
用意出来たら
ファイル→開く→プロジェクト/ソリューション

エラーの起こっている.exeファイルを開く

F5デバッグを開始してエラーを探す
基本的にエラーはコンパイルデバッグ時点で見つかるけどね。

コンパイルは通るのにexeファイルが実行できない

そんなときは、依存関係のファイル(.pngとか.jsonとか)が読み込めてないことが多いと思う。

こんな感じでソースコードから.jsonファイルを読み込もうとしてるのにそれが見つからない!とか。

それを解決するには.exeのあるディレクトリに、依存関係のファイルを突っ込む

C++ UnrealEngineのDelegateを作ってみた

やあ

DirectXでゲームを作ってるときに、UnrealEngineならデリゲートがあるのになーって思う場面があったので、作ってみた。
間違えているところや、こうしたほうがいいよなどあれば教えていただけると幸いです。

デリゲートとは

AはBを知っていて、BはAを知らない時にBで発生したイベントを、Aに伝えられるようにするもの。

あると何がうれしいのか?

・AとBを分離できる
デリゲートなしでAにBで発生したイベントを伝えようとすると、B.cppにA.hをインクルードしてAの処理を呼び出すことになる。
デリゲートを使うと、A.cppでB.hをインクルードするだけで済む。

・Bから不特定多数にイベントを伝えることができる。

要件?

・グローバル関数の保存
ラムダ式の保存
 - lvalueとrvalueそれぞれの保存に対応する
メンバ関数の保存
 - オブジェクトの保存

実装環境

Windows 10
Visual Studio 2022
・ISO C++ 20 標準

実装する

関数ポインタ : 手動

main関数の中で関数ポインタを宣言し、関数のアドレスを保存して呼び出してみる

▼見たい人だけ

グローバル関数

main.cpp

#include <iostream>

using namespace std;

void GlobalFunc(std::string tag)
{
    cout << "Hello " << tag << " from global function" << endl;
}

int main()
{
    // 関数ポインタの宣言

    void (*ptr)(std::string);
    // typedefを使った書き方
    typedef void(*tdFuncPtr)(std::string);
    // usingを使った書き方 ← 個人的にこれが直観的にわかりやすい
    using uFuncPtr = void(*)(std::string);

    // 参照する
    ptr = GlobalFunc;
    tdFuncPtr tdfn = GlobalFunc;
    uFuncPtr fn = GlobalFunc;

    // 呼び出す
    ptr("local");
    tdfn("typedef");
    fn("using");
    std::invoke(ptr, "invoke");

    return 0;
}

実行結果

Hello local from global function
Hello typedef from global function
Hello using from global function
Hello invoke from global function

こんな感じで関数のアドレスをポインタに保存できる!

ラムダ式

main.cpp

int main()
{
    // 関数ポインタの宣言

    void (*ptr)(std::string);
    // typedefを使った書き方
    typedef void(*tdFuncPtr)(std::string);
    // usingを使った書き方 ← 個人的にこれが直観的にわかりやすい
    using uFuncPtr = void(*)(std::string);

    auto lambda = [](std::string tag)
    {
        cout << "Hello " << tag << " from lambda function!" << endl;
    };

    // 参照する
    ptr = lambda;
    tdFuncPtr tdptr = lambda;
    uFuncPtr uptr = lambda;

    // 呼び出す
    ptr("local");
    tdptr("typedef");
    uptr("using");
    std::invoke(ptr, "invoke");

    return 0;
}

実行結果

Hello local from lambda function!
Hello typedef from lambda function!
Hello using from lambda function!
Hello invoke from lambda function!

メンバ関数

main.cpp

class TestClass
{
public:
    void ClassMemberFunction(std::string tag)
    {
        cout << "Hello " << tag << " from class member function" << endl;
    }
};

int main()
{
    // 関数ポインタの宣言
    void(TestClass:: * memberPtr)(std::string);
    // typedef
    typedef void(TestClass::* tdMemberPtr)(std::string);
    // using
    using uMemberPtr = void(TestClass::*)(std::string);

    // メンバ関数の参照
    memberPtr = &TestClass::ClassMemberFunction;
    tdMemberPtr tdmPtr = &TestClass::ClassMemberFunction;
    uMemberPtr umPtr = &TestClass::ClassMemberFunction;

    // 呼び出す
    // 呼び出し方.1 クラスへのポインタ
    TestClass* pTestClass = new TestClass();
    (*pTestClass.*memberPtr)("local");
    (*pTestClass.*tdmPtr)("typedef");
    (*pTestClass.*umPtr)("using");
    std::invoke(umPtr, *pTestClass, "invoke");

    // 呼び出し方.2 クラス
    TestClass testClass = TestClass();
    (testClass.*memberPtr)("local2");
    (testClass.*tdmPtr)("typedef2");
    (testClass.*umPtr)("using2");
    std::invoke(umPtr, testClass, "invoke2");

    return 0;
}

実行結果

Hello local from class member function
Hello typedef from class member function
Hello using from class member function
Hello invoke from class member function
Hello local2 from class member function
Hello typedef2 from class member function
Hello using2 from class member function
Hello invoke2 from class member function

めんどくさい!!

関数ポインタ : 自動?

templateを使って、戻り値も引数もわからない状態で関数ポインタを宣言する!

Delegate.h

// 特殊化のために必要
template<typename T>
class Delegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
private:
    using Function = Ret(*)(Args...);

    /* 関数ポインタ */
    Function mFunction;
};

これで戻り値も引数もわからない関数ポインタmFunctionができた!

このDelegateクラスで関数のアドレスを保存したり、呼び出したりできるようにする。

関数のアドレスを保存したり呼び出したりする

グローバル関数

Delegate.h

class DelegateNotBound : public std::exception
{
public:
    const char* what() const noexcept override
    {
        return "Delegate is not bound";
    }
};

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    template<auto Function>
    void Bind()
    {
        /* グローバル関数をラムダ式で保存する! */
        mFunction = [](Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    explicit operator bool() const { return mFunction; }

    template<typename ...Args>
    Ret Broadcast(Args&&... args)
    {
        if (!*this)
        {
            throw DelegateNotBound();
        }
        /* 関数ポインタに値(args)を渡して呼び出す! */
        return mFunction(std::forward<Args>(args)...);
    }

    bool IsBound() const
    {
        return mFunction;
    }
private:
    using Function = Ret(*)(Args...);

    /* 関数ポインタ */
    Function mFunction;
};

実際にDelegateクラスを使ってみる!
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

void GlobalFunc(std::string tag)
{
    cout << "Hello " << tag << " from global function" << endl;
}

int main()
{
    // Delegateクラスの宣言
    Delegate<void(std::string)> ptr;

    // 参照
    ptr.Bind<GlobalFunc>();

    // 呼び出す
    ptr.Broadcast("pto8193");

    return 0;
}

実行結果

Hello pto8193 from global function

ラムダ式

ラムダ式を保存する。
しかし、単純にグローバル関数のように保存してしまうと、
Delegateラムダ式を呼び出す前にラムダ式の寿命が来る可能性がある。

そのためラムダ式自体を保存する必要がある。

様々なタイプのポインタを保存してみる

▼見たい人だけ

まずはオブジェクト
main.cpp

#include <iostream>

using namespace std;

class TestClass
{
public:
    void Show()
    {
        cout << "Hello my ID is " << id << endl;
    }
    void SetID(int inID)
    {
        id = inID;
    }
private:
    int id;
};

int main()
{
    // void* : 8バイトのメモリを確保(システムによって変わります、64bitなので8バイトです。)
    std::byte mData[sizeof(void*)];
    
    TestClass* testClass = new TestClass();
    testClass->SetID(10);

    // mDataのアドレスにtestClassをコピーする
    new (&mData) TestClass(*testClass);

    // mDataはアドレスなのでcastして使える形にする
    TestClass* mDataTestClass = reinterpret_cast<TestClass*>(mData);

    mDataTestClass->Show();
    
    return 0;
}

実行結果

Hello my ID is 10

templateで書いてみる
main.cpp

using Storage = std::byte(*)[sizeof(void*)];

template<typename Type>
Type* ConvertByteToType(Storage mData, Type& instance)
{
    new (mData) Type(instance);

    return reinterpret_cast<Type*>(mData);
}

int main()
{
    std::byte mData[sizeof(void*)];
    
    TestClass* testClass = new TestClass();
    testClass->SetID(10);

    TestClass* mDataTestClass = ConvertByteToType<TestClass>(&mData, *testClass);
    mDataTestClass->Show();
    
    return 0;
}

これで、
オブジェクトのポインタをmDataに保存。
mDataからオブジェクトを復元。
オブジェクトの実行ができるようになった。

次に、ラムダ式をmDataに保存してみる。

int main()
{
    std::byte mData[sizeof(void*)];

    auto aLambda = [](std::string tag)
    {
        cout << "Hello " << tag << " from lambda" << endl;
    };

    auto lambda = ConvertByteToLambda(&mData, aLambda);
    // 関数ポインタなので呼び出し方が変わる
    std::invoke(*lambda, "pto8913");
    (*lambda)("pto8913");

    return 0;
}

様々なタイプのポインタを保存の仕方が分かった!(本当に?)

ラムダ式

ラムダ式Delegate.h内に保存できるようにするため、様々なタイプのポインタを保存してみるで作ったmDataをDelegate.hに統合する。
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    template<auto Function>
    void Bind()
    {
        new (&mData) std::nullptr_t(nullptr);

        /* グローバル関数をラムダ式で保存する! */
        mFunction = [](Storage, Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    template<typename ...Args>
    Ret Broadcast(Args&& ...args)
    {
        /* 関数ポインタに値(args)を渡して呼び出す! */
        return mFunction(&mData, std::forward<Args>(args)...);
    }

private:
    using Storage = std::byte (*)[sizeof(void*)];
    using Function = Ret(*)(Storage, Args...);

    /* ラムダ式などを保存する */
    alignas(void*) std::byte mData[sizeof(void*)];

    /* 関数ポインタ */
    Function mFunction;
};

ラムダ式を保存するときに考えなきゃいけないのが、lvalueとrvalue
例 :

// lvalue
auto lambda = [](){};
hogeDelegate.Bind(lambda);

// rvalue
hogeDelegate.Bind([](){});

この二つに対応するBind関数を作る
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    // 省略

    template<typename Type>
    void Bind(Type& funcPtr)
    {
        cout << "lvalue function" << endl;

        // lvalueのラムダ式を保存する
        new (&mData) Type* (&funcPtr);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        cout << "rvalue function" << endl;

        // rvalueのラムダ式を保存する
        new (&mData) Type(std::move(funcPtr));

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = reinterpret_cast<Type*>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }
};

実際に使ってみる!
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

int main()
{
    Delegate<void(std::string)> lvalueLambda;
    auto aLambda = [](std::string tag)
    {
        cout << "lambda : " << tag << endl;
    };
    lvalueLambda.Bind(aLambda);
    lvalueLambda.Broadcast("pto8913 lvalue ");

    Delegate<void(std::string)> rvalueLambda;
    rvalueLambda.Bind(
        [](std::string tag)
        {
            cout << "lambda : " << tag << endl;
        }
    );
    rvalueLambda.Broadcast("pto8913 rvalue ");

    return 0;
}

実行結果

lvalue function
lambda : pto8913 lvalue
rvalue function
lambda : pto8913 rvalue

できた!

メンバ関数

メンバ関数を保存するBind関数を作ってみる
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    template<auto Function, typename Type>
    void Bind(Type& instance)
    {
        cout << "member function" << endl;
        new (&mData) Type* (&instance);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(Function, *instance, std::forward<Args>(args)...);
        };
    };
};

実際に使ってみる。
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class TestClass
{
public:
    void Show()
    {
        cout << "Hello my ID is " << id << endl;
    }
    void SetID(int inID)
    {
        id = inID;
    }
private:
    int id;
};

int main()
{
    TestClass* testClass = new TestClass();
    testClass->SetID(99);

    Delegate<void()> memberFunction;
    memberFunction.Bind<&TestClass::Show>(*testClass);
    memberFunction.Broadcast();

    return 0;
}

実行結果

member function
Hello my ID is 99

できた!・・・と言いたいところだけど、このままだとなんでもBindできちゃう。
例 :

    // コンパイルエラー
    Delegate<void(std::string)> invalidMemberFunction;
    invalidMemberFunction.Bind<&GlobalFunc>(*testClass);
    invalidMemberFunction.Broadcast("pto8913");

こういったことを回避するために事前にチェックできるようにする。
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    // 省略
    
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>>>
    void Bind(Type& instance)
    {
        // 省略
    };
};

templateにstd::is_member_function_pointerを追加するだけです。

typename = typename std::enable_if_t<std::is_member_function_ponter_v<decltype(Function)>>

これでメンバ関数以外の関数に対してエラーが出るようになりました。

しかし、メンバ関数ならば戻り値や引数、クラスに関係なく保存できるようになっているので、これも排除します。
Delegate.h

    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    // 省略

std::is_invocable_r_vを追加することで、保存しようとしている関数の戻り値や引数、クラスの型を解析してエラーを発してくれます。

std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>

グローバル関数のtemplateについても処理を追加する。
Delegate.h

    // 省略
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    // 省略

ラムダ式については調べようがないので、そのままです。

エラー書き出してみた。
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class TestClass
{
public:
    void StrFunction(std::string tag)
    {
        cout << "Hello " << tag << " from class member function" << endl;
    }
    void VoidFunction()
    {
        cout << "Void Function" << endl;
    };
};

void GlobalFunc() {
    cout << "Global Function" << endl;
};

int main()
{
    Delegate<void()> globalFunction;
    globalFunction.Bind<GlobalFunc>();
    globalFunction.Broadcast();

    TestClass* testClass = new TestClass();

    Delegate<void()> memberFunction;
    memberFunction.Bind<&TestClass::VoidFunction>(*testClass);

    // 引数が正しくないのでエラー
    //memberFunction.Bind<&TestClass::StrFunction>(*testClass);

    // クラスが違うのでエラー
    //TestClass2* testClass2 = new TestClass2();
    //memberFunction.Bind<&TestClass::VoidFunction>(*testClass2);

    auto aLambda = []()
    {
        cout << "lvalue lambda" << endl;
    };

    Delegate<void()> lvalueLambdaFunction;
    lvalueLambdaFunction.Bind(aLambda);
    lvalueLambdaFunction.Broadcast();

    Delegate<void()> rvalueLambdaFunction;
    rvalueLambdaFunction.Bind(
        []()
        {
            cout << "rvalue lambda" << endl;
        }
    );
    rvalueLambdaFunction.Broadcast();

    // 引数が正しくないのでコンパイルエラー
    //rvalueLambdaFunction.Bind(globalFunction);

    return 0;
}

ここまでの実装

Delegate.h

#pragma once

class DelegateNotBound : public std::exception
{
public:
    const char* what() const noexcept override
    {
        return "Delegate is not bound";
    }
};

template<typename T>
class Delegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    {
        new (&mData) std::nullptr_t(nullptr);

        mFunction = [](Storage, Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    {
        new (&mData) Type* (&instance);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(Function, *instance, std::forward<Args>(args)...);
        };
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr)
    {
        new (&mData) Type* (&funcPtr);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        new (&mData) Type(std::move(funcPtr));

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = reinterpret_cast<Type*>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    explicit operator bool() const { return mFunction; }

    template<typename ...Args>
    Ret Broadcast(Args&& ...args)
    {
        if (!*this)
        {
            throw DelegateNotBound();
        }
        return mFunction(&mData, std::forward<Args>(args)...);
    }

private:
    using Storage = std::byte (*)[sizeof(void*)];
    using Function = Ret(*)(Storage, Args...);

    alignas(void*) std::byte mData[sizeof(void*)];
    Function mFunction;
};

複数の関数を保存できるようにする

Delegate.h

#include <vector>

// 省略

template<typename T>
class MulticastDelegate;

template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>();
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>(instance);
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
    }
    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
    }

    explicit operator bool() const { return !mDelegates.empty(); }

    template<typename ...Args>
    void Broadcast(Args&& ...args)
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.Broadcast(std::forward<Args>(args)...);
        }
    }

    bool IsBound() const
    {
        return mDelegates.size() > 0;
    }
private:
    using DelegateType = Delegate<Ret(Args...)>;

    std::vector<DelegateType> mDelegates;
};

実際に使ってみる
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class InventoryComponent
{
public:
    void AddItem(int inItem)
    {
        item += inItem;
        OnItemAdded.Broadcast(item);
    }

    MulticastDelegate<void(int)> OnItemAdded;
private:
    int item;
};

void ItemAdded(int inItem)
{
    cout << "Item Added : " << inItem << endl;
}

class InventoryUI
{
public:
    void UpdateInventoryUI(int inItem)
    {
        item = inItem;
        cout << "InventoryUI Updated : " << item << endl;
    }
private:
    int item;
};

int main()
{
    InventoryComponent* inventoryComp = new InventoryComponent();

    inventoryComp->OnItemAdded.Bind<ItemAdded>();

    InventoryUI* inventoryUI = new InventoryUI();
    inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI);

    inventoryComp->AddItem(10);

    return 0;
}

実行結果

Item Added : 10
InventoryUI Updated : 10

デリゲートの宣言をマクロにする

Delegate.h

#define DECLARE_DELEGATE(DelegateName) typedef Delegate<void()> DelegateName;
#define DECLARE_DELEGATE_OneParam(DelegateName, val0) typedef Delegate<void(val0)> DelegateName;
#define DECLARE_DELEGATE_TwoParams(DelegateName, val0, val1) typedef Delegate<void(val0, val1)> DelegateName;

#define DECLARE_DELEGATE_RET(DelegateName, ret) typedef Delegate<ret()> DelegateName;
#define DECLARE_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef Delegate<ret(val0)> DelegateName;
#define DECLARE_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef Delegate<ret(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE(DelegateName) typedef MulticastDelegate<void()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_OneParam(DelegateName, val0) typedef MulticastDelegate<void(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_TwoParams(DelegateName, val0, val1) typedef MulticastDelegate<void(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE_RET(DelegateName, ret) typedef MulticastDelegate<ret()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef MulticastDelegate<ret(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef MulticastDelegate<ret(val0, val1)> DelegateName;

使ってみる
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

DECLARE_MULTICAST_DELEGATE_OneParam(FOnItemCountChanged, int)

class InventoryComponent
{
public:
    void AddItem(int inItem)
    {
        item += inItem;
        OnItemAdded.Broadcast(item);
    }

    void RemoveItem(int count)
    {
        item -= count;
        OnItemRemoved.Broadcast(item);
    }

    FOnItemCountChanged OnItemAdded;
    FOnItemCountChanged OnItemRemoved;
private:
    int item;
};

// 省略

・マクロでFOnItemCountChangedを宣言することで、FOnItemCountChangedを複数の変数で使えるようになる。
・後から引数などを変更したくなったら、マクロの部分を変えるだけなのでとても楽。

削除(忘れてた)

うまいこと消す方法を思いつかなかったのでMulticastDelegateにタグを持たせる。
Delegate.h

template<typename T>
class Delegate;

template<typename T>
class MulticastDelegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    // 省略

    // 削除
    void ClearBind()
    {
        new (&mData) std::nullptr_t(nullptr);
        mFunction = nullptr;
    }
};

template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind(std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>();
        mFunctionTags.push_back(tag);
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>(instance);
        mFunctionTags.push_back(tag);
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    void Unbind(std::string tag)
    {
        cout << "TRY Unbind function : " << tag << endl;

        int i = 0;
        for (auto& _delegate : mDelegates)
        {
            if (mFunctionTags[i] == tag)
            {
                _delegate.ClearBind();

                mFunctionTags.erase(mFunctionTags.begin() + i);
                mDelegates.erase(mDelegates.begin() + i);

                cout << "Unbind lambda function : " << tag << endl << endl;
                break;
            }
            ++i;
        }
    }

    void ClearBind()
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.ClearBind();
        }
        mDelegates.clear();
        mFunctionTags.clear();
    }
    // 省略
private:
    // 省略

    std::vector<std::string> mFunctionTags;
};

使ってみる
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class InventoryComponent
{
public:
    void AddItem(int inItem)
    {
        item += inItem;
        OnItemAdded.Broadcast(item);
    }

    MulticastDelegate<void(int)> OnItemAdded;
private:
    int item;
};

void ItemAdded(int inItem)
{
    cout << "Item Added : " << inItem << endl;
}

class InventoryUI
{
public:
    void UpdateInventoryUI(int inItem)
    {
        item = inItem;
        cout << "InventoryUI Updated : " << item << endl;

    }
private:
    int item;
};

int main()
{
    InventoryComponent* inventoryComp = new InventoryComponent();

    inventoryComp->OnItemAdded.Bind<ItemAdded>("ItemAdded");

    InventoryUI* inventoryUI = new InventoryUI();
    inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI, "UpdateInventoryUI");

    InventoryUI* inventoryUI2 = new InventoryUI();
    inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI2, "UpdateInventoryUI2");

    inventoryComp->AddItem(10);

    inventoryComp->OnItemAdded.Unbind("ItemAdded");

    inventoryComp->OnItemAdded.Unbind("UpdateInventoryUI");

    inventoryComp->AddItem(10);

    inventoryComp->OnItemAdded.Bind(
        [](int i)
        {
            cout << "item adddeeeed to rvalue lambda" << endl << endl;
        },
        "OnItemAdded"
    );
    inventoryComp->OnItemAdded.Unbind("OnItemAdded");

    inventoryComp->AddItem(10);

    return 0;
}

実行結果

Item Added : 10
InventoryUI Updated : 10
InventoryUI Updated : 10
TRY Unbind function : ItemAdded
Unbind lambda function : ItemAdded

TRY Unbind function : UpdateInventoryUI
Unbind lambda function : UpdateInventoryUI

InventoryUI Updated : 20
TRY Unbind function : OnItemAdded
Unbind lambda function : OnItemAdded

InventoryUI Updated : 30

タグ付けるのめんどくさい・・・
何かいい方法あれば教えてください。

全体

Delegate.h

#pragma once

#include <vector>

#define DECLARE_DELEGATE(DelegateName) typedef Delegate<void()> DelegateName;
#define DECLARE_DELEGATE_OneParam(DelegateName, val0) typedef Delegate<void(val0)> DelegateName;
#define DECLARE_DELEGATE_TwoParams(DelegateName, val0, val1) typedef Delegate<void(val0, val1)> DelegateName;

#define DECLARE_DELEGATE_RET(DelegateName, ret) typedef Delegate<ret()> DelegateName;
#define DECLARE_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef Delegate<ret(val0)> DelegateName;
#define DECLARE_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef Delegate<ret(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE(DelegateName) typedef MulticastDelegate<void()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_OneParam(DelegateName, val0) typedef MulticastDelegate<void(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_TwoParams(DelegateName, val0, val1) typedef MulticastDelegate<void(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE_RET(DelegateName, ret) typedef MulticastDelegate<ret()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef MulticastDelegate<ret(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef MulticastDelegate<ret(val0, val1)> DelegateName;

class DelegateNotBound : public std::exception
{
public:
    const char* what() const noexcept override
    {
        return "Delegate is not bound";
    }
};

template<typename T>
class Delegate;

template<typename T>
class MulticastDelegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    friend class MulticastDelegate<Ret(Args...)>;
public:
    Delegate()
    {
        ClearBind();
    }
    virtual ~Delegate()
    {
        ClearBind();
    }
    
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    {
        new (&mData) std::nullptr_t(nullptr);

        /* 関数をラムダ式で保存する! */
        mFunction = [](Storage, Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    {
        new (&mData) Type* (&instance);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(Function, *instance, std::forward<Args>(args)...);
        };
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr)
    {
        new (&mData) Type* (&funcPtr);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        new (&mData) Type(std::move(funcPtr));

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = reinterpret_cast<Type*>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    // 削除
    void ClearBind()
    {
        new (&mData) std::nullptr_t(nullptr);
        mFunction = nullptr;
    }

    explicit operator bool() const { return mFunction; }

    template<typename ...Args>
    Ret Broadcast(Args&& ...args)
    {
        if (!*this)
        {
            throw DelegateNotBound();
        }
        /* 関数ポインタに値(args)を渡して呼び出す! */
        return mFunction(&mData, std::forward<Args>(args)...);
    }

    bool IsBound() const
    {
        return mFunction;
    }
private:
    using Storage = std::byte (*)[sizeof(void*)];
    using Function = Ret(*)(Storage, Args...);

    /* ラムダ式などを保存する */
    alignas(void*) std::byte mData[sizeof(void*)];

    /* 関数ポインタ */
    Function mFunction;
};

template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind(std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>();
        mFunctionTags.push_back(tag);
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>(instance);
        mFunctionTags.push_back(tag);
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    void Unbind(std::string tag)
    {
        int i = 0;
        for (auto& _delegate : mDelegates)
        {
            if (mFunctionTags[i] == tag)
            {
                _delegate.ClearBind();

                mFunctionTags.erase(mFunctionTags.begin() + i);
                mDelegates.erase(mDelegates.begin() + i);
                break;
            }
            ++i;
        }
    }

    void ClearBind()
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.ClearBind();
        }
        mDelegates.clear();
        mFunctionTags.clear();
    }

    explicit operator bool() const { return !mDelegates.empty(); }

    template<typename ...Args>
    void Broadcast(Args&& ...args)
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.Broadcast(std::forward<Args>(args)...);
        }
    }

    bool IsBound() const
    {
        return mDelegates.size() > 0;
    }
private:
    using DelegateType = Delegate<Ret(Args...)>;

    std::vector<DelegateType> mDelegates;
    std::vector<std::string> mFunctionTags;
};

間違えているところや、こうしたほうがいいよなどあれば教えていただけると幸いです。

UE5 MetaSound AttenuationSettingsを設定したら音が消えた

やあ

MetaSoundを触り始めて、Attenuation SettingsMetaSoundに距離減衰をかけようと思ったら、何も聞こえなくなっちゃった。
そんなときに見るメモ

これ!

MetaSoundのアセットに最初から実装されてるUE.Source.OneShotインターフェースが原因!
このインターフェースを消せば聞こえるようになるよ!

UE5 python Google SpreadSheetからCSVを自動でインポートする

やあ

前回の続きだよ。
pto8913.hatenablog.com

前回はGoogle SpreadSheetから、pythonを介してデータを取ってこられるようになった。

今回はUnreal Engineのエディタでボタンを押すだけで、Google SpreadSheetからシートをcsvでダウンロードして、そのcsvと一致するDataTableを再インポートできるようにするよ。

前提条件

DataTableの名前 = "Google SpreadSheetのタイトル"_-_"シートの名前"
の形になっていること
DataTableの構造とGoogle SpreadSheetの構造が一致していること

pythonからGoogle SpreadSheetをcsvでダウンロードする

サービスアカウントのメールアドレスに共有したGoogle SpreadSheetからすべてのWorkSheetをダウンロードしてみる。

import json
import gspread
import csv
import os

ROOT_DIR = os.path.abspath(os.curdir) + "/"
SCRIPT_DIR = f'{ROOT_DIR}Scripts/'

# 前回作ったサービスアカウントのjsonファイル!
gc = gspread.service_account(f'{SCRIPT_DIR}service_account.json')

# サービスアカウントのメールアドレスに共有したSpreadSheetの一覧を取得
for row in gc.openall():
    spread_sheet = gc.open(row.title)
    for worksheet in spread_sheet.worksheets():
      filename = f'{spread_sheet.title}_-_{work_sheet.title}.csv'
      with open(f'{ダウンロードする場所}/{filename}', 'w', encoding='utf-8') as f:
          writer = csv.writer(f)
          writer.writerows(work_sheet.get_all_values())

動かすたびに全部ダウンロードするのはうれしくない!
なので特定のファイルをダウンロードする

特定のGoogle SpreadSheetから特定のWorkSheetをcsvでダウンロード

SpreadSheetのタイトルでSpreadSheetを指定 タイトルはこの部分!

spread_sheet = gc.open(SpreadSheetのタイトル)

もしくは

# https://docs.google.com/spreadsheets/d/{この部分がSpreadSheetのId}/edit#gid={この部分がWorkSheetのId}
spread_sheet = gc.open_by_key(SpreadSheetのId)
work_sheet = spreadsheet.get_worksheet_by_id(WorkSheetのId)

これでさっきみたいにcsv.writerでダウンロードする

いちいちタイトルやIdなんかを調べるのは大変だ・・・
なので次はタイトルやIdをjsonに保存して、EditorUtilityWidgetを使ってjsonをエディタでみられるようにする!

タイトルとIdのリストを作る!

jsonに保存する

こんな構造でjsonに書き込む

{
    spreadsheet1:
    {
        worksheet1: id,
        worksheet2: id
    },
    spreadsheet2:
    {
        worksheet1: id
    }
}
def RecreateSheetJson():
    titles = {}
    for row in gc.openall():
        sheets = {}
        spread_sheet = gc.open(row.title)
        for work_sheet in spread_sheet.worksheets():
            sheets[work_sheet.title] = work_sheet.id
        titles[row.title] = sheets
    with open(f'{SCRIPT_DIR}sheet.json', 'w', encoding='utf-8') as f:
        json.dump(titles, f, indent=4)

Unreal Enginepythonjsonを使えるようにする

エディタの設定からpython Developer Modeを有効にする


PluginsからJson Blueprint Utilitiesを有効にする


エディタを再起動。

Unreal Enginepythonからgspreadを呼べるようにする

Ctrl+Rcmdコマンドプロンプトを開きます
開いたらUnreal Enginepythonpip install gspreadします

"C:\Program Files\Epic Games\UE_5.0\Engine\Binaries\ThirdParty\Python3\Win64\python.exe" -m pip install gspread

EditorUtilityWidgetでjsonのリストを表示する

Content Browserを右クリックしてEditorUtilityWidgetを作成
名前はEUW_SpreadSheet_Importにしたよ

ボタンを押してpythonの関数を呼んでみる

いい感じにボタンを配置します

ボタンのOnClickedイベントを実装する

pythonのファイルから関数を呼び出す!

sheet_to_json.py

import json
import unreal
import gspread

ROOT_DIR = unreal.Paths.project_dir()

SCRIPT_DIR = f'{ROOT_DIR}Scripts/'
SHEET_JSON = f'{SCRIPT_DIR}sheet.json'

gc = gspread.service_account(f'{SCRIPT_DIR}service_account.json')

def RecreateSheetJson():
    titles = {}
    for row in gc.openall():
        sheets = {}
        SpreadSheet = gc.open(row.title)
        for WorkSheet in SpreadSheet.worksheets():
            sheets[WorkSheet.title] = WorkSheet.id
        titles[row.title] = sheets
        
    with open(SHEET_JSON, 'w', encoding='utf-8') as f:
        json.dump(titles, f, indent=4)

実行するとProjectName/Scriptsの下にsheet.jsonができるはずだよ

リストを表示するためのUIを作る

Content Browserを右クリックしてWidget Blueprintを作る
名前はWBP_SpreadSheet_WorkSheet_List

SpreadSheetのタイトルをクリックしたらWorkSheetのリストが表示できるようにする

WorkSheetを表示するためのスロットUIを作る
名前はWBP_SpreadSheet_WorkSheet_Slot


WBP_SpreadSheet_WorkSheet_Listに実装


ボタンを押したらSpreadSheetのリストが表示されるようにする

EUW_SpreadSheet_ImportWBP_SpreadSheet_WorkSheet_Listを表示するためにちょっと変更

EUW_SpreadSheet_Importに実装


これでボタンを押したら、サービスアカウントのメールアドレスに共有したGoogle SpreadSheetが表示されるはずだよ

リストをクリックしたらSpreadSheetとWorkSheetの名前を指定できるようにする

WBP_SpreadSheet_WorkSheet_SlotOnClickedイベントを作成

WBP_SpreadSheet_WorkSheet_ListOnClicked イベントを作成

WBP_SpreadSheet_WorkSheet_ListOnExpansionChanged イベントを作成

WBP_SpreadSheet_WorkSheet_Slotのクリックイベントにバインド

EUW_SpreadSheet_Importをちょっと変更

EUW_SpreadSheet_Importにイベントを追加

WBP_SpreadSheet_WorkSheet_Listのイベントをバインド

これでボタンを押したら選択できるようになったはずだよ。

ボタンを押して再インポートできるようにする

sheet_to_json.py

import json
import unreal
import gspread
import csv
import os

ROOT_DIR = unreal.Paths.project_dir()

SCRIPT_DIR = f'{ROOT_DIR}Scripts/'
SHEET_JSON = f'{SCRIPT_DIR}sheet.json'

DATATABLE_DIR = f'{ROOT_DIR}DataTableSource/'
if not os.path.exists(DATATABLE_DIR):
    os.makedirs(DATATABLE_DIR, exist_ok=True)

gc = gspread.service_account(f'{SCRIPT_DIR}service_account.json')

def RecreateSheetJson():
    titles = {}
    for row in gc.openall():
        sheets = {}
        SpreadSheet = gc.open(row.title)
        for WorkSheet in SpreadSheet.worksheets():
            sheets[WorkSheet.title] = WorkSheet.id
        titles[row.title] = sheets
        
    with open(SHEET_JSON, 'w', encoding='utf-8') as f:
        json.dump(titles, f, indent=4)

def GetFileName(SpreadSheetTitle: str, WorkSheetTitle: str) -> str:
    return f'{SpreadSheetTitle}_-_{WorkSheetTitle}'

def GetDownloadName(DownloadDir: str, SpreadSheetTitle: str, WorkSheetTitle: str) -> str:
    return f'{DownloadDir}{GetFileName(SpreadSheetTitle,WorkSheetTitle)}.csv'

def DownloadWorkSheet(DownloadDir: str, SpreadSheetTitle: str, WorkSheetTitle: str) -> bool:
    with open(SHEET_JSON, 'r', encoding='utf-8') as f:
        Sheet_Json = json.load(f)
    if Sheet_Json[SpreadSheetTitle]:
        SpreadSheet = gc.open(SpreadSheetTitle)
        if not SpreadSheet:
            return False

        WorkSheet_Id = Sheet_Json[SpreadSheetTitle][WorkSheetTitle]
        WorkSheet = SpreadSheet.get_worksheet_by_id(WorkSheet_Id)
        if not WorkSheet:
            return False
        
        DOWNLOAD_NAME = GetDownloadName(DownloadDir, SpreadSheetTitle, WorkSheetTitle)
        with open(DOWNLOAD_NAME, 'w', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            writer.writerows(WorkSheet.get_all_values())
            return True
    return False


def ReimportWorkSheet(EditorDataTableDir: str, SpreadSheetTitle: str, WorkSheetTitle: str, IsReDownload: bool = True) -> bool:
    DOWNLOAD_NAME = GetDownloadName(DATATABLE_DIR, SpreadSheetTitle, WorkSheetTitle)
    if IsReDownload:
        if not DownloadWorkSheet(DATATABLE_DIR, SpreadSheetTitle, WorkSheetTitle):
            return False

    if not os.path.exists(DOWNLOAD_NAME):
        if not DownloadWorkSheet(DATATABLE_DIR, SpreadSheetTitle, WorkSheetTitle):
            return False
 
    FILE_NAME = GetFileName(SpreadSheetTitle,WorkSheetTitle)
    if os.path.exists(DOWNLOAD_NAME):
        DTPaths = unreal.EditorAssetLibrary.list_assets(EditorDataTableDir)
        for DTPath in DTPaths:
            if FILE_NAME in DTPath:
                task = unreal.AssetImportTask()
                task.filename = DOWNLOAD_NAME
                task.destination_path = EditorDataTableDir
                task.replace_existing = True
                task.automated = True
                task.save = False

                csv_factory = unreal.CSVImportFactory()
                Obj = unreal.EditorAssetLibrary.load_asset(DTPath)
                DataTable = unreal.DataTable.cast(Obj)
                row_struct = DataTable.get_editor_property("row_struct")
                csv_factory.automated_import_settings.import_row_struct = row_struct

                task.factory = csv_factory

                asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
                asset_tools.import_asset_tasks([task])
                return True
    return False

def ReimportAllWorkSheet(EditorDataTableDir: str, SpreadSheetTitle: str, IsReDownload: bool = True) -> bool:
    SpreadSheet = gc.open(SpreadSheetTitle)
    for WorkSheet in SpreadSheet.worksheets():
        ReimportWorkSheet(EditorDataTableDir, SpreadSheetTitle, WorkSheet.title, IsReDownload)
    return True

EUW_SpreadSheet_Importにボタンを追加

OnClickedイベントを作成

これでボタンを押したら指定したGoogle SpreadSheetを再インポートできるよ

完全版

プラグインにした

github.com

pythonでGoogle Spread Sheetをいじれるようにする

やあ

Unreal Engineでゲームを作ってるときに、Google Spread Sheetを編集するたびにダウンロードして、それをエディタにインポートするっていう地味にめんどくさい作業をすることが多くて、自動化したいなーと思ってたからやることにしたよ。
今回はその準備だ!

準備 (Google Cloud Console)

Google Cloud Consoleを開く

初めて開いた場合はこんな画面になるので利用規約に同意して続行。

プロジェクトを作成する





鈴マークからプロジェクトを選択。
または、左上にあるドロップボックスから選択。

サービスアカウントを作成する

ずっと画面の左側に出てるメニューからIAMと管理をクリック。

▼ピン止めを外しちゃった場合


メニューをスクロール。
その他のプロダクトっていうのがあるからそれを開いてIAMと管理をピン止めしなおしといてね。

IAMと管理に移動したら、サービスアカウントをクリック。

上のほうに出てくるサービスアカウントを作成をクリック。

名前を入れて完了。

サービスアカウントの鍵を作る

さっき作ったサービスアカウントをクリックして、アカウントの管理画面に移動するよ。

キーのタブに移動。
鍵を追加するよ!

新しい鍵を作成!

JSONで作成!

作成したらprojectname-00000.jsonがダウンロードされるはずだよ。
ダウンロードしたファイルの名前をservice_account.jsonに変更!

APIの追加

画面左上のナビゲーションメニューをクリック。

APIとサービスをクリック。

APIとサービスの有効化をクリック。

APIライブラリの検索欄にsheet apiと入力して検索!


有効化!
有効化が完了するまでちょっとだけ待つ。

有効化できたら自動的にこんな画面が表示されるよ。

もう一度、APIライブラリに行ってね。
検索欄にdrive apiと入力して検索!

なんかいっぱい出てくるけど一番上のやつをクリック!

有効化!

ちょっとだけ待つ!

やる

まずCtrl+Rcmdでコマンドコンソールを開きます。
いろいろpip installします。

pip install gspread

インストールしてる最中に
IAMと管理画面からメールアドレスをコピー!

pythonからアクセスしたいGoogle Spread Sheetを開く
右上の共有をクリック!

コピーしたメールアドレスを貼り付けて共有!

import gspread
service_account_json = "service_account.json"
gc = gspread.service_account(service_account_json)
print(gc.openall())

これでいじれるようになった!

次は、Google Spread SheetをダウンロードしてからUnreal Engineにインポートするまでを自動化するよ。

pto8913.hatenablog.com

GIMP 画像を絵みたいにする

やあ

Gimpで画像を絵みたいにするよ

やる

適当な画像を用意します

Gimpを開きます

フィルター→ぼかし→メディアンぼかし

いい感じにぼやけます

フィルター→芸術的効果→漫画

絵っぽくなってきた

フィルター→ノイズ→ごまかす

鉛筆で書いたみたいになった

これだけでもいいけど
フィルター→変形→風

ちょっとだけシュッってする

できた!