Unity3D开发AI桌面精灵/宠物系列 【一】 窗口透明化 背景剔除 、去边框、去Logo动画UI正常显示_unity桌面宠物
Unity3D 交互式AI桌面宠物开发系列【一】
文章主要介绍怎么制作AI桌面宠物的流程,我会从项目开始创建初期到最终可以和AI宠物进行交互为止,项目已经开发完成,我会仔细梳理一下流程,分步讲解。 这篇文章主要讲初期一些设置和部署。
提示:内容纯个人编写,欢迎评论点赞,来指正我。
文章目录
- Unity3D 交互式AI桌面宠物开发系列【一】
- 前言
- 一、AI桌面宠物是什么?
- 二、Unity 基础配置介绍
-
- 1.Unity版本
- 2.形象配置
- 三、开发功能
- 四、总结
前言
本篇内容主要讲Unity开发桌面宠物前期准备工作,大家感兴趣也可以了解一下这个开发方向,目前还是挺有前景的,AI智能科技发展这么迅猛,紧跟步伐哈~
下面让我们出发吧 ------------>----------------->
一、AI桌面宠物是什么?
顾名思义,桌面宠物就是在PC端桌面上运行的一个2D/3D定制的虚拟形象,那么加上AI功能后就可以实现
宠物唤醒 ->识别用户语音->接入交互大模型->宠物语音播放内容->模型口型同步语音
当然后期还可以扩展一些类似和宠物直接的鼠标交互,比如点击一下播放个挠痒动画啊,或者向你招个手啊,后期我会根据反馈更新功能。
先看效果 跟我的拯救者毫无违和感, 哈哈哈哈哈
二、Unity 基础配置介绍
1.Unity版本
- 我开发使用的Unity版本是2021.3.44f1 目前没有遇到版本问题,当然高版本更好,最好不要太低,在2019.4以上吧尽量
2.形象配置
- 找一个3D模型,这个可爱的小姐姐是官方插件里面的,要找带有BlendShape组件的模型哦,网上很多的,找不到的跟我说。
三、开发功能
1.分辨率设置
- 分辨率设置,也就是窗口大小,在窗口内是鼠标是可以拖动着程序移动的。不要设置全屏,我设置的是 600*600
2. Player设置
- 按我这个设置就行,就不过多解释了,目的就是不影响窗口化
3. 窗口透明化、背景剔除效果
①. 相机设置
- 设置Clear Flags为Solid Color模式,然后将Background的颜色设置为黑色,透明度设置为0,其他默认或者自行调节,重点在下面的脚本
②. 代码剔除效果
TransparentWindow脚本代码:
using System;using System.Runtime.InteropServices;using UnityEngine; public class TransparentWindow : MonoBehaviour{ [SerializeField] private Material m_Material; private struct MARGINS { public int cxLeftWidth; public int cxRightWidth; public int cyTopHeight; public int cyBottomHeight; } [DllImport(\"user32.dll\")] private static extern IntPtr GetActiveWindow(); [DllImport(\"user32.dll\")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport(\"Dwmapi.dll\")] private static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins); const int GWL_STYLE = -16; const uint WS_POPUP = 0x80000000; const uint WS_VISIBLE = 0x10000000; void Start() {#if !UNITY_EDITOR var margins = new MARGINS() { cxLeftWidth = -1 }; var hwnd = GetActiveWindow(); SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); DwmExtendFrameIntoClientArea(hwnd, ref margins); #endif } void OnRenderImage(RenderTexture from, RenderTexture to) { Graphics.Blit(from, to, m_Material); }}
- 挂载在相机上吗,下面是编写Shader效果,并制作材质球,挂载在这个脚本的Material变量处
③. Shader编写、材质球制作
Shader代码如下:
Shader \"Custom/ChromakeyTransparent\" { Properties{ _MainTex(\"Base (RGB)\", 2D) = \"white\" {} _TransparentColourKey(\"Transparent Colour Key\", Color) = (0,0,0,1) _TransparencyTolerance(\"Transparency Tolerance\", Float) = 0.01 } SubShader{ Pass{ Tags{ \"RenderType\" = \"Opaque\" } LOD 200 CGPROGRAM #pragma vertex vert #pragma fragment frag #include \"UnityCG.cginc\" struct a2v { float4 pos : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(a2v input) { v2f output; output.pos = UnityObjectToClipPos(input.pos); output.uv = input.uv; return output; } sampler2D _MainTex; float3 _TransparentColourKey; float _TransparencyTolerance; float4 frag(v2f input) : SV_Target { float4 colour = tex2D(_MainTex, input.uv); from the chosen transparency colour float deltaR = abs(colour.r - _TransparentColourKey.r); float deltaG = abs(colour.g - _TransparentColourKey.g); float deltaB = abs(colour.b - _TransparentColourKey.b); // If colour is within tolerance, write a transparent pixel if (deltaR < _TransparencyTolerance && deltaG < _TransparencyTolerance && deltaB < _TransparencyTolerance) { return float4(0.0f, 0.0f, 0.0f, 0.0f); } return colour; } ENDCG } }}
- 新建Shader将上述代码贴进去,然后新建一个材质球,选择该Shader 如下图:
- 调整材质球的 Transparent Colour Key 组件的 颜色为黑色并将透明度Alpha值设置为0, 将制作好的材质球挂载在相机脚本 TransparentWindow 的材质球变量上就OK了。
- 到这一步就完成了透明背景!!!!接下来设置屏幕无边框
4. 程序无边框
可以参考 Unity打包窗口化放大、缩小、拖拽功能、无边框设置 C# 讲的也很详细,下面就再来一遍吧,为了项目完整性,换一种方式来。
① 无边框设置代码
using UnityEngine;using System.Collections;using System;using System.Runtime.InteropServices;using System.IO;using UnityEngine.XR;using UnityEngine.UI; /// /// 一共可选择三种样式/// public enum enumWinStyle{ /// /// 置顶 /// WinTop, /// /// 置顶并且透明 /// WinTopApha, /// /// 置顶透明并且可以穿透 /// WinTopAphaPenetrate}public class WinSetting : MonoBehaviour{ #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(\"user32.dll\")] static extern int SetLayeredWindowAttributes(IntPtr hwnd, int crKey, int bAlpha, int dwFlags); [DllImport(\"Dwmapi.dll\")] static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins); [DllImport(\"user32.dll\")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); private const int WS_POPUP = 0x800000; [DllImport(\"user32.dll\")] private static extern IntPtr GetActiveWindow(); private const int GWL_EXSTYLE = -20; private const int GWL_STYLE = -16; private const int WS_EX_LAYERED = 0x00080000; private const int WS_BORDER = 0x00800000; private const int WS_CAPTION = 0x00C00000; const uint WS_VISIBLE = 0x10000000; 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; // private const int ULW_COLORKEY = 0x00000001; private const int ULW_ALPHA = 0x00000002; private const int ULW_OPAQUE = 0x00000004; private const int ULW_EX_NORESIZE = 0x00000008; #endregion public string strProduct;//项目名称 public enumWinStyle WinStyle = enumWinStyle.WinTop;//窗体样式 public int ResWidth;//窗口宽度 public int ResHeight;//窗口高度 private int currentX;//窗口左上角坐标x private int currentY;//窗口左上角坐标y private bool isApha;//是否透明 private bool isAphaPenetrate;//是否要穿透窗体 void Awake() { Invoke(\"SetScreen\", 0.2f); } void SetScreen() { currentX = Screen.width/2+300; currentY = Screen.height/2; Screen.fullScreen = false;#if UNITY_EDITOR print(\"编辑模式不更改窗体\");#else switch (WinStyle) { case enumWinStyle.WinTop: isApha = false; isAphaPenetrate = false; break; case enumWinStyle.WinTopApha: isApha = true; isAphaPenetrate = false; break; case enumWinStyle.WinTopAphaPenetrate: isApha = true; isAphaPenetrate = true; break; } IntPtr hwnd = FindWindow(null, strProduct); if (isApha) { //去边框并且透明 SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED); // 获得当前样式 int intExTemp = GetWindowLong(hwnd, GWL_EXSTYLE); if (isAphaPenetrate)//是否透明穿透窗体 { SetWindowLong(hwnd, GWL_EXSTYLE, intExTemp | WS_EX_TRANSPARENT | WS_EX_LAYERED); } SetWindowPos(hwnd, -1, currentX, currentY, ResWidth, ResHeight, SWP_SHOWWINDOW); var margins = new MARGINS() { cxLeftWidth = -1 }; SetLayeredWindowAttributes(hwnd, 0, 255, 1); } else { SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_BORDER & ~WS_CAPTION); //无边框、无标题栏 SetWindowPos(hwnd, -1, currentX, currentY, ResWidth, ResHeight, SWP_SHOWWINDOW); }#endif } /// /// 拖拽程序 /// public void Drag() { PInvoke.DragWindow(); Debug.Log(\"Drag\"); }}
-Drag函数里面的方法在如下脚本:
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 常量 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;//(最小化窗口) 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); [DllImport(\"user32.dll\", SetLastError = true)] public static extern bool GetWindowRect(IntPtr hwnd, ref RECT lpRect); [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 //最小化窗口 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}
-将上述脚本挂载在相机或者其他物体上 如下WinSetting脚本:
StrProduct 变量替换成自己的项目的名字 去Player里面找
Win Style 里面有三种模式 默认第一种就好 其他两种可以自行测试效果
② 增加程序拖拽功能
- 可以在分辨率大小内的窗口里鼠标左键按住拖拽窗口,这个范围是设置的分辨率大小,自己根据需要设置,目前是600*600的大小
- 新建一个Image
!!!!!重点!!!!!!!!!!!!!
- (1)Image的锚点设置为充满全屏也就是大小跟分辨率一样,这样才能保证分辨率内的任何地方都可以拖拽程序
- (2) Image的颜色设置随便,Alpha透明度设置为0 ,达到看不见UI的效果。
- (3)添加组件EventTrigger ,然后添加一个Drag事件,将相机上的WinSetting方法绑定在Drag事件上。
4.去Logo 直接上代码
using System.Threading.Tasks;using UnityEngine;using UnityEngine.Rendering;using UnityEngine.Scripting;#if !UNITY_EDITOR [Preserve] //防止打包的时候没有被打包进程序public class ClearLogoAni : MonoBehaviour{ //在启动画面显示之前执行这个方法 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] private static void Run() { Task.Run(() => { SplashScreen.Stop(SplashScreen.StopBehavior.StopImmediate); }); }}#endif
- 直接扔哪个代码文件里,不需要挂载任何物体,运行时自动执行!!
然后保存设置并打包测试吧
小姐姐给大家作揖啦
比心啦 ❥(^_-)
桌面宠物基础功能就实现啦!
四、总结
- 提示: 大家根据需求来做功能,后续继续其他功能啦,不懂的快喊我。
- 大家可以在评论区讨论其他系列下一期出什么内容,这个系列会继续更新的
- 点赞收藏加关注哦~ 蟹蟹