【C++】画面スクローリング

DALL·E-2024-01-22-20.54.09-Photo-style-illustration C/C++
DALL·E-2024-01-22-20.54.09-Photo-style-illustration

こんにちは🤗✨

本日も活気溢れる中、自作ロックマンの開発に没頭しております。

まだ開発開始から1ヶ月ほどですが、はじめて画面に動きが付きました!😃

いくつかサンプルgifを置いておきます。

horizonal direction scrolling sample
horizonal direction scrolling sample
vertical direction scrolling sample
vertical direction scrolling sample
8way scrolling sample
8way scrolling sample

中でも8方向スクロールについては、FC版ロックマンでは実際に稼働していない要素なので、試みとしてもネタになりそうで実際にプレイするのが私自身楽しみだったりします☺

仕組みの話

これらの画面スクロールは、単純に一枚の絵を転がしているわけではありません。

実際にはマップ画面を構築しているのは16×16のタイルを画面上に敷き詰めて描画しています。

それは兼ねてより開発をしていたC#.NETのマップエディタでお見せした内容になります。

マップエディタ?何それ?と思ったあなた。
もし詳しく知りたい場合は以下の記事もご覧いただけますと幸いです😄

よって、背景画像の元データは以下になります。

これをそれぞれマップ構造体とグラフィックタイルデータとしてメモリに取り込んでそれを順番に結合して画面に出力します。

それをした後に、コントローラーの入力キーに沿って画面スクロールのロジックを動かします。

具体的なソースコードは説明が長くなってしまうので今回は割愛させて頂きますが、また何かの手段で説明していきたいですね。

ちなみにこのC++プロジェクトも、マップエディタ開発の時同様にC言語の糞コードというものが存在し、それはかつて私自身がまだプログラミングの知識が浅はかだった頃に書いたオーパーツなるものです(笑)

実はそのコードから一部処理を拝借されてもらってはいるんですが、オブジェクト指向設計でコーディングをするように心掛けている手前、かなりメンテナンス性は飛躍的に向上しているように思います。

最近気づいたのは、コードは邪魔にならない程度冗長であって、名前は多少長くても適切なものが付いていて、クラスはSOLID原則に忠実で多めな構造であることが保守性に優れたコードである気がしてます。

一部、え?それ違うんじゃね?と思う部分があると思いますが、作っていくうちに概念や宗教に傾倒する事や依存的な構造になる事がダメな事なのだと気づき始めました。


C++の型推論について

C++にはテンプレートと呼ばれる型を指定しない機能がありますが、この機能は実装を一度で機能は多方面という動きを再現し、メソッドやクラスに拡張性をもたらすジェネリックプログラミングです。

これらを実際に使った例は以下です。

template <typename T>
T Sum(T a, T b) {
    return a + b;
}
Sum<int>(1, 2);
Sum<double>(3.14, 3.6);
出力結果:3 6.74

これらは最も簡潔な一例ですが、テンプレートは実に強力で複数のパラメータを渡して処理の特殊化をしたり、型推論のデータ型を渡したりする事もできます。
vector配列などはこれを使っていますね。vector<T>。

中でも型推論については、この機能を持っているがためにテンプレートが存在していると言っても過言ではないレベルで実用的です。

例えば、双頭のテンプレート指定子へそれぞれ別々のデータ型を渡して、判定結果を返す機能を構築する変速的なコーディングができます。1

template <typename T, typename U>
struct IsSameImpl {
  using value = std::false_type;
};

template <typename T>
struct IsSameImpl <T, T> {
  using value = std::true_type;
};
template <typename T, typename U>
struct IsSame : public IsSameImpl<T, U>::value {};
...
std::cout << IsSame<int, int>::value << std::endl; // 1
std::cout << IsSame<int, long>::value << std::endl; // 0

そして今回注目したいキーワードが「decltype」というものです。

これは何なのかと言うと、「auto myclass = new MyClass(i, j);」でMyClass型のmyclassが作れるのと同様に、式の結果の型を取得する事ができます。

