Menu
CoddyTech

コマンドパターン

CoddyのGDScriptジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 50/65。

Commandパターンは、アクションをオブジェクトにします。そのオブジェクトにはアクションを実行するために必要なすべての情報が保存されるため、リストに保持したり、後で実行したり、取り消したりできます。

すべてのコマンドには、抽象親クラスで宣言された同じ2つのメソッドがあります。

# command.gd
@abstract
class_name Command
extends RefCounted

@abstract func execute() -> void
@abstract func undo() -> void

具体的なコマンドは、そのコマンドが作用するオブジェクト(レシーバーと呼びます)と、アクションのデータを保持します。ここでは、レシーバーは Hero で、var x := 0 を持っています。

# move_command.gd
class_name MoveCommand
extends Command

var hero: Hero
var dx: int

func _init(target: Hero, step: int) -> void:
	hero = target
	dx = step

func execute() -> void:
	hero.x += dx

func undo() -> void:
	hero.x -= dx

Invoker(実行者)はコマンドを実行し、記録します。コマンドが何をするのかを知る必要はありません。

# command_history.gd
class_name CommandHistory
extends RefCounted

var done: Array[Command] = []

func run(command: Command) -> void:
	command.execute()
	done.append(command)

func undo_last() -> void:
	if done.is_empty():
		return
	var last: Command = done.pop_back()
	last.undo()
# main.gd
var hero := Hero.new()
var history := CommandHistory.new()
history.run(MoveCommand.new(hero, 3))
history.run(MoveCommand.new(hero, 2))
print(hero.x)
history.undo_last()
print(hero.x)
5
3

履歴はスタックです。pop_back()は最新のコマンドを取り出すため、取り消しではアクションを逆順にたどって戻ります。同じオブジェクトを使って、キーの再割り当て(キーをコマンドに対応付ける)、リプレイ(保存したリストを再実行する)、ターンキューも実現できます。

参照は一方向です。コマンドはヒーローを参照し、ヒーローはコマンドについて何も知りません。ヒーローもコマンドを保存すると、オブジェクト同士が互いを保持し、解放されなくなります。

challenge icon

チャレンジ

簡単

ヒーローはグリッド上を移動し、移動するたびに取り消すことができます。Command(command.gd)はexecute()とundo()を宣言し、Hero(hero.gd)はx、y、position_text()を持ちます。次のように実装してください。

  • MoveCommand:コンストラクターでヒーローと移動量step_x、step_yを保存します。execute()は移動量をヒーローのxとyに加算し、undo()はそれらを減算します。
  • CommandHistory:run(command)はコマンドを実行して保存します。undo_last()は最後に保存されたコマンドを取り消して削除し、trueを返します。保存されたコマンドがない場合はfalseを返します。

用意されているコードは、後続するアクションの数を読み取ってから、1行につき1つのアクション(up、down、left、right、またはundo)を読み取り、それぞれの後に位置を出力します。upはyを1減らします。right、up、undo、undo、undoを指定した場合の出力は次のとおりです。

right -> (1, 0)
up -> (1, -1)
undo -> (1, 0)
undo -> (0, 0)
Nothing to undo

コードはmove_command.gdとcommand_history.gdに記述してください。main.gdには用意されている入出力コードが含まれており、編集できません。

自分で試してみよう

extends Node

func _ready():
	# 提供された入出力コード: そのままにしてください
	var hero := Hero.new()
	var history := CommandHistory.new()
	var steps := {"up": [0, -1], "down": [0, 1], "left": [-1, 0], "right": [1, 0]}
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var action := OS.read_string_from_stdin()
		if action == "undo":
			if history.undo_last():
				print("undo -> ", hero.position_text())
			else:
				print("Nothing to undo")
		else:
			var step: Array = steps[action]
			history.run(MoveCommand.new(hero, step[0], step[1]))
			print(action, " -> ", hero.position_text())
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: GDScriptオンラインコンパイラ