このチュートリアルシリーズでは、スーパーマリオのような横スクロールアクションゲーム、いわゆる「プラットフォーマー」というジャンルのゲームを作っていく。今回は、初回ということで、ゲームのプレイ画面にプレイヤーキャラクターを用意して操作するところまでやってみよう。
Memo:
ゲームを作り始めるのに以下の記事もお役立てください。
Godot をダウンロードする
Godot のプロジェクトマネージャー
Godot の言語設定
Godot で作るブロック崩しシリーズ
新規プロジェクトを作る
右側の「新規プロジェクト」ボタンをクリックする。
プロジェクトを保存したい場所を選択して、右上の「フォルダーを作成」をクリックしてプロジェクトのフォルダ名を入力して「OK」をクリックする。ここではシンプルに「Platformer」と名付けたが、実際に自分のオリジナルのゲームを開発するときは仮でも独自の名前を設定しよう。
「現在のフォルダーを選択」をクリックする。
レンダラーには「OpenGL ES3.0」を選択して「作成して編集」をクリックする。Webブラウザで動くようなゲームを開発する場合はレンダラーに「OpenGL ES 2.0」を選択するが、このチュートリアルでは PC ゲームを想定しているので、これで良い。
これでエディターの初期画面が表示された。
アセットファイルを取り込む
まずはプロジェクトに必要なキャラクターや背景の画像ファイル一式(アセット)をまとめて準備しておこう。
ただし、このまま画像のインポートをすると、ドット絵(Pixel Art)の場合、特有のエッジが効いた画像ではなく、ぼやけた感じの画像に補正されてしまう。これを避けるために、事前準備として、インポートのデフォルトの設定を変更する。
具体的には、Godot エディタ左上の「インポート」ドックを開き、「プリセット」ボタンをクリックしたら「2D Pixel」を選択する。
同様に「プリセット」を開き、「‘Texture’のデフォルトとして設定」を選択する。
これでインポートの必要な設定変更ができたので、下記のリンクからアセットをまとめてダウンロードしよう。
Download Assets:
1. Dropboxの共有フォルダ > Assets.zip
2. アセット1: Pixel Adventure
3. アセット2: Pixel Adventure 21 は、このチュートリアルのために 2、3 でダウンロードしたファイルを一つにまとめたものです。2、3で直接ダウンロードする場合、制作者の方に寄付したり、作品に高評価をつけたりすることで、結果制作活動を応援することができます。ここで取り扱っているアセットファイルは Creative Commons Zero (CC0) ライセンスの下でリリースされています。ライセンスについて詳しくはこちら をご覧ください。
ダウンロードしたアセットファイルをまとめた「Assets」フォルダを丸ごと、ファイルシステム上の「res://」フォルダ直下にドラッグ&ドロップして取り込む。
ゲームのウインドウサイズを設定する
事前準備がもう少しある。実際にゲームをプレイする時のウインドウサイズを設定しておこう。
「プロジェクト」メニュー>「プロジェクト設定」を選択する。
「一般」タブを開いた状態で、「検索」ボタンをクリックして「window」と検索しよう。すると「Display」カテゴリの「Window」という項目が見つかるはずだ。これを選択してウインドウサイズを設定する。
「Size」セクションの「Width(幅)」と「Height(高さ)」を 384 x 256 px に設定する。これから使用するキャラクターのスプライトの画像が 32 x 32 px なので、32 で割り切れるサイズにした。
続けて「Stretch」セクションの「Mode」を「2d」に変更し、「Aspect(縦横比)」を「keep」にしておこう。
Level1 シーンを作る
レベルというと、日本ではRPGのキャラクターの成長を表す指標としてよく用いられる単語だが、海外のゲームの場合、日本のアクションゲームやシューティングゲームで階層を表す「ステージ」に相当する単語として用いられる。海外のスタンダードにも慣れておきたいので、このチュートリアルでも「ステージ」ではなく「レベル」という単語を使用していく。
さっそくだが、まずは最初のレベルである「Level1」のシーンを作っていく。
プロジェクトを作ってすぐの画面では、左側のシーンドックでルートノードを生成できる。ここでは「2D シーン」を選択しよう。これを選択すると「Node2D」クラスのノードがルートノードとして作られる。
Node2D ノードの名前を「Level1」に変更しよう。
そのまま「シーン」メニュー>「シーンを保存」、もしくは Windows: Ctrl + S / macOS: Cmd + S のショートカットでシーンを保存する。このとき、レベルのシーンをまとめるためのフォルダを先々のために用意しておく。まずは表示されたパネルの右上にある「フォルダーを作成」をクリックする。
「Levels」と名付けて「OK」をクリック。
Level1 シーンのファイル名を「Level1.tscn」として「保存」をクリックする。
これでゲームの舞台となる「Level1」シーンが用意できた。この舞台に、背景を配置したり、役者であるプレイヤーキャラクターや、敵キャラクター、コインなどのアイテム、ゴールなどを配置して、徐々にゲーム画面を作り上げていく。
プレイヤーキャラクターのノードを作る
「Level1」ノードが同名の「Level1」シーンのルートノードであり、そのルートノードに紐づくノードを子ノードと言う。子ノードにさらにノードがぶら下がって孫ノードとなり、これらのルートの下に紐づく子ノード以下のノードの集まりをブランチという。
シーンを一つの木だとすると、ルートノードが木の幹、ブランチが枝葉である。このような構造を一般的にツリー構造と呼ぶ。Godot の一つの特徴として、このツリー構造を最大限活かして、コンポーネントの構成が非常に分かりやすくなっている。
ではルートノード「Level1」にプレイヤーキャラクターとして必要なノードを追加していこう。「Level1」ノードを選択した状態で、上の「+」ボタン、もしくはショートカット(Windows: Ctrl + A / macOS: Cmd + A)でノードを追加する。
最初に追加するのは「KinematicBody2D」クラスのノードだ。検索して選択したら「作成」ボタンをクリックすれば、このクラスのノードが追加される。「KinematicBody2D」ノードの名前を「Player」に変更しておこう。
次に「Player」ノードを選択して、子ノードを追加する。「AnimatedSprite」クラスのノードを追加しよう。
同様に、「Player」ノードを選択した状態で、子ノードをもう一つ追加する。今度は「CollisionShape2D」クラスのノードを追加しよう。
ここまでで、シーンドックはこのように表示されていればOKだ。
AnimatedSprite ノードを編集する
ただ一枚の画像をスプライトとして割り当てるなら「Sprite」クラスのノードを使用するが、スプライトに複数の画像を割り当ててアニメーションさせたいときは、「AnimatedSprite」クラスのノードを使用する。
公式オンラインドキュメント:
AnimatedSprite
シーンドックで「AnimatedSprite」ノードを選択した状態で、右側のインスペクタードックを見てほしい。
では、一番上の「Frames」プロパティを編集する。「空」の箇所をクリックするとプルダウンメニューが表示されるので、「新規 SpriteFrames」を選択しよう。
次に「Playing」プロパティをオンにしよう。デフォルトではオフになっているが、それではアニメーションが再生されないから要注意だ。
プロパティの編集はひとまずこれだけだ。次に、一番上の「Frames」プロパティの「SpriteFrames」をクリックしよう。
すると Godot エディタ下部の「スプライトフレーム」パネルが開く。ここでアニメーションを作っていく。
元々一つ用意されているアニメーションの名前を「defalut」から「idle」に変更しよう。
このアニメーション「idle」にスプライトシート(複数の画像が一つの画像ファイルにまとめられたもの)を割り当てる。「アニメーションフレーム」セクション上部の左から二番目のアイコンをクリックしよう。
このチュートリアルで使うプレイヤーキャラクターは「Pink Man」にしたいので、先ほどインポートしたアセットファイル一式から「res://Assets/Main Characters/Pink Man」フォルダまでたどり、「idle (32x32).png」ファイルを選択して「開く」を押す。
今度はパネル左上を見てみよう。スプライトシート上で水平方向と垂直方向にいくつずつ画像が分けられているのかを指定する。今回開いたスプライトシートを見ると、画像は横一列に11種類用意されているので、ここでは「水平:11、垂直:1」としておこう。この数値がおかしいと、画像が途中で切れたり、隣の画像が入ってきたりするのでよく見て設定してほしい。
続けてパネル右上を見てみると「すべてのフレームを選択/消去」ボタンがある。これをクリックしよう。
すると、シートの画像が正しい区切りで全て選択できた。
最後にパネル下部の「11フレームを追加」をクリックする。
これでスプライトシート上の全てのスプライトのテクスチャ画像を一括で取り込むことができた。
アニメーションの速度を変更しておこう。値は 1 秒あたりのフレーム数(FPS: Frames Per Second)だ。デフォルトは 5 になっており、値が大きくなるほど、アニメーションの動きが速くなる。ここでは 24 とした。
ちなみに、アニメーションフレームセクション右上の (+) と (-) ボタンで画像の拡大縮小が可能だ。
同様の手順で他のスプライトシートの分もアニメーションを用意しておこう。
なお「Pink Man/Fall (32x32).png」と「Pink Man/Jump (32x32).png」はスプライトシートではなくテクスチャ画像が1つだけのファイルなので、単純にファイルシステムドックからアニメーションフレームへドラッグ&ドロップしたほうが早い。
インスペクターで「Animation」プロパティの選択を切り替えながら、2D ワークスペース上でそれぞれのアニメーションをチェックしてみよう。
最終的に作ったアニメーション全てを使うかどうかはまだこの時点ではわからないが、ひとまずプレイヤーキャラクターのアニメーション作成は完了だ。
CollisionShape2D ノードを編集する
ここからは「CollisionShape2D」ノードのプロパティを編集していく。とは言っても、比較的直感的に作業することが多いので、サクッと終えられるに違いない。さっそくコリジョン形状を設定しよう。
公式オンラインドキュメント:
CollisionShape2D
シーンドックで「CollisionShape2D」ノードを選択したら、インスペクターで「Shape」プロパティのプルダウンメニューをクリックし、「新規CapsuleShape2D」を選択しよう。「新規RectangleShape2D」でも良いのだが、スプライトのテクスチャが丸みのあるデザインのため、今回は四角ではなくカプセル型にした。
次に2Dワークスペースで、今設定したばかりのコリジョン形状を調整していく。まずはドラッグ操作で調整しやすいように、ツールバーのスナッピングオプションアイコンをクリックして、「ピクセルスナップを使用」にチェックを入れておく。これで 1 pixel 単位でコリジョン形状がスナップするので、調整しやすいはずだ。ドット絵の場合は特に便利なので覚えておこう。
そのまま2Dワークスペース上で、コリジョン形状の上と右にある 赤い丸
をドラッグしてサイズと形を調整しよう。下のスクリーンショットのように、横幅はスプライトテクスチャの体の幅に、縦の高さはスプライトテクスチャの頭のてっぺんから足までの長さに、それぞれ合わせられたらOKだ。
調整が完了したら、「Player」ノードの子ノードである「AnimatedSprite」ノードや「CollisionShape2D」ノードの位置が2Dワークスペース上で誤ってズレてしまわないように、子ノードを選択できないようにしよう。
まずシーンドックで「Player」ノードを選択する。
ツールバーの「子ノードを選択不可にする」アイコンをクリックするだけでOKだ。
同じアイコンがシーンドックの「Player」ノードの横に表示された。これで2Dワークスペース上で「Player」の子ノードは選択できない状態になった。
仮の足場を作る
このゲームはプラットフォーマーなので、このあとキャラクターに歩いたりジャンプしたりする動作を設定していく。しかし、その時に足場がないと、キャラクターはゲームが開始するや否や、重力によって画面の下方向に落下して消えてしまう。
そこで、「Level1」ノードに、ひとまず仮の足場として「StaticBody2D」ノードを追加しよう。名前を「TempGround」とでも変更しておくと分かりやすい。さらに「TempGround」ノードに「CollisionShape2D」ノードを追加しよう。ちなみに、マップはあとできちんと作る機会があるので安心してほしい。
「CollisionShape2D」ノードにコリジョン形状を設定する。「Shape」プロパティを「新規RectangleShape2D」とし、「Extents」プロパティ (x, y) を (128, 10) に設定しよう。
2D ワークスペースで「Player」ノードを大体中央に移動し、「TempGround」ノードをその少し下に配置する。これで仮の足場ができた。
インプットマップにアクションを追加する
インプットマップとは、ゲームの入力操作設定だ。キーボードのキーやマウスのクリック、ゲームパッドのボタンなど、ゲームで利用する予定のものを「アクション」として登録することができる。デフォルトの「アクション」もいくつか登録されているが、このチュートリアルではキーボードのキー入力をいくつか追加登録していく。プレイヤーの動きをプログラミングする前に必要な作業だ。
まずは「プロジェクト」メニュー>「プロジェクト設定」を選択する。
「インプットマップ」タブに切り替えたら、追加したい「アクション」(入力操作)に名前をつけて「追加」ボタンで登録する。
以下の4つのアクションを登録しよう。登録できたら「+」ボタンでキーを割り当てていく。
- move_right
- move_left
- jump_force
- dash
登録したアクションの「+」ボタンを押したら、入力装置の種類の選択をする。ここでは「キー」を選択しよう。これはキーボードのキーを意味している。
中央に「確認」のダイアログが出るので、この状態で設定したいキーボードのキーを実際に押下する。例えば、一つ目のアクション「move_right」の場合はキーボードの「右矢印キー」を押下すれば良い。確定するときは「OK」をクリックする。つい「Enter」キーを押下しがちだが、それだと「Enter」キーが割り当てられてしまう。
追加した4つのアクションには以下通りにキーを割り当てると良い。もちろん自分好みに変更していただいても結構だ。
- move_right: Right(右矢印キー)
- move_left: Left(左矢印キー)
- jump: Up(上矢印キー)
- dash: Space(スペースキー)
4つとも登録できたら「閉じる」ボタンをクリックして設定は完了だ。
Player ノードにスクリプトをアタッチする
さてここからは、プレイヤーキャラクターの動きを作っていく。動きを作るには、プログラミングが必要だ。プログラミングがまだ不慣れな方は、ひとまずチュートリアルのコードをコピー&ペーストして、プレイヤーキャラクターの動きを見ながら理解を深めていっていただいても良いだろう。
ところで、Godot では GDScriptという独自のプログラミング言語か、別のゲームエンジン Unity でも使用される C# と言うプログラミング言語を使用する。C# の場合、Godot とは別のエディターを使用する必要があったり、文法がやや難しかったりで、経験者向きの側面が強いことから、このチュートリアルでは、文法がより初学者にも理解しやすい GDScript を使用する。GDScript なら、Godot エディター上でそのままコーディング(コードを入力してプログラムを作っていく作業のこと)でき、ドキュメンテーションを Godot エディター上で確認できるので、よりゲーム開発の作業に集中しやすいだろう。
では「Player」ノードにプログラムを記述するためのスクリプトをアタッチしよう。シーンドックで「Player」を選択し、右上の「選択したノードに新規または既存のスクリプトをアタッチする」アイコンをクリックする。
スクリプトの保存先を変更したいので、「パス」の右側にあるフォルダーアイコンをクリックする。
パネルが開いたら、パスを確認しよう。パスがリソースのルート「res://」になっていなければ、左上の「↑」アイコンをクリックしてルートまで戻り、そこで右上の「フォルダーを作成」ボタンをクリックする。
フォルダーの名前に「Player」と入力して「OK」をクリックして確定する。
パネル上部の「パス」が今作成したフォルダーのパスになっていればOKだ。ファイル名を「Player.gd」として「開く」をクリックしよう。ちなみに、ファイルの拡張子 .gd は GDScript ファイルのことを指している。
パスが変更されたのを確認したら「作成」ボタンをクリックする。
Godot エディタの中央が、2D ワークスペースからスクリプトエディタに切り替わり、今アタッチしたばかりのスクリプト「Player.gd」が開いた状態になった。これから、このスクリプトを編集していく。
プレイヤーキャラクターの動きを実装する
まず、デフォルトで入力されているコメントや_ready
メソッドはこのスクリプトでは不要なので、削除しておこう。
次に必要なプロパティ(クラス内で定義する変数のこと)を定義していく。下記のコードを「Player.gd」スクリプトの 3 行目以下に記述してほしい。
export var acceleration = 256
export var max_speed = 64
export var max_dash_speed = 80
export var friction = 0.1
export var gravity = 512
export var jump_force = 224
export var air_resistance = 0.02
var velocity = Vector2.ZERO
コードの先頭にexport
がついているプロパティは、その値をインスペクターでも編集できるようになる。試しに「player」ノードを選択してインスペクターを見てみよう。スクリプトに記述したプロパティが表示されているのがわかるだろう。スクリプトで定義した値がデフォルトの値になっている。
export
キーワードをつければ、スクリプト上でプロパティの値を直接編集しなくても、インスペクター上で気軽に編集できる。デバッグしながら値の微調整が必要になると思われるプロパティにはexport
をつけるのがおすすめだ。
では今回定義したプロパティについてそれぞれ説明しておこう。
acceleration
: プレイヤーキャラクターの左右移動操作時の加速度。max_speed
: プレイヤーキャラクターの左右移動操作時の最大速度。max_dash_speed
: プレイヤーキャラクターの左右ダッシュ移動操作時の最大速度。friction
: プレイヤーキャラクターの左右移動操作をやめた時に受ける摩擦抵抗。gravity
: プレイヤーキャラクターが常に画面下方向に受ける重力。jump_force
: プレイヤーキャラクターのジャンプ操作時のジャンプ力。air_resistance
: プレイヤーキャラクターのジャンプ操作時に受ける空気抵抗。velocity
: 方向の要素を持ったプレイヤーキャラクターの速度、デフォルト値はVector2
型で(0, 0)。値はこのあとのプログラミングによりリアルタイムで更新される予定。
さらにスクリプトの一番下の行で、sprite
プロパティをonready
キーワードをつけて定義しよう。
onready var sprite = $AnimatedSprite
sprite
プロパティの値は、「Player」ノードの子ノードである「AnimatedSprite」ノードを定義した。onready
をつけたプロパティは、全てのノードの読み込みが終わってから定義される。ノードの読み込みは親ノードの方が子ノードより先なので、子ノードをプロパティに格納したい場合は、onready
キーワードを利用してプロパティを定義する。
このようにして、子ノードのプロパティやメソッドにアクセスする予定がある場合は、プロパティとして定義しておくと、このあとのコーディングが楽になる。また、シーンツリー(シーン内のノードの構成)に変更があっても、このプロパティの値だけ変更すれば良いので、メンテナンスがしやすいというメリットもある。Godot でのゲーム開発では割と一般的な手法だ。
次に定義するのは_physics_process
メソッドだ。このメソッドは「Node」クラスの組み込み関数だ。「KinematicBody2D」クラスは「Node」クラスを継承しているので利用できる。
_physics_process
メソッドは、物理フレーム(デフォルトでは1秒間に60フレーム)ごとにメソッド内のコードを実行してくれる。これを利用して、プレイヤーの入力操作によって、プレイヤーキャラクターの動きを制御することができる。
では具体的にやっていこう。スクリプトの一番下に次のコードを記述しよう。
func _physics_process(delta):
velocity.y += gravity * delta
_physics_process
の引数delta
は1フレームあたりの秒数(デフォルトでは1/60秒)だ。velocity.y
の値にgravity
にdelta
を乗じた値を足している。ゲーム開発では一般的に、2次元の x, y 座標で y 軸の値は、画面の下にいくほど大きく、上にいくほど小さくなる。毎フレームgravity
の値をvelocity
の y
に加算することで、プレイヤーキャラクターを常に画面下方向に動かす力を加えている。これにより、キャラクターに重力がかかっている状態を作ることができた。
さらに、次のコードを_physics_process
メソッドに追加しよう。
var x_input = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
if x_input != 0:
velocity.x += x_input * acceleration
velocity.x = clamp(velocity.x, -max_speed, max_speed)
メソッド内でx_input
変数を定義した。Input
クラスのget_action_strength
メソッドは、引数のインプットアクションが入力されれば 1 を、されなければ 0 を返す。つまり、プレイヤーが右矢印キーを押せば 1 、左矢印キーを押せば -1 、どちらも押さなければ 0 の値がx_input
に入る。
その次のif x_input != 0:
の if 構文は、右か左の矢印キーいずれかを押した場合、ということになる。
velocity.x
に、x_input
にacceleration
を乗じた値を加算している。これで、プレイヤーが右か左の矢印キーを押した方向へ、毎フレーム加速しながらプレイヤーキャラクターを移動させる仕組みができた。
しかし、このままだと毎フレーム加速し続け、とんでもないスピードでキャラクターが移動してしまう。そこで、組み込みのclamp
メソッドを利用して、velocity.x
の値が最大値および最小値の範囲内に収まるようにする。clamp
メソッドは最大値に第二引数、最小値に第三引数、どちらにも達していなければ第一引数を返す。
さらに、プレイヤーキャラクターにダッシュ機能を実装したいので、インプットアクション「dash」の入力操作、つまりスペースキーを入力しているかどうかで、条件分岐させた。if x_input != 0:
のブロックは以下のように変更した。
if x_input != 0:
velocity.x += x_input * acceleration
if Input.is_action_pressed("dash"):
velocity.x = clamp(velocity.x, -max_dash_speed, max_dash_speed)
else:
velocity.x = clamp(velocity.x, -max_speed, max_speed)
さらにif x_input != 0:
ブロックの最後にこのコードも追加しよう。
sprite.flip_h = x_input < 0
スプライトのテクスチャの水平方向に反転させるプロパティflip_h
は、x_input
が 0 より小さい値(つまり左矢印キーを押下して -1 )になっているときに有効になるようにした。
velocity
の値を実際のキャラクターの移動に反映させるために、「KinematicBody2D」クラスの組み込みメソッドであるmove_and_slide
を_physics_process
メソッドの最後に追加する。
velocity = move_and_slide(velocity, Vector2.UP)
このmove_and_slide
メソッドは、第一引数に割り当てた速度に delta を自動的に乗じて、このスクリプトがアタッチされている「KinematicBody2D」クラスのノードを移動してくれる。第二引数は、上方向がどちらかを指定する。プラットフォーマーは画面の上方向がそれに当たるのでVector2.UP
を割り当てた。これによって、自動的に天井、地面、壁がどの方向なのかを判別してくれる。
では一旦ここでプロジェクトを実行してキャラクターの動きを確認してみよう。
デバッグパネルで、足場である「TempGround」ノードのコリジョン形状が見えるようにするため、先に「デバッグ」メニュー>「コリジョン形状を表示」にチェックを入れておこう。
Godot エディタ右上の「プロジェクトを実行」アイコンをクリックする。
まだメインシーンを選択していない場合はダイアログが表示されるので、「現在のものを選択」をクリックする。
デバッグパネルが開くので動きを確認してみよう。
重力は機能して、キャラクターはきちんと足場に落ちている。スプライトのテクスチャもflip_h
プロパティの切り替えにより向きが変われば反転している。しかし、左右の移動は右または左の矢印キーを一回押下しただけで滑り続けて止まらない状態だ。さらに必要なコードを追加していこう。
今度は、velocity = move_and_slide(velocity, Vector2.UP)
のコードより前に、以下の if ブロックのコードを追加しよう。
if is_on_floor():
if x_input == 0:
sprite.play("idle")
velocity.x = lerp(velocity.x, 0, friction)
else:
sprite.play("run")
is_on_floor
メソッドは、現在地面にキャラクターが接しているかを判定してくれる。接していれば戻り値が true になる。if x_input == 0:
は左右移動の入力がない場合という条件の if 構文だ。この if / else ブロック内を見ていく。
まず、sprite
(「AnimatedSprite」ノード)のplay
メソッドによりアニメーション「idle」を再生する。
次に、velocity.x
の値は組み込みのlerp
メソッドにより、現在のvelocity.x
の値の間を第三引数であるfriction
プロパティの重み分だけ、第二引数 0 に近づけていく。_physics_process
メソッドが毎フレーム実行されることにより、次第に値は 0 になる。
else ブロックの方は、左右移動の入力があった場合という条件になり、このとき「AnimatedSprite」ノードのplay
メソッドによりアニメーション「run」が再生される。
それでは、改めてプロジェクトを実行して、滑らなくなったか、アニメーションが切り替わるか確認しよう。
lerp
メソッドによる摩擦抵抗の実装で、左右移動の入力がなければ減速して停止するようになった。スペースキーを押下した時のダッシュの挙動も上々だ。ちなみに、何度かmax_dash_speed
の値を変更して検証してみたが、80 ではあまり移動速度が変わらなかったので 200 に変更した。せっかくなので、あなたもインスペクターからお好みの速度に調整してみよう。
次はキャラクターがジャンプできるようにしょう。続けてif is_on_floor():
ブロック内に次のコードを追加しよう。
if Input.is_action_just_pressed("jump"):
sprite.play("jump")
velocity.y = -jump_force
この if ブロックif Input.is_action_just_pressed("jump"):
は、(地面にキャラクターが接している時に)インプットアクション「jump」の操作、つまり上矢印キーを一回押した場合、という意味合いだ。この場合「AnimatedSprite」ノードのplay
メソッドによりアニメーション「jump」を再生する。そして、この瞬間にvelocity.y
にジャンプ力を示すjump_force
を引き算している。引き算するのは、先ほどお伝えした通り、画面上方向にいくほど y 軸の値が小さくなるからだ。
ではプロジェクトを実行して、ジャンプ操作も確認してみよう。
今のところ大きな問題はなさそうなので良しとしよう。
あとは少し細かいが、ジャンプ中に左右移動の入力がなかった場合と、ジャンプしてすぐに上矢印キーを離した場合、このそれぞれの動きをコーディングしていこう。
if is_on_floor():
ブロックに続けて、以下のelse
ブロックを追加して、if / else
構文にする。
else:
if x_input == 0:
velocity.x = lerp(velocity.x, 0, air_resistance)
if Input.is_action_just_released("jump") and velocity.y < -jump_force / 2:
velocity.y = -jump_force / 2
else
ブロックは、is_on_floor
メソッドの戻り値が false だったら、と言い換えられる。つまり、「地面に接していなかったら(空中だったら)」という意味になる。さらにネストされたif
構文が続く。一つ目のif
ブロックの条件は、if x_input == 0:
で、これは「左右移動の入力がなかったら」という意味だ。その場合、velocity.x
はlerp
メソッドにより、air_resistance
の空気抵抗により毎フレーム 0 に近づいていく。
else
ブロックにネストされて2つ目のif
構文を見てみよう。条件式は2つの条件がand
で結合されていて、「2つとも満たした場合」という条件になる。
一つ目の条件は、Input
クラスのis_action_just_released
メソッドを利用した、(空中で)一度「jump」アクション操作(上矢印キー)を離した場合、という内容だ。
2つ目の条件は、velocity.y
の値が-jump_force / 2
(ジャンプ力の2分の1)未満の場合、という内容だ。
これらを合わせると、空中でスペースキーを離した時にプレイヤーキャラクターの y 軸方向の速度がジャンプ力の半分未満だったら、という意味になる。これを満たした場合、velocity.y
の値には-jump_force / 2
(ジャンプ力の半分)の値を適用する。これにより、スペースキーをちょんと押しただけだと、低いジャンプになる。
それでは、プロジェクトを実行して変更した内容を確認してみよう。
スペースキーを押し続けた時と、ちょんと押した時とで、ジャンプする高さが変わった。また、ジャンプ中に左右移動の入力をしない場合、空気抵抗の影響を受け、x 方向の移動距離が少し落ちた。
ひとまずプレイヤーキャラクターの動きが想定通りになったので、「Player.gd」スクリプトの編集は一旦ここまでにしておこう。
Part 1 で編集したスクリプトのコード
今回編集した「Player.gd」スクリプト全体のコードを最後に共有しておこう。何か動きが思い通りに行かない場合はこちらを参考にしてほしい。
Player.gd スクリプトを確認する
extends KinematicBody2D
export var acceleration = 256
export var max_speed = 64
export var max_dash_speed = 80
export var friction = 0.1
export var gravity = 512
export var jump_force = 224
export var air_resistance = 0.02
var velocity = Vector2()
onready var sprite = $AnimatedSprite
func _physics_process(delta):
velocity.y += gravity * delta
var x_input = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
if x_input != 0:
velocity.x += x_input * acceleration
if Input.is_action_pressed("dash"):
velocity.x = clamp(velocity.x, -max_dash_speed, max_dash_speed)
else:
velocity.x = clamp(velocity.x, -max_speed, max_speed)
sprite.flip_h = x_input < 0
if is_on_floor():
if x_input == 0:
sprite.play("idle")
velocity.x = lerp(velocity.x, 0, friction)
else:
sprite.play("run")
if Input.is_action_just_pressed("jump"):
sprite.play("jump")
velocity.y = -jump_force
else:
if x_input == 0:
velocity.x = lerp(velocity.x, 0, air_resistance)
if Input.is_action_just_released("jump") and velocity.y < -jump_force / 2:
velocity.y = -jump_force / 2
velocity = move_and_slide(velocity, Vector2.UP)
Player ノードをシーンにする
最後に「Player」ノードを個別のシーンとして保存し、「Level1」シーンには「Player」シーンのインスタンスをノードとして追加させるようにする。今後、別の新たなブランチをルートノードに追加する場合も、「Player」ノードと同様に、先に別シーンとして作成してから、そのインスタンスをルートノードに追加していくことになるだろう。
実は Godot では、このように個々のシーンのインスタンスを別のより大きなシーンの子ノードとして追加するのが一般的だ。ゲームの小さい部品を作って、それらを集めて少し大きな部品のシーンを作る、ということを繰り返して、少しずつゲームの規模を大きくしていくというわけだ。とはいえ、Godot でのゲーム開発をはじめ、オブジェクト指向プログラミング全般がそういうものだとも言えるだろう。大きく複雑なプログラムも小さくて簡単なプログラムの組み合わせに過ぎない、と考えれば、ゲーム開発はまったくもって怖くない。
では具体的な作業を進めよう。
シーンドックで「Player」ノードを右クリックして「ブランチをシーンとして保存」を選択する。
シーンの保存先パスを「res://Player」とし、ファイル名を「Player.tscn」として、「保存」をクリックする。
すると「Level1」シーンの「Player」ノードの右側に「エディターで開く」アイコンが表示される。これは別のシーンのインスタンスであることを示している。このアイコンをクリックしてみよう。
するとシーンドックと2Dワークスペースが「Player.tscn」シーンの表示に切り替わる。これで「Player」ノードをシーンにすることができた。
最後に、後処理を少しやっておこう。まず「Player.tscn」シーンのまま、インスペクターの「Position」プロパティを見て見よう。今までのまま(192, 80)のように、(0, 0)以外の値になっているはずだ。これを、「Player.tscn」シーン上では (0, 0)にしておこう。なぜならこのシーン上でプレイヤーキャラクターの位置を変更する必要がないからだ。
続けて、2Dワークスペースにて「Level1」タブをクリックして「Level1.tscn」シーンに戻そう。
同じくインスペクターから「Position」プロパティを (0, 0) から (192, 80)などに変更しておこう。これでさっきデバッグしていた時と同じ状態に戻った。
おわりに
以上で Part 1 は完了だ。プレイヤーキャラクターが自分の操作で想定通りに動くと嬉しいものだ。ゲーム開発のモチベーションを高めるには、最初にプレイヤーキャラクターを作るというのが実践しやすい一つの方法ではないだろうか。
次回はタイルマップを作ってゲームの世界を作っていくのでお楽しみに。
UPDATE
2022-10-16 仮の足場を作るの手順の「Area2D」の誤記を「StaticBody2D」に訂正
2022-03-26 Dropbox のアセットの内容に Part 12 で利用するファイルを追加してリンクを更新
2022-03-24 Dropbox のアセットの内容に Part 11 で利用するファイルを追加してリンクを更新