Menu
CoddyTech

ステートパターン

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

Stateパターンは、オブジェクトが取り得る各モードに対して、個別の状態オブジェクトを与えます。オブジェクトは現在の状態に処理を委譲するため、状態が変わると振る舞いも変化します。

状態が少ない場合は、enum と match で十分です。各状態が複数のアクションに対してそれぞれ独自の方法で反応するようになると、その match は状態やアクションが増えるたびに大きくなります。State パターンでは、各状態のルールをそれぞれ専用のクラスに移します。

# hero_state.gd
@abstract
class_name HeroState
extends RefCounted

@abstract func get_state_name() -> String
@abstract func handle(action: String) -> HeroState

handle()は次の状態を返します。切り替える新しい状態オブジェクト、またはそのままの状態を維持するためのselfです。

# idle_state.gd
class_name IdleState
extends HeroState

func get_state_name() -> String:
	return "Idle"

func handle(action: String) -> HeroState:
	if action == "run":
		return RunState.new()
	return self

RunStateも同じように記述します。名前は"Running"で、"stop"はIdleState.new()を返します。ヒーロー、つまりコンテキストは現在の状態を保持し、その状態に何をすべきかを尋ねます。

# hero.gd
class_name Hero
extends RefCounted

var state: HeroState = IdleState.new()

func handle_action(action: String) -> void:
	var next := state.handle(action)
	if next != state:
		print(state.get_state_name(), " -> ", next.get_state_name())
		state = next
# main.gd
var hero := Hero.new()
for action in ["stop", "run", "run", "stop"]:
	hero.handle_action(action)
Idle -> Running
Running -> Idle

同じアクションでも状態によって意味が異なります。"stop"は待機中は何もせず、実行中のヒーローを停止させます。各ルールはその状態のクラスに属するため、ジャンプ中のような新しい状態を追加するには、新しいクラスを1つ作成します。

状態はヒーローを保持する代わりに、次の状態を返します。ヒーローが状態を保持する一方で、その状態もヒーローを保持すると参照サイクルが発生し、どちらのオブジェクトも解放されなくなります。

challenge icon

チャレンジ

簡単

ドアの状態はLocked、Closed、またはOpenです。DoorState(door_state.gd)はget_state_name()とhandle(action)を宣言し、LockedState(locked_state.gd)は実装済みです。unlockするとドアはClosedになります。以下を実装してください:

  • ClosedState.handle():openの場合は新しいOpenStateを、lockの場合は新しいLockedStateを返し、それ以外の場合はselfを返します。
  • OpenState.handle():closeの場合は新しいClosedStateを返し、それ以外の場合はselfを返します。
  • Door.handle_action(action):現在の状態に次の状態を問い合わせます。同じオブジェクトの場合はACTION: still STATEを出力し、そうでない場合はACTION: OLD -> NEWを出力して、新しい状態に切り替えます。

ドアはLocked状態から始まります。用意されているコードは、後に続くアクションの数を読み取り、その後1行につき1つのアクションを読み込み、最後の状態を出力します。open、unlock、open、lockの場合、出力は次のとおりです:

open: still Locked
unlock: Locked -> Closed
open: Closed -> Open
lock: still Open
Final state: Open

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

自分で試してみよう

extends Node

func _ready():
	# 提供された入出力コード: そのままにしてください
	var door := Door.new()
	var count := int(OS.read_string_from_stdin())
	for i in count:
		door.handle_action(OS.read_string_from_stdin())
	print("Final state: ", door.state.get_state_name())
quiz icon腕試し

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

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

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