Unityを触り始めた頃は、つい Update() に「とりあえず全部書く」スタイルになりがちですよね。入力チェックも、ゲームロジックも、UI更新も、デバッグ用の処理も、ぜんぶ1つのスクリプトに詰め込んでしまうと、次のような問題が出てきます。

  • スクロールしないと全体像が見えない「Godクラス」化
  • どこで何をしているか分かりにくく、バグ調査がつらい
  • シーンごとにコピペしたり、条件分岐だらけになって再利用しづらい

この記事では、そんな「全部入りUpdate」から卒業するために、スクリーンショット撮影だけを担当する小さなコンポーネントを作ってみます。

F12キーなどで画面をキャプチャし、日時を含んだファイル名で保存してくれる ScreenshotTaker コンポーネントです。デバッグ用の記録や、レベルデザインの確認、クライアントへの確認用キャプチャなどで地味に便利なやつですね。

【Unity】ワンキーでデバッグ撮影!「ScreenshotTaker」コンポーネント

ここでは、Unity6(新Input System対応)で動く、ScreenshotTaker のフルコードを掲載します。

  • F12キーでスクショ撮影(キーはインスペクターから変更可)
  • 保存先フォルダもインスペクターから変更可(デフォルトはプロジェクト直下の Screenshots
  • ファイル名は Screenshot_2025-12-05_23-59-59.png のように日時入り

フルコード


using System;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.InputSystem; // 新Input System用

/// <summary>
/// 指定キーを押したときにスクリーンショットを撮影するコンポーネント。
/// ・日時入りファイル名でPNG保存
/// ・保存先フォルダとキーはインスペクターから変更可能
/// </summary>
public class ScreenshotTaker : MonoBehaviour
{
    // --- 設定項目(インスペクターから編集) ----------------------------------

    [Header("入力設定")]
    [SerializeField]
    [Tooltip("スクリーンショットを撮影するキー(新Input SystemのKeyboard)")]
    private Key screenshotKey = Key.F12;

    [Header("保存設定")]
    [SerializeField]
    [Tooltip("プロジェクトフォルダからの相対パス。例: \"Screenshots\"")]
    private string folderName = "Screenshots";

    [SerializeField]
    [Tooltip("ファイル名のプレフィックス。例: \"Screenshot_\" → Screenshot_2025-01-01_12-00-00.png")]
    private string fileNamePrefix = "Screenshot_";

    [SerializeField]
    [Tooltip("撮影時にコンソールへログを出すかどうか")]
    private bool logOnCapture = true;

    [Header("上級者向け")]
    [SerializeField]
    [Tooltip("Time.timeScale = 0 などでゲームが停止していても撮影したい場合は true")]
    private bool ignoreTimeScale = true;

    // --- 内部状態 -----------------------------------------------------------

    // 連打で大量保存される事故を防ぐための簡易クールダウン
    [SerializeField]
    [Tooltip("連続撮影を防ぐクールダウン時間(秒)")]
    private float captureCooldown = 0.2f;

    private float _lastCaptureTime = -999f;

    // ------------------------------------------------------------------------
    // ライフサイクル
    // ------------------------------------------------------------------------

    private void Update()
    {
        // 入力チェックはここだけに閉じ込める
        if (!IsCaptureKeyPressed())
        {
            return;
        }

        // クールダウン中なら無視
        if (Time.unscaledTime - _lastCaptureTime < captureCooldown)
        {
            return;
        }

        _lastCaptureTime = Time.unscaledTime;

        // 実際の撮影処理
        CaptureScreenshot();
    }

    // ------------------------------------------------------------------------
    // 入力判定
    // ------------------------------------------------------------------------

    /// <summary>
    /// スクリーンショット撮影キーが押されたかどうかを判定。
    /// 新Input SystemのKeyboardクラスを使用。
    /// </summary>
    private bool IsCaptureKeyPressed()
    {
        // キーボードが存在しない環境(モバイルなど)では false
        if (Keyboard.current == null)
        {
            return false;
        }

        // ignoreTimeScale が true の場合も、Update は呼ばれる前提でOK。
        // TimeScaleには依存せず、純粋にキー入力だけを見る。
        var keyControl = Keyboard.current[screenshotKey];
        return keyControl != null && keyControl.wasPressedThisFrame;
    }

    // ------------------------------------------------------------------------
    // 撮影メイン処理
    // ------------------------------------------------------------------------

    /// <summary>
    /// スクリーンショットをPNG形式で保存する。
    /// ファイル名に現在日時を含める。
    /// </summary>
    private void CaptureScreenshot()
    {
        // 保存先フォルダの絶対パスを作成
        string folderPath = Path.Combine(Application.dataPath, "..", folderName);

        // ディレクトリがなければ作成
        if (!Directory.Exists(folderPath))
        {
            Directory.CreateDirectory(folderPath);
        }

        // 日時入りファイル名を生成
        string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
        string safePrefix = MakeFileNameSafe(fileNamePrefix);
        string fileName = $"{safePrefix}{timestamp}.png";

        string fullPath = Path.GetFullPath(Path.Combine(folderPath, fileName));

        // 実際の撮影。ScreenCapture.CaptureScreenshot は1フレーム遅延で書き出される。
        ScreenCapture.CaptureScreenshot(fullPath);

        if (logOnCapture)
        {
            Debug.Log($"[ScreenshotTaker] スクリーンショットを保存しました: {fullPath}");
        }
    }

    // ------------------------------------------------------------------------
    // ユーティリティ
    // ------------------------------------------------------------------------

    /// <summary>
    /// ファイル名として不正になりうる文字を安全な文字に置き換える。
    /// (Windows / macOS / Linux で共通して問題になりやすい文字を対象)
    /// </summary>
    private string MakeFileNameSafe(string raw)
    {
        if (string.IsNullOrEmpty(raw))
        {
            return string.Empty;
        }

        // OSごとに禁止文字は微妙に違うが、よく問題になる文字をざっくり排除
        char[] invalidChars = Path.GetInvalidFileNameChars();
        StringBuilder sb = new StringBuilder(raw.Length);

        foreach (char c in raw)
        {
            bool isInvalid = false;
            for (int i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    isInvalid = true;
                    break;
                }
            }

            sb.Append(isInvalid ? '_' : c);
        }

        return sb.ToString();
    }
}

