「Godot」0からゲーム作ろう! (実践2: 信号 & 衝突判定 ー2Dゲーム基礎ー)

記事3へようこそ! Coding課のAugustです.


本シリーズは, 完全に0から, あらゆる2Dゲームにも使われるエンジン機能や, ゲーム制作に欠かせない

  • キャラ操作
  • 信号(Signal, ~=UntiyのEvent System)
  • Tweenの作り方
  • User Interface

などを
Godotエンジンを使って解説するものです.

Unity達人でも, Godotエンジンでの制作流れをみて, 感じられるものがいるかもしれません.


本記事は,

  • 信号(Signal)
  • 信号を使う衝突判定

という順で進みます.


なお, もし 「このゲーム・この機能, どうやって作れる?」に対して興味を持つ方は, (複雑過ぎない機能かつ現段階2D限定)なら, Discordでメッセージくれたら次回の記事で作りたいと思います.


では早速始めましょう.

信号(Signal)とは?

Godotにおける「信号」は, 「イベントが発生したことを, 誰かが聞いていようといまいと一方的に伝える仕組み」である.

まずはブログ記事の例で考えてみよう:


僕が記事を書くとき,
「誰が読むか?」は気にしない.
だが, 新記事は投稿される.

signal 記事_published(タイトル: String)
記事.emit("0からゲームを作ろう!")


一方で, RiG++のTwitterアカウントがこのイベントに登録していれば, 自動でツイートされる:

記事.connect(on_new記事_published)
func on_new記事_published(タイトル: String):
tweet(タイトル)


つまり, 発信(emit)は一方通行.
受信側がいるかどうかは関係ない.



うん, ちょっと分かりにくいかもしれない.

次に交通信号のアナロジーで考えよう:


交通信号機は, 普通センサーを持たない.
誰かが待っていようといまいと, 時間が来れば青になる.

signal light_changed(state: String)
信号機.emit("green")

歩行者が信号機に登録されていれば, 青になったとき行動を起こす:

信号機.connect(_on_light_changed)

func _on_light_changed(state: String):
if state == "green":
歩行者.cross()


重要なのは:

信号機は誰が見てるかに興味がない.
ただ状態を伝えるだけ.


これは, decoupling(疎結合)の本質である.

今回の記事は触らないが, 興味あったらSingletonという「信号の中継局」について調べてもよい.

では実際に実装してみよう!

注: 以下は前回の記事(キャラ操作)の内容に繋いで制作を行っている.

衝突範囲を定義する:


てはじめに, 前回作ったPlayer SceneのSprite2Dは, 少し大きいので, Scale を0.5倍にした.
次に, PlayerをNode2Dから, CharacterBody2Dに変換して, CollisionShape2Dという子Nodeを追加して, 形をカプセル(Capsule)に設定しよう:

敵を作り, 範囲を定義する:


次に, 修正前のPlayerのように, Node2D型のSceneを作り, Enemyという名前をつけ, Sprite2Dを追加して素材を貼り付け, 更に Area2Dと対応するCollisionShape2Dを追加して, 今回は120*120の四角に設定しよう:

Layer & Mask


さて, Area2Dのノードをクリックして, 右のインスペクターを見て, Collisionという欄を展開すると, Layer と Maskの二行が見える:


今は, Layerの1を右クリックで名称を変えて, 「player」にする.

EnemyのLayerの1を消そう:

同様に, プレイヤーのCharacterBody2Dから, 今回はMaskの1を消す.

簡単に説明すると: Layerは, 「存在する空間」を意味し, Maskは 「探索する空間」を意味する. なお, 同時に複数空間に存在・探索してもいいし, 存在しない・探索しないのも可能である.

例えば, 敵の飛び道具のダメージ判定は, 大まか二種類ある.
1. Layerを「敵飛び道具」に設定して, Maskは何も設定しないことにし, playerは, maskでは「敵飛び道具」をOnにするやり方と,
2. Maskを「Player」に設定し, Playerは「Player」Layerに存在する
方法1は, 被弾判定のスクリプトはPlayerにあり, 方法2は, 被弾判定は飛び道具自身にある.

正直, どっちのやり方も大差ないように感じるが, 一般的に, 被弾判定はPlayer側に書く方が見やすいとも思われる.

作業に戻る


さて, Area2Dのノードは, すでにかなり信号がついている. 今シリーズ我々が使うのは, 下の6種類である

area_entered, area_exitedは, 英語の意味通り, 「Area」が衝突・離れるときに発信する. Bodyは同様, 「Body」,例えば, PlayerのCharacterBody2Dに衝突することで発信する. なお, 発信時, 「誰に」衝突したのかも分かる. (この機能の重要さは言葉では説明切れない)

@onready var area2D:Area2D = $Area2D

func _ready():
area2D.body_entered.connect(_on_body_entered_function_name_does_not_matter)

func _on_body_entered_function_name_does_not_matter(_other:Node2D):
pass

手始めに area2D部品にpointerを着ける. そこから
area2D.body_entered.connect(関数名)
で信号に繋ぐ. body_enteredは信号の名前.


ここで, 自作信号の使い方も説明しよう:
signal my_signal

はい, これだけで行ける. 好きな所でmy_signal.emitし, 適当な関数に繋げば動ける.

すべてをまとめる:

func _ready():
area2D.body_entered.connect(_on_body_entered)
area2D.body_exited.connect(_on_body_exited)


func _on_body_entered(_other:Node2D):
sprite.modulate = Color(1,0,0)


func _on_body_exited(_other:Node2D):
sprite.modulate = Color(1,1,1)

次回の記事のスニークピーク

さて, 今回は次回の内容, Tweenについてちっと見せたいと思う.

extends Node2D

@onready var area2D:Area2D = $Area2D
@onready var sprite:Sprite2D = $Sprite2D


func _ready():
	area2D.body_entered.connect(_on_body_entered)

func _on_body_entered(_other:Node2D):
	var tween = create_tween()
	tween.tween_method(tween_method,0.0,1.0,5)
	tween.parallel()
	tween.tween_property(self,"scale",Vector2(3.0,3.0),5)
	tween.parallel()
	tween.tween_property(sprite,"modulate",Color(1,0,0),5)
	tween.tween_callback(queue_free)
	
func tween_method(progression:float):
	if(progression>=0.5):
		_random_motion()
		
func _random_motion():
	var viewport_size:Vector2 = get_viewport_rect().size
	self.position = Vector2(randf_range(50,viewport_size.x-50),randf_range(50,viewport_size.y-50))
	await get_tree().create_timer(0.05).timeout


コメントを残す

CAPTCHA