類似機能のご案内
スタンドアロンプレイヤーではなく、Unity6のエディター自体のウィンドウの角の丸めを抑制したい場合はUnity6のエディターのウィンドウの角を丸めないようにする機能を作りましたをご覧ください
はじめに
Windows11にアップデートしてしばらく経ったのでそこそこ新しいUIに慣れてきました。ですが、UIに関する慣れではどうにもならない問題があります。
それは、「ウィンドウの角が丸いせいでゲームなどで四隅にある表示などが見えなくなる」であったり、「OS経由のスクリーンショットでも角が丸い状態で撮影されるため、丸い角の下が見えてしまったり、その位置に本来ある表示が撮影できなくなる」といったもので、些細なようで結構問題になりそうな事柄だったりします。
本題
そこで、UnityのWindows向けスタンドアロンプレイヤーのウィンドウの角の種類を変更する機能を作りました。導入したプロジェクトをWindows向けにビルドしたプレイヤーのウィンドウのみに作用します。

上の画像のように、ビルドしたWindows用のプレイヤーの角を任意のタイミングで変えることができます。
検証した環境
- Windows 11 24H2
- Unity 2021.3.45f1 / Unity 2022.3.62f1 / Unity 6000.0.53f1
(いずれのバージョンも、API Compatibility Levelは.NET Standard 2.1、Scripting BackendはMonoとIL2CPPの両方で動作します)
使い方
導入方法
-
導入にはGitが必要になります。事前にインストールを済ませてください
Windows向けのGitの配布サイト -
以下の文字列(Git URL)をコピーする
https://github.com/HWataame/UnityPlayerWindowCorner.git -
導入するUnityのプロジェクトを開き、Package Managerを表示し、Install package from git URL...を選択する

-
Assembly Definition Assetの管理下から使用する場合は、対象のasmdefファイルのAssembly Definition Referencesに
HW.UnityPlayerWindowCornerを追加する

使用方法
ウィンドウの角の種類を変更する
WindowCorner.Set(WindowCornerType cornerType)メソッド(名前空間HW.UnityPlayerWindowCorner内)を実行すると、スタンドアロンプレイヤーのウィンドウの角の種類を変更できます。
// 角の種類
var cornerType = HW.UnityPlayerWindowCorner.WindowCornerType.DoNotRound;
// スタンドアロンプレイヤーのウィンドウの角の種類を変更する
HW.UnityPlayerWindowCorner.WindowCorner.Set(cornerType);
ウィンドウの角の種類は、
| ウィンドウの角の種類 | 外見 | 備考 |
|---|---|---|
WindowCornerType.Default |
丸い角 | OSに委任する 24H2時点ではWindowCornerType.Roundと同じ外見になる |
WindowCornerType.DoNotRound |
四角い角 | Windows10時代のような外見になる |
WindowCornerType.Round |
丸い角 | 24H2時点では未指定時(WindowCornerType.Default)と同じ外見になる |
WindowCornerType.RoundSmall |
丸い角 |
WindowCornerType.Roundよりも丸みが小さい |
の4種類から設定できます。それぞれは以下の画像のような見た目になります。

利用例:アプリケーション起動時に角を四角くする
Windowsのビルドに載るソースコードに
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ProcessWindowCornerOnStartup()
{
WindowCorner.Set(WindowCornerType.DoNotRound);
}
と記述する(メソッド名やアクセス修飾子はお好みで変更可)と、スタンドアロンプレイヤーのウィンドウの角を最初から四角くすることができます。
非対応環境での実行時の警告ログを設定する
この機能はWindows11で動作するスタンドアロンプレイヤーを対象にしているため、当然ながら他の環境では何も起こりません。
非対応の環境(特にWindows上で動作するUnityEditor)でもWindowCorner.Setが呼び出されていることを確認できるようにするため、非対応の環境では実行すると警告が出るようにしています(以下の画像は、Windows上で動作するUnityEditorのPlayModeで呼び出した時のものです)。

