Surreal Engine C++ を WebAssembly に移植

この投稿では、Surreal Engine ゲーム エンジンを WebAssembly に移植する方法について説明します。

シュール エンジン – Unreal Engine 1 の機能のほとんどを実装するゲーム エンジン、このエンジン上の有名なゲーム -アンリアル トーナメント 99、アンリアル、デウス エクス、アンダイング。これは、主にシングルスレッド実行環境で動作する古典的なエンジンを指します。

当初、私は、妥当な時間内に完了できないプロジェクトに挑戦して、Twitch フォロワーに私でも実行できないプロジェクトがあることを示すことを考えていました。最初のストリーム中に、Emscripten を使用して Surreal Engine C++ を WebAssembly に移植するタスクが実行可能であることに突然気づきました。

Surreal Engine Emscripten Demo

1 か月後には、WebAssembly でフォークとエンジンのアセンブリをデモンストレーションできるようになります。
https://demensdeum.com/demos/SurrealEngine/

オリジナルと同様に、コントロールはキーボードの矢印を使用して実行されます。次に、これをモバイル コントロール (タチ) に適応させ、Unreal Championship 99 レンダリングの正しいライティングやその他のグラフィック機能を追加する予定です。

どこから始めればよいですか?

最初に言いたいのは、Emscripten を使用すると、どのプロジェクトでも C++ から WebAssembly に移植できるということです。唯一の問題は、機能がどの程度完成するかということです。 Surreal Engine の場合は、ライブラリ ポートがすでに Emscripten で利用できるプロジェクトを選択してください。エンジンは SDL 2、OpenAL – ライブラリを使用します。どちらも Emscripten に移植されています。ただし、Vulkan はグラフィックス API として使用されており、現在 HTML5 では利用できません。WebGPU の実装作業が進行中ですが、これもドラフト段階にあり、Vulkan から WebGPU へのさらなる移植がどれほど簡単になるかも不明です。完全に標準化された後。したがって、Surreal Engine 用に独自の基本的な OpenGL-ES / WebGL レンダリングを作成する必要がありました。

プロジェクトのビルド

Surreal Engine でシステムを構築 – CMake も移植を簡素化します。 Emscripten はネイティブ ビルダーを提供します。えむけ、えむけ

Surreal Engine の移植は、Death-Mask と呼ばれる、WebGL/OpenGL ES および C++ で作成した最新ゲームのコードに基づいていました。そのため、開発ははるかに簡単で、必要なビルド フラグとコード サンプルがすべて揃っていました。

CMakeLists.txt の最も重要なポイントの 1 つは Emscripten のビルド フラグです。以下はプロジェクト ファイルの例です。


-s MAX_WEBGL_VERSION=2 \

-s EXCEPTION_DEBUG \

-fexceptions \

--preload-file UnrealTournament/ \

--preload-file SurrealEngine.pk3 \

--bind \

--use-preload-plugins \

-Wall \

-Wextra \

-Werror=return-type \

-s USE_SDL=2 \

-s ASSERTIONS=1 \

-w \

-g4 \

-s DISABLE_EXCEPTION_CATCHING=0 \

-O3 \

--no-heap-copy \

-s ALLOW_MEMORY_GROWTH=1 \

-s EXIT_RUNTIME=1")

ビルド スクリプト自体:


emmake make -j 16

cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data

cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js

cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm

cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html

cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png

次に、インデックスを準備します。 .html 、プロジェクト ファイル システム プリローダーが含まれています。 Web にアップロードするために、Unreal Championship Demo バージョン 338 を使用しました。CMake ファイルからわかるように、解凍されたゲーム フォルダーがビルド ディレクトリに追加され、Emscripten のプリロード ファイルとしてリンクされました。

メインコードの変更

その後、ゲームのゲーム ループを変更する必要がありました。無限ループを実行することはできません。これによりブラウザがフリーズします。代わりに emscripten_set_main_loop を使用する必要があります。この機能については 2017 年のメモ「< a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">SDL C++ ゲームを HTML5 (Emscripten) に移植」
while ループを終了するコードを if に変更し、ゲーム ループを含むゲーム エンジンのメイン クラスをグローバル スコープに表示し、グローバル オブジェクトからゲーム ループ ステップを呼び出すグローバル関数を作成します。 :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

