SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Godot Engine 2Dゲーム開発入門

【詳細解説】Godot Engineで始める2Dゲーム開発──公式サンプルを試してみよう

Godot Engine 2Dゲーム開発入門 第2回

3.Playerのスクリプトの作成

 前節では「Player」シーンを作成しました。ここでは「Player」にスクリプトを追加していきます。「Player」ノードにplayer.gdというスクリプトをアタッチして、次のような処理を追加していきます。

  • 上下左右のキー操作による「Player」の操作
    • キー操作のバインド
    • キーの状態によるスクリプトの処理
  • 移動によるアニメーションの切り替え
  • 敵との当たり判定の準備

 それでは順番に作業を進めていきましょう。

スクリプトの作成とアタッチ

 「シーン」ドックの「Player」を右クリックして「スクリプトをアタッチ」を選択します。

スクリプトをアタッチ
スクリプトをアタッチ

 「ノードにスクリプトをアタッチする」ダイアログが開きます。

「ノードにスクリプトをアタッチする」ダイアログ
「ノードにスクリプトをアタッチする」ダイアログ

 「テンプレート」の横のチェックボックスをチェックしてください。右横の「空」の表示が「Node: Default」という表示に変わります。

 パスがres://player.gdになっていますので、そのまま「作成」ボタンを押します。

テンプレートにチェック
テンプレートにチェック

 「ファイルシステム」にplayer.gdが追加され、Scriptビューが表示されてplayer.gdが開いた状態になります。

 このスクリプトの先頭行にはextends Area2Dと書いてあります。Area2Dを継承しているのが分かります。

 また_ready_processという2つの関数が自動で追加されています。それぞれの関数の機能は次の通りです。

  • _ready関数
    • ノードツリーに追加され、準備が完了したタイミングで呼び出される。
  • _process関数
    • フレームごとに呼び出される。引数の「delta」は、1つ前のフレームからの経過時間。
player.gd
player.gd

 関数の中で何もおこなわない時は、Pythonと同様にpass文を書きます。

メンバー変数の追加

 それでは、このplayer.gdにメンバー変数を追加しましょう。

 1つはspeedでスピードを表します。この変数の宣言は@export var speedのように書き、400という値を代入します。

 もう1つはscreen_sizeでスクリーンサイズを表します。この変数の宣言はvar screen_sizeのように書き、まだ値は代入しないでおきます。

extends Area2D

@export var speed = 400 	# プレイヤーが移動する速さ (pixels/sec)
var screen_size 	# ゲームウィンドウのサイズ


# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	pass

 メンバー変数はextendsの行のあとに書きます。また、GDScriptのコーディング規約に従い、関数との間は2行の隙間を空けます。

 このようにスクリプトを書き換えると、「インスペクター」ドックの「Area2D」の上に「player.gd」というグループが現れます。そして「player.gd」の下に「Speed」という項目が表示されます。

 メンバー変数に@exportを付けることで、外部(インスペクター)から値を設定可能にできます。

メンバー変数
メンバー変数

 この「インスペクター」から設定可能にしたメンバー変数について、少しだけ補足します。

 1つ目は名前です。スクリプトでspeedと書いた変数は、「インスペクター」では「Speed」と表示されます。変数名をspeed_pixelsのように書くと、「インスペクター」では「Speed Pixels」と表示されます。

 2つ目は優先順位です。「インスペクター」で設定した値の方が、スクリプト内で変数を宣言した時に代入した値よりも優先されます。

_ready関数の実装

 次に_ready関数の中で、screen_sizeの値を初期化します。passの行を削除して、新しい処理に差し替えます。

extends Area2D

@export var speed = 400 	# プレイヤーが移動する速さ (pixels/sec)
var screen_size 	# ゲームウィンドウのサイズ


# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	screen_size = get_viewport_rect().size


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	pass

 変更したところで、スクリプト中のsizeの上にマウスを載せてください。ポップアップで説明が出ます。Godot Engineのエディターでは、マウスオーバーで各種の説明が出ます。

ポップアップの説明
ポップアップの説明

 ポップアップを見ると、sizeのに入っている値の型がVector2で、このVector2は数値2つを保持していることが分かります。また、2つの数値は横幅と高さであることも分かります。

 また、sizeのあとに.を書くと、関数やプロパティのリストが表示されます。

コード補完
コード補完

 「Ctrl」を押しながらスクリプトの一部をクリックすると、ローカルのドキュメントを開くことも可能です。

