ゲーム制作勉強中!あこがれだったプログラマーに今からなろう!

昔、あこがれていたプログラマー。今からでも勉強してみようと思い立ち、チャレンジ開始! 勉強メモや、悪戦苦闘な日々の記録です。

Unity経験者のGodotの遊び方:乱数・awaitについて

昨日、GameManagerらしきものをGodotで作成することはできたので、Enemy.gd(これで、Enemyスクリプトを表すらしいです。)
を一気に進めていこうと思います。

Unityで作成した時のEnemyControllerには、下記の関数がありました。
・SetParameter関数
・SetEnemyColor関数
・Moving関数
・ScaleVertically関数
・OnCollisionEnter2D関数

EnemyGeneratorクラスからEnemyが生成されるタイミングで、SetParameter関数とSetEnemyColor関数が呼ばれ、
GameManagerの設定値からEnemyの初期設定を行います。

Moving関数はEnemyを徐々に下に移動させる機能と、色変わりもしくは成長する機能を持ちます。
ScaleVertically関数は、画面に動きが欲しかったので、EnemyをゆっくりY方向に伸縮させています。

今日のところは、これらをGDScript化していきます。

まずは、変数の宣言に、SetParameter関数とSetEnemyColor関数を作ります。

extends Area2D

@export var Color_Change_Span :float #敵の色が変わるスピード
@export var Growth_Span :float #敵が大きくなるスピード
@export var Drop_Down_Speed :float #敵が落ちてくるスピード
@export var Living_Time :float #敵が生きている時間

var Next_Color_Change_Time :float #次に色が変わる時間
var Next_Growth_Time :float #次に大きくなる時間

var Enemy_Color :int #敵の色
var Enemy_Size :int #敵の大きさ

@export var Puru_puru_Time :float #ぷるぷるタイム

var isIncreasing :bool =true #呼吸方向判定用
var Base_Scale :Vector3 #基本のスケール

@export var Scale_Change :float #スケールの増減値

@export var Scale_Interval :float #スケール変更の間隔
var Color_Change_Timer :float
var Puru_puru :bool
var Puru_puru_Counter :int
var Color_Changing :bool

var Base_Size :float =0.4 #Enemy_Sizeの基準値
var Base_Growth_Size :float =0.05 #Enemy_Size が1増えた時の成長量の基準値

func _ready():
	_Scale_Vertically()


func _Set_Parameter():
	Living_Time=0
	Color_Change_Span=GameManager.Set_Color_Change_Span-GameManager.Game_Level*0.15
	Drop_Down_Speed=GameManager.Set_Drop_Down_Speed+GameManager.Game_Level*0.02

	Next_Color_Change_Time=Color_Change_Span
	Next_Growth_Time=Growth_Span

	# _Moving()
	_Scale_Vertically()

func _Set_Enemy_Color(color:int):#intを引数で受け取る。
	Enemy_Color=color
	$Sprite2D.modulate=Color(GameManager.Colors[Enemy_Color].x,GameManager.Colors[Enemy_Color].y,GameManager.Colors[Enemy_Color].z,1)
	pass

SetParameter関数

func _Set_Parameter():
	Living_Time=0
	Color_Change_Span=GameManager.Set_Color_Change_Span-GameManager.Game_Level*0.15
	Drop_Down_Speed=GameManager.Set_Drop_Down_Speed+GameManager.Game_Level*0.02

	Next_Color_Change_Time=Color_Change_Span
	Next_Growth_Time=Growth_Span

	_Scale_Vertically()

ここでは何も難しいことはしていません。

GameManagerというグローバルクラスから、
・Game Level変数 ・・・ゲームレベルを格納
・Set_Color_Change _Span変数 ・・・基本の色変わりの間隔を格納
・Set _Drop _Down_Speed変数 ・・・基本の落下速度を格納
を取得してきて、計算した結果をプライベート変数のColor_Change_Span変数と、Drop_Down_Speed変数に代入しています。


SetEnemyColor関数

func _Set_Enemy_Color(color:int):#intを引数で受け取る。
	Enemy_Color=color
	$Sprite2D.modulate=Color(GameManager.Colors[Enemy_Color].x,GameManager.Colors[Enemy_Color].y,GameManager.Colors[Enemy_Color].z,1)
	pass

ここは昨日作成した通りです。
GameManagerのColors配列変数から色番号に沿って色情報を取得して、Sprite2Dの色を設定しています。

Moving関数 → _physics_process関数

func _physics_process(delta): # ← deltaを渡している。
	Living_Time+=delta
	print("Living_Time ",Living_Time)
	if GameManager.Game_Over:
		#将来、ゲームオーバーの処理を書く場所
		pass

	position.y+=GameManager.Set_Drop_Down_Speed #今だけの暫定。完成時にはDrop_Down_Speedに置き換える。

	if position.y<+5:
		# $QueueFree()
		# Enemyをオブジェクトプールに返す処理を書く
		pass
	

	if Living_Time>Next_Color_Change_Time:

		Color_Change_Timer+=delta

		if Color_Change_Timer>Puru_puru_Time:
			Puru_puru=!Puru_puru
			Color_Change_Timer=0
			Puru_puru_Counter+=1

			if Puru_puru:
				$Sprite2D.scale=Vector2(
					(Base_Size+Enemy_Size*Base_Growth_Size)-Scale_Change/2,
					(Base_Size+Enemy_Size*Base_Growth_Size)-Scale_Change/2)
				
			else:
				$Sprite2D.scale=Vector2(
					(Base_Size+Enemy_Size*Base_Growth_Size)+Scale_Change/2,
					(Base_Size+Enemy_Size*Base_Growth_Size)+Scale_Change/2)

		if Puru_puru_Counter>6:
			Puru_puru_Counter=0
			Puru_puru=false

			match randi_range(0,1):
				0:
					_Set_Enemy_Color(GameManager._Set_Enemy_Color())
					Next_Color_Change_Time+=Color_Change_Span
					pass
				1:
					Enemy_Size+=1
					$Sprite2D.scale=Vector2(
						(Base_Size+Enemy_Size*Base_Growth_Size),
						(Base_Size+Enemy_Size*Base_Growth_Size))

					Next_Color_Change_Time+=Color_Change_Span
					pass||<

今日のメインはここです🤣
Unityではasync/awaitで行っていましたが、よくよく見返せば、別に非同期である必要はなかったので、普通にUnityでいうFixedUpdateの
_physics_process(delta)関数で作る事にしました。
Unityと同じく、ほぼ毎フレームで呼び出される関数です。

func _physics_process(delta): # ← deltaを渡している。
	Living_Time+=delta

いきなり、Unityと異なる場所です。
GodotではTime.deltaTimeはdeltaと書きます。しかも、関数で引数で渡しておかないと関数内で使用できません。

	position.y+=GameManager.Set_Drop_Down_Speed #今だけの暫定。完成時にはDrop_Down_Speedに置き換える。

昨日も書きましたが、Godotではシーン内のノードのプロパティは比較的簡単にアクセスすることができます。
今回は、スクリプトがアタッチされているArea2D自身のpositionを変更したいのですが、

positionはUnityと同じようにTransformの下になるのですが、Godotの場合、positionと書くだけで大丈夫です。
大きな違いは、Unityでは2Dも3DもpostionやscaleはVector3ですが、Godotの場合は2DではpositionもscaleもVector2になります。
Unityの感覚でVector3で計算しようとするとエラーになってしまうので、最初のうちは注意が必要そうです。

			match randi_range(0,1):
				0:
					_Set_Enemy_Color(GameManager._Set_Enemy_Color())
					Next_Color_Change_Time+=Color_Change_Span
					pass
				1:
					Enemy_Size+=1
					$Sprite2D.scale=Vector2(
						(Base_Size+Enemy_Size*Base_Growth_Size),
						(Base_Size+Enemy_Size*Base_Growth_Size))

					Next_Color_Change_Time+=Color_Change_Span
					pass

randi_rangeというのは、乱数を発生させる関数で、引数で範囲を指定します。

randi_range(0,1):

最初の引数が最小値を示し、二番目の引数が最大値を示します。
ですので、この場合は0か1の乱数が作成されます。
少数の乱数を作成したい場合は、

randf_range(0.0,1.0):

この様に、randの次の文字をintのiから、floatのfにかえて引数も少数表記にします。
これで0〜1.0の少数の乱数が作成されます。
次にmatchですが、これはUnityでいうSwitch分岐になります。

つまり、乱数で0か1を作成し、その数字に合わせて、成長か色変わりかを判定する仕組みです。



ScaleVertically関数

func _Scale_Vertically():
	while true:
		if GameManager.Game_Over:
			break
		
		await get_tree().create_timer(Scale_Interval).timeout

		if isIncreasing:
			scale=Vector2(
				(Base_Size+Enemy_Size*Base_Growth_Size),
				(Base_Size+Enemy_Size*Base_Growth_Size)*0.96)
		else:
			scale=Vector2(
				(Base_Size+Enemy_Size*Base_Growth_Size),
				(Base_Size+Enemy_Size*Base_Growth_Size)*1.02)

		# 増減の方向を切り替え
		isIncreasing = not isIncreasing

今日の最後です。
Godotは3.5版から4版の変更でかなり破壊的な改革が入った様で、async・awaitも仕様が変わった様です。
まだしっかりと公式ドキュメントを読みきれていないのですが、どうも、関数内にawaitがあると、その関数は非同期として扱われるようです。
ですので、この関数の様にwhile trueで無限ループする処理を作っても、問題ありませんでした。

await get_tree().create_timer(Scale_Interval).timeout

Godot特有の書き方で、変数宣言したScale_Intervalの秒数のTimerを作成して、そのTimerがタイムアウトするまでawait。
つまり、Scale_Interval秒だけawaitしてくれます。

これで基本の敵の挙動はできたから、次はEnemyGeneratorかなあ🧐
www.youtube.com