废话:

woc····累死我了,搞了一下午,颈椎都要折了。。。赶紧趁热打铁记录一下。


有点用的铺垫:

为什么要写这篇博客呢?模拟鼠标点击又是什么意思呢?

先来理解鼠标点击,比如,你在浏览某些网站的时候,突然看到一张劲爆的美女图,心血来潮你想要看的更仔细是不是?于是你移动鼠标点了点美女,进入了新的页面,顿时看到了高清无码的美女图。爽!这是你人为控制的鼠标点击。

但是有些时候,我不想人为控制鼠标去点击某些东西,我想要程序内部给个坐标点,然后在该点模拟鼠标点击。比如我输入了该美女在屏幕坐标系下的位置,我想要程序替我点击了,从而进入新的页面。这就是我理解的模拟鼠标点击。

恩。。。为什么又在打擦边球啊摔!


继续废话:

好了言归正传。在实现该功能之前,我翻阅了我都不知道多少篇博客,但是恕我直言,各位大大都讲的太不详细了啊!!今天本博就来手把手讲述那些年在工地搬砖的故事。


重要!乖乖坐好看:

先上Demo,打开你的Unity,然后创建一个Button,让这个Button至于Canvas的中间

unity鼠标点击触发 unity鼠标点击模型_屏幕坐标

相信聪明的你应该已经知道我们要干啥了吧。

没错我们就是要让程序点击这个Button,而不是我们鼠标去点击这个Button!!!

亲爱的你,来思考一下,如果是你,你会怎么写这个程序?

前提:很明显,我们要知道这个Button的位置坐标,虽然在Unity中都各种坐标系:屏幕坐标、世界坐标、视口坐标。但是它们的转换都是Unity封装好的函数,直接调用就行了,所以我就不赘述了。这里我们假设Button的坐标,我们已知的是屏幕坐标

思考:首先我们知道在Unity中并没有封装好的模拟鼠标点击的函数,但是在Windows下有封装好的模拟鼠标点击的函数,那就是SetCursorPos()&&mouse_event()。因此我们要想办法调用这两个函数。来看看这两个函数,要用它们必须导入user32.dll:

[DllImport("user32.dll")]
private static extern int SetCursorPos(int x,int y); //设置光标位置
[DllImport("user32.dll")]
private static extern bool GetCursorPos(ref int x,ref int y); //获取光标位置
[DllImport("user32.dll")]
static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo); //鼠标事件

到了这里,也许你开始激动了,我现在知道Button的屏幕坐标了,那我直接把这个坐标传给SetCursorPos不就行了吗?

非也非也。这里还牵扯到一个知识点,那就是Unity的屏幕坐标系和系统的桌面坐标系是不一样的,请看:

unity鼠标点击触发 unity鼠标点击模型_Game_02

unity鼠标点击触发 unity鼠标点击模型_System_03

相信你可以看到,Unity的屏幕坐标系是从左下角开始,而桌面的屏幕坐标系是从左上角开始

也许你看到这里又开始激动了,我直接拿Screen.Height-Pos.y不就把Unity坐标转换到桌面坐标了吗?非也非也。也许Unity不是在全屏模式下呢?

unity鼠标点击触发 unity鼠标点击模型_System_04

如果这里你么有看懂的话,请去看这篇博客: 这篇博客的代码在全屏模式下也许是可以运行的,但是如果Unity的Game没有设置全屏,就没招了。

 

别担心,本博可以给你不在全屏模式的思路,并且本博也验证了在某些情况下,全屏模式,上面的博客代码也略有瑕疵。

来看Unity这个软件的界面,你会发现,在16:10的情况下,场景并没有铺满Game窗口,而是还有两块儿黑色的边,那么这些琐碎的东西是否还要考虑在内?答案是:是的:

unity鼠标点击触发 unity鼠标点击模型_unity_05

 你一定有很多疑问,比如说,Unity屏幕坐标是从哪里开始作为原点的?我的Button坐标是怎么先知的?如何获取Unity窗口在桌面屏幕上的位置?如何获取Game窗口在桌面屏幕上的位置?

Unity屏幕坐标是从哪里开始作为原点的?怎么获取Unity的屏幕分辨率?

Debug.Log("Unity的屏幕分辨率"+UnityEngine.Screen.width+"x"+UnityEngine.Screen.height); 

Debug.Log("系统的分辨率"+UnityEngine.Screen.currentResolution); //也就是桌面屏幕分辨率

unity鼠标点击触发 unity鼠标点击模型_Game_06

unity鼠标点击触发 unity鼠标点击模型_Game_07

