0
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.

【Godot 4.0】スマホ3Dゲームを作るための勉強 その8 発射する

Last updated at Posted at 2023-03-01

ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
Godot_v4.0-rc1_win64.exe.zipを使用しています。

目的

 何か発射しましょう。

内容

 
 1. 新たにシーンを作成して、弾丸を作成します。
 2. 弾丸シーンをGDScriptから生成・追加しましょう
 3. タイマーシグナルを利用して一定時間置きに発射しましょう。

ベースプロジェクト

 下記で作成したプロジェクトをベースに機能追加をします。

 【Godot 4.0】スマホ3Dゲームを作るための勉強 その7 3D空間のタッチしたところに移動したい
 https://qiita.com/FootInGlow/items/564dfe87dc7fc5135c67

新規シーンを作成する

 シーンメニューから新規シーンを選択します。
スクリーンショット (19).png
 その他のノードボタンを押下して、CharacterBody3Dノードを追加します。
スクリーンショット (20).png

 下記のようにしましょう。
 CharacterBody3Dノードは「Bullet」に名前を変更します。
(_) S08_shoot_001 - Godot Engine 2023_02_27 19_16_07.png

 一度Ctrl-Sキーなどでシーンを保存しましょう。
 保存するとシーンファイルが追加されます。
 シーンファイルは拡張子が.tscnで、前回作成したシーンはglobal.tscn、今回作成したシーンはbullet.tscnです。ファイル名を変更している場合は適宜読み替えてください。
 (_) S08_shoot_001 - Godot Engine 2023_02_27 19_16_08.png

  • MeshInstance3Dにカプセル型を設定します。
    インスペクタ内のMeshInstance3D/Meshを新規CapsuleMeshに設定します。
    少し小さくしましょう。Radiusを0.1m、Heightを0.5mにします。
    bullet.tscn - S08_shoot_001 - Godot Engine 2023_03_01 19_51_50.png

  • CollisionShape3Dに衝突形状を設定
    MeshInstance3Dと同じ形・大きさにします。
    インスペクタ内のCollisionShape3D/Shapeに新規CapsuleShape3Dを設定します。
    Radiusを0.1m、Heightを0.5mにします。
    bullet.tscn - S08_shoot_001 - Godot Engine 2023_03_01 19_53_10.png

## スクリプトを設定する
 bulletを右クリックして「スクリプトをアタッチ」を選択します。
 前方に進むスクリプトです。

extends CharacterBody3D

@export var m_v_dir = Vector3(0.0, 0.0, -1.0)
@export var m_d_speed_mps = 2.0

func _physics_process(delta):
	velocity = m_v_dir.normalized() * m_d_speed_mps
	move_and_slide()

一次的にシーンの動きを確認する

 globalシーンにBulletシーンを一時的に追加して動きを確認します。

 ファイルシステムにあるBullet.tscnを、
 スクリーンショット (9).png
 シーン内のglobalにドラッグアンドドロップします。
 スクリーンショット (10).png

 global直下にBulletが追加されました。
 他のノードとぶつからないように、y=1.5mにします。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 14_21_47.png

 実行すると、球の前方にBulletで追加したノードが進むようになります。

 確認したらglobalに追加したBulletは削除してください。

Playerノードから発射するようにする

 スクリーンにタッチしている間、1秒周期で繰り返しPlayerからBulletを発射するようにします。
 先ほどはIDE上のドラッグアンドドロップでBulletシーンをglobalシーンに追加しましたが、同じことをGDScriptから実行します。

Timerノードを追加して周期的にsignalを受信する

 Godotにはノード間で通信をするためのsignal通信の仕組みがあります。
 Timerノードは指定した時間経過すると、timeoutシグナルを発行することができ、同じシーン内の任意のスクリプトで受信することができます。タイマーは自動で繰り返されます。1回だけタイマーを起動することも簡単にできるので、時間でイベントが欲しいときにはとても便利です。

 Playerノードを右クリックして「子ノードを追加」からTimerを追加します。
 また名称をTimer_fireにします。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 21_12_07.png

 Timer_fireをクリック選択した状態でインスペクタを表示します。
 Autostartをオンにします。実行すると自動的にタイマーが開始します。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 21_36_25.png

 続いてインスペクタの隣の「ノード」タブをクリックします。
 シグナルを選択(選択すると青になります)すると下記のように、Timerノードが発行するsignalが表示されます。
 timeoutしたときにPlayerノードに通知してほしいので、timeoutを右クリックして「接続」します。
スクリーンショット (13).png
 同じシーンの中のスクリプトを持つノードを選択できます。受信側メソッド名を確認して、Playerを選択して接続ボタンを押下します。
スクリーンショット (14).png

 Playerのスクリプトを見ると、先ほどのダイアログに表示されていた受信側メソッドが追加されます。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 21_23_38 (1).png
これでTimerノードのtimeoutシグナルがPlayerのスクリプトに接続されました。接続されていることはIDEの3か所で確認することができます。

 まずシーンのsignalを追加したTimer_fireノードの右に電波のようなアイコンが表示されます。
 この電波のようなアイコンをクリックすると、
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 21_23_37.png
 ノード/シグナル表示がアクティブになります。
 先ほど接続したtimeoutを見ると下に「..::_on_timer_fire_timeout()関数と接続したことが表示されています。
 「..」が1個上の階層を意味するので、シーン内でTimerの親ノードのスクリプトの_on_timer_fire_timeout()関数を表しています。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 21_23_38.png
 この関数をダブルクリックすると接続したPlayerのスクリプトの該当の関数が表示されます。
bullet.tscn - S08_shoot_001 - Godot Engine 2023_03_01 20_06_54.png
 関数の左側にある「➡コ」みたいなアイコンをクリックすると、送信元であるソースノード、送信先のターゲットノードとその間を接続するシグナル名を表示します。
スクリーンショット (43).png

PlayerのGDScriptの修正してtimeout時にBulletシーンを追加する

 タッチしている間、1秒周期で発射するように Player.gdスクリプトを下記のように修正します。
またドラッグアンドドロップでPlayerのインスペクタにBullet.tscnを登録する必要がありますので後で説明します。

extends CharacterBody3D

@export var m_speed_mps = 5.0
@export var m_ray_length_m = 1000		# [m]十分な長さにする
@export var m_scn_bullet : PackedScene

var m_touch_pos = Vector2.ZERO
var m_d_move_to_pos_x_m = 0
var m_f_is_screen_touch = false

func _input(event):
	if ( event is InputEventScreenDrag ) or ( event is InputEventScreenTouch ):
		m_touch_pos = event.position
		# スマホタッチ中フラグ
		m_f_is_screen_touch = event.is_pressed() or ( event is InputEventScreenDrag )

func _physics_process(delta):
	var camera = get_viewport().get_camera_3d()

	# カメラを利用して3D空間のカメラ位置とタッチしたピクセルに対応する方向の1000m先の位置を計算する
	var from3d = camera.project_ray_origin(m_touch_pos)
	var to3d = from3d + camera.project_ray_normal(m_touch_pos) * m_ray_length_m

	# 3D ray physics queryの作成
	var query = PhysicsRayQueryParameters3D.create(from3d, to3d)
	query.collide_with_areas = true		# Area3Dを検知できるようにする
	
	# Godotの3Dの物理とコリジョンを保存しているspaceという情報を使用してオブジェクト検出
	var space_state = get_world_3d().direct_space_state
	var result = space_state.intersect_ray(query)
	if result:
		m_d_move_to_pos_x_m = result.position.x

	# 移動制御は毎周期実行する
	var d_diff_x_m = (m_d_move_to_pos_x_m - transform.origin.x);
	var d_speed_mps = m_speed_mps if d_diff_x_m>0 else -m_speed_mps
	var d_move_diff_x_m = d_speed_mps * delta
	if absf(d_move_diff_x_m) > absf(d_diff_x_m):
		# 移動すると目標位置を超えるため、目標位置を設定する
		transform.origin.x = m_d_move_to_pos_x_m
	else:
		move_and_collide(Vector3(d_move_diff_x_m, 0, 0))

