🚲

今から始める『Godot Engine』

2020/10/23に公開

Godot Engine は Windows / Mac / Linux で動作するオープンソースのゲーム開発エンジンです。
C++で開発されており、かなり快適に動作し、ファイルサイズはわずか30MBほどの軽量なソフトウェアです。
IDEもGodot Engine上で作られているため、開発においてもGodot Engineのコア機能のほとんどにアクセスができます。

開発言語は、Pythonをベースとした独自言語GDscriptまたはC#で、プログラマにも人気の高い言語で開発することができます。

ピクセルベースで開発できる2Dエンジンと、他のゲームエンジンと同様に開発できる3Dエンジンを持っており、どちらも簡単に共存させて開発が可能です。
これがかなり強力で、他のゲームエンジンと並ぶ魅力と言えます。

オープンソースですのでエンジンそのものを改変することも可能です。
例えば、チームでの開発においてどうしても必要になる機能を追加したりすることもできます。

準備

先述したとおり、Godot Engineはオープンソースであり、完全無料で使用ができます。
ほとんど日本語に対応しており、英語で苦しむという事も少ないでしょう。

ダウンロード

公式サイトまたはGithubリポジトリのリリースからダウンロードしてZipファイルを解凍するだけで使用可能です。
僕はSteamストアページからダウンロードして使用しています。itch.ioからダウンロードすることも可能です。

プロジェクトマネージャーでプロジェクトを作成

プロジェクトはプロジェクトマネージャーを通して管理できます。
Steamからダウンロードするとサンプルプロジェクトが同梱されています。

新規でプロジェクトを作成する場合、新規プロジェクト から空のディレクトリを指定します。

新規プロジェクトを作成したら、次回以降は実行をクリックすることで開発を再開することができます。

レンダラーはGodot Engine 3.2においては2種類ですが、デスクトップアプリケーションを開発するのであればOpenGL ES 3.0で問題ありません。

プロジェクトファイル

Godot Engineのプロジェクトは非常にシンプルです。

  • プロジェクト定義ファイル
  • シーンファイル
  • リソースファイル(マテリアルやシェーダーなど)
  • 画像などのアセットのインポート設定ファイル

上記が基本となり、画像やオーディオファイルなどのインポートするアセット以外はすべてアスキーファイルであり、最低限の情報のみが定義されているため、差分もわかりやすく、Gitでの管理も非常に容易です。
またインポート設定ファイルが存在しなくても、シェーダーでの使用で不都合が出たり、アーティストが望んだ結果にならない可能性はありますが、動作自体に影響はありません。

早速作ってみる

今回作成するのは、ボタンを押す事で「Hello world」の文字をタイプライター風に表示するだけのシンプルなプロジェクトです。
GUIのシステムを使うだけなので、実際のゲームでは様々なノードを使用する必要がありますが、Godot Engineの全体的な概念を説明する事が目的ですので、今後の記事で紹介できればと思っています。

メインシーンの作成

Godot Engineで新規プロジェクトを作成すると、3Dビューポートが開きますが、驚かないでください。

このシーンはまだ保存されていませんので、左上にあるシーンパネルからユーザーインターフェースを選択して、GUIシーンを作成します。

GUIシーンを作るとシーンパネルにはControlというルートノードが作成されます。
3Dだったビューポートは2Dの見た目に変わります。

作成したGUIシーンはまだ保存されていませんので、Ctrl+Sでシーンを保存します。

保存する際に、Godot Engineはデフォルトでres://というパスに保存しようとします。
これはプロジェクトのルートディレクトリを示しています。
今回はルートディレクトリに今作成したGUIシーンをControl.tscnという名前で保存します。

ファイルシステムパネルControl.tscnファイルが追加されました。
tscnファイルはシーンファイルで、様々なノードやスクリプトを梱包したパッケージと言う感じです。

プロジェクトにはメインシーンが必要になります。
メインシーンとは、このプロジェクトが実行される際に一番最初に実行されるシーンです。

ファイルシステムパネルで、作成したControl.tscnを右クリックし、「メインシーンとして設定」 をクリックします。

これで、プロジェクトを実行したときに、最初にメインシーンとしてControl.tscnが実行されます。

子ノードを追加していく

シーンパネルに戻り、ルートノードを右クリックして「子ノードを追加」を選択します。

