废话:
woc····累死我了,搞了一下午,颈椎都要折了。。。赶紧趁热打铁记录一下。
有点用的铺垫:
为什么要写这篇博客呢?模拟鼠标点击又是什么意思呢?
先来理解鼠标点击,比如,你在浏览某些网站的时候,突然看到一张劲爆的美女图,心血来潮你想要看的更仔细是不是?于是你移动鼠标点了点美女,进入了新的页面,顿时看到了高清无码的美女图。爽!这是你人为控制的鼠标点击。
但是有些时候,我不想人为控制鼠标去点击某些东西,我想要程序内部给个坐标点,然后在该点模拟鼠标点击。比如我输入了该美女在屏幕坐标系下的位置,我想要程序替我点击了,从而进入新的页面。这就是我理解的模拟鼠标点击。
恩。。。为什么又在打擦边球啊摔!
继续废话:
好了言归正传。在实现该功能之前,我翻阅了我都不知道多少篇博客,但是恕我直言,各位大大都讲的太不详细了啊!!今天本博就来手把手讲述那些年在工地搬砖的故事。
重要!乖乖坐好看:
先上Demo,打开你的Unity,然后创建一个Button,让这个Button至于Canvas的中间
相信聪明的你应该已经知道我们要干啥了吧。
没错我们就是要让程序点击这个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的屏幕坐标系是从左下角开始,而桌面的屏幕坐标系是从左上角开始。
也许你看到这里又开始激动了,我直接拿Screen.Height-Pos.y不就把Unity坐标转换到桌面坐标了吗?非也非也。也许Unity不是在全屏模式下呢?
如果这里你么有看懂的话,请去看这篇博客:https://blog.csdn.net/qq_21397217/article/details/78488072 这篇博客的代码在全屏模式下也许是可以运行的,但是如果Unity的Game没有设置全屏,就没招了。
别担心,本博可以给你不在全屏模式的思路,并且本博也验证了在某些情况下,全屏模式,上面的博客代码也略有瑕疵。
来看Unity这个软件的界面,你会发现,在16:10的情况下,场景并没有铺满Game窗口,而是还有两块儿黑色的边,那么这些琐碎的东西是否还要考虑在内?答案是:是的:
你一定有很多疑问,比如说,Unity屏幕坐标是从哪里开始作为原点的?我的Button坐标是怎么先知的?如何获取Unity窗口在桌面屏幕上的位置?如何获取Game窗口在桌面屏幕上的位置?
Unity屏幕坐标是从哪里开始作为原点的?怎么获取Unity的屏幕分辨率?
Debug.Log("Unity的屏幕分辨率"+UnityEngine.Screen.width+"x"+UnityEngine.Screen.height);
Debug.Log("系统的分辨率"+UnityEngine.Screen.currentResolution); //也就是桌面屏幕分辨率
没错就是上面这块红色的区域。。如果你不信,你可以测试一下,别问我怎么知道的。我就是测试出来的。我的测试代码。然后我就在Play模式下,用鼠标去探测,发现那里是原点,那里正好到Screen.Width,那里正好到Screen.Height(箭头的位置):
并且这里我知道了点击Button的坐标:(249,158)。
然后你说那完了,就这点区域,我怎么转成系统的桌面坐标啊。别急,你只要能获取到Game窗口在桌面坐标系下的坐标不就行了?
它们的嵌套关系是这样的:
应用到实际场景中就是这样的:
如果你能看明白这两幅图,那就好办啦,很显然。我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上:
/**
*┌──────────────────────────────────────────────────────────────┐
*│ 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模式下,输出是这样的:
很明显用程序成功的点击了鼠标。欧耶\(^o^)/
因为写的很匆忙,所以代码不够简洁优雅。后续将会改进····好了,狗子要去寻食续命啦。。。。
----------------------
补充一句,我是怎么知道Unity的Game窗口的类和名字的?是用spy++获取的。这个软件真好用。没的说,可以在该博客下载https://blog.csdn.net/a1_s2_c3_/article/details/93711370: