🤖

【Godot Engine 4】Godot Engineで2Dゲームを作るときに知っておきたい基本的な機能

2023/09/24に公開

Godot Engineで2Dゲームを作るときに知っておくと良さそうな知識をまとめておきました。

基本

シーンとゲームオブジェクトの関係

Unityに例えるとシーンは「Prefab」であり「Scene」でもあると言えます。

上記はシーンの構成の一例で、ゲームを動かすためのメインシーン「Main.tscn」が存在し、そこにプレイヤーシーン「Player.tscn」や敵シーン「Enemy.tscn」を配置していく…といった使い方になっていきます。

シーンはエディタ上で直接シーンにぶら下げても良いですし、スクリプトから生成してぶら下げることもできます。(画像では "Enemies"と"Bullets" を Node2D としていますが、CanvasLayer ノードにすると描画順を制御できて便利です。このことについては「描画順の制御」の項で説明します)
スクリプトから生成する場合は、preload() で読み込み、instantiate() で生成を行います。

extends Area2D

# プレイヤークラス.
class_name Player

# ショットシーンを読み込み
const SHOT_OBJ = preload("res://Scenes/Shot.tscn")

...

func _shot() -> void:
  # ショット生成
  var shot = SHOT_OBJ.instantiate()

  # 座標と移動量を設定
  shot.start(position.x, position.y, 90, 1000)

  # ルートノードを取得
  var main_node = get_owner()
  # "Bullets"ノードを取得.
  var bullets = main_node.find_child("Bullets")
  bullets.add_child(shot)

シーンを実行する3つの方法

ゲームを動かすにはシーンを実行するには3つの方法があります。

  1. メインシーンを実行する
  2. 現在のシーンを実行する(現在選んでいるシーンを実行する)
  3. 特定のシーンを実行する(シーンを選んで実行する)

1. メインシーンの設定方法

まずはプロジェクト設定をメニューから開きます(メニューから「プロジェクト > プロジェクト設定」)。

「一般」タブが設定されていることを確認して「アプリケーション > 実行 > メインシーン」の項目から変更ができます。

ここによく実行するシーンを設定しておきます。ゲームを配布するときに実行されるシーンがこの設定となります。
なお、macOS環境の場合はデフォルトのショートカットキーが Cmd+B だけれども個人的に1つのキーを押しただけで実行したいので F5 など押しやすいキーに変更しています。

ショートカットキーの変更はメニューの「エディタ > エディタ設定」でエディタ設定画面を開きます。

「ショートカット」タブを選び、検索フィルタに "実行" と入力して、「エディター > プロジェクト実行」のところにある +ボタン をクリックしてショートカットキーを登録します。

2. 現在開いているシーンを実行する

プロジェクト設定から実行したいシーンを切り替えるのが手間な場合は、現在開いているシーンを直接実行する方法も用意されています。
開いているシーンを実行するにはこのボタンをクリックします。

なお「開いているシーン」というのはタブ上でアクティブになっているシーンで、下記画像であれば「Player」シーンが「開いているシーン」となります。

3. シーンを選んで実行する

すべてのシーンの中から実行するシーンを選ぶ方法です。特定のシーンを実行したい場合はこのボタンをクリックします。

シーンを読み込んで生成する方法

「シーンとゲームオブジェクトの関係」のところで説明したので繰り返しになってしまいますが、作成したシーンを別のシーンに配置するには、エディタ上でぶら下げるか preload() で読み込んで instantiate() でインスタンスを作ります。

# ショットシーンを読み込み
const SHOT_OBJ = preload("res://Scenes/Shot.tscn")

func _shot() -> void:
  var shot = SHOT_OBJ.instantiate()
  add_child(shot)

なお add_child() でシーンにぶら下げないと表示されないことに注意します(このミスは結構やりがちです)。

クラス名の定義方法

少し混乱してしまうポイントですが、preload() で読み込んだ値は PackedScene というデータ型でそのオブジェクトの型を表現するものではないです。
明示的に型を指定したい場合は、スクリプト内で class_name を定義します。

extends Area2D

# プレイヤークラス
class_name Player

これによりスクリプト上で型を指定して変数を定義したり、アクセスできるようになります。

