【Godot】Nodeやスクリプトのクラスを外部から扱う場合の使い分け方法

この記事では、Nodeやスクリプトで定義したクラスの扱い方について解説します。

この記事はGodot3.xを基準に書いています

Godot4.xに読み替える場合は、以下の置き換えを行います

  • インスタンス生成の “instance()” を “instantiate()” にする
  • “onready” を “@onready” にする

扱い方の3つのパターン

このパターンの方法としては以下の3つが考えられます。

  • 1. シーンにスクリプトをアタッチして、シーンとして生成する
  • 2. スクリプトのみで Node を継承する
  • 3. スクリプトのみで Node は継承しない
No説明シーンNodeの継承自動読み込み
1シーンにスクリプトを
アタッチする
ありする可能
2スクリプトのみで
Nodeを継承する
なしする可能
3スクリプトのみで
Nodeは継承しない
なししないできない

少しややこしいところなのですが、シーンとスクリプトは必ずしも1対1で存在する必要はなく、互いに独立して存在することが可能です。またスクリプトはシーンとして扱うのかそうでないのかによって動作が異なります。

1. シーンにスクリプトをアタッチして、シーンとして生成する

最もよく使用するのが、このパターンだと思います。プレイヤーや敵などのゲームオブジェクトは通常この方法で実装し、インスタンス化します

例えばプレイヤーを以下のノード構成であるとします。

このシーンが “Player.tscn” の場合、preload() でこのシーンを読み込み、instance() をインスタンスを作ります。

const PLAYER_OBJ = preload("res://Player.tscn")

...

## プレイヤーの生成
func _create_player() -> void:
  var player = PLAYER_OBJ.instance()
  add_child(player)

もしくはシーンに直接ぶら下げて、onready で取得します。

onready var _player = $Player # ノードツリーから直接取得する.

スクリプトで型情報を定義する

なお、preload() で取得したときに定義した名前は型ではなく、ただの変数です。

# これはインスタンス生成用の変数であり、型ではない
const PLAYER_OBJ = preload("res://Player.tscn")

型情報を明示的にする場合には、シーンにアタッチしたスクリプトに class_name キーワードで型名を指定します。

extends KinematicBody2D

class_name Player # 型名を指定する

var _velocity = Vector2()
...

これですべてのスクリプトでは “Player” はプレイヤーの型名として使用することができます。

2. スクリプトのみで Node を継承する

次に「シーン」にアタッチしていないスクリプトを使うパターンです。シーンの情報をプログラム側で構築したい場合や、_process() などの更新関数を使いたい場合、スクリプトのみで問題ないが「自動読み込み」を使用したい場合に使います

例えばプレイヤーのスクリプトである Player.gd を生成するコードです。

const PLAYER_SCR = preload("res://Player.gd")

...

## プレイヤーの生成
func _create_player() -> void:
  var player = PLAYER_SCR.new()
  # ここでプレイヤーのノード構成をスクリプト側で行う.
  add_child(player)

スクリプトを preload() した場合は、instance() は使用できず new() で生成します。この場合にはスクリプト以外のノード情報はないので、ノード階層と必要なパラメータをプログラムですべて構築する必要はありますが、動的に細かく入れ替えたい場合に使えます(ただ、あまりにも面倒なのでめったに使うことはないですが…)。

どちらかというと「自動読み込み」の対象としてスクリプトにするのは良く使うと思います。

例えば、以下は共通モジュールの「Common.gd」を自動読み込みの対象とする設定です。

これにより「Common」という名前を通してすべてのスクリプトで Common.gd スクリプトの機能にアクセスできるようになります。

## 開始.
func _ready() -> void:
	Common.new_game_rnd() # 乱数を初期化.
	# シード値を入れる.
	_spinbox_seed.value = Common.get_seed()

ちなみに「自動読み込み」はルートノードの直下にされるため、何かを直接描画するといった使い方はせず、セーブデータなどグローバルなデータや、各種オブジェクトのデータの受け渡し用として使うのが良いと思います。

3. スクリプトのみで Node は継承しない

Nodeとしての機能は不要で、「自動読み込み」も不要、という場合は Node を継承しないスクリプトとすることもできます。

純粋なプログラムのデータ構造の定義やロジックのみ実装したい場合に使用します。

例えばゲームのパラメータを管理するデータベース機能であったり、パズルゲームのタイルの消去ロジックは Node の機能を必要としないため、Nodeなしのスクリプトを使っても良いかと思います。

こちらの場合も instance() で生成することはできず、new() で生成することになります。

なお、class_name で型を定義した場合は preload() なしで、そのまま new() することができます

# preload() なしでそのまま new() できる.
var player = Player.new()

class で定義したクラスの扱いについて

少し例外的なケースとして、class キーワードで定義したクラスの扱いです。

例えば以下のクラスをスクリプトに定義したとします。

## 加算した値の合計を求めるクラス
class Hoge:
	var list = []
	func add(a:int) -> void:
		list.append(a)
	func sum() -> int:
		var ret = 0
		for v in list:
			ret += v
		return ret

1. class_name が定義されている場合

class_name キーワードで型が指定されているとします。

extends Node

class_name Field


## 加算した値の合計を求めるクラス
class Hoge:
	var list = []
	func add(a:int) -> void:
		list.append(a)
	func sum() -> int:
		var ret = 0
		for v in list:
			ret += v
		return ret

そうするとその名前で生成することができます。

	var hoge = Field.Hoge.new()
	hoge.add(1)
	hoge.add(2)
	hoge.add(3)
	print(hoge.sum()) # "6" が出力される

2. 自動読み込みしている場合

このスクリプトが「自動読み込み」されている場合は、自動読み込みで設定した名前でアクセスできます。(以下は、例えば自動読み込みの名前が “Common” の場合)

	var hoge = Common.Hoge.new()
	hoge.add(1)
	hoge.add(2)
	hoge.add(3)
	print(hoge.sum()) # "6" が出力される

おまけ:クラスを継承する

classキーワードで定義したクラスですが、extends で継承して機能拡張することもできます。

例えばこのように定義して…

extends Node

class_name Field


## 加算した値の合計を求めるクラス
class Hoge:
	var list = []
	func add(a:int) -> void:
		list.append(a)
	func sum() -> int:
		var ret = 0
		for v in list:
			ret += v
		return ret

# Hogeを拡張する.
class Piyo extends Hoge:
	# 掛け算の合計を求める.
	func sum_mul() -> int:
		var ret = 1
		for v in list:
			ret *= v
		return ret

拡張されたPiyoクラスを使うこともできます。

	var piyo = Field.Piyo.new()
	piyo.add(2)
	piyo.add(3)
	piyo.add(5)
	print(piyo.sum()) # "10" と出力される
	print(piyo.sum_mul()) # "30" と出力される