2025-01-22

Godotでシンプルな3マッチパズルゲームを作って公開するまで

Godotの勉強の成果としてシンプルな3マッチパズルゲームを作って公開しましたので、初めてGodotに触れた感想を残しておこうと思います。

当初はGDevelopで作る予定だった

まず、GDevelopで3マッチパズルを作ろうと思って以下の2つのアプローチを考えていました。両方のやり方を試してみて、実装の手間が少ない方を採用する予定でした。

  • 一時的な当たり判定オブジェクトを用意して、同色のピースが並んでいるかチェックする瞬間だけ作成と破棄を行う。重力による落下はピースに重力のビヘイビアを設定して実現する。
  • パズルゲームの盤面を2次元配列で表現する。同色のチェックや重力による落下は2次元配列を処理することで実現し、パズルのピースは2次元配列の内容に合わせて描画する。

最初に2次元配列を使わない方法を試していたのですが、隣接する同色のピースのチェック処理を途中までイベントシートに書いた時点で、想像以上に複雑になっていました。さらに都合の悪いことに、GDevelopのイベントシートは同じ種類のオブジェクト同士が衝突した時に双方を区別した処理を書くのが難しい問題があるため、継続を断念しました。

次に2次元配列を処理する方法を進めてみましたが、イベントシート内に2次元配列を処理するためのロジックが膨らんでいくにつれて「こういう処理はスクリプトでやった方が良いのでは?」と思うようになりました。そこでGDevelopのJavaScript APIを調べてみたのですが、JavaScriptとGDevelopの間でデータをやりとりする際にコツが必要で、作成中のイベントシートに加えて自作のJavaScriptまで管理するのは負担が大きすぎると思ったので、最初からスクリプトが使える他の開発環境を探すことにしました。

スペックが高くないパソコンでも動作するGodotに決めた

スクリプトが使えるゲーム開発環境を探してみると、やはりUnityが筆頭で、時々GameMaker Studioが挙がりました。Unity関連の記事を見ていくと、2023年の料金体系の騒動がまだ記憶に新しく、そこでGodotが比較対象として出ていました。
Godotについては名前こそ知っていたものの、スクリプトがPythonに似ているとはいえ独自のルールを持っているので、追加で学ぶ時間が必要になる点が気に掛かりました。しかし、様々なツールが統合された開発環境であること、比較的スペックの低いパソコンでも動作することが自分にとって魅力的だったので、最終的にGodotをインストールしました。

しばらくは公式ドキュメントを読む日々

GDevelopを利用し始めた時はシーンエディタを触りながら使い方を覚えましたが、Godotについてはシーンツリーのような根幹技術について理解が必要だと感じたため、当初は余暇に公式ドキュメントを上から読み進めつつ、サンプルゲームやチュートリアル動画を時々見る日々でした。
公式ドキュメントの流れとしては、序盤のGetting startedの時点でサンプルゲームを作りますが、この時点では作ったゲームは動作しても、シーンツリーの使い方が合っているのか判断が付かない状態でした。

やっと3マッチパズル自体の複雑さに気付く

Godotでスプライトに画像ファイルを設定したりGUIを画面に配置したり、といった初歩的な操作に慣れてくると、いよいよスクリプトでパズル部分の処理の実装に取り掛かるわけですが、ここでようやく3マッチパズルの仕組みが見た目よりもずっと複雑だということに気づきました。

  • T字やL字のようなパターンを考えだすと途端に処理が複雑になる。どこまでの形を1つのグループとみなすか?
  • ピースの組み合わせを表すデータ構造を考える必要がある。可変長配列に同じグループのピースを入れるとして、並ぶ方向がタテかヨコかをどのように表現するか?
  • ピースが消せると分かったとしても、即座にピースのデータを削除すると問題がある。例えばL字の場合、タテのラインを先に削除すると、ヨコのラインのピースが2個以下になった時に消せなくなってしまう。
  • ピースをまとめて4個以上消した場合に、特別なピースが作成されるようにしたいが、どの位置に配置するのが適切か? L字やT字の場合はタテとヨコのラインが重なる位置が良さそうだが、プレイヤーが操作したピースの位置に配置するのも良さそうに見える。
  • これ以上ピースを動かせない時のルールを決めないと、プレイヤーが途中で遊べなくなってしまう。詰み状態をCPUが判断するのか? それともプレイヤーが自主的にシャッフルやギブアップを選択するのか?