func _on_timer_fire_timeout():
	# スマホタッチ中の場合、発射する
	if m_f_is_screen_touch:
		var scn_bullet = m_scn_bullet.instantiate()
		scn_bullet.transform.origin = transform.origin
		add_sibling(scn_bullet)

Bulletシーンファイルをスクリプトのメンバ変数に設定する

  • @export var m_scn_bullet : PackedScene
     m_scn_bulletを@exportしてインスペクタに表示します。PackedSceneクラス型の宣言をします。

Godot3.5の時はこのように宣言していました。4.0で変更された点のひとつです。

export(PackedScene) var m_scn_bullet

 シーン内のPlayerをクリック選択してからインスペクタを見ると、M Scn Bulletという名前で表示されています。まだ何も登録していないので、<empty>です。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 14_37_47.png
 ファイルシステムのbullet.tscnを<empty>にドラッグアンドドロップして登録しましょう。
スクリーンショット (11).png
 登録したシーンのイメージが表示されます。
キャプチャ 2023_02_28 14_38_31.png

スマホタッチ中フラグの追加

 _inputを修正してスマホタッチ中のフラグを設定するようにしました。

  • var m_f_is_screen_touch = false
    スマホタッチ状態を保持できるようにメンバ変数を追加します。

  • m_f_is_screen_touch = event.is_pressed() or ( event is InputEventScreenDrag )
     _input()の中で、eventがInputEventScreenDrag且つevent.is_pressed()がtrueの時、もしくはeventがInputEventScreenDragのときに、タッチ中と判断しています。

timeoutシグナル受信したときに発射処理の実装

 timeoutをしたときに、スマホタッチしている場合は、Bulletを発射する処理を実装しました。

  • func _on_timer_fire_timeout():
    Timer_fireのtimeoutシグナルを接続したときに追加したメソッドです。

  • if m_f_is_screen_touch:
    スマホタッチ中のみ以下の処理を実行

  • var scn_bullet = m_scn_bullet.instantiate()
    新規にBulletシーンのインスタンスを生成します。
    m_scn_bulletには先ほどドラッグアンドドロップでbullet.tscnをセットされています。

  • scn_bullet.transform.origin = transform.origin
    Playerと同じ位置を設定します。

  • add_sibling(scn_bullet)
    Playerと同じ階層にBulletシーンインスタンスを追加します。  
    シーンを追加する場合、add_childとadd_siblingがあります。
    add_childではPlayerの子ノードとして追加されます。その場合Playerが移動するとBulletもついてきて期待する動作とは異なる結果になります。
    add_siblingとするとPlayerと同じ階層に追加されます。

 実行しましょう。

S08_shoot_001 (DEBUG) 2023_02_28 21_50_00.png

 シーンのリモートを確認すると、add_sibling()で追加したBulletはPlayerと同じ階層に追加されていることがわかります。
「【Godot 4.0】スマホ3Dゲームを作るための勉強 その8 発射したい」を編集 - Qiita - Google Chrome 2023_02_28 21_50_46.png

 問題が2つあります。ひとつはBulletが消えないことです。
 2つ目の問題は、BulletやPlayerの球がおかしな方向(Z軸方向)に動くことです。これは衝突判定をしているPlayerの中にBulletを生成したので、衝突による速度計算がおかしくなっているためだと思います。

画面外に出た場合Bulletを消すようにする

 Bulletが画面外にでたところで消すようにしましょう。
 bullet.tscnを開いて、Bulletを右クリックして「子ノードを追加」からVisibleOnScreenNotifier3Dを追加します。
 現在のカメラから見える範囲に入った場合、出た場合にsignalを発行することができます。
 (_) bullet.tscn - S08_shoot_001 - Godot Engine 2023_02_28 21_59_53.png
 ノード/シグナルを表示して、screen_exitedを右クリックして、Bulletのスクリプトに接続します。 
 スクリーンショット (15).png
 受信側メソッドを確認して接続ボタンを押下します。
スクリーンショット (16).png

 _on_visible_on_screen_notifier_3d_screen_exitedメソッドが追加されました。
 下記のように修正しましょう。

extends CharacterBody3D

@export var m_v_dir = Vector3(0.0, 0.0, -1.0)
@export var m_d_speed_mps = 2.0