使い方の手順

ここからは、実際にプロジェクトへ組み込む手順を具体的に見ていきましょう。

  1. スクリプトを作成する
    • Unity エディタの Project ビューで、任意のフォルダ(例: Scripts/Utility)を右クリック → Create > C# Script
    • 名前を ScreenshotTaker に変更
    • 自動生成された中身をすべて削除し、上記のフルコードをコピペして保存
  2. シーンに専用オブジェクトを置く
    入力やユーティリティ的な処理は、専用のGameObjectにまとめるとスッキリします。
    • Hierarchy で右クリック → Create Empty
    • 名前を System_Screenshot などに変更
    • この GameObject に ScreenshotTaker コンポーネントをアタッチ
  3. インスペクターで設定を調整する
    System_Screenshot を選択し、インスペクターから以下を調整します。
    • Screenshot Key: デフォルトは F12。例えばデバッグビルド用に F9 に変えたいなどがあれば変更。
    • Folder Name: デフォルトは Screenshots
      プロジェクトフォルダ直下に Screenshots フォルダが自動で作られます。
      例: Builds/DebugShots のように階層を含めてもOKです。
    • File Name Prefix: デフォルトは Screenshot_
      例えばプレイヤー用なら Player_、UI用なら UI_ などにすると整理しやすくなります。
    • Log On Capture: 撮影したパスをコンソールに出したい場合はONのまま。
    • Ignore Time Scale: 一時停止中(Time.timeScale = 0)でも撮影したいならONのままでOK。
  4. 実行してF12を押す
    • Playボタンでゲームを実行
    • ゲームウィンドウをアクティブにした状態で F12(もしくは設定したキー)を押す
    • プロジェクトフォルダを開き、Screenshots フォルダの中に Screenshot_yyyy-MM-dd_HH-mm-ss.png ができているか確認