今回はできるだけ複雑な要素を減らすことを重視した結果、盤面のサイズは5×6で固定、特別な能力を持つピースは3種類だけにしました。(同色のピースをまとめて4個消すとタテまたはヨコに1ライン消すピースが発生、5個以上なら同色のピースをまとめて消す虹色のピースが発生)
また、同色のピースの並び方で特別にサポートする形は、L字とT字だけにしました。もし同色のピースが密集していた場合、虹色のピースが2個以上発生する可能性があります。

Tweenが大活躍

ピースが上から降ってきたり、スワイプで移動したり、あるいは消える直前に色が薄くなる処理や、スコアの上昇まで、動きのある部分はTweenで実装しました。状態変数を用意してフレーム単位で細かい制御をする手間を減らせたのは、Tweenの貢献が大きいです。
特に興味深い利用法はスコアの上昇で、スコアが100増えた時に段階的に加算する処理が簡単に実装できました。

どこに公開するか

UIを付けて見た目を整えて、最低限のBGMとSEも加えて、何とか完成の目途が立ちました。後は公開する場所です。GDevelopの場合は手軽にアップロードできるgd.gamesがありましたが、Godotの場合はitch.ioような公開先を探して登録するか、自前でセットアップする必要があります。
当初はGoogle Cloudに静的ファイルとして配置することも考えていましたが、調べていくうちに思った以上に設定が面倒な感じだったので、改めて公開できる場所を探すことにしました。
たまたま検索にヒットしたのがGodotPlayerで、ありがたいことにGodotの30MB以上あるランタイムを含めたHTMLファイル一式をアップロードして公開することができました。

これから

今回はGodotを利用しましたが、開発環境やエンジンごとに強みが違うので、またやりたいことがあったら改めてツールを探すところから始めます。

2025-01-20

GDevelopを触ってみた感想

2024年に小さな2Dプラットフォームゲームをリリースしましたが、その時の感想を書いていなかったので書き残しておこうと思います。

良かった点

2Dプラットフォームゲームを始めやすい

当初はPhaser.jsなどのライブラリを調べたこともありましたが、自分が使える時間を考えると最初から各種エディタやツールが統合された開発環境を利用した方が良い、と判断しました。
そこでUnity2Dにほんのちょっとだけ触れてみて、アングリーバード風のサンプルゲームを見たりもしたのですが、やりたいことに対してUnityは巨大すぎる印象を受けたので、機能が2Dに限定された開発環境を探すことにしました。
2D向けのゲーム開発環境は、シンプルな代わりに機能が限定的すぎるものや、機能が充実している代わりにゲームプログラミングの深い理解を要するものなどがあり、最終的に2Dプラットフォームゲームが一番作りやすそうなGDevelopに決まりました。
シーンエディタにドラッグ&ドロップで敵キャラやアイテムを配置して、イベントシートに衝突時の処理を書くだけでそれなりにゲームになるので、とっつきやすかったです。

無料のゲーム開発環境の中では比較的情報が多い

