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

Leave a Comment

Your email address will not be published. Required fields are marked *