Godotでプラットフォームゲーム基礎作成 (Godot 4.3 対応)その1
1. はじめに
Godotを使ってプラットフォームゲームを作成するための基本的な要素と手順を紹介します。
初心者を対象に、ゲーム開発の基本を学びながら、自分で簡単なプラットフォームゲームを作成できるようになります。
Youtubeでもこの記事の内容に沿ってゲームを作っているので動画を見ながら取り組んでみてください。
チャンネル登録していただけると嬉しいです。
環境
今回の動画では下記のバージョンを前提にゲーム作成を行います。
- Godot:4.3
2. プロジェクトの設定
それではまず初めにプロジェクトの設定をしましょう。
画面の設定
表示 > ウィンドウ から下記の通り設定します。
項目 | 値 |
---|---|
ピューポートの幅 | 1280 |
ピューポートの高さ | 720 |
ストレッチ | canvas_items |
テクスチャフィルタの設定
レンダリング > テクスチャ からデフォルトのテクスチャフィルタをNearest
にします。
ドット絵をメインで扱う場合はデフォルト設置だとボヤけてしまうのでこちらを設定します。
レイヤーの名前設定
次にレイヤーの名称を設定します。レイヤーに名前をつけることでどのコリジョン(当たり判定)の管理がしやすくなります。
Layer Names > 2D物理より設定してください。
項目 | 値 |
---|---|
Layer 1 | プラットフォーム |
Layer 2 | プレイヤーの体 |
Layer 3 | プレイヤーの攻撃範囲 |
Layer 4 | 敵の体 |
Layer 5 | 敵の攻撃範囲 |
入力キーの追加
基本的にはデフォルトのキーコンフィグを利用しますが、攻撃処理のキーの割り当てがないのと、ジョイパットにも対応できるように設定を追加します。
デフォルトのアクションに追加する場合は組み込みアクションを表示のトグルをONにします。
今回は新しくui_attackというアクションを追加し、Zキーを設定します。
またSwitchのコントローラーに対応するようにジョイパッドの設定を追加します。
3. ディレクトリ構成の説明
Godotプロジェクトのディレクトリ構成は、プロジェクトの整理と管理を容易にするために重要です。
以下は、一般的なディレクトリ構成の例です。
my_platformer_game/
├── assets/
│ ├── sprites/
│ │ ├── player.png
│ │ └── enemy.png
│ ├── tiles/
│ │ └── tileset.png
│ ├── sounds/
│ │ ├── jump.wav
│ │ └── bgm.ogg
│ └── fonts/
│ └── font.tres
├── scenes/
│ ├── main_stage.tscn
│ ├── player.tscn
│ └── enemy.tscn
├── scripts/
│ ├── player.gd
│ ├── enemy.gd
│ └── main.gd
├── project.godot
└── README.md
ディレクトリの詳細
- assets: ゲームで使用するすべてのアセット(画像、音声、フォントなど)を格納します。
- sprites: スプライト画像を格納します。
- tiles: タイルセット画像を格納します。
- sounds: 効果音やBGMを格納します。
- fonts: フォントファイルを格納します。
- scenes: ゲームのシーンファイルを格納します。各シーンは .tscn ファイルとして保存されます。
- scripts: ゲームのスクリプトファイルを格納します。各スクリプトは .gd ファイルとして保存されます。
- project.godot: Godotプロジェクトファイルです。プロジェクトの設定が保存されています。
- README.md: プロジェクトの説明や使用方法を記載したファイルです。
今回の最終的なディレクトリ構成
今回のチュートリアルを完了させると最終的には下記のディレクトリ構成になります。
my_platformer_game/
├── assets
│ └── sprites
│ ├── character
│ │ ├── enemy
│ │ │ └── enemy.png
│ │ └── player
│ │ ├── player_attack.png
│ │ ├── player_die.png
│ │ ├── player_hurt.png
│ │ ├── player_idle.png
│ │ ├── player_jump.png
│ │ └── player_walk.png
│ └── tiles
│ └── tileset.png
├── icon.svg
├── scenes
│ ├── character
│ │ ├── enemy.tscn
│ │ └── player.tscn
│ ├── hud
│ │ └── hud.tscn
│ └── stage
│ └── stage01.tscn
├── script
│ ├── character
│ │ ├── enemy
│ │ │ ├── enemy.gd
│ │ │ ├── enemy_hit_box.gd
│ │ │ └── enemy_hurt_box.gd
│ │ └── player
│ │ ├── player.gd
│ │ ├── player_hit_box.gd
│ │ └── player_hurt_box.gd
│ └── hud
│ └── health_bar.gd
└── project.godot
ディレクトリの作成
それでは先にディレクトリだけ作成してみましょう。
my_platformer_game/
├── assets
│ └── sprites
│ ├── character
│ │ ├── enemy
│ │ └── player
│ └── tiles
├── scenes
│ ├── character
│ ├── hud
│ └── stage
├── script
│ ├── character
│ │ ├── enemy
│ │ └── player
│ └── hud
└── project.godot
4. プレイヤーキャラクターの作成
プレイヤーのキャラクターを作成していきます。
素材のダウンロード
今回はフリーで利用ができる素材を使っていきます。下記の素材サイトよりダウンロードしてきましょう。このチュートリアルでは下記の3つの素材を利用しています。
- プレイヤー:https://free-game-assets.itch.io/free-tiny-hero-sprites-pixel-art
- タイルマップ:https://brullov.itch.io/oak-woods
- 敵キャラクター:https://ivess.itch.io/slime-animations-pack
素材をダウロードしましたら扱いやすいようにファイル名を変更します。
下記に変更したファイル名を記載します。
用途 | 変更前 | 変更後 |
---|---|---|
プレイヤー | Pink_Monster_Attack1_4.png | player_attack.png |
プレイヤー | Pink_Monster_Death_8.png | player_die.png |
プレイヤー | Pink_Monster_Hurt_4.png | player_hurt.png |
プレイヤー | Pink_Monster_Idle_4.png | player_idle.png |
プレイヤー | Pink_Monster_Jump_8.png | player_jump.png |
プレイヤー | Pink_Monster_Walk_6.png | player_walk.png |
タイルマップ | oak_woods_tileset.png | tileset.png |
敵キャラクター | slime_frames_all.png | enemy.png |
ファイル名が変更できたら前項のディレクトリ構成になるように画像をインポートしましょう。
プレイヤーの作成
画像ファイルの準備ができたら早速プレイヤーキャラクターを作成します。
新規にシーンを作成し、ルートノードにCharacterBody2Dノードを作成し、
子ノードにAnimatedSprite2DノードとCollisionShape2Dノードを追加しましょう。
CharacterBody2DノードはPlayerに名前を変更しておきましょう。
シーンパネルでは下記のようなツリー状態になるようにしましょう。
Player
├── AnimatedSprite2D
└── CollisionShape2D
5. アニメーションの設定
まだ画像ファイル(スプライトシート)を設定していないので、キャラクターが表示されていないのでキャラクターの画像を設定します。
Sprite Framesの設定
AnimatedSprite2Dを選択し、インスペクターパネルのAnimationのSprite Framesから新規SpriteFrames
を選択します。下部にアニメーションドックが表示されるので、アニメションを追加していきます。
アニメーション追加アイコンを押下して必要なアニメーションを追加していきます。
今回は7個のアニメーションを作成するので、アニメーションを追加して名前を図の通り設定しましょう。
次に各アニメーションに画像を割り当てていきます。
割り当てたいアニメーションの名前を選択したら、スプライトシートからフレームを追加するアイコンを押下します。
それではidleに画像を設定していきましょう。
画像選択のウィンドウが表示されるので、player_idle.pngを選択します。
Godotエディタが画像の分割を自動で判断してくれますが、今回は垂直方向にも4分割されているので、1分割に変更します。
分割されたら利用したい画像のフレームを再生したい順番にクリックしていきます。
選択し終わったら右下のフレームを追加ボタンを押します。
同じように他のアニメーションも追加していきましょう。
アニメーション | 画像ファイル | 選択フレーム |
---|---|---|
attack | player_attack.png | |
die | player_die.png | |
fall | player_jump.png | |
hurt | player_hurt.png | |
jump | player_jump.png | |
walk | player_walk.png |
アニメーションで設定したフレームはデフォルトで持続時間が1となっているので、攻撃とジャンプはメリハリがつくようにフレームに対して時間を設定します。
アニメーションに画像が設定できたのでCollisionShape2Dノードに新規RectangleShape2Dを設定して当たり判定の設定をしましょう。
次はプログラムでアニメーションの制御していきます。
Playerノードに新しいスクリプトをアタッチして、下記のプログラムを書きます。
extends CharacterBody2D
class_name Player
const SPEED = 100.0
const JUMP_VELOCITY = -400.0
@onready var animated_sprite_2d = $AnimatedSprite2D
enum PlayerState {IDLE, WALK, JUMP, FALL, ATTACK, HURT, DIE}
var _state: PlayerState = PlayerState.IDLE
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
calculate_from_input()
move_and_slide()
transition_state()
update_animation()
func calculate_from_input():
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
animated_sprite_2d.flip_h = velocity.x < 0
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
func transition_state():
if is_on_floor():
if velocity.x == 0:
_state = PlayerState.IDLE
else:
_state = PlayerState.WALK
else:
if velocity.y > 0:
_state = PlayerState.FALL
else:
_state = PlayerState.JUMP
func update_animation():
var animation:StringName
match _state:
PlayerState.IDLE:
animation = "idle"
PlayerState.WALK:
animation = "walk"
PlayerState.JUMP:
animation = "jump"
PlayerState.FALL:
animation = "fall"
if animated_sprite_2d.animation == animation:
return
animated_sprite_2d.play(animation)
6. タイルマップの設定
次にキャラクターが移動する地面を作っていきましょう。
今回はGodot4.3で追加されたTileMapLayer
を触っていきます。
まずは新しいシーンを作成し、ルートノードにNode2Dを追加し名前をMainStage
にします。次に子ノードにTileMapLayerノードを追加します。
TileMapLayerノードが追加できたら、インスペクターパネルからTile Setに新規TileSetを選び、下部のTileSetからプラスアイコンのアトラスを選択し、tileset.pngを選択します。
タイル画像が16pxで区切られるため、TileMapから配置したいタイルを選択して、配置していきます。
一旦プレイヤーシーンをステージシーンに追加して動作を確認して見ましょう。
カメラがプレイヤーに追従するように追加したプレイヤーシーンのノードの子ノードにCamera2Dを追加して、Zoomを3.7に設定したらゲームをプレイして見ましょう。
どうでしょうか。プレイヤーが地面をすり抜けてしまったと思います。
これはまだタイルにコリジョンの設定が出来ていないからです。
でも安心してください。TileMapLayerでは簡単にコリジョンの設定ができます。
インスペクターパネルのTile SetからPhysics Layersのアコーディオンを開き、要素を追加します。そうするとタイルに対して物理の項目が追加されます。
この項目からタイルにたいしてコリジョンの範囲を設定することができます。
タイルの範囲全てにコリジョン判定を設定したい場合は、ペイントの物理レイヤーを選択し、設定したいタイルをクリックすると簡単に設定できます。
もう一度ゲームをプレイすると今度はちゃんとプレイヤーが地面の上で止まると思います。
7. 攻撃処理の追加
次はプレイヤーに攻撃処理を追加しましょう。
Playerノードの子ノードにArea2Dノードを追加して、名前をHitBoxに変えておきます。
当たり判定が何に対して衝突の判定とするかインスペクターパネルで設定できるので、CollisionのLayerを設定しましょう。
今回はプレイヤーの攻撃範囲なので、Layerは3、Maskは4を設定します。
この値はプロジェクト設定で名前を設定したものになりますので、3はプレイヤーの攻撃範囲、4は敵の体を指していることがわかります。
設定項目
改めて設定した項目を記載します。これはこの当たり判定が、プレイヤーの攻撃範囲に属していて、敵の体の当たり判定に対して衝突判定が行われることを意味しています。
- Layer:3(プレイヤーの攻撃範囲)
- Mask:4(敵の体)
次に子ノードにCollisionShape2Dを追加して名前をHitBoxCollisionにしてください。
こちらが攻撃の当たり判定の範囲になりますので、キャラクターの前に配置しましょう。
また、こちらの当たり判定は攻撃モーションの時にだけ有効化したいので、最初は無効化しておきましょう。Disabled のチェックボックスをオンにすることで無効化出来ます。
HurtBoxの作成
あとで敵側の攻撃も作っていくので先にプレイヤー側のダメージを受ける当たり判定を追加しておきましょう。
HitBoxと同じようにPlayerノードの子ノードにArea2Dノードを追加して、名前をHurtBoxに変えておきます。
子ノードにCollisionShape2Dを追加して名前をHurtBoxCollisionにしてください。
こちらもCollisionのLayerを設定しましょう。
今回はプレイヤーのダメージを受ける範囲なので、Layerは2、Maskは5を設定します。
- Layer:2(プレイヤーの体)
- Mask:5(敵の攻撃範囲)
準備ができたので次はプログラムに修正を加えていきます。
extends CharacterBody2D
class_name Player
const SPEED = 100.0
const JUMP_VELOCITY = -400.0
@onready var animated_sprite_2d = $AnimatedSprite2D
@onready var hit_box_collision: CollisionShape2D = $HitBox/HitBoxCollision # 追加
enum PlayerState {IDLE, WALK, JUMP, FALL, ATTACK, HURT, DIE}
var _state: PlayerState
var _is_attacking: bool = false # 追加
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
calculate_from_input()
update_collision_shape() # 追加
move_and_slide()
transition_state()
update_animation()
## 追加 inputを受け付けるか判定
func can_input() -> bool:
if _is_attacking:
return false
return true
func calculate_from_input():
# 追加
if not can_input():
velocity.x = 0
return
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
animated_sprite_2d.flip_h = velocity.x < 0
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# 追加 キャラクターの向きに合わせて攻撃判定を逆にする
func update_collision_shape():
if animated_sprite_2d.flip_h:
hit_box_collision.position.x = -abs(hit_box_collision.position.x)
else:
hit_box_collision.position.x = abs(hit_box_collision.position.x)
func transition_state():
# 追加
if _is_attacking:
return
if is_on_floor():
# 追加
if Input.is_action_just_pressed("ui_attack"):
_state = PlayerState.ATTACK
_is_attacking = true
hit_box_collision.disabled = false
return
if velocity.x == 0:
_state = PlayerState.IDLE
else:
_state = PlayerState.WALK
else:
if velocity.y > 0:
_state = PlayerState.FALL
else:
_state = PlayerState.JUMP
func update_animation():
var animation:StringName
match _state:
PlayerState.IDLE:
animation = "idle"
PlayerState.WALK:
animation = "walk"
PlayerState.JUMP:
animation = "jump"
PlayerState.FALL:
animation = "fall"
# 追加
PlayerState.ATTACK:
animation = "attack"
if animated_sprite_2d.animation == animation:
return
animated_sprite_2d.play(animation)
# 追加
func interrupt_animation(animation: StringName) -> void:
animated_sprite_2d.play(animation)
# 追加
func attack_finished() -> void:
_is_attacking = false
_state = PlayerState.IDLE
hit_box_collision.disabled = true
interrupt_animation("idle")
# 追加
func _on_animation_finished() -> void:
match _state:
PlayerState.ATTACK:
attack_finished()
追加したプログラムでは攻撃アクションが行われたら、アニメーションが完了するまで他の動作を受け付けないように制御を行い、攻撃モーションが終わったら動作を受け付けるようにしています。
また歩きながら攻撃した際に横移動しないように制御も入れています。
攻撃モーションが終わったことはアニメーション終了のシグナルを設定することで検知するようにします。
animation_finishedのシグナルの受け側のメソッドに_on_animation_finished
を設定してください。
次は敵キャラクターの作成ですが、その前に敵にダメージを与えるプログラムを準備しておきましょう。
HitBoxに新しいスクリプトをアタッチし、下記のプログラムを書きましょう。
extends Area2D
class_name PlayerHitBox
var damage: int = 1
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
connect("area_entered", _on_area_enterd)
func _on_area_enterd(hurtbox: EnemyHurtBox) -> void:
if hurtbox == null:
return
hurtbox.hit(damage)
まだ敵キャラクターを作成していないので、EnemyHurtBox
がエラーになると思いますが一旦無視しておきましょう。
8. 敵の追加
敵キャラクターを作成していきましょう。
新しいシーンを作成し、プレイヤー同様にノードを設定していきましょう。
ルートノードにはCharacterBody2Dノードを作成し、
子ノードにAnimatedSprite2DノードとCollisionShape2Dノードを追加しましょう。
あとはArea2dノードを2追加してそれぞれにCollisionShape2Dノードを追加します。
各ノードの名前は下記の画像の通り設定しましょう。
アニメーションの設定
プレイヤー同様にアニメーションを設定していきます。
アニメーション | 画像ファイル | 選択フレーム |
---|---|---|
die | enemy.png | |
hurt | enemy.png | |
idle | enemy.png |
コリジョンの設定
コリジョンにはそれぞれ下記を設定します。
CollisionShape2D
HurtBox
- Layer:4(敵の体)
- Mask:3(プレイヤーの攻撃範囲)
HitBox
- Layer:5(敵の攻撃範囲)
- Mask:2(プレイヤーの体)
敵のプログラム作成
敵キャラクターの準備が整ったのでプログラムを作成していきます。
まずEnemyノードに新しくスクリプトをアタッチします。
extends CharacterBody2D
class_name Enemy
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var hurt_box_collision: CollisionShape2D = $HurtBox/HurtBoxCollision
@onready var hit_box_collision: CollisionShape2D = $HitBox/HitBoxCollision
enum EnemyState {IDLE, HURT, DIE}
var _state: EnemyState
var _is_dying = false
var _is_control_locked = false
var health = 3
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
if _is_dying or _is_control_locked:
return
move_and_slide()
update_animation()
func update_animation() -> void:
var animation:StringName
match _state:
EnemyState.IDLE:
animation = "idle"
if animated_sprite_2d.animation == animation:
return
animated_sprite_2d.play(animation)
func interrupt_animation(animation: StringName) -> void:
animated_sprite_2d.play(animation)
func hurt_finished() -> void:
_is_control_locked = false
_state = EnemyState.IDLE
interrupt_animation("idle")
func die() -> void:
interrupt_animation("die")
hurt_box_collision.call_deferred("set", "disabled", true)
hit_box_collision.call_deferred("set", "disabled", true)
_is_dying = true
func _on_take_damage(damage) -> void:
health -= damage
print("ライフ:%d" % health)
if health > 0:
_is_control_locked = true
_state = EnemyState.HURT
interrupt_animation("hurt")
return
die()
func _on_animation_finished() -> void:
match _state:
EnemyState.HURT:
hurt_finished()
if _is_control_locked:
_is_control_locked = false
if _is_dying:
set_physics_process(false)
queue_free()
HurtBoxにも新しくスクリプトをアタッチします。
extends Area2D
class_name EnemyHurtBox
signal hurt(damage: int)
func hit(damage: int):
print("ダメージ:%d" % damage)
hurt.emit(damage)
シグナルの設定をしていきます。
敵キャラクターがお亡くなりになった時に消滅のアニメーションの後にノード削除などの処理を制御するためプレイヤー同様にanimation_finishedのシグナルを設定します。
受け側のメソッドに_on_animation_finished
を設定してください。
次にプレイヤーの攻撃を受けてダメージを反映させるためのシグナルを設定します。
カスタムシグナルのhurtに_on_take_damage
を設定します。
プレイヤーからのシグナルの流れを説明すると下記の図のように処理が実行されます。
敵の攻撃とプレイヤーへのダメージ
同様に敵からのダメージも実装していきましょう。HitBoxにも新しくスクリプトをアタッチします。
extends Area2D
class_name EnemyHitBox
@onready var hit_box_collision: CollisionShape2D = $HitBoxCollision
var damage: int = 1
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
connect("area_entered", _on_area_enterd)
func _on_area_enterd(hurtbox: PlayerHurtBox) -> void:
if hurtbox == null:
return
hurtbox.hit(damage)
プレイヤーシーンに戻り、プレイヤーのHurtBoxに新しくスクリプトをアタッチしましょう。
extends Area2D
class_name PlayerHurtBox
signal hurt(damage: int)
func hit(damage: int):
print("ダメージ:%d" % damage)
hurt.emit(damage)
こちらもカスタムシグナルのhurtに_on_take_damage
を設定します。
ダメージを受けている時にキャラクターが攻撃できないようにplayer.gdも修正していきます。
extends CharacterBody2D
class_name Player
const MAX_HEALTH = 3 # 追加
const SPEED = 100.0
const JUMP_VELOCITY = -400.0
signal change_health # 追加
@onready var animated_sprite_2d = $AnimatedSprite2D
@onready var hit_box_collision: CollisionShape2D = $HitBox/HitBoxCollision
enum PlayerState {IDLE, WALK, JUMP, FALL, ATTACK, HURT, DIE}
var _state: PlayerState = PlayerState.IDLE
var _is_attacking: bool = false
var _is_dying: bool = false # 追加
var _is_control_locked: bool = false # 追加
var health = MAX_HEALTH # 追加
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# 追加
if _is_dying or _is_control_locked:
return
calculate_from_input()
update_collision_shape()
move_and_slide()
transition_state()
update_animation()
## inputを受け付けるか判定
func can_input() -> bool:
if _is_attacking:
return false
return true
func calculate_from_input():
if not can_input():
velocity.x = 0
return
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
animated_sprite_2d.flip_h = velocity.x < 0
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
func update_collision_shape():
if animated_sprite_2d.flip_h:
hit_box_collision.position.x = -abs(hit_box_collision.position.x)
else:
hit_box_collision.position.x = abs(hit_box_collision.position.x)
func transition_state():
if _is_attacking:
return
if is_on_floor():
if Input.is_action_just_pressed("ui_attack"):
_state = PlayerState.ATTACK
_is_attacking = true
hit_box_collision.disabled = false
return
if velocity.x == 0:
_state = PlayerState.IDLE
else:
_state = PlayerState.WALK
else:
if velocity.y > 0:
_state = PlayerState.FALL
else:
_state = PlayerState.JUMP
func update_animation():
var animation:StringName
match _state:
PlayerState.IDLE:
animation = "idle"
PlayerState.WALK:
animation = "walk"
PlayerState.JUMP:
animation = "jump"
PlayerState.FALL:
animation = "fall"
PlayerState.ATTACK:
animation = "attack"
if animated_sprite_2d.animation == animation:
return
animated_sprite_2d.play(animation)
func interrupt_animation(animation: StringName) -> void:
animated_sprite_2d.play(animation)
func attack_finished() -> void:
_is_attacking = false
_state = PlayerState.IDLE
hit_box_collision.disabled = true
interrupt_animation("idle")
# 追加
func hurt_finished() -> void:
_is_control_locked = false
_state = PlayerState.IDLE
interrupt_animation("idle")
# 追加
func _on_take_damage(damage) -> void:
health -= damage
change_health.emit()
print("ライフ:%d" % health)
if health > 0:
_is_control_locked = true
_state = PlayerState.HURT
interrupt_animation("hurt")
return
die()
# 追加
func die() -> void:
interrupt_animation("die")
_state = PlayerState.DIE
_is_dying = true
func _on_animation_finished() -> void:
match _state:
PlayerState.ATTACK:
attack_finished()
PlayerState.HURT:
hurt_finished()
# 追加
if _is_dying:
set_physics_process(false)
queue_free()
9. 体力ゲージの作成
プレイヤーのダメージ処理が出来たので、今度はユーザに見えるように体力ゲージを用意して
体力に連動して反映されるようにしてみましょう。
新しくシーンを作成してルートノードにCanvasLayerを追加し、子ノードにProgressBarを追加し、名前をHealthBarにします。デフォルトではパーセント表示されるので、こちらを非表示にしましょう。
インスペクターパネルのShowPercentageのチェックを外すと非表示にできます。
次に体力がある場合は緑ゲージ、体力を失っているゲージは赤色に設定していきます。
Theme Overridesのスタイルから設定ができます。
Backgroud | Fill |
---|---|
準備が整ったのでプログラムを書いていきます。
HealthBarに新規スクリプトをアタッチして下記プログラムを書いてみましょう。
extends ProgressBar
@export var player: Player
func _ready() -> void:
player = get_node("../../Player")
player.change_health.connect(update)
update()
func update():
value = player.health * 100 / player.MAX_HEALTH
プログラムが書き終わったらHUDシーンをステージシーンに追加してみましょう。
プレイヤーがダメージを受けると体力ゲージに反映されることがわかると思います。
10. まとめ
今回の記事ではGodotを使ってプラットフォームゲームを作成するための基本的な要素と手順を説明しました。この記事を参考にして、ぜひ自分だけのプラットフォームゲームを作成してみてください。
記事内では冗長的になるため細かい手順を省略しているところもありますが、その辺りは動画の方で確認いただければと思います。
さらに詳しい説明や具体例が必要な場合は、遠慮なくお知らせください!
Discussion