追加できるノードが表示されますが、基本的には何でも追加できます。
しかし今回は「Control」カテゴリにある「Label」ノードを追加します。

Labelノードはテキストを表示するノードです。
子ノードとして追加したLabelを選択して、インスペクタパネルを見てみましょう。

いろんな項目がありますが、Labelノードにおいて重要なのはTextのプロパティです。
ここに半角で「Hello World」と入力してみましょう。

すると、ビューポートに入力した文字が現れます。
実際に実行して見てみましょう。

ウィンドウ右上の実行ボタンかF5キーを押して実行します。
灰色のウィンドウの左上に小さく「Hello World」が表示されました。

更に面白くカスタマイズ

テキストを表示するだけならすごく簡単ですが、少しスクリプトを使って、タイプライター風のテキスト表示をしてみましょう。

スクリプトのアタッチ

ルートノードを選択した状態で、シーンパネルの右上にあるスクリプトアイコンをクリックします。

自動的にアタッチ対象のノードを継承して、スクリプトを記述できるようにしてくれます。
Control.gdという名前でスクリプトを作成します。(自動的に指定パスに保存されます)

内蔵のスクリプトエディタ画面に切り替わり、スクリプトを記述することができます。

GDscriptで処理を書いてみる

それでは以下のような処理を書いてみようと思います。

  • 子ノードのLabelを取得
  • Labelに対して1文字ずつ文字を追加

まずはこれだけです。

子ノードのLabelを取得

extends Control

onready var _label := $Label #子ノードのLabelを取得

func _ready():
	pass

onready キーワードは、その名の通り「準備ができたら」という意味です。
子ノードも含めすべて実行可能な状態になった状態を示します。

変数 := 値という記述方法は独特ですが、変数に型を指定しなくても値によって自動的に型を決める便利キーワードだと思ってください。
もちろん型を指定することも、型を指定せずにおくこともできます。

$Labelスクリプトがアタッチされたノードから見たパスです。
例えば、ルートノードControlに子ノードPanelを追加し、更にPanelの子ノードとしてLabelを追加した場合のパスは以下になります。

onready var _label := $Panel/Label #孫ノードのLabelを取得

スクリプトからLabelにテキストを指定する

extends Control

onready var _label := $Label #子ノードのLabelを取得

func _ready():
	_label.set_text("Morning world") #Labelにテキストを指定

Labelノードはクラスなので、set_text(_string:String)というメソッドを持っています。
func _ready()onreadyキーワードと同様に、すべての準備ができたら実行されるメソッドです。

実行すると「Hello World」だったテキストが「Morning World」に変わっているのがわかります。

コルーチンで処理してみる

コルーチンというと難しく感じるのですが、指定秒毎になにか処理したり、少し処理を待たせたりする処理をやってみます。

func _ready():
	var _text := "Good night World"	#最終的に表示するテキストを定義
	yield(get_tree().create_timer(1.0),"timeout")	#指定秒待たせる処理
	_label.set_text(_text)

まずは上記のように書いてみました。
これで実行すると 「Hello World」と書かれたテキストが、1秒後に「Good Night World」に変化します。

yield(get_tree().create_timer(1.0),"timeout") というメソッドを実行することで指定秒処理を待たせる事ができます。

細かい説明

get_tree()というのは現在実行されているノードツリー全体を返します。
そのノードツリーに対してcreate_timer(1.0)というメソッドで1.0秒で信号を発するタイマーを作成します。
yield()の第2引数で信号の種類を文字列で指定します。
タイマーの信号は固定でtimeoutが定義されていますので、直接文字列で指定しています。

※信号(シグナル)については後述します。

繰り返し処理をする

func _ready():
	var _text := "Good night World"	#最終的に表示するテキストを定義
	var _time := 0.1		#待ち時間
	_label.set_text("")		#Labelのテキストを初期化
	
	for i in range(_text.length()):	#文字数分繰り返し処理
		_label.set_text(_text.left(i+1))	#i+1より左にある文字を設定
		yield(get_tree().create_timer(_time),"timeout")	#指定時間待つ

_timeとして待たせる秒数をローカル変数で定義しました。

