本帖最后由 da11 于 2025-1-2 19:13 编辑
Unity3D (2022版本亲测可用)制作桌宠核心内容及代码
准备工作
1.本帖子示例为使用sprite来制作宠物对象,参考帖子使用的是Live2D人物。
2.原帖子项目是在 unity2018.4.24.f1(很重要) 的环境下开发(已在2022.3.40f1c1版本测试通过,其余版本未测试),操作系统为Windows。
核心中的核心-全屏+背景透明+点击穿透+置顶
导出程序的时候必须使背景透明,否则就谈不上桌宠了。这里直接给出背景透明+点击穿透的代码,将此代码直接拖动给相机Camera,然后把相机的Clear Flags设为Solid Color,并把背景颜色调成黑色。(代码基于这篇博客修改)
#####################################################################
using UnityEngine;
using System.Runtime.InteropServices; // 为了使用DllImport
using System;
// 让程序背景透明
public class TestWindows : 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>
}
}
#####################################################################
相机参数:
交互
现在我们的桌宠只能看不能摸,也不会发出声音,下面我们来编写一些交互的代码。这里直接挂在宠物对象上。
#####################################################################
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class PetManager : MonoBehaviour
{
public Camera game_camera;
[SerializeField] public Collider coll;
Vector3 offset;
float depth;
private float ySpeed = 120.0f; //旋转视角时相机y轴转速
private int yMinLimit = -360;
private int yMaxLimit = 360;
private float x = 0.0f; //存储相机的euler角
private float y = 0.0f; //存储相机的euler角
private Vector3 mouse_position;
public GameObject PetMenuUICanvas;
public GameObject PetMenuUIMain;
private RectTransform PetMenuUIMainRTF;
private float TimeGo;
void Start()
{
coll = GetComponent<Collider>();
PetMenuUIMainRTF = PetMenuUIMain.GetComponent<RectTransform>();
//宠物初始化时,UI菜单也要初始化位置,跟随宠物的位置
//PetMenuUIMain.GetComponentInChildren<Button>().GetComponent<RectTransform>().anchoredPosition = new Vector2((this.transform.position.x - (float)5) * (float)27, (this.transform.position.y + (float)3) * (float)27);
PetMenuUIMainRTF.anchoredPosition = new Vector2((this.transform.position.x) * (float)54, (this.transform.position.y) * (float)54);
}
void Update()
{
TimeGo = TimeGo + Time.deltaTime;
//print(TimeGo);
if (Input.GetMouseButton(0))
{
print("鼠标左键点击");
}
Ray ray = game_camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (coll.Raycast(ray, out hit, 10.0f))
{
if (Input.GetMouseButton(1))
{
print("鼠标右键点击");
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.2f;
y = ClampAngle(y, yMinLimit, yMaxLimit);
transform.rotation = Quaternion.Euler(0, y + 180, 0);
//打开UI菜单需要1秒的间隔,不能频繁打开
if (TimeGo>1)
{
if (PetMenuUICanvas.activeSelf == false)
{
PetMenuUICanvas.SetActive(true);
}
else
{
PetMenuUICanvas.SetActive(false);
}
TimeGo = 0;
}
}
}
}
//鼠标拖拽方法--宠物拖拽
void OnMouseDrag()
{
print("拖拽开始");
mouse_position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = new Vector3(mouse_position.x, mouse_position.y, 0);
//PetMenuUIMain.GetComponentInChildren<Button>().GetComponent<RectTransform>().anchoredPosition = new Vector2((mouse_position.x + (float)35.5) * (float)27, (mouse_position.y + (float)20) * (float)27);
//拖拽宠物时,UI菜单跟随
//PetMenuUIMain.GetComponentInChildren<Button>().GetComponent<RectTransform>().anchoredPosition = new Vector2((mouse_position.x - (float)5) * (float)27, (mouse_position.y + (float)3) * (float)27);
//PetMenuUIMain.GetComponent<RectTransform>().anchoredPosition = new Vector2((mouse_position.x - (float)5) * (float)27, (mouse_position.y + (float)3) * (float)27);
//PetMenuUIMain.GetComponent<RectTransform>().anchoredPosition = new Vector2((mouse_position.x) * (float)27, (mouse_position.y) * (float)27);
//4K分辨率
PetMenuUIMainRTF.anchoredPosition = new Vector2((mouse_position.x) * (float)54, (mouse_position.y) * (float)54);
//print(PetMenuUIMain.GetComponentInChildren<Button>().GetComponent<RectTransform>().anchoredPosition.x+":"+ PetMenuUIMain.GetComponentInChildren<Button>().GetComponent<RectTransform>().anchoredPosition.y);
//print(PetMenuUIMain.GetComponent<RectTransform>().anchoredPosition.x+":"+ PetMenuUIMain.GetComponent<RectTransform>().anchoredPosition.y);
}
static float ClampAngle(float angle, float min, float max)
{
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp(angle, min, max);
}
}
##################################################################
此代码我加了个UI菜单跟随,Unity层级如下所示:
项目打包设置
因为此帖子使用的unity版本是2022.3.40f1c1版本,需要额外设置一个选项才能使得窗口透明化。
在打包界面的这里,取消勾选这个选项,然后在打包,就可以实现窗口化透明了。
|