この後、アプリケーションにバックグラウンド スレッドがないことを確認する必要があります。存在する場合は、シングル スレッド実行用に書き直すか、Emscripten の phtread ライブラリを使用する準備をします。
Surreal Engine のバックグラウンド スレッドは音楽の再生に使用されます。データは、現在のトラック、音楽を再生する必要性、またはその不在に関するメイン エンジン スレッドから取得され、バックグラウンド スレッドはミューテックス経由で新しい状態を受け取り、新しい音楽の再生を開始します。 、または一時停止します。バックグラウンド ストリームは、再生中に音楽をバッファリングするためにも使用されます。
pthread を使用して Emscripten 用の Surreal Engine を構築しようとした試みは失敗しました。SDL2 ポートと OpenAL ポートは pthread サポートなしで構築されており、音楽のために再構築したくなかったためです。そこで、BGM ストリームの機能をループを使用したシングルスレッド実行に移行しました。 C++ コードから pthread 呼び出しを削除することで、バッファリングと音楽再生をメイン スレッドに移動し、遅延が発生しないように、バッファを数秒増やしました。

次に、グラフィックとサウンドの具体的な実装について説明します。

Vulkan はサポートされていません!

はい、Vulkan は HTML5 ではサポートされていませんが、すべてのマーケティング パンフレットでは Vulkan の主な利点としてクロスプラットフォームおよび広範なプラットフォームのサポートが紹介されています。このため、簡素化された OpenGL タイプの基本的なグラフィックス レンダラーを独自に作成する必要がありました。 ES はモバイル デバイスで使用され、最新の OpenGL のファッショナブルな機能が含まれていない場合もありますが、WebGL に非常によく移植されており、まさに Emscripten が実装しているものです。基本的なタイル レンダリング、最も単純な GUI 表示用の bsp レンダリング、およびモデルとマップのレンダリングの作成は 2 週間で完了しました。おそらくこれがこのプロジェクトで最も困難な部分でした。 Surreal Engine レンダリングの全機能を実装するには、まだ多くの作業が必要です。そのため、読者からのコードやプル リクエストの形での支援を歓迎します。

OpenAL がサポートされています!

幸運なことに、Surreal Engine はオーディオ出力に OpenAL を使用しています。 OpenAL で簡単な Hello World を記述し、Emscripten を使用して WebAssembly で組み立てたので、すべてがいかに単純であるかが明らかになり、サウンドの移植に着手しました。
数時間のデバッグ後、Emscripten の OpenAL 実装にはいくつかのバグがあることが明らかになりました。たとえば、モノラル チャネル数の読み取りを初期化するときに、メソッドが無限の数を返し、無限サイズのベクトルを初期化しようとした後、C++例外vector::length_errorでクラッシュします。
>
モノラル チャンネルの数を 2048 にハードコーディングすることで、この問題を回避することができました。


		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);



#if __EMSCRIPTEN__

		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?

#endif



ネットワークはありますか?

Surreal Engine は現在オンライン プレイをサポートしていません。ボットでのプレイはサポートされていますが、これらのボット用の AI を作成する人が必要です。理論的には、Websocket を使用して WebAssembly/Emscripten にネットワーク ゲームを実装できます。

結論

結論として、Emscripten ポートがあるライブラリの使用と、WebAssembly 用に C++ でゲームを実装した過去の経験のおかげで、Surreal Engine の移植は非常にスムーズに完了したと言いたいと思います。エムスクリプテンで。以下は、このトピックに関する知識源とリポジトリへのリンクです。
マ、マ、マ、モンスターを倒せ!

また、できれば WebGL/OpenGL ES レンダリング コードでプロジェクトを支援したい場合は、Telegram で私に連絡してください。
https://t.me/demenscave

リンク

https://demensdeum.com/demos/Sur​​realEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten

https://github.com/dpjudas/SurrealEngine

Flash Forever – Interceptor 2021

Recently, it turned out that Adobe Flash works quite stably under Wine. During a 4-hour stream, I made the game Interceptor 2021, which is a sequel to the game Interceptor 2020, written for the ZX Spectrum.

For those who are not in the know – the Flash technology provided interactivity on the web from 2000 to around 2015. Its shutdown was prompted by an open letter from Steve Jobs, in which he wrote that Flash should be consigned to history because it lagged on the iPhone. Since then, JS has become even more sluggish than Flash, and Flash itself has been wrapped in JS, making it possible to run it on anything thanks to the Ruffle player.

You can play it here:
https://demensdeum.com/demos/Interceptor2021

Video:
https://www.youtube.com/watch?v=-3b5PkBvHQk

Source code:
https://github.com/demensdeum/Interceptor-2021

メイソンズ-DR デモ ゲーム

