【Steam】ストア審査で躓かない!ゲームリリースに必要な画像14枚のチェックリストとNGデザイン

先日、Boothとitch.ioとSteamにて、AnimSprite Pixelizerという2Dゲーム開発効率化ツールをリリースしました。CLIP Studioなどで作画したキャラ歩行アニメーション等を、共通のピクセルサイズで一括変換してスプライトシート書き出しができるというアプリです。

Boothとitch.ioは商品情報と画像を登録すれば、すぐに販売することができます。しかしSteamでゲームをリリースするためには、事前に商品情報とゲーム本体について、サポートチームの審査に合格する必要があります

AnimSprite Pixelizerの場合、シンプルなツールなので、アプリ本体の審査は一発で合格しましたが、商品画像について何度か訂正を求められました。今回の記事では、Steam販売に必要な14枚の画像およびデザインの注意点について説明したいと思います。

AnimSprite Pixelizer


  1. なぜこんなに多くの画像が必要なのか
  2. Steam登録に必要な画像チェックリスト
  3. デザイン面での重要な注意点
  4. まとめ:効率的な画像制作のコツ

なぜこんなに多くの画像が必要なのか

「ストアの画像を用意するのメンドクサ!!」

Steam販売経験のあるクリエイターさんは100%同じ言葉を口にするでしょう。それもそのはず。Steamの審査には最低14枚ほどの画像を用意する必要があるのです。