ローカルのドキュメント
ローカルのドキュメント

 さらに詳しい情報を得たいときは、ビューの上方にある「オンラインドキュメントで開く」をクリックして、公式ドキュメントのページを確認できます。

 同じようにget_viewport_rect関数にマウスを載せても、ポップアップで説明が出ます。「Ctrl+クリック」でドキュメントを開くことも可能です。こうした情報を活用してください。

入力の準備

 スクリプトの作成を先に進む前に、少しだけ準備をおこないます。キー入力の設定をおこない、プログラムから利用できるようにします。

 メニューの「プロジェクト」から「プロジェクト設定」を選びます。「プロジェクト設定」ダイアログが開きますので、上部の「インプットマップ」タブを選択します。

「インプットマップ」タブ
「インプットマップ」タブ

 「新しいアクションの追加」入力欄に「move_right」と入力して、右側にある「追加」ボタンを押します。

「move_right」と入力
「move_right」と入力
「追加」ボタンを押したあとの状態
「追加」ボタンを押したあとの状態

 この時点では、アクションはまだ割り当てられていません。追加された「move_right」の行の右端にある「+」ボタンを押します。「move_right のイベント設定」ダイアログが開きます。

「move_right のイベント設定」ダイアログ
「move_right のイベント設定」ダイアログ

 このダイアログでは「入力を待機しています」と表示されています。キーボードの「→」キーを押してください。そうすると表示が変わります。

「→」キーを押したあとの状態
「→」キーを押したあとの状態

 「OK」ボタンを押します。これで「move_right」のアクションに「→」キーの操作が割り当てられました。

「move_right」に「→」キーを割り当て
「move_right」に「→」キーを割り当て

 同じように「move_left」を「←」キーに、「move_up」を「↑」キーに、「move_down」を「↓」キーに割り当てます。

各方向を割り当て
各方向を割り当て

 ここまで設定できたら「閉じる」ボタンを押してプロジェクト設定を終了します。その他のキーやマウスの操作、ジョイパッドの操作を割り当てる時も、同じようなやり方で設定を追加できます。

_process関数の実装1

 続いて_process関数の中身を書いていきます。先ほど割り当てたキーの設定を利用します。少し長くなりますので、_process関数の中身だけを書きます。

func _process(delta: float) -> void:
	# キー入力による速度の設定
	var velocity = Vector2.ZERO		# Playreの移動ベクトル
	if Input.is_action_pressed("move_right"):
		velocity.x += 1
	if Input.is_action_pressed("move_left"):
		velocity.x -= 1
	if Input.is_action_pressed("move_down"):
		velocity.y += 1
	if Input.is_action_pressed("move_up"):
		velocity.y -= 1

	# 移動しているか、していなかで処理を分岐
	if velocity.length() > 0:
		# 移動しているなら、速度のベクトルを正規化したあと、係数を掛ける
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()	# AnimatedSprite2Dを再生
	else:
		$AnimatedSprite2D.stop()	# AnimatedSprite2Dを停止

	# 位置を更新、画面外にはみ出さないようにする
	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

	# 表示するアニメーションの種類と向きを設定
	if velocity.x != 0:
		$AnimatedSprite2D.animation = "walk"
		$AnimatedSprite2D.flip_v = false	# 縦方向の反転なし
		$AnimatedSprite2D.flip_h = velocity.x < 0	# 横方向の反転
	elif velocity.y != 0:
		$AnimatedSprite2D.animation = "up"
		$AnimatedSprite2D.flip_v = velocity.y > 0	# 縦方向の反転

_process関数の実装2 キー入力

 スクリプトが長いので分解して見ていきましょう。まずはキー入力による処理の部分です。

	# キー入力による速度の設定
	var velocity = Vector2.ZERO		# Playreの移動ベクトル
	if Input.is_action_pressed("move_right"):
		velocity.x += 1
	if Input.is_action_pressed("move_left"):
		velocity.x -= 1
	if Input.is_action_pressed("move_down"):
		velocity.y += 1
	if Input.is_action_pressed("move_up"):
		velocity.y -= 1

 最初にVector2.ZEROつまり(0, 0)のベクトルを用意します。そして左右の入力があった場合にはxの値を、上下の入力があった場合にはyの値を変更します。