WebでGDevelopについて検索すると、公式ドキュメントか他のユーザーの記事かフォーラムのやりとりが何かしら見つかったので、少なくとも2Dプラットフォームゲームの作成を始める際の情報には困りませんでした。
また、開発環境の入力欄の1つ1つに説明文が付いている場合が多いので、特に新しいビヘイビアを利用する際に理解しやすかったです。
作成中のゲームに新しい機能が必要になった時の典型的な流れとしては、例えばキャラクターを点滅させたい場合、まずWebで検索してフォーラムのやりとりがヒットして「Flashビヘイビアというものがある」と分かり、開発環境に戻ってキャラクターにFlashビヘイビアを設定すると、点滅の時間の入力欄と説明文が表示されるので、入力してプレビューで確認する、という順序で解決しました。

作ったゲームを公開する場所がある

昔、Windows向けのミニゲームを作っていた時は、自分のWebサイトにEXEファイルを含めたZipアーカイブを置いていました。シンプルなゲームであれば最初からWebで遊べた方が利用しやすいのですが、当時は古い技術が廃止されたり新しい技術が発展途上だったりと、なかなか手を出しづらい状況でした。
GDevelopはWebアプリ(旧HTML5アプリ)としてゲームをエクスポートできるだけでなく、エクスポート先としてgd.gamesが用意されているので、公開できる場所を探したり自前で公開するためのセットアップ作業をせずに済みました。
もちろんEXEファイルをエクスポートすることも可能で、GDevelopのクラウドサービスでビルドする場合は無料アカウントだと1日1回までですが、自力でElectronをインストールしてビルドする方法もあり、公式ドキュメントに手順が書いてあります

既存機能で出来ないことがあった時の拡張性が高い

GDevelopも他のゲーム開発環境と同様に拡張機能(エクステンション)をサポートしているため、2D向けの高精度な物理演算やコヨーテタイム(落下中でもジャンプできる猶予時間)などの機能を作成中のゲームに組み込むことができます。多くのビジュアルプログラミング環境と同様に、複雑な処理は外部の拡張機能に任せるのがベストな選択です。
一方で、目的の機能そのものを持つ拡張機能が無かった場合、拡張機能をJavaScriptで自作するか諦めるかの単純な2択にならないのがGDevelopの特徴で、GDevelopのイベントシートは複雑な処理が書けるようにプログラミング言語の要素が一部含まれています(ローカル変数、foreachループやwhileループ、関数呼び出しなど)。デバッグの労力が増大する危険性はありますが、ゲーム内の一部の処理だけ複雑な実装をする必要がある場合に、いきなりGDevelopのJavaScript APIについて調べなくても、対応する余地はあります。

ここまではGDevelopの良かった点ですが、懸念事項も書いておきます。

懸念点

イベントシートの条件分岐の書き方が冗長になりがち

イベントシートで「変数>=0」のイベントに加えて「それ以外(プログラミング言語のelseに相当)」のイベントを用意したい場合、明示的に「変数<0」のイベントを作成する必要があります。条件が複雑な場合はNOT演算子に相当する命令を使うことで省力化できますが、元々の条件が変わる度に「それ以外」のイベントの方も条件の修正が必要になります。
代替案として、ローカル変数でフラグを用意して「変数>=0」の場合にフラグを立てるものとし、次にフラグが立っていない時に実行するイベントを用意すれば疑似的に「それ以外」のイベントとして動作しますが、やや冗長に見えます。
複雑な条件を1つのイベントにまとめて書くと読みにくくなる場合、サブイベントに分けて書くことも可能ですが、サブイベントの階層が深くなるため、後からイベントを追加する時に適切な階層を選択するように注意が必要です。

途中で処理を打ち切りたい場合はフラグが必要になる

前述の「それ以外」イベントの課題と関連しますが、イベントシートにはプログラミング言語のbreakのように処理を打ち切る方法がないため、代わりにフラグを用意して処理をスキップする必要があります。フラグが立った時の処理をサブイベントにすれば、フラグが立っていない時にまとめてスキップさせることが可能になります。

イベントに書いた内容は条件が成立する限り毎フレーム実行される

