この記事では、Nodeやスクリプトで定義したクラスの扱い方について解説します。
目次
扱い方の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" と出力される