場合によっては、警告の出る仕様が煩わしくなることもあるかと考え、警告ログの出力を任意に設定できるようにしました。
WindowCorner.IsOutputLogプロパティ(名前空間HW.UnityPlayerWindowCorner内)にfalseを指定すると、警告ログを出力しないようにできます(出力を再開させたい場合は同プロパティにtrueを指定します)。
実装について
概要
この機能は、すべてC#で記載されていて、WINAPIをP/Invokeで呼び出すことによってウィンドウの角を操作しています。
また、使用しているAPIがおおよそUnityEditorのウィンドウの角を操作する機能の時と同じであるため、重複する部分は簡潔に説明します。
ウィンドウの角の種類を列挙型にする
DwmSetWindowAttribute関数には4バイトの整数値への参照を渡せばいいため、内部値の型がuintの列挙型を定義します。今回はWindowCornerTypeとして定義しました。
| 値 | 外見 | 対応するC++使用時の表記 |
|---|---|---|
| 0 | 角の外見をOSに委任する(24H2時点では丸い角(2)) | DWMWCP_DEFAULT |
| 1 | 四角い角 | DWMWCP_DONOTROUND |
| 2 | 丸い角 | DWMWCP_ROUND |
| 3 | 丸い角(2)よりも丸みが小さい角 | DWMWCP_ROUNDSMALL |
ウィンドウの角を操作する
DwmSetWindowAttribute関数を呼び出します。この関数のシグネチャは
// WINAPI(C++)での定義
HRESULT DwmSetWindowAttribute(HWND, DWORD, LPCVOID, DWORD);
なのですが、今回はウィンドウの角の操作にしか使用しないため、先述の列挙型を使用して
// C#側でDwmSetWindowAttributeをウィンドウの角の操作のために呼び出すための定義
[DllImport("dwmapi.dll", CallingConvention = CallingConvention.Winapi)]
static extern uint DwmSetWindowAttribute(nint windowHandle, uint attribute, ref WindowCornerType cornerType, uint dataSize);
と定義します。
型の読み替えについて
- 戻り値の
HRESULTは32ビット整数型であるため、今回はuintに読み替える1 - 第1引数の
HWNDは(今時あまり気にすることはないですが)ビルド対象のビット数で型のサイズが変わるため、nintに読み替える - 第2、第4引数の
DWORDは32ビット符号なし整数型であるためuintに読み替える - 第3引数の
LPCVOID(C++ではconst void*と等価)は本来であればnintに読み替えるが、今回はウィンドウの角の操作にしか使用しないため、この用途では32ビット整数値へのポインタを渡せばよい。そのため特例で先ほど定義した32ビット整数型の列挙型を参照渡しにしたref WindowCornerTypeに読み替える
スタンドアロンプレイヤーのウィンドウを取得する
ウィンドウの角を操作する方法を用意しても操作する対象がなければ意味がないため、スタンドアロンプレイヤーのウィンドウを取得します。とは言ってもUnity自体にはプレイヤーのウィンドウハンドルを取得する機能が公開されていないため、
-
GetCurrentProcessId関数を呼び出して自身のプロセスIDを取得しておく -
EnumWindows関数を呼び出して現在のシステムに存在するトップレベルのウィンドウをすべて列挙する -
GetWindowThreadProcessId関数を呼び出して、列挙されたウィンドウハンドルを生成したプロセスのIDを取得する - 2.で取得した自身のプロセスIDと比較する
という処理の流れをとります。3.と4.はEnumWindowsのコールバック関数内での処理で、列挙されるウィンドウハンドルの数ぶん繰り返されます。
このとき、自身(プレイヤー)と列挙されたウィンドウのプロセスIDが同じであれば、そのウィンドウはプレイヤーのプロセスが生成したものであるため、そのウィンドウに対して角の操作を行います。
これらの処理で使用する関数のC#での定義は以下のようにします。
(また、EnumWindowsなどに出現するSetWindowCornerCallbackParametersは、今回必要な値をコールバック関数に渡すために定義したref構造体で、自身のプロセスID(uint)と、設定する角の種類(WindowCornerType)の2種類のフィールドを含みます)
// 1. 現在のプロセスのIDを取得する
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
static extern uint GetCurrentProcessId();
// 2. EnumWindowsのコールバック関数のデリゲート
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
delegate bool EnumWindowsProc(nint windowHandle, ref SetWindowCornerCallbackParameters parameters);
// 2. トップレベルのウィンドウを列挙する
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows(EnumWindowsProc callback, ref SetWindowCornerCallbackParameters parameters);
// 3. ウィンドウを生成したプロセスのIDを取得する
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi)]
static extern uint GetWindowThreadProcessId(nint windowHandle, out uint processId);
型の読み替えについて(追加説明分のみ)
-
EnumWindowsの第1引数はWNDENUMPROCで、HWNDとLPARAMを引数に持ち、BOOL値を返す関数ポインタの型である。関数ポインタは等価の引数と戻り値の組み合わせのデリゲートで読み替えることができるため、今回は同等の引数と戻り値型のデリゲート型を定義し、そのデリゲート型で読み替えた - デリゲート型と
EnumWindowsの第2引数のLPARAMはポインタ型で、本来であればnintに読み替えるが、今回はパラメーターを渡す必要があるため、構造体の参照渡しとして読み替えている。なお、今回使用する構造体はref構造体であるため参照が示す先は必ずスタックになる
ネイティブ関数のコールバックにC#のメソッドを渡す時の注意事項
MonoなどのJIT環境で動かしているうちは問題ないのですが、IL2CPPでビルドした場合は対策を講じないとコールバックを渡そうとすると例外が出ます。[AOT.MonoPInvokeCallback(System.Type)]属性をコールバックに渡すメソッドに付与し、その属性でコールバックの型を指定するとIL2CPP環境でも動くようになります。
ライセンス
MITライセンスです。
リポジトリ
-
例外が出ることを許容する設計である場合ば
[DllImport]属性にPreserveSig = falseを指定して、戻り値をvoidにする定義も可能です。その場合は失敗時に例外が発生するようになります。 ↩
