LoginSignup
13
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-21

追記: 2018/05/23

続きを書きました。
Unity + Mac + Swift で透過最前面ウィンドウを作る

やりたいこと

環境

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側

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側

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 で透過最前面ウィンドウを作る

感想

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

13
11
0

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
13
11