マルチステージ

動画を見たり、作ってみたりしているわけだが、マルチステージのマリオのようなゲームはどのように作るのだろうと構成を考えてみる

■ゲームコントロール(メイン)
■ゲームイントロ(スタート画面)
■ゲームオーバー
■オプション(設定)
■ステージ1
■ステージ2
・
・
・

こんな感じだろうか。
シューティングやプラットフォーマーで制作したのはステージに該当する部分なので、他を入れてサンプルを作ってみたいと思う。

適当なシーンを作ってみる

シーンチェンジに関して
ボタンクリック時にchange_scene_to_fileで変更しようとしたのだが、動画検索をすると違う方法の方が良いらしい

func _on_stage_1_button_pressed() -> void:
	get_tree().change_scene_to_file("res://scenes/stage_1.tscn")
extends Node

var current_scene = null

func _ready() -> void:
	var root = get_tree().root
	current_scene = root.get_child(root.get_child_count() - 1)

func switch_scene(res_path):
	call_deferred("_deferred_switch_scene", res_path)

func _deferred_switch_scene(res_path):
	current_scene.free()
	var s = load(res_path)
	current_scene = s.instantiate()
	get_tree().root.add_child(current_scene)
	get_tree().current_scene = current_scene

プロジェクトのオートロードに「scene_switcher.gd」を登録して上のスクリプトを入れる。
個々のボタンからは以下

func _on_start_button_pressed() -> void:
	#get_tree().change_scene_to_file("res://scenes/game.tscn")
	SceneSwitcher.switch_scene("res://scenes/game.tscn")

Github:https://github.com/morino5555/GodotExample/tree/main/multiplestage

2Dプラットフォーマー

エネミー
Area2D、AnimationSprite、CollisionShape、RayCast(左右)

スクリプト
エネミーは壁のないところに配置するのでRayCastで地面があるか判定してから反転の動きです。
左右に壁がある場所に配置する場合は「!」を無しで

extends Area2D

const SPEED = 50.0
var direction = -1

@onready var game_manager: Node2D = %GameManager

@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var ray_cast_left: RayCast2D = $RayCastLeft
@onready var ray_cast_right: RayCast2D = $RayCastRight

func _physics_process(delta: float) -> void:
	if !ray_cast_left.is_colliding():
		direction = 1
		animated_sprite_2d.flip_h = true
	if !ray_cast_right.is_colliding():
		direction = -1
		animated_sprite_2d.flip_h = false
	if !ray_cast_left.is_colliding() and !ray_cast_right.is_colliding():
		direction = -1
		animated_sprite_2d.flip_h = false

	position.x += direction * SPEED * delta
	animated_sprite_2d.play("walk")


func _on_body_entered(body: Node2D) -> void:
	if body.is_in_group("player"):
		game_manager.take_damage()

スコアとハート(HP)はCanvasLayerに背景になるColorRectを入れてLabelとハートを横に並べるHBoxContainerにTextureRectを入れてハートを表示しています
Font:https://tinyworlds.itch.io/free-pixel-font-thaleah

game_manager.gd
エネミーにプレイヤーが触れたらエネミー側からtake_damageが呼ばれハートが減ります

extends Node2D

@onready var score_label: Label = %scoreLabel
@onready var heart_parent: HBoxContainer = %heart

var score = 0
var heart = 3
var heart_list : Array[TextureRect]

func _ready() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	score_label.text = "Score : " + str(score)
	for child in heart_parent.get_children():
		heart_list.append(child)

func _process(delta: float) -> void:
	if Input.is_action_just_pressed("ui_cancel"):
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func add_coin():
	score += 1
	score_label.text = "Score : " + str(score)
	
func take_damage():
	if heart > 1:
		heart -= 1
		update_heart()
	else:
		get_tree().reload_current_scene()

func update_heart():
	for i in range(heart_list.size()):
		heart_list[i].visible = i < heart
		prints("i:",i, " H:", heart)
		

2Dプラットフォーマー

プラットフォーマーと言えばコインなので、コインを設置する
Area2Dを親にAnimateSpriteとCollisionを設定

メイン画面にコインを配置

スクリプト
コインにプレイヤーが触れたら消す
最終的にはメインシーンのスコアにコインポイントを加算する

