ぬじろぐ

好きなゲーム発表ぬこちゃん

素人がGodotでジャンプゲームを作る回


今回は苦労したので長いです。

流石にシンタックスハイライトはGDscriptには対応してませんよねー…
javascript指定にするとなんかいい感じに色分けされるみたいなので、これで行きます。

次何作ろうねえ
(っΦωΦ)(ΦωΦ˘)そうだねえ
好きな総菜発表ぬこちゃんつくろう
( Φ∀Φ)(ΦωΦ )もう少し動きがあるゲームを作りなさいよ

好きな総菜を発表しながら動くぬこちゃんを作ろう
( Φ∀Φ)(ΦωΦ )それならまあ…

Chromeに組み込まれてるこういうやつか?
まあ練習にはいいんじゃね…シンプルそうだし

Chromeで「chrome://dino」って入力すると遊べるやつ

恐竜ゲーム (クローム) - Wikipedia

環境

Godot v4.2.2
GDscript

必要機能

  • 一定の速さで勝手に走る
  • ジャンプで障害物をよける
  • 当たったらゲームオーバー
  • 生き延びたタイム=スコア

こんなもんか
好きな総菜は?
(ΦωΦ)(ΦωΦ)いらんだろ総菜

ぬこちゃんをジャンプさせる

Player=ぬこちゃんです さっき描きました

ぬこです よろしくおねがいします

流石にいきなりでは分からん。勉強する

【Godot】ラン&ジャンプゲームのチュートリアル | 2dgames.jp

参照先のコードをコピペでは何かが引っかかるらしく動かない… バージョンが違うのでノード名が変わってしまっている模様

試しにKinemeticBody2D→ CharactorBody2Dに読み替えて、CharactorBody2D作った時にデフォルトで入力されているスクリプトをそのまま使うともう既にアクションゲームっぽい動きする
普通のアクションゲーム作るならこれだけで良さそう
親切だなあ

今回は横移動はいらないのでジャンプだけに直す。
横移動の指定をしている部分をコメントアウト
ぬこちゃんは走ってる風に足踏みさせたいのでアニメーションノードを作る
【Godot】2Dスプライトアニメーションの基本的な使い方 #Godot - Qiita

そんで何もしていない時にアニメを再生するようにスクリプトを書く…なんかエラーになっちゃう?
どうもonreadyは頭に@をつけないといけなくなった模様
あっはい てかこれチュートリアルにあったっけ?なかったわ
ん、もしかして最初に引っかかったのも@つけたら解決したのか?まあいいや
今更だけどチュートリアルの2Dゲームってガチの初心者に作らすには辛くない?あんなもんなの?私も初心者だからわからない…

エラーは出なくなったけど動かない…?
2D sprite animation — Godot Engine (4.x)の日本語のドキュメント

あ、わかった。記法が違う
最初からここ見ればよかったんだな。


ソースコード【クリックで開く】

extends CharacterBody2D

const MOVE_SPEED = 200.0 # 移動速度
const SPEED = 200.0 #スピード 左右移動しないので使わない
const JUMP_VELOCITY = -600.0 #ジャンプ力 デフォルト-400
@onready var anim = $AnimatedSprite2D #アニメーションを設定

var _speed := MOVE_SPEED # 移動速度
	
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _ready():
	pass

func _physics_process(delta: float) -> void:
	# 移動速度を反映.
	velocity.x = _speed
	
	anim.play("running") #走るアニメを再生

	# Add the gravity.
	if not is_on_floor():
		#地面に足がついていない時
		velocity.y += gravity * delta
		anim.play("jump") #ジャンプアニメを再生

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	#左右移動は不要なので無効化
	#var direction = Input.get_axis("ui_left", "ui_right")
	#if direction:
	#	velocity.x = direction * SPEED	
	#else:
	#	velocity.x = move_toward(velocity.x, 0, SPEED)
	
	move_and_slide()

足踏みするようになりました。ジャンプのときのアニメも設定した。
デフォルトで入力されてるnot is_on_floor()はジャンプした時にやれじゃなくて、「地面についてないときこれをやれ」って指示なのね
言われてみればそうだけどぉ…
sprite2DとかCharactorBody2Dとか違いがよく分からんのだが、CharactorBody2Dならis_on_floor()が使えるってことらしい
今更だが、ノードの種類が色々あって違いが全然分かっていない。

かがむ動作はどうやってつければいいんだろう…
調べたけどアニメに合わせてコリジョンを変形させる方法がわからん。今回は諦め。

続けて障害物を設置する

障害物が右から出てきて左にスライドすることで疑似的に歩いているかのように見せかける(想定)
これなら公式のチュートリアルでやったことだけで実現できるはず…モブが出現する位置を固定すればいいのだ

???
なんして?

えーん わかんないよぉ
諦めてジャンプゲームチュートリアルの通りに延々と床を出してカメラを動かす方法にします

床を設置する

参考にしているラン&ジャンプゲームではプレイヤーの位置を参照して足場を追加したり不要になった足場を消したりしているが、ぬこちゃんゲームでは平らな地面を延々出し続けたいだけなので、もっとシンプルになる気がする…

地面ノードをインスタンス化…チュートリアルをコピペで…できない。なんかエラーになる。なんでや。
わからん。
悩んだ結果分からんのでここで一旦休む。

やり方を調べて、床を1マス出すことには成功する
このまま何個も並べればいいんだな…位置をずらして繰り返しで行けんか
→床がすっ飛んで行ってしまう なにゆえ?床のタイルを複数置きたいんだが?

ごにょごにょした結果なんかできた
インスタンス化する命令を書く位置があかんかった模様


スクリプト【クリックで開く】

extends Node
var obj_floor = preload("res://floor.tscn") # 床オブジェクト

const FLOOR_INTERVAL = 32 # 足場は 32px 単位で登場する
var WINDOW_W = 1024 # 画面サイズ
var y = 464 # 足場の高さは固定
var x = 0 #足場の初期位置 進行方向

@onready var _field_mgr = $FieldMgr # フィールド管理

func _process(_delta:float) -> void:
#地面の生成位置を設定
	for child in _field_mgr.get_children():
		var obj = obj_floor.instantiate() 
		
		# 通り過ぎた地面とモブを消す
		if child.position.x < _player.position.x - 140:
			child.queue_free()
			
			#地面を消すたびに右側に追加する
			if x < _player.position.x + WINDOW_W:
				x = x + FLOOR_INTERVAL
				obj.position.x = x + FLOOR_INTERVAL
				obj.position.y = y
				_field_mgr.add_child(obj)
					
		if x < WINDOW_W: #ウィンドウ幅より小さい時、地面を全部敷く ※初期表示用
			x = x + FLOOR_INTERVAL
			obj.position.x = x + FLOOR_INTERVAL
			obj.position.y = y
			_field_mgr.add_child(obj)


動画はテスト用なのでゆっくりですが、本番はもうちょっと早足になる予定

床のマップチップは32pxなので、32pxごとに表示
最初は歩く速度に合わせてタイマーで出してたんですが、床が一つ消えたら一つ出すでいいじゃんと思い書き換えました。シンプルな仕様

あとは繰り返しを使って端まで敷くようにします。

障害物(モブ)を出す

…の前に背景をつけました。適当に描いた
実装は簡単だった。
前景と背景の2枚で適当にスクロールするようになってます

モブの画像素材は借りてきました。
dotown.maeda-design-room.net

ちょっと加工して向きを進行方向にしたり、アニメーション用の差分を作ったり
地面生成時に一緒にランダムで生成する

( ΦωΦ) 8~ 8~ 8~
うーむ、生成には成功しているが重力が働いて地面に落ちてしまっている模様
空中に配置するにはどうすればいいんだろう

→モブをRigid Body 2Dで作ってたから自動で重力が働いてた
Area2Dに変更

モブにぶつかったらシグナルを受け取ってゲームオーバーの処理に行く設定にしたいのだが、どうもうまくいかない。
とりあえず別の部分を先に作る。

動画ではすでに設定してしまっているが、スタートボタン、テキスト、などを適当に追加

シグナル設定をする

んでシグナル…
同じノード内のシグナルはちゃんと動く。違うノードにシグナルを送る方法がよくわかってない。
永遠に分からないまま詰みになりそうだったので、諦めてジャンプゲームのチュートリアルをコピペする。
ぶつかった相手を消す設定なんですねこれは
プレイヤーが消えたらゲームオーバーと

そういえば…見えてないけど、childを消すたびに地面を伸ばすという事は、モブを消すときにも地面が追加されてしまうのでは?特にゲームプレイに支障はないのだが、なんか良くなさそうな気がしたので制限をかけることにした。
次に地面を生成する場所を計算しているので、ぬこちゃんの位置から1024px以下の場合にのみ地面を追加する。

いい感じにする

疲れてきた。
残りの作業…
ゲームオーバー時の処理、childで生成したモブを消す必要がある。いや、モブと地面を区別してないからどっちも消えないか…?
→公式チュートリアルでやってたわ。Mobノードでグループを作っておけば解決

#Mobグループを消す
get_tree().call_group("mobs", "queue_free")

ゲームオーバーになったあと再スタートさせる仕組みを作らないといけない。
→これはもうMain Nodeを再読み込みしてリセットする 最初にスタートボタン出すようにしてるからええやろ

#このシーンをリロードする
get_tree().reload_current_scene()

音楽もつけようとしたけど、鳴り出してすぐ勝手にフェードアウトしていってしまうのでまた今度。
なんでそんなことになっているのか分からない。ぬこちゃんが走ってるから?

好きな総菜を言うようにする

必要?この機能…
吹き出しを自作。
10秒経過ごとにラベルでランダムにテキストを出しているだけです。

# 総菜リスト
var souzai = [
	"きのこと めんつゆ\nつけたやつ",
	"ぶたにく れんこん",
	"なす\nにくみそいため",
	]
	
	func update_score(score):
	#スコア更新
	$ScoreLabel.text = str(score)
	
	#スコアが10の倍数のとき、ランダムな総菜を発表する
	if score %10 == 0:
		souzai.shuffle()
		$Hukidasi/Label.text = str(souzai[0])
		$Hukidasi.show()
		#3秒表示して消す
		await get_tree().create_timer(3).timeout 
		$Hukidasi.hide()

配列に入れたテキストをランダムに出す方法は覚えたから、ぬこちゃん占いは作れそうだな…

結局何をしてるのか

動作をまとめると、

  • ゲーム起動
  • processが勝手に動いているので地面が端まで生成される、ぬこちゃんは歩いているが蜂は出ない。スタートボタン押下待ち
  • ぬこちゃんの後ろ140px(画面の外)になったものは消える、消えたことをトリガーにして進行方向に新たな地面が生成される
  • スタートボタンが押されたタイミングでスコアのカウント開始、蜂とおやつの生成開始
  • スコアのカウント10ごとに好きな総菜をランダム表示する
  • 蜂に当たったらぬこちゃんとすべての蜂(グループ)を消す
  • ぬこちゃんが消えたことをトリガーとしてゲームを止める、ゲームオーバーの表示を待ってリロード=最初に戻る

完成品

こちらになります。し、しょうもな~~~!
このクソゲーを作るために2週間以上かけてしまった 才能ないかもしれん…
どの程度できればプログラムをまともに学んだことがない初心者合格なのか?わからん