1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Godot4で3D風2D縦スクロールシューティングゲームを作る 15回目 スクロール速度を変化するシナリオ要素を追加

Posted at

Godot4.1.3でシューティングゲームを作っていきます。

スクロール速度を変化するシナリオ要素を追加

 背景のスクロールの速度を変化するためのシナリオ要素を作成します。ついでに、画面から敵と敵の弾がすべて消えるまで待つシナリオ要素も追加します。

スクロール速度変更の方法について

 今の実装では、天球の星空画像を1秒間に-5°回転させることで、背景がスクロールしているように見せています。スクロールの速度を指定するのに、-5°の数字を変更するのは符号を間違ったりして混乱しそうなので、倍率変数を追加して、倍率変数を変更すると背景のスクロールの速度が変化するようにします。

  • 変更前:固定
     スクロール速度 = -5[°/秒]
  • 変更後:倍率変数を追加
     スクロール速度 = -5[°/秒] × 倍率変数

 またいきなり指定した倍率になるのではなく、時間をかけて徐々に倍率変数が目標倍率に変化するようにします。そのためスクロール速度の変更パラメータは、目標速度倍率と倍率変更完了時間の2つを指定するようにします。例えば現在の倍率変数が1.0で、目標速度の倍率を7.0倍、倍率変更完了時間を5.0秒に指定すると以下のグラフのように、5秒間かけて倍率変数が1.0から7.0に徐々に変化するようにします。

image.png

 経過時間に比例して倍率を変更するので、Timerノードを使用して、time_leftプロパティから経過時間を取得します(経過時間=倍率変更完了時間 - time_left)

 また、経過時間に比例した倍率を計算するため、remap関数を使用します。

float remap(value: float, istart: float, istop: float, ostart: float, ostop: float)

 入力値の範囲をistart、istopに設定し、出力したい値の範囲をostart,ostopに指定して、valueに入力値を指定すると、入力値から入力値範囲内の位置を計算して、出力値範囲から対応する値を出力します。
 入力値が入力値範囲外の場合、出力値も範囲外になります。
 今回の場合入力値が経過時間、出力値は倍率になります。

入力値範囲:istart:0.0秒~istop:5.0秒
出力値範囲:ostart:1.0倍~ostop:7.0秒

入力値value 出力 説明
0.0秒 1.0倍 下限
2.5秒 4.0倍 入力値が中間の場合出、力値も中間
4.0秒 5.8倍 入力値が上限よりに8割の場合、出力値も
5.0秒 7.0倍 上限
-1.0秒 -0.2倍 範囲外の入力値の場合、出力値も範囲外
6.0秒 8.2倍 範囲外の入力値の場合、出力値も範囲外

image.png

github

 本記事で実装したものをgithubで公開しています。

追加・修正ファイル

 下図で選択されているのが本記事で追加・修正したファイルで、2か所の赤枠で囲んだファイルが新規追加した2つのシナリオ要素です。
 上の赤枠の「control_scroll_speed.gd/tscn」が背景のスクロール速度を変化するためのシナリオ要素でgame.gd、WorldEnvironment.gd、global.gdと合わせて、スクロール速度変更を実現します。
下の赤枠の「wait_until_all_disappear.gd/tscn」は、画面上から敵や敵の弾がいなくなるまで待つためのシナリオ要素です。
 新規追加したシナリオ要素は、scenario_stage01.tscnシナリオファイルに追加します。

image.png

背景のスクロール速度を変化するシナリオ要素の追加

背景スクロール速度の倍率変数を追加

 背景のスクロールを制御しているWorldEnvironment.gdに倍率変数を追加します。

res://game/WorldEnvironment.gd

extends WorldEnvironment

+var d_scroll_speed_magni = 1.0
var _d_speed_degps = -5.0		# 回転速度 [degree/sec]

func _process(delta):
-	get_environment().sky_rotation.x += deg_to_rad(m_d_speed_degps * delta)
+	get_environment().sky_rotation.x += deg_to_rad(_d_speed_degps * d_scroll_speed_magni * delta)

 メンバ変数に「d_scroll_speed_magni」が倍率変数で、_processメソッド内の回転量に掛け算するようにしました。