_process関数の実装3 移動の分岐

 次は、移動があるかないかで処理を分岐します。

	# 移動しているか、していなかで処理を分岐
	if velocity.length() > 0:
		# 移動しているなら、速度のベクトルを正規化したあと、係数を掛ける
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()	# AnimatedSprite2Dを再生
	else:
		$AnimatedSprite2D.stop()	# AnimatedSprite2Dを停止

 Vector2length関数は、ベクトルの大きさを表します。最初は0だったので、そこからxyの値が加減算されているとlength関数の戻り値は1以上になります。0以上かを判定することで、移動があったかを調べられます。

 Vector2normalized関数は、ベクトルの大きさを1に正規化します。

 たとえば右と上に1ずつ移動した場合は、ベクトルの大きさはルート2で1.414…になります。そうなると斜めに移動するときだけ速く移動してしまいます。そのためにnormalized関数で正規化をおこなったあと、係数のspeedを乗算します。

 また移動があった場合は、AnimatedSprite2Dplay関数でアニメーションを再生して、移動がなかった場合はstop関数でアニメーションを停止します。

 ここで、新しい書き方が出てきました。$AnimatedSprite2Dget_node("AnimatedSprite2D")のショートカットです。現在のノードから、配下のAnimatedSprite2Dノードを得るという意味になります。GDScriptでは頻出する書き方です。

 たとえば、次のようなノード構造をしているとします。

┗━ root
     ┣━ Character
     ┃    ┣━ Sword
     ┃    ┗━ Backpack
     ┃         ┗━ Dagger
     ┣━ MyGame
     ┗━ Swamp
          ┣━ Alligator
          ┣━ Mosquito
          ┗━ Goblin

 このとき、Characterノードから、各ノードを参照するには、次のように書きます。

get_node("Sword")
get_node("Backpack/Dagger")
get_node("../Swamp/Alligator")
get_node("/root/MyGame")

 get_node関数では、相対パスや絶対パスでノードを得ることができます。

 ノードの取得は、$Swordのように簡便に書けます。$Backpack/Daggerのように相対パスも書けます。

 $NodeNameと書いた際に、存在しないノードだった場合はnullが返ります。

 スクリプトを実行した際に、「デバッガー」パネルにAttempt to call function '~' in base 'null instance' on a null instanceのようなエラーが出た場合には、ノード名のスペルが間違っている可能性があります。

 $NodeNameの名前は、シーンツリーに表示されている名前と一致させる必要があります。エラーが出た場合には見直してください。

 $/root/MyGameのような書き方も可能です。$/root/~のように書く場合は、$/root/<グローバル変数>$/root/<シーンのルート>のようなパスになります。少し特殊なので、print($/root/Main/Button)のように出力して、正しく取れているか確認するとよいです。

 ...が入る場合には、$../Swamp/Alligatorのような書き方はできません。この場合はクオーテーションを使い、$"../Swamp/Alligator"と書くことも可能です。こちらも、print関数で確認してから使うとよいでしょう。

_process関数の実装4 プレイヤーの位置の更新

 次は、プレイヤーの位置の更新をおこないます。

	# 位置を更新、画面外にはみ出さないようにする
	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

 Node2DのTranformのpositionに対して、速度velocityと差分時間deltaを掛けた値を加算します。

 ベクトル同士の計算では、各プロパティーを個別に計算する必要はありません。

 また、position.clamp関数を利用して、positionの値が、原点から画面サイズの範囲内に収まるように補正します。

実行確認

 ここまでのスクリプトを書いたら、一度実行してみましょう。「Ctrl+S」でプロジェクトを保存したあと、ウィンドウ右上のボタンが並んでいる場所から、「カチンコ内の▶」(現在のシーンを実行)をクリックします。あるいは「F6」キーを押します。

現在のシーンを実行
現在のシーンを実行
現在のシーンを実行

 確認したら、hide関数を追加して、プレイヤーを隠しましょう。スタートボタンを押すまでは非表示にするためです。

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	screen_size = get_viewport_rect().size
	hide()

次のページ
4.衝突の準備

この記事は参考になりましたか?

Godot Engine 2Dゲーム開発入門連載記事一覧

もっと読む

この記事の著者

柳井 政和(ヤナイ マサカズ)

クロノス・クラウン合同会社 代表社員http://crocro.com/オンラインソフトを多数公開。プログラムを書いたり、ゲームを作ったり、記事を執筆したり、マンガを描いたり、小説を書いたりしています。「めもりーくりーなー」でオンラインソフト大賞に入賞。最近は、小説家デビューして小説も書いています(『裏切りのプログラム』他)。面白いことなら何でもOKのさすらいの企画屋です。 

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/21370 2025/05/28 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー