この記事では、Rustで小さなゲームを開発した私の個人的な経験についてお話します。 作業バージョンを作成するのに約24時間かかりました(主に夕方または週末に作業しました)。 ゲームはまだ終わっていませんが、この経験は役に立つと思います。 私が学んだことと、ゲームをゼロから構築するときに行われた観察のいくつかについて説明します。
Skillboxの推奨事項: 2年間の実践コース「私はPRO Web開発者です。 」
「Habr」の読者には、「Habr」プロモーションコードを使用してSkillboxコースに登録すると10,000ルーブルの割引があります。
なぜ錆びたのですか?
この言語を選んだ理由は、この言語について多くの良いことを聞いたからであり、ゲーム開発の分野でますます人気が高まっていることがわかります。 ゲームを書く前は、Rustで簡単なアプリケーションを開発した経験はほとんどありませんでした。 これは、ゲームの作成中に特定の自由を感じるのにちょうど十分でした。
なぜまさにゲームであり、どのようなゲームなのか?
ゲームを作るのは楽しいです! もっと理由はありますが、「ホーム」プロジェクトでは、通常の仕事にあまり関係のないトピックを選択します。 どんなゲーム? Cities Skylines、Zoo Tycoon、Prison Architect、テニス自体を組み合わせたテニスシミュレーターのようなものを作りたかったのです。 一般的には、人々が遊びに来るテニスアカデミーに関するゲームでした。
技術研修
Rustを使用したかったのですが、「ゼロから」開始する方法が正確にはわかりませんでした。 ピクセルシェーダーを作成してドラッグアンドドロップを使用したくなかったため、最も柔軟なソリューションを探していました。
私があなたと共有する有用なリソースを見つけました:
- ゲームはまだですか -ゲーム開発に必要なRust要素のリスト。
- Rust game dev subreddit;
- 無料のピクセルアート。
私はいくつかのRustゲームエンジンを調査し、最終的にはPistonとggezを選択しました。 前のプロジェクトに取り組んでいるときに出会った。 結局、私はggezを選択しました。小さな2Dゲームを実装するのにより適しているように思えたからです。 ピストンのモジュール構造は、初心者の開発者(またはRustを初めて使用する人)には複雑すぎます。
ゲーム構造
プロジェクトのアーキテクチャについて少し考えました。 最初のステップは、「土地」、人々、テニスコートを作ることです。 人々は裁判所を動き回って待たなければなりません。 プレイヤーは時間とともに向上するスキルを持っている必要があります。 さらに、新しい人や裁判所を追加できるエディタが必要ですが、これはもはや無料ではありません。
すべてを考えて、私は仕事に取り掛かりました。
ゲーム作成
開始:サークルと抽象化
私はggezから例を取り、画面上に円を得ました。 すごい 今、いくつかの抽象化。 ゲームオブジェクトの概念を無視するのは良いことだと思いました。 以下に示すように、各オブジェクトをレンダリングおよび更新する必要があります。
// the game object trait trait GameObject { fn update(&mut self, _ctx: &mut Context) -> GameResult<()>; fn draw(&mut self, ctx: &mut Context) -> GameResult<()>; } // a specific game object - Circle struct Circle { position: Point2, } impl Circle { fn new(position: Point2) -> Circle { Circle { position } } } impl GameObject for Circle { fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { Ok(()) } fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { let circle = graphics::Mesh::new_circle(ctx, graphics::DrawMode::Fill, self.position, 100.0, 2.0)?; graphics::draw(ctx, &circle, na::Point2::new(0.0, 0.0), 0.0)?; Ok(()) } }
このコードのおかげで、オブジェクトの優れたリストを取得でき、同様に優れたループで更新およびレンダリングできます。
mpl event::EventHandler for MainState { fn update(&mut self, context: &mut Context) -> GameResult<()> { // Update all objects for object in self.objects.iter_mut() { object.update(context)?; } Ok(()) } fn draw(&mut self, context: &mut Context) -> GameResult<()> { graphics::clear(context); // Draw all objects for object in self.objects.iter_mut() { object.draw(context)?; } graphics::present(context); Ok(()) } }
main.rsにはすべてのコード行が含まれているため、必要です。 ファイルを分離し、ディレクトリ構造を最適化するために少し時間を費やしました。 以下は、それがどのように見え始めたのかです。
リソース->これはすべてのアセットが存在する場所です(画像)
src
-エンティティ
-game_object.rs
-circle.rs
-main.rs->メインループ
人、フロア、画像
次のステップは、Personゲームオブジェクトを作成し、画像をロードすることです。 すべてが32 * 32タイルに基づいている必要があります。
テニスコート
テニスコートがどのように見えるかを研究したので、私はそれらを4 * 2タイルで作ることにしました。 最初は、このサイズの画像を作成するか、8つの個別のタイルを作成することができました。 しかし、その後、2つの一意のタイルのみが必要であることに気付きました。
合計で、1と2の2つのタイルがあります。
コートの各セクションは、タイル1またはタイル2で構成されています。通常のように配置するか、180度逆さまにすることができます。
建設のメインモード(アセンブリ)
サイト、人、およびマップのレンダリングを達成した後、基本的なビルドモードも必要であることに気付きました。 次のように実装されました。ボタンが押されると、オブジェクトが選択され、クリックすると適切な場所に配置されます。 そのため、ボタン1ではコートを選択でき、ボタン2ではプレーヤーを選択できます。
ただし、1と2の意味を覚えておく必要があるので、ワイヤフレームを追加して、選択されているオブジェクトを明確にします。 外観は次のとおりです。
アーキテクチャとリファクタリングに関する質問
今、私はいくつかのゲームオブジェクトを持っています:人、コート、フロアです。 ただし、ワイヤフレームが機能するためには、オブジェクト自体がデモンストレーションモードにあるのか、単にフレームが描画されているのかをオブジェクトの各エンティティに伝える必要があります。 これはあまり便利ではありません。
いくつかの制限が明らかになるように、アーキテクチャを再考する必要があるように思えました。
- それ自体を表示および更新するエンティティの存在は問題です。なぜなら、このエンティティはレンダリングする必要があるものを「知る」ことができないためです-画像とワイヤフレーム。
- 個々のエンティティ間でプロパティと動作を交換するためのツールがありません(たとえば、is_build_modeプロパティやレンダリング動作)。 継承を使用できますが、Rustでそれを実装する通常の方法はありません。 本当に必要だったのはレイアウトです。
- 人々を裁判所に割り当てるには、エンティティ同士が相互作用するためのツールが必要でした。
- エンティティ自体はデータとロジックの混合物であり、すぐに手に負えなくなりました。
さらに調査を行って、ゲームで一般的に使用されているECSアーキテクチャ、Entity Component Systemを発見しました。 ECSの利点は次のとおりです。
- データはロジックから分離されています。
- 継承ではなくレイアウト。
- データ指向アーキテクチャ。
ECSには3つの基本概念があります。
- エンティティ-識別子が参照するオブジェクトのタイプ(プレーヤー、ボール、またはその他のもの)。
- コンポーネント-エンティティはそれらで構成されます。 例としては、レンダリングコンポーネント、レイアウトなどがあります。 データウェアハウスです。
- システム-オブジェクトとコンポーネントの両方を使用し、さらにこのデータに基づく動作とロジックが含まれます。 例としては、レンダリング用のコンポーネントを使用してすべてのエンティティを反復処理し、レンダリングを実行するレンダリングシステムがあります。
勉強した後、ECSがそのような問題を解決することが明らかになりました:
- エンティティのシステム編成に継承ではなくレイアウトを使用します。
- 制御システムに起因するコードのハッシュを取り除く。
- is_build_modeなどのメソッドを使用して、ワイヤーフレームのロジックをレンダリングシステムの同じ場所に保存します。
ECSの実装後に何が起こったのかを次に示します。
リソース->これはすべてのアセットが存在する場所です(画像)
src
-コンポーネント
-position.rs
-person.rs
-tennis_court.rs
-floor.rs
-wireframe.rs
-mouse_tracked.rs
-リソース
-mouse.rs
-システム
-rendering.rs
-constants.rs
-utils.rs
-world_factory.rs->ワールドファクトリ関数
-main.rs->メインループ
裁判所に人を割り当てる
ECSは生活を楽にしました。 これで、エンティティにデータを追加し、このデータに基づいてロジックを追加する体系的な方法ができました。 そして、これにより、裁判所による人々の分布を整理することが可能になりました。
私がしたこと:
- 割り当てられた裁判所に関するデータを個人に追加。
- 分散した人々に関するデータをTennisCourtに追加しました。
- CourtChoosingSystemを追加しました。これにより、ユーザーとサイトを分析し、アクセス可能な裁判所を見つけて、プレーヤーをそれらに配布できます。
- 裁判所に割り当てられた人々を検索するPersonMovementSystemシステムを追加し、彼らがそこにいない場合は、必要に応じて人々を送ります。
まとめると
このシンプルなゲームでの作業は本当に楽しかったです。 さらに、Rustを使用して作成したことを嬉しく思います。
- Rustは必要なものを提供します。
- 彼は優れたドキュメントを持っています。Rustは非常にエレガントです。
- 恒常性はクールです。
- クローニング、コピー、または他の同様のアクションに頼る必要はありません。これは私がC ++でよくしたことです。
- オプションは作業に非常に便利であり、エラーも完全に処理します。
- プロジェクトをコンパイルできれば、99%で機能し、正確に機能するはずです。 コンパイラエラーメッセージは、私が見た中で最高のものです。
Rustでのゲーム開発はまだ始まったばかりです。 しかし、Rustをすべての人に公開するために取り組んでいる安定したかなり大きなコミュニティが既に存在します。 したがって、私は言語の将来を楽観的に見て、共通の仕事の結果を楽しみにしています。
Skillboxの推奨事項:
- オンラインコース「Profession frontend-developer」 。
- 実践コース「Mobile Developer PRO」 。
- 実用的な年次コース「PHP開発者0からPRO」 。