ゲーム作りは楽しい

なんか書く

毎日ゲームチャレンジをしています。

ゲームは1日1時間

ゲームは1日1時間ってことば聞いたことありますか?
僕は何度かあります。

これ一般的には最大1時間って意味で使われてると思ってるんですけど

アウトプットばっかしててインプットの時間がちゃんと確保できてないという事を少し気になっていたので
むしろ 最低1時間 はやったほうがいいと考えて

ゲームは1日1時間するようにしました。(最低なので2,3時間やってもOK)

※強制感もちすぎるのも辛いので、まぁ無理だった日は、次の日2時間やればOKみたいなノリで縛られすぎないルールにしました。
音ゲーは既に遊ぶ習慣ができているので、この時間に含めないことにしました。

もともと好きなゲームが出たときは勝手にやるんですけど(カービィとか)、
RPGみたいな重い腰をあげないと進まないゲームを起動する気力が落ちがちなので、
このあたりを遊ぶ時間を意識的に確保しようという狙いもありました。

成果

今年の4月から始めたのですが以下のゲームを遊べました。

  • ポケットモンスター アルセウス (アルセウスゲットまで)
  • ゼノブレイド DE (エンディングまで + 追加ストーリーエンディングまで)
  • ゼノブレイド2 (エンディングまで + 追加ストーリーエンディングまで)
  • MOTHER (エンディングまで)
  • MOTHER2 (エンディングまで)
  • ガンヴォルト鎖環 (BADエンディングまで)
  • ゼノブレイド3 (エンディングまで)
    • 追加コンテンツが今後出てくるのでまだ遊ぶと思う
  • カービィのグルメフェス (ランクMAXまで)
  • マリオカート8DX (150cc でグランプリ星3クリアを埋めた)
    • たまに遊んでる

現在プレイ中

今後の予定スタック

  • 幻影異聞録 #FE Encore

あとがき

特にゼノブレイドシリーズは興味はありつつも手が出せてなかった作品だったのですが めちゃくちゃハマった

ストーリーが大好きでした。
今後のシリーズにも期待したいですね
(他の過去のゼノシリーズまで遡って遊ぶ気力は出ず)

2Dゲーム 上から視点の反射表現 を作って遊んでた

↑こういうのをやってみてた 方針的には以下のようなことをしていた

ソースコードが欲しい人もいると思うが ちょっと汚いので割愛


2D 横スクロール視点の場合は、作りも変わると思う

雑談

ポケモンルビサファくらいのグラフィック表現って今に思うと、いろいろすごいな
完全に2Dドット絵なんだが、なんか影とか反射とか

どうなってんだろう?

Siv3Dで仮想ウィンドウクラスを作った

VirtualWindow

Windowsエクスプローラーを参考に作った
だいたいそれっぽい動きをしている

ツールのレイアウトとかをサクッと作れるといいなぁと思っていて
第一歩という感じ

ソースコード

結構ごり押し。一つ一つの挙動を書いていくしかない
ちゃんとファイル分けしたから結構コード量ある感じになった

Siv3D VirtualWindow · GitHub

レイヤー合成、計算式メモ

随時更新するかも

スクリーン

float screen(float dest, float src)
{
    return 1 - (1 - dest) * (1 - src);
}
float4 screen(float4 dest, float4 src)
{
    float4 color;
    color.r = screen(dest.r, src.r);
    color.g = screen(dest.g, src.g);
    color.b = screen(dest.b, src.b);
    color.a = 1.0;
    return color;
}

オーバーレイ

float overlay(float dest, float src)
{
    return dest < 0.5 ? 2.0 * dest * src : 1.0 - 2.0 * (1 - dest) * (1 - src);

}
float4 overlay(float4 dest, float4 src)
{
    float4 color;
    color.r = overlay(dest.r, src.r);
    color.g = overlay(dest.g, src.g);
    color.b = overlay(dest.b, src.b);
    color.a = 1.0;
    return color;
}

ソフトライト

float softlight(float dest, float src)
{
    return src < 0.5 ? 2.0 * dest * src + dest * dest * (1.0 - 2.0 * src) : 2.0 * dest * (1 - src) + sqrt(dest) * (2 * src - 1.0);
}
float4 softlight(float4 dest, float4 src)
{
    float4 color;
    color.r = softlight(dest.r, src.r);
    color.g = softlight(dest.g, src.g);
    color.b = softlight(dest.b, src.b);
    color.a = 1.0;
    return color;
}

