「Godot」0からゲーム作ろう! (実践1: キャラ操作 ー2Dゲーム基礎ー)
記事2へようこそ! Coding課のAugustです.

本シリーズは, 完全に0から, あらゆる2Dゲームにも使われるエンジン機能や, ゲーム制作に欠かせない
- 信号(Signal, ~=UntiyのEvent System)
- キャラ操作
- Tweenの作り方
- User Interface
などを
Godotエンジンを使って解説するものです.
Unity達人でも, Godotエンジンでの制作流れをみて, 感じられるものがいるかもしれません.
本記事は,
- 初期設定
- キーボード・ゲームパッドでのキャラ操作の実装
という順で進みます.
(今回の進展を示す動画)

なお, もし 「このゲーム・この機能, どうやって作れる?」に対して興味を持つ方は, (複雑過ぎない機能かつ現段階2D限定)なら, Discordでメッセージくれたら次回の記事で作りたいと思います.
では早速始めましょう.
Godot Engineのdownloadは, https://godotengine.org/ または Steam上でできます.
私は普段先行リリースのVer 4.5を使っています. Godot4である以上, 支障はないと考えます.

下準備
プロジェクトを開いたら

3D画面から2Dに移行して, その他のノードからを選び, 一番上の “Node”型ノードを制作し, 名前を”Main”(適当) に変え, Ctrl+Sで保存しよう.
次に, 画面の右上にある playボタン, あるいはF5を押して, 実行してみよう


「現在のものを選択」を押したら, “Main” Sceneは, ゲームを開いて最初に表示されるシーンにあった. これは通常のゲームでは, メインメニューなどに該当する.
さて, これから正式に制作を始めよう!
キーボード・ゲームパッドでのキャラ操作の実装
Ctrl + N (新しいSceneを作る) を押して, 2Dシーンを作る.

ノードを”Player”(名前自由) をし, Ctrl+A, あるいはこの「+」を押して, 子ノードを追加して, Sprite2Dを選ぶ

次に, プロジェクトフォルダに以下の画像(あるいは自分の画像)を入れよう.




さて, この画像をSprite2DのTextureとして適用しよう.

これで, 動かしたいキャラクターは定義された.
次は, 移動に使うキーボードボタンを定義しよう.

さて, コードを書き始めよう!


ここで, GDScriptの基本を教えよう:
extends Node2D (Node2Dというクラスを継承する) (Node2D<-CanvasItem<-Node<-Object)

GDScript はPythonのような動的型付け言語と同時に, 静的型付けもできる. var speed:int = 200
(新しい変数speedを定義し, speedはintで, デフォルト値は200)
または
var speed:= 200
(新しい変数speedを定義し, speedのデフォルト値は200, 型は「200」と同じ)
は静的型付けの例で,
int speed = 200;
を 意味する.
静的型付けた変数は, 違う型につけられるとエラーが出る.
動的型付けにしたい場合:
var speed = 200
と書けばいい.
func _ready() は, Unityのstart()と同様で, ノードがツリーに入る同時に実行する.
func _process()は, UnityのUpdate()と同様に, フレームごと実行される.
さて, この行
func _process(_delta: float) -> void:
if Input.is_action_pressed("right"):
self.position += speed * Vector2.RIGHT * _delta

float型変数_deltaとは, フレーム毎の時間差である. これをspeedにかけることで, どのPCのFPSでも安定な200/sの速度が保証される.if Input.is_action_pressed("right")
英語が意味する通り, “right”と定義されたボタンが押されたら発動する
さて, そろそろ長くなってきたので, スピードアップしていきましょう!
var direction:Vector2 = Vector2.ZERO
func _process(_delta:float) -> void:
direction = get_movement_vector()
self.position += direction*speed*_delta
func get_movement_vector() -> Vector2:
var x_movement = Input.get_action_strength("right")-Input.get_action_strength("left")
var y_movement = Input.get_action_strength("down") - Input.get_action_strength("up")
return Vector2(x_movement,y_movement).normalized()
簡単にいうと, get_movement_vectorという二次元ベクトルを戻り値にする関数を定義し, それを_process()で毎フレーム呼ぶことで, 上下左右のキャラ操作を実現する.
x軸から右は正の値で, 右-左で, 同時に押されても0になるだけ
Godot2Dのy軸は逆で, 0,0は画面の左上にある. つまり, 下方向の方は正の値で, “down” – “up”
最後に, .normalized()を使ってベクトルを正規化する理由は, 斜め移動のノルム(長さ)が sqrt(1^2+1^2) = sqrt(2) ~= 1.414になるため, 移動速度が1.4倍になることを防ぐためである.
さて, このキャラクターをMain Sceneに召喚して, 実行してみよう!

さて, ここから, キャラクターの移動方向に従い, 素材を変えよう!
@onready var sprite:Sprite2D = $Sprite2D
@onready var front_img:Texture2D = preload("res://front.png")
@onready var side_img:Texture2D = preload("res://side.png")
@onready var back_img:Texture2D = preload("res://back.png")
@onready とは, func _ready() の中に入ると同意味で, とはいえ, 実際の_ready()関数より前に実行される. (今回は無関係)
$の印は, 子ノードを意味する. $Player/Sprite2D は, 子ノードのプレイヤーの子ノードである Sprite2Dを指す.
preload(“素材へのパス”)は, ゲームが実行する前(pre)に, コンパイル時素材を用意するということ (ので, load()も存在する, 使い分けるは確実決定.)
func _process(_delta:float) -> void:
direction = get_movement_vector()
self.position += direction*speed*_delta
if direction.y < 0:
sprite.texture = back_img
elif direction.y > 0:
sprite.texture = front_img
elif direction.x < 0:
sprite.texture = side_img
sprite.flip_h = false
elif direction.x > 0:
sprite.texture = side_img
sprite.flip_h = true
次に_processに, これを入れる.
簡単にいうと, 純粋な上下運動時, 正面・背面の素材を使う, 左右方向が含まれる場合, 側面の素材を左右反転しながら使う. 今回支給した側面素材は, 左向きなので, 右に移動するときは,
sprite.flip_h = true
をする. (注:この実装法はかなり雑です. 信号(Signal)まだ紹介してないため使用していない.)

今回のコード:
extends Node2D
class_name Player
@export var speed:int = 200
@onready var sprite:Sprite2D = $Sprite2D
@onready var front_img:Texture2D = preload("res://front.png")
@onready var side_img:Texture2D = preload("res://side.png")
@onready var back_img:Texture2D = preload("res://back.png")
func _ready() -> void:
pass
var direction:Vector2 = Vector2.ZERO
func _process(_delta:float) -> void:
direction = get_movement_vector()
self.position += direction*speed*_delta
if direction.y < 0:
sprite.texture = back_img
elif direction.y > 0:
sprite.texture = front_img
elif direction.x < 0:
sprite.texture = side_img
sprite.flip_h = false
elif direction.x > 0:
sprite.texture = side_img
sprite.flip_h = true
func get_movement_vector() -> Vector2:
var x_movement = Input.get_action_strength("right")-Input.get_action_strength("left")
var y_movement = Input.get_action_strength("down") - Input.get_action_strength("up")
return Vector2(x_movement,y_movement).normalized()