カスタムイテレータ
CoddyのGDScriptジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 33/65。
Pythonでは、__len__、__getitem__、__contains__、__iter__などのマジックメソッドを使って、クラスをリストのように振る舞わせることができます。Godotには、これらの役割のうち1つだけ、つまりforループ用の仮想メソッドがあります。次の3つのメソッドによって、for item in my_object:が機能します。
_iter_init(iter)は、最初の反復の前に1回実行されます。開始位置を設定し、最初の要素がある場合はtrueを返します。_iter_get(position)は現在の位置にある要素を返し、ループ変数にはその値が入ります。_iter_next(iter)は、それ以降の各反復の前に実行されます。次の位置に進み、要素が残っていない場合はfalseを返して、ループを終了します。
# team.gd
class_name Team
extends RefCounted
var _members: Array[String] = []
func add(member: String) -> void:
_members.append(member)
func _iter_init(iter: Array) -> bool:
iter[0] = 0
return iter[0] < _members.size()
func _iter_next(iter: Array) -> bool:
iter[0] += 1
return iter[0] < _members.size()
func _iter_get(position):
return _members[position]iterは、Godotが各ループに対して作成する1つのスロットを持つ配列です。iter[0]にはそのループの位置が格納され、Godotはその値を_iter_get()に渡します。これでTeamはforループで使えるようになります。
var team := Team.new()
team.add("Ada")
team.add("Bo")
for member in team:
print(member) # Ada、次にBo位置はメンバー変数(_positionなど)ではなく、iter[0]に保持します。各ループは独自のiterを持つため、同じチームを対象とする2つのループは、一方がもう一方の内側にある場合でも、互いに影響しません。メンバー変数を使うと、内側のループによって外側のループの位置が移動してしまいます。
リストの他の操作には仮想メソッドがありません。len(team) と team[0] はエラーになります。size()、get_at(index)、has(member) など、Array のメソッドと同じような名前の通常のメソッドを書きます。in でメンバーシップをテストしないでください。"Ada" in team は、Team に Ada という名前のプロパティまたはメソッドがあるかどうかを調べるため、false になります。
チャレンジ
簡単bookshelf.gd に Bookshelf クラスを書いてください。タイトルは _titles に保持し、最大 _capacity 冊の本を格納します。
add(title)はタイトルを追加してtrueを返します。棚がいっぱいの場合、またはすでにそのタイトルがある場合は、追加せずにfalseを返します。size()は本の冊数を返し、has(title)はそのタイトルが棚にあるかどうかを返します。get_at(index)はそのインデックスにあるタイトルを返します。インデックスが棚の範囲外の場合は、空の文字列を返します。_iter_init()、_iter_next()、_iter_get()により、forループで追加された順にタイトルをたどることができます。
提供されたコードは、容量、タイトルの数、各行に1つずつタイトルを読み込み、追加できなかったタイトルを報告します。本の冊数を表示し、棚を対象にした for ループで本を一覧表示した後、もう1つタイトルを読み込み、そのタイトルが棚にあるかどうかと、最後の本を表示します。容量が 3 で、タイトルが Dune、Emma、Dune、Ulysses、Hamlet、検索するタイトルが Emma の場合、次のように表示されます。
Could not add: Dune
Could not add: Hamlet
Books: 3
1. Dune
2. Emma
3. Ulysses
Has Emma: true
Last: Ulyssesコードは bookshelf.gd に記述します。main.gd には提供された入出力コードがあり、編集できません。
自分で試してみよう
extends Node
func _ready():
# 提供された入出力コード:そのままにしておく
var shelf := Bookshelf.new(int(OS.read_string_from_stdin()))
var count := int(OS.read_string_from_stdin())
for i in count:
var title := OS.read_string_from_stdin()
if not shelf.add(title):
print("Could not add: " + title)
print("Books: %d" % shelf.size())
var number := 1
for title in shelf:
print("%d. %s" % [number, title])
number += 1
var query := OS.read_string_from_stdin()
print("Has %s: %s" % [query, shelf.has(query)])
print("Last: " + shelf.get_at(shelf.size() - 1))
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: GDScriptオンラインコンパイラ