Masonry-AR は、現実世界で街を移動し、本からフリーメーソンの知識を収集し、通貨を入手してフリーメーソン教団の領土を占領する必要がある拡張現実ゲームです。このゲームは実際の組織とは何の関係もありません。すべての試合はランダムです。

ゲームデモ:
https://demensdeum.com/demos/masonry-ar/client

ヴィッキー:
https://demensdeum.com/masonry-ar-wiki-ru/

ソースコード:
https://github.com/demensdeum/Masonry-AR

フレームスチールエンジンランナー

フレーム スチール エンジン ランナーをご紹介します – Flame Steel Engine ツールキットに基づいてマルチメディア アプリケーションを起動するためのプラットフォーム。サポートされているプラ​​ットフォームは、Windows、MacOS、Linux、Android、iOS、HTML 5 です。アプリケーション コード開発の重点は、スクリプト作成に移ってきています。現時点では TinyJS を使用して JavaScript のサポートが追加されており、ツールキット自体とエンジンは引き続きハードウェアに近い言語 (C、C++、Rust など) で開発されます
Flame Steel Engine Runner Demo
以下のページでは、キューブを回転させ、JavaScript でコードを記述し、[ファイルのアップロード] ボタンを使用してモデル、サウンド、音楽、コードをアップロードし、[実行] ボタンを使用して main.js ファイルから開始することができます。
https://demensdeum.com/demos/FlameSteelEngineRunner/

スペースジャガー アクションRPG 0.0.4

Webassembly 用の Space Jaguar アクション RPG ゲームの最初のプロトタイプ:

読み込み表示がなければ、読み込みに時間がかかります (53MB)。

スペース ジャガー アクション RPG –宇宙海賊のライフシミュレーター。現時点では、最も単純なゲーム メカニクスが利用可能です。

  • 宇宙を飛ぶ
  • 死ぬ
  • 食べる
  • 寝てください
  • チームを雇う
  • 落ち着きのない、急速に流れる時間の流れを見てください

Web バージョンでは 3D シーンのレンダリングの最適化が不十分なため、3D 環境内を歩き回る機能は利用できません。最適化は将来のバージョンで追加される予定です。

エンジン、ゲーム、スクリプトのソース コードは、MIT ライセンスに基づいて入手できます。改善のためのすべての提案は非常に好意的に受け取られています。

https://gitlab.com/demensdeum/space-jaguar -アクション RPG

Linux のネイティブ バージョンのスクリーンショット:

C による ZX Spectrum のゲーム開発

このナンセンスな投稿は、C 言語の古い ZX Spectrum コンピューター用のゲーム開発に特化しています。ハンサムな男を見てみましょう。

1982 年に生産が開始され、1992 年まで生産されました。マシンの技術的特徴: 8 ビット Z80 プロセッサ、16 ~ 128 kb のメモリ、およびAY-3-8910 サウンド チップ

などのその他の拡張機能。

このマシンのコンペティション Yandex Retro Games Battle 2019 の一環として、私はゲームを書きましたZ80のアセンブリ言語を学ぶ時間がなかったので、Cで開発することにしました。ツールチェーンとして、既製のセットを選択しました。 z88dk には、Spectrum 用アプリケーションの実装を高速化するための C コンパイラーと多くの補助ライブラリが含まれています。また、MSX、Texas Instruments 電卓など、他の多くの Z80 マシンもサポートしています。

次に、コンピューター アーキテクチャである z88dk ツールチェーンに関する私の表面的な飛行について説明し、OOP アプローチを実装し、デザイン パターンを使用する方法を示します。

インストール機能

z88dk のインストールは、リポジトリのマニュアルに従って実行する必要がありますが、Ubuntu ユーザー向けに、次の機能に注意してください。 Z80 用のコンパイラが deb パッケージからインストールされている場合は、それらを削除する必要があります。ツールチェーン コンパイラのバージョンに互換性がないため、z88dk はデフォルトで bin フォルダからコンパイラにアクセスするため、おそらく何もコンパイルできなくなります。 /p>

ハローワールド

Hello World の記述は非常に簡単です。

#include 

void main()
{
    printf("Hello World");
}

タップ ファイルのコンパイルはさらに簡単です。

zcc +zx -lndos -create-app -o helloworld helloworld.c

実行するには、オンラインなど、タップ ファイルをサポートする ZX Spectrum エミュレータを使用します。
http://jsspeccy.zxdemo.org/

全画面で画像に描画します

