[Unity 3d] 使用 Unity 开发无边框、可拖拽、缩放、置顶、最小化的应用

文章出处:
https://www.jianshu.com/p/c81342e96def

using System;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public static class PInvoke
{
    static IntPtr ptr;
    public static IntPtr UnityHWnd
    {
        get
        {
            if (ptr == null || ptr == IntPtr.Zero)
            {
                ptr = GetUnityWindow();
            }
            return ptr;
        }
    }

    #region 常量
    //https://docs.microsoft.com/zh-cn/windows/win32/winmsg/window-styles
    public const ulong WS_MAXIMIZEBOX = 0x00010000L; //最大化的按钮禁用
    public const ulong WS_DLGFRAME = 0x00400000L; //不现实边框
    public const ulong WS_SIZEBOX = 0x00040000L; //调大小的边框
    public const ulong WS_BORDER = 0x00800000L; //边框
    public const ulong WS_CAPTION = 0x00C00000L; //标题栏

    // Retreives pointer to WindowProc function.
    public const int GWLP_WNDPROC = -4; //Windows 绘制方法的指针
    public const int WM_SIZING = 0x214;
    public const int WS_POPUP = 0x800000;
    public const int GWL_STYLE = -16;
    //边框参数
    public const uint SWP_SHOWWINDOW = 0x0040;
    public const uint SWP_NOMOVE = 0x0002;
    public const int SW_SHOWMINIMIZED = 2;//(最小化窗口)
    // Name of the Unity window class used to find the window handle.
    public const string UNITY_WND_CLASSNAME = "UnityWndClass";
    #endregion

    #region Win32 API
    // Passes message information to the specified window procedure.
    [DllImport("user32.dll")]
    public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    //获得窗口样式
    [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
    public static extern IntPtr GetWindowLongPtr(IntPtr hwnd, int nIndex);
    // Retrieves the dimensions of the bounding rectangle of the specified window.
    // The dimensions are given in screen coordinates that are relative to the upper-left corner of the screen.
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool GetWindowRect(IntPtr hwnd, ref RECT lpRect);
    // Retrieves the coordinates of a window's client area. The client coordinates specify the upper-left
    // and lower-right corners of the client area. Because client coordinates are relative to the upper-left
    // corner of a window's client area, the coordinates of the upper-left corner are (0,0).
    [DllImport("user32.dll")]
    public static extern bool GetClientRect(IntPtr hWnd, ref RECT lpRect);

    // 改变指定窗口的属性 ,该函数还在额外窗口内存中的指定偏移处设置一个值。
    [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    //设置当前窗口的显示状态
    [DllImport("user32.dll")]
    public static extern bool ShowWindow(System.IntPtr hwnd, int nCmdShow);

    //设置窗口位置,大小
    [DllImport("user32.dll")]
    public static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

    // 通过将每个窗口的句柄依次传递给应用程序定义的回调函数,枚举与线程关联的所有非子窗口。
    [DllImport("user32.dll")]
    private static extern bool EnumThreadWindows(uint dwThreadId, EnumWindowsProc lpEnumFunc, IntPtr lParam);
    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
    //检索调用线程的线程标识符。
    [DllImport("kernel32.dll")]
    private static extern uint GetCurrentThreadId();
    // 检索指定窗口所属的类的名称。
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);


    //窗口拖动
    [DllImport("user32.dll")]
    public static extern bool ReleaseCapture();
    [DllImport("user32.dll")]
    public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);


    #endregion
    #region Static Function
    //最小化窗口
    //具体窗口参数看这     https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
    public static void SetMinWindows()
    {
        if (!Application.isEditor)
        {
            ShowWindow(UnityHWnd, SW_SHOWMINIMIZED);
        }
    }

    //拖动窗口
    public static void DragWindow()
    {
        ReleaseCapture();
        SendMessage(UnityHWnd, 0xA1, 0x02, 0);
        SendMessage(UnityHWnd, 0x0202, 0, 0);
    }

    public static IntPtr GetUnityWindow()
    {
        var unityHWnd = IntPtr.Zero;
        EnumThreadWindows(GetCurrentThreadId(), (hWnd, lParam) =>
        {
            var classText = new StringBuilder(UNITY_WND_CLASSNAME.Length + 1);
            GetClassName(hWnd, classText, classText.Capacity);

            if (classText.ToString() == UNITY_WND_CLASSNAME)
            {
                unityHWnd = hWnd;
                return false;
            }
            return true;
        }, IntPtr.Zero);
        return unityHWnd;
    }

    #endregion
    #region Assistant
    /// 
    /// WinAPI RECT definition.
    /// 
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
        public override string ToString()
        {
            return "left = {Left}\nright = {Right}\ntop = {Top}\nbottom = {Bottom}";
        }
    }
    #endregion
}

