最开始开发时,我是以开发壁纸游戏的想法在制作,直到效果基本实现了才想到,挂机游戏是置顶运行而不是运行在桌面底部。于是只好推倒重来,不过好在都是围绕窗体属性开发,也算是丰富了相关的知识。后续或许可以写个壁纸游戏开发的文章哈哈。那么在项目正式开始前,让我们大概来描述一下桌面挂机游戏的显示需求:
需求1.窗体只会占据屏幕底部的一部分,会置顶显示,不会被其他窗体覆盖
需求2.窗体是无边框的,且不会有Windows窗体默认的三个按钮
需求3.若我们游戏窗体背景是透明的,应该能保证我们能看到其后面的内容。
现在让我们逐一实现上述的三个需求,请注意,本篇开发内容只考虑了Windows桌面平台的使用需求,Windows通过Win32和DWM等头文件向开发者提供了许多系统和窗体相关的开发接口,这些接口直接面向C++。C#脚本则可以通过DLLImport来调用相关函数。:
解决1:我们可以直接通过Win32的SetWindowPos函数实现,这个函数可以直接设置窗体的分辨率、置顶或放于底部。
解决2:可以通过Win32的SetWindowLong来实现,这个函数可以调整窗体的诸多属性,不止是边框按钮,列如我们浏览器的alt菜单栏也可以在这其中添加,具体可以参考官方文档。
解决3:想要让游戏背景透明我们需要让windows窗体玻璃化,保证其在桌面平台能够有透明效果,这需要调用DWM的DwmExtendFrameIntoClientArea函数。DWM提供了很多窗体绘制的功能,还能够实现毛玻璃、添加图标等效果,具体可以参考这个文章。同时要让Unity经由Camera渲染后的画面背景颜色透明,通过将MainCamera的ClearFlags设置为SolidColor、BackGround设置为纯黑色,并将PlayerSetting中的Use DXGI Filp Model Swapchain for D3D1取消勾选就能够实现了。这里照抄了这个文章。
另外还需要让Unity发布后允许修改分辨率以及后台运行,具体可以按需参考下图修改。
最终效果能够通过以下代码实现,将以下脚本组件添加到场景中,发布运行就能够实现显示效果了!
using System; using System.Runtime.InteropServices; using UnityEngine; public class MyWindow : MonoBehaviour { //项目名称,请填写为PlayerSetting中的ProductName private string projectName = "DesktopHook"; IntPtr currentIntPtr; private IntPtr programIntPtr = IntPtr.Zero; Resolution[] resolutions;//分辨率 private Rect screenPosition;//最终的屏幕的位置和长宽 void Awake() { #if UNITY_EDITOR print("unity内运行程序"); return; #endif //获得游戏窗体的句柄,请注意projectName应当取值为PlayerSetting中的ProductName currentIntPtr = Win32.FindWindowExA(IntPtr.Zero, IntPtr.Zero, null, projectName); //获取当前屏幕分辩率 resolutions = Screen.resolutions; //游戏窗体宽度,这里设置为与最大分辨率同宽 screenPosition.width = resolutions[resolutions.Length - 1].width; //游戏窗体高度,这里设置为与任务栏一起占用屏幕底部1/3的高度 screenPosition.height = Screen.currentResolution.height / 3 - GetTaskBarHeight(); //在Unity中设置游戏分辨率 Screen.SetResolution((int)screenPosition.width, (int)screenPosition.height, false); screenPosition.x = 0; screenPosition.y = Screen.currentResolution.height / 3 * 2; //取消窗口自带的边框 Win32.SetWindowLong(currentIntPtr, Win32.GWL_STYLE, Win32.GetWindowLong(IntPtr.Zero, Win32.GWL_STYLE) & ~Win32.WS_BORDER & ~Win32.WS_THICKFRAME & ~Win32.WS_CAPTION); //设置游戏窗体的分辨率、位置、置顶显示 bool result = Win32.SetWindowPos(currentIntPtr, -1, (int)screenPosition.x, (int)screenPosition.y, (int)screenPosition.width, (int)screenPosition.height, Win32.SWP_SHOWWINDOW); ////设置窗体玻璃化 var margins = new Win32.MARGINS() { cxLeftWidth = -1 }; Win32.DwmExtendFrameIntoClientArea(currentIntPtr, ref margins); } /// <summary> /// 获取任务栏高度 /// </summary> /// <returns>任务栏高度</returns> private int GetTaskBarHeight() { int taskbarHeight = 10; IntPtr hWnd = Win32.FindWindow("Shell_TrayWnd", 0); //找到任务栏窗口 Win32.RECT rect = new Win32.RECT(); Win32.GetWindowRect(hWnd, ref rect); //获取任务栏的窗口位置及大小 taskbarHeight = (int)(rect.Bottom - rect.Top); //得到任务栏的高度 return taskbarHeight; } } /// <summary> /// 设置窗体用到的和一些常用Win32API /// </summary> public static class Win32 { public const int WS_THICKFRAME = 262144; public const int WS_BORDER = 8388608; public const uint SWP_SHOWWINDOW = 0x0040; public const int GWL_STYLE = -16; public const int WS_CAPTION = 0x00C00000; public const int GWL_EXSTYLE = -20; public const int WS_EX_LAYERED = 0x00080000; public const int LWA_COLORKEY = 0x00000001; public const int LWA_ALPHA = 0x00000002; public const int WS_EX_TRANSPARENT = 0x20; [DllImport("Dwmapi.dll")] public static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins); [DllImport("user32.dll")] public static extern IntPtr SetWindowLong(IntPtr hwnd, int _nIndex, int dwNewLong); //当前窗口 [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern IntPtr FindWindow(string className, string winName); [DllImport("user32.dll")] public static extern IntPtr SendMessageTimeout(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam, uint fuFlage, uint timeout, IntPtr result); [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc proc, IntPtr lParam); public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam); [DllImport("user32.dll")] public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string className, string winName); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hwnd, int nCmdShow); [DllImport("user32.dll")] public static extern IntPtr SetParent(IntPtr hwnd, IntPtr parentHwnd); //使用查找任务栏 [DllImport("user32.dll")] public static extern IntPtr FindWindow(string strClassName, int nptWindowName); //获取窗口位置以及大小 [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); [DllImport("user32.dll")] public static extern IntPtr FindWindowExA(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow); //设置窗口位置,尺寸 [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); //设置无windows自带边框 [DllImport("user32.dll")] public static extern IntPtr SetWindowLongPtr(IntPtr hwnd, int _nIndex, int dwNewLong); [DllImport("user32.dll")] public static extern int GetWindowLong(IntPtr hWnd, int nIndex); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; //最左坐标 public int Top; //最上坐标 public int Right; //最右坐标 public int Bottom; //最下坐标 } public struct MARGINS { public int cxLeftWidth; public int cxRightWidth; public int cyTopHeight; public int cyBottomHeight; } }
现在我们的游戏主窗体已经可以置顶运行了,在游戏过程中,会需要有一些设置、菜单等功能页面让玩家浏览,一般大多类似游戏都不会把对应页面直接放在置顶游戏窗体内(可能是窗体太小了,还会会置顶占用)。
通过Unity等引擎发布的程序,在Windows看来是一个单窗体程序,因而游戏内制作的UI窗体都只能在游戏窗体中显示,而不是一个独立的Windows窗体,如果我们想要制作一个可以在游戏窗体外随意显示的功能页面(比如设置/商店等等页面)就需要另辟蹊径了。这就是下一文章的内容啦。
如果你对合作开发游戏有兴趣,或是有什么好建议欢迎评论或联系我哦! QQ:2763686216
暂无关于此日志的评论。