# 例えばメインシーンに配置済みの "Player" ノードの型を指定してメンバ変数にする.
@onready var _player:Player  = $Player

型を指定する一番のメリットは型チェックが機能したり、コード補間が有効になることですが、少しトリッキーなメリットとしては、衝突判定のシグナルで is キーワードで特定の型を持つオブジェクトのみ処理する…、といった判定ができるメリットがあります。

## プレイヤーと衝突した.
func _on_body_entered(body: Node2D) -> void:
	
  if body is Player:
    # アイテムゲット.
    var player = body as Player
    player.gain_item(_id)
    # 消滅する.
    queue_free()

キー・マウス入力の判定

キー・マウスの入力判定ですが、基本的に「Inputモジュールの is_action_*()」または「シグナル」を使うと実装しやすいです。

if Input.is_action_just_pressed("ui_accept"):
  print("SPACEキーを押した")

Godotではよく使うキーが最初から登録されていて、上記の "ui_accept" では SpaceキーEnterキー が「アクション」として割り当てられています。(プロジェクト設定から「インプットマップ」タブを選んで「組み込みアクションを表示」を有効にすると確認できます)。

また独自のアクションを定義することが可能で、「インプットマップ」タブから「新しいアクションを追加」の欄に文字を入力して「追加ボタン」をクリックします。

すると指定した名前でアクションが作られるので、「+ボタン」をクリックして、対応するイベント(入力)を指定します。

これでクリック判定を行うことができます。

	if Input.is_action_just_pressed("click"):
		print("クリックした")

またボタンUIなど、ボタンクリックの判定を行う場合はシグナルを使うのが楽です。シグナルの設定はインスペクタの隣にある「ノード」タブを選んで、使いたいシグナルをダブルクリックしてスクリプトにバインドしていきます。

なおノートPCなど画面の小さいモニタを使っている場合はタブが表示されないので、">"ボタンをクリックすると表示されます。

2D/3Dエディタとスクリプトエディタの切り替え方法

2D/3Dエディタとスクリプトエディタは、エディタ上部にあるこのアイコンからそれぞれのモードに切り替えが可能です。

シングルトンの作り方

ゲームを作る上で重要なのが「シングルトン(ゲーム内で1つしか必要ないインスタンス)」ですが、方法は以下の2つあります。

  1. AutoLoad(自動読み込み)に指定する
  2. staticキーワードを使う(Godot4.1以降)

1. AutoLoad(自動読み込み)に指定する

AutoLoad(自動読み込み)とはシーンが作られたときに ルートノード直下に直接配置されるシーンを指定する設定です。

AutoLoadのシーンにするには、プロジェクト設定から「パス」に読み込みたいシーンを指定して、「追加ボタン」をクリックすると設定できます。

この指定をすることで例えば、Common.gdplay_se() という関数がある場合に、どこからでもこの関数を呼び出せるようになります。

# どこからでも呼び出せる.
Common.play_se("powerup")

なお、AutoLoadには「スクリプト(.gd)」「シーン(.tscn)」どちらでも指定できます。使い分けとしては、描画が必要な場合は「シーン」、描画が不要な場合は「スクリプト」という使い分けで多くの場合は問題ないと思います。

2. staticキーワードを使う(Godot4.1以降)

AutoLoadは便利ですが、クラスの循環参照でスクリプトエラーになることがあります。その場合は、Godot4.1からはstatic キーワードが用意されたので、このキーワードで実装すると解消できる可能性があります。(※.tscnを使わないことを前提)

extends Node2D

## 実績管理クラス
class_name Achievement

static var _unlock_flags:Array[bool] = []

static func unlocked(idx:int) -> bool:
	return _unlock_flags[idx]

個人的にはスクリプトのみ(*.gd)のシングルトンであれば、基本的にstaticキーワードを使用したほうが良いと考えています。

当たり判定を持つオブジェクト

2Dゲームで当たり判定を持つオブジェクトを実装する場合は、基本的に Area2D / CharacterBody2D / StaticBody2D / RigidBody2D を使用します。
これらを継承したノードについてこのページでは「物理オブジェクト」と定義します。

Area2D / CharacterBody2D / StaticBody2D / RigidBody2D の違い

Godot Engineで使用できる物理システムに関するノードと分類は以下のとおりです。

ノード名 概要 使用例
Area2D * 衝突の「検知」「影響」
* シグナルでの衝突検知
* 領域内の重力や速度減衰
* 衝突検知のみ行うオブジェクト
(2Dシューティングなど)
*重力を変化させる装置
CharacterBody2D スクリプトで細かく
動きを制御する物理オブジェクト
* プレイヤー
* 動く床
StaticBody2D 衝突しても動かない静的なオブジェクト * 動かない壁や床
*ベルトコンベア
RigidBody2D 物理エンジンで動くオブジェクト * 投擲オブジェクト
* 物理パーティクル

例えば 2Dシューティングゲームのように、衝突が発生するとダメージや消滅させるだけのゲームシステム(衝突検知のみ)であれば、Area2D を使うことをおすすめします。

ただ地形との衝突があり押し戻し処理(衝突応答)を行うようなゲームの場合は、CharacterBody2D を使用してめり込みが発生しないようにする必要があります。

以下ゲームジャンルごとに使いそうなノードのまとめです。

ゲームジャンル 使いそうなノード
2DSTG Area2D
2Dジャンプアクション * CharacterBody2D(プレイヤー・敵・移動床など)
* StaticBody2D(動かない地形)
物理パズルゲーム RigidBody2D, StaticBody2D

ちなみに「ヴァンパイアサバイバーズ(Vampire Survivors)」のような大量のオブジェクトが出るゲームを作る場合、CharacterBody2Dで大量の敵を実装すると押し戻し処理などが重たいので、基本的にArea2Dで実装して押し戻し処理はスクリプトで自作したほうが処理は軽くなると思います。

物理オブジェクトの移動方法

物理オブジェクトを移動する場合、継承元のノードによって方法が異なります。

ノード名 移動方法
Area2D positionに直接移動量を加算
CharacterBody2D move_and_* 系の関数で移動する
StaticBody2D move_and_collide()で移動する
RigidBody2D apply_impulse系, add_force系で移動

Area2Dのみpositionの値を直接変更することで移動できます。それ以外は押し戻し処理が発生する影響で特定の関数で移動する…という制約があります。

物理オブジェクトの衝突判定

衝突判定も物理オブジェクトの種類によって変化します。

ノード名 判定方法
Area2D Area2D同士ならarea_*系。Body2D系との衝突はbody_*
CharacterBody2D move_and_slide() を呼び出した後、get_slide_collision()で衝突したコリジョンを取得して判定する
StaticBody2D move_and_collide()の戻り値で判定
RigidBody2D Contact Monitor を有効にする。または get_colliding_bodies()で衝突したBody2Dの配列を取得

Area2Dだとわかりやすく衝突判定できるのですが、それ以外は少し癖がある判定方法となります。

また RigidBody2D.get_colliding_bodies() はドキュメントに以下のような注意書きがあり、衝突タイミングを細かく制御するゲームには向いていないような気がします。

Returns a list of the bodies colliding with this one. Requires contact_monitor to be set to true and max_contacts_reported to be set high enough to detect all the collisions.

Note: The result of this test is not immediate after moving objects. For performance, list of collisions is updated once per frame and before the physics step. Consider using signals instead.

このボディと衝突するボディのリストを返します。 contact_monitor を true に設定し、max_contacts_reported をすべての衝突を検出できる十分な大きさに設定する必要があります。

注: このテストの結果は、オブジェクトを移動した直後には反映されません。 パフォーマンスのために、衝突のリストはフレームごとに 1 回、物理ステップの前に更新されます。 代わりにシグナルの使用を検討してください。

プロジェクトのフォルダ構成をどうするか問題

私自身いくつかゲームを作ってきて、以下のようなフォルダ構成が良さそうと考えています。

root(プロジェクトのルートフォルダ)
 +-- assets
 |    +-- fonts: フォントデータ
 |    +-- images: 画像データ
 |    +-- sound: サウンド
 |    +-- tiles: タイルマップ
 |
 +-- src: ".tscn" / ".gd" を配置