extends Area2D

func _on_body_entered(body: Node2D) -> void:
	print("Coin")
	queue_free()

2Dプラットフォーマー

Area2Dを親としてCollisionShape2Dを設定する

スクリプト
コリジョンに触ったら最初のシーンに戻る

extends Area2D

func _on_body_entered(body: Node2D) -> void:
	get_tree().reload_current_scene()
	

メイン画面にDeathZoneを設置

E 0:00:03:379   death_zone.gd:6 @ _on_body_entered(): Removing a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Remove with call_deferred() instead.
  <C++ ソース>     scene/2d/physics/collision_object_2d.cpp:98 @ _notification()
  <スタックトレース>    death_zone.gd:6 @ _on_body_entered()

エラーの原因が良く分からないが、戻りが速すぎるらしいのでTimerを設置

extends Area2D

@onready var timer: Timer = $Timer

func _on_body_entered(body: Node2D) -> void:
	#get_tree().reload_current_scene()
	timer.start()

func _on_timer_timeout() -> void:
	get_tree().reload_current_scene()
	

2Dプラットフォーマー

この部分を左右に動かしたい

AnimatableBody2Dを親にして、Sprite2Dを繋げ、CollisionShape2Dを設定する

メインシーンのタイルを消して制作したPlatformを配置する

左右に動かすアニメーションを追加する
AnimationPlayerを追加して「新規」でmoveという名前でアニメーションを作る

左端にプラットフォームを移動してTransformのPositionにある鍵マークをクリックすると0秒の位置に設定される

アニメーションを2秒の位置に移動して、プラットフォームを右端に移動させ、Positionの鍵マークをクリックする
この状態で再生すると左右に移動する。
左右に移動しない時は、アニメーションの右側のアニメーションループを「|<>|」設定する。スピードが速いようなら2秒を3秒にしてポジションを調整する
アニメーションはオート再生の「A」をクリックしておく

2Dプラットフォーマー

idle、jump、walkのアニメーションを追加

スクリプトを変更
左右に動いたときに反転表示させ、移動したらwalk、ジャンプならjumpを表示

extends CharacterBody2D


const SPEED = 200.0
const JUMP_VELOCITY = -500.0
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D


func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

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

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var direction := Input.get_axis("move_left", "move_right")
	if direction:
		velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)

	if velocity.y < 0:
		animated_sprite_2d.play("jump")
	else :
		if velocity.x < 0:
			animated_sprite_2d.flip_h = true
		elif velocity.x > 0:
			animated_sprite_2d.flip_h = false
		if velocity.x:
			animated_sprite_2d.play("walk")
		else :
			animated_sprite_2d.play("idle")

	move_and_slide()

シーンを実行してみる
移動で左右反転、ジャンプはOKだが、丸太の上に乗れない

丸太のタイルを調整する
丸太を選びpolygon_0_one_wayをオンにして、右の一覧の丸太をクリックすると、Vが表示される。
上部に乗れて下からジャンプできるようになる

カメラがプレイヤーを追従していないので、プレイヤーにカメラを設定

シーン実行すると中心にプレイヤーが居てカメラが追従してくる

2Dプラットフォーマー

プラットフォーマーが何かと言うと、スーパーマリオのような横スクロール型アクションゲームです。

Godot4.4.1
アセット:https://www.kenney.nl/assets/new-platformer-pack

初期設定

プロジェクト設定
画面サイズは標準のまま

キー設定
左右移動とジャンプ

プレイする地面を作る

TileMapLayerを追加。今回のタイルは64x64

背景と地面を別ノードで配置。背景の空は256x256タイル

プレイヤーを配置

CharacterBody2Dを親にAnimatedSprite2D、CollisionShape2Dを配置

スクリプトはBasicMovement

メインシーンに配置して実行するとプレイヤーが落下していきます。
これは地面に対してPhysics、物理的な設定をしていないからなので、次で設定します

TileMapLayerのGroundでPhysicsLayersを開き「要素を追加」

下部のTileSetからペイントを選び「物理レイヤー」を設定

地面に使っているものを選び物理範囲をしてしていく

丸太は上だけなので範囲を調整して設定する

シーンを再生すると