tl;dr 画像はタイル (サイズ 8×8 ピクセルのタイル) で描画され、タイル自体は Spectrum フォントに組み込まれ、画像はインデックスからの線として印刷されます。

スプライトおよびタイル出力ライブラリ sp1 は、UDG を使用してタイルを出力します。画像は個々の UDG (タイル) のセットに変換され、インデックスを使用して画面上で組み立てられます。 UDG はテキストを表示するために使用され、画像に非常に大きなタイルのセット (たとえば、128 タイルを超える) が含まれている場合は、セットの境界を越えてデフォルトのスペクトルを消去する必要があることに注意してください。フォント。この制限を回避するために、ベース 128 – を使用しました。元のフォントをそのまま残しながら画像を簡素化することで、255 を作成します。以下の写真の簡略化について。

全画面画像を描画するには、次の 3 つのユーティリティを準備する必要があります。
ギンプ
img2スペック
png2c-z88dk

本物の ZX マン、本物のレトロ戦士には方法があります。これは、スペクトル パレットを使用してグラフィック エディタを開き、画像出力の機能を把握し、画像出力を手動で準備し、png2c-z88dk または png2scr を使用してアップロードすることです。< /p>

より簡単な方法 – 32 ビット画像を取得し、Gimp で色数を 3 ~ 4 に切り替え、少し編集してから、色制限を手動で処理しないように img2spec にインポートし、png をエクスポートして、png2c を使用して C 配列に変換します。 z88dk。

エクスポートを成功させるには、各タイルに 2 色以上を含めることはできないことに注意してください。

その結果、一意のタイルの数を含む h ファイルを受け取ります。タイル数が 128 を超える場合は、Gimp で画像を簡略化し (再現性を高め)、新しいタイルに対してエクスポート手順を実行します。 .

エクスポート後、文字通りタイルから「フォント」をロードし、タイル インデックスからの「テキスト」を画面に印刷します。以下は、レンダリング「クラス」の例です。

// грузим шрифт в память
    unsigned char *pt = fullscreenImage->tiles;

    for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
            sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
    }

    // ставим курсор в 0,0
    sp1_SetPrintPos(&ps0, 0, 0);

    // печатаем строку
    sp1_PrintString(&ps0, fullscreenImage->ptiles);

画面上にスプライトを描画する

次に、画面上に 16×16 ピクセルのスプライトを描画する方法を説明します。アニメーションや色の変更まではできなかったので…おそらくこの段階ですでにメモリが不足していることは些細なことです。したがって、ゲームには透明なモノクロ スプライトのみが含まれています。

Gimp でモノクロの PNG イメージ 16×16 を描画し、png2sp1sprite を使用してそれを asm アセンブリ ファイルに変換し、C コードでアセンブリ ファイルから配列を宣言し、アセンブリ段階でファイルを追加します。< /p>

スプライト リソースを宣言する段階の後、スプライト リソースを画面の目的の位置に追加する必要があります。以下は、ゲーム オブジェクトの「クラス」のコードの例です。

    struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, col2-col1, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB,  SP1_TYPE_2BYTE, 0, 0);
    sp1_IterateSprChar(bubble_sprite, initialiseColour);

関数の名前から – の意味はおおよそ理解できます。スプライトにメモリを割り当て、8×8 列を 2 つ追加し、スプライトに色を追加します。

スプライトの位置は各フレームに示されます。

sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);

OOP のエミュレーション

C には OOP の構文がありません。それでも本当に使いたい場合はどうすればよいでしょうか? OOP 機器などというものは存在せず、最終的にはすべてがマシン アーキテクチャの 1 つに行き着き、そこにはオブジェクトやそれに関連する他の抽象概念がまったく存在しないという考えに心をつなぎ、啓発される必要があります。< /p>

この事実により、私は長い間、なぜ OOP が必要なのか、最終的にすべてがマシンコードになるのになぜ OOP を使用する必要があるのか​​を理解することができませんでした。

しかし、製品開発に携わった後、主に開発の柔軟性、適切なアプローチによるコード保護メカニズム、エントロピーの削減、チーム作業の簡素化など、このプログラミング パラダイムの楽しさを発見しました。上記のメリットはすべて、3 つの柱から生まれています。ポリモーフィズム、カプセル化、継承。

アプリケーション アーキテクチャに関連する問題の解決が簡素化されていることも注目に値します。アーキテクチャ上の問題の 80% は前世紀にコンピュータ科学者によって解決され、デザイン パターンに関する文献で説明されているからです。次に、OOP のような構文を C に追加する方法について説明します。