プレイヤーキャラクターの歩くアニメーションが終わった時に、歩数カウンターを増やしたいとします。もし現在のアニメーション名が"Walk"であることだけを条件に歩数カウンターを増やすイベントを書いた場合、プレイヤーキャラクターのアニメーション名が"Walk"になった直後から毎フレームで歩数カウンターが増加します。この場合、アニメーションが完了したことも条件に追加する必要があります。
イベントが毎フレーム実行される仕様は、イベントシートが大きくなって各イベントに目が届かなくなるほど厄介になります。ひとまとまりの処理をサブイベントにすることで、メインイベントの条件を満たした時だけ実行されるように制御できます。
また、「1回だけ実行」という条件を追加するとイベントが連続して実行されなくなるため便利ですが、意図したタイミングで実行されない場合もあるため、必要に応じてアニメーション終了やタイマーなどの他の条件が使えないか調べてみることも重要です。

ピクセルエディターの挙動が独特

GDevelopのピクセルエディターはレイヤー機能や同色範囲の選択ツールが使えるなど、ゲーム開発環境に付属しているお絵かきソフトとしてはそこそこ高機能なのですが、他のペイントツールと微妙に操作方法が違うので戸惑います。また、画像を複製してから変更した時に他の複製画像まで同じ変更で上書きされてしまうことがあります。
ちょっと調べてみた限りでは、piskelというGDevelopとは全く別の製品が動作しているため、動作に変なところがあってGDevelopチームに指摘しても、piskelの関係者ではないので対応は難しいようです。
最初から画像データの作成は他のペイントツールで行い、アニメーションの作成だけGDevelopで行うことで影響を小さくできますが、統合された開発環境としてのメリットは低下します。
また、ピクセルエディターで画像を作成する際に、早い段階で名前を指定しておかないとNewSprite.png、NewSprite2.png、NewSprite3.png...という具合に自動でファイル名が設定されます。リソースエディタで画像リソースと画像ファイルの関連付けを変えることができるので、画像ファイル名を変更してから画像リソースとの関連付けを再設定することもできますが、次々と作成されるNewSprite.pngに対応するのは大変なので、基本的に放っておくのが一番手間の掛からない対処法です。

イベントシートをデバッグするのが難しい

シーンエディタに様々なオブジェクトを追加したりイベントシートに処理を追加したりと、色々追加しているうちは良いのですが、全てが変な動きをしないようにデバッグするのは難しいです。ある程度の変な動きはあきらめて、自分がゲームの中で重要だと思っている部分に注力した方が効率的かもしれません。
GDevelopのデバッガーは変数の中身を細かく確認できる上に、ゲームを一時停止することもできるので便利ですが、デバッガーで特定のオブジェクトが予期せぬ状態に変わったことは分かっても、その原因を調べるのには苦労します。
同じような管理が必要なオブジェクトはグルーピング機能でまとめて、さらにイベントシートの一部を外部イベントや拡張機能として分離することで、問題の発生個所が見つけやすくなるよう工夫することはできます。


以上のように、ビジュアルプログラミングとイベントシートという特徴に起因した難点はあるものの、プログラミングが必須ではないゲーム開発環境としては必要な機能が一通り揃っており、取り組みやすいソフトであることは間違いないです。

2025-01-13

Godotでシンプルな3マッチパズルゲームを作って公開しました

この年末年始はGodotの勉強のために3マッチパズルを作ってました。パズルの基本部分だけ実装できたので年始の終わりに一度公開しましたが、さらにUIや攻略できるステージを整備してリリースしました。


Simple Match 3-5x6 - フリーゲーム投稿サイト GodotPlayer

https://godotplayer.com/games/syjiro_simple_match3_5x6




[基本的なルール]

  • ピースをマウスドラッグかスワイプで動かして、2つのピースを入れ替えてください。
  • 同じ色のピースをタテかヨコに3個以上並べると消えます。
  • ピースを入れ替える度に、画面上部に表示されている移動回数が減ります。0になるとゲームオーバーです。
  • 同じ色のピースを4個並べると、タテまたはヨコに一直線に消せるピースが出現します。
  • 同じ色のピースを5個以上並べると(L字型やT字型も含む)、同じ色のピースをまとめて消せる虹ピースが出現します。

