Godot4の覚書です。
簡単な構成で、一通り確認することを目的としています。
構成
だいたいのゲームは以下のような構成になっていると思います。
スタート画面 | ゲーム画面 | ゲームオーバー画面 | 設定画面(ない場合も) |
。。。途中 |
ゲームの遷移状態は、大きく分けて以下の4種類に分類できます。
1. ゲームシーン
ゲーム全体を構成する大きな区切りの状態です。タイトル画面、ゲームプレイ画面、ゲームオーバー画面などがあります。
2. プレイヤーの状態
プレイヤーキャラクターの動作や状況を表す状態です。移動、攻撃、ジャンプ、しゃがみ、待機などがあります。
3. オブジェクトの状態
ゲーム内に出現するオブジェクトの動作や状況を表す状態です。表示、非表示、アクティブ、非アクティブ、破壊などがあります。
4. その他の状態
上記以外にも、ゲームによっては以下のような状態が存在します。
- アイテムの状態 (所持、使用、消費)
- イベントの状態 (発生、進行、完了)
- BGMの状態 (再生、停止、音量調整)
- セーブデータの状態 (ロード、セーブ)
これらの状態は、ゲームエンジンやプログラミング言語によって、さまざまな方法で管理されます。
遷移状態の例
以下は、ゲームの遷移状態の例です。
- プレイヤーが敵に攻撃をヒットさせると、敵の「ダメージ」状態になり、体力が減少します。
- プレイヤーがアイテムを獲得すると、アイテムの「所持」状態になり、インベントリに追加されます。
- プレイヤーがゲームクリアすると、「ゲームクリア」状態になり、エンディング画面が表示されます。
遷移状態を設計する際のポイント
ゲームの遷移状態を設計する際には、以下の点に注意する必要があります。
- プレイヤーにとってわかりやすい状態であること
- 必要な状態のみを定義すること
- 状態間の遷移がスムーズであること
- エラー状態を考慮すること
まとめ
ゲームの遷移状態は、ゲームを構成する重要な要素です。適切に設計することで、プレイヤーにとってわかりやすく、スムーズなゲームプレイを実現することができます。
ゲーム内の状態を管理するには、さまざまな方法があります。状況に応じて適切な方法を選択することが重要です。
代表的な方法
- ステートマシン
状態を列挙し、各状態間での遷移を矢印で表した有限ステートマシン (FSM) を用いる方法です。シンプルなゲームや、状態間の遷移が比較的単純な場合に適しています。
- フラグ
特定の状態を表すフラグ変数を用意し、その値を操作することで状態を管理する方法です。シンプルなゲームや、状態管理が比較的単純な場合に適しています。
- コンポーネント
各オブジェクトに状態を管理するコンポーネントをアタッチする方法です。オブジェクトごとに異なる状態を管理する必要がある場合や、複雑な状態遷移を表現する必要がある場合に適しています。
- エンティティ-コンポーネントシステム (ECS)
エンティティとコンポーネントを用いてゲームオブジェクトを表現し、状態を管理する方法です。大規模なゲームや、複雑な状態管理が必要な場合に適しています。
その他
上記以外にも、ゲームエンジンやフレームワークによっては、独自のステート管理機能を提供している場合があります。
状態管理のポイント
- ゲームの状態を明確に定義する
- 適切な方法で状態を管理する
- 状態間の遷移を明確にする
- エラー状態を考慮する
まとめ
ゲーム内の状態を管理するには、さまざまな方法があります。状況に応じて適切な方法を選択し、ゲームの状態を明確に定義し、適切な方法で管理することが重要です。
設定
画面サイズ
ゲーム画面のサイズを設定しておきます。
幅:800、 高さ:1380、 モード:viewport
スタート画面
スタート画面を作ります。
startが明滅するようにしています。
extends Control
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
var counti:int
func _process(delta):
counti +=1
$start.show()
if counti > 50:
$start.hide()
if counti > 100:
counti = 0
#print("ループ"+str(counti))
シーンの切り替え
シーン遷移
スタート画面からゲーム本編へ移動します。または設定画面へ移動する場合もあります。
本編や設定画面を作成したら、シーンを呼び出して移動します。
ほとんどの場合において、なんらかのキーを押すことで移動しますので、キー設定も行う必要があります。startするためのボタンは、キーボードなら何でもよいようにします。
以下のコードを追加します。キーボードのキーが押された場合、mianシーンに変えます。
#シーンの切り替え
func _input(event: InputEvent) -> void:
# キーボードのキーが押された場合の処理
if event is InputEventKey:
get_tree().change_scene_to_file("res://Scene/main.tscn")
終了画面
作りはスタート画面とほぼ同じです。
ゲームオーバーの条件を満たした場合に遷移する設定を、ゲームmainに設定します。
画面の作成と、キーによる操作の設定をします。
インプットマップでキーボードのqキーにQuitを設定します。
func _input(event):
if event.is_action_released("Quit"):
get_tree().quit() #ゲーム終了
elif event.is_action_released("ui_accept"):
get_tree().change_scene_to_file("res://Scene/start_game.tscn")
"ui_accept"は組み込みアクションになります。スペースキーやエンターキーが該当します。
ゲーム本編
ひとまず、簡単な条件を設定します。
・プレイヤーだけが登場
・5秒停止しているとゲームオーバー
画面
mainのシーンを作成してゲーム画面とします。
プレイヤー
プレイヤーのシーンを作成してmainシーンに配置します。
プロジェクト設定のインプットマップにキー操作を設定します。(動画だとすでにゲームパッドの設定が入っています)
このスクリプトは位置を指定して動作します。(下記に速度でのコードがあります)
func _process(delta):
if Input.is_action_pressed(&"move_right"):
position.x += 1
if Input.is_action_pressed(&"move_left"):
position.x -= 1
if Input.is_action_pressed(&"move_down"):
position.y += 1
if Input.is_action_pressed(&"move_up"):
position.y -= 1
velocity
チュートリアルにあるサンプルは速度変更して動作します。
正規化することで斜めの移動速度も上下左右と同じにしています。
これを追加して同時に動かしています。
var screen_size # Size of the game window.
@export var speed = 80
func _ready():
screen_size = get_viewport_rect().size
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
ルール
ここで、mainシーンにゲームルールのスクリプトを記述しておきます。
var lastPosi_X:float
var lastPosi_Y:float
var timeCount:float
func _process(delta):
#print(delta)
#位置を調べる
if $Player.position.x== lastPosi_X && $Player.position.y== lastPosi_Y:
timeCount += delta
else:
timeCount = 0
lastPosi_X = $Player.position.x
lastPosi_Y = $Player.position.y
#カウント5以上で画面遷移
if timeCount >= 5:
get_tree().change_scene_to_file("res://Scene/end_game.tscn")
プレイヤーの位置が変化ない場合、deltaをカウントして5以上になると、ゲームオーバー画面に変わります。
HUD
経過時間を表示するHead-Up Display(HUD)を設定します。
$HUD/time.text = str(ceil(timeCount * 10) / 10) #ceil関数:切り捨て整数表示
フォーマットで指定する場合
$HUD/time.text = "%7.1f" % timeCount #7は全文字数(.を含む)1は小数点以下数
Pause画面
ゲーム中に一旦停止する画面です。
ここでは別のシーンを呼び出さず、ノードを表示する方法で行います。
Pause画面で大事なこととして、Pauseした場合に再度操作するにあたって、
Process設定をAlwaysにしておくことです。設定しておかないと、操作を受け付けてくれません。
マップ
マップではなく障害物のような扱いですが、地形を描いてみます。
プレイヤーが接触するとゲームオーバーになります。
マップのシーンを作成して、mainシーンに配置します。
mainのスクリプトで、mapからのシグナルを受信して処理を行います。
if area.name == "Player":
# ゲームオーバーになる
get_tree().change_scene_to_file("res://Scene/end_game.tscn")
接触する場合
接触物として当たる場合には、CharacterBody2Dノードでプレイヤーを作成して、マップをStaticBody2Dノードで作成します。
var screen_size # Size of the game window.
@export var speed = 80
func _ready():
screen_size = get_viewport_rect().size
func _physics_process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
move_and_slide()
エネミー
エネミー(敵)というよりは、シーンの組付けに関することです。
構成
ゲームではそれぞれの構成が関係しあいます。また、デザインであったり個々でも深く作り込まれます。
いくつかのケースを確認します。
マップ追加
。。。途中
サンプルモデル
githubに置いています。
コリジョンの形状表示をonにする必要があります。
メニュー>デバッグ>コリジョン形状を表示 にしてください。
コメント