アプリがユーザーのスクリーンショットや画面録画を検知したいケースは意外とあります。たとえば、銀行アプリなどではセキュリティ対策として一般的です。
ゲームアプリでは、スクリーンショットを検出することで演出の幅を広げたり、新しい機能を提案したりすることができます。たとえば、ガチャ結果の画面でスクリーンショットを撮影した際に、アプリ内の共有機能を案内する。または、キャラクターとのインタラクション時にスクリーンショットを撮ったら、「もぉ~、いきなり撮らないでよ~!」といった、第四の壁をくすぐる演出を加えることも可能です。
本記事では、iOS 上でスクリーンショットや画面録画を Unity アプリ側で検知する方法について、実装の試行過程を記録していきます。
iOS のスクリーンショットと画面録画を「通知」する仕組み
ここで言う「通知」とは、エンドユーザーに送るプッシュ通知ではなく、システムがイベントの発生をアプリに伝える仕組みを指します。
iOS では、NSNotification
という通知クラスがあり、さまざまなシステムイベントの発生をアプリに通知できます。たとえば、AVAudioSessionMicrophoneInjectionCapabilitiesChangeNotification
(長いですね…)は、「音声入力の介入が可能かどうか」が変わるたびに発火する通知です。
今回の記事では、UIScreenCapturedDidChangeNotification
とUIApplicationUserDidTakeScreenshotNotification
を使用して、画面録画とスクリーンショットの検出を行います。
前者のUIScreenCapturedDidChangeNotification
は、画面録画の状態が変わったときに発火する通知です。この通知を監視していれば、ユーザーが画面録画を開始・終了するたびにアプリが検知できます。
後者のUIApplicationUserDidTakeScreenshotNotification
はシンプルで、スクリーンショットが撮影されるたびに発火する通知です。
Unity のフレームワーク内では、iOS の通知を直接監視することはできません。
そのため、iOS のシステム機能を利用するには、ネイティブライブラリを作成し、Unity アプリ内の C#から呼び出す必要があります。
この「相互運用(Interop)」という手法は、iOS に限らず、各プラットフォームで活用可能です。
ここからは、Unity アプリで通知を検出できるようにするため、ネイティブライブラリを作成し、C#から呼び出す方法を解説していきます。
GitHub リポジトリ
サンプルコード(ネイティブライブラリと C# プラグイン)を公開している GitHub リポジトリがあります。ビルド済みのライブラリも提供しており、C# プラグインは UPM (Unity Package Manager) 形式 でホストしています。ぜひこちらのリポジトリを利用して、効果を試してみてください。 ([GitHub リポジトリ])
ページの長さを配慮して、以下のソースコードを切り取って説明します。
ネイティブライブラリ
この ScreenshotNotifier.mm は、iOS の NSNotification を利用して画面録画とスクリーンショットの検出を行うネイティブライブラリのソースです。
ビルド方法は割愛しますが、ネット上で iOS ネイティブライブラリのビルド方法に関する記事が多くあります。
[ScreenshotNotifier.mm のソースコードはこちらから](以下、一部切り取って説明します)
この ScreenshotNotifier.mm は、iOS のスクリーンショット撮影および画面録画の状態を検出し、Unity アプリ側に通知するためのネイティブライブラリです。ObjectiveC++ を使用しており、C 言語の extern “C” を利用して Unity の C# から呼び出せる形になっています。
コールバック関数の定義
1 | typedef void (*ScreenRecordingStatusChangedCallback)(bool); |
この部分では、Unity 側から渡されるコールバック関数の型を定義しています。
- ScreenRecordingStatusChangedCallback は、画面録画の開始・終了時に bool 値(録画中かどうか)を引数に持ちます。
- ScreenshotDetectedCallback は、スクリーンショット撮影時に呼び出されるコールバックで、引数はありません。
1 | static ScreenRecordingStatusChangedCallback screenRecordingStatusChangedCallback = NULL; |
これらの変数は、Unity から登録されたコールバック関数を保持するためのグローバル変数です。
コールバックの設定
1 | void SetScreenRecordingStatusChangedCallback(ScreenRecordingStatusChangedCallback callback) { |
これらの関数は、Unity 側から呼び出され、コールバック関数を設定する役割を持ちます。
Unity から SetScreenRecordingStatusChangedCallback に関数を渡すと、その関数が screenRecordingStatusChangedCallback に保存され、録画状態が変わるたびに実行されます。同様に SetScreenshotDetectedCallback もスクリーンショット検出時のコールバックを登録します。
画面録画&スクリーンショットの監視開始 (EnterCaptureProhibitSession)
EnterCaptureProhibitSession
関数は、画面録画とスクリーンショットの検出を開始するための監視セッションを開始します。
(1) 初期状態の画面録画検出
1 | bool isRecording = [UIScreen mainScreen].isCaptured; |
このコードでは、アプリ起動時や監視開始時にすでに録画が行われているかどうかをチェックし、コールバックを即座に実行して Unity 側に通知します。
(2) 画面録画状態の監視
1 | screenRecordingObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenCapturedDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { |
- UIScreenCapturedDidChangeNotification は、画面録画の開始・終了時に iOS システムから発火される通知です。
- addObserverForName を使い、この通知を受け取ると UIScreen.mainScreen.isCaptured の値を取得し、コールバックを実行して Unity 側に通知します。
(3) スクリーンショットの検出
1 | screenshotObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationUserDidTakeScreenshotNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { |
- UIApplicationUserDidTakeScreenshotNotification は、スクリーンショットが撮影されるたびに発火する iOS の通知です。
- この通知を受け取ると、登録された screenshotDetectedCallback を実行し、Unity にスクリーンショットのイベントを通知します。
監視の終了 (ExitCaptureProhibitSession)
1 | void ExitCaptureProhibitSession() { |
この関数は、監視を解除するために使用されます。
- removeObserver を呼び出し、画面録画とスクリーンショットの監視を停止します。
- screenRecordingObserver や screenshotObserver を nil にすることで、不要なメモリ使用を防ぎます。
現在の録画状態を取得(CheckCaptureStatus)
1 | bool CheckCaptureStatus() { |
- UIScreen.mainScreen.isCaptured を直接参照し、現在の画面録画の状態を返します。
- 監視を開始せずに、Unity 側から手動で現在の画面録画状態を取得したい場合に使用できます。
改めてまとめますと、この ScreenshotNotifier.mm は、iOS の NSNotification を利用して画面録画とスクリーンショットの検出を行うネイティブライブラリです。
このネイティブライブラリを Unity から利用することで、iOS アプリでもスクリーンショットや画面録画の状態を検知し、適切なアクションを実行できるようになります。
C#コード
Unity に戻って、C#側のコードを用意します。
この ScreenshotNotifier クラスは、iOS のスクリーンショットと画面録画の検出を Unity で扱うためのラッパーです。
DllImport(“__Internal”) を利用して、前述の ScreenshotNotifier.mm でビルドされたネイティブライブラリ (ObjectiveC++ ネイティブライブラリ)と相互運用(Interop)できるように設計されています。
ネイティブライブラリの導入方法については Unity 公式のマニュアルを参照してください。
また、このプロジェクトの配布されている UPM パッケージにもビルド済みのライブラリとその設定例が含まれております。
[ScreenshotNotifier.cs のソースコードはこちらから](以下一部切り取って説明します)
先頭にあるクラス定義や、状態管理フラグなどの説明はスキップします。
デリゲート(コールバック関数)
1 | public delegate void ScreenRecordingStatusChangedHandler(bool isRecording); |
ネイティブライブラリにあるコールバックと同じ形で用意します。
- ScreenRecordingStatusChangedHandler: 画面録画の開始・終了を通知するデリゲート(bool 引数あり)。
- ScreenshotDetectedHandler: スクリーンショット撮影を通知するデリゲート(引数なし)。
1 | public static event ScreenRecordingStatusChangedHandler OnScreenRecordingStatusChanged; |
それぞれのイベント (OnScreenRecordingStatusChanged, OnScreenshotDetected) が発火すると、Unity 側のコードで登録されたハンドラーが呼び出される仕組みです。
例えば、Unity のスクリプトで ScreenshotNotifier.OnScreenshotDetected += SomeMethod; と登録すると、スクリーンショットが撮影されたときに SomeMethod が実行されます。
監視開始 (EnterCaptureProhibitSession)
1 | NativeMethods.SetScreenRecordingStatusChangedCallback(OnNativeScreenRecordingStatusChanged); |
- ネイティブコード側の関数(ObjectiveC++)を呼び出して、イベントを登録します。
- OnNativeScreenRecordingStatusChanged と OnNativeScreenshotDetected をコールバック関数として設定します。
- EnterCaptureProhibitSession を呼び出して iOS の通知監視を開始します。
監視の停止 (ExitCaptureProhibitSession)
1 | NativeMethods.SetScreenRecordingStatusChangedCallback(null); |
- コールバック関数を解除(null に設定)し、ネイティブ側の監視を停止します。
ネイティブコードとの連携
この部分では、C#コードからどうやってネイティブライブラリの関数を利用するを説明します。
(1) コールバックの処理
1 | [ ] |
- ネイティブ側から画面録画状態の変化を受け取ったとき、またはスクリーンショットが撮影されたときに実行されるコールバック関数です。
- ?.Invoke(isRecording) により、リスナー(Unity 側で OnScreenRecordingStatusChanged に登録された関数)があれば実行します。
(2) ネイティブメソッド (NativeMethods)
この NativeMethods クラスには、ネイティブコード(ObjectiveC++)とやりとりするための関数が定義されています。
1 | [ ] |
- DllImport(“__Internal”) を使い、iOS のネイティブ関数(ScreenshotNotifier.mm)を直接呼び出しています。
- extern 修飾子を使って、ObjectiveC++ の関数を C# 側に公開します。
1 |
|
- iOS 以外の環境では、これらの関数は空の処理となり、影響を与えません。
- CheckCaptureStatus は false を返し、常に録画されていないことを示します。
- この空実装を用意しない場合、iOS 以外の環境で実行する(コンパイルする)とき、定義されていない関数の参照が発生します。
実際この C#コードを使ってスクショや録画を検出してみる
この SampleUsage クラスは、Unity で ScreenshotNotifier を使って iOS のスクリーンショット・画面録画を検出し、ログに出力するサンプルスクリプトです。
1 | using UnityEngine; |
実際に通知を実行するためのコードは Awake の初期化処理が一番大事です。
(1) イベントハンドラの登録
- ScreenshotNotifier.OnScreenshotDetected
- スクリーンショットが撮られたときに ScreenshotNotifierOnOnScreenshotDetected を実行するように設定。
- ScreenshotNotifier.OnScreenRecordingStatusChanged
- 画面録画の開始・停止が検出されたときに ScreenshotNotifierOnOnScreenRecordingStatusChanged を実行するように設定。
(2) EnterCaptureProhibitSession() の呼び出し
- ScreenshotNotifier.EnterCaptureProhibitSession() を実行し、スクリーンショット・録画の検出を開始 する。
これで、スクリーンショットや画面録画されるときに、関連する Debug.Log が発火します。
まとめ
この記事では、Unity アプリ内で iOS のスクリーンショットおよび画面録画を検出する方法について解説しました。スクリーンショットや画面録画のイベントを検出するために、iOS の通知機能を利用したネイティブライブラリを作成し、それを C# から呼び出す方法を紹介しました。
今後の展望
現在、iOS 版の実装を紹介しましたが、Android 版の実装にも挑戦したいと考えています。ただし、Android の場合、OS バージョンによって仕組みが異なるため、どの方法を採用するかで少し悩んでいます。もし良い方法が見つかれば、また別途共有したいと思いますので、楽しみにしていてください!