それとシーンとスクリプトを別々のフォルダにするべきかどうかですが、おそらく一緒のフォルダ置いた方がまとまりがあって良いのかなと思います。

日本語を表示する

文字を表示するには Label ノードを使用します。

Godot3.xのときはデフォルトでは日本語は表示できなかったのですが、Godot4.xからはとりあえず日本語を出したいのであれば、そのまま日本語を指定することでテキストを描画できます。

好みのフォントに差し替えたい場合は Label Settings から行います。
ここでは以下のページから、"M+ FONTS" をダウンロード (Rawボタンをクリック)してプロジェクトに追加済みとします。

まずはインスペクタから Label Settings<空> のところをクリックします。

ポップアップが表示されるので「新規LabelSettings」を選びます。

そして作成された「LabelSettings」をクリックするとフォントの設定項目が表示されます。

次に「Font > Font」の <空> をクリックして クイックロード を選びます。

ここから設定したいフォントを選びます。

あとはフォントサイズやアウトライン(縁取り)などを自由に選びます。

なおこの LabelSettings はリソースとして保存しておくと他の Label でも使いまわすことができます。

ピクセルパーフェクトにする

ドット絵中心のゲームを作る場合、デフォルト設定だと拡大時にバイリニアフィルタリングで縁がぼやけてしまいます。

これを回避するには、プロジェクト設定から「レンダリング > テクスチャ > キャンバスのテクスチャ > デフォルトのテクスチャフィルタ」 を Nearest にします。

なおこの設定はプロジェクト全体のものなので、個別に設定したい場合はインスペクタから「CanvasItem > Texture > Filter」を Nearest にすることでその描画のみピクセルパーフェクトにすることができます。

Sprite2Dの中央揃えを無効にする

Offset > Centered のチェックを外すと Sprite2D の中央揃えが無効になります。

あとパラメータのリセットをするときには、この「くるくるアイコン」をクリックするとパラメータが初期化されます。

描画順の制御

Godotでは基本的にノードの上から描画されます。

そのため上記のようにノードが並んでいれば "Godot"ノードは "Player"ノードよりも上に描画されます。

描画順を変えるには Z IndexCanvasLayer の2つの方法があります。

"Z Index"による描画順の変更

インスペクタの「CanvasItem > Ordering > Z Index" の値を大きくすると大きい方を優先して描画できます。

ただ、オブジェクトが増えてくると、この数値の管理が大変なので通常はCanvasLayerを使用して描画順を制御します。

CanvasLayerによる描画順の変更

CanvasLayer ノードにオブジェクトをぶら下げると、描画順が CanvasLayerで設定したものに統一されます。

例えば 2DSTG で、画面奥から

  1. 背景
  2. プレイヤー
  3. 自機の弾
  4. 敵弾
  5. エフェクト
  6. UI

という順で描画したい場合は以下のように CanvasLayer のノード階層を作っておき、各インスタンスをそれぞれのCanvasLayerに add_child() することで描画順を簡単に制御できます。

CanvasLayerの注意点

CanvasLayerはデフォルトでカメラのスクロールを無視する設定となっています。
カメラに追従させるにはインスペクタから「Follow Viewport > Enabled」を有効(オン)にします。

便利機能

クイックオープン

個人的には「クイックオープン」という機能がお気に入りです。

これは入力した文字に一致するファイルをすばやく開く機能です。
ただデフォルトのショートカットキーがかなり押しにくいので「Ctrl+T (Cmd+T)」などに設定しておくのがオススメです。

最後に

他にもGodotには便利な機能がたくさんありますが、まずは基本的な機能だけをまとめてみました。
使い慣れないうちはたくさんの機能を理解するのが大変なので、まずは小さいプロジェクトを少しずつ作りながら機能を覚えていく良いのではないかと思います。

例えば2Dプラットフォーマー(ジャンプアクション)にいきなり挑戦すると結構難易度が高いと思うので、最初は2DSTG、見下ろし型のSTGやパズルを作ったあとで挑戦するのが良さそうです。

それと、このページで紹介しきれなかったGodot機能を逆引きできるページを個人的にまとめていますので、良ければのぞいてみると何か得られるものがあるかもしれません。

https://2dgames.jp/godot-tips/

Discussion