1
1

Unity でスクリーンセーバーを作る方法を模索してみた

Last updated at Posted at 2023-12-15

次の日付の記事

はじまり

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 プロジェクトとしてエクスポートしてみます。

Build Settings で Platform を Android にすると、 Export Project というチェックボックスがあり、ここにチェックを入れてると Build ボタンが Export ボタンに変化するのでこれを押します。

これを Android Studio で開いてみると、いつもの UnityPlayerActivity のソースコードを開くことができました。

Windows なら Ctrl+Shift+N、macOS なら ⇧⌘O で検索すると早いですよ!

この中で com.unity3d.player.UnityPlayer をあれこれしているので、これの継承関係を Android Studio で調べてみるとどうやらFrameLayout のサブクラスのようです。
ならば Android 公式のサンプルコード3における自作FrameLayoutとおおむね同様に使えそうです。

Android 公式ブログの BouncerDaydream と unityLib に収録されている UnityPlayerActivity.java の定義を見ながら見様見真似でサービスをつくっていきます。

UnityPlayerDreamService

できましたー!

しかしここで新たな問題発生です。 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 公式の中にこういうのがあるのでオミットしちゃいます。

ブランクスクリーンセーバーの設定を開こうとすると「設定できるオプションはありません」というメッセージが表示されるだけになっています。

フォームアプリのパブリッシュで手間取ったり、プレビュー後に本実行すると映像が低解像度になるバグが発生したりしましたが、紆余曲折を経て以下のコードができました!

Program.cs

/P の処理に何か書いてあるのがわかるでしょうか?
プレビューと本実行を区別するため、起動時にレジストリをいじって PlayerPrefs を書き換えています。
レジストリの場所は例によって公式ドキュメント7を参考にしました。

こいつをビルドして出てきた.exeを拡張子 .scr に変えて Unity のビルドに混ぜたら完成です!

仕上げ

最後に Unity 側で終了処理を書いたら実装完了!

QuitTimerScript (名前と機能が合ってないのはご容赦ください)

検証結果

  • Android: プラグイン書いたら無事に作成できました
  • Windows: 起動用のscrを作ったら無事に作成できました

感想

「右も左もわからないものを調べながらなんとか作る」感覚を久しぶりに味わいました。 (※普段はUnity使ってない)
最近新しいメンバーが時々入ってくるので、運用資料の更新とかコードレビューの時はこの心を思い出しながら行いたいです。

以上、スクリーンセーバーのつくり方でした。
これで当初の目的だったシェイダーアート・スクリーンセーバーが現実圏内になりました!

git リポ

https://github.com/TkoolerLufar/UnityScreenSaver.git

その後

後日談

参考文献

脚注にまとめました。

  1. スクリーン セーバーの処理 - Win32 apps | Microsoft Learn (2023年12月4日閲覧) https://learn.microsoft.com/ja-jp/windows/win32/lwef/screen-saver-library

  2. Creating a Screen Saver with C# (2023年12月4日閲覧) https://sites.harding.edu/fmccown/screensaver/screensaver.html 2

  3. Android Developers Blog: Daydream: Interactive Screen Savers (2023年12月4日閲覧) https://android-developers.googleblog.com/2012/12/daydream-interactive-screen-savers.html 2

  4. Screen Saver | Apple Developer Documentation (2023年12月4日閲覧) https://developer.apple.com/documentation/screensaver

  5. Unity スタンドアロンプレイヤーのコマンドライン引数 - Unity マニュアル (2023年12月11日閲覧) https://docs.unity3d.com/ja/2023.2/Manual/PlayerCommandLineArguments.html

  6. Unity - Manual: Integrating Unity into Windows applications (2023年12月11日閲覧) https://docs.unity3d.com/Manual/UnityasaLibrary-Windows.html

  7. PlayerPrefs - Unity スクリプトリファレンス (2023年12月11日閲覧) https://docs.unity3d.com/ja/2023.2/ScriptReference/PlayerPrefs.html

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1