[ステージの紹介]

  • ENDLESS:500点ごとに移動回数が増加
  • Stage1~5:画面左上に提示されているピースを消す
  • Stage6~10:氷ブロックを消す(氷ブロックは近くのピースが消えると一緒に消える)
  • Stage11~15:上から落ちてくるピースは、斜め下が空いていると斜め下にも落ちる
  • Stage16~20:凍っているピースは1回消すと中身のピースが現れる
  • Stage20~25:水滴を一番下まで落とす
  • Stage26~30:上から落ちてくるピースが6色に増える

3マッチパズルのスマホゲームは広告も含めて大量に目にしてきましたが、いざ自分で作ってみると思った以上に大変でした。ルールが単純なので、少量のコーディングですぐに終わるだろうと甘く見ていましたが、大間違いでした。その辺の苦労はまた別の記事で触れたいと思います。

2024-09-18

GDevelopでプロジェクトのassetsフォルダに未使用の画像ファイルが残る

GDevelopでスプライト用の画像を描いたりアセットを入れたりしていると、プロジェクトのassetsフォルダに未使用のファイルが溜まっていきます。

他の記事の調査で作ったプロジェクトのリソースタブを開いてみると、Stop2~5の4つの画像ファイルが表示されました。

一方で、Windowsのエクスプローラでassetsフォルダを確認してみると、7つの画像ファイルが入っています。未使用の画像ファイルが3つあるということです。

プロジェクトをHTML5形式でエクスポートしてみると、使用中の4つの画像ファイルだけがコピーされていました。


ただし、リソースを整理するつもりで不要なファイルをリソースタブに表示している場合は注意が必要です。

今回のプロジェクトのリソースタブで右クリックメニューの「プロジェクトフォルダ内をスキャン>画像」を実行すると、不要なファイルも含めた7つの画像ファイルが一覧表示されます。

この状態でエクスポートすると、未使用の画像ファイルを含めた7つの画像ファイルがコピーされていました。


このような場合は、リソースタブで右クリックメニューの「未使用のファイルを削除...>画像」を実行すると、一覧から未使用の画像が削除されます。ただし、この操作は一覧に表示されなくなるだけで、assetsフォルダには引き続き画像ファイルが残ります。

未使用の画像ファイルが残るのが気になる場合は、事前に空のフォルダを作った後で、GDevelopのファイルメニューで「名前を付けて保存」で空のフォルダを保存先にすると、未使用のファイルを除いたプロジェクトフォルダの内容がコピーされます。

2024-09-17

GDevelopでテキスト型変数を利用する場合は、初期値を空欄にしない方がよい

GDevelopで変数の種類をテキストにした場合、初期値を空欄にすることができます。
初めてGDevelopに触れた時は、この時点で変数に長さ0の文字列が設定されたものと思っていましたが、どうも思ったような動作にならないので調べてみることにしました。

テキストのシーン変数を追加して、初期値を空欄にします。

プレビューしてデバッガで Scene variables を確認してみると、「"value" : "0"」となっていました。テキスト型変数を利用する場合は、1文字以上のテキストを初期値として入れておいた方がよさそうです。


代わりに、できるだけ早いタイミングで変数に""を設定する方法を考えます。イベントシートでシーン開始時に2個目のシーン変数に""を設定します。

再びプレビューしてデバッガで Scene variables を確認してみると、今度は2個目のシーン変数が「"value" : ""」になりました。


GDevelopの開発環境で変数に特定のシーン名を設定する時はリストから選びたい

GDevelopでシーンを変更する時はリストから既存のシーン名を選択できますが、変数にシーン名を設定する場合はシーン名を直接入力する必要があります。また、シーンに付けた名前を後から変更した場合、GDevelopの開発環境が影響のありそうな所を自動的に新しい名前で置換してくれますが、直接入力したシーン名は変更されません。

