【Unity】macOS向けビルドしたアプリにドラッグアンドドロップを可能にするUniDragAndDropForMacを作りました

UnityからmacOS向けのアプリをビルドして、そのアプリに対し**ドラッグアンドドロップ**を実装するという知見は、僕が調べた限り見つける事は出来なかったため、[UniDragAndDropForMac](https://github.com/baobao/UniDragAndDropForMac)というネイティブプラグインを作りました。
![macDD.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328771/00e8ef7f-e1fc-d1d9-f119-d7f4d46733d8.gif)
[GitHub:baobao/UniDragAndDropForMac](https://github.com/baobao/UniDragAndDropForMac)

以下技術的な話になります。

## そもそも出来るのか？

参考 : [【Unity】【C#】ランタイム時にファイルをドラッグ＆ドロップして取得する（Windows のみ）](http://fantom1x.blog130.fc2.com/blog-entry-315.html?sp)
調査する中Windows版での実装方法は見つかりました。ソースをチラ見すると、どうやらWindowsのネイティブ機能を使って実装している事が分かったので、macOSでも同様にネイティブの機能を使えば実装できるだろうと思いました。

## 用意されていないネイティブ機能を使う場合はネイティブプラグインで解決させる

まずはCocoaフレームワークの中にドラッグアンドドロップのAPIを調査します。

- [[macOS] [Swift3.0] Drag & Dropでファイルパスを取得 - Qiita](https://qiita.com/Akira_Kido_N/items/9fbda65a7efe218d9e12)
- [ファイルをNSViewにドラッグしてパスを取得する (Objective-C) - Qiita](https://qiita.com/IKEH/items/904142af934a47b38f94)

Swiftはほぼほぼ未経験だったので、成果物を作り上げるスピードを優先させるため今回はObjective-C(以下:ObjC)で実装することにしました(後者の記事を参考にさせて頂きました)。


- C#側からObjCに文字列(ファイルパス)を送ってもらう関数ポインタを渡す
- Unityが作ったネイティブViewにアクセス
- ObjC側でドラッグアンドドロップされたファイルパスをC#に送る

このような手順で実装していきます。

### C#側からObjCに文字列(ファイルパス)を送ってもらう関数ポインタを渡す

```UniDragAndDrop.cs
// コールバックのデリゲート
delegate void callback_delegate(string val);

// ネイティブプラグインの実行、関数ポインタをobjCにわたす
[DllImport("UniDragAndDrop")]
private static extern void Initialize(callback_delegate callback);

// objCから実行されるコールバック
[MonoPInvokeCallback(typeof(callback_delegate))]
private static void cs_callback(string dragAndDropPath)
{
    // Do Something...
}
```

C#側からObjCにアクセスする部分は上記のようなコードになっています。


<br />

```UniDragAndDrop.mm
// ドラッグアンドドロップを実装したViewをアプリ全面に生成
GetDragAndDropFilePath *ddview = [[GetDragAndDropFilePath alloc] initWithFrame:view.frame];

// C#の関数ポインタを渡す
[ddview setCallback:callback];
```
GetDragAndDropFilePathは**NSImageViewを継承したドラッグアンドドロップを実装するView**です。これをアプリ全面に表示することでドラッグアンドドロップが出来るようにしています。

### Unityが作ったネイティブViewにアクセス

```objc
NSArray *ar = [NSApp orderedWindows];
NSWindow *window = [ar objectAtIndex:0];
NSView *view = [window contentView];
```
この3行の処理でUnityが実行しているネイティブのNSView(*view)を取得しています。
`*view`に対してドラッグアンドドロップ用のView(前述のGetDragAndDropFilePath)を追加していきます。


### ObjC側でドラッグアンドドロップされたファイルパスをC#に送る

```GetDragAndDropFilePath.h
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

typedef void (*cs_callback)(const char*);

@interface GetDragAndDropFilePath : NSImageView {
    cs_callback _callback;
}
- (void)setCallback:(cs_callback) callback;
@end

```
NSImageViewを継承したクラスを作成してドラッグアンドドロップを実装しています。
<br />

```GetDragAndDropFilePath.mm
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
    NSURL* fileURL = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
    NSString *url = fileURL!=NULL ? [fileURL path] : @"";
    // C#にファイルパスを送る
    _callback([url UTF8String]);
    return NO;
}
@end 
```

ドロップが完了したタイミングで予め渡されていたC#の関数に対してファイルパスを送るようにしています。


## UniDragAndDropForMacとしてリリース

どうせ作るならOSSで作りたい欲求が有るので、以下のリポジトリにObjCソース含め全て公開しています。
[UniDragAndDropForMac](https://github.com/baobao/UniDragAndDropForMac)

## インストール方法

[UniDragAndDropForMac.unitypackage](https://github.com/baobao/UniDragAndDropForMac/releases)を用意しているのでインポートして使ってください。


## 最後に

欲しい物が無い、探しても見つからない、そんな時は今後もネイティブの力を使って作っていこうと思います。





## 環境

- Unity2019.4.4f1
- macOS Catalina 10.15.5