forループrange(_text.length())で取得した文字の長さ分だけループ処理しています。
ラベルに対してテキストを、Stringクラスのメソッドleft()を使用してテキストを指定しています。
left()メソッドは引数に指定した数値よりも左にある文字列だけを返すメソッドです。
forループの変数i0から始まるので、+1した左側の文字列を返します。

そして指定秒処理を待つyeild()メソッドで0.1秒待たせて、再度ループに入ります。

実行すると、1文字ずつ「Good night World」のテキストが表示されるのがわかります。

ボタンを押して処理させる

最後にボタンを押すことでテキストを表示させる処理を作成します。

ボタンノードの追加

ルートノードの子ノードとしてButtonノードを追加します。
Buttonノードはいろんな状態によって信号(シグナル)を発行するノードです。

この信号を受け取って、何かしらの処理を行います。

ビューポート上で適当にButtonノードをドラッグしてレイアウトします。
割愛しますが、ボタンに表示するテキストもインスペクタから設定してください。

ボタンから信号を接続する

ノードは信号を発行することができ、接続した別のノードが信号を受け取って、何らかの処理を行う事ができます。

まずは、追加したButtonノードをクリックして、ノードパネルを開きます。
初期状態だとノードパネルが隠れているので分かりづらいですが、インスペクタパネルと重なっていると思います。

選択したノードが持っているシグナル(信号)の一覧が表示されます。
今回は押したときに処理したいのでpressed()を選びダブルクリックします。

Buttonノードが接続元となり、接続先を選ぶ画面が表示されます。
受信側のメソッドを入力できますが、今回はこのまま 「接続」 します。

すると以下のようなメソッドが、Controlノードのスクリプトに自動的に追加されます。

func _on_Button_pressed():
	pass # Replace with function body.

これでボタンが押された時、実行するメソッドが準備されました。

タイプライター表示の処理を移植する

extends Control

onready var _label := $Label # 子ノードのLabelを取得

func _on_Button_pressed():
	var _text := "Good night World"
	var _time := 0.1
	_label.set_text("")
	for i in range(_text.length()):
		_label.set_text(_text.left(i+1))
		yield(get_tree().create_timer(_time),"timeout")

_ready()に書いていた処理をそのまま、信号(シグナル)の受信メソッドに移植しました。
ちなみに_ready()メソッドは消してしまっても問題ないです。

これで実行してみましょう。

_ready()Labelのテキストを初期化していないので、「Hello World」が表示されていますが、
ボタンを押す事で、タイプライター風のテキスト表示が実装できました。

ちょっと便利にカスタマイズ

  • 表示するテキスト
  • タイプライター表示の速度

この2つをインスペクタで指定できるようにカスタマイズします。
とはいえ、とっても簡単です。

extends Control

export(String) var _text := ""
export(float, 0, 1) var _time := 0.1

onready var _label := $Label # 子ノードのLabelを取得

func _on_Button_pressed():
	_label.set_text("")
	for i in range(_text.length()):
		_label.set_text(_text.left(i+1))
		yield(get_tree().create_timer(_time),"timeout")

exportキーワードを付けた変数宣言は、インスペクタで表示されます。

(String)などの括弧は型を指定し、インスペクタでの入力タイプを変更することができます。
(float, 0, 1)と指定していますが、float型で第2引数の値から第3引数の値までに制限し、スライダーで表示することができます。

変数のデフォルト値はスクリプトで指定している値になります。

最後に

Zennの投稿を始めるにあたり、Godot Engineの簡単な使い方を紹介してみました。
Markdownで書けてGithubで管理できるZenn…素敵ですね…!

Godot Engineは非常に使いやすいゲームエンジンです。
他のエンジンと比べてデメリットももちろんありますが、何より無料でゲームをリリースできるので、この記事で知った方は是非触ってみてください。

この記事で紹介している 「ノード」「シーン」「シグナル」という概念 は、Godot Engineにおいて基本になります。
逆に言えば、この基本があれば2Dでも3Dでも、同じような感覚でゲームを作っていく事ができます。

是非楽しいGodot Engineライフをお過ごしください!

Discord日本コミュニティ

僕が運営するDiscordサーバーは、現在参加者100名を超えた日本人を中心とした技術交流コミュニティになっています。
人見知りな僕のような方のために、参加しても「○○さんが参加しました」といったメッセージは出ないようにしています。
こっそり入ってこっそり抜けても大丈夫です。

Discussion