拡張機能を自作する案

堅実な方法としては、拡張機能を自作してテキスト型変数にシーン名を保持し、変数に対するアクセスは拡張機能の提供するアクションと式に限定する、といったやり方があります。

概要だけ示すと、例えば SceneManager という拡張機能を作成し、次のシーン名を管理するために NextSceneName という変数を追加します。シーン変数にするかグローバル変数するかは用途に応じて変えます。

SceneManager に対し、 NextSceneName を参照するための NextScene 式と、変更するための SetNextScene アクションを用意します。

イベントシートで次のシーン名を取得したい時は「SceneManager::NextScene()」式を使います。

イベントシートのアクションで次のシーンを変更する時は「その他のアクション」から SceneManager のアクションをクリックすると、リストからシーンを選択できます。

変数にアクセスする手段を制限することで不正なシーン名が入りにくくなり、名称の変更にも対応しやすくなりました(例えば SetNextScene アクションの名前を SetNextSceneName に変えた場合など)。チームで作業したり、拡張機能を不特定多数に配布する場合、このような方法でプログラムの部品を制御することが重要になります。

一方で、製作しようとしている作品の規模によっては、ここまで徹底するのは大げさすぎるかもしれません。

シーン名を渡すだけの式を作成する案

拡張機能でアクションや式のパラメーターを「シーン名(テキスト)」にすると、イベントシートで入力する際にリストからシート名を選択できるようになります。この特徴を利用して「シーン名(テキスト)のパラメーターをそのまま返すだけの式」を作成すれば、必要な時だけリストからシート名を選択できそうです。

拡張機能の式を作成して、パラメーターを「シーン名(テキスト)」にします。


式の内容は戻り値にパラメーターの値を設定するだけです。戻り値もシーン名なので、式のタイプも「シーン名(テキスト)」にします。


イベントシートの条件やアクションで「MyExt::InputSceneName("")」と入力し、カーソルを""の中に移すと、シート名をリストから選択できるようになります。もし後で該当シーンの名前が変わっても、""の中のシーン名は自動的に新しい名前に変わります。


変数に何回か特定のシーン名を設定するだけの場合は、この方法で事足りるように見えます。とはいえ開発が進んでシーン名を扱う箇所が増えていくと、最終的には SceneManager に相当する機能を自作するかアセットストアから導入することになるかもしれません。

もし将来のGDevelopが変数の種類として「シーン名(テキスト)」をサポートするようになった場合、MyExt::InputSceneNameのような機能は単純に無駄になるので、あくまでも現時点の妥協的な手段と考える必要があります。

2024-09-16

20年ぶりにゲームを作って公開しました

GDevelopで作成した短い2DアクションゲームをWebブラウザ向けに公開しました。
4つのステージでゴールに到達するとクリアです。
※スマホ非対応、キーボード入力のみ

Pixelamid Adventure | Play on gd.games
https://gd.games/syjiro/pixelamidadventure 

  • 十字キーの ← または → で移動
  • スペースキー か Z キー でジャンプ
  • X キーで武器を発射

20年以上前にHSP(Hot Soup Processor、Windows向けスクリプト環境)で作ったゲームを公開して以来なので、本当に久しぶりの個人ゲーム制作でした。
キャラクターが自動的に目的地へ歩いたり敵を倒したりするスタート画面を作ったのは初めてかもしれません。
サウンドとBGMはOtoLogic様の音声素材を使用しました。短い期間でゲームに音声を付ける目途が立ったのは、使いやすく音声ごとのテーマが分かりやすい素材の助けあってこそでした。
スプレーの説明を読むために顔を近付けるくらい年齢を感じだした日々の中で、このような貴重な経験ができたことに感謝します。
GDevelopを触ってみての感想などは別の機会に書きたいと思います。