没错就是上面这块红色的区域。。如果你不信,你可以测试一下,别问我怎么知道的。我就是测试出来的。我的测试代码。然后我就在Play模式下,用鼠标去探测,发现那里是原点,那里正好到Screen.Width,那里正好到Screen.Height(箭头的位置):

unity鼠标点击触发 unity鼠标点击模型_System_08

并且这里我知道了点击Button的坐标:(249,158)。

然后你说那完了,就这点区域,我怎么转成系统的桌面坐标啊。别急,你只要能获取到Game窗口在桌面坐标系下的坐标不就行了?

它们的嵌套关系是这样的:

unity鼠标点击触发 unity鼠标点击模型_unity_09

应用到实际场景中就是这样的:

unity鼠标点击触发 unity鼠标点击模型_unity鼠标点击触发_10

如果你能看明白这两幅图,那就好办啦,很显然。我Unity屏幕下的Button的坐标转换成系统桌面坐标系下的坐标的公式就是:

Dx+x+(Window_Width-Resolution_Width)/2, Dy+(Window_Height-y)

那么如何在Windows下获取窗口的位置和大小的信息呢?这个Windows也是给API了:

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetForegroundWindow(); //获取当前活动窗口的句柄
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr FindWindow(string strClassName, string strWindowName); //根据要找窗口的类或者标题查找窗口,只能找父窗口
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); //在窗口列表中寻找与指定条件相符的第一个子窗口

[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle); //获取窗口大小

因此我们最终的代码如下我们把它附载到Button上:

unity鼠标点击触发 unity鼠标点击模型_unity鼠标点击触发_11

/**
*┌──────────────────────────────────────────────────────────────┐
*│ Description: 模拟鼠标点击的操作                                                   
*│ Author:#Keneyr#                                              
*│ Version:#1.0#                                                
*│ Date:#2019.7.30#  
*│  UnityVersion: #Unity2018.3.2f#                            
*└──────────────────────────────────────────────────────────────┘
*┌──────────────────────────────────────────────────────────────┐                                   
*│ ClassName:#MouseSimulator#                                      
*└──────────────────────────────────────────────────────────────┘
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
//using System.Diagnostics;

public class MouseSimulator : MonoBehaviour
{
    [DllImport("user32.dll")]
    private static extern int SetCursorPos(int x,int y); //设置光标位置
    [DllImport("user32.dll")]
    private static extern bool GetCursorPos(ref int x,ref int y); //获取光标位置
    [DllImport("user32.dll")]
    static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo); //鼠标事件
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetForegroundWindow(); //获取当前活动窗口的句柄
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr FindWindow(string strClassName, string strWindowName); //根据要找窗口的类或者标题查找窗口,只能找父窗口
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); //在窗口列表中寻找与指定条件相符的第一个子窗口
    //[DllImport("user32.dll")]
    //private static extern int EnumChildWindows(IntPtr hWndParent, CallBack lpfn, int lParam); //枚举一个父窗口的所有子窗口
    //public delegate bool CallBack(IntPtr hwnd, int lParam);
    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle); //获取窗口大小

    //这个枚举同样来自user32.dll
    [Flags]
    enum MouseEventFlag : uint
    {
        Move = 0x0001,
        LeftDown = 0x0002,
        LeftUp = 0x0004,
        RightDown = 0x0008,
        RightUp = 0x0010,
        MiddleDown = 0x0020,
        MiddleUp = 0x0040,
        XDown = 0x0080,
        XUp = 0x0100,
        Wheel = 0x0800,
        VirtualDesk = 0x4000,
        Absolute = 0x8000
    }

    //这个也来自uer32.dll
    [StructLayout(LayoutKind.Sequential)]
    public struct Rect 
    {
        public int Left { get; set; } //最左坐标
        public int Top { get; set; } //最上坐标
        public int Right { get; set; } //最右坐标
        public int Bottom { get; set; } //最下坐标
    }

    //声明Unity屏幕坐标转换和系统屏幕坐标转换所需要的变量,目前是系统坐标嵌套Game窗口,Game窗口嵌套分辨率窗口
    private int Dx,Dy; //窗口在桌面坐标系下的位置
    private int Window_Width,Window_Height; //窗口的宽和高
    private static int Resolution_Width = UnityEngine.Screen.width;
    private static int Resolution_Height = UnityEngine.Screen.height; //屏幕分辨率,其实就是Game视图下有景色的地方,是嵌套中最小的

    bool hasMouseDown = false;
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("MouseSimulator Start");
        //模拟键盘输入
        //SendKeys.Send("{BACKSPACE}");

        Debug.Log("Unity的屏幕分辨率"+UnityEngine.Screen.width+"x"+UnityEngine.Screen.height); //widthxheight
        Debug.Log("系统的分辨率"+UnityEngine.Screen.currentResolution); //widthxheight

        //GetTheCurrentWindowInfo();

        GetTheChildWindowInfo();

    }


    // Update is called once per frame
    void Update()
    {

        // if (Input.GetKey(KeyCode.Backspace))
        // {
        //     Debug.Log("backspace is pressed");
        // }

        //获取鼠标点击的位置
        if(Input.GetMouseButtonDown(0))
        {
            Debug.Log("鼠标点击位置是:"+Input.mousePosition);
            //Debug.Log(Camera.main.ScreenToWorldPoint(Input.mousePosition));
        }

        if(!hasMouseDown)
        {
            if (Time.time > 2)
            {
                Debug.Log("Simulator Mouse Down");
                
                //模拟鼠标在一个按钮上点击,这个按钮会调用下面的CloseSelf()方法
                //SetCursorPos(20, UnityEngine.Screen.height-20); //这种写法只适合Unity的全屏模式

               
                //假设button的坐标是249,158
                Vector2 pos = ConvertUnityScreenToSystemScreen(249,158);
                
               
                SetCursorPos((int)pos.x,(int)pos.y);

                mouse_event(MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero);
                mouse_event(MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);
                hasMouseDown = true;
            }
            
        }
    }

    //模拟鼠标左键点击
    public void MouseClickSimulate(int x,int y)
    {
        SetCursorPos(x,y);
        mouse_event(MouseEventFlag.LeftDown,0,0,0,UIntPtr.Zero);
        mouse_event(MouseEventFlag.LeftUp,0,0,0,UIntPtr.Zero);

    }
    public void CloseSelf()
    {
        Debug.Log("点击鼠标,调用了CloseSelf()");
    }
    
    //坐标转换:Unity屏幕坐标到系统屏幕坐标转换
    public Vector2 ConvertUnityScreenToSystemScreen(int x,int y)
    {
        //
        int pointSystemCoordinatex = Dx + x + (Window_Width-Resolution_Width)/2;
        int pointSystemCoordinatey = Dy + (Window_Height-y);
        Vector2 pos = new Vector2(pointSystemCoordinatex,pointSystemCoordinatey);
        return pos;
    }

    //获取当前活动窗口的信息
    public void GetTheCurrentWindowInfo()
    {
        //获取当前活动窗口的句柄
        IntPtr ptr = GetForegroundWindow();
        GetWindowInfo(ptr);

    }

    //根据进程名字找到该窗口,并获取其信息
    public void GetTheWindowInfoByProcessName(String processname)
    {
        System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(processname);
        System.Diagnostics.Process lol = processes[0];
        IntPtr ptr = lol.MainWindowHandle; //获取到窗口的句柄
        GetWindowInfo(ptr);
    }

    //获取当前主窗口下特定子窗口的信息:获取Unity下的Game视图窗口
    public void GetTheChildWindowInfo()
    {
       IntPtr father_ptr = FindWindow("UnityContainerWndClass",null);
       IntPtr child_ptr = FindWindowEx(father_ptr,IntPtr.Zero,"UnityGUIViewWndClass","UnityEditor.GameView");
       if(child_ptr!=IntPtr.Zero)
       {
           GetWindowInfo(child_ptr);
       }
       else
       {
           Debug.Log("Cannot find the childWindow");
           return;
       }

    }

    //根据窗口句柄获取窗口基本信息:位置和大小
    private void GetWindowInfo(IntPtr ptr)
    {
        Rect currentRect = new Rect();
        GetWindowRect(ptr, ref currentRect); //ptr为窗口句柄

        //窗口的width和height
        Window_Width = currentRect.Right-currentRect.Left;
        Window_Height = currentRect.Bottom-currentRect.Top;
        
        Debug.Log(Window_Width+","+Window_Height);
        
        //窗口所在位置
        Dx=currentRect.Left;
        Dy=currentRect.Top;
        
        Debug.Log(Dx+" "+Dy);
    }

}

在Play模式下,输出是这样的:

unity鼠标点击触发 unity鼠标点击模型_System_12

很明显用程序成功的点击了鼠标。欧耶\(^o^)/

因为写的很匆忙,所以代码不够简洁优雅。后续将会改进····好了,狗子要去寻食续命啦。。。。

----------------------

补充一句,我是怎么知道Unity的Game窗口的类和名字的?是用spy++获取的。这个软件真好用。没的说,可以在该博客下载:

unity鼠标点击触发 unity鼠标点击模型_屏幕坐标_13