シナリオ要素からWorldEnvironmentをアクセスできるようにグローバル変数を追加

 シナリオ要素から、WorldEnvironment.gdのメンバ変数、「倍率変数」にアクセスしたいので、グローバルデータにnode_worldを追加して、game.gdの_readyメソッドでWorldEnvironmentノードを設定し、「g_val.node_world」でいつでもどこからでも、WorldEnvironmentにアクセスできるようにします。

res://global/global.gd

extends Node3D

# アップグレードアイテムの種別
enum EnItemKind{
	POWER_UP,
	SPEED_UP	
}

# ゲーム画面の見える範囲
# ゲーム画面の上端をtop、下端をbotommとする
var d_visible_top_z_m = -200.0
var d_visible_top_min_x_m = -75.0
var d_visible_top_max_x_m =  75.0

var d_visible_bottom_z_m = -15.0
var d_visible_bottom_min_x_m = -25.0
var d_visible_bottom_max_x_m =  25.0


# gameのインスタンス
var node_game : Node3D = null 

# 生成したインスタンスの追加先
var node_lasers : Node3D = null
var node_enemies : Node3D = null 
var node_bullets : Node3D  = null
var node_others : Node3D  = null
+var node_world : WorldEnvironment  = null

# Playerのインスタンス
var node_player : Area3D = null 

res://game/game.gd

extends Node3D

var _scn_scenario_stage = preload("res://scenario/scenario_stage01.tscn")

var _node_scenario_ins

var _i_score : int = 0

func _ready():
	# gameインスタンスを保存
	g_val.node_game = self
	
	# インスタンス化したシーンの追加先を設定する
	g_val.node_lasers = $DynamicNodes/lasers
	g_val.node_enemies = $DynamicNodes/enemy/bodies
	g_val.node_bullets = $DynamicNodes/enemy/bullets
	g_val.node_others = $DynamicNodes/others
+	g_val.node_world = $WorldEnvironment

	# Playerインスタンスを設定する
	g_val.node_player = $Player
	
	# ステージシナリオをシーンツリーに追加
	_node_scenario_ins = _scn_scenario_stage.instantiate()
	add_child(_node_scenario_ins)

func _process(delta):
	# シナリオを駆動する
	_node_scenario_ins.drive_scenario()

func _on_add_to_score(add_score):
	_i_score += add_score
	$HUD/score_value.text = "%010d" % _i_score 

スクロール速度変更シナリオ要素用シーンを追加

 res://scenario/elements配下に新規シーンを追加します。ルートノードはNode3Dにしてファイル名はcontrol_scroll_speed.tscnにします。
 また子ノードに、倍率変更完了時間までの経過時間を計算するため、Timerノードを追加します。1回だけ時間を計りたいので、One Shotプロパティをオンにします。

image.png

image.png

スクロール速度変更シナリオのスクリプトを実装

 control_scroll_speed.tscnシーンのルートノードにスクリプトをアタッチし、以下のように実装します。

extends Node

@export var _d_target_scroll_magni = 7.0	# 目標とするスクロール速度の倍率
@export var _d_period_time_sec = 5.0		# 目標倍率完了時間

var _d_init_speed_magni = 1.0

func start_element():
	# シナリオ要素開始時点の、スクロール倍率変数を取得
	_d_init_speed_magni = g_val.node_world.d_scroll_speed_magni
	# タイマー開始
	$Timer.start(_d_period_time_sec)

func is_blocking_to_move_on_next_scenario() -> bool:
	# シナリオを次に進める
	return false

func _physics_process(delta):
	if !$Timer.is_stopped():
		# タイマーが動いている間はスクロール速度倍率を制御する
		var d_past_time = _d_period_time_sec - $Timer.time_left
		var sc_magni = remap(
					d_past_time,								# value
					0,					_d_period_time_sec,		# istart, istop
					_d_init_speed_magni,_d_target_scroll_magni	# ostart, ostop
				)
		g_val.node_world.d_scroll_speed_magni = sc_magni
	else:
		pass

 スクリプトを説明すると、スクロール速度のパラメータとして、目標とするスクロール速度の倍率と倍率変更完了時間の2つを実装します。exportして、変更可能にします。

