【Godot】Lambda式でキャプチャした変数の値が書き換わらないときの対処方法

Lambda式の使い方で少しハマったので、メモ書きです。

Lambda式でキャプチャした変数の値が書き換わらないときの対処方法

例えば回復アイテムを使用する際、ステータスパラメータによる補正を加えたい場合に、補正の条件が複数あると、foreachで処理をしたいことがよくあると思います。

## 補正効果のforeach.
func foreach_additionals(f:Callable) -> void:
	for i in range(MAX_ADDITIONAL):
		var type = additionals[i]
		var val = additionals_val[i]
		f.call(i, type, val)

Lambda式を渡すことで、for文を省略する関数です。

この関数を使って以下のように実装したのですが、retの値が書き換わらずに「なぜ…?」となりました。(常に “ret” の値は “0”)

## 補正回復量を取得する.
func additional_recovery() -> int:
	if is_instance_valid(_owner) == false:
		return 0

	# この値がLambda式の中では書き変わらない.
	var ret = 0

	foreach_additionals(func(idx:int, type:eAdditional, val:int):
		match type:
			eAdditional.RECOVER_VIT: # 耐久値で回復補正.
				ret += _owner.vit * val
				print("eAdditional.RECOVER_VIT:", ret)
			eAdditional.RECOVER_DEX: # 器用さで回復補正.
				ret += _owner.dex * val
				print("eAdditional.RECOVER_DEX:", ret)
			_:
				pass
	)
	print("additional_recovery():", ret)
	return ret

調べたところによると、これは GDScript 2.0 の仕様で、プリミティブ型 (int や float など) の値の書き換えはスコープ外には反映されないとのことです。

Lambda functions capture the local environment. Local variables are passed by value, so they won’t be updated in the lambda if changed in the local function:

Lambda 関数はローカル環境をキャプチャします。ローカル変数は値によって渡されるため、ローカル関数で変更されてもラムダ内で更新されません。

GDScript reference

このような仕様になった理由は推測ですが、Lamda式のもととなった関数型言語では関数呼び出しによる副作用を避ける傾向があるので、そのあたりの設計思想が反映されたのかもしれません。

ひとまずの回避策としては、リストオブジェクトなど参照型の変数にすることで値を書き換えられるようになります。

## 補正回復量を取得する.
func additional_recovery() -> int:
	if is_instance_valid(_owner) == false:
		return 0
	
	var ret = [0] # GDScript 2.0の仕様上、オブジェクトにしないと値を変更できない.
	foreach_additionals(func(idx:int, type:eAdditional, val:int):
		match type:
			eAdditional.RECOVER_VIT: # 耐久値で回復補正.
				ret[0] += _owner.vit * val
				print("eAdditional.RECOVER_VIT:", ret)
			eAdditional.RECOVER_DEX: # 器用さで回復補正.
				ret[0] += _owner.dex * val
				print("eAdditional.RECOVER_DEX:", ret)
			_:
				pass
	)
	print("additional_recovery():", ret[0])
	return ret[0]

参考