or

float softlight(float dest, float src)
{
    return src < 0.5 ? pow(dest, 2.0 * (1.0- src)) :pow(dest, 1.0 / (2.0 * src)); 
}
float4 softlight(float4 dest, float4 src)
{
    float4 color;
    color.r = softlight(dest.r, src.r);
    color.g = softlight(dest.g, src.g);
    color.b = softlight(dest.b, src.b);
    color.a = 1.0;
    return color;
}

ハードライト

float hardLight(float dest, float src)
{
    return src < 0.5 ? dest * src * 2.0: 1 - 2 * (1 - dest) * (1 - src);
}
float4 hardLight(float4 dest, float4 src)
{
    float4 color;
    color.r = hardLight(dest.r, src.r);
    color.g = hardLight(dest.g, src.g);
    color.b = hardLight(dest.b, src.b);
    color.a = 1.0;
    return color;
}

焼きこみ

float colorBurn(float dest, float src)
{
    return src <= 0 ? 0 : 1 - (1 - dest) / src;
}
float4 colorBurn(float4 dest, float4 src)
{
    float4 color;
    color.r = colorBurn(dest.r, src.r);
    color.g = colorBurn(dest.g, src.g);
    color.b = colorBurn(dest.b, src.b);
    color.a = 1;
    return color;
}

覆い焼き

float colorDodge(float dest, float src)
{
    return src >= 1 ? 1 : dest / (1 - src);
}
float4 colorDodge(float4 dest, float4 src)
{
    float4 color;
    color.r = colorDodge(dest.r, src.r);
    color.g = colorDodge(dest.g, src.g);
    color.b = colorDodge(dest.b, src.b);
    color.a = 1;
    return color;
}

ビビッドライト

float vividLight(float dest, float src)
{
    return src < 0.5 ? colorBurn(dest, 2 * src) : colorDodge(dest, 2 * (src - 0.5));
}
float4 vividLight(float4 dest, float4 src)
{
    float4 color;
    color.r = vividLight(dest.r, src.r);
    color.g = vividLight(dest.g, src.g);
    color.b = vividLight(dest.b, src.b);
    color.a = 1.0;
    return color;
}

参考

osakana.factory - ブレンドモード詳説

[CG] 描画モードについて - CG/CG概論

Python3 & OpenCV で画像処理を学ぶ[5] 〜 AfterEffects/Photoshopにある描画モードを実装する - Optie研

C++でコードベースのファイル名を使用したマイグレーションみたいなものを作った

コードベースのマイグレーションみたいなものを作りました。

wandbox.org

以下に

  • 1_Test.cpp
  • 2_Hoge.cpp

がありますが、これのprefixの数字をidとして、カレントバージョンから最新バージョンまで全部実行する仕組みです


1_Test.cpp

#include"Migration.hpp"
#include <iostream>

namespace 
{
    MIGRATION(Test)
    {
        void up() const override
        {
            std::cout << "Up Test" << std::endl;
        }
        void down() const override
        {
            std::cout << "Down Test" << std::endl;
        }
    }_;
}

2_Hoge.cpp

#include"Migration.hpp"
#include <iostream>

namespace 
{
    MIGRATION(Hoge)
    {
        void up() const override
        {
            std::cout << "Up Hoge" << std::endl;
        }
        void down() const override
        {
            std::cout << "Down Hoge" << std::endl;
        }
    }_;
}

呼び出し
main.cpp

int main()
{
    using Migration::MigrationHundler;
    int current = 0;
    std::cin >> current; 
    
    std::cout << "Latest: " << current << std::endl;
    
    std::cout << "-- Migration Up --" << std::endl;
    current = MigrationHundler::Up(current);
    std::cout << "Latest: " << current << std::endl;

    std::cout << "-- Migration Down --" << std::endl;
    current = MigrationHundler::Down(current);    
    std::cout << "Latest: " << current << std::endl;    
    
    std::cout << "-- Migration Down --" << std::endl;
    current = MigrationHundler::Down(current);    
    std::cout << "Latest: " << current << std::endl;      
}

出力

Latest: -1
-- Migration Up --
Up Test
Up Hoge
Latest: 2
-- Migration Down --
Down Hoge
Latest: 1
-- Migration Down --
Down Test
Latest: -1

実装

Migration.hpp

#pragma once
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <optional>

namespace Migration
{
    struct IMigration
    {
    public:
        virtual ~IMigration() = default;
        virtual void up() const = 0;
        virtual void down() const = 0;
    };

    template<int Id>
    struct Migration;

    class MigrationHundler
    {
    public:
        template<int Id>
        static void Regist(IMigration* migration)
        {
            m_migrations[Id] = migration;
        }

        static IMigration* Get(int version)
        {
            return m_migrations[version];
        }
        
        static int Up(int currentVersion)
        {
            int nextVersion = -1;
            std::vector<int> updateVersions;
            for (auto&& m : m_migrations) {
                if (m.first > currentVersion) {
                    updateVersions.push_back(m.first);
                    if(nextVersion < m.first) {
                        nextVersion = m.first;
                    }
                }
            }
            std::ranges::sort(updateVersions);
            for(int v : updateVersions) {
                m_migrations[v]->up();
            }
            return nextVersion == -1 ? currentVersion : nextVersion;
        }
        static int Down(int currentVersion)
        {
            if(m_migrations.find(currentVersion) != m_migrations.end()){
                m_migrations[currentVersion]->down();
            }
            int nextVersion = -1;
            for (auto&& m : m_migrations) {
                if (m.first < currentVersion) {
                    if(nextVersion < m.first) {
                        nextVersion = m.first;
                    }
                }
            }
            return nextVersion;
        }        
    private:
        MigrationHundler() = default;
        MigrationHundler(const MigrationHundler& other) = delete;
        void operator=(const MigrationHundler& other) = delete;
    private:
        inline static std::unordered_map<int, IMigration*> m_migrations;
    };

    template<int Id>
    struct Migration : public IMigration
    {
    public:
        Migration()
        {
            MigrationHundler::Regist<Id>(this);
        }
    };

    namespace detail
    {
        consteval int versionId(const char* path)
        {
            // filename取得
            const char* filename = path;
            while (*path) {
                if (*path++ == '\\') {
                    filename = path;
                }
            }
            // バージョン値取得
            const char* str = filename;
            int value = 0;
            while (char c = *str) {
                if (c <= '9' && c >= '0') {
                    value = (c - '0') + value * 10;
                } else {
                    break;
                }
                ++str;
            }
            return value;
        }
    }
}

#define MIGRATION(name) struct name :  ::Migration::Migration<::Migration::detail::versionId(__FILE__)>

ファイル名からprefixのidへの変換はdetail::versionIdでします。
これをMIGRATIONマクロに隠蔽することでよしなに紐づけてます。

Hundlerへの登録はインスタンス生成時にthisポインタを渡しますので、各実装をしたら一つどこかにインスタンスが必要です。
例では_の変数名で無名名前空間に生成しています

実際の運用について

実際にはカレントバージョンをどこかに永続化する必要があるので何度も更新が走らないようにtxtなりdbなり自由に保存しておきましょう。

メタクラスについて勉強してみました。

メタクラスについて勉強してみた。

class とか structっていうキーワードを使う箇所をユーザー定義したものに変えて

my_class Hoge{};

みたいに使います。
んで、 何ができるかというと

例えばC++ではstructはデフォルトpublic, classはprivateっていう話がありますが、 そーゆーデフォルト時のルールを決めることができたり、違反したらコンパイルエラーにできたりする。
メンバ変数は指定しなければprivateになる。すべてprivateじゃなければコンパイルエラー!みたいな

例だとinterface メタクラスを例に上げていた

$class interface
    {
     // デストラクタ定義
    ~interface() noexcept { }
    constexpr
    {
        // メンバ変数はもったらコンパイルエラー
        compiler.require($interface.variables().empty(), "interfaces may not contain data");
        for (auto f : $interface.functions())
        {
            // コピー、ムーブはコンパイルエラー
            compiler.require(!f.is_copy() && !f.is_move(), "interfaces may not copy or move; consider a" " virtual clone() instead");
            // アクセス指定子いないならpublicにする
            if (!f.has_access()) f.make_public();
            // public以外があったらコンパイルエラー
            compiler.require(f.is_public(), "interface functions must be public");
            // 純粋仮想関数とする
            f.make_pure_virtual();
        }
    }
};
interface IHoge
{
    void hoge();
};

class IHoge
{
    public:
    virtual ~IHoge() = 0;
    virtual void hoge() = 0;
};

みたいになるという感じですかね

未来の話なのでどうなることかわからんが、面白そうだった

参考

https://www.fluentcpp.com/2017/08/04/metaclasses-cpp-summary/