@export var _d_target_scroll_magni = 7.0	# 目標とするスクロール速度の倍率
@export var _d_period_time_sec = 5.0		# 目標倍率完了時間

 スクロール速度の倍率変数は、現在の倍率から目標倍率まで変化させるので、シナリオ要素が開始した時点の現在倍率を保存します。パラメータの目標倍率完了時間を指定して、Timerノードを開始します。

var _d_init_speed_magni = 1.0

func start_element():
	# スクロール開始時点の、スクロール速度倍数を取得
	_d_init_speed_magni = g_val.node_world.d_scroll_speed_magni
	# タイマー開始
	$Timer.start(_d_period_time_sec)

 スクロースの速度を変更している間にシナリオを止める理由はないので、falseを返して次のシナリオ要素を開始を許可します。

func is_blocking_to_move_on_next_scenario() -> bool:
	# シナリオを次に進める
	return false

 _physics_process()でぐるぐる回っています。
 Timerノードを開始してからタイマーが満了して停止するまでの間、経過時間を計算して、remap関数でスクロール速度の倍率を計算し、WorldEnvironmentノードの倍率変数を更新します。

func _physics_process(delta):
	if !$Timer.is_stopped():
		# タイマーが動いている間はスクロール速度倍率を制御する
		var d_past_time = _d_period_time_sec - $Timer.time_left
		var sc_magni = remap(
					d_past_time,								# value
					0,					_d_period_time_sec,		# istart, istop
					_d_init_speed_magni,_d_target_scroll_magni	# ostart, ostop
				)
		g_val.node_world.d_scroll_speed_magni = sc_magni
	else:
		pass

 WorldEnvironmentのd_scroll_speed_magniは、外部から読み書きされるのに排他していませんが、複数のシーン(シナリオ要素)が同時刻にスクロール速度の倍率を変更することはないという前提です。

画面から敵と敵の弾がすべて消えるまで待つシナリオ要素の追加

res://scenario/elements配下に新規シーンを作成します。ルートノードはNode3Dで、ファイル名はwait_until_all_disappear.tscnです。

res://scenario/elements/wait_until_all_disappear.tscn
image.png
 ルートノードにスクリプトをアタッチして下記のように修正します。
 敵、敵の弾、アイテムなど動的に生成されたインスタンスは、gameシーンのDynamicNodes配下の決められたノードに子ノードとして追加され、撃墜されたり画面外に出るなどして、不要になった場合は削除されます。
 つまり、DynamicNodes配下のenemy/bodiesノードとenemy/bulletsノードとothersノードの、いずれかの子ノード数が0より大きい場合は、敵か敵の弾、もしくはアイテムが存在すると判断できます。その場合trueを返して次のシナリオ要素に進むのをブロックし、子ノード数が全て0の場合は、falseを返して、次のシナリオ要素に進むのを許可します。

res://scenario/elements/wait_until_all_disappear.gd

extends Node

func start_element():
	pass
	
func is_blocking_to_move_on_next_scenario() -> bool:
	# 敵が残っている場合、シナリオは進めない
	if g_val.node_enemies.get_child_count() > 0:
		return true
	# 敵の弾が残っている場合、シナリオは進めない
	if g_val.node_bullets.get_child_count() > 0:
		return true
	# アイテムが残っている場合、シナリオは進めない
	if g_val.node_others.get_child_count() > 0:
		return true

	# シナリオを進める
	return false

 動的に生成したインスタンスの追加先のうち、敵と敵の弾、アイテムの追加先を赤枠で示しています。
res://game/game.tscn
image.png

新規追加したシナリオ要素をシナリオに追加

 本記事で追加した、control_scroll_speed.tscnを2つ、wait_until_all_disappear.tscnをひとつ、シナリオに追加しました。
res://scenario/scenario_stage01.tscn
image.png

 上の方のスクロール速度変更シナリオ要素control_scroll_speedは、目標速度倍率を7.0倍、目標倍率完了時間を5秒にしました。スクロール速度の初期値は1.0倍なので、5秒かけて1.0倍から7.0倍に変化します
image.png

 下の方のスクロール速度変更シナリオ要素control_scroll_speed2は目標速度倍率を1.0倍、目標倍率完了時間は同じ5秒にしました。スクロール速度が7.0倍になっているはずなので、5秒かけて7.0倍から1.0倍に変化します
image.png

 以上です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?