はじまり
Unity でHLSL書いてシェイダーアートを作ってみたいなーと漠然と思いつつ、どうせならスクリーンセーバーとして動かせないものかと漠然と思いながらあれこれ調べてみました。
するとどうも Windows と Android でビルド方法が見えそうだったので試してみることにしました。
目的
Unity で以下のプラットフォーム向けにスクリーンセーバーをビルドする設定や手順を調べる。
(シェイダーアートを書くのはここではやりません。)
- Windows x86-64
- Android
環境
- Windows 11 22H2
- Android 14
- Unity 2022.3.14f1
下調べ…スクリーンセーバーの仕組みを調べる
そもそもUnity以外のスクリーンセーバーがどのように作られてるのか調べないと手順が見えてこないのでOSごとに調べます。
Windows スクリーンセーバー
Windows のスクリーンセーバーの拡張子は .scr ですが、中身は実行可能ファイルです。
最低限 .exe の拡張子を .scr に変えて開けば実行できます。
しかしコマンドライン引数が特別仕様で、この仕様に合った実行ファイルを作らないと実用に耐えません。
つまり、スクリーンセーバー仕様のコマンドライン引数を解釈する.exe を拡張子 .scr でビルドすれば動きます。
この .exe を自作すれば UnityPlayer をスクリーンセーバーにできそうですね。
Windows SDK (C/C++) で作ればコマンドライン引数をいい感じに解釈してくれる SCRNSAVW.LIB1 をリンクできますが、 .NET を使って C# でゴリゴリ書く2実装も理論上は可能です。どっちでいこうかなー?
Android Daydream スクリーンセーバー
Daydream といえばVRプラットフォームの Daydream VR がありましたが、 Android 4.2 が新しかったころ Daydream という名前はスクリーンセーバーの機能名でした。
Android のスクリーンセーバーは充電中に表示します。
その仕組みを大雑把に説明すると Android API の android.service.dreams.DreamService というクラスを継承したフォアグラウンドサービスを実装すること。
Unity は普通にビルドすると1個のアクティビティからなるアプリを実装しますが、そもそもプログラムの始まりがアクティビティではないのでフォアグラウンドサービスを自作する必要があります。
逆に言えばスクリーンセーバーのフォアグラウンドサービスを自作3することで動かせるようになるはずですね。
標準の GameActivity が使ってるはずの com.unity3d.player.UnityPlayer のクラスの仕様がマニュアルに載ってないのが曲者の雰囲気ですが……
その他
macOS のスクリーンセーバーも実行可能バンドルを特定手順でつくればスクリーンセーバーになりそうですが、細かい手順4もさることながら「UserDefaults を使わずに ScreenSaverDefaults で設定を保存せよ」という制約が嫌な感じです。
Unity の中間成果物をいじって PlayerPrefs の実装を魔改造しないと PlayerPrefs 封印する羽目になりそう……?
サブマシンの Mac が古いので別の機会に調べましょう。
実装
Android
作成したばかりのまっさらなプロジェクトを試しに Android Studio プロジェクトとしてエクスポートしてみます。
これを Android Studio で開いてみると、いつもの UnityPlayerActivity のソースコードを開くことができました。
この中で com.unity3d.player.UnityPlayer をあれこれしているので、これの継承関係を Android Studio で調べてみるとどうやらFrameLayout のサブクラスのようです。
ならば Android 公式のサンプルコード3における自作FrameLayoutとおおむね同様に使えそうです。
Android 公式ブログの BouncerDaydream と unityLib に収録されている UnityPlayerActivity.java の定義を見ながら見様見真似でサービスをつくっていきます。
できましたー!
しかしここで新たな問題発生です。 Unity 側から currentActivity が取得できないのです。
元来 UnityPlayer は Activity と一緒に使う前提で組まれているのですが、スクリーンセーバーの描画領域を提供するアクティビティは非公開のようです。
なのでスクリーンセーバー起動時 UnityPlayer.currentActivity は null にせざるを得ません。
Android API を使う Unity パッケージが動かなくなることがあります。外部パッケージとの相性は特に注意しましょう。
それでも自分の書いたコードで使いたい場合のために、代わりに Daydream のサービスを取得するための UnityPlayerDream.Companion.getCurrentService() を実装してみました。
そもそもアクティビティではなくサービスなのでいろいろと勝手が異なりますが、ないよりはマシでしょう。
マネージドコードでスクリーンセーバーを終了するサンプルを作ってみました。
→ サンプルコード
早速 LogCat をつないで動かしてみると終了間際に NullPointerException が発生してるのでまだ粗削りですが、とりあえず動くようになりました。
もう少し頑張れば、いつもの UnityPlayerActivity と両方入れてアクティビティで起動したら設定画面を開く、みたいな芸当もできそうです。
Windows
C++ と Windows SDK で書こうか迷いましたが、 Unity 使いに読んでもらうなら C# で書いた方がいいだろうと判断したので .NET で書くことにします。
有志の情報2によると、本実行してほしいときには /S
という引数を受け取るようです。
では「引数が /S
だったら UnityPlayer を全画面で開く Windows アプリ」を .NET でサクッと書いてみましょうか。
なお本実行の際は Unity が自分でウィンドウを開くため、スクリーンセーバーの終了処理を Unity で実装する必要があります。
マウスの動き(と、ついでにタッチイベント)を毎フレーム見て変化があったら終了するようにしてみましょう。
プレビューと本実行の区別は後述。
ところで Windows 版 UnityPlayer が受け取る引数5に -parentHWND
というのがありまして、ここに10進数でウィンドウハンドルを渡すと、そのウィンドウに Unity の画面をレンダリングするようになるらしいです。6
プレビューの時はこれを使います。
あとは引数が無い場合と /C
の場合は設定画面を開くべきなのですが、 Windows 公式の中にこういうのがあるのでオミットしちゃいます。
フォームアプリのパブリッシュで手間取ったり、プレビュー後に本実行すると映像が低解像度になるバグが発生したりしましたが、紆余曲折を経て以下のコードができました!
/P
の処理に何か書いてあるのがわかるでしょうか?
プレビューと本実行を区別するため、起動時にレジストリをいじって PlayerPrefs を書き換えています。
レジストリの場所は例によって公式ドキュメント7を参考にしました。
こいつをビルドして出てきた.exeを拡張子 .scr に変えて Unity のビルドに混ぜたら完成です!
仕上げ
最後に Unity 側で終了処理を書いたら実装完了!
→ QuitTimerScript (名前と機能が合ってないのはご容赦ください)
検証結果
- Android: プラグイン書いたら無事に作成できました
- Windows: 起動用のscrを作ったら無事に作成できました
感想
「右も左もわからないものを調べながらなんとか作る」感覚を久しぶりに味わいました。 (※普段はUnity使ってない)
最近新しいメンバーが時々入ってくるので、運用資料の更新とかコードレビューの時はこの心を思い出しながら行いたいです。
以上、スクリーンセーバーのつくり方でした。
これで当初の目的だったシェイダーアート・スクリーンセーバーが現実圏内になりました!
git リポ
→ https://github.com/TkoolerLufar/UnityScreenSaver.git
その後
→ 後日談
参考文献
脚注にまとめました。
-
スクリーン セーバーの処理 - Win32 apps | Microsoft Learn (2023年12月4日閲覧) https://learn.microsoft.com/ja-jp/windows/win32/lwef/screen-saver-library ↩
-
Creating a Screen Saver with C# (2023年12月4日閲覧) https://sites.harding.edu/fmccown/screensaver/screensaver.html ↩ ↩2
-
Android Developers Blog: Daydream: Interactive Screen Savers (2023年12月4日閲覧) https://android-developers.googleblog.com/2012/12/daydream-interactive-screen-savers.html ↩ ↩2
-
Screen Saver | Apple Developer Documentation (2023年12月4日閲覧) https://developer.apple.com/documentation/screensaver ↩
-
Unity スタンドアロンプレイヤーのコマンドライン引数 - Unity マニュアル (2023年12月11日閲覧) https://docs.unity3d.com/ja/2023.2/Manual/PlayerCommandLineArguments.html ↩
-
Unity - Manual: Integrating Unity into Windows applications (2023年12月11日閲覧) https://docs.unity3d.com/Manual/UnityasaLibrary-Windows.html ↩
-
PlayerPrefs - Unity スクリプトリファレンス (2023年12月11日閲覧) https://docs.unity3d.com/ja/2023.2/ScriptReference/PlayerPrefs.html ↩