一、背景设置
效果:
- 除模型外区域透明显示
- 透明部分鼠标可穿透。即点击透明区域,不聚焦在程序上
编辑器内设置:
Camera组件设置:Clear Flags选择Solid Color,Background改成黑色
代码:
using UnityEngine;
using System.Runtime.InteropServices; // 为了使用DllImport
using System;
/// <summary>
/// 让程序背景透明
/// </summary>
public class BackGroundSet : MonoBehaviour
{
private IntPtr hwnd;
private int currentX;
private int currentY;
#region Win函数常量
private struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
[DllImport("user32.dll")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
[DllImport("Dwmapi.dll")]
static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
[DllImport("user32", EntryPoint = "SetLayeredWindowAttributes")]
private static extern uint SetLayeredWindowAttributes(IntPtr hwnd, int crKey, int bAlpha, int dwFlags);
// 定义窗体样式,-16表示设定一个新的窗口风格
private const int GWL_STYLE = -16;
//设定一个新的扩展风格
private const int GWL_EXSTYLE = -20;
private const int WS_EX_LAYERED = 0x00080000;
private const int WS_BORDER = 0x00800000;
private const int WS_CAPTION = 0x00C00000;
private const int SWP_SHOWWINDOW = 0x0040;
private const int LWA_COLORKEY = 0x00000001;
private const int LWA_ALPHA = 0x00000002;
private const int WS_EX_TRANSPARENT = 0x20;
#endregion
void Awake()
{
Application.targetFrameRate = 60;
var productName = Application.productName;
#if !UNITY_EDITOR
// 获得窗口句柄
hwnd = FindWindow(null, productName);
// 设置窗体属性
int intExTemp = GetWindowLong(hwnd, GWL_EXSTYLE); // 获得当前样式
SetWindowLong(hwnd, GWL_EXSTYLE, intExTemp | WS_EX_LAYERED); // 当前样式加上WS_EX_LAYERED // WS_EX_TRANSPARENT 收不到点击的透明
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_BORDER & ~WS_CAPTION); // 无边框、无标题栏
// 设置窗体位置为右下角
currentX = Screen.currentResolution.width - 900;
currentY = Screen.currentResolution.height - 800;
SetWindowPos(hwnd, -1, currentX, currentY, 1200, 900, SWP_SHOWWINDOW); // Screen.currentResolution.width
// 扩展窗口到客户端区域 -> 为了透明
var margins = new MARGINS() { cxLeftWidth = -1 }; // 边距内嵌值确定在窗口四侧扩展框架的距离 -1为没有窗口边框
DwmExtendFrameIntoClientArea(hwnd, ref margins);
// 将该窗口颜色为0的部分设置为透明,即背景可穿透点击,人物模型上不穿透
SetLayeredWindowAttributes(hwnd, 0, 255, 1);
/// <summary>
///设置窗体可穿透点击的透明.
///参数1:窗体句柄
///参数2:透明颜色 0为黑色,按照从000000到FFFFFF的颜色,转换为10进制的值
///参数3:透明度,设置成255就是全透明
///参数4:透明方式,1表示将该窗口颜色为0的部分设置为透明,2表示根据透明度设置窗体的透明度
/// </summary>
#endif
/// <summary>
/// 1
/// 调节窗体透明度可以先使用SetWindowLong为窗体加上WS_EX_LAYERED属性,
/// 再使用SetLayeredWindowAttributes来指定窗体的透明度。
/// 这样就可以在程序运行时动态的调节窗体的透明度了。
/// 2
/// 给 GWL_EXSTYLE 设置 WS_EX_TRANSPARENT 让窗口透明,此时应用程序只能收到鼠标消息但收不到触摸消息
/// 3
/// 前面加上取反操作符"~",就可以得到相反效果。比如,WS_CAPTION代表窗口有标题栏,~WS_CAPTION代表窗口没有标题栏
/// 4
/// GWL_STYLE指的是那些旧的窗口属性。相对于GWL_EXSTYLEGWL扩展属性而言的
/// 5
/// 要给窗口添加某属性,用 | 来连接,要去除某属性,用 & 来连接
/// </summary>
}
}
二、鼠标事件
效果:
- 监听鼠标左右中键以及拖拽
- 控制触发事件
代码:
参考 https://www.jianshu.com/p/6165a90dcbb2
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.EventSystems;
public class MouseEvents : MonoBehaviour
{
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(int vKey);
private const int VK_LBUTTON = 0x01; //鼠标左键
private const int VK_RBUTTON = 0x02; //鼠标右键
private const int VK_MBUTTON = 0x04; //鼠标中键
private bool _isLeftDown;
private bool _isRightDown;
private bool _isMiddleDown;
public event Action<MouseKey, Vector3> MouseKeyDownEvent;
public event Action<MouseKey, Vector3> MouseKeyUpEvent;
public event Action<MouseKey, Vector3> MouseDragEvent;
public event Action<MouseKey> MouseKeyClickEvent;
public Vector3 MousePos { get; private set; }
private bool _hasDragged;
private Vector3 _leftDownPos;
private Vector3 _rightDownPos;
private Vector3 _middleDownPos;
[SerializeField] private Animator anim;
[DllImport("user32.dll")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
/// <summary>
/// 获得鼠标在屏幕上的位置
/// </summary>
/// <param name="lpPoint"></param>
/// <returns></returns>
[DllImport("user32.dll")]
static extern bool GetCursorPos(ref POINT lpPoint);
/// <summary>
/// 记录当前鼠标的位置
/// </summary>
public POINT point;
/// <summary>
/// 设置目标窗体大小,位置
/// </summary>
/// <param name="hWnd">目标句柄</param>
/// <param name="x">目标窗体新位置X轴坐标</param>
/// <param name="y">目标窗体新位置Y轴坐标</param>
/// <param name="nWidth">目标窗体新宽度</param>
/// <param name="nHeight">目标窗体新高度</param>
/// <param name="BRePaint">是否刷新窗体</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);
/// <summary>
/// 当前窗体句柄
/// </summary>
private IntPtr hwnd;
private bool dragModle; // 是否在拖拽模型
[SerializeField] private Transform neck; // 脖子部位,控制模型朝向鼠标位置的开关
[Header("右键主菜单")]
[SerializeField] private GameObject mainMenu;
[Header("设置大小页")]
[SerializeField] private GameObject sizePage;
private void Start()
{
Init();
}
// 失焦时关闭主菜单
private void OnApplicationFocus(bool focus)
{
if (!focus && mainMenu.activeSelf)
{
mainMenu.SetActive(false);
}
}
private void Update()
{
// 按下左键
if (GetAsyncKeyState(VK_LBUTTON) != 0)
{
// 左键不在UI上,关闭主菜单
if (mainMenu.activeSelf && !EventSystem.current.IsPointerOverGameObject())
{
mainMenu.SetActive(false);
}
if (!_isLeftDown)
{
_isLeftDown = true;
_leftDownPos = MouseKeyDown(MouseKey.Left);
}
else if (MousePos != Input.mousePosition)
{
MouseKeyDrag(MouseKey.Left);
if (!_hasDragged)
{
_hasDragged = true;
// 开启拖拽模型,根据鼠标位置判断是否可拖拽。显示模型时用碰撞体+射线检测,在模型不显示时可用是否在UI上判断
if (Physics.Raycast(Camera.main.ScreenPointToRay(MousePos), out RaycastHit hit, 10f, 1 << LayerMask.NameToLayer("Player")) ||( EventSystem.current.IsPointerOverGameObject() && (UIScript.Instance.inGameMode || UIScript.Instance.inWorkMode)))
{
dragModle = true;
mainMenu.SetActive(false);
}
}
}
}
// 按下右键
if (GetAsyncKeyState(VK_RBUTTON) != 0)
{
sizePage.SetActive(false);
// 右键模型出现主菜单,并根据鼠标位置设置主菜单出现位置,防止主菜单显示到屏幕外,是把屏幕分为四个象限来判断
if (Physics.Raycast(Camera.main.ScreenPointToRay(MousePos), out RaycastHit hit, 10f, 1 << LayerMask.NameToLayer("Player")) || (EventSystem.current.IsPointerOverGameObject() && (UIScript.Instance.inGameMode || UIScript.Instance.inWorkMode)))
{
if (point.X <= Screen.currentResolution.width / 2 && point.Y <= Screen.currentResolution.height / 2)
{
mainMenu.transform.localPosition = new Vector3(Input.mousePosition.x * 3 - 1500, Input.mousePosition.y * 3 - 1800, 0);
}
else if (point.X > Screen.currentResolution.width / 2 && point.Y <= Screen.currentResolution.height / 2)
{
mainMenu.transform.localPosition = new Vector3(Input.mousePosition.x * 3 - 1500 - 600, Input.mousePosition.y * 3 - 1800, 0);
}
else if (point.X <= Screen.currentResolution.width / 2 && point.Y > Screen.currentResolution.height / 2)
{
mainMenu.transform.localPosition = new Vector3(Input.mousePosition.x * 3 - 1500, Input.mousePosition.y * 3 - 1800 + 800, 0);
}
else if (point.X > Screen.currentResolution.width / 2 && point.Y > Screen.currentResolution.height / 2)
{
mainMenu.transform.localPosition = new Vector3(Input.mousePosition.x * 3 - 1500 - 600, Input.mousePosition.y * 3 - 1800 + 800, 0);
}
mainMenu.SetActive(true);
}
if (!_isRightDown)
{
_isRightDown = true;
_rightDownPos = MouseKeyDown(MouseKey.Right);
}
else if (MousePos != Input.mousePosition)
{
MouseKeyDrag(MouseKey.Right);
if (!_hasDragged)
{
_hasDragged = true;
}
}
}
// 按下中键
if (GetAsyncKeyState(VK_MBUTTON) != 0)
{
if (!_isMiddleDown)
{
_isMiddleDown = true;
_middleDownPos = MouseKeyDown(MouseKey.Middle);
}
else if (MousePos != Input.mousePosition)
{
MouseKeyDrag(MouseKey.Middle);
if (!_hasDragged)
{
_hasDragged = true;
}
}
}
// 抬起左键
if (GetAsyncKeyState(VK_LBUTTON) == 0 && _isLeftDown)
{
_isLeftDown = false;
MouseKeyUp(MouseKey.Left);
// 无拖拽、downPos==upPos
if (!_hasDragged && _leftDownPos == MousePos)
{
MouseKeyClick(MouseKey.Left);
// 点击模型的触发动作设置
if(!anim.GetBool("bow") && !anim.GetBool("exit") && !UIScript.Instance.randomAnimMode && !UIScript.Instance.inGameMode && Physics.Raycast(Camera.main.ScreenPointToRay(MousePos), out RaycastHit hit, 10f, 1 << LayerMask.NameToLayer("Player")))
{
int num = UnityEngine.Random.Range(1, 3); // 1/2
neck.transform.rotation = Quaternion.Euler(0, 0, 0);
switch (num)
{
case 1:
GameObject.FindWithTag("Player").GetComponent<Animator>().SetBool("bow", true); break;
case 2:
GameObject.FindWithTag("Player").GetComponent<Animator>().SetBool("exit", true); break;
}
}
}
_hasDragged = false;
// 停止拖拽模型
dragModle = false;
}
// 抬起右键
if (GetAsyncKeyState(VK_RBUTTON) == 0 && _isRightDown)
{
_isRightDown = false;
MouseKeyUp(MouseKey.Right);
if (!_hasDragged && _rightDownPos == MousePos)
{
MouseKeyClick(MouseKey.Right);
}
_hasDragged = false;
}
// 抬起中键
if (GetAsyncKeyState(VK_MBUTTON) == 0 && _isMiddleDown)
{
_isMiddleDown = false;
MouseKeyUp(MouseKey.Middle);
if (!_hasDragged && _middleDownPos == MousePos)
{
MouseKeyClick(MouseKey.Middle);
}
_hasDragged = false;
}
// 拖拽
if (dragModle)
{
GetCursorPos(ref point); // 获取鼠标在屏幕上的位置(原点在左上).而不是鼠标在unity中的位置(原点在左下)
MoveWindow(hwnd, point.X - Screen.width / 2, point.Y - Screen.height / 2, Screen.width, Screen.height, true);
}
}
public void Init()
{
_isLeftDown = false;
_isRightDown = false;
_isMiddleDown = false;
_hasDragged = false;
hwnd = FindWindow(null, Application.productName);
dragModle = false;
point.X = Screen.currentResolution.width;
point.Y = Screen.currentResolution.height;
}
private Vector3 MouseKeyDown(MouseKey mouseKey)
{
RefreshMousePos();
MouseKeyDownEvent?.Invoke(mouseKey, MousePos);
return MousePos;
}
private Vector3 MouseKeyUp(MouseKey mouseKey)
{
RefreshMousePos();
MouseKeyUpEvent?.Invoke(mouseKey, MousePos);
return MousePos;
}
private Vector3 MouseKeyDrag(MouseKey mouseKey)
{
RefreshMousePos();
MouseDragEvent?.Invoke(mouseKey, MousePos);
return MousePos;
}
private void MouseKeyClick(MouseKey mouseKey)
{
MouseKeyClickEvent?.Invoke(mouseKey);
}
private Vector3 RefreshMousePos()
{
MousePos = Input.mousePosition;
return MousePos;
}
public Vector3 MousePosToWorldPos(Vector3 mousePos)
{
var screenInWorldPos = Camera.main.WorldToScreenPoint(mousePos);
var refPos = new Vector3(mousePos.x, mousePos.y, screenInWorldPos.z);
var pos = Camera.main.ScreenToWorldPoint(refPos);
return pos;
}
}
public enum MouseKey
{
None,
Left,
Right,
Middle
}
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
public override string ToString()
{
return ("X:" + X + ", Y:" + Y);
}
}
三、人物模型看向鼠标位置
代码:
using UnityEngine;
public class TowardMousePos : MonoBehaviour
{
[SerializeField] private Transform neck;
[SerializeField] private Animator anim;
private void Update()
{
// 设置分辨率为1200:900可以计算出鼠标当前位置(设置别的也行,自行调试)
float x = Mathf.Clamp((Input.mousePosition.x - 1200 * .5f) / Screen.width * .5f * 180, -50, 50);
float y = Mathf.Clamp((Input.mousePosition.y - 900 * .5f) / Screen.height * .5f * 180, -20, 50);
// 旋转脖子角度(可加if判断)
neck.transform.rotation = Quaternion.Euler(-y + 45, -x, 0); // 上下,左右
}
}
结语:
- 若透明背景未出现,注意PlayerSettings中分辨率模块里的Use DXGI Flip Mode Swapchain for D3D11选项,关闭即可
- 失焦时无法收到Unity内置的InputSystem的事件,监听鼠标位置用GetCursorPos(),监听键盘输入需要钩子(Hook),都需要导入user32.dll
- 写得不好,大佬轻喷