追記: 2018/05/23
続きを書きました。
Unity + Mac + Swift で透過最前面ウィンドウを作る
やりたいこと
紆余曲折を経て、デスクトップにシロちゃんを召喚することに成功しました #SiroTalk pic.twitter.com/pvJRvgO2e0
— かりばぁ (@KRiver1) May 21, 2018
環境
Mac OSX High Sierra バージョン 10.13.4
Unity Version 2018.1.0f2 Personal
Target pratform は Standalone Mac OS X のみを想定。
ネイティブプラグインを作る
透過ウィンドウを作るためには、Unityのクロスプラットフォームな機能ではなく、targetとなる環境ごとに固有のAPIを用いなければならない。
こういったプラットフォーム依存なプログラムを書くための機能がUnityのネイティブプラグインである。
練習
WindowsではWin32APIを用いればよいのだが、MacではわかりやすいAPIがなかなか見つからない。
とりあえず、まずはこのサイトを参考に、ネイティブプラグインを作ってみる。
ここにある通り、XCodeで開発することが想定されているので、指示に粛々と従ってbundleをビルド。
途中、Code signingで怒られたり(don't code sign を選択して解決)、bundleの出力先を見失ったり(Preference->Locations->Advanced->Custom->Relativeを選択して解決)するなどしつつ、なんとかbundleを作成し、UnityのAssets/Plugins/***.bundle
にコピー(実際はシンボリックリンクを貼った)。
プラグインを読み込むスクリプトを、先ほどのサイトを参考に作成し、適当なGameObject
にアタッチ。
これでうまく動いた(Unity Console の Information にメッセージが出た)。
本番
Apple Development Docmentationとにらめっこしつつ、何度もビルドを繰り返し、最終的に以下のようなスクリプトを書いた。
bundle側
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
// From: http://tatsudoya.blog.fc2.com/blog-entry-244.html
void MakeTransparent() {
// Style mask
// See: https://developer.apple.com/documentation/appkit/nswindow.stylemask
NSWindowStyleMask styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskClosable | NSWindowStyleMaskTitled;
// Step 1: set Unity window
// Get the window used in the Unity application
NSArray *ar = [NSApp orderedWindows];
NSWindow *window = [ar objectAtIndex:0];
// Make its border hidden
[window setStyleMask:styleMask];
// Make its background transparent
[window setBackgroundColor:[NSColor clearColor]];
[window setOpaque:NO];
// Make it no-shadow
[window setHasShadow:false];
// Step 2: set Unity view
// Get the view to the Unity application
NSView *view = [window contentView];
// Make it Layer Backed
// See: https://blog.fenrir-inc.com/jp/2011/07/nsview_uiview.html
[view setWantsLayer:YES];
// Make its layer transparent
// Note: CGColor is not compatible with NSColor, so [NSColor clearColor] cannot be applied
[[view layer] setBackgroundColor:CGColorCreateGenericRGB(0, 0, 0, 0)];
[[view layer] setOpaque:NO];
// Step 3: generate a new window and make it transparent
// Get a content rect
NSRect rect = [window contentLayoutRect];
// Generate a new, borderless, and same-size-as-content window
NSWindow *newWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
// Place it in center
[newWindow center];
// Memory release setting
[newWindow setReleasedWhenClosed:YES];
// Make it transparent
[newWindow setBackgroundColor:[NSColor clearColor]];
[newWindow setOpaque:NO];
// Temporary make it front
[window orderFrontRegardless];
[newWindow orderFrontRegardless];
// Make it front
// See: https://qiita.com/ocadaruma/items/790e96245c99e7af42a3
[window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorFullScreenAuxiliary];
[newWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorFullScreenAuxiliary];
[window setLevel:NSFloatingWindowLevel];
[newWindow setLevel:NSFloatingWindowLevel];
// Step 4: add the Unity view to the new window
[newWindow setContentView:view];
// Also add it to the Unity window
[window setContentView:view];
// Hide the new created window
[newWindow setAccessibilityHidden:true];
}
Unity側
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
public class NativePluginLoader : MonoBehaviour
{
[DllImport("Transparent")]
private static extern void MakeTransparent();
// Use this for overall initialization on load
[RuntimeInitializeOnLoadMethod]
static void Initialize()
{
// Invoke transparent script when it's not on editor
#if UNITY_EDITOR
// None
#else
MakeTransparent();
#endif
}
}
ウィンドウモードでビルドすると、背景の透過された画面が現れ、非アクティブにしても最前面に居座り続ける。
タイトルバーだけは残っているので、自由に移動したり削除したりすることができる。
上記ソースコードにはいくつか「この工程いらないのでは…?」と思う部分もあるのだが、本当に長い時間を掛けて奇跡的に生まれた成功コードなので、正直これ以上いじりたくないという思いが強く、どの部分が不要なコードでどの部分が必要なコードなのか検証できていない。
Mac OS X API に詳しい人がいたらぜひきれいに書き直してほしい。
(追記: 2018/05/23) 書き直しました。
Unity + Mac + Swift で透過最前面ウィンドウを作る
感想
クロスプラットフォームなAPIだけで生きていける世界に生まれたかった。