具体的な使用例

  • プレイヤー挙動のデバッグ
    プレイヤーキャラクターの動きやアニメーションを調整しているときに、
    「この瞬間のポーズを記録しておきたい」という場面があります。
    そんなときに ScreenshotTaker をシーンに入れておけば、
    F12を押すだけでその瞬間の画面をPNGで保存できます。
  • 敵AIの確認
    敵の編成や配置をテストしているときに、「この距離感がちょうどいい」「この視界の広さは広すぎる」といった判断を、
    スクリーンショットで記録しておくと、後で比較・相談しやすくなります。
  • 動く床やギミックのレベルデザイン
    動く床やトラップの配置を調整しているときに、
    「プレイヤーがここにいるときに床がここまで動いている」という状況を、
    何枚か連続で撮影しておくと、あとで仕様書やドキュメントに貼り付けるのに便利です。

メリットと応用

ScreenshotTaker を「スクショ専用コンポーネント」として切り出すことで、次のようなメリットがあります。

  • Godクラス化を防げる
    「デバッグ用スクショ撮影」をプレイヤー制御スクリプトやゲームマネージャーに書いてしまうと、
    それらのクラスがどんどん肥大化していきます。
    撮影ロジックは ScreenshotTaker に集約し、他のスクリプトは「ゲームのロジック」に集中させましょう。
  • プレハブ化してどのシーンでも再利用できる
    System_Screenshot オブジェクトをプレハブにしておけば、
    どのシーンにもドラッグ&ドロップで簡単に導入できます。
    「このプロジェクト、全部のシーンでF12撮影できるようにしたい」という要望にもすぐ対応できます。
  • レベルデザインのワークフローが楽になる
    レベルデザイナーやプランナーが、ゲームを触りながら気になる箇所を即スクショ→共有、という流れが作りやすくなります。
    日時入りファイル名なので、あとから「どのバージョンのときのスクショか」も追いやすいです。
  • ビルド後でも機能する
    ScreenCapture.CaptureScreenshot を使っているので、エディタ上だけでなくビルド後の実行ファイルでも動作します。
    QAチームやクライアントに渡したビルドでも、同じキーでスクショを撮ってもらえます。

改造案:連番付きの撮影モードを追加する

「テストプレイ中に何十枚も撮るから、日時より連番の方が管理しやすい」というケースもあります。その場合は、例えば次のようなメソッドを追加して、インスペクターから切り替えられるようにしてもいいですね。


    // 連番カウンタ(クラス内フィールドとして追加)
    [SerializeField]
    [Tooltip("連番ファイル名を使う場合の開始番号")]
    private int sequenceStartIndex = 1;

    private int _currentSequenceIndex = -1;

    /// <summary>
    /// 連番ファイル名でスクリーンショットを撮影する例。
    /// 例: Shot_0001.png, Shot_0002.png ...
    /// </summary>
    private void CaptureScreenshotWithSequence()
    {
        if (_currentSequenceIndex < 0)
        {
            _currentSequenceIndex = sequenceStartIndex;
        }

        string folderPath = Path.Combine(Application.dataPath, "..", folderName);
        if (!Directory.Exists(folderPath))
        {
            Directory.CreateDirectory(folderPath);
        }

        string safePrefix = MakeFileNameSafe(fileNamePrefix);
        string fileName = $"{safePrefix}{_currentSequenceIndex:D4}.png";
        string fullPath = Path.GetFullPath(Path.Combine(folderPath, fileName));

        ScreenCapture.CaptureScreenshot(fullPath);

        if (logOnCapture)
        {
            Debug.Log($"[ScreenshotTaker] 連番スクリーンショットを保存しました: {fullPath}");
        }

        _currentSequenceIndex++;
    }

このように、「撮影処理」「入力判定」「ファイル名生成」などを小さな関数に分けておくと、機能追加や改造がとてもやりやすくなります。コンポーネント指向で、スクショ撮影のような小さな機能も気持ちよく分離していきましょう。