【Godot】イテレーターの自作方法

今回はイテレーターの自作方法について解説します

イテレーターとは

イテレータ(英語: iterator)とは、プログラミング言語において配列やそれに類似する集合的データ構造(コレクションあるいはコンテナ)の各要素に対する繰り返し処理の抽象化である。実際のプログラミング言語では、オブジェクトまたは文法などとして現れる。JISでは反復子(はんぷくし)と翻訳されている

イテレータ – wikipedia

イテレーターを簡単に説明すると for文 で開始と終端を自動で判定してくれるデータ構造です。

GDScriptで言えば、ListやDictionaryなどがイテレーターです。

var list = [2, 3, 5, 7, 11]
for i in list:
  print(i)

イテレーターの使い道

イテレーターは1つ以上のオブジェクトのグループを管理するのに都合が良いです。

例としては、以下のものが該当すると思います

  • 敵を管理するイテレーター
  • 敵弾を管理するイテレーター
  • マップ上に落ちているアイテムのイテレーター

このようにデータとしてのまとまりはあるけれども、それを外部から固有のキーから識別して取得する必要がないものはイテレーターで管理して良いのかなと思います。

イテレーターを実装するのに必要な4つの関数

イテレーターを実装するには以下の4つの関数を実装する必要があります

  • _iter_init(): イテレーターの開始の処理
  • _iter_next(): 次のループ開始時の処理
  • _iter_get(): イテレーターの現在の値を取得する
  • _iter_is_running(): イテレーターが終了したかどうかを判定する関数

正確には「_iter_is_running()」はイテレーターで実装する関数ではありませんが、実質必要となるのでここでは含めています。

イテレーターの実装例

以下、イテレーターを実装したコードの例となります。

# カスタムイテレータークラス
class CustomIterator extends Reference:
	var length := 8 # データサイズ
	var _index := 0 # イテレーターのインデックス
	
	func _iter_init(_arg) -> bool:
		# イテレーターの開始時に呼び出される関数
		_index = 0
		return _iter_is_running() # ループが終了するかどうかを返す
	func _iter_next(_arg) -> bool:
		# イテレーターの次のループ時に呼び出される関数
		_index += 1
		return _iter_is_running() # ループが終了するかどうかを返す
	func _iter_get(_arg) -> int:
		# 現在のイテレーターの値を返す
		return _index
	func _iter_is_running() -> bool:
		# ループが終了するかどうかを返す
		return _index < length # 最大サイズを超えたら終了

func _ready() -> void:
	var c = CustomIterator.new()
	for i in c:
		print(i)

“Reference” を継承し、それぞれの関数を実装します。

実行結果
1
2
3
4
5
6
7

1つのスクリプトに実装する場合

先程の例は、インナークラス(スクリプト内に別クラスが存在する)での書き方でしたが、1つGDScriptに記述する場合は以下のようになります。

extends Reference

# カスタムイテレータークラス
class_name CustomIterator

var length := 8 # データサイズ
var _index := 0 # イテレーターのインデックス

func _iter_init(_arg) -> bool:
	# イテレーターの開始時に呼び出される関数
	_index = 0
	return _iter_is_running() # ループが終了するかどうかを返す
func _iter_next(_arg) -> bool:
	# イテレーターの次のループ開始時に呼び出される関数
	_index += 1
	return _iter_is_running() # ループが終了するかどうかを返す
func _iter_get(_arg) -> int:
	# 現在のイテレーターの値を返す
	return _index
func _iter_is_running() -> bool:
	# ループが終了するかどうかを返す
	return _index < length # 最大サイズを超えたら終了

“CustomIterator.gd” を作成しこのように記述します。

そして別のスクリプトから呼び出す場合には以下のように記述します。

# スクリプトをプリロード
const CustomIter = preload("res://CustomIterator.gd")

func _ready() -> void:
	var c = CustomIter.new()
	for i in c:
		print(i)
実行結果
0
1
2
3
4
5
6
7

参考

こちらの動画内で紹介されていた「TIPS #5 Custom Iterators」を参考にさせていだきました。