func _physics_process(delta):
	velocity = m_v_dir.normalized() * m_d_speed_mps
	move_and_slide()

func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()
  • queue_free()
    自身のノードインスタンスを開放(削除)します。

 実行しましょう。
 リモートで確認するとBulletが画面から消えると、シーンからも消えるのが確認できます

Bulletの発射位置を指定する

 Collision Layer/Maskを設定して衝突しないようにすればよいのですが、登場位置を正確に設定する必要もあるので、今回はBulletの発射位置を指定して対処します。(Collision Layer/Maskは次回制御します)
 global.tscnを開いて、Playerを右クリックして「子ノードを追加」からMarker3Dを追加します。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 22_26_34.png
 Node3D/Transform/Zに-0.7mを設定します。
 球の半径が0.5m、Bulletの半径が0.1mなのでその合計より前方にしました。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 22_26_36.png
Marker3Dはこのように球の前方に表示されます。
global.tscn - S08_shoot_001 - Godot Engine 2023_02_28 22_28_58.png

Godot3.5のとき、Marker3DはPosition3Dという名称でした。

 Marker3Dの位置を利用してBulletの発射位置を設定します。

extends CharacterBody3D

@export var m_speed_mps = 5.0
@export var m_ray_length_m = 1000		# [m]十分な長さにする
@export var m_scn_bullet : PackedScene

var m_touch_pos = Vector2.ZERO
var m_d_move_to_pos_x_m = 0
var m_f_is_screen_touch = false

func _input(event):
	if ( event is InputEventScreenDrag ) or ( event is InputEventScreenTouch ):
		m_touch_pos = event.position
		# スマホタッチ中フラグ
		m_f_is_screen_touch = event.is_pressed() or ( event is InputEventScreenDrag )

func _physics_process(delta):
	var camera = get_viewport().get_camera_3d()

	# カメラを利用して3D空間のカメラ位置とタッチしたピクセルに対応する方向の1000m先の位置を計算する
	var from3d = camera.project_ray_origin(m_touch_pos)
	var to3d = from3d + camera.project_ray_normal(m_touch_pos) * m_ray_length_m

	# 3D ray physics queryの作成
	var query = PhysicsRayQueryParameters3D.create(from3d, to3d)
	query.collide_with_areas = true		# Area3Dを検知できるようにする
	
	# Godotの3Dの物理とコリジョンを保存しているspaceという情報を使用してオブジェクト検出
	var space_state = get_world_3d().direct_space_state
	var result = space_state.intersect_ray(query)
	if result:
		m_d_move_to_pos_x_m = result.position.x

	# 移動制御は毎周期実行する
	var d_diff_x_m = (m_d_move_to_pos_x_m - transform.origin.x);
	var d_speed_mps = m_speed_mps if d_diff_x_m>0 else -m_speed_mps
	var d_move_diff_x_m = d_speed_mps * delta
	if absf(d_move_diff_x_m) > absf(d_diff_x_m):
		# 移動すると目標位置を超えるため、目標位置を設定する
		transform.origin.x = m_d_move_to_pos_x_m
	else:
		move_and_collide(Vector3(d_move_diff_x_m, 0, 0))

func _on_timer_fire_timeout():
	# スマホタッチ中の場合、発射する
	if m_f_is_screen_touch:
		var scn_bullet = m_scn_bullet.instantiate()
		scn_bullet.transform.origin = $Marker3D.global_transform.origin
		add_sibling(scn_bullet)

 func _on_timer_fire_timeout():の位置設定処理を変更します

  • scn_bullet.transform.origin = $Marker3D.global_transform.origin

 Marker3Dのglobal座標をBulletに設定します。
 子ノードは「\$+ノード名」で表すので、\$Marker3Dです。シーンからスクリプトエディター画面にドラッグアンドドロップすると相対パスを考慮してスクリプトに挿入されて便利です。
 またMarker3DはPlayerの子ノードですの、\$Marker3D.transform.originはPlayrのローカル座標系です。
 BulletはPlayerと同じ世界座標系で動作するためtransformではなくglobal_transformを使用しています。

 実行しましょう。
 球とBulletが押し合うことはなくなります。

以上

0
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
0
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?