SRPGタクティカルゲーム開発チュートリアル

Part 2: ユニットの作成


Godot Engine バージョン4.4

このチュートリアルでは、Godot 4.4を使用してファイアーエムブレム風の SRPG(シミュレーションRPG)を作成する方法を学びます。キャラクターユニットの基礎構造を構築し、プレイヤー・敵ユニットの情報と動作を定義します。



デモプレイはこちらから!

アセットのダウンロードはこちらから!



このパートで学ぶこと

  • Resource を使ったユニットデータ(名前・HP・攻撃など)の定義
  • Node2D ベースのキャラクターユニットシーンの作成
  • スプライト表示、マウス選択、移動アニメーションの制御
  • 行動済み・未行動の管理と表示



始める前にはグローバルにシグナルを送受信する仕組み

始める前にグローバルにシグナルを送受信する仕組みを作成します。
signal_bus.gdというスクリプトを作成します。下記のコードを追加します。

extends Node

# ユニットが選択されたときに発信されるシグナル
signal unit_selected(unit)

# ユニットが移動したときに発信されるシグナル
signal unit_moved(is_move)

次に Project SettingsのGlobalsに先ほど作成したスクリプトをSignalBusという名前で追加します。




1. UnitDetail リソースの作成

まず、各ユニットの基本情報を定義するための Resource スクリプト unit_detail.gd を作成します。
extends Resource
class_name UnitDetail

# ユニットの種類を定義(プレイヤー or エネミー)
enum UnitType {
PLAYER,
ENEMY,
}

# ユニット名
@export var unit_name: String
# ユニットのタイプ(プレイヤー or エネミー)
@export var unit_type: UnitType
# 攻撃力
@export var unit_attack: int
# 防御力
@export var unit_defense: int
# 最大HP
@export var unit_max_health: int
# 現在のHP
@export var unit_health: int
# 移動範囲
@export var unit_move_range: int
# 攻撃範囲
@export var unit_attack_range: int
# クリティカル発生率
@export var unit_critical_rate: int
# クリティカル時のダメージ倍率
@export var unit_critical_damage: int
# スプライトアニメーション
@export var unit_sprite: SpriteFrames
この Resource を使って、プレイヤーや敵のステータスを個別に管理します。



2. キャラクターユニットシーンの構成

画像の通り、CharacterUnit シーンは以下のノード構成になっています:

CharacterUnit (Node2D)
├─ AnimatedSprite2D
├─ Area2D
└─ CollisionShape2D
AnimatedSprite2D:移動・待機などのアニメーション表示用
Area2D + CollisionShape2D:マウス入力(クリック・ホバー)の検出用



3. CharacterUnit スクリプトの内容

character_unit.gd では、ユニットの表示や状態管理を実装します。

@tool
extends Node2D
class_name CharacterUnit

# ユニットの詳細情報(名前・ステータス・スプライト等)
@export var unit_detail: UnitDetail:
set(value):
unit_detail = value
# スプライト情報があればAnimatedSprite2Dに反映
if value && value.unit_sprite && $AnimatedSprite2D:
$AnimatedSprite2D.frames = value.unit_sprite
# nullの場合はスプライトをクリア
if value == null:
$AnimatedSprite2D.frames = null
notify_property_list_changed()
@export var move_range = 3 # 移動可能範囲
@export var move_speed = 0.2 # 移動速度
@export var tile_size = Vector2i(16, 16) # タイルサイズ

@onready var animation: AnimatedSprite2D = $AnimatedSprite2D

var tween: Tween
var is_selected = false # 選択状態
var is_action_done = false # 行動済みフラグ
var previous_position: Vector2 # 直前の位置

func _ready():
# Area2Dのinput_eventシグナルに接続
$Area2D.input_event.connect(_on_input_event)
# エネミーユニットの場合はスプライトを左右反転
if unit_detail && unit_detail.unit_type == UnitDetail.UnitType.ENEMY:
animation.flip_h = true

# ユニット選択時の処理
func _on_input_event(viewport, event, shape_idx):
# 行動済みまたはHP0の場合は何もしない
if unit_detail.unit_health <= 0 or is_action_done:
return
# マウスボタン押下で選択状態を切り替え、シグナル発行
if event is InputEventMouseButton and event.pressed:
is_selected = !is_selected
if is_selected:
SignalBus.unit_selected.emit(self)

# 指定パスに沿ってユニットを移動
func move_path(path: Array[Vector2i]):
previous_position = position
# プレイヤーユニットの場合、移動開始シグナル発行
if unit_detail.unit_type == UnitDetail.UnitType.PLAYER:
SignalBus.unit_moved.emit(true)
for i in path.size():
# 既存のTweenが動作中なら停止
if tween && tween.is_running():
tween.kill()
tween = get_tree().create_tween()
var pos = path[i] * tile_size
pos = pos + (tile_size / 2)
pos = Vector2(pos.x, pos.y)
tween.tween_property(self, "position", pos, move_speed)
animation.play("move")
await tween.finished
animation.play("default")
# プレイヤーユニットの場合、移動終了シグナル発行
if unit_detail.unit_type == UnitDetail.UnitType.PLAYER:
SignalBus.unit_moved.emit(false)


# 行動状態をリセット
func reset_action():
is_action_done = false

# 行動済み状態にし、HPが残っていればスプライトを暗くする
func action_done():
is_action_done = true
if unit_detail.unit_health != 0:
$AnimatedSprite2D.self_modulate = Color(0.7, 0.7, 0.7, 1)

# スプライトの色を元に戻す
func clear_action():
$AnimatedSprite2D.self_modulate = Color(1, 1, 1, 1)



✅ スクリプトのポイント

- スプライトアニメーションの読み込み(@tool対応)
@export var unit_detail: UnitDetail:
set(value):
unit_detail = value
if value && value.unit_sprite && $AnimatedSprite2D:
$AnimatedSprite2D.frames = value.unit_sprite
if value == null:
$AnimatedSprite2D.frames = null
notify_property_list_changed()
- ユニットの選択処理(マウスクリックで選択状態を切り替え)
func _on_input_event(viewport, event, shape_idx):
if unit_detail.unit_health <= 0 or is_action_done:
return
if event is InputEventMouseButton and event.pressed:
is_selected = !is_selected
if is_selected:
SignalBus.unit_selected.emit(self)
- パスに沿った移動処理
func move_path(path: Array[Vector2i]):
...
for i in path.size():
if tween && tween.is_running():
tween.kill()
tween = get_tree().create_tween()
var pos = path[i] * tile_size + (tile_size / 2)
tween.tween_property(self, "position", pos, move_speed)
animation.play("move")
await tween.finished
animation.play("default")
- 行動済みフラグの管理とスプライトの暗転
func action_done():
is_action_done = true
if unit_detail.unit_health != 0:
$AnimatedSprite2D.self_modulate = Color(0.7, 0.7, 0.7, 1)

func clear_action():
$AnimatedSprite2D.self_modulate = Color(1, 1, 1, 1)



✅ 補足ヒント

@tool を使うことで、エディタ上で unit_detail を設定すると自動的にスプライトが反映されます。
SignalBus はグローバルにシグナルを送受信する仕組みで、他ユニットやUIとの連携に便利です。

次回は下記を実装します!
「ユニット配置と選択状態の管理」 マップにユニットを配置し、マウスで選んで移動させたり行動させたりするロジックを構築します!
最終更新日: 2025/06/06 03:02

コメント

    SRPGタクティカルゲーム開発チュートリアル Part 2: ユニットの作成 | GODOT TUTORIAL