どういう事?となるかと思いますが、簡潔に言うと

vector<MyClass> instance;

を、

auto robot = new MyClass(i,j);
vector<decltype(robot)> instance;

のように書けますよ、という話です。

ちなみにですが、C++20ではなんとメソッドの引数にも「auto」キーワードが書けます。
オーバーロード?そんなものはオワコンなのだ( ^ω^)

それができるという事はもう従来のコーディングとはかけ離れた遥かなる旅が始まりそうですが、それはひとまず置いておいて decltype に見るプログラミングスタイルの変遷は、メンテナンスを容易にするといった意図が含まれています。

それは一貫して変わらない事実です。

プログラミングに関わらずこれらの時代、インターネットさえあれば世界中の情報へアクセスできる世界で、我々が生き残っていくには如何に軽装で軽量で軽快にモノづくりに勤しむ事ができるか、という所ではないでしょうか。

今回の8方向スクロールで背景描画をする箇所でC++の型推論を使用させて頂きました。

bool PolyExternalViewer::constructExtendedRoom() {
    auto [scraper, pageindex] = createScraperAndGetPageIndex();

    _material->mapClear(L"map");
    // Stack the required background map rooms into the BG.Geometry by referencing the current coordinate values.
    if (!_material->mapDraw(L"map", 0, pageindex, _basePoint)) return false;
    if (0 > _basePoint.X) {
        if (!_material->mapDraw(L"map", 0, scraper.getPageIndex(scraper.getRightRoom(pageindex)), raw::Point(_basePoint.X + WINDOW_WIDE, _basePoint.Y, _basePoint.Z))) return false;
    }
    else if (0 < _basePoint.X) {
        if (!_material->mapDraw(L"map", 0, scraper.getPageIndex(scraper.getLeftRoom(pageindex)), raw::Point(_basePoint.X - WINDOW_WIDE, _basePoint.Y, _basePoint.Z))) return false;
    }
    if (0 > _basePoint.Y) {
        if (!_material->mapDraw(L"map", 0, scraper.getPageIndex(scraper.getUnderRoom(pageindex)), raw::Point(_basePoint.X, _basePoint.Y + WINDOW_LENGTH, _basePoint.Z))) return false;
    }
    else if (0 < _basePoint.Y) {
        if (!_material->mapDraw(L"map", 0, scraper.getPageIndex(scraper.getOverRoom(pageindex)), raw::Point(_basePoint.X, _basePoint.Y - WINDOW_LENGTH, _basePoint.Z))) return false;
    }
    // In the case of multi-way scrolling, it is necessary to find a room that is relatively far from the current room,
    //  and draw that room by pseudo-referring to the four corners from the coordinates.
    if (_basePoint.X && _basePoint.Y) {
        using namespace util_number;
        auto room = 0 > _basePoint.X ? scraper.getRightRoom(pageindex) : scraper.getLeftRoom(pageindex);
        auto index = scraper.getPageIndex(room);
        room = 0 > _basePoint.Y ? scraper.getUnderRoom(index) : scraper.getOverRoom(index);
        if (!_material->mapDraw(
            L"map",
            0,
            scraper.getPageIndex(room),
            raw::Point(
                _basePoint.X - WINDOW_WIDE * sign<decltype(_basePoint.X)>(_basePoint.X),
                _basePoint.Y - WINDOW_LENGTH * sign<decltype(_basePoint.Y)>(_basePoint.Y),
                _basePoint.Z
            )
        )) {
            return false;
        }
    }

    return true;
}

なお、このプロジェクトのソースコードは下記のgithub上にて公開しています。

unlimitedloop-admin/enigma: This is a project to 100% reproduce the Rockman 2 (that hack game) with a Windows application.

もし興味のある方がおられましたら、ご自由にお持ちください😄

今回の記事はこれで終わりです。
次回の更新は前回予告しておいて出来なかったロックマンのアクションアルゴリズムを紹介していこうと思います。

  1. https://tech.retrieva.jp/entry/2019/05/15/132008 ↩︎

コメント

タイトルとURLをコピーしました