コンポジションと継承
CoddyのGDScriptジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 40/65。
クラスは2つの方法でコードを再利用します。継承(extends)は、あるクラスが別のクラスの一種であることを示します。つまり、MageはPlayerです。コンポジションは、あるクラスが一部を持つことを示します。メンバー変数に別のオブジェクトを格納し、そのオブジェクトに処理を委ねます:
# motor.gd
class_name Motor
extends RefCounted
func start() -> String:
return "motor running"
# car.gd
class_name Car
extends RefCounted
var motor := Motor.new()
func start() -> String:
return "Car starts, " + motor.start()Car.new().start()はCar starts, motor runningを返します。車はモーターの仕組みを知りません。motor.start()を呼び出します。このように呼び出しを部品に任せることを委譲と呼びます。(EngineはすでにGodotのクラスなので、クラス名はMotorです。)
部品が変更されたり組み合わされたりするとき、コンポジションの真価が発揮されます。継承では、歩くキャラクターと飛ぶキャラクターは2つのサブクラスであり、飛び始めるコウモリはクラスを変更できません。コンポジションでは、キャラクターは移動の部品を持ち、それを切り替えるのは1つの代入です:
# character.gd
class_name Character
extends RefCounted
var char_name: String
var movement: Movement
func _init(new_name: String, new_movement: Movement) -> void:
char_name = new_name
movement = new_movement
func move() -> String:
return char_name + " " + movement.move()
# main.gd
var bat := Character.new("Bat", Walk.new())
print(bat.move()) # Batが歩く
bat.movement = Fly.new()
print(bat.move()) # Batが飛ぶここでは、WalkとFlyが小さなMovementクラスを拡張しており、2つの手法が連携して動作します。GDScriptのクラスは1つのクラスしかextendできないため、複数のパーツを保持することで、1つのオブジェクトに複数の能力を持たせる方法としてもコンポジションが使われます。
決して変わることのない実際の「〜である」という関係には継承を使いましょう。クラスが何かを「持つ」場合や、ゲームの実行中にその部分を交換する必要がある場合は、コンポジションを使いましょう。
チャレンジ
簡単騎士は武器を継承するのではありません。騎士は武器を持っており、武器を切り替えることができます。両方のクラスを作成しましょう。
Weaponはweapon_nameとdamageを保持し、どちらもコンストラクターで設定します:Weapon.new("Sword", 7)。Knightはknight_name(コンストラクターから設定)と武器を保持します。武器は最初はnullです。equip(new_weapon)は武器を置き換え、drop()は武器をnullに戻します。attack()は、騎士の名前と武器の名前およびダメージを使ってAda attacks with Sword for 7 damageを返します。武器がない場合はAda has no weaponを返します。
用意されたコードは騎士の名前と後続のコマンド数(equip Sword 7、drop、attack)を読み取り、それらを実行します。Ada と attack、equip Sword 7、attack、equip Axe 12、attack、drop、attack の場合、次のように出力されます:
Ada has no weapon
Equipped Sword
Ada attacks with Sword for 7 damage
Equipped Axe
Ada attacks with Axe for 12 damage
Dropped the weapon
Ada has no weaponコードは weapon.gd と knight.gd に記述します。main.gd には用意された入出力コードが含まれており、編集できません。
自分で試してみよう
extends Node
func _ready():
# 用意された入出力コード: そのままにしておく
var knight := Knight.new(OS.read_string_from_stdin())
var count := int(OS.read_string_from_stdin())
for i in count:
var parts := OS.read_string_from_stdin().split(" ")
if parts[0] == "equip":
knight.equip(Weapon.new(parts[1], int(parts[2])))
print("Equipped ", parts[1])
elif parts[0] == "drop":
knight.drop()
print("Dropped the weapon")
else:
print(knight.attack())
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: GDScriptオンラインコンパイラ