实现:

  1. 无边框

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
    static void InitAppWindow()
    {
    if (Application.isEditor) return;
    var dwStyles = GetWindowLongPtr(UnityHWnd, GWL_STYLE);
    var sty = ((ulong)dwStyles);
    sty &= ~(WS_CAPTION| WS_DLGFRAME)&WS_POPUP;
    SetWindowLongPtr(UnityHWnd, GWL_STYLE, (IntPtr)sty);
    }
    注意:设置无边框后,任务栏点击无法实现 App 最小化,如果有解决方案欢迎提 PR

  2. 最小化

    //最小化窗口
    //具体窗口参数看这 https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
    public static void SetMinWindows()
    {
    if (!Application.isEditor)
    {
    ShowWindow(UnityHWnd, SW_SHOWMINIMIZED);
    }
    }
    //设置当前窗口的显示状态
    [DllImport(“user32.dll”)]
    public static extern bool ShowWindow(System.IntPtr hwnd, int nCmdShow);

Tips: 代码中用到的 win32 api 以及常量请参阅本文配套 GitHub 仓库:点我。

  1. 缩放

using UnityEngine;
using UnityEngine.EventSystems;
using static PInvoke;

public class WindowResizeHandler : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IBeginDragHandler, IEndDragHandler, IDragHandler
{
bool isDragging = false;
bool isInsideOfHandler = false;
public Vector2 hotspot = Vector2.zero;

// Minimum and maximum values for window width/height in pixel.
[SerializeField]
private int minWidthPixel = 768;
[SerializeField]
private int maxWidthPixel = 2048;

public Texture2D wnes;
private float aspect = 16 / 9f;
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData) => isDragging = eventData.pointerId==-1;
void IDragHandler.OnDrag(PointerEventData eventData) => WindowProcess(eventData);
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
{
    isDragging = false;
    if (!isInsideOfHandler)
    {
        Cursor.SetCursor(default, default, CursorMode.Auto);
    }
}
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
{
    isInsideOfHandler = true;
    Cursor.SetCursor(wnes, hotspot, CursorMode.Auto);
}
void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
{
    isInsideOfHandler = false;
    if (!isDragging)
    {
        Cursor.SetCursor(default, default, CursorMode.Auto);
    }
}
private void WindowProcess(PointerEventData eventData)
{
    if (Application.isEditor || eventData.pointerId != -1) return;
    RECT rc = default;
    GetWindowRect(UnityHWnd, ref rc);
    int newWidth = Mathf.Clamp(rc.Right - rc.Left + Mathf.RoundToInt(eventData.delta.x), minWidthPixel, maxWidthPixel);
    int newHeight = Mathf.RoundToInt(newWidth / aspect);
    SetWindowPos(UnityHWnd, 0, rc.Left, rc.Top, newWidth, newHeight, SWP_SHOWWINDOW);
}

}

Tips: 代码中用到的 win32 api 以及常量请参阅本文配套 GitHub 仓库:点我。

  1. 拖拽窗体

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using static PInvoke;
[RequireComponent(typeof(Graphic))]
public class WindowMoveHandler : MonoBehaviour,IPointerDownHandler,IPointerUpHandler,IPointerExitHandler
{
static bool isDrag = false;
void IPointerDownHandler.OnPointerDown(PointerEventData eventData) => isDrag = eventData.pointerId==-1;
void IPointerExitHandler.OnPointerExit(PointerEventData eventData) => isDrag = false;
void IPointerUpHandler.OnPointerUp(PointerEventData eventData) => isDrag = !(eventData.pointerId==-1);
private void Update()
{
if (!Application.isEditor&&isDrag)
{
DragWindow();
}
}
}

//拖动窗口
public static void DragWindow()
{
    ReleaseCapture();
    SendMessage(UnityHWnd, 0xA1, 0x02, 0);
    SendMessage(UnityHWnd, 0x0202, 0, 0);
}
  1. 置顶窗体(Topmost)

    // 应用窗口置顶
    public static void SetTopmost(bool isTopmost)
    {
    if (!Application.isEditor)
    {
    int ptr = isTopmost ? -1 : -2;
    SetWindowPos(UnityHWnd, ptr, 0, 0, 0, 0, 1 | 2 | 64);//0x0040
    }
    else
    {
    Debug.LogWarning($“{nameof(PInvoke)}: 为避免编辑器行为异常,请打包 exe 后测试!”);
    }
    }
    Tips: 代码中用到的 win32 api 以及常量请参阅本文配套 GitHub 仓库:点我。

扩展阅读:
System-Tray-Icon-For-Unity - 为 Unity 开发的 App 提供 System Tray Icon,后面计划使用 Unity MenuItem 的方式,现在使用起来感觉不怎么便利。
UnitySkipSplash - 几句话跳过 Unity Logo 闪屏界面,别问我为何这么 big 胆,仅供学习用途嘛。
Simple-Customize-ERP-System - 本文配套的 GitHub 仓库。
写到最后:
版权所有,转载请注明出处!

作者:雨落随风
链接:https://www.jianshu.com/p/c81342e96def
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(unity3d,unity,3d,游戏引擎)