クラス インスタンスのデータを格納するための基礎として C 構造体を使用する方が便利です。もちろん、バイト バッファを使用したり、クラスやメソッドの独自の構造を作成したりすることはできますが、なぜ車輪の再発明が必要なのでしょうか?結局のところ、私たちはすでに構文を再発明しつつあるのです。

クラスデータ

ゲームオブジェクトの「クラス」データフィールドの例:

struct GameObjectStruct {
    struct sp1_ss *gameObjectSprite;
    unsigned char *sprite_col;
    unsigned char x;
    unsigned char y;
    unsigned char referenceCount;
    unsigned char beforeHideX;
    unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;

クラスを「GameObject.h」として保存し、適切な場所に #include 「GameObject.h」を実行して使用します。

クラスメソッド

Objective-C 言語の開発者の経験を考慮すると、クラス メソッドのシグネチャはグローバル スコープの関数となり、最初の引数は常にデータ構造となり、その後にメソッド引数が続きます。以下は、「クラス」GameObject の「メソッド」の例です。

void GameObject_hide(GameObject *gameObject) {
    gameObject->beforeHideX = gameObject->x;
    gameObject->beforeHideY = gameObject->y;
    gameObject->y = 200;
}

メソッド呼び出しは次のようになります。

GameObject_hide(gameObject);

コンストラクターとデストラクターは同じ方法で実装されます。コンストラクターをアロケーターおよびフィールド初期化子として実装することは可能ですが、私はオブジェクトの割り当てと初期化を個別に制御することを好みます。

メモリの操作

C++ と一致するように、新しいマクロと削除マクロでラップされた malloc と free を使用したフォームの手動メモリ管理:

#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)

複数のクラスで同時に使用されるオブジェクトの場合、古い Objective-C ランタイム ARC メカニズムのイメージと同様に、参照カウントに基づいて半手動のメモリ管理が実装されます。

void GameObject_retain(GameObject *gameObject) {
    gameObject->referenceCount++;
}

void GameObject_release(GameObject *gameObject) {
    gameObject->referenceCount--;

    if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
        sp1_DeleteSpr(gameObject->gameObjectSprite);
        delete(gameObject);
    }
}

したがって、各クラスは、retain を使用して共有オブジェクトの使用を宣言し、release を通じて所有権を解放する必要があります。最新バージョンの ARC では、自動保持/解放呼び出しが使用されます。

響け!

Spectrum には 1 ビット音楽を再生できるツイーターがあり、当時の作曲家は最大 4 つのサウンド チャンネルを同時に再生できました。

Spectrum 128k には、トラッカー ミュージックを再生できる別個のサウンド チップ AY-3-8910 が含まれています。

z88dk のツイーターを使用するためのライブラリが提供されています

今後学ばなければならないこと

私は、Spectrum について知り、z88dk を使用してゲームを実装し、多くの興味深いことを学ぶことに興味がありました。たとえば、Z80 アセンブラを使用すると、Spectrum のフルパワーを使用したり、メモリ バンクを操作したり、AY-3-8910 サウンド チップを操作したりできるため、まだ学ぶべきことがたくさんあります。来年もコンテストに参加したいと思っています!

リンク

https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/

ソースコード

https://gitlab.com/demensdeum/ zx-projects/tree/master/interceptor2020

デスマスク ワイルド ベータ版

ゲーム Death-Mask がパブリック ベータ ステータス (ワイルド ベータ) に入ります
ゲームのメイン メニュー画面が再設計され、心地よい音楽をバックにテクノラビリンスのブルー ゾーンのビューが追加されました。

次に、ゲームプレイ コントローラーを再加工し、古いシューティング ゲームのようなスムーズな動き、ボックス、武器、敵の高品質 3D モデル、ポータルだけでなくテクノラビリンスの他のレベルに移動できる機能を追加する予定です (エレベーター、ドア、床の穴、壁の穴などの落下など、生成される迷宮の環境に少し変化を加えていきます。ゲームバランスについても
取り組んでいきます。スケルトン アニメーションは、リリース前の磨き段階として追加されます。< /p>

Windows 用デスマスク プロトタイプ

ビデオ:

Windows バージョン:
http://demensdeum.com/games/deathMask/prototype/DeathMaskPrototype.exe

Linux および OS X ユーザーは、ソースからゲームをビルドできます。
https://github.com/demensdeum/Death-Mask

次に、ゲームプレイ要素、照明、アニメーションを追加します。