「なんでそんなに必要なの!?」と思う人はSteamをよく見てみてください。

  • 検索時のリストに表示される小さな画像(小型カプセル
  • フロントページ上部の特集画像(メインカプセル
  • 季節セールに表示される縦長の画像(垂直カプセル
  • ライブラリでゲームを選択したときに、プレイ時間などと一緒に表示される上部のヒーロー画像

これらすべてを準備しなければなりません。

しかも、これらの画像は厳格にサイズが決められています。例えば、ヘッダーカプセルは920×430px、小型カプセルは462×174pxです。完璧に同じサイズでなければなりません。SteamWorksでは、ドロップされた画像のサイズに基づいて各画像が設定されるため、少しでもサイズが違うと拒絶されます

Steam登録に必要な画像チェックリスト

今回は登録に必要な画像のチェックリストを作成しておきました。これを見つつ、Photoshopなど画像作成ツールでキャンバスを作成してデザインしましょう。

Steamのストア情報に必要なグラフィックアセットは、「ストアアセット」「スクリーンショットアセット」「ライブラリアセット」の3種類です。下記のチェックリストを参考に制作しましょう。

※記事執筆時点(2025年7月)の指定サイズです。

📋 ストアアセット

画像タイプ サイズ(px) 用途
ヘッダーカプセル 920×430 ストアページ上部、検索結果など
小型カプセル 462×174 検索結果のリスト表示
メインカプセル 1232×706 フロントページの特集枠
垂直カプセル 748×896 セール時の縦長表示

📸 スクリーンショットアセット

  • スクリーンショット:1920×1080(推奨)× 最低5枚

📚 ライブラリアセット

画像タイプ サイズ(px) 用途
ライブラリカプセル 600×900 ライブラリのグリッド表示
ライブラリヘッダー 920×430 ライブラリ詳細ページ上部
ライブラリヒーロー画像 3840×1240 ライブラリの大型バナー
ライブラリのロゴ 1280×720 ゲームのロゴ表示

👥 その他

  • コミュニティアイコン:184×184

デザイン面での重要な注意点

画像サイズと同じくデザインについても拒絶理由が存在するので注意しましょう。

⚠ 注意点A:「ロゴ以外の文字を乗せるな!」

実際にデザインをしていると、良かれと思ってテキストを追加したくなりますが、ロゴ以外の文字があると不合格になるので注意してください。下記のような小さな文字でもNGです。

マーケティング用コピーや引用など、ロゴ以外の文字情報を追加すると不合格になります。

⚠ 注意点B:「とにかく視認性を良く!」

上記の場合は「アプリの操作イメージを伝えたい」という思いからUIのスクショなどを組み込んでデザインしましたが不合格になりました。UI画像を削除して要素を大きく表示したら合格しました。

確かに客観的に見ても修正後のほうが視認性が良いですよね。作っているのがサムネイルであることを意識しながらデザインするのが大切です。

まとめ:効率的な画像制作のコツ

今回の注意点を参考にしてもらえれば、Steamのストア情報もスムーズに登録できるはずです。

Steamサポートチームはかなり具体的に修正内容を通知してくれるので助かりました。画像サイズについて説明する記事はいくつもあるけど、NGデザインについてはほぼほぼ情報がなかったので参考にしていただけると幸いです。

最初はヘッダーカプセル(920×430)あたりからデザインを始めて、完成したら別名保存をして、キャンバスサイズを変更して、要素の位置だけ整えて別名保存して、を繰り返せば、位置を変えるだけで複数のパターンを効率的に制作できます

Steamストア申請で面倒なのは、SteamWorksアカウント取得時の税務情報の登録と、ストア画像の作成だと思うので、まずはここを乗り越えてゲームをリリースしましょう!

また、Steam販売に際してローカライズしたほうがよい言語については、以前の記事で解説しているので、そちらも御覧ください。

それとAnimSprite Pixelizerもヨロシクネ。自分で絵も描く2Dゲーム開発者の人にオススメです。

AnimSprite Pixelizer

【Steam】個人ゲーム開発、どの言語にローカライズすべきか?【統計分析2025】

動画ファイルをピクセルアートに変換するイメージ画像

先日、Boothとitch.ioとSteamにて、AnimSprite Pixelizerという2Dゲーム開発効率化ツールをリリースしました。CLIP Studioなどで作画したキャラ歩行アニメーション等を、共通のピクセルサイズで一括変換してスプライトシート書き出しができるというアプリです。

AnimSprite Pixelizerのスクリーンショット

アプリが完成したあと、せっかくなので「多言語化対応」にも挑戦してみました。

今回のツールはElectronをベースに開発しているため、i18nの多言語化機能を用いて、日本語・英語のインターフェース切り替え機能を簡単に実装することができました。

次の課題は「どの言語にローカライズすべきか?」という点です。この記事は、特に個人・小規模でゲームを開発している方に向けて、どの言語を優先的に対応すべきかのヒントをまとめたものです。Steamが毎月公開している「Steam ハードウェア&ソフトウェア調査」を参考に、最新の動向を分析していきましょう。

AnimSprite Pixelizer
Steam ハードウェア&ソフトウェア調査


  1. Steam言語別利用者データの最新状況
  2. トップ10言語と市場動向
  3. ローカライズ戦略の優先順位
  4. 実際のローカライズで気をつけるポイント
  5. まとめ:多言語化を前提とした設計が重要に

Steam言語別利用者データの最新状況

Steam公式の「Hardware & Software Survey」から得られた最新データによると、現在のSteam利用者の言語分布は以下のようになっています。このデータは任意参加の調査ですが、Steamにおける最も信頼性の高い公式統計です。

Steamの2025年6月時点での言語別ユーザー数を示す円グラフ

Steamハードウェア&ソフトウェア 調査: June 2025

トップ10言語と市場動向

現在のSteam言語別利用者ランキングは以下のとおりです:

順位 言語 利用者割合 前回比 傾向
1位 英語 36.31% -1.93% 📉 減少
2位 簡体中国語 26.73% +2.61% 📈 大幅増加
3位 ロシア語 9.46% +0.43% 📈 増加
4位 スペイン語(スペイン) 4.34% -0.40% 📉 減少
5位 ポルトガル語(ブラジル) 3.87% -0.35% 📉 減少
6位 ドイツ語 2.86% -0.19% 📉 減少
7位 日本語 2.59% -0.10% 📉 微減
8位 フランス語 2.33% -0.13% 📉 減少
9位 ポーランド語 1.68% -0.09% 📉 微減
10位 韓国語 1.48% +0.27% 📈 増加

📈 成長している言語市場:

  • 簡体中国語:+2.61%(最大の成長率)。中国国内のPCゲーム市場の成熟が背景にあると考えられます。
  • ロシア語:+0.43%。インディーゲームとの親和性が高いとされる市場です。
  • 韓国語:+0.27%。熱心なゲームコミュニティを持つ市場です。
  • 繁体中国語:+0.07%
  • タイ語:+0.07%

📉 減少している言語市場:

  • 英語:-1.93%(最大の減少率)
  • スペイン語(スペイン):-0.40%
  • ポルトガル語(ブラジル):-0.35%
  • ドイツ語:-0.19%

日本語ユーザー比率はわずか2.59%。世界の広さを感じますね。

最も注目すべきは、英語と簡体中国語で全体の63%以上のユーザーをカバーできるという点です。ローカライズをするなら、この2言語への対応は必須級と言えるでしょう。

特に簡体中国語ユーザーは近年急増しており、「黒神話: 悟空」が大きな話題となった2024年8月の調査では、簡体中国語が英語を抜いて1位になるという逆転現象も発生しました。

参考記事:Steam言語別ユーザー数で「中国語」がトップに。中国発の高評価ACT『黒神話:悟空』が影響か (2024/09/03) – GameSpark

ローカライズ戦略の優先順位

これらのデータを踏まえて、限られた開発リソースでどの言語を優先すべきかを考えてみましょう。

Steam言語のローカライズ優先度をTier別に示した図

Tier別優先度の考え方

🥇 Tier 1(必須レベル)

  • 英語(36.31%)- 依然として最大のユーザーベース、グローバル標準言語
  • 簡体中国語(26.73%)- 急成長中、将来性が極めて高い

この2言語だけで既に63%以上のユーザーをカバーできます。小規模チームや個人開発者は、まずこの2つに集中するのが最も効率的です。

🥈 Tier 2(高優先)

  • ロシア語(9.46%)- 安定した成長傾向、大きなユーザーベース
  • スペイン語(スペイン)(4.34%)- 減少傾向ですが依然として大きな市場。また、ラテンアメリカの広大なスペイン語圏ユーザーにもリーチできる可能性があります。
  • ポルトガル語(ブラジル)(3.87%)- 南米の重要市場

🥉 Tier 3(中優先)

  • ドイツ語(2.86%)- 欧州の主要市場
  • 日本語(2.59%)- 高い購買力、品質を重視する市場
  • フランス語(2.33%)- 欧州・カナダ市場

🌟 Tier 4(新興市場・将来性)

  • 韓国語(1.48%)- 成長中(+0.27%)、ゲーム文化が発達
  • 繁体中国語(1.39%)- 台湾・香港市場
  • タイ語(0.88%)- 東南アジアの成長市場

段階的なローカライズ戦略

日本の個人開発者がローカライズを進める場合、下記のような段階的アプローチが現実的でしょう。

  1. Phase 1:日本語・英語対応(開発者の母語+グローバル標準)
  2. Phase 2:簡体中国語を追加(最大の成長市場を狙う)
  3. Phase 3:ロシア語、韓国語を追加(成長中の主要市場を押さえる)
  4. Phase 4:その他のTier 2-3言語(スペイン語、ポルトガル語など)を順次追加

実際のローカライズで気をつけるポイント

フォント対応

特に中国語、韓国語、日本語、タイ語などは、それぞれ専用のフォントが必要です。Webフォントやシステムフォントで対応できるか、あるいはフォントファイルを同梱する必要があるかを事前に確認しましょう。

文字数の変動

言語によってテキストの長さは大きく変わります。UIデザインはこれらの変動に対応できるよう、柔軟性を持たせることが重要です。

  • 短くなる傾向:日本語、中国語、韓国語(表意文字)
  • 長くなる傾向:ドイツ語、ロシア語(複合語や格変化)

ボタンやテキストボックスがはみ出さないよう、あらかじめ長めのテキストでテストしておくと安心です。

ドキュメントの整備(FAQ)

多言語化するということは、様々な言語でお問い合わせが届く可能性があるということです。すべての質問に個別対応するのは大変な労力です。あらかじめ想定される質問とその回答(Q&A)をドキュメントとしてまとめておき、対応言語に翻訳して公開しておきましょう。これにより、ユーザーは自己解決でき、開発者のサポート負担を大幅に軽減できます。

右から左へ記述する言語(RTL)

アラビア語やヘブライ語など、右から左へ記述する言語(RTL: Right-to-Left)に対応する場合、UI全体のレイアウト反転が必要になることもあります。Tier上位ではありませんが、将来的に対応する可能性があれば念頭に置いておくと良いでしょう。

まとめ:多言語化を前提とした設計が重要に

今回の調査で、理想的にはランキング上位の10言語に対応できれば、大半のSteamユーザーにリーチできる可能性が作れることがわかりました。

昨今はChatGPT、Claude、Geminiといった生成AIの活用により、非常に精度の高い翻訳が手軽に可能になっています。UIやシステムテキストであれば、AI翻訳だけでもかなりの品質が期待できるでしょう。もちろん、ストーリーやキャラクターの会話など、ゲームの没入感を左右する重要なテキストは、依然としてプロの翻訳家やネイティブスピーカーによるチェックが望ましいです。

重要なのは、開発の初期段階から多言語化を前提とした設計を心掛けることです。

テキストをプログラム内に直接書き込む(ハードコードする)のではなく、言語ごとのCSVファイルやJSONファイルから読み込む形式にしておけば、後から対応言語を増やすのが非常に容易になります。

フォントの準備や、文字数増減によるレイアウト崩れを防ぐUI設計など、事前に考慮すべき点はありますが、実装面のハードルはAIの進化によって着実に下がってきています。グローバル市場に挑戦するために、ぜひ多言語化を検討してみてください。

【Godot】これからGodotを始める2Dゲーム開発者が知っておくべき13の重要機能とオススメ教材

前回の記事ではGodotの基礎学習についてまとめましたが、今回はその続編として、より実践的な2Dアクションアドベンチャーゲームの開発に挑戦しました。Udemyの「Godot4: Build a 2D Action-Adventure Game」というコースを完走し、基礎編では触れられなかった多くの実用的な機能を学ぶことができました。

このコースは、基本的なプレイヤー操作から始まり、NPCとの対話、パズル要素、敵との戦闘システムまで、2Dゲーム開発に必要な要素が体系的に網羅されています。具体的な内容はコース本編で学んでもらうとして、ここではコースを通して学んだGodotの重要機能や概念を復習します。


  1. コース概要:2Dアクションアドベンチャーゲーム開発を通して学ぶ
  2. Godotの重要機能・概念の深堀りノート
  3. まとめ:実践開発から見えたGodotの真価

コース概要:2Dアクションアドベンチャーゲーム開発を通して学ぶ

今回受講した「Godot4: Build a 2D Action-Adventure Game」は、2Dアクションアドベンチャーゲームをゼロから作り上げることで、Godotの実践的な機能を体系的に学べる非常に優れた英語コースでした。具体的には以下のような内容を学びます。

  • プレイヤーの8方向移動とアニメーション
  • Terrains機能を使ったオートタイルによる環境構築
  • Y-Sortによるキャラクターとオブジェクトの重なり順制御
  • RigidBody2Dを使った物理パズル(ブロック押し)
  • NPCとのダイアログシステムとポーズ制御
  • Autoloadを使ったデータ永続化(宝箱の開封状態など)
  • 敵AI、ノックバックを含む戦闘システム
  • パーティクルエフェクトによる視覚効果

前回の記事で紹介した「Godot Engineで気軽に2Dゲームを作ろう」よりも難易度が少し上がりますが、簡易的な敵AI&戦闘システム&ダイアログシステムの構築方法が学べるのでとても有益です。

  • どうやってエリア移動するの?
  • どうやってモノを押すの?
  • 発見済みの宝箱の状態をどのように維持するの?
  • NPCとの会話をどうやって管理するの?
  • 複数のスイッチを押したら開く扉はどのように実装する?
  • 被ダメ時のホワイトフラッシュはどこで操作できる?

上記のような「ゲーム開発で本当に必要な知識」が詰まっており、Godotへの理解を一段階引き上げるのに役立ちます。

興味があれば、下記のUdemyサイトからコースを受講してみてください。

※Udemyは頻繁に90%OFFセールを開催しているので、お気に入りに登録して、セール時に購入することをおすすめします。英語に慣れていない場合でも、画面のコードを写経しつつ、ドキュメントを調べたりChatGPTやGeminiなどに質問をすれば、問題なく内容を理解できるはずです。難しかったら返金申請もできるので気軽にチャレンジしてみてください。

Godot4: Build a 2D Action-Adventure Game (Udemy)

Godotの重要機能・概念の深堀りノート

ここからは、コースの学習を通して特に重要だと感じたGodotの機能や概念について、自身の理解を深めるためにまとめたノートを共有します。

NodeのProcess Mode:ポーズ機能の賢い使い方

Godotでは、各ノードのProcess Modeを設定することで、ゲームがポーズした際の動作を細かく制御できます。これは非常に強力な機能です。

例えば「NPCと会話中は背景の敵やプレイヤーは止めたいが、ダイアログウィンドウの操作は続けたい」という場面で活躍します。

  • Pausable (停止可能): デフォルト。get_tree().paused = trueになると、_process_physics_processが停止します。プレイヤーや敵など、ゲーム世界のオブジェクトに適しています。
  • Always (常に実行): ポーズ状態を無視して常に動作します。会話中のNPCやUI、ポーズ中も流れ続けるBGMなどに使います。
  • When Paused (ポーズ中のみ実行): ポーズ中だけ動作します。「PAUSE」と表示する画面など、ポーズメニュー専用のUIに適しています。

この仕組みにより、特定のノードだけポーズの影響を受けないように設定できます。

実装例: ダイアログ表示中にゲームをポーズする

※会話中はシーンが停止(get_tree().paused = true)されて、会話終了と同時に解除される

# NPC.gd

# このNPCノードのProcess Modeをインスペクターで "Always" に設定しておく

func _process(delta):
    # (プレイヤーが近くにいて、かつインタラクトキーが押されたら)
    if Input.is_action_just_pressed("interact") and can_talk:
        if is_dialog_active():
            # ダイアログを閉じてポーズ解除
            close_dialog()
            get_tree().paused = false
        else:
            # ダイアログを開いてゲームをポーズ
            open_dialog()
            get_tree().paused = true

このように、NPC自身の動作は止めずにゲーム全体をポーズすることで、安全にダイアログの開閉処理を行えます。

CharacterBody2DのMotion Mode:トップダウンと横スクロールの使い分け

CharacterBody2Dには、キャラクターの物理挙動を決定するMotion Modeという重要な設定があります。ゲームのジャンルに合わせて正しく設定することが肝心です。

  • Grounded (接地): デフォルト。重力が自動で適用され、is_on_floor()などの床判定が機能します。ジャンプや落下があるプラットフォーマーや横スクロールアクションに最適です。
  • Floating (浮遊): 重力の影響を受けず、床の概念もありません。トップダウン(見下ろし型)のアクションゲームやシューティングゲームなど、キャラクターがXY平面を自由に動き回るゲームに適しています。

実装例: トップダウン型の移動

# Player.gd (Motion Modeを "Floating" に設定)
extends CharacterBody2D

@export var speed: float = 200.0

func _physics_process(delta):
    var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = direction * speed
    move_and_slide()

Motion Modeを正しく設定しないと、意図しない重力が発生したり、床判定がうまく機能しなかったりするため、プロジェクト開始時に必ず確認すべき項目です。

InputMap:キーバインドの効率的な管理

Godot InputMap

GodotのInput Map(プロジェクト設定 → Input Map)は、キーボードのキーやゲームパッドのボタンに「アクション名」を割り当てる機能です。これにより、コード内では具体的なキー名(”Aキー”など)ではなく、抽象的なアクション名(”move_left”など)で入力を扱えるようになります。

利点:

  • 複数のキーを同じアクションに割り当て可能(例: “move_left”に「A」と「左矢印キー」を登録)。
  • キーコンフィグ機能の実装が容易になる。
  • コードの可読性が向上する。

実装例: InputMapを使った入力取得

func _process(delta):
    # 単発の入力(ボタンが押された瞬間)
    if Input.is_action_just_pressed("interact"):
        open_chest()
    
    # 継続的な入力(ボタンが押され続けている間)
    if Input.is_action_pressed("dash"):
        speed = DASH_SPEED
    else:
        speed = NORMAL_SPEED
    
    # 2軸のアナログ的な入力(非常に便利)
    var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = direction * speed
    move_and_slide()

特にInput.get_vector()は、4つのアクションから正規化されたVector2を返してくれるため、トップダウンの移動処理を非常に簡潔に記述できます。

Godotの基本的な入力処理:just_pressed、pressed、releasedの使い分け

Inputについて (Godot公式ドキュメント)

InputMapでアクションを定義した後は、実際のゲーム内でその入力を適切に処理する必要があります。Godotでは入力の状態に応じて複数のメソッドが用意されており、用途に合わせて使い分けることが重要です。

入力状態の種類

  • is_action_just_pressed():ボタンが押された瞬間のみtrue
  • is_action_pressed():ボタンが押されている間ずっとtrue
  • is_action_just_released():ボタンが離された瞬間のみtrue

実用的な使い分け例

func _process(delta):
    # 1回限りのアクション(ジャンプ、攻撃、メニュー開閉など)
    if Input.is_action_just_pressed("jump"):
        if is_on_floor():
            velocity.y = JUMP_VELOCITY
    
    if Input.is_action_just_pressed("attack"):
        perform_attack()
    
    if Input.is_action_just_pressed("pause"):
        toggle_pause_menu()
    
    # 継続的なアクション(移動、ダッシュ、チャージなど)
    if Input.is_action_pressed("dash"):
        current_speed = dash_speed
    else:
        current_speed = normal_speed
    
    # チャージ系の処理
    if Input.is_action_pressed("charge"):
        charge_power += charge_rate * delta
        charge_power = min(charge_power, max_charge)
    
    # ボタンを離した瞬間の処理(チャージ攻撃の発動など)
    if Input.is_action_just_released("charge"):
        fire_charged_shot(charge_power)
        charge_power = 0.0

よくある間違いと対策

間違いの例:連続発動してしまう処理

# 悪い例:is_action_pressed()を使うと毎フレーム弾が発射される
func _process(delta):
    if Input.is_action_pressed("shoot"):  # 間違い
        fire_bullet()  # 毎フレーム実行されてしまう

# 良い例:is_action_just_pressed()で1回限りの発動
func _process(delta):
    if Input.is_action_just_pressed("shoot"):  # 正しい
        fire_bullet()  # ボタンを押した瞬間のみ実行

高度な入力処理:get_action_strength()とアナログ入力

ゲームパッドのアナログスティックやトリガーのような、0.0〜1.0の範囲で値が変化する入力にはget_action_strength()を使用します。

func _process(delta):
    # アナログ入力の取得(0.0〜1.0の値)
    var move_strength = Input.get_action_strength("move_forward")
    var brake_strength = Input.get_action_strength("brake")
    
    # 車の加速処理例
    if move_strength > 0.0:
        velocity += forward_direction * acceleration * move_strength * delta
    
    # ブレーキ処理例
    if brake_strength > 0.0:
        velocity = velocity.move_toward(Vector2.ZERO, brake_force * brake_strength * delta)

処理関数の使い分け指針

用途 使用する関数 具体例
1回限りのアクション is_action_just_pressed() ジャンプ、攻撃、メニュー開閉、アイテム使用
継続的なアクション is_action_pressed() 移動、ダッシュ、チャージ、エイム
離した瞬間の処理 is_action_just_released() チャージ攻撃発動、長押し判定の終了
アナログ入力 get_action_strength() 車のアクセル、武器のエイム精度

この使い分けを理解することで、プレイヤーにとって自然で反応の良い操作感を実現できます。

移動系組み込み関数:move_and_slideとmove_towardの使い分け

ノックバックにはmove_toward

Godotには移動に関する便利な関数が多数用意されていますが、特にmove_and_slide()move_toward()の使い分けは重要です。

  • move_and_slide(): CharacterBody2Dの主力関数。現在のvelocity(速度)に基づいてオブジェクトを移動させ、壁や床との衝突を自動で処理し、適切にスライドさせてくれます。物理挙動の基本はこれに任せるのが定石です。
  • move_toward(target_velocity, delta): 現在の速度を、目標の速度に向かって指定した量(delta)だけ変化させます。急な速度変化を避け、滑らかな加速・減速を表現するのに役立ちます。

なぜ移動処理とノックバック処理の両立にmove_towardが必要だったのか?
コース内でノックバック機能を実装する際、通常の移動処理とノックバック処理を両立させる必要がありました。最初は単純にvelocityを直接設定していましたが、これではノックバック効果が一瞬で消えてしまう問題が発生しました。

move_towardのdeltaパラメータの役割
move_toward(target_velocity, delta)の第2引数deltaは、「現在の速度から目標速度に向かって、今回のフレームでどれだけ移動するか」を指定します。通常はacceleration * deltaの形で使用します。

# 問題のあるコード例(直接代入)
func move_player():
    var move_vector = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    # 直接代入:入力があると即座に目標速度になる
    velocity = move_vector * move_speed
    # ノックバック後に入力があると、ノックバック効果が瞬時に消える

# 改善されたコード:move_towardを使用
@export var acceleration: float = 500.0  # 1秒間に500ピクセル/秒の加速

func move_player():
    var move_vector = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    var target_velocity = move_vector * move_speed
    # 段階的変化:現在の速度から目標速度へ少しずつ近づく
    velocity = velocity.move_toward(target_velocity, acceleration * delta)
    
# ノックバック処理
func apply_knockback(direction: Vector2, strength: float):
    # velocityに直接力を加える
    velocity += direction * strength

具体的な動作例(フレームごとの変化):

シナリオ:プレイヤーが敵の攻撃を受けた場合

  1. 攻撃前:velocity = (100, 0) ※右方向に移動中
  2. 攻撃直後:apply_knockback()でvelocity = (100, 0) + (200, 0) = (300, 0) ※右方向に吹き飛ばされる
  3. その後の復帰過程(acceleration = 500、delta = 0.016秒、プレイヤーは右キーを押し続けている場合):
    • 目標速度:target_velocity = (100, 0) ※通常の移動速度
    • フレーム1:velocity = (300, 0).move_toward((100, 0), 500 * 0.016) = (292, 0)
    • フレーム2:velocity = (292, 0).move_toward((100, 0), 8) = (284, 0)
    • フレーム3:velocity = (284, 0).move_toward((100, 0), 8) = (276, 0)
    • …25フレーム後:velocity = (100, 0) ※通常速度に復帰

直接代入の場合の問題:

  1. 攻撃前:velocity = (100, 0)
  2. 攻撃直後:velocity = (300, 0)
  3. 次フレーム:velocity = target_velocity = (100, 0) ※ノックバック効果が即座に消失

つまり、move_towardにより「吹き飛ばされた状態のvelocityから、acceleration * deltaの速度で本来の移動ポイント(target_velocity)に段階的に復帰する」という理解が正確です。

敵AIでも同じ原理
敵も同様にmove_towardを使うことで、攻撃を受けてノックバックされた後も、自然にプレイヤー追跡を再開できます。

# 敵のAI例
@export var chase_acceleration: float = 300.0

func chase_target():
    if target:
        var direction = global_position.direction_to(target.global_position)
        var target_velocity = direction * chase_speed
        # ノックバック効果を保持しつつ、段階的に追跡速度へ変化
        velocity = velocity.move_toward(target_velocity, chase_acceleration * delta)

move_towardの3つの効果:

  • ノックバック保持:直接代入ではなく段階的変化により、ノックバック効果が即座に消えない
  • 制御可能な復帰速度:accelerationの値により、ノックバック後の復帰速度を細かく調整可能
  • 滑らかな操作感:急激な方向転換を避け、慣性のある自然な動きを実現

この手法により、プレイヤーも敵も、ノックバック効果を受けながら自然な移動感を維持できるようになりました。accelerationパラメータを調整することで、キャラクターごとに異なる「重さ」や「機敏さ」を表現することも可能です。

グループ:柔軟なオブジェクト判定の要

グループは、あらゆるノードに付与できるタグのようなものです。UnityのTagシステムと非常に似ており、オブジェクトの種類を柔軟に識別できます。Unityでは1つのGameObjectに1つのTagしか設定できませんでしたが、Godotのグループは複数設定可能で、より柔軟な分類ができます。

例えば、「プレイヤーの攻撃が当たったオブジェクトが “enemy” グループに属しているか?」といった判定が簡単に記述できます。is Playerのような型チェックよりも汎用性が高く、Godot開発では頻繁に使われます。

使い方:

  1. 判定したいノード(例: Slime)を選択し、インスペクターの「Node」タブ → 「Groups」で “enemies” などのグループ名を入力して追加。
  2. コード内でis_in_group()メソッドを使って判定する。

実装例: プレイヤーの攻撃範囲に入ったオブジェクトを判定する

# Playerの攻撃判定用Area2Dに接続された関数
func _on_sword_area_body_entered(body: Node2D):
    # 接触したオブジェクトが "enemies" グループならダメージ処理
    if body.is_in_group("enemies"):
        body.take_damage(attack_power)
    
    # 接触したオブジェクトが "pushable" グループなら押せるオブジェクト
    elif body.is_in_group("pushable"):
        # ...オブジェクトを押す処理
        pass

Collision Layers/Masks:衝突判定の整理術

衝突判定を整理するため、GodotはLayers(レイヤー)Masks(マスク)という仕組みを提供しています。これらを正しく設定することで、パフォーマンスを向上させ、意図しない衝突を防ぐことができます。

  • Collision Layer: そのオブジェクトが「どの層に存在するか」を示します。
  • Collision Mask: そのオブジェクトが「どの層と衝突判定を行いたいか」を示します。

レイヤー分けの例:

  • Layer 1: Player – プレイヤーキャラクター
  • Layer 2: Enemies – 敵キャラクター
  • Layer 3: Weapons – プレイヤーの武器(攻撃判定)

シチュエーション別のMask設定:

プレイヤー(Layer 1)の場合:
敵に接触してダメージを受けたいので、Maskに「Layer 2: Enemies」を設定します。

敵(Layer 2)の場合:
プレイヤーに接触してダメージを与えたいが、敵同士はすり抜けて欲しいので、Maskに「Layer 1: Player」のみを設定します。

プレイヤーの武器(Layer 3)の場合:
敵だけを攻撃したいので、Maskに「Layer 2: Enemies」のみを設定します。プレイヤー自身とは衝突しません。

# コード例:衝突判定での使い分け
func _on_weapon_area_body_entered(body):
    # 武器のArea2DのMaskで「Layer 2: Enemies」のみ設定しているため、
    # この関数には敵だけが入ってくる
    if body.is_in_group("enemies"):
        body.take_damage(attack_power)

「プロジェクト設定」→「Layer Names」で各レイヤーに名前を付けておくと、インスペクターでの設定が非常に分かりやすくなります。

Terrains機能:RPGツクール並みのオートタイル作成

GodotのTerrains機能は、いわゆる「オートタイル」を驚くほど簡単に作成できるシステムです。UnityでRule Tileを使ったことがある人なら、その設定の直感性と手軽さに感動するはずです。

基本的な手順は、TileSetリソース内で「Terrains」タブを開き、タイルのどの辺がどのTerrain(地形タイプ、例: 草、土)に接するかを視覚的にペイントしていくだけです。あとはTileMapエディタでブラシツールを使えば、境界線を自動で判断して適切なタイルを配置してくれます。

便利機能:

  • ランダムブラシ:ダイスアイコンで複数タイルからランダム選択
  • 確率制御:特定タイルの出現頻度を調整可能
  • Physics一括設定:「F」キーで全タイルに衝突判定を一括適用

Marker2D:なぜ管理ノードに最適なのか

シーン内に物理的な実体はないが、スクリプトをアタッチして何かを管理させたい場合(例: GameManager, PuzzleManager)、Unityでは空のGameObjectを使います。Godotにおけるその役割を果たすのがMarker2Dです。

Marker2Dは、位置情報(Transform)だけを持つ最も軽量な2Dノードです。レンダリングや物理演算の負荷が一切ないため、シーン全体のイベントやデータを管理する「マネージャー」役のスクリプトを配置するのに最適です。

Editable ChildrenとScene継承:NPCの効率的な量産方法

Godotでは、一つのベースとなるシーンからバリエーションを作成する方法として、主に2つのアプローチがあります。

  • Editable Children (子ノードを編集可能にする): シーンに配置したインスタンスを右クリックし、「Editable Children」を選択すると、そのインスタンスの中身(スプライトや当たり判定など)を直接編集できます。変更はそのシーン内にのみ保存されます。
    用途: 機能は同じだが、見た目やセリフだけが違うモブNPCを大量に配置する場合に便利。
  • Scene Inheritance (シーンの継承): ベースとなるシーン(例: BaseNPC.tscn)を継承して、新しいシーン(例: Shopkeeper.tscn)を作成します。継承シーンでは、親の機能を引き継ぎつつ、新しいノードやスクリプトを追加して独自の機能を実装できます。
    用途: 「話す」という基本機能に加えて「アイテムを売買する」という特別な機能を持つ商人NPCなど、機能的に異なる派生種を作る場合に最適。
EditableChildrenはノード表記が黄色になる

使い分けの指針:

特徴 Editable Children Scene 継承
向いているNPC 村人A、村人Bなど(見た目・セリフ違い) 商人、鍛冶屋など(独自機能持ち)
再利用性 低い(その場限り) 高い(継承シーンを色々な場所に配置可能)
管理 シンプル(ベースシーンのみ) 体系的(機能ごとにファイルが分かれる)

Autoload:シーンをまたぐデータ管理の仕組み

Autoloadは、UnityのDontDestroyOnLoadとシングルトンパターンを組み合わせたような機能です。プロジェクト設定でスクリプトやシーンをAutoloadに登録すると、ゲーム起動時に自動で読み込まれ、どのシーンからでもグローバルな変数としてアクセスできるようになります。

使い方:

  1. グローバル管理用のスクリプト(例: GameManager.gd)を作成する。
  2. 「プロジェクト設定」→「AutoLoad」タブで、作成したスクリプトを登録し、グローバル名(例: GameManager)を付ける。

実装例: 開封済みの宝箱の状態を記録する

# GameManager.gd (AutoLoadに登録)
extends Node

var opened_chests: Array[String] = []
var player_hp: int = 3
var player_spawn_position: Vector2

# ...その他のグローバルデータ
# TreasureChest.gd
extends StaticBody2D

@export var chest_id: String # インスペクターでユニークなIDを設定 ("forest_chest_01"など)

func _ready():
    # ゲームマネージャーに自分のIDが記録されていれば、開封済みにする
    if GameManager.opened_chests.has(chest_id):
        play_open_animation(false) # アニメーションだけ再生

func open_chest():
    # ...開封処理...
    GameManager.opened_chests.append(chest_id) # IDを記録
    play_open_animation(true)

これにより、プレイヤーの体力、スコア、インベントリ、クエストの進捗など、シーンをまたいで維持したいデータを簡単に管理できます。

視覚効果:modulateによるホワイトフラッシュ実装

ノックバックにはmove_toward

キャラクターがダメージを受けた際に一瞬白く光る「ホワイトフラッシュ」効果は、CanvasItem(Sprite2DやCharacterBody2Dなどが継承)が持つmodulateプロパティで簡単に実装できます。

modulateは、ノードとその子孫の色に乗算されるカラー値です。デフォルトは白(1, 1, 1)で、これを変更することでノード全体の色調を手軽に変えられます。

modulateの特徴

  • 継承性:親ノードから子ノードに自動継承
  • 乗算処理:元の色に対して乗算で色調変更
  • 範囲:1.0が基準値、それ以上で明るく、以下で暗く

実装例: 被ダメージ時のフラッシュ

# Player.gd
func take_damage(amount):
    # ...ダメージ計算...
    
    # フラッシュ処理を呼び出す
    flash_effect()

func flash_effect():
    # 白く光らせる (元の色に乗算されるので、大きな値にすると明るくなる)
    modulate = Color(2, 2, 2)
    
    # 0.1秒待機 (awaitを使うと非同期処理を簡潔に書ける)
    await get_tree().create_timer(0.1).timeout
    
    # 元の色に戻す
    modulate = Color(1, 1, 1)

await get_tree().create_timer(0.1).timeoutは、タイマーノードを追加しなくても一時的な待機処理を1行で書ける便利な記法です。CharacterBody2Dのmodulateを変更すれば、その子であるAnimatedSprite2Dも自動的に色が変わるため、個別にスプライトを操作する必要がありません。

AnimatedSprite2D vs AnimationPlayer:アニメーション機能の使い分け

Godotには主要な2Dアニメーションシステムが2つあり、用途に応じて使い分けることが重要です。

AnimatedSprite2D

  • 用途:スプライトフレームアニメーション
  • 特徴:スプライトシートから直接アニメーション作成
  • 適用場面:キャラクターの歩行、攻撃、アイドルアニメーション
# AnimatedSprite2Dの基本使用
if velocity.x > 0:
    $AnimatedSprite2D.play("move_right")
elif velocity.x < 0:
    $AnimatedSprite2D.play("move_left")
else:
    $AnimatedSprite2D.stop()

AnimationPlayer

  • 用途:複合的なアニメーション制御
  • 特徴:位置、回転、スケール、プロパティを同時制御
  • 適用場面:剣を振る動作、UI演出、カメラワーク
# AnimationPlayerの使用例(剣を振る動作)
func attack():
    var player_animation: String = $AnimatedSprite2D.animation
    if player_animation == "move_right":
        $AnimatedSprite2D.play("attack_right")
        $AnimationPlayer.play("attack_right")  # 剣の位置・角度を制御

UnityのAnimatorとの比較

Unity Animator ≈ Godot AnimationPlayer

  • 共通点:状態遷移、ブレンド、複数プロパティの同時制御
  • 違い:GodotのAnimationPlayerはより直接的で設定が簡単

UnityのAnimatorは状態機械ベースですが、GodotのAnimationPlayerはより直接的にアニメーションを制御できます。複雑な状態遷移が必要な場合はAnimationTree(Godotの上位システム)を使用します。

使い分けの指針

アニメーション内容 推奨システム
スプライトフレーム切り替えのみ AnimatedSprite2D
位置・回転・スケール変更 AnimationPlayer
複数オブジェクトの同期 AnimationPlayer
複雑な状態遷移 AnimationTree

実際の開発では、キャラクターの基本動作にAnimatedSprite2D、武器やエフェクトの動作にAnimationPlayerを組み合わせて使用することが多くなります。

まとめ:実践開発から見えたGodotの真価

今回の実践的なコースを通して、Godotの設計思想の美しさと、2Dゲーム開発におけるその強力さを改めて実感しました。基礎学習だけでは見えなかった、実際の開発フローにおけるGodotの優位性が数多くありました。

特に印象的だった点

  • 一貫した設計思想:Process Mode、Motion Mode、Collision Layersなど、概念が統一されている
  • 組み込み機能の充実:move_and_slide()、Terrains、Y-Sortingなど、よく使う機能が標準搭載
  • 直感的なワークフロー:Editable Children、Autoload、awaitなど、開発効率を重視した設計
  • 軽量性と拡張性の両立:Marker2D、Signalシステムなど、必要な部分のみを軽量に実装

特に、ノードベースの設計とシグナルシステムの組み合わせは、オブジェクト間の連携を疎結合に保ちつつ、直感的な開発を可能にしてくれます。また、Terrains(オートタイル)やY-Sort、各種組み込み関数など、「こういう機能が欲しかった」というかゆいところに手が届く機能が標準で充実している点も大きな魅力です。

Unity経験者から見たGodotの位置づけ

Unity経験者にとって、Godotは「学習しやすく、使いやすい」ゲームエンジンという印象です。完全な乗り換えではなく、プロジェクトの性質に応じて使い分ける選択肢として、非常に有力だと感じました。

Godotが特に優れている分野:

  • 2Dゲーム開発(機能が非常に洗練されている)
  • インディーゲーム開発(軽量で高速な開発サイクル)
  • プロトタイピング(直感的な操作性)
  • 教育用途(概念が理解しやすい)

Unityと比較した場合、アセットストアの規模や情報量ではまだ及びませんが、こと2Dゲーム開発、特にピクセルアート系のゲームにおいては、Godotは非常に強力な選択肢であると確信しました。エンジン自体の軽量さも相まって、プロトタイピングから製品リリースまで、ストレスなく高速に開発サイクルを回せるポテンシャルを感じます。

今後の学習計画

基礎編、実践編を通して、Godotの基本的な開発フローは理解できました。今後は以下の分野をさらに深く学習していきたいと考えています:

  • 3Dゲーム開発:Godotの3D機能の探求
  • 高度なシステム:AnimationTree、VisualScript、GDExtensionなど
  • パフォーマンス最適化:大規模プロジェクトでの最適化手法
  • アセット管理:効率的なプロジェクト構成とワークフロー

今後もGodotの学習を続け、3D機能やより高度なシステムについても探求していきたいと考えています。この学習ノートが、これからGodotでゲーム開発を始める方、特にUnityからの移行を検討している方の一助となれば幸いです。

Godot4: Build a 2D Action-Adventure Game (Udemy)

【Godot】これからGodotを学ぶUnity開発者のための対応表とオススメ教材


※Godot公式が配布しているのデモプロジェクト (最初の2Dゲーム)

Godot Engineについてご存知でしょうか?

2014年にオープンソース化されたMITライセンスのゲーム開発エンジンですが、近年注目された要因は2023年9月22日にUnityがインストールごとに料金を徴収する新料金プラン「Runtime Fee」を発表したことでした。この発表はコミュニティから大批判を受け、Unityは最終的に方針を撤回しましたが、この騒動をきっかけに多くの開発者がGodotへの移行を検討し、一気に知名度が上がりました。

私自身も以前から興味は持っていたものの、UnityやUnreal Engineでの開発に慣れていたためなかなか手が出せずにいました。しかし、近年Godotが順調に機能拡張を続けており、また気分転換という意味合いも込めて、本格的にGodot学習を開始してみることにしました。


※学習中のUdemyコース「Godot4: Build a 2D Action-Adventure Game

今回はUdemyの「Godot Engineで気軽に2Dゲームを作ろう」というコースを修了した感想と、主にUnity開発者視点でみたときのGodotに対する疑問点を自分の学習ノートを見ながらまとめたいと思います。

なお、私はあくまでGodot初学者です。Godotについて技術的アドバイスができるほどの知見はまだないですが、初学者だからこそ感じられる疑問点や感動などを記録しておくことで、これからGodotを学習し始める人の参考になればと考えています。

Godot Engine 公式サイト


  1. Godot Engineに対するQ&A
  2. Godotの基礎学習の開始
  3. Udemy講座「Godot Engineで気軽に2Dゲームを作ろう」を学んだ感想
  4. Godotに触れて感動したこと
  5. Unity開発者向け: Godotとのおおよその対応表
  6. Unity開発者向け: コードで見るGodotとUnityの違い
  7. まとめ

Godot Engineに対するQ&A

下記は自分がGodot学習前に書き記していた疑問に対する自問自答文です。

Q. Godot Engineって?
A. 2014年にオープンソース化されたゲーム開発エンジン。MITライセンスのため完全無料で利用可能。エンジン本体はC++製。

Q. UnityやUnreal Engineと比べた第一印象
A. エンジンが圧倒的に軽量(v4.4.1時点の実行ファイルは約149MB)。ただし日本語情報が少ない。

Q. Godotで作られた有名ゲームって?
A. 「Backpack Battles」、「Backshot Roulette」、「Unrailed 2: Back on Track」など。

Q. ノードベースってなに?
A. 例えば、物理判定のあるキャラクターを作成する場合、CharacterBody2Dというノードの中に、AnimatedSprite2Dというアニメーション用のノードと、CollisionShape2Dという衝突判定用ノードを設置する。Godotはこのようにノードをネストする形で個々の機能を作成していく。

Q. ノードベースって難しい?
A. 簡単。最初は戸惑うかもしれないが、Unityでコンポーネントをアタッチしていく感覚と同じ。

Q. ノードベースって自由度低そうじゃない?
A. 「ノード」と聞くとプリセット範囲でしか値が変更できないような窮屈なイメージがあったが、実際はノードを継承して様々な独自実装ができるので、コーディング体験はUnityやUnreal Engineと何も変わらない。自由。

Q. GDScriptって何?
A. PythonベースのGodot独自言語。開発には基本的にこれを使う。C#も使えるらしいけど、少なくとも基礎学習の範囲では素直にGDScriptで進めたほうが良い。構文もシンプルで理解しやすい。

※Godotで開発されたゲームについては、公式サイトのShowcaseページで一部を閲覧できます。各年度のまとめ動画がオススメです。

Godot基礎学習の開始

新しいゲームエンジンを学習するうえで、公式ドキュメントを確認したり、デモプロジェクトを動かしてみるのは非常に重要です。

私はそれに加えて、YouTubeやUdemyなどの動画チュートリアルを複数個完了させることで基礎を固めています。今回はUdemyにて「Godot Engineで気軽に2Dゲームを作ろう」という基礎に最適なコースを見つけました。これを6.5時間ほどかけて修了しました。

Godotの独自仕様については、ChatGPTやClaudeなどに「これはUnity/Unreal Engineで例えるとどのようなものか?」と質問しながら進めると基礎学習がはかどります。

・@exportは、UnityのSerializeFieldのようなもの
・get_tree().change_scene_to_file()は、UnityのSceneManager.LoadSceneのようなもの

もちろん細かい仕様は異なるとは思いますが、他のゲームエンジンの概念と紐づけることで理解が促進されます。

Udemy講座「Godot Engineで気軽に2Dゲームを作ろう」を学んだ感想


※コースの手順で作成できるゲームの完成形

カリキュラム概要

  • Section 1-3: 基礎(プロジェクト設定、ノード・シーン理解)
  • Section 4-5: 出力(Windows、Web向けビルド)
  • Section 6-9: 2Dゲーム基礎(Player、Mob、HUD実装)
  • Section 10: GDScript詳細
  • Section 11-13: 高度な2D機能(迷路生成、シーン遷移)
  • Section 14-18: システム機能(BGM、UI、データ保存、敵AI、射撃システム)

基礎にフォーカスされたコースで、Godotのインストール手順、セットアップ、エディタの見方、公式デモの実演、より実践的なオリジナルゲームの開発という流れで説明が進みます。1動画あたり1~5分で要点を簡潔に説明してくれるため、サクサクと内容を理解することができました。

オリジナルゲーム開発では、穴掘り法アルゴリズムを用いた迷路のプロシージャル生成、UI作成、データ保存、射撃システムなど、非常に実践的でツボを抑えた解説がされます。これによりタイトル、エリア選択、迷路、タイトルというゲームサイクルを構築することができます。


※穴掘り法アルゴリズムの実行ログ

Godotに興味はあるけどまだ触れたことがない、という人はこのコースから基礎学習を開始することをおすすめします。Godotの日本語情報は非常に少なく、ここまでキレイに整備されたカリキュラムは稀有なので、こうしたコースを第一歩にしてから、より複雑な英語圏のGodotコースを受講するのが良いでしょう。

Udemyはかなり頻繁にセールをしているので、とりあえずお気に入りに登録して、セールのときに購入して空き時間に消化するというスタイルがオススメです。

Godot Engineで気軽に2Dゲームを作ろう (Udemy)

ここからはコースの内容ではなく、Godotエンジンに初めて触れて驚いたことや、実際に学習を進めながらメモしたGodotとUnityの対応表などについてまとめてみます。

Godotに触れて感動したこと

このコースを通じて、Godotの設計思想の美しさを実感できました。特に印象的だったのは以下の点です:

  • ノードシステムの直感性: 「ノード」と「シグナル」を使いこなせるようになるとGodotは超快適です。エンジン自体が軽量なので、UnityやUEのようにエディタのロードやコンパイルに時間を取られることなく、ガシガシとコードを書いて開発を進めることができます。シグナルは最初は混乱しますが、Unityなどでイベント駆動開発をしたことがある人ならすぐ理解できると思います。
  • シーンの再利用性: GodotのシーンはUnity開発者ほど最初は混乱するかもしれませんが、実際の開発体験はUnityと大差ありません。例えば、Unityでは全体管理用のGameManagerというスクリプトをアタッチした空のGameObjectをPrefab化してシーンに配置すると思います。Godotは空のシーンは作れないので、最も軽量なMaker2DというノードにGameManagerスクリプトをアタッチし、そのシーンを別シーンに配置することで再利用することができます。また、グローバル設定にシーンを登録することでシングルトン化することも可能です。つまり、最初の混乱を乗り越えたら、既存のUnity知識がGodot理解の助けになってくれます。
  • 軽量な動作: Unreal Engineは言わずもがなですが、Unityでもプロジェクトを開くときに関連パッケージの読み込みに時間がかかることがあります(※Unity6でかなり改善された気はする)。ですがGodotはそもそものエンジンサイズが軽量で、公式サイトからダウンロードしたエディタの.exeをクリックするだけですぐにゲーム開発を再開できるのでストレスがありません。そのうえでしっかりとした開発機能はあるので、かなり面白いエンジンだなと思いました。
  • 2D機能の充実: TileMapやAnimatedSprite2Dなど専用ノードの豊富さ
  • AnimatedSprite2Dのスプライトシート制作が快適: UnityではスプライトシートをSliceして、個別画像をAnimatorコンポーネントなどに設定して~とかなり手間がかかります。しかしGodotはAnimatedSprite2DのSpriteでスプライトシートを選択してグリッドアイコンを押して、スプライトシートから必要な画像(例: 右歩行の差分4枚)をクリックしたら、即座にタイムラインにその画像が並びます。2Dゲーム開発者ならこの快適さが伝わるのではないでしょうか。
  • タイルセット作成時の衝突判定設定が簡単: Unityではオートタイルの登録や衝突判定を設定してタイルシートを作成するのにかなり手間が必要です。一方、Godotではより直感的に進行可否部分を設定することができ、Physics Layerの設定でタイルごとの当たり判定を効率的に管理できます。複雑な設定なしに、視覚的にタイルの衝突範囲を決められるため、ストレスがほとんどありませんでした。

Unity開発者向け: Godotとのおおよその対応表

Unity開発者がGodotを学習する際に、概念的にあらかじめ知っておくと理解の助けになる対応表を作成しました。これから基礎学習を始める前にざっと目を通しておくことをおすすめします。

シーン・オブジェクト関連

  • Godotのシーン ≒ UnityのPrefab + Scene
  • get_tree().change_scene_to_file() ≒ SceneManager.LoadScene()
  • $記法 / get_node() ≒ GameObject.Find() / FindObjectOfType()
  • get_parent() ≒ transform.parent

ライフサイクル関連

  • _ready() ≒ Start()
  • _process() ≒ Update()
  • _physics_process() ≒ FixedUpdate()

UI・表示関連

  • CanvasLayer ≒ Canvas (Sorting Order)
  • Control ≒ UI GameObject
  • Label ≒ Text (TextMeshPro)

イベント・通信関連

  • シグナル ≒ UnityEvent / C# Event
  • emit_signal() ≒ event.Invoke()

その他の基礎機能

  • @export ≒ [SerializeField]
  • @onready ≒ Awake() / Start()
  • AutoLoad ≒ DontDestroyOnLoad
  • load() ≒ Resources.Load()

Unity開発者向け: コードで見るGodotとUnityの違い

Unity開発者が最も気になるであろう、実際のコードの記述スタイルの違いをいくつかの例で見てみましょう。

例1:変数の公開と初期化 (`@export` vs `[SerializeField]`)

インスペクターで値を調整できるように変数を公開し、ゲーム開始時にその値をコンソールに出力する基本的な処理です。

Godot (GDScript)

# Player.gd
extends Node2D

@export var player_name: String = "Hero"
@export var speed: int = 100

# UnityのStart()に相当
func _ready():
    print("Player Name: ", player_name)
    print("Initial Speed: ", speed)

Unity (C#)

// Player.cs
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private string playerName = "Hero";
    [SerializeField] private int speed = 100;

    // Godotの_ready()に相当
    void Start()
    {
        Debug.Log("Player Name: " + playerName);
        Debug.Log("Initial Speed: " + speed);
    }
}

例2:毎フレームの処理 (`_process` vs `Update`)

オブジェクトを右に移動させ続ける処理です。`delta` (Unityの `Time.deltaTime`) の使い方が分かります。

Godot (GDScript)

# Mover.gd
extends Sprite2D

@export var move_speed: float = 200.0

# UnityのUpdate()に相当
func _process(delta):
    position.x += move_speed * delta

Unity (C#)

// Mover.cs
using UnityEngine;

public class Mover : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 200.0f;

    // Godotの_process()に相当
    void Update()
    {
        transform.position += new Vector3(moveSpeed * Time.deltaTime, 0, 0);
    }
}

例3:物理演算の処理 (`_physics_process` vs `FixedUpdate`)

入力に応じて物理的にキャラクターを動かす処理です。

Godot (GDScript)

# Character.gd
extends CharacterBody2D

const SPEED = 300.0

# UnityのFixedUpdate()に相当
func _physics_process(delta):
    var direction = Input.get_axis("ui_left", "ui_right")
    velocity.x = direction * SPEED
    move_and_slide()  # Godotの便利な組み込み関数

Unity (C#)

// Character.cs
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class Character : MonoBehaviour
{
    [SerializeField] private float speed = 300.0f;
    private Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    // Godotの_physics_process()に相当
    void FixedUpdate()
    {
        float moveInput = Input.GetAxis("Horizontal");
        rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);
    }
}

例4:ノード(オブジェクト)の取得 (`$` vs `GetComponent/transform.Find`)

子ノード(子オブジェクト)を取得して、そのプロパティを変更する処理です。

Godot (GDScript)

# Player.gd
extends Node2D

func _ready():
    # $記法で子ノードに直接アクセス
    $AnimatedSprite2D.play("run")
    
    # get_node() を使う方法もある
    var collision_shape = get_node("CollisionShape2D")
    collision_shape.disabled = true

Unity (C#)

// Player.cs
using UnityEngine;

public class Player : MonoBehaviour
{
    void Start()
    {
        // 子オブジェクトからコンポーネントを取得
        Animator animator = GetComponentInChildren<Animator>();
        if (animator != null)
        {
            animator.Play("run");
        }

        // 名前で子オブジェクトを探す場合
        Transform collisionShape = transform.Find("CollisionShape");
        if (collisionShape != null)
        {
            collisionShape.gameObject.SetActive(false);
        }
    }
}

例5:シグナル(イベント)の使用 (`signal` vs `UnityEvent`)

体力が変化した時に他のオブジェクトに通知する処理です。

Godot (GDScript)

# Player.gd
extends Node2D

signal health_changed(new_health)

var health: int = 100

func take_damage(damage: int):
    health -= damage
    health_changed.emit(health)  # シグナル発信
# UI.gd
extends Control

func _ready():
    var player = get_node("../Player")
    player.health_changed.connect(_on_health_changed)

func _on_health_changed(new_health: int):
    print("体力が ", new_health, " になりました")

Unity (C#)

// Player.cs
using UnityEngine;
using UnityEngine.Events;

public class Player : MonoBehaviour
{
    [SerializeField] private UnityEvent<int> onHealthChanged;
    
    private int health = 100;

    public void TakeDamage(int damage)
    {
        health -= damage;
        onHealthChanged?.Invoke(health);  // イベント発信
    }
}
// UI.cs
using UnityEngine;

public class UI : MonoBehaviour
{
    [SerializeField] private Player player;

    void Start()
    {
        player.onHealthChanged.AddListener(OnHealthChanged);
    }

    private void OnHealthChanged(int newHealth)
    {
        Debug.Log("体力が " + newHealth + " になりました");
    }
}

例6:シーン切り替え (`change_scene_to_file` vs `SceneManager.LoadScene`)

ゲームクリア時に次のレベルに移動する処理です。

Godot (GDScript)

# GameManager.gd
extends Node

func level_complete():
    print("レベルクリア!")
    get_tree().change_scene_to_file("res://scenes/Level2.tscn")

Unity (C#)

// GameManager.cs
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public void LevelComplete()
    {
        Debug.Log("レベルクリア!");
        SceneManager.LoadScene("Level2");
    }
}

まとめ

今回は「Godot Engineで気軽に2Dゲームを作ろう」コースを通じて、Godotの真価を実感することができました。

結論から言うと、ノードベースシステムのGodotは、特に2D開発者にかなりおすすめできるゲームエンジンだと感じました。私はUnityやUnreal Engineにおける2Dゲーム開発の方法を一通り学んでいますが、Godotは主に2Dスプライトやタイルセット管理でかなりの優位性があるように感じています。純粋な2DゲームはGodot、HD-2Dなどライティング演出にこだわりたいならUnityという使い分けが良いのかなと思案中です。

今回は初学者なりにGodotの魅力をまとめてみましたが、エンジンとしてのクオリティでいえば、UnityやUnreal Engineのほうが現状は圧倒的に上です。関連情報やノウハウの量、Unity StoreやFabなどのストア環境、商業ゲームにおける採用実績などでは、Godotはまだまだ駆け出しエンジンといえるでしょう。

ただし、現状普通に楽しく開発できるだけのクオリティになっているGodotには可能性を感じます。

「Backpack Battles」や「Backshot Roulette」など「えっ、あれってGodot製だったの?」というゲームも少しずつ増えていっている印象です。UnityのRuntime Fee騒動のときに間違いなく一定数の開発者はGodotに移行したはずなので、彼らの作品が完成して注目されれば、Godotシェアも今後伸びていく可能性はあるでしょう。

オープンソースというのが非常にユニークな点です。かつてMayaや3dsMaxが覇権を握っていた3DCG業界が、いつのまにかBlenderに支配されているのを見ると、Godotも化けるかも…?と想像してしまいます。(※Blenderの急速進化が30万円以上する3DCGソフトしかマトモな制作環境がなかったことへの改善圧だと考えると、UnityやUnreal Engineは億単位の収益を稼ぐまでは基本無料なので、Blenderと同じ文脈で進化することはなさそう?やはりRuntime Free騒動のようにUnityかUnreal Engineが批判を受けるたびに利用者を吸収する受け皿として成長していく予感がする。)

とはいえ私はUnityもUnreal Engineにも引き続き触れていきます。Godotは第三の選択肢になり得るポテンシャルがあると確認できただけも収穫です。

ノードベースシステムは、UnityともUnreal Engineとも異なる開発体験が味わえるので、興味があればぜひ触れてみてください。

今後もGodotに関する学習メモを定期的に記事にしつつ、日本におけるGodot情報不足の解消に微力ながら協力していければなと考えています。

【RPGツクールMV/MZ】簡単なカットシーン作成術&カメラ演出で魅力UP!(Galv_CamControl編)

カメラ演出を加えたRPGツクールのカットシーン例

RPGツクールでゲームにドラマティックなカットシーン(イベントシーン)を盛り込みたい!と思ったとき、実はUnityやUnreal Engineといった他のゲームエンジンと比較して、RPGツクールでは驚くほど簡単に基本的なカットシーンを作成できます。

この記事では、RPGツクールの標準機能を使った基本的なカットシーンの作り方から、さらに表現力を豊かにするためのカメラ演出の追加方法までを解説します。カメラ演出には、定番のプラグイン「Galv_CamControl.js」を使用します。

これらのテクニックを使えば、あなたのゲームの物語をより魅力的に、そしてプレイヤーの記憶に残るものにできるはずです。

この記事の内容

  1. 基本的なカットシーンの作り方(RPGツクール標準機能)
  2. カメラ演出の追加方法(Galv_CamControlプラグイン)
  3. リッチなカットシーンの完成へ

基本的なカットシーンの作り方(RPGツクール標準機能)

まずは、プラグインを使わずにRPGツクールの標準機能だけでカットシーンを作成する方法です。

RPGツクールの標準機能だけで作成したシンプルなカットシーン

(↑標準機能だけでも、このようなシーンは作成可能です)

RPGツクールのイベントコマンド設定例(基本的なカットシーン)

RPGツクールのイベントコマンドにある、以下の基本機能を組み合わせるだけで、キャラクター同士の会話や動きを含むカットシーンを簡単に作成できます。

  • 文章の表示: キャラクターのセリフやナレーションを表示します。顔グラフィックと組み合わせることで、誰が話しているかを明確にできます。
  • フキダシアイコンの表示: キャラクターの頭上に「!」や「?」などのアイコンを表示し、感情や状態を視覚的に表現します。
  • 移動ルートの設定: イベントキャラクターやプレイヤーを指定した経路で移動させます。向きの変更、ウェイト(待ち時間)、スイッチ操作なども組み込めます。対象キャラを「このイベント」や名前で指定できるのが非常に直感的です。
  • 透明状態の変更: プレイヤーキャラクターなどを一時的に画面から見えなくします。イベントシーンへの導入時や終了時に便利です。

【Tips】イベントには分かりやすい名前を付けよう!

イベント設定画面でイベント名を設定する

移動ルートの設定でイベント名を指定する場面

他のゲームエンジン経験者から見ると、RPGツクールのイベント作成機能、特にキャラクター指定(「プレイヤー」「このイベント」「イベントID:1」など)や移動ルート設定の手軽さは驚くほど洗練されています。

ここで一つ重要なポイントは、マップ上に配置するキャラクターやオブジェクトなどのイベントには、必ず分かりやすい名前(例:「村人A」「宝箱_洞窟」など)を付けておくことです。イベントコマンドで対象を指定する際に名前で選択できるため、管理が非常に楽になります。これは個人開発であっても、後々の修正やデバッグ時に「あのイベントどれだっけ?」と迷う時間を減らし、将来の自分を助けることにつながります。ゲーム制作が進むほどデータは複雑になるので、こうした小さな心がけが大切です。

さて、標準機能だけでもカットシーンは作れますが、ずっと同じ画角だと少し単調に見えてしまうかもしれません。そこで次に、カメラワークを追加してみましょう。

カメラ演出の追加方法(Galv_CamControlプラグイン)

基本的なカットシーンにカメラワークを加えることで、シーンの臨場感を高めたり、プレイヤーの視線を誘導したりと、より印象的な演出が可能になります。RPGツクールMV/MZでカメラ制御を実現するには、プラグインを利用するのが一般的です。

今回は、多くのツクールユーザーに利用されている定番のカメラ制御プラグイン「Galv_CamControl.js」を使用します。

プラグイン配布元:Galv’s Scripts – MV Cam Control (※MZで利用する場合は、有志による改変版などが必要になる場合があります。別途検索・ご確認ください)

【プラグインの導入】

  1. 上記サイト(または対応版を探して)からプラグインファイル(`Galv_CamControl.js`)をダウンロードします。
  2. RPGツクールのプロジェクトフォルダ内にある `js/plugins` フォルダに、ダウンロードしたファイルをコピーします。
  3. RPGツクールエディタを開き、「ツール」メニューから「プラグイン管理」を選択します。
  4. プラグイン管理ウィンドウで、空いている行をダブルクリックし、「名前」のドロップダウンリストから「Galv_CamControl」を選択して「OK」をクリックします。
  5. 状態が「ON」になっていることを確認し、「OK」をクリックしてプラグイン管理ウィンドウを閉じます。

これでプラグインが有効になり、イベントコマンドの「プラグインコマンド」からカメラ制御の命令を実行できるようになります。

Galv_CamControlのプラグインコマンド入力例

Galv_CamControlの基本的な使い方 (プラグインコマンド)

イベントコマンドの「プラグインコマンド」を使って、以下のような命令を実行することでカメラを制御できます。(これはMVベースの書式例です。MZや特定の改変版では書式が若干異なる場合がありますので、プラグインのヘルプ等をご確認ください)

  • 特定のイベントにカメラを向ける(追従させる):
    例: CAM EVENT 1 (イベントIDが1のキャラクターにカメラを向ける)
    例: CAM EVENT 2 (イベントIDが2のキャラクターにカメラを向ける)
  • プレイヤーにカメラを戻す(追従させる):
    CAM PLAYER
  • 指定したマップ座標にカメラを移動させる:
    CAM MAP X Y (例: CAM MAP 10 15 で座標(10, 15)にカメラを移動)
  • カメラの追従を解除(現在の位置で固定)する:
    CAM TARGET 0

【補足:カメラの移動時間(スクロール)】
上記のコマンドの後に、半角スペースを空けて数字(フレーム数)を指定すると、カメラがその時間をかけてスムーズに移動します。例えば、CAM EVENT 1 60 と入力すると、60フレーム(1秒)かけてイベント1の位置までカメラがスクロールします。時間を指定しない場合は、カメラは瞬時に移動します。

これらのコマンドを、イベントコマンドの「文章の表示」や「移動ルートの設定」、「ウェイト」などと組み合わせることで、会話の話し手にカメラをフォーカスさせたり、重要なアイテムや場所にカメラを向けたりといった演出が可能になります。

【カメラ演出の応用:見せて伝えるゲームデザイン】

カメラを宝箱に向ける演出例

優れたゲームデザインでは、プレイヤーに「どこへ行け」「何を取れ」と指示するだけでなく、目的地や対象物をカメラで一度映し出すことで、プレイヤーが直感的に理解できるように誘導します。

Galv_CamControlのようなカメラ制御プラグインは、カットシーンの演出強化だけでなく、このような「見せて伝える」ゲームデザインを実現するためにも非常に有効です。例えば、NPCからクエストを受けたら、その目的地や関連するオブジェクトに一瞬カメラをパンさせる、といった使い方です。これにより、プレイヤーは次に何をすべきか分かりやすくなり、ゲーム体験が向上します。

💡 応用テクニック:対峙シーンの演出
キャラクター同士が対峙するような緊迫したシーンでは、二人の間に透明な空のイベントを配置し、そのイベントにカメラを固定(例: CAM EVENT [空イベントID] [時間]CAM TARGET 0)することで、キャラクターが画面の両端に配置され、中央に空間ができるような、映画的な構図を作り出すことも可能です。

リッチなカットシーンの完成へ

カメラ演出を加えたRPGツクールのカットシーン完成例

基本的なイベントコマンド(会話、移動、フキダシなど)に、Galv_CamControlによるカメラワークを加えるだけで、最初のシンプルなカットシーンがよりダイナミックで、見ごたえのあるものになったのがお分かりいただけるかと思います。

特にRPGツクールのような2Dベースのゲームでは、画面に動きがないとイベントシーンが単調に見えがちです。カメラを効果的に動かすことで、プレイヤーの視点を誘導し、シーンの重要性を高め、感情的なインパクトを与え、プレイヤーを飽きさせない工夫を凝らしましょう。

RPGツクールの手軽なイベント作成機能と、プラグインによるカメラ制御を組み合わせることで、あなたのゲームはさらに魅力的なものになるはずです。


【RPGツクールMV/MZ】マップ単位でキャラクターを自動切り替えするプラグイン UM_MapActorSetting.js

RPGツクールMVRPGツクールMZで、複数の主人公が登場したり、特定のマップでは特定のキャラクターを操作したりするようなゲームを作りたいと思ったことはありませんか?

通常、マップごとに操作キャラクターを切り替えるには、マップ遷移イベントや場所移動イベントの中に「パーティリーダーの変更」コマンドを配置する必要があります。しかし、対象となるマップが多い場合、この方法は以下のような課題があります。

  • マップごとにイベントを設定・コピーする手間がかかる
  • 設定漏れやキャラクターの指定ミスなど、バグの原因になりやすい。
  • どのマップでどのキャラクターがリーダーになるのか、後から確認・管理するのが大変

これらの課題を解決するために、マップのメモ欄に簡単なタグを記述するだけで、そのマップに入った際に自動的に指定したキャラクターに操作を切り替えるプラグイン「UM_MapActorSetting.js」を作成しました。

この記事では、この自作プラグインの機能、導入方法、設定手順、そして具体的な使用例について解説します。イベントコマンドを使わずに、もっと手軽にキャラクター切り替えを実装したい方におすすめです。

マップ移動時に自動で操作キャラクターが切り替わる様子


この記事の内容

  1. プラグインの概要とメリット
  2. プラグインのインストール方法
  3. 基本的な設定手順 (パラメータとマップメモ)
  4. 具体的な使用例 (勇者と姫の切り替え)
  5. 使用上の注意点
  6. プラグインコード (UM_MapActorSetting.js)

プラグインの概要とメリット

前述の通り、RPGツクールMV/MZでマップごとに操作キャラクターを切り替える標準的な方法は、イベントコマンド「パーティリーダーの変更」を使用することです。しかし、この方法はマップ数が多くなるほど設定が煩雑になり、管理も大変です。

UM_MapActorSetting.js プラグインは、この問題を解決するために開発されました。

主な機能とメリット:

  • マップのメモ欄で指定: どのマップでどのキャラクターを操作させるかを、各マップの「メモ」欄に簡単なタグ(例: ``)で指定します。
  • 自動切り替え: プレイヤーがメモタグを設定したマップに移動すると、プラグインが自動的にパーティの先頭キャラクター(操作キャラクター)を指定されたアクターに入れ替えます。
  • イベント不要: マップごとに切り替え用のイベントを作成・配置する必要がなくなります。
  • 設定がシンプル: プラグインパラメータで「識別子」と「アクターID」を紐付け、マップメモに識別子を書くだけなので、設定が簡単で、後からの確認や変更も容易です。

これにより、複数キャラクターを切り替えるゲームの開発がよりスムーズになります。

プラグインのインストール方法

プラグインの導入は簡単です。

  1. この記事の最後にあるプラグインコードをコピーし、テキストエディタ(メモ帳など)に貼り付けて、ファイル名を `UM_MapActorSetting.js` として保存します。
  2. 作成した `UM_MapActorSetting.js` ファイルを、あなたのRPGツクールプロジェクトフォルダ内の `js/plugins` フォルダに配置します。
  3. RPGツクールエディタを開き、「ツール」メニューから「プラグイン管理」を開きます。
  4. プラグインリストの空いている行をダブルクリックし、「名前」のドロップダウンから `UM_MapActorSetting` を選択し、「状態」を「ON」にして「OK」をクリックします。

RPGツクールMV/MZのプラグイン管理画面でUM_MapActorSettingを有効化

これでプラグインが有効になりました。

基本的な設定手順 (パラメータとマップメモ)

次に、プラグインが正しく動作するように設定を行います。

プラグインパラメータの設定

まず、マップのメモ欄で使う「識別子(任意の文字列)」と、実際に切り替える「アクター(キャラクター)」を紐付けます。

  1. プラグイン管理画面で `UM_MapActorSetting` をダブルクリック(または選択してEnter)し、右側の「パラメータ」欄を開きます。
  2. 「ActorSettings」(アクター設定リスト)という項目をダブルクリックします。
  3. リストの空いている行をダブルクリック(または下の「+」ボタン的なものを操作)して、新しい設定を追加します。
  4. 追加した行で、以下の2つの項目を設定します。
    • Character ID (キャラクターID): マップのメモ欄で使用する任意の識別子(半角英数字推奨、例: `hero`, `princess`, `actor1` など)を入力します。
    • Actor (アクター): 右側のプルダウンメニューから、この識別子に対応させたいアクターをデータベースから選択します。
  5. 切り替えたいキャラクターの数だけ、この設定を追加します。
  6. 設定が終わったら「OK」をクリックして閉じます。

プラグインパラメータActorSettingsの設定画面を開く

Character IDとActorを設定する

マップメモの設定

次に、操作キャラクターを切り替えたいマップ側で設定を行います。

  1. RPGツクールエディタで、操作キャラクターを自動で切り替えたいマップを開きます。
  2. マップ編集画面で、マップ名を右クリック(または編集モードでマップを選択)し、「マップ設定」(または「マップのプロパティ」)を開きます。
  3. 右下にある「メモ」欄に、以下の形式でタグを記述します。
    <Player:識別子>

    識別子 の部分には、プラグインパラメータで設定した「Character ID」を正確に入力します。(例: `<Player:hero>`)

  4. 設定したら「OK」をクリックして閉じます。

マップ設定のメモ欄にタグを記述する

これで、プレイヤーがこのマップに移動してきた際に、<Player:識別子> タグで指定された識別子に対応するアクターが、自動的にパーティの先頭(操作キャラクター)になります。

具体的な使用例 (勇者と姫の切り替え)

例として、「アクターID: 1」が「勇者」、「アクターID: 2」が「姫」としてデータベースに登録されているとします。

1. プラグインパラメータの設定:

プラグイン管理画面で以下のように設定します。

勇者(hero)と姫(princess)のパラメータ設定例

  • 設定1:
    • Character ID: hero
    • Actor: 0001: 勇者 (データベースのアクターID 1)
  • 設定2:
    • Character ID: princess
    • Actor: 0002: 姫 (データベースのアクターID 2)

プラグインパラメータ設定後のリスト表示

2. マップメモの設定:

例えば、2つのマップに対して以下のようにメモを設定します。

  • マップ1(勇者の街)のメモ欄:
    <Player:hero>
  • マップ2(お城)のメモ欄:
    <Player:princess>

結果:

  • プレイヤーが「勇者の街」マップに入ると、操作キャラクターが自動的に「勇者」になります。
  • プレイヤーが「お城」マップに入ると、操作キャラクターが自動的に「姫」になります。

マップ移動でキャラクターが切り替わるデモ

このように、イベントコマンドを一切使わずに、マップごとの操作キャラクター切り替えを実現できます。

使用上の注意点

  • キャラクターの切り替えは、マップ遷移時(場所移動コマンド実行後など)とゲームロード時に自動的に行われます。
  • マップのメモ欄に記述するタグ <Player:識別子> の「識別子」部分は、プラグインパラメータで設定した「Character ID」と完全に一致させてください。大文字・小文字も区別されます。記述を間違えると切り替えは機能しません。
  • プラグインは、指定されたアクターをパーティの先頭に入れ替える(元々いた場合は先頭にし、いなければ追加して先頭にする)シンプルな動作をします。パーティメンバー全体の入れ替えや並び順の制御は行いません。もし元々パーティにいないアクターを指定した場合、そのアクターがパーティに追加されてリーダーになります。(元のリーダーはパーティから外れます)
  • データベースで新しいアクターを追加・変更した場合や、プラグインパラメータの「Character ID」を変更した場合は、必ずプラグインパラメータの設定も更新してください。
  • 他のパーティメンバー変更系プラグインと競合する可能性はあります。併用する場合はご注意ください。

プラグインコード (UM_MapActorSetting.js)

このプラグインコードはご自由にお使いください。改変なども問題ありません。


//=============================================================================
// UM_MapActorSetting
//=============================================================================

/*:
 * @plugindesc マップのメモタグに基づいて、自動的にプレイヤーキャラクターを切り替えます。
 * @author UHIMA
 *
 * @param MapActorSettings
 * @text アクター設定
 * @type struct<ActorConfig>[]
 * @desc 各アクターの設定
 *
 * @help
 * ============================================================================
 * ■ 概要
 * ============================================================================
 * このプラグインは、マップのメモタグに基づいて自動的にプレイヤーキャラクターを
 * 切り替えることを可能にします。
 *
 * ============================================================================
 * ■ 使用方法
 * ============================================================================
 * プレイヤーキャラクターを切り替えたいマップには、以下のメモタグを追加してください:
 *
 * <Player:characterId>
 *
 * プラグインパラメータで「データベース」のアクターIDとキャラクター名を紐づけることができます。
 * これにより<Player:harold>のような簡易的な記述でアクターを切り替えることができます。
 * 新規キャラクターを追加した場合やアクターIDを変更した場合は、このプラグインの紐づけも対応させてください。
 *
 * 例:
 * <Player:harold>
 */

/*~struct~ActorConfig:
 * @param Character ID
 * @text キャラクターID
 * @type string
 * @desc マップメモタグで使用する識別子(例:harold)
 *
 * @param Actor
 * @text アクター
 * @type actor
 * @desc 切り替えるアクター
 */

(function () {
  var parameters = PluginManager.parameters("UM_MapActorSetting");
  var MapActorSettings = JSON.parse(parameters["MapActorSettings"] || "[]").map(
    (setting) => JSON.parse(setting)
  );

  var _Game_Player_performTransfer = Game_Player.prototype.performTransfer;
  Game_Player.prototype.performTransfer = function () {
    _Game_Player_performTransfer.call(this);
    switchActorByMap();
  };

  function switchActorByMap() {
    if ($dataMap && $dataMap.meta.Player) {
      var characterId = $dataMap.meta.Player;
      var actorConfig = MapActorSettings.find(
        (config) => config["Character ID"] === characterId
      );

      if (actorConfig) {
        var newActorId = Number(actorConfig.Actor);
        var currentLeader = $gameParty.leader();
        var currentLeaderId = currentLeader ? currentLeader.actorId() : null;

        if (currentLeaderId !== newActorId) {
          if (currentLeaderId) {
            $gameParty.removeActor(currentLeaderId);
          }
          $gameParty.addActor(newActorId);
          $gamePlayer.refresh();
        }
      }
    }
  }

  var _Scene_Map_start = Scene_Map.prototype.start;
  Scene_Map.prototype.start = function () {
    _Scene_Map_start.call(this);
    switchActorByMap();
  };
})();

【RPGツクールMV/MZ】スイッチで床が出現!イベントを使ったギミックの作り方

RPGツクールでスイッチON時に床が出現する様子

RPGツクールMVRPGツクールMZでゲームを作成する際、特定の条件を満たすと道が開ける、といったギミックを実装したくなることがありますよね。例えば、ボスを倒したり、仕掛けを解いたりした後に、それまで通れなかった場所に床が出現して進めるようになる、といった演出です。

このような「スイッチをONにすると出現する床」は、プレイヤーに達成感を与え、探索の幅を広げる効果的なギミックとなります。

この記事では、RPGツクールMV/MZで、特定のスイッチがONになった時にイベント機能を使って床(足場)を出現させ、通行可能にする具体的な作り方を解説します。


この記事の内容

  1. なぜ「イベント」で床を作るのか?(概要説明)
  2. イベントで床タイル画像を使うための「タイルセット」編集
  3. スイッチで出現・通行可能になる床イベントの設定手順
  4. まとめ:イベント活用で動的なマップを作ろう

なぜ「イベント」で床を作るのか?(概要説明)

RPGツクールでは、マップの基本的な地形や壁などは「タイル」を配置して作成します。しかし、マップに配置したタイルそのものは、基本的に静的なものであり、ゲーム中のスイッチや変数の状態によって表示を切り替えたり、消したりすることはできません。

そのため、今回のような「特定の条件(スイッチON)で出現する床」のような動的な仕掛けは、「イベント」機能を使って実装する必要があります。イベントであれば、出現条件を設定したり、表示する画像を指定したり、通行設定を制御したりすることが可能です。

この記事では例として、ゲーム内のスイッチ「#0001_通行床表示」がONになった時に、指定した場所に床タイルが表示され、通行可能になるように設定していきます。

イベントで床タイル画像を使うための「タイルセット」編集

まず、出現させたい床のグラフィックをイベントの画像として設定する準備が必要です。

マップ上で新しいイベントを作成し、画像設定を開いてみてください。おそらく、[タイルセットB](壁やオブジェクトなど)や[タイルセットC](建物の屋根など)の画像は選択できても、肝心の地面や床に使われるタイル画像がリストに表示されないことに気づくはずです。

イベント画像設定の初期状態(床タイルがない)

これは、イベント画像として使用できるタイルセットがデフォルトでは限定されているためです。これを解決するには、「データベース」(ツールバーの歯車アイコンなど)を開き、「タイルセット」タブで設定を変更する必要があります。

1. データベースを開き、「タイルセット」タブを選択します。

2. 現在のマップで使用しているタイルセット(例: 001: フィールド)を選択します。

3. 右側の設定項目で、[A]タブの[A2](地面タイル)に設定されている画像シートと同じ画像ファイルを、[D]タブ(または空いている他のタブ、例: [E])にも設定します。

タイルセットのA2と同じ画像をDに設定する

4. これで設定は完了です。「適用」または「OK」を押してデータベースを閉じます。

この操作により、イベントの画像設定リストに[タイルセットD](または設定したタブ)が追加され、そこから床タイルを選択できるようになります。

【重要】通行設定の確認!

タイルセット編集画面で、追加設定した床タイルの画像を選択し、左側の「通行」設定がどのようになっているか必ず確認してください。

  • : 通行可能
  • ×: 通行不可
  • : 通行可能(プレイヤーより上に表示)

出現させたい床タイルが「×」(通行不可)になっていると、イベントで床が表示されてもプレイヤーはその上を歩くことができません。必ず「」(または状況に応じて★)に設定されているか確認し、もし×だったらクリックして◯に変更しておきましょう。

スイッチで出現・通行可能になる床イベントの設定手順

タイルセットの準備ができたら、床を出現させるイベントを作成します。設定は非常にシンプルです。

1. 床を出現させたいマップ上のマスを右クリックし、「イベントの作成」を選択します。

2. イベントエディターが開いたら、まず1ページ目(初期状態のページ)は、画像などを何も設定せず、基本的にそのままの状態にしておきます。ただし、以下の2点は確認・設定してください。

  • オプション: 「すり抜け」のチェックがオフになっていることを確認します。(チェックが入っていると、床がない状態でもプレイヤーが通過できてしまいます)
  • プライオリティ:通常キャラの下」を選択します。(これにより、後で床が出現したときにプレイヤーがその上を歩けます)

(※トリガーはデフォルトの「決定ボタン」のままで問題ありません。実行内容も空のままでOKです。)

出現床イベントの設定画面(1ページ目:ほぼデフォルト)

3. 次に、イベントエディター上部の「EVページ作成」ボタンをクリックし、2ページ目を作成します。

4. 2ページ目で、床が出現した状態を設定します。

  • 出現条件: 左側の「出現条件」欄で「スイッチ」にチェックを入れ、対象のスイッチ(例: #0001_通行床表示)が「ON」であることを指定します。
  • 画像: 左下の「画像」欄をダブルクリックし、タイルセット選択画面を開きます。先ほどタイルセット編集で選択可能になった床タイル(例: [タイルセットD]から)を選び、「OK」をクリックします。
  • オプション: 1ページ目と同様に、「すり抜け」はオフのままにします。
  • プライオリティ: 1ページ目と同じく「通常キャラの下」を選択します。

(※トリガーや実行内容は、このページも基本的にそのままでOKです。)

出現床イベントの設定画面(2ページ目:スイッチONで床画像表示)

5. これでイベントの設定は完了です。「OK」を押してイベントエディターを閉じます。

6. 作成した「出現床」イベントをコピー(Ctrl+C または Cmd+C)し、床を出現させたい他のマスにペースト(Ctrl+V または Cmd+V)して配置していきます。

マップ上に作成した出現床イベントをコピーして配置する

ゲームをテストプレイし、指定したスイッチ(例: #0001_通行床表示)をONにするイベントを実行すると、イベントを配置した場所に床が出現し、その上を歩けるようになっているはずです。

スイッチON後に床が出現し通行可能になったゲーム画面

以上で、スイッチ操作によって出現する通行可能な床の実装は完了です。


まとめ:イベント活用で動的なマップを作ろう

この記事では、RPGツクールMV/MZで「スイッチがONになると出現する床」をイベント機能を使って実装する基本的な方法を解説しました。

ポイントは以下の通りです。

  • タイル自体は動的に変更できないため、「イベント」として床を作成する。
  • イベント画像で床タイルを使うために、事前に「データベース」で「タイルセット」設定を変更しておく必要がある。
  • タイルセット編集時には、床タイルの「通行設定」が「◯(通行可能)」になっているか確認する。
  • イベントは2ページ構成にし、1ページ目は空白(スイッチOFF時)、2ページ目は出現条件(スイッチON)と床画像、オプション(すり抜けOFFプライオリティ通常キャラの下)を設定する。

このテクニックを使えば、ボス撃破後に新たな道が開けたり、仕掛けを解くことで隠し通路が出現したりと、プレイヤーの行動によってマップが変化する、よりダイナミックなゲームを作ることができます。ぜひ、あなたの作品作りに活用してみてください!

【Unity】InputSystemを使って「溜め攻撃」を超シンプルに実装する方法

この記事では、Unityの新しい入力システム「Input System」を活用して、ゼルダの伝説の回転斬りのような「溜め攻撃」アクションを実装する方法を解説します。

Input Systemには長押しを検知する「Hold Interaction」もありますが、今回はあえてそれを使わず、ボタン入力の基本的なイベントである「押した瞬間 (started)」と「離した瞬間 (canceled)」の時刻差を利用するアプローチを紹介します。

従来のUpdate関数内で毎フレーム入力を監視する方法と比較して、Input Systemのイベント駆動型アプローチは、コードがシンプルになり、特に機能追加や変更に対する保守性が向上するメリットが期待できます。


この記事の内容

  1. 溜め攻撃の実装:Input System (Press) vs Update vs Hold Interaction
  2. Input SystemのインストールとInput Actionsアセットの作成
  3. Input Actions:左クリック(攻撃ボタン)アクションの定義
  4. スクリプト連携:PlayerInputとイベント処理スクリプト
  5. 溜め時間の計算ロジック:startedとcanceledの活用
  6. 中間演出の実装:コルーチンでInput Systemの弱点を補う
  7. まとめ:Pressイベント方式のメリット・デメリットと使い分け

溜め攻撃の実装:Input System (Press) vs Update vs Hold Interaction

溜め攻撃を実装する方法はいくつか考えられます。それぞれの特徴を見てみましょう。

  • 従来のUpdate関数による実装:
    • Update()内で毎フレーム入力をチェックし、ボタンが押されている時間を計測します。
    • 仕組みは直感的ですが、入力の種類や条件分岐が増えるとコードが複雑化しがちです。
  • Input System の Hold Interaction を使う実装:
    • Input ActionsアセットでInteractionを「Hold」に設定し、指定時間(Hold Time)長押しされるとperformedイベントが発生します。
    • 設定は簡単ですが、後述するように「短押し(通常攻撃)」と「長押し(溜め攻撃)」を同じボタンで使い分けたい場合に、実装がやや複雑になることがあります。
  • Input System の Press イベント (started/canceled) を使う実装(本記事のアプローチ):
    • ボタンが押された瞬間(started)と離された瞬間(canceled)のイベントを利用し、その間の時間を計測します。
    • イベント駆動でコードが整理されやすく、短押し・長押しの判定や溜め中の処理を柔軟に実装できます。

Q. なぜHold InteractionではなくPressイベント (started/canceled) を使うのか?

Hold Interactionは指定時間長押しされたことを検知するのに便利ですが、「同じボタンで短押し(通常攻撃)と長押し(溜め攻撃)を使い分けたい」場合や、「溜めている最中に演出を入れたい」場合には、少し扱いにくい側面があります。

  • 短押しと長押しの両立: Hold Interactionでは、長押しが成立してperformedが呼ばれる前に、短押し時の処理(通常攻撃など)をボタンを押した直後に実行させたい場合、工夫が必要です。例えば、「押した瞬間(started)に通常攻撃を出し、Holdが成立したらキャンセルする」といった制御や、別途短押し用のアクションを用意するなど、実装が複雑になりがちです。
  • 押下開始タイミングの活用: ボタンを押した瞬間にキャラクターが構えモーションに入ったり、溜めエフェクトを開始したりといった処理を即座に行いたい場合、Hold Interactionだけではタイミングを計りにくいことがあります。
  • 溜め中の制御: 溜めゲージを表示したり、一定時間ごとに溜めレベルが上がる演出を入れたりする場合、最終的にperformedイベントが発生するまで待つ必要があるHold Interactionでは、溜めている途中の細かな制御がしづらいです。

一方、本記事で紹介するPressイベント (started/canceled) を利用する方法では、

  • ボタンを押した瞬間のstartedイベントと、離した瞬間のcanceledイベントの時刻差を計測することで、押下時間を正確に把握できます。
  • これにより、「一定時間未満なら通常攻撃、一定時間以上なら溜め攻撃」といった判定をcanceledイベント内で簡単に行えます。
  • startedイベント発生時に溜め動作やエフェクトを開始し、canceledイベントで攻撃を発動、という流れを自然に実装できます。
  • (後述するコルーチンなどを組み合わせれば)溜めている最中の演出や状態変化も柔軟に組み込めます。

このように、短押し/長押しの判定や、溜め中の演出・状態管理を柔軟に行いたい場合には、started/canceledイベントを利用するアプローチが適していると言えます。


Input SystemのインストールとInput Actionsアセットの作成

まず、プロジェクトにInput Systemパッケージを導入し、入力を定義するInput Actionsアセットを作成します。

Input Systemパッケージのインストール

Unityエディタのメニューから操作します。

Package ManagerからInput Systemをインストールする手順

  1. Window > Package Manager を開きます。
  2. 左上のドロップダウンメニューで「Packages: Unity Registry」を選択します。
  3. リストから「Input System」を探し、「Install」ボタンをクリックします。
  4. インストール中にプロジェクト設定の変更を促すダイアログが出たら、「Yes」を選択してエディタを再起動します。

Input Actionsアセットファイルの作成

次に、入力アクションを定義するためのアセットファイルを作成します。

ProjectウィンドウでInput Actionsアセットを作成

  1. Projectウィンドウで右クリックし、Create > Input Actions を選択します。
  2. 作成されたアセットファイル(例: `PlayerInputActions.inputactions`)に分かりやすい名前を付けます。
  3. 作成したアセットファイルを選択し、Inspectorウィンドウで「Generate C# Class」にチェックを入れ、「Apply」ボタンを押します。

Generate C# Classにチェックを入れるとC#スクリプトが生成される

「Generate C# Class」にチェックを入れることで、このInput Actionsアセットに対応するC#クラスが自動生成され、スクリプトから入力イベントを扱いやすくなります。


Input Actions:左クリック(攻撃ボタン)アクションの定義

作成したInput Actionsアセットファイル(例: `PlayerInputActions.inputactions`)をダブルクリックして編集ウィンドウを開き、溜め攻撃に使用するアクションを定義します。

Input Actionsエディタで攻撃アクションを設定する

  1. Action Maps 列の「+」ボタンをクリックし、新しいAction Mapを作成します(例: `Gameplay`)。Action Mapは関連するアクションをまとめるグループです。
  2. Actions 列の「+」ボタンをクリックし、新しいActionを作成します(例: `AttackLeft`)。これが具体的な入力操作に対応します。
  3. 作成した`AttackLeft` Actionを選択し、右側のPropertiesパネルで以下を設定します。
    • Action Type:Button」を選択します。これは、押す/離すの単純な入力に適しています。
  4. `AttackLeft` Actionの下にある``を選択し、Propertiesパネルで以下を設定します。これが具体的な入力デバイスのボタンとの紐付け(Binding)です。
    • Path: プルダウンメニューから「Mouse」>「Left Button」を選択します。(ゲームパッドのボタンなどもここで設定可能)
  5. 編集が終わったら、ウィンドウ上部の「Save Asset」ボタンをクリックして変更を保存します。

これで、「マウスの左クリック」が`AttackLeft`という名前のアクションとして、スクリプトからイベントとして受け取れるようになりました。


スクリプト連携:PlayerInputとイベント処理スクリプト

定義したInput Actionsを実際にゲーム内で機能させるために、スクリプトとの連携を設定します。

1. シーン内に空のGameObjectを作成し、分かりやすい名前(例: `InputManager`)を付けます。

2. 作成した`InputManager` GameObjectに「Player Input」コンポーネントを追加します。

3. Player Inputコンポーネントの「Actions」フィールドに、先ほど作成したInput Actionsアセット(例: `PlayerInputActions.inputactions`)をドラッグ&ドロップで設定します。

4. 以下のC#スクリプト(例: `InputManager.cs`)を作成し、`InputManager` GameObjectにアタッチします。

using UnityEngine;
using UnityEngine.InputSystem;

// PlayerInputコンポーネントが必須であることを示す
[RequireComponent(typeof(PlayerInput))]
public class InputManager : MonoBehaviour
{
    // ボタンが押され始めた時刻を記録する変数
    private float buttonPressStartTime;
    // 溜め攻撃と判定する時間のしきい値(例: 1秒)
    private const float specialAttackThreshold = 1.0f;

    // PlayerInputコンポーネントから呼び出されるメソッド
    // メソッド名はInput Actionsで定義したアクション名(例: AttackLeft)に
    // "On" を付けたものにするか、後述のようにInspectorで手動設定する
    public void OnAttackLeft(InputAction.CallbackContext context)
    {
        // ボタンが押された瞬間 (started) の処理
        if (context.started)
        {
            Debug.Log("Attack button pressed (started)");
            // 押下開始時刻を記録
            buttonPressStartTime = Time.time;
            // ここで構えモーションや溜めエフェクト開始などの処理を入れることも可能
        }
        // ボタンが離された瞬間 (canceled) の処理
        else if (context.canceled)
        {
            Debug.Log("Attack button released (canceled)");
            // 押されていた時間を計算
            float pressDuration = Time.time - buttonPressStartTime;
            Debug.Log($"Press duration: {pressDuration} seconds");

            // 押下時間がしきい値を超えていたら溜め攻撃
            if (pressDuration > specialAttackThreshold)
            {
                PerformSpecialAttack(); // 溜め攻撃実行メソッド呼び出し
            }
            // しきい値未満なら通常攻撃
            else
            {
                PerformNormalAttack(); // 通常攻撃実行メソッド呼び出し
            }
        }
        // context.performed は Button タイプでは started とほぼ同じタイミングで呼ばれることが多い
        // Hold Interaction を使わない場合、主に started と canceled を使う
    }

    // 通常攻撃を実行する処理(中身は仮)
    private void PerformNormalAttack()
    {
        Debug.Log("Perform Normal Attack!");
        // ここに実際の通常攻撃ロジックを記述
    }

    // 溜め攻撃を実行する処理(中身は仮)
    private void PerformSpecialAttack()
    {
        Debug.Log("Perform Special Attack!");
        // ここに実際の溜め攻撃ロジックを記述
    }
}

5. `InputManager` GameObjectを選択し、InspectorウィンドウでPlayer Inputコンポーネントの「Behavior」を「Invoke Unity Events」に設定します。

6. 「Events」セクションが展開されるので、設定したAction Map名(例: `Gameplay`)を開き、その中のアクション名(例: `Attack Left`)に対応するイベント欄の「+」ボタンをクリックします。

7. イベント欄に`InputManager` GameObject自体をドラッグ&ドロップし、右側のドロップダウンメニューから「InputManager」>「OnAttackLeft (InputAction.CallbackContext)」を選択します。(スクリプトのメソッド名が `On[アクション名]` であれば自動で認識されることもあります)

これで、マウスの左ボタンがクリックされる(押される、または離される)たびに、`InputManager.cs`スクリプト内の`OnAttackLeft`メソッドが呼び出されるようになります。このイベント駆動の仕組みにより、`Update`関数を使うことなく入力処理を実現できます。


溜め時間の計算ロジック:startedとcanceledの活用

前述のスクリプト (`InputManager.cs`) 内の `OnAttackLeft` メソッドで行っている溜め時間の計算ロジックを詳しく見てみましょう。

  1. 押下開始 (context.started):
    • マウスの左ボタンが押された瞬間にこのブロックが実行されます。
    • 現在の時刻 (`Time.time`) を `buttonPressStartTime` 変数に記録します。これが溜め時間の計測開始点となります。
  2. 押下終了 (context.canceled):
    • 押されていた左ボタンが離された瞬間にこのブロックが実行されます。
    • 現在の時刻 (`Time.time`) から、記録しておいた押下開始時刻 (`buttonPressStartTime`) を引くことで、ボタンが押されていた時間 (`pressDuration`) を計算します。
    • 計算した `pressDuration` と、あらかじめ定義しておいた溜め攻撃のしきい値 (`specialAttackThreshold`) を比較します。
    • pressDuration がしきい値より長ければ `PerformSpecialAttack()` メソッドを、短ければ `PerformNormalAttack()` メソッドを呼び出します。

このように、Input Systemの `started` と `canceled` イベントを利用することで、ボタンが押されていた時間を正確に計測し、それに基づいて通常攻撃と溜め攻撃を振り分けることができます。`Update()` 関数内で毎フレーム時間を加算していく必要がないため、コードがシンプルになり、処理負荷も軽減される可能性があります。


中間演出の実装:コルーチンでInput Systemの弱点を補う

Input Systemのイベント駆動モデルは、「押した」「離した」といった瞬間のイベントを捉えるのは得意ですが、「ボタンを押し続けている間の特定のタイミング(例: 溜め攻撃が可能になる瞬間)」で何か処理を行いたい場合には、少し工夫が必要です。

例えば、「溜め時間がしきい値に達したらキャラクターを光らせる」「溜め完了のSEを鳴らす」といった演出を入れたい場合、`started` と `canceled` イベントだけでは、その「中間点」を直接検知できません。

この問題を解決する一般的な方法の一つが、Unityの「コルーチン (Coroutine)」を利用することです。ボタンが押された (`started`) 時点でコルーチンを開始し、一定時間(溜め攻撃のしきい値)が経過したら、溜め完了の合図(シグナル)を送る、という仕組みを作ります。

以下は、先ほどの `InputManager.cs` にコルーチンを追加し、溜め完了のシグナル(ここではDebugログ出力)を実装した例です。

using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections; // コルーチンのために必要

[RequireComponent(typeof(PlayerInput))]
public class InputManager : MonoBehaviour
{
    private float buttonPressStartTime;
    private const float specialAttackThreshold = 1.0f;
    // 実行中のコルーチンを保持する変数
    private Coroutine chargeCheckCoroutine;
    // 溜め完了シグナルが送られたかどうかのフラグ
    private bool isChargeComplete = false;

    public void OnAttackLeft(InputAction.CallbackContext context)
    {
        if (context.started)
        {
            Debug.Log("Attack button pressed (started)");
            buttonPressStartTime = Time.time;
            isChargeComplete = false; // 溜め開始時にフラグをリセット

            // もし既にコルーチンが動いていたら停止する(連打対策)
            if (chargeCheckCoroutine != null)
            {
                StopCoroutine(chargeCheckCoroutine);
            }
            // 溜め時間監視コルーチンを開始
            chargeCheckCoroutine = StartCoroutine(ChargeTimerCoroutine());
        }
        else if (context.canceled)
        {
            Debug.Log("Attack button released (canceled)");
            // ボタンが離されたら、溜め時間監視コルーチンを停止
            if (chargeCheckCoroutine != null)
            {
                StopCoroutine(chargeCheckCoroutine);
                chargeCheckCoroutine = null; // 保持しているコルーチン参照をクリア
            }

            float pressDuration = Time.time - buttonPressStartTime;
            Debug.Log($"Press duration: {pressDuration} seconds");

            // 溜め完了フラグが立っていれば(=しきい値を超えていれば)溜め攻撃
            if (isChargeComplete) // または pressDuration > specialAttackThreshold でも判定可能
            {
                PerformSpecialAttack();
            }
            else
            {
                PerformNormalAttack();
            }

            // 攻撃実行後にフラグをリセット
            isChargeComplete = false;
        }
    }

    // 溜め時間を監視するコルーチン
    private IEnumerator ChargeTimerCoroutine()
    {
        // しきい値の時間だけ待機
        yield return new WaitForSeconds(specialAttackThreshold);

        // しきい値に到達したら(かつボタンがまだ押されている場合)
        // isChargeComplete フラグを立て、溜め完了の合図を送る
        // ※ context.ReadValue() > 0 などでボタンが押され続けているか確認する方がより厳密
        Debug.Log("Charge Complete threshold reached!");
        isChargeComplete = true;

        // ここで溜め完了エフェクト(光る、SE鳴らすなど)をトリガーする
        TriggerChargeCompleteEffect();

        chargeCheckCoroutine = null; // コルーチン終了
    }

    private void PerformNormalAttack()
    {
        Debug.Log("Perform Normal Attack!");
        // 通常攻撃ロジック
    }

    private void PerformSpecialAttack()
    {
        Debug.Log("Perform Special Attack!");
        // 溜め攻撃ロジック
    }

    private void TriggerChargeCompleteEffect()
    {
         Debug.Log("Play Charge Complete Effect!");
        // 溜め完了時の演出処理(エフェクト表示、SE再生など)
    }
}

このコードでは、ボタンが押されたら `ChargeTimerCoroutine` を開始し、`specialAttackThreshold` 秒後に `isChargeComplete` フラグを `true` にして、溜め完了演出メソッド `TriggerChargeCompleteEffect()` を呼び出します。ボタンが離された (`canceled`) 時点で、このフラグが `true` になっているかどうかで溜め攻撃か通常攻撃かを判断します。

このようにコルーチンを組み合わせることで、Input Systemのイベント駆動のメリットを活かしつつ、溜め攻撃における中間的なタイミングでの処理も実現できます。一見すると `Update` で実装するより複雑に感じるかもしれませんが、入力の種類が増えたり、他のアクションとの連携が必要になったりした場合、Input SystemのAction Mapやイベントによる責務分離がコード全体の整理に役立ちます。


まとめ:Pressイベント方式のメリット・デメリットと使い分け

UnityのInput SystemにおけるPressイベント (started / canceled) を利用することで、イベント駆動に基づいた溜め攻撃の実装が可能になることを見てきました。

メリット:

  • Update関数を使わずに済み、コードがイベント単位で整理されやすい。
  • 押下開始・終了のタイミングを正確に捉えられる。
  • 短押し/長押しの判定や、それに応じた処理の分岐が比較的容易。
  • Input Actionsアセットによる入力マッピング管理が直感的で、キーコンフィグなどへの拡張性が高い。

デメリット(考慮点):

  • 「押され続けている間の特定のタイミング」での処理(溜め完了演出など)には、コルーチンなどの補完的な仕組みが必要になる。
  • 単純な溜め時間計測だけなら、Update関数で実装する方がシンプルに感じる場合もある。

どちらを選ぶべきか?

最終的な実装方法は、プロジェクトの規模や要件、開発チームの好みによって異なります。

  • 小規模なプロジェクトやプロトタイプで、溜め攻撃以外の入力が少ない場合は、Update関数によるシンプルな実装でも十分かもしれません。
  • 中規模以上のプロジェクトで、多様な入力(ゲームパッド対応、キーコンフィグなど)や、他のアクションとの連携、将来的な拡張性・保守性を重視する場合は、Input SystemのPressイベント + コルーチン等による補完という組み合わせが有力な選択肢となるでしょう。Hold Interactionも選択肢ですが、本記事で解説したような柔軟性を求める場合はPressイベント方式が有利な場面があります。

Input Systemは学習コストが少しありますが、慣れれば強力な武器になります。ぜひ、ご自身のプロジェクトに合った方法で溜め攻撃の実装に挑戦してみてください。

【Unity】シーン遷移はインスペクタのリスト選択で!SceneAsset+OnValidateで安全&効率UP

Unityでシーンを切り替える際、SceneManager.LoadScene("シーン名") を使うのが基本的な方法です。しかし、シーン名を直接文字列で指定するこの方法は、シーンファイルの名前を変更した際の修正漏れや、単純なタイプミスによるエラーの原因となりやすく、プロジェクトが大きくなるにつれて保守性を低下させる一因にもなります。

この記事では、こうした文字列指定のリスクを回避し、より安全で効率的にシーン遷移を実装するためのテクニックとして、UnityEditor.SceneAssetOnValidateメソッドを組み合わせる方法を紹介します。この方法を使えば、Unityエディタ上でインスペクタから直感的に遷移先のシーンを選択でき、かつビルド後も問題なく動作するため、日々の開発効率とコードの安全性を高めることができます。


この記事の内容

  1. 従来のシーン遷移(文字列指定)の問題点
  2. 基本的な実装方法(文字列指定)
  3. 改良版:SceneAssetとOnValidateを使った実装
  4. 少し詳しい技術解説
  5. その他のシーン管理アプローチ
  6. まとめ:安全なシーン遷移で開発効率を上げよう

従来のシーン遷移(文字列指定)の問題点

なぜ文字列指定は危険なのか?

SceneManager.LoadScene("MyScene") のようにシーン名を文字列で直接コードに書き込む方法は、一見シンプルですが、以下のような問題を引き起こしがちです。

  • シーンファイル名の変更に弱い: Projectウィンドウでシーンファイルの名前を変更しても、コード内の文字列は自動で更新されません。関連する全てのコードを手動で修正する必要があり、漏れが発生しやすいです。
  • タイプミス(Typo)によるエラー: “MyScene” を “MyScen” など、少しでも間違えて入力すると、実行時にシーンが見つからずエラーになります。コンパイル時にはエラーが出ないため、発見が遅れることがあります。
  • 管理の煩雑化: プロジェクト内のシーン数が増えるにつれて、どこでどのシーン名を文字列で使っているか把握するのが難しくなり、コードの保守性が低下します。

これらの問題は、特にチーム開発や長期にわたるプロジェクトでは、無視できない開発コストの増加につながります。

解決策:SceneAsset + OnValidate の概要

そこで役立つのが、UnityEditor.SceneAssetOnValidate メソッドを組み合わせたテクニックです。この方法には以下のメリットがあります。

  • インスペクタからシーンを選択可能に: SceneAsset 型の変数をスクリプトに用意することで、インスペクタ上でシーンファイルを直接ドラッグ&ドロップして指定できます。これにより、文字列の手入力が不要になり、タイプミスを防げます。
  • ビルド後も安全に動作: SceneAsset はエディタ専用ですが、OnValidate メソッドを使って、エディタでシーンが選択された瞬間にそのシーン名を内部的に文字列として保持します。これにより、ビルド後の実行環境でも正しいシーン名を扱えるようになります。
  • 保守性と開発効率の向上: シーン名の変更があっても、インスペクタで再設定するだけで済み、コード修正の手間が大幅に減ります。

基本的な実装方法(文字列指定)

まずは比較のために、従来の文字列指定による基本的なシーン遷移の実装を見てみましょう。

通常のシーン遷移コード例

以下は、特定のGameObject(例: ゴール地点)にプレイヤーが接触した際に、指定した名前のシーンに遷移するシンプルなスクリプトです。

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTransitionTrigger : MonoBehaviour
{
    // インスペクタで遷移先のシーン名を入力する
    [SerializeField] private string sceneToLoad;

    private void OnTriggerEnter2D(Collider2D other)
    {
        // 接触したのがプレイヤーだったら
        if (other.CompareTag("Player"))
        {
            // 文字列で指定したシーンをロード
            SceneManager.LoadScene(sceneToLoad);
        }
    }
}

このスクリプトをアタッチしたGameObjectのインスペクタには、`Scene To Load` という文字列入力フィールドが表示されます。

基本的なシーン遷移コードのインスペクタ表示(文字列入力)

ここに遷移したいシーンの名前(例: “Stage2″)を正確に入力する必要があります。

【重要】Build Settingsへのシーン追加

非常に重要な注意点として、SceneManager.LoadScene でロードするシーンは、必ず事前に Build Settings に追加しておく必要があります。これを行わないと、エディタ上では動作してもビルド後にシーンが見つからずエラーになります。

  1. Unityメニューの File > Build Settings… を開きます。
  2. 「Scenes In Build」のリストに、遷移先のシーンファイル(Projectウィンドウから)をドラッグ&ドロップして追加します。

Build Settingsにシーンファイルを追加する画面

(↑ここにリストアップされているシーンのみ、ビルド後もロード可能です)


改良版:SceneAssetとOnValidateを使った実装

それでは、文字列指定の問題点を解決する改良版の実装を見ていきましょう。

SceneAssetを利用したコード例

以下のスクリプトでは、インスペクタ上にSceneAsset型のフィールドを用意し、そこにシーンファイルを直接ドラッグ&ドロップできるようにします。そして、OnValidateメソッドを使って、選択されたシーンの名前を内部的な文字列変数に自動でコピーします。

改良版コードのインスペクタ表示(SceneAsset選択フィールド)

(↑インスペクタでシーンファイルを直接アタッチできるように!)

using UnityEngine;
using UnityEngine.SceneManagement;
// UnityEditor 名前空間はエディタ専用機能を使う場合に必要
#if UNITY_EDITOR
using UnityEditor;
#endif

public class SceneLoaderSafe : MonoBehaviour
{
    // [HideInInspector] 実行時にはこの文字列だけあれば良いのでインスペクタからは隠す
    [HideInInspector]
    [SerializeField] private string sceneToLoad;

// #if UNITY_EDITOR ~ #endif で囲まれた部分はエディタ上でのみ有効になる
#if UNITY_EDITOR
    // インスペクタに表示するためのSceneAsset型変数
    [Header("遷移先シーン選択")] // インスペクタに見出しを表示
    [SerializeField] private SceneAsset sceneAsset; // ここにシーンファイルをD&Dする
#endif

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            // ロード時は保持しておいた文字列変数を使う
            if (!string.IsNullOrEmpty(sceneToLoad))
            {
                SceneManager.LoadScene(sceneToLoad);
            }
            else
            {
                Debug.LogError("遷移先のシーン名が設定されていません!");
            }
        }
    }

// OnValidateメソッドもエディタ専用
#if UNITY_EDITOR
    // インスペクタで値が変更された時などに自動で呼ばれるメソッド
    private void OnValidate()
    {
        // sceneAssetフィールドにシーンが設定されたら
        if (sceneAsset != null)
        {
            // そのシーンの名前(文字列)を sceneToLoad 変数にコピーする
            sceneToLoad = sceneAsset.name;
        }
        else
        {
            // SceneAssetが未設定なら文字列も空にする
            sceneToLoad = "";
        }
    }
#endif
}

コードのポイント:

  • using UnityEditor;#if UNITY_EDITOR / #endif: SceneAssetOnValidate はエディタ専用機能なので、これらを使ってビルドエラーを防ぎます。
  • [SerializeField] private SceneAsset sceneAsset;: インスペクタにシーンファイルを設定するためのフィールドです。
  • [HideInInspector] [SerializeField] private string sceneToLoad;: 実際にSceneManager.LoadSceneで使うシーン名を格納する文字列変数。インスペクタからは隠しておきます。
  • private void OnValidate(): インスペクタでsceneAssetが変更されるたびに呼び出されます。ここでsceneAsset.name(選択されたシーンの名前)を取得し、sceneToLoad変数に代入します。
  • SceneManager.LoadScene(sceneToLoad);: 実際のシーンロード時には、OnValidateによって事前に設定された文字列sceneToLoadを使用します。

なぜOnValidateが必要なのか?ビルドエラー回避の鍵

「インスペクタでSceneAssetを直接指定できるなら、OnValidateでわざわざ文字列に変換しなくても、SceneManager.LoadScene(sceneAsset.name)で直接ロードすれば良いのでは?」と疑問に思うかもしれません。

しかし、それはできません。なぜなら、UnityEditor.SceneAssetはUnityエディタ専用のクラスであり、ビルドされたゲーム内では利用できないからです。もしOnValidateを使わずにsceneAsset.nameを直接ロードしようとすると、エディタ上では動作しても、ビルドしたゲームを実行すると「UnityEditor名前空間が見つからない」というエラーが発生し、シーン遷移が失敗してしまいます。

OnValidateメソッドは、まさにこの問題を解決するための「橋渡し役」です。エディタ上でSceneAssetが選択・変更されるたびにOnValidateが自動的に実行され、その瞬間にシーン名を文字列としてsceneToLoad変数に保存してくれます。このsceneToLoad変数は通常の文字列なので、ビルド後のゲームでも問題なく参照できます。

つまり、OnValidateを使うことで、「エディタ上での安全で直感的なシーン選択(SceneAsset)」と「ビルド後も確実に動作するシーンロード(文字列)」という、両方のメリットを実現できるのです。


少し詳しい技術解説

UnityEditor.SceneAsset とは?

UnityEditor.SceneAssetクラスは、Unityエディタ内でシーンファイル(.unity)そのものをアセットとして参照するためのものです。インスペクタ上でシーンファイルをドラッグ&ドロップで設定できるため、視覚的で間違いが起こりにくいインターフェースを提供します。ただし、前述の通り、UnityEditor名前空間に属しており、ビルド後の実行ファイルには含まれません。

参考:Unity Documentation: UnityEditor.SceneAsset (※リンク先は古いバージョンの場合があるため、お使いのUnityバージョンに合わせて確認してください)

OnValidateメソッドの役割

OnValidate()は、MonoBehaviourを継承したクラス内で定義できる特殊なメソッドの一つです。これは、スクリプトがロードされたとき、またはインスペクタで値が変更されたときに、エディタ上でのみ呼び出されます。この性質を利用することで、インスペクタでの設定変更をトリガーにして、何らかの処理(今回の場合はシーン名の文字列への変換・保存)を自動的に行うことができます。

参考:Unity Documentation: MonoBehaviour.OnValidate()

エディタでの選択からビルド後の動作までの流れ

この改良版の実装における処理の流れを整理すると、以下のようになります。

  1. 開発時(エディタ上):
    1. 開発者は、スクリプトのインスペクタでsceneAssetフィールドに遷移したいシーンファイル(例: `Stage2.unity`)をドラッグ&ドロップする。
    2. 値が変更されたため、OnValidate()メソッドが自動的に実行される。
    3. OnValidate()内で、sceneAsset.name(この場合は “Stage2″)が取得され、sceneToLoad変数に文字列 “Stage2” が保存される。
  2. 実行時(ビルド後):
    1. プレイヤーがトリガーに接触する。
    2. OnTriggerEnter2Dメソッドが実行される。
    3. SceneManager.LoadScene(sceneToLoad) が呼び出される。この時、sceneToLoadにはエディタで設定された文字列 “Stage2” が格納されている。
    4. シーン “Stage2” が正常にロードされる。

このように、エディタ専用の機能と実行時にも有効なデータをOnValidateが繋ぐことで、安全で効率的なシーン遷移が実現します。


その他のシーン管理アプローチ

今回紹介したSceneAsset + OnValidate方式以外にも、Unityのシーン遷移を管理・実装する方法はいくつか存在します。

  • シーン名を定数やenum(列挙型)で管理する: シーン名をコード内で定数として定義したり、enum型で管理したりする方法。文字列のハードコーディングは避けられますが、シーン追加・削除時に定数やenumの定義も更新する必要があります。
  • ScriptableObjectでシーンリストを管理する: 遷移可能なシーンのリストをScriptableObjectアセットとして作成・管理する方法。中央集権的に管理できますが、設定の手間はかかります。
  • Addressable Asset Systemを使用する: シーン自体をAddressableアセットとして扱い、アドレス(文字列キー)でロードする方法。非同期ロードや動的なコンテンツ配信に適していますが、システム自体の学習コストがかかります。

それぞれにメリット・デメリットがありますが、今回紹介したSceneAsset + OnValidate方式は、特別なアセットや複雑なシステムを導入することなく、比較的手軽に実装でき、かつエディタ上での利便性とビルド後の安全性を両立できる点で、特に小~中規模のプロジェクトにおいて有効な選択肢の一つと言えるでしょう。


まとめ:安全なシーン遷移で開発効率を上げよう

Unityにおけるシーン遷移で、シーン名を文字列で直接指定する方法は、タイプミスやシーン名変更時の修正漏れといったリスクを伴い、プロジェクトの保守性を低下させる可能性があります。

この記事で紹介したUnityEditor.SceneAssetOnValidateメソッドを組み合わせる方法は、これらの問題を解決する効果的なアプローチです。

  • インスペクタからシーンファイルを直接選択できるため、タイプミスを防ぎ、設定が直感的になります。
  • OnValidateによって、エディタでの選択情報がビルド後も有効な文字列データとして保持されるため、安全なシーンロードが可能です。
  • シーン名の変更が必要になった場合も、インスペクタでシーンファイルを再設定するだけで済み、コード修正の手間が大幅に削減されます。

これにより、シーン遷移に関するエラーのリスクを低減し、開発効率とコードの保守性を向上させることが期待できます。シーン数が増えてきて管理が煩雑になってきたと感じたら、ぜひこの実装方法を試してみてください。

【Unity】マウス位置のTilemap座標を表示するCoordinate Brushの使い方と導入手順

Coordinate Brush使用中のUnityエディタ画面。マウス位置に座標が表示されている。

Unityで2Dゲームを開発する際、Tilemapを使ってマップを作成することは多いですよね。タイルを配置したり、イベントを設定したりする中で、「今マウスカーソルがある場所の座標っていくつだろう?」と正確な位置を知りたくなる場面は少なくありません。

そんな時に非常に便利なのが「Coordinate Brush」というツールです。このブラシを使うと、Tile Paletteウィンドウ上でマウスを動かすだけで、その位置に対応するTilemap座標をリアルタイムに表示してくれます。

この記事では、この便利なCoordinate Brushの機能と、現在のUnityバージョンでの導入方法(少し注意が必要です!)、そして基本的な使い方について詳しく解説します。


この記事の内容

  1. Coordinate Brushとは?
  2. 主な機能と利点
  3. 導入方法(※重要:現在の導入手順)
  4. Coordinate Brushの基本的な使い方
  5. トラブルシューティング
  6. まとめ:座標確認を効率化しよう

Coordinate Brushとは?

Coordinate Brushは、UnityのTilemapシステムで利用できるカスタムブラシの一種です。元々はUnity公式が提供する「2D Tilemap Extras」という便利な拡張機能パッケージに含まれていました。

その主な機能は、Tile Paletteウィンドウ上でタイルマップにマウスカーソルを合わせた際に、そのカーソル位置のセル座標(Tilemap座標)をリアルタイムで表示することです。

下の画像のように、マウスカーソルのすぐそばに (X, Y) 形式で座標が表示されるため、タイルの正確な配置や、イベント発生位置の確認などが格段にしやすくなります。

Coordinate Brush使用中のUnityエディタ画面。マウス位置に座標が表示されている。

主な機能と利点

Coordinate Brushを使用する主なメリットは以下の通りです。

  • リアルタイム座標表示: マウスを動かすだけでTilemap上の座標を即座に確認できます。
  • 正確な位置把握: グリッドに沿った正確な座標がわかるため、タイルの精密な配置が容易になります。
  • イベント・オブジェクト配置補助: 特定の座標にイベントやオブジェクトを配置したい場合に、目的の座標を素早く見つけることができます。
  • デバッグ・レベルデザイン効率化: マップ編集中やデバッグ時に、座標の確認作業にかかる時間を大幅に短縮できます。

導入方法(※重要:現在の導入手順)

Coordinate Brushの導入方法は、過去と現在で少し異なります。以前はこのツールが含まれていた「2D Tilemap Extras」パッケージの扱いが変わったため、注意が必要です。

注意点:2D Tilemap Extrasパッケージの現状

かつてCoordinate Brushは「2D Tilemap Extras」パッケージの一部として提供されていました。しかし、Unity公式ドキュメントによると、このパッケージのバージョン3.1.0以降では、Coordinate Brushは標準の機能セットから除外されています。(現在はGameObject Brush, Group Brush, Line Brush, Random Brushなどが標準に含まれています)

参考: 2D Tilemap Extras v3.1.0 Manual

そのため、Package Managerから最新の「2D Tilemap Extras」をインストールしただけでは、Coordinate Brushは利用できません。

Coordinate Brush本体の導入 (GitHubから)

最新のUnity環境でCoordinate Brushを使用するには、以下の手順でGitHubリポジトリから直接スクリプトファイルを導入する必要があります。

  1. 以下のリンクから、Unity公式が管理する2d-extrasのGitHubリポジトリにあるCoordinateBrush.csファイルを開きます。
    GitHub: Unity-Technologies/2d-extras/…/CoordinateBrush.cs
  2. ページ右側にある「Raw」ボタンを右クリックし、「名前を付けてリンク先を保存」などを選択して、CoordinateBrush.csファイルをダウンロードします。(または、コード全体をコピーして、手動で同名のファイルを作成します)
  3. ダウンロードしたCoordinateBrush.csファイルを、お使いのUnityプロジェクトのAssetsフォルダ内にあるEditorフォルダに配置します。(もしEditorフォルダがなければ、Assetsフォルダ直下に新規作成してください)
  4. ファイルを配置したら、Unityエディタが自動的にスクリプトをコンパイルします。もし自動で認識されない場合は、Unityエディタを再起動してみてください。

これで、Coordinate Brushがプロジェクトに導入され、使用できる状態になります。

Coordinate Brushの基本的な使い方

Coordinate Brushの使い方は非常にシンプルです。

  1. Unityエディタのメニューから Window > 2D > Tile Palette を選択し、Tile Paletteウィンドウを開きます。
  2. Tile Paletteウィンドウ上部にあるブラシ選択のドロップダウンメニュー(デフォルトでは標準のブラシアイコンが表示されている場所)をクリックします。
  3. ブラシのリストの中に「Coordinate Brush」が表示されているはずなので、それを選択します。
  4. Tile Paletteウィンドウ上で、Tilemapが存在するシーンビューにマウスカーソルを移動させます。
  5. すると、マウスカーソルの位置に合わせて、Tilemapのセル座標がリアルタイムで表示されます。

これで、タイルを配置したい場所や確認したい場所の座標を簡単に把握できます。

トラブルシューティング

Coordinate Brushがうまく動作しない場合に考えられる原因と対処法です。

  • ブラシリストにCoordinate Brushが表示されない:
    • CoordinateBrush.csファイルが、プロジェクト内のEditorフォルダに正しく配置されているか確認してください。
    • ファイルを配置した後、Unityエディタを再起動してみてください。
  • ブラシを選択しても座標が表示されない / エラーが出る:
    • CoordinateBrush.csスクリプトにコンパイルエラーが発生していないか、Unityエディタのコンソールウィンドウを確認してください。(GitHubから正しくダウンロード・コピーできていない可能性があります)
    • Unityのバージョンと互換性がない可能性もゼロではありませんが、通常は比較的新しいバージョンでも動作するはずです。
  • 古い2D-Extrasパッケージとの混同: もし過去にPackage Managerから古いバージョンの2D Tilemap Extrasをインストールしていた場合、予期せぬ競合が起きる可能性も考えられます。不要であれば古いパッケージは削除し、GitHubから最新のスクリプトを導入する方法に統一することをお勧めします。

まとめ:座標確認を効率化しよう

Coordinate Brushは、UnityのTilemapを使った2Dゲーム開発において、マップ上の座標をリアルタイムで確認できる非常に便利なエディタ拡張ツールです。

現在はUnityの標準パッケージには含まれていませんが、GitHubからスクリプトファイルを導入することで、最新のUnity環境でも利用可能です。

正確なタイル配置が求められるレベルデザイン時や、特定の座標にイベントを設定したい場合、あるいはデバッグ時にオブジェクトの位置を確認したい場合など、様々な場面で開発効率を向上させてくれます。導入も簡単なので、Tilemapを活用している方はぜひ試してみてはいかがでしょうか。

【Unity】Live2Dモーションに組み込んだイベントのタイミングで効果音を鳴らす方法

Live2Dで作成したキャラクターアニメーション(モーション)には、タイムライン上の特定のフレームに「イベント」というマーカーのようなものを埋め込む機能があります。このイベントは、Unityにモデルをインポートした後、モーション再生中の特定のタイミングでUnity側の処理(例えば効果音(SE)の再生、エフェクトの表示など)を呼び出すトリガーとして活用できます。

この記事では、Live2D Cubism Editorでモーションにイベントを設定し、それをUnityプロジェクトにインポート、そして設定したイベントのタイミングで効果音を鳴らすための具体的な手順と、さらに効率化するためのTipsを解説します。


この記事で解説する手順

  1. [Live2D] モーションデータ内にイベントを作成・設定する
  2. [Live2D] イベント情報を含めてモーションデータを書き出す
  3. [Unity] モデルとモーションファイルをUnityにインポートする
  4. [Unity] イベントを受け取り効果音を再生するスクリプトと設定
  5. [Unity] SDKスクリプト編集でイベント関数名の自動設定(効率化Tips)
  6. オススメ参考情報

[Live2D] モーションデータ内にイベントを作成・設定する

まず、Live2D Cubism Editorのアニメーション作成画面(タイムライン)を開き、効果音を鳴らしたいフレーム(タイミング)にイベントを設定します。

  1. タイムライン上で、イベントを設定したいパラメータグループ(例: `Expression`など、どこでも可)を選択します。
  2. 効果音を鳴らしたいフレームに再生ヘッド(赤い縦線)を移動させます。
  3. そのフレーム上で右クリックし、「イベントの設定」を選択します。(または、タイムライン上部のイベント用トラックで直接右クリック)

Live2D Editorのタイムラインでイベントを設定する

すると、イベントの文字列を入力するダイアログが表示されます。

Live2Dイベント設定の入力ダイアログ

ここに入力した文字列は、後でUnityにインポートした際に、モーション(AnimationClip)内のAnimationEventstringParameter(文字列パラメータ)として渡されます。

そのため、単に「SE」と入力するだけでなく、再生したい効果音を識別するためのIDや名前(例: `Footstep`, `AttackSE_01`, `Voice_Happy`など)を設定するのがおすすめです。こうすることで、Unity側のスクリプトでこの文字列を受け取り、PlaySFX(audioID) のように、再生する効果音を動的に切り替えることが可能になります。

必要なタイミングにイベントを設定していきましょう。タイムライン上に黄色いマーカーとして表示されます。

タイムライン上に設定されたイベントマーカー

[Live2D] イベント情報を含めてモーションデータを書き出す

イベントの設定が完了したら、モーションデータをUnityで読み込める形式(.motion3.json)で書き出します。

  1. Live2D Editorのメニューから「ファイル」>「組み込み用ファイル書き出し」>「モーションファイル書き出し」を選択します。
  2. 書き出し設定ダイアログが表示されます。
  3. 【重要】 設定項目の中にある「イベントを書き出し」のチェックボックスに必ずチェックを入れてください。これを忘れると、せっかく設定したイベント情報がファイルに含まれず、Unity側で受け取ることができません。

Live2Dモーションファイル書き出しメニュー

モーション書き出し設定で「イベントを書き出し」にチェックを入れる

設定を確認したら「OK」を押し、.motion3.jsonファイルを書き出します。

[Unity] モデルとモーションファイルをUnityにインポートする

Live2Dモデル本体のデータ(.model3.jsonファイルやテクスチャ等が含まれるフォルダ)と、先ほど書き出したモーションファイル(.motion3.json)をUnityプロジェクトにインポートします。

通常は、これらのファイルやフォルダをUnityのProjectウィンドウにドラッグ&ドロップするだけで、Live2D Cubism SDK for Unityが自動的に処理を行い、モデルのPrefabやモーションのAnimationClipなどが生成されます。

(Live2D Cubism SDK for Unityがプロジェクトに導入されていない場合は、先に導入が必要です)

参考: SDKをインポートしてモデルを表示する – Live2D公式ドキュメント

[Unity] イベントを受け取り効果音を再生するスクリプトと設定

Unityにモーションファイルをインポートすると、Live2D Editorで設定したイベントは、生成されたAnimationClip内の「Animation Event」として自動的に変換・登録されます。

確認してみましょう。生成されたAnimationClipアセットを選択し、Animationウィンドウ(またはInspectorウィンドウのEventsセクション)を開きます。

UnityのAnimationウィンドウで生成されたAnimationEventを確認

タイムライン上に、Live2Dで設定したタイミングでイベントマーカーが表示されているはずです。そのマーカーを選択すると、詳細を確認できます。

AnimationEventの詳細:Stringパラメータにイベント文字列が設定されている

String」パラメータには、Live2D Editorでイベントに設定した文字列(例: `Footstep`)が正しく入っていることがわかります。

しかし、このままではまだ効果音は鳴りません。なぜなら、「Function」欄が空欄だからです。Animation Eventは、指定された関数(Function Name)を、そのイベントを持つAnimatorコンポーネントと同じGameObjectにアタッチされているスクリプトから呼び出す仕組みになっています。FunctionNameが空だと、どの関数を呼び出せば良いか分からず、何も実行されません。

そこで、以下の手順で効果音再生用のスクリプトを作成し、設定を行います。

  1. 効果音再生用スクリプトを作成: 以下のような、文字列を引数として受け取り、対応する効果音を再生する関数を持つC#スクリプトを作成します(内容はあくまで例です。ご自身のプロジェクトの効果音再生システムに合わせて実装してください)。
    using UnityEngine;
    
        public class Live2DSoundPlayer : MonoBehaviour
        {
            // 効果音のAudioSourceなどを設定(Inspectorなどから)
            public AudioSource audioSource;
            // 効果音クリップの管理方法(例: Dictionaryや配列など)は適宜実装
            // public AudioClip footstepClip;
            // public AudioClip attackClip;
    
            /// 
            /// AnimationEventから呼び出される関数。
            /// 引数(audioID)にはAnimationEventのStringパラメータが渡される。
            /// 
            /// 再生したい効果音のID(Live2Dイベントで設定した文字列)
            public void PlaySFX(string audioID)
            {
                Debug.Log("PlaySFX called with ID: " + audioID);
                // audioIDに基づいて再生するAudioClipを決定し、再生する処理
                // 例:
                // if (audioID == "Footstep" && footstepClip != null)
                // {
                //     audioSource.PlayOneShot(footstepClip);
                // }
                // else if (audioID == "AttackSE_01" && attackClip != null)
                // {
                //     audioSource.PlayOneShot(attackClip);
                // }
                // ... など
            }
        }
        

  2. スクリプトをアタッチ: 作成した効果音再生用スクリプト(例: `Live2DSoundPlayer.cs`)を、Live2DモデルのPrefabのルートGameObjectAnimatorコンポーネントがアタッチされているGameObject)に追加(アタッチ)します。

    【重要】Animatorと同じGameObjectにアタッチしないと、Animation Eventが関数を見つけられません!
  3. FunctionNameを手動設定: 再度、モーションのAnimationClipをAnimationウィンドウで開きます。各Animation Eventマーカーを選択し、Inspectorウィンドウの「Function」欄に、作成したスクリプト内の関数名(例: `PlaySFX`)を手動で入力します。

これで、モーションが再生され、イベントが設定されたフレームに到達すると、指定した`PlaySFX`関数が呼び出され、引数としてLive2Dで設定した文字列(効果音ID)が渡されるようになります。関数内でそのIDに応じた効果音を再生する処理を実装すれば、目的の動作が実現します。

[Unity] SDKスクリプト編集でイベント関数名の自動設定(効率化Tips)

前のステップで、Animation EventのFunctionNameを手動で設定する方法を説明しましたが、イベントの数が多い場合や、Live2D側でモーションを修正して再インポートするたびに設定し直すのは非常に手間がかかります。

Live2D Editor側でイベントの文字列(パラメータ)は設定できますが、呼び出す関数名(FunctionName)を直接指定することはできません。しかし、Unity側でモーションファイル(.motion3.json)をインポートする際の処理をカスタマイズすることで、FunctionNameを自動的に設定させることが可能です。

これを行うには、Live2D Cubism SDK for Unityに含まれるスクリプトファイルを編集します。

  1. UnityのProjectウィンドウで、以下のパスにあるスクリプトファイルを探して開きます。
    Packages/Live2D Cubism/Framework/Json/CubismMotion3Json.cs
    (※SDKのバージョンやインストール方法によってパスが異なる場合があります。Assetsフォルダ内に直接インポートしている場合はそちらを探してください)
  2. スクリプト内でToAnimationClipメソッドの中を探し、AnimationEventを生成している箇所を見つけます。(new AnimationEvent と書かれているあたりです)
  3. そのAnimationEventを生成するコードに、functionNameの行を追加(または編集)し、呼び出したい関数名(例: `”PlaySFX”`)を直接文字列で指定します。
    // CubismMotion3Json.cs の ToAnimationClip メソッド内の一部を編集
    // (元のコード構造に合わせて編集してください)
    
    var animationEvent = new AnimationEvent
    {
        time = UserData[i].Time, // イベントの発生時間
        // ここに呼び出したい関数名を直接記述!
        functionName = "PlaySFX",
        stringParameter = UserData[i].Value, // Live2Dで設定したイベント文字列
    };
    

  4. スクリプトを保存します。

この変更により、今後.motion3.jsonファイルをUnityにインポート(または既存のファイルを右クリックして「Reimport」)した際に、生成されるAnimationEventFunctionNameフィールドに、指定した関数名(この例では “PlaySFX”)が自動的に設定されるようになります。これにより、手動でFunctionNameを入力する手間が省け、モーション更新時の作業も大幅に効率化されます。

【注意】SDKのスクリプトを直接編集する方法は、SDKのアップデート時に変更が上書きされてしまう可能性があるため、その点は留意が必要です。変更箇所を記録しておくか、可能であればより安全なカスタマイズ方法(例えば、インポート後の後処理スクリプトを作成するなど)を検討するのも良いでしょう。

参考: motion3.jsonに設定されたイベントを取得する – Live2D公式ドキュメント

オススメ参考情報

今回の実装にあたり、以下の情報が大変参考になりました。

【Unity】ゲームの画面サイズを1080pと720pで切り替えられるようにする Screen.SetResolution

Unityで開発中のゲームに、プレイヤーがグラフィック設定を変更できるオプション、特に「画面解像度」の切り替え機能を実装したい、と考えたことはありませんか? フルHD(1920×1080)で高画質を楽しみたいユーザーもいれば、PCスペックに合わせて少し軽い解像度(例: 1280×720)でプレイしたいユーザーもいます。こうしたニーズに応えるために、Unityでは Screen.SetResolution という便利な関数が用意されています。

この関数を使うことで、ゲームの実行中にプログラムから画面の解像度やフルスクリーン/ウィンドウモードを動的に変更することが可能です。

この記事では、Screen.SetResolution 関数の基本的な使い方から、UIボタンを使った具体的な実装例、そして使用する上での注意点やプラットフォーム別の挙動、UIスケーリングへの配慮といった実践的なTIPSまでを分かりやすく解説します。


この記事の内容

  1. はじめに:なぜ解像度変更機能が必要か?
  2. サンプル実装:解像度切り替えボタンの作り方
    1. C#スクリプト例 (ResolutionChangeButton.cs)
    2. UIボタンへのスクリプト割り当て方法
  3. Screen.SetResolution関数の詳細解説
    1. パラメータ (width, height, fullscreen) の意味
    2. ウィンドウモードとフルスクリーンモードの制御
    3. 解像度がうまく変更されない場合のチェックポイント
  4. 実装時のTIPSと注意点
    1. ターゲットプラットフォームによる挙動の違い
    2. アスペクト比の変化とUIスケーリングへの配慮
    3. より使いやすい解像度変更メニューのアイデア
  5. 参考資料
  6. まとめ:Screen.SetResolutionで快適なプレイ環境を提供

はじめに:なぜ解像度変更機能が必要か?

プレイヤーが使用するモニターの解像度やPCのスペックは様々です。開発者が意図した解像度だけでなく、プレイヤーが自身の環境に合わせて最適な画面サイズを選択できるようにすることは、ゲームのアクセシビリティと快適性を向上させる上で重要です。例えば、高解像度モニターを持つユーザーには鮮明な映像を提供し、一方でスペックが低いPCのユーザーには解像度を下げることでフレームレートを安定させる、といった選択肢を与えることができます。Unityの Screen.SetResolution は、これを実現するための基本的な機能を提供します。

サンプル実装:解像度切り替えボタンの作り方

ここでは、最もシンプルな例として、「1920×1080 (フルHD)」と「1280×720 (HD)」の2つの解像度に切り替えるためのUIボタンを実装する方法を紹介します。

C#スクリプト例 (ResolutionChangeButton.cs)

まず、解像度を変更するためのC#スクリプトを作成します。

using UnityEngine;

public class ResolutionChangeButton : MonoBehaviour
{
    /// <summary>
    /// 指定された幅と高さで解像度を設定する共通メソッド
    /// </summary>
    /// <param name="width">設定したい幅 (ピクセル)</param>
    /// <param name="height">設定したい高さ (ピクセル)</param>
    public void SetResolution(int width, int height)
    {
        // Screen.SetResolution(幅, 高さ, フルスクリーンかどうか);
        // 第3引数に Screen.fullScreen を渡すと、現在のフルスクリーン状態を維持したまま解像度を変更します。
        // フルスクリーンにしたければ true、ウィンドウモードにしたければ false を指定します。
        Screen.SetResolution(width, height, Screen.fullScreen);
        Debug.Log($"Resolution set to: {width}x{height}, Fullscreen: {Screen.fullScreen}");
    }

    /// <summary>
    /// 解像度を1920x1080に設定するメソッド (ボタンから呼び出す用)
    /// </summary>
    public void SetResolution1080p()
    {
        SetResolution(1920, 1080);
    }

    /// <summary>
    /// 解像度を1280x720に設定するメソッド (ボタンから呼び出す用)
    /// </summary>
    public void SetResolution720p()
    {
        SetResolution(1280, 720);
    }

    // --- 必要に応じて他の解像度用のメソッドも追加 ---
    // public void SetResolutionYourChoice()
    // {
    //     SetResolution(yourWidth, yourHeight);
    // }

    // --- フルスクリーン切り替えメソッドの例 ---
    // public void ToggleFullscreen()
    // {
    //     Screen.fullScreen = !Screen.fullScreen;
    //     // 解像度は現在のものを維持する場合
    //     // Screen.SetResolution(Screen.currentResolution.width, Screen.currentResolution.height, Screen.fullScreen);
    //     Debug.Log($"Fullscreen toggled: {Screen.fullScreen}");
    // }
}

このスクリプトは、特定の解像度を設定するメソッド(SetResolution1080p, SetResolution720p)と、それらの内部で共通して呼ばれるSetResolutionメソッドで構成されています。

UIボタンへのスクリプト割り当て方法

次に、作成したスクリプトをUnityのUIボタンに割り当てます。

  1. Unityエディタで、解像度変更用のUIボタン(Buttonコンポーネントを持つGameObject)を2つ作成します(例: 「1080p Button」、「720p Button」)。
  2. 空のGameObjectを作成し(例: `ResolutionManager`)、それに先ほど作成したResolutionChangeButton.csスクリプトをアタッチします。
  3. 「1080p Button」を選択し、InspectorウィンドウでButtonコンポーネントの「OnClick ()」イベントリストの「+」ボタンを押します。
  4. イベントリストの「None (Object)」と表示されているフィールドに、`ResolutionManager` GameObjectをHierarchyウィンドウからドラッグ&ドロップします。
  5. 右側のドロップダウンメニュー(初期状態では「No Function」)から、「ResolutionChangeButton」>「SetResolution1080p ()」を選択します。
  6. 同様に、「720p Button」を選択し、OnClick()イベントに`ResolutionManager`を割り当て、関数として「ResolutionChangeButton」>「SetResolution720p ()」を選択します。

UnityのButtonコンポーネントのOnClickイベント設定画面

これで、ゲーム実行中に各ボタンをクリックすると、対応するスクリプト内のメソッドが呼び出され、Screen.SetResolutionによって画面解像度が変更されるようになります。

Screen.SetResolution関数の詳細解説

パラメータ (width, height, fullscreen) の意味

Screen.SetResolution関数は、基本的に3つの引数を取ります。

Screen.SetResolution(int width, int height, bool fullscreen)

  • int width: 設定したい画面のをピクセル単位で指定します。
  • int height: 設定したい画面の高さをピクセル単位で指定します。
  • bool fullscreen: 画面モードを指定します。
    • true: フルスクリーンモードで指定した解像度に設定します。
    • false: ウィンドウモードで指定した解像度(ウィンドウサイズ)に設定します。

例えば、Screen.SetResolution(1920, 1080, true); と記述すると、画面は1920×1080ピクセルのフルスクリーン表示に切り替わります。

(注意:Unity 2021.2以降では、第4引数としてリフレッシュレートを指定できるオーバーロードも追加されていますが、基本的な使い方は上記の3引数版です。)

ウィンドウモードとフルスクリーンモードの制御

Screen.SetResolutionの第3引数fullscreenの扱いについて、もう少し詳しく見てみましょう。

  • 現在のモードを維持したい場合: サンプルコードのように Screen.SetResolution(width, height, Screen.fullScreen); と記述します。Screen.fullScreenは現在のフルスクリーン状態(trueまたはfalse)を返すプロパティなので、これを使うと、フルスクリーン状態は変えずに解像度だけを変更できます。
    • 現在ウィンドウモードなら、指定したwidth x heightのウィンドウサイズになります。
    • 現在フルスクリーンモードなら、指定したwidth x heightのフルスクリーン解像度に切り替わります(モニターが対応していれば)。
  • モードも同時に切り替えたい場合: 第3引数に直接 true または false を指定します。
    • Screen.SetResolution(1280, 720, false); // 1280×720のウィンドウモードにする
    • Screen.SetResolution(1920, 1080, true); // 1920×1080のフルスクリーンモードにする
  • フルスクリーン状態だけを切り替えたい場合: Screen.fullScreen = !Screen.fullScreen; のように、Screen.fullScreenプロパティに直接代入することで、解像度を変えずにフルスクリーン/ウィンドウモードをトグルできます。(ただし、モード切り替え時に一瞬画面が乱れることがあります)

解像度がうまく変更されない場合のチェックポイント

Screen.SetResolutionを呼び出しても期待通りに解像度が変更されない場合、以下の点を確認してみてください。

  • Unityエディタ上での制限: UnityエディタのGameビューでは、Screen.SetResolutionによる解像度変更が完全には反映されない、あるいは挙動が異なる場合があります。特にフルスクリーンモードのテストは制限されます。正確な動作確認は、必ずビルドした実行ファイルで行ってください
  • ターゲットプラットフォームの制限:
    • WebGL: ブラウザ上で実行されるため、ブラウザウィンドウ自体のサイズやHTML/CSSによる制御が優先され、Screen.SetResolutionが意図した通りに機能しないことが多いです。WebGLでは通常、Canvas要素のサイズ調整で対応します。
    • モバイル (iOS/Android): スマートフォンやタブレットは基本的にデバイス固有のネイティブ解像度で動作するため、Screen.SetResolutionで任意の解像度に設定することは推奨されませんし、機能しない場合が多いです。モバイルでは通常、描画負荷を軽減するために内部的なレンダリング解像度を調整する(Screen.SetResolutionとは異なる)アプローチが取られます。
  • 他のスクリプトやアセットとの競合: プロジェクト内で画面サイズやフルスクリーン設定を制御している他のスクリプトやアセット(特にウィンドウ管理系のアセットなど)が存在する場合、それらとScreen.SetResolutionの呼び出しが競合している可能性があります。
  • OS側の設定: OSのディスプレイ設定(スケーリング設定など)が影響を与える可能性もゼロではありません。

実装時のTIPSと注意点

ターゲットプラットフォームによる挙動の違い

前述の通り、Screen.SetResolutionの挙動はターゲットプラットフォームによって異なります。

  • PC (Windows, Mac, Linux) スタンドアロンビルド: 最も意図した通りに動作しやすいプラットフォームです。プレイヤーに解像度とフルスクリーン/ウィンドウモードの選択肢を提供することが一般的です。
  • WebGL: 基本的にScreen.SetResolutionは使わず、HTML/JavaScript側でCanvasサイズを制御することを検討します。
  • モバイル: 任意の解像度変更は行わず、ネイティブ解像度で動作させるのが基本です。描画負荷軽減は別の方法(レンダリングスケール調整など)で行います。

開発するゲームがどのプラットフォーム向けなのかを意識し、実装方法を検討する必要があります。

アスペクト比の変化とUIスケーリングへの配慮

解像度を変更すると、画面のアスペクト比(横縦比、例: 16:9, 4:3)が変わる可能性があります。これに伴い、UI要素のレイアウトが崩れてしまうことがよくあります。

この問題に対応するには、UnityのUIシステム(uGUI)の機能を活用します。

  • Canvas Scaler コンポーネント: CanvasオブジェクトにアタッチされているCanvas Scalerコンポーネントの「UI Scale Mode」を「Scale With Screen Size」に設定します。これにより、画面解像度が変わっても、UI要素が基準解像度に合わせて自動的に拡大縮小されるようになります。「Match」スライダーで、幅と高さのどちらを基準にスケーリングするかのバランスを調整できます。
  • アンカー (Anchors) とピボット (Pivot): 各UI要素(Image, Button, Textなど)のRect Transformコンポーネントで、アンカーとピボットを適切に設定します。これにより、画面の端からの相対位置や、要素自体の伸縮挙動を制御でき、異なるアスペクト比でもある程度レイアウトを保つことができます。
  • Layout Group コンポーネント: Vertical Layout GroupやHorizontal Layout Group, Grid Layout Groupなどを使うと、子要素を自動的に整列・配置してくれるため、解像度変更に強いレイアウトを作りやすくなります。

解像度変更機能を実装する場合は、必ず複数の異なる解像度とアスペクト比でUIの表示を確認し、適切にスケーリングされるように調整することが重要です。

より使いやすい解像度変更メニューのアイデア

サンプル実装では固定の解像度ボタンを配置しましたが、よりユーザーフレンドリーな設定メニューにするためのアイデアもいくつかあります。

  • 解像度リストのドロップダウンメニュー: Screen.resolutions プロパティを使うと、プレイヤーのモニターがサポートしている解像度のリストを取得できます。これをドロップダウンメニューに表示し、プレイヤーが選択できるようにすると親切です。
  • フルスクリーン/ウィンドウモード切り替えボタン/チェックボックス: 解像度とは別に、フルスクリーンモードとウィンドウモードを切り替えるための専用のボタンやチェックボックスを用意します。(Screen.fullScreenプロパティを操作)
  • 任意解像度の入力フィールド(上級者向け): 幅と高さを直接入力できるフィールドを用意する方法もありますが、無効な値が入力される可能性もあるため、入力値のバリデーションなどが必要です。
  • 設定の保存と読み込み: プレイヤーが選択した解像度やフルスクリーン設定をPlayerPrefsなどに保存し、次回起動時に自動で適用するようにすると、さらに利便性が向上します。

参考資料


まとめ:Screen.SetResolutionで快適なプレイ環境を提供

今回は、Unityの Screen.SetResolution 関数を使って、ゲーム実行中に画面の解像度フルスクリーン/ウィンドウモードを変更する方法について解説しました。

この機能を実装することで、プレイヤーは自身のPC環境や好みに合わせて最適な表示設定を選択できるようになり、より快適なゲーム体験を提供できます。

実装にあたっては、ターゲットとするプラットフォームによる挙動の違いや、解像度変更に伴うUIレイアウト(アスペクト比、スケーリング)への配慮が重要になります。Canvas Scalerやアンカー設定などを活用し、様々な画面サイズでも破綻しないUIを目指しましょう。

基本的なボタンによる切り替えから、サポート解像度リストの表示、設定の保存・読み込みなど、より高度なオプションメニューの実装も可能です。ぜひ、あなたのプロジェクトに解像度変更機能を取り入れて、プレイヤー満足度の向上につなげてください。