Unity + Mac で透過最前面ウィンドウを作る

# 追記: 2018/05/23

続きを書きました。
[Unity + Mac + Swift で透過最前面ウィンドウを作る](https://qiita.com/KRiver1/items/9ecf65759cf1349f56af)

# やりたいこと
<blockquote class="twitter-tweet" data-conversation="none" data-partner="tweetdeck"><p lang="ja" dir="ltr">紆余曲折を経て、デスクトップにシロちゃんを召喚することに成功しました <a href="https://twitter.com/hashtag/SiroTalk?src=hash&amp;ref_src=twsrc%5Etfw">#SiroTalk</a> <a href="https://t.co/pvJRvgO2e0">pic.twitter.com/pvJRvgO2e0</a></p>&mdash; かりばぁ (@KRiver1) <a href="https://twitter.com/KRiver1/status/998516380499505153?ref_src=twsrc%5Etfw">May 21, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 環境

Mac OSX High Sierra バージョン 10.13.4
Unity Version 2018.1.0f2 Personal

Target pratform は Standalone Mac OS X のみを想定。

# ネイティブプラグインを作る

透過ウィンドウを作るためには、Unityのクロスプラットフォームな機能ではなく、targetとなる環境ごとに固有のAPIを用いなければならない。
こういったプラットフォーム依存なプログラムを書くための機能がUnityの[ネイティブプラグイン](https://docs.unity3d.com/ja/current/Manual/NativePlugins.html)である。

## 練習

[WindowsではWin32APIを用いればよい](https://forum.unity.com/threads/solved-windows-transparent-window-with-opaque-contents-lwa_colorkey.323057/)のだが、MacではわかりやすいAPIがなかなか見つからない。
とりあえず、まずは[このサイト](http://tatsudoya.blog.fc2.com/blog-entry-237.html)を参考に、ネイティブプラグインを作ってみる。

[ここにある通り](https://docs.unity3d.com/ja/current/Manual/PluginsForDesktop.html)、XCodeで開発することが想定されているので、指示に粛々と従ってbundleをビルド。
途中、Code signingで怒られたり（don't code sign を選択して解決）、bundleの出力先を見失ったり（Preference->Locations->Advanced->Custom->Relativeを選択して解決）するなどしつつ、なんとかbundleを作成し、Unityの`Assets/Plugins/***.bundle`にコピー（実際はシンボリックリンクを貼った）。
プラグインを読み込むスクリプトを、[先ほどのサイト](http://tatsudoya.blog.fc2.com/blog-entry-237.html)を参考に作成し、適当な`GameObject`にアタッチ。

これでうまく動いた（Unity Console の Information にメッセージが出た）。

## 本番

[Apple Development Docmentation](https://developer.apple.com/documentation/appkit?language=objc)とにらめっこしつつ、何度もビルドを繰り返し、最終的に以下のようなスクリプトを書いた。


### bundle側

```objective_c:transparent.m
#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側

```csharp:NativePluginLoader.cs
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 で透過最前面ウィンドウを作る](https://qiita.com/KRiver1/items/9ecf65759cf1349f56af)


# 感想

クロスプラットフォームなAPIだけで生きていける世界に生まれたかった。