Unity3D开发AI桌面精灵/宠物系列 【一】 窗口透明化 背景剔除 、去边框、去Logo动画UI正常显示

Unity3D 交互式AI桌面宠物开发系列【一】


文章主要介绍怎么制作AI桌面宠物的流程,我会从项目开始创建初期到最终可以和AI宠物进行交互为止,项目已经开发完成,我会仔细梳理一下流程,分步讲解。 这篇文章主要讲初期一些设置和部署。


提示:内容纯个人编写,欢迎评论点赞,来指正我。


前言

本篇内容主要讲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;
 
/// <summary>
/// 一共可选择三种样式
/// </summary>
public enum enumWinStyle
{
    /// <summary>
    /// 置顶
    /// </summary>
    WinTop,
    /// <summary>
    /// 置顶并且透明
    /// </summary>
    WinTopApha,
    /// <summary>
    /// 置顶透明并且可以穿透
    /// </summary>
    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
    }
 
    /// <summary>
    /// 拖拽程序
    /// </summary>
    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
    /// <summary>
    /// WinAPI RECT definition.
    /// </summary>
    [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

 
  • 直接扔哪个代码文件里,不需要挂载任何物体,运行时自动执行!!

然后保存设置并打包测试吧

小姐姐给大家作揖啦

在这里插入图片描述
比心啦 ❥(^_-)

在这里插入图片描述桌面宠物基础功能就实现啦!

四、总结

  • 提示: 大家根据需求来做功能,后续继续其他功能啦,不懂的快喊我。
  • 大家可以在评论区讨论其他系列下一期出什么内容,这个系列会继续更新的
  • 点赞收藏加关注哦~ 蟹蟹
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与火星的孩子对话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值