• 本次系列课程的目标是让Unity3D初学者掌握Unity3d的网络开发技术,课程重点将对TCP数据通信时如何对数据进行序列化和反序列化,如何解析数据包等重要知识点进行深入探讨。
  • 我发现侵权的一个网站 只改了潦草的几个字就把我的文章复制粘贴过去了 是一个培训机构的网站 :http://www.newbieol.com/information/998.html 这种培训...hello,最近用unity做了进程通信,应该是和c++的PC端实现通信

    我发现侵权的一个网站 只改了潦草的几个字就把我的文章复制粘贴过去了 是一个培训机构的网站 :http://www.newbieol.com/information/998.html  这种培训机构就是坑钱的 跟传销差不多基本学不到东西就是坑你的学费的 不知道有没有人知道这种培训机构的举报通道 举报他丫的 完全没有做人的底线

    hello,最近用unity做了进程通信,应该是和c++的PC端实现通信,才开始一头雾水,后来实现了才知道好繁杂......先感谢对我提供帮助的百度,谷歌以及游戏圈的大大们。


    在进程通信中很多方法,但是wm_copydate绝对要比别的什么内存共享好了许多。unity大部分用c#语言,c#本身Forms这个dll里面也提供了对windows消息的接收但是在unity中无法很好地使用System.Windows.Forms,所以在下面我的代码我用unity发送进程消息的是 user32.dll 中的sendMessage,对于接收则是用的hook(钩子)。下面代码是unity打包出来的exe的通信。就不和c++通信了,原理都一样。


    整个过程要导入user32.dll ,所以在需要using System.Runtime.InteropServices;剩下需要引用什么添加什么,里面还有发送json数据以及许多细节的c#取地址读取地址,我也一并分享大家乐,吐舌头以后也要帮我哦.


    发送端(利用sendMessage),test1.cs挂载在unity场景中


    using System;
    using UnityEngine;
    using System.Collections;
    using System.Runtime.InteropServices;
    using System.Text;
    
    
    public class test15 : MonoBehaviour
    {
        #region
        public IntPtr m_hWnd;  
        /// <summary>
        /// 发送windows消息方便user32.dll中的SendMessage函数使用
        /// </summary>
        public struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;
            public IntPtr lpData;
        }
        //user32.dll中的SendMessage
        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, ref COPYDATASTRUCT lParam);
        //user32.dll中的获得窗体句柄
        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string strClassName, string strWindowName);
        //宏定义 
        private const ushort IPC_VER = 1;
        private const int IDT_ASYNCHRONISM = 0x0201;
        private const uint WM_COPYDATA = 0x004A;
        private const ushort IPC_CMD_GF_SOCKET = 1;
        private const ushort IPC_SUB_GF_SOCKET_SEND = 1;
        private const int IPC_SUB_GF_CLIENT_READY = 1;
        private const int IPC_CMD_GF_CONTROL = 2;
        private const int IPC_BUFFER = 10240;//最大缓冲长度
        //查找的窗体
        private IntPtr hWndPalaz;
         //数据包头配合使用
        public unsafe struct IPC_Head
        {
            public ushort wVersion; 
            public ushort wPacketSize; 
            public ushort wMainCmdID; 
            public ushort wSubCmdID; 
    
    
    
    
        }
        public unsafe struct IPC_Buffer
        {
            public IPC_Head Head;  //IPC_Head结构
            public fixed byte cbBuffer[IPC_BUFFER]; //指针        存放json数据 利用byte[]接收存放 
        }
     #endregion
        /// <summary>
        /// 发送把json转换为指针传到SendData()方法
        /// </summary>
        private void sendJson() {
            IntPtr hWndPalaz = FindWindow(null, "你们要查找的窗体名字");//就是窗体的的标题
            Debug.Log(hWndPalaz);
            if (hWndPalaz != null)
            {
                //获得游戏本身句柄 
                m_hWnd = FindWindow("UnityWndClass", null);
    
    
               
    
                //发送用户准备好消息(这个是个json插件我就不提供了你们自己搞自己的json new一个实例这里不改会报错)
                JSONObject jsStart = new JSONObject();
                jsStart.AddField("六六", "是我");
                jsStart.AddField("sya", "学习游戏为了装逼小组");
                jsStart.AddField("doing", "this is your time");
    
    
                string uRstr = jsStart.ToString();
                byte[] bytes = Encoding.UTF8.GetBytes(uRstr);
                IntPtr pData = Marshal.AllocHGlobal(2 * bytes.Length);
                Marshal.Copy(bytes, 0, pData, bytes.Length);
                SendData(m_hWnd, IPC_CMD_GF_SOCKET, IPC_SUB_GF_SOCKET_SEND, pData, (ushort)bytes.Length);
                
    
            }
        
        }
        /// <summary>
        /// SendMessage发送
        /// </summary>
        /// <param name="hWndServer">指针</param>
        /// <param name="wMainCmdID">主命令</param>
        /// <param name="wSubCmdID">次命令</param>
        /// <param name="pData">json转换的指针</param>
        /// <param name="wDataSize">数据大小</param>
        /// <returns></returns>
        public unsafe bool SendData(IntPtr hWndServer, ushort wMainCmdID, ushort wSubCmdID, IntPtr pData, ushort wDataSize)
        {
            //给IPCBuffer结构赋值
            IPC_Buffer IPCBuffer;
            IPCBuffer.Head.wVersion = IPC_VER;
            IPCBuffer.Head.wSubCmdID = wSubCmdID;
            IPCBuffer.Head.wMainCmdID = wMainCmdID;
            IPCBuffer.Head.wPacketSize = (ushort)Marshal.SizeOf(typeof(IPC_Head));
    
    
            //内存操作
            if (pData != null)
            {
                //效验长度
                if (wDataSize > 1024) return false;
                //拷贝数据
                IPCBuffer.Head.wPacketSize += wDataSize;
    
    
                byte[] bytes = new byte[IPC_BUFFER];
                Marshal.Copy(pData, bytes, 0, wDataSize);
    
    
                for (int i = 0; i < IPC_BUFFER; i++)
                {
                    IPCBuffer.cbBuffer[i] = bytes[i];
                }
            }
    
    
            //发送数据
            COPYDATASTRUCT CopyDataStruct;
            IPC_Buffer* pPCBuffer = &IPCBuffer;
            CopyDataStruct.lpData = (IntPtr)pPCBuffer;
            CopyDataStruct.dwData = (IntPtr)IDT_ASYNCHRONISM;
            CopyDataStruct.cbData = IPCBuffer.Head.wPacketSize;
            SendMessage(hWndServer, 0x004A, (int)m_hWnd, ref CopyDataStruct);
    
    
            return true;
        }
        void Update()
        {
            sendJson();//一直发送方便测试
        }
    }


        

    接收端 test2.cs 随便建场景保存将这个脚本挂载场景的物体上面 ,然后用unity打包pc端的exe  接收利用windows的hook钩子(这里我就不做详细的注释了,自己体会hook的妙用了,不懂可以给我留言)


    using UnityEngine;
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using Debug = UnityEngine.Debug;
    public class test2: MonoBehaviour
    {
    //钩子接收消息的结构
    public struct CWPSTRUCT
    {
        public int lparam;
        public int wparam;
        public uint message;
        public IntPtr hwnd;
    }
     //建立钩子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, uint dwThreadId);
    
    
        //移除钩子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern bool UnhookWindowsHookEx(int idHook);
    
    
        //把信息传递到下一个监听
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int CallNextHookEx(int idHook, int nCode, int wParam, int lParam);
    //回调委托
        private delegate int HookProc(int nCode, int wParam, int lParam);
        //钩子
        int idHook = 0;
        //是否安装了钩子
        bool isHook = false;
        GCHandle gc;
        private const int WH_CALLWNDPROC = 4;  //钩子类型 全局钩子
    
    //定义结构和发送的结构对应
    public unsafe struct IPC_Head
        {
            public int wVersion;
            public int wPacketSize;
            public int wMainCmdID;
            public int wSubCmdID;
        }
        private const int IPC_BUFFER = 10240;//最大缓冲长度
        public unsafe struct IPC_Buffer
        {
            public IPC_Head Head;
            public fixed byte cbBuffer[IPC_BUFFER];  //json数据存的地方
        }
        public struct COPYDATASTRUCT
        {
            public int dwData;
            public int cbData;
            public IntPtr lpData;
        }
        void Start()
        {
    //安装钩子
            HookLoad();
        }
    
    
        void OnDestroy()
        {
    //关闭钩子
            HookClosing();
        }
         private void HookLoad()
        {
            Debug.Log("开始运行");
            //安装钩子
            {
                //钩子委托
                HookProc lpfn = new HookProc(Hook);
                //关联进程的主模块
                IntPtr hInstance = IntPtr.Zero;// GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
                idHook = SetWindowsHookEx(WH_CALLWNDPROC, lpfn, hInstance, (uint)AppDomain.GetCurrentThreadId());
                if (idHook > 0)
                {
                    Debug.Log("钩子[" + idHook + "]安装成功");
                    isHook = true;
                    //保持活动 避免 回调过程 被垃圾回收
                    gc = GCHandle.Alloc(lpfn);
                }
                else
                {
                    Debug.Log("钩子安装失败");
                    isHook = false;
                    UnhookWindowsHookEx(idHook);
                }
            }
        }
    
    
        //卸载钩子
        private void HookClosing()
        {
            if (isHook)
            {
                UnhookWindowsHookEx(idHook);
            }
        }
    
    
        private bool _bCallNext;
        public bool CallNextProc
        {
            get { return _bCallNext; }
            set { _bCallNext = value; }
        }
    
    
    
    
        
        //钩子回调
    
    
        private unsafe int Hook(int nCode, int wParam, int lParam)
        {
    
    
            try
            {
                IntPtr p = new IntPtr(lParam);
                CWPSTRUCT m = (CWPSTRUCT)Marshal.PtrToStructure(p, typeof(CWPSTRUCT));
               
                if (m.message == 74)
                {
                    COPYDATASTRUCT entries = (COPYDATASTRUCT)Marshal.PtrToStructure((IntPtr)m.lparam, typeof(COPYDATASTRUCT));
                    IPC_Buffer entries1 = (IPC_Buffer)Marshal.PtrToStructure((IntPtr)entries.lpData, typeof(IPC_Buffer));
                    
                    IntPtr intp = new IntPtr(entries1.cbBuffer);
                    string str = new string((sbyte*)intp);
                    Debug.Log("json数据:" + str);
                }
                if (CallNextProc)
                {
                    return CallNextHookEx(idHook, nCode, wParam, lParam);
                }
                else
                {
                    //return 1;
                    return CallNextHookEx(idHook, nCode, wParam, lParam);
                }
            }
            catch (Exception ex)
            {
                Debug.Log(ex.Message);
                return 0;
            }
           
        }
    }
    



    OK,所有代码终于特么的完毕了,把test2.cs的场景打包,把test1.cs的代码放在unity运行就行了。最终看test2.cs的exe中的这些代码花了我好长时间,很值钱的我就这么分享给大家了,希望大家有好的东西也不要吝啬啊。嘿嘿,最后有不懂的可以加我的Q群479853988问我哦。里面很多大神也可以问。转载注重原创哦。微笑




    展开全文
  • Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304 Unity UGUI图文混排源码(二):http://blog.csdn.net/qq992817263/article/details/51112311 我从一开始想到的图文混排的...

    Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304

    Unity UGUI图文混排源码(二):http://blog.csdn.net/qq992817263/article/details/51112311


    我从一开始想到的图文混排的概念都是通过文字间的空隙去粘贴一张图片,这样确定图片前面文字的最后一个位置变成了最主要的参数,接下来就给出两种解决方案

    首先,先发UGUI源码的一个链接,很多东西可以参考他本身代码和一些可获取的属性:https://bitbucket.org/Unity-Technologies/ui


    图文混排解决方案一:

    通过获取preferredWidth和preferredHeight来确定他的位置,再通过空格,给图片留下位置,这里只提一下关键点,具体建议使用第二种解决方案

    1.通过自定义标签,将文本中的文字分为几段

    void DealTextInfor(string value)
        {
            if (m_spriteAsset == null)
                return;
            if (m_spriteAsset.listSpriteInfor.Count <= 0)
                return;
    
            if (value.Contains("<sprite="))
            {
                //获取到<sprite前面的所有内容 并确定最后一个字符的位置信息 再+四个空格 赋值给m_text
                int iNum = value.IndexOf("<sprite=");
                m_text.text += value.Substring(0, iNum);
                Debug.Log("m_Text preferredWidth:" + m_text.preferredWidth + "  m_Text preferredHeight:" + m_text.preferredHeight);
                //Debug.Log("m_Text line count:" + m_TextGenerator.GetLinesArray().Length);
                UILineInfo[] linesInfo = m_textGenerator.GetLinesArray();
                for (int i = 0; i < linesInfo.Length; i++)
                {
                    Debug.Log("start char Index:" + linesInfo[i].startCharIdx + "start char:" + m_text.text.ToCharArray()[linesInfo[i].startCharIdx] + "     line height:" + linesInfo[i].height);
                }
                Vector3 textpos = Vector3.zero;
                int startCharIdx = linesInfo[linesInfo.Length - 1].startCharIdx;
                textpos.x = m_textGenerator.GetPreferredWidth(m_text.text.Substring(startCharIdx, m_text.text.Length - startCharIdx), m_textSetting);
                textpos.y = -(m_text.preferredHeight);
                Debug.Log("text pos:" + textpos);
    
                m_text.text += "    ";
                //获取到精灵索引的ID 并去掉精灵标签
                value = value.Substring(iNum, value.Length - iNum);
                Debug.Log("value:" + value);
                iNum = value.IndexOf("/>");
                int iSpriteIndex = int.Parse(value.Substring(0, iNum).Trim().Replace("<sprite=", ""));
                SaveSpriteInfor(textpos, m_textGenerator.GetPreferredWidth("    ", m_textSetting), iSpriteIndex);
                Debug.Log("sprite index:" + iSpriteIndex);
    
                value = value.Substring(iNum + 2, value.Length - iNum - 2);
                DealTextInfor(value);
            }
            else
            {
                m_text.text += value;
                DrawSprite();
            }
        }
    

    2.将当前图片的前面的几段文字集合,通过获取这段文字的preferredWidth和preferredHeight来确定他的位置,这个局限性就在于文本最处于左上角或者左下角的排列位置,才能方便计算,同时如果文字的字体差异较大之后,preferredWidth和preferredHeight的位置并不能体现很好的效果

    3.获取preferredWidth和preferredHeight的方式,可以通过Text自身的属性去直接获取,也可以通过代码去计算

     Text m_Text = GetComponent<Text>();
    
            TextGenerator m_TextGenerator = m_Text.cachedTextGeneratorForLayout;
            TextGenerationSettings m_TextGenerationSettings = m_Text.GetGenerationSettings(Vector2.zero);
            float fWidth = m_TextGenerator.GetPreferredWidth("一段文字,获取所占宽度", m_TextGenerationSettings);
    
            m_TextGenerationSettings = m_Text.GetGenerationSettings(new Vector2(m_Text.rectTransform.rect.x,0.0f));
            float fHeight = m_TextGenerator.GetPreferredHeight("一段文字,获取所占高度", m_TextGenerationSettings);
    

    4.参考源码计算的方式

    public virtual float preferredWidth
            {
                get
                {
                    var settings = GetGenerationSettings(Vector2.zero);
                    return cachedTextGeneratorForLayout.GetPreferredWidth(m_Text, settings) / pixelsPerUnit;
                }
            }
            
            public virtual float preferredHeight
            {
                get
                {
                    var settings = GetGenerationSettings(new Vector2(rectTransform.rect.size.x, 0.0f));
                    return cachedTextGeneratorForLayout.GetPreferredHeight(m_Text, settings) / pixelsPerUnit;
                }
            }
    

    5.具体的表现效果参考上一篇文章:http://blog.csdn.net/qq992817263/article/details/51000744

    展开全文
  • U3D如何运行下载的demo

    2015-05-18 01:29:28
    如何运行从网上下载的demo。 我从网上下载了demo,文件夹里文件如下: Assets Library ProjectSettings 文件用u3d打开后感觉两眼抹黑不知道如何运行。 刚开始学,还不太会=-=求知道的指点一下,不胜感激。
  • 对使用unity开发网络游戏和网络软件的学员对网络基础知识,开发流程,开发过程中需要解决的问题有一个全面的,深入的了解。
  • 连上服务器,老是下载配置文件,然后更新失败,这是什么原因,求大神讲解
  • unity语音SDK Demo,已经写好代码,直接拖进去用。
  • 前言 因为有不少同学反应在使用图文混排的时候,出现很大的性能问题,导致画面帧率不稳定,甚至极低。博主对此非常抱歉,当时仅凭兴趣,在一个个功能上不断叠加,几乎没有考虑到性能的优化。这次有几天的空余时间,...

    前言

    因为有不少同学反应在使用图文混排的时候,出现很大的性能问题,导致画面帧率不稳定,甚至极低。博主对此非常抱歉,当时仅凭兴趣,在一个个功能上不断叠加,几乎没有考虑到性能的优化。这次有几天的空余时间,就针对图文混排做了一次整理和优化。其实,这篇博客比较发得比较晚,在github上博主都是及时更新了代码,有兴趣的同学欢迎关注。不过,博主对图文混排并没有实际的使用机会,可能有些地方还有考虑不完善的地方,博主会尽量优化。

    强制优化

    • 动态表情的帧数能少则少,我这里测试因为是使用的网络资源,每个动态表情有八张图片,在实际项目中建议两张就应该足够了。
    • 能使用静态表情,就放弃动态表情。因为动态表情是在update里计时更新图片的uv来做表情切换,如果使用静态表情就省下了很大的循环计算量。
    • 表情能少则少,不管在每个Text里,或者在整个聊天框内。根据项目的需求,可以限制Text的表情数量,或者使用对象池动态增删,不过我这里并没有去封装对象池,需要自定义。
    • 能使用单图集就不使用多图集,理由同上。
    • 如果不使用表情,肯定性能会更好,这就是一句废话。这里的强制优化都是从最直观的去节省性能,简单粗暴,而且效果较好。

    配置文件替换字符解析

    • 之前的版本是将表情名称,大小,比例等一系列参数,放在Text里面通过SetVerticesDirty函数在最开始解析获得。现在将这些参数都写到了SpriteAsset里面,并且在编辑器中可编辑。然后只需要在最开始读取一下SpriteAsset的文件,就知道当前信息,比如表情的名称,每个表情里有哪些Sprite,每个Sprite的大小,uv等相关信息。
    • 示例截图:
      这里写图片描述
    • 这里还新增加了一个标签的变量,在前期需要配置的,就是把一个动态表情的所有Sprite都统一选择到同一标签下
      这里写图片描述

    简化正则表达式

    • 转化对比:<quad name=test Size=24 Wdith=1 />->[#test]
    支持多图集
    • 支持多图集:[图集ID#表情名称],图集ID对应SpriteAssets的ID,表情名称对应SpriteAssets的标签,ID为0时,可省略不写
      这里写图片描述
    支持超链接
    • 支持超链接,图集ID输入为-1时,就默认为超链接,后面则为链接名称,示例[-1#超链接名称],并且在InlineText有一个OnHrefClick事件可以监听点击
      这里写图片描述

    避免sprite的重复绘制

    • 将之前版本的世界坐标信息,转化为本地坐标,只要相对父物体的位置信息不变,也可以避免重复绘制。在之前版本,因为这个原因拖动Scroll View会造成大量多余的计算,导致帧率极低。
    • 增加SpriteGraphic模型的对比封装,以标签和位置信息作为对比参数,如果一样,就不再进行图片绘制
     #region 模型数据信息
        private class MeshInfo
        {
            public string[] _Tag;
            public Vector3[] _Vertices;
            public Vector2[] _UV;
            public int[] _Triangles;
    
            //比较数据是否一样
            public bool Equals(MeshInfo _value)
            {
                if (_Tag.Length!= _value._Tag.Length|| _Vertices.Length!= _value._Vertices.Length)
                    return false;
                for (int i = 0; i < _Tag.Length; i++)
                    if (_Tag[i] != _value._Tag[i])
                        return false;
                for (int i = 0; i < _Vertices.Length; i++)
                    if (_Vertices[i] != _value._Vertices[i])
                        return false;
                return true;
            }
        }
        #endregion
        ```
    #### **重写文本所占宽高**
    因为文本会根据正则增加一些其他的字符串到text中,于是默认的preferredWidth和preferredHeight就不再准确,这里根据实际的text内容重新计算,方便做聊天气泡等功能的文本所占宽高获取
    #### **增加层级管理**
    增加一个InlineManager脚本,由它来管理子物体中所有的SpriteGraphic和InlineText,并保证两者的正确联系与层级管理。
    这里也就可以创建多个InlineManager,来保证每一个之间的互不干扰。
    ####  **新增快捷创建**
    你可以像创建其他UI一样,点击GameObject/UI/TextInline,直接创建默认的UI预设。
    #### **源码链接**
    github:[https://github.com/coding2233/TextInlineSprite](https://github.com/coding2233/TextInlineSprite)
    #### **问题遗留**
    * 在编辑器中,会造成系统的异常卡顿,在另一台win7系统没有影响,有影响的系统为win10;
    * 对于在编辑模式下表情的预览绘制逻辑整理;
    * 对于在运行模式下的逻辑流程的整理;
    * 偶尔可能会出现一些非正常的表现;
    
    展开全文
  • springboot 连接数据库依赖及相关代码(gradle项目) mysql: //application.yml: spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/database?...

    springboot 连接数据库依赖及相关代码(gradle项目)

    mysql:

    	//application.yml:
    	spring:
     	 datasource:
       		 username: root
       		 password: root
      		 url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
       		 driver-class-name: com.mysql.cj.jdbc.Driver
      thymeleaf://是否启用模板
        enabled: false
    
    	//gradle依赖
    	runtimeOnly('mysql:mysql-connector-java')
    

    SQL server:

    	//application.yml
    	spring:
     		 mvc:
        		view: //jsp项目配置
        		prefix: /WEB-INF/jsp/
          		suffix: .jsp
     		datasource:
       			driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
        		url: jdbc:sqlserver://localhost\SQLSERVER:1433;database=database//SQLSERVER为数据库实例
        		username: sa
        		password: root
    
    	//gradle依赖
        implementation('com.microsoft.sqlserver:mssql-jdbc:6.1.0.jre8')
        runtimeOnly('com.microsoft.sqlserver:sqljdbc42:4.0')
    
    展开全文
  • 一、背景介绍 VR是什么 虚拟现实Virtual Reality的英语缩写。VR 主要有手机盒子、头盔和一体机三种。...虚拟现实技术是一种可以创建和体验虚拟世界的计算机仿真系统它利用计算机生成一种模拟环境是一种多源信息...

    一、背景介绍

    VR是什么

    虚拟现实Virtual Reality的英语缩写。VR 主要有手机盒子、头盔和一体机三种。

    虚拟现实技术是一种可以创建和体验虚拟世界的计算机仿真系统它利用计算机生成一种模拟环境是一种多源信息融合的交互式的三维动态视景和实体行为的系统仿真使用户沉浸到该环境中。

    智平测试组耗时一个月的时间,研发了一款基于Oculus的VR语音聊天室软件,现做个阶段性总结:

    研发语言

    • 客户端: unity + C#

    • 语音sdk: Apollo

    • 后台: python + Flask

    硬件环境

    • VR环境: oculus dk 2

    • 运行平台: windows

    二、客户端开发

    1、环境配置

    引擎unity安装: https://unity3d.com/cn/get-unity/download/archive。

    Unity5.1版本后全力支持VR开发,所以最好下载5.1版本以后的版本。安装过程是一键式安装。

    编译Support包:unity可以很方便一直到多个平台上,依赖的即是下载安装对应unity版本的Support-for-Editor。下载安装方式是,打开安装的unity,随便新建一个工程,打开File->Build Settings如下图,如果没有下载平台的Support-for-Editor,就会有下图中的Open Download page,点击按钮就会下载,然后就是一键式安装,重启unity可生效。 

    Unity VR支持:

    1)下载OVRPlugin for Unity5(https://developer.oculus.com/downloads/game-engines/1.3.2/OVRPlugin_for_Unity_5),将压缩包中对应的unity版本的文件夹(如5.4,5.3)中的文件解压到Unity安装目录的/Editor/Data/VR/Unity/下边;

    2)打开Unity,Edit->Project Settings->players设置Virtual Reality Supported如下图;

    3)Oculus VR的runtime版本大于0.8;

    4)设置VR的显示模式为Direct(最新的runtime在Oculus的安装目录的/Support/oculus-runtime/DirctDisplayConfig.exe设置)。完成这些步骤以后可以在Unity中直将工程运行在VR上。 

    Oculus runtime安装:0.8以下的版本可在 https://developer.oculus.com/downloads 直接下载,新版需要在https://developer.oculus.com先下载下载器,然后根据提示下载800多兆安装包,等待下载完(公司网络会墙,下载失败,这里建议使用无线网卡接GuestWifi使用VPN翻墙下载),将VR的HDMI和显卡连接(电脑至少有独立显卡),Tracker连USB3.0(USB2.0也可以,会影响VR的延时),然后就是一键式安装。

    注意:1)在安装新版本的runtime之前要先卸载旧版本;
         2)电脑显卡驱动版本更新为最新版),卸载Microsoft Visual C++ 2015相关的组件。
    

    完成上面四个步骤的准备后便可以在Unity中尽情绘制你的VR世界了。

    Oculus sdk 配置流程:

    1、下载OVRPlugin for Unity5(https://developer.oculus.com/downloads/game-engines/1.3.2/OVRPlugin_for_Unity_5),将压缩包中对应的unity版本的文件夹(如5.4,5.3)中的文件解压到Unity安装目录的/Editor/Data/VR/Unity/下边。

    2、下载Oculus Utilities for Unity5 (https://developer.oculus.com/downloads/game-engines/1.5.0/Oculus_Utilities_for_Unity_5),在Unity3d中import Custom Package,导入下载压缩包中OculusUtilties目录下的OculusUtilities.unitypackage官方插件包,在我们的工程下会生成OVR总目录,看下其中包含的子目录:

    • Editor:包含了新增Unity编辑器功能的脚本。
    • Materials:包含了内置图形组件所需的材质。
    • Meshes:包含OVR脚本所需的网格,例如TrackerBounds。
    • Moonlight:包含了开发GearVR所需的一些基本的类、材质、组件等。
    • Prefabs:包含了三个支持VR场景开发的预置体OVRCameraRig相机预置体、OVRTrackerBounds位置跟踪预置体、OVRPlayerController角色控制预置体。
    • Scenes:demo场景。
    • Scripts:包含用来绑定VR框架和Unity组件的C#脚本。
    • Textures:部分组件用到的纹理贴图。

    3、将OVRPlayerController组件添加到场景中,调整位置,大小,碰撞器等。

    4、调整OVRPlayerController里的CharacterController以适应角色控制的需求。

    5、根据需求修改OVRPlayerController源码,适配产品提出的需求。

    6、焦点控制逻辑在OVR/Gaze/目录下,目前焦点控制方式支持Click和Gaze两种方式,Gaze的响应时间支持自定义,需要添加到2D控件的EventSystem中,并需要与默认的StandaloneInputModule的enable保持互斥状态。

    官方文档 [https://developer.oculus.com/documentation](https://developer.oculus.com/documentation)](https://developer3.oculus.com/documentation/)
    

    2、聊天室主要功能模块业务逻辑架构图

    2.1 多人实时语音聊天 

    2.2 客户端/后台用户管理 

    2.3 语音发送彩蛋 

    三、聊天室动画功能实现

    聊天室动画涉及的内容主要有

    • 1、人物在房间内的走动
    • 2、彩蛋效果的添加
    • 3、多个角色(三个)动画的适配

    第一部分:人物在房间内的走动

    人物走动部分的实现稍微复杂一些,用到了2D Blendtree,这是一个什么概念呢?

    首先解释下BlendTree,他是把多个动画进行合并到一个状态中,举个例子:把左前,往前走,右前的动画放到一个BlendTree中,用一个float型参数控制;好处:减少状态个数

    2D是什么概念呢,还是举个例子:如果我想要用键盘的左右方向键控制动画的左前右转,用上下键控制idle走和跑,那么我可以用两个float变量分别控制两个方向的动画,这就是2D BlendTree。

    来,让我们为聊天室的走动添加2D BlendTree吧!

    第一步:添加动画资源,添加如下动画资源(直接导入fbx文件) 

    第二步:调整动画资源中的animationchip,完成以下的动画片段 

    这里有个坑提前预防说一下: 

    1、Loop Time选项一定选上啊,否则动画只转向不运动啊 

    2、这个选项勾上啊,要不让动画会跑到地面底下啊

    第三步:在状态机中添加BlendTree(右键添加),双击进入BlendTree界面进行设置:1、设置direction和speed两个参数;2、选择为2DBlendTree;

    3、添加动画;

    4、调整动画在坐标系中的位置,便于代码控制 

    注,可以通过调整BlendTree中的speed和direction控制块预览动画效果。
    

    第四步:代码控制,主要是控制横向的方向和纵向的速度。 
    代码很简单,就不用多讲了 

    第二部分:彩蛋效果的添加

    彩蛋效果是,按键盘的某些按键可以触发一些动画效果;再聊天室中,也可以通过语音触发一些动画效果,这里做的就是动画效果。

    还记得前面提到的动画控制器中的layer层嘛,就是通过不同层添加的动画效果

    有两种方式添加,第一种方式是,直接从其他状态切换到菜单动画;优点是:非常简单;缺点是:动画不会叠加,要终止之前的动画后,才能播放菜单动画。

    第二种是添加一个新的layer,为新的层添加Avatar蒙板,此种添加的好处是,添加的蒙板动画可以和底层动画叠加,可以在底层动画运动的同时做出菜单动作,例如:在走动的过程中挥手。缺点是:要自己做蒙板(其实也很简单啦)

    因为聊天室内基本上走动比较少,之前预定是静止时添加彩蛋,需求决定实现,所以采用的是第一种方式,因为比较简单,这里就不赘述了。

    我们重点说下第二种方式:

    第一步:为状态机添加一个新层,命名为Wave 

    第二步:创建一个蒙板,并且配置新添加的层

    1、在assets的某个文件中创建蒙板:右键》create》avatar mask,我们这里命名为WaveMask;

    2、单击此文件,在右侧Inspector中查看,配置蒙板如下图(除右手臂外,其他都禁用)

    3、配置Wave层如下 

    第三步:添加彩蛋动画

    1、添加挥手动画资源,把动画拖动到wave层,设置如下(添加一个空的状态不加任何动画) 

    2、添加一个WaveBool的bool型变量控制挥手(初始值设为false) 

    3、添加EmptyState到IdleWave的transition的条件控制(为true时触发此状态) 

    4、添加控制代码在初始化代码中设置动画层的权重为1 
     
    通过按下H键,控制播放挥手 

    注意:EmptyState—>IdleWave的transition中下面参数最好设置为false 

    IdleWave—>EmptyState的transition中下面参数一定设置为true 

    要在Start()函数中初始化时,设置层级的权重为1(不仅是配置wave层时层级权重为1喔)

    最后,点击运行,在按方向键控制运动的同时,按下H键,你会发现,你的动画同时挥手了。

    第三部分:多个角色(三个)动画的适配

    其实Unity动画系统Mecanim的retargeting,我觉的有两层意思

    第一,就是导入的模型骨骼,和系统内部的内置的骨骼肌肉进行映射关联

    第二,是把已知的动画控制器control和导入的新模型进行匹配,大家还记得这个图嘛 
     
    导入了带有蒙皮的动画模型,生成avatar,关联动画控制器,新导入的模型就按照控制器的方式动起来了。

    四、VR视线交互

    Unity的Camera支持Raycasting特性,其实际功能是从视野中心向世界场景中投射一条线,射线Ray是object,能够指向视野中的具体的点,可以返回相应的坐标或者触碰到的物体信息,这个信息可以通过RaycastHit 对象获取到。

    代码实例

    using UnityEngine;
    using System.Collections; 
    
    public class eyeControl : MonoBehaviour 
    {
    Ray ray;RaycastHit hitInfo;
    public Transform mainCamera; void Update ()
     { 
     //声明ray,方向是主Camera的前方
    ray = new Ray(mainCamera.transform.position, mainCamera.transform.forward); 
    //Physics检测碰撞体,得到被视线碰撞到的物体objectHit 
    
    if (Physics.Raycast (ray, out hitInfo)) { 
            Transform objectHit = hitInfo.transform;
     //针对碰撞体做处理,实现视线交互动作
    } 
    } 
    } 
    

    需要注意的是如果想要Raycast能够碰撞对应物体进行控制,需要给object添加Collider,Collider的形状可以和物体模型完全一致。 

    也可以设置为特定几何形状的碰撞体,如胶囊形状、球形。 

    Oculus OVRPlugin Camera绑定问题

    使用Oculus VR进行开发调试时,需要使用Oculus开发插件里的OVRPlayerController来进行Camera绑定,否则对应的视线无法从正确的VR摄像头发出。

    选中OVRPlayerController中的CenterEyeAnchor,使视线和中央视野锚点绑定 
     
     
    视线周围实现了一个进度圈,在视线碰撞到物体时,视线周围会出现对应的进度条提示用户当前存在操作。

    实现效果图如下: 

    五、研发小tips:

    1、实现的难点和注意点:

    UI

    在传统的非VR项目中,UI通常显示在界面的顶部,用来显示生命值,得分之类的信息,通常被称为HUD。在Unity中,添加HUD样式的非剧情型UI相对简单,只需要在UI Canvas的Render Mode中选择Screen Space——Overlay或者是Screen Space-Camera。 
     
    但是这种UI界面对VR基本不适用,我们的眼睛无法聚焦在如此近的物体上,而Unity VR中根本就不支持Screen Space-Overlay。

    和前面的UI不同的是,我们需要将UI放置到环境中,并在Canvas的Render Mode中选择World Space模式。通过这种方式,就可以让用户的眼睛聚焦到UI上了。 
     
    因为UI被放置在环境中,有可能我们通过摄像机看到UI会是侧面,或者是反过来的。 
     
    如果要使得UI一直在摄像机中正常显示,可以将UI的rotation绑定到mainCamera的rotation上。如下图,使用Oculus时,我们可以将UI绑定到CenterEyeAnchor上。 

    GameObject

    1、如果是在场景中固有的GameObject,即将GameObject直接拖到Hierarchy中的,在其他GameObject的脚本中需要使用的该GameObject的一些属性是,尽量避免使用GameObject.Find(),而是在脚本中申明public GameObject;然后在Script直接赋值。

    2、GameObject的SendMessage()方法,可以方便调用到绑定在GameObject上的脚本里的方法,但是调不到GameObject的子物体上的脚本。 

    3、GameObject.Instantiate(Resources.Load(“Dude_CharacterNet”)) 
    as GameObject 创建物体只能在主线程中创建,不能在子线程中进行。Mono脚本的主线程有Start(),UpDate(),Awake()等。

    调试

    1、Unity在IDE里面运行工程时,在Console界面可以看到开发哥打的Log以及系统抛的异常警告等。

    2、在编译的时候勾选play->use player log,运行编译生成的exe时,在同级目录EXECNAME_Data\ouyput_log.txt的文件中保存有Debug.Log的信息。 

    3、在执行EXE时实时输出Debug.Log的方法:http://blog.csdn.net/cartzhang/article/details/49818953

    32位dll

    当unity IDE是64位,使用了32位dll时,在IDE里面运行项目时,会提示dll找不到的状况(System.DllNotFoundException)。这时应将工程编译成exe后再运行,且编译的时候Architeture应选择X86。 

    本章完~

    原文链接:(http://mp.weixin.qq.com/s?__biz=MzIxNzEyMzIzOA==&tempkey=vJVMUTb9SkapkhCyHTYQJIgY/3BrmjkDAAwInCX4LgFYqgbecbgPcOk

    展开全文
  • DAMO在启动或初始化时可能会挂起 环境 产品:Lotus Domino Access for Microsoft Outlook平台:Windows版本:6.5 问题 ...
  • 1.C# GameObject.Find("ObjectName").GetComponent&lt;scriptName&gt;().enabled=true/false; 2.js GameObject.Find("...).GetComponent(scriptname).enabled=true/false;... 
  • Learn more about The Computing Conference 2017 The 21st century needs a 21st-century approach to problem-solving, Jack Ma said Wednesday. Addressing a standing room-only crowd ...
  • 一、进程间通信概念首先,需要了解一下什么是进程间通信。进程之间的相互通信的技术,称为进程间通信(InterProcess Communication,IPC)。下图列出 4 种实现所支持的不同形式的 IPC。之前进程间交换信息的方法只能...
  • 集成支付宝支付Damo

    2020-05-28 23:34:21
    支付宝支付Damo,里面main里注释的代码放开就可以了,不放开就是跟后台要orderInfo数据
  • 1.TCP的NIO服务端代码实现DAMO(java) package cgz.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; ...
  • owsdamo版本发布帖 OWS团队开发的目标是:用最简单的代码构造最实用的软件。 下载说明: 此版本适用于有一定运维基础的朋友下载安装。 下载后请仔细阅读readme.txt,根据readme进行安装和配置。 ...
  • 毕设做了2周,实现了基本的本地识图功能,就两个文件,接下来复杂的功能就得多家几个文件了就变得麻烦,先发出来damo吧。 可以实现精确识图功能(缩略图那样的) (界面没优化,将就着看看吧……当作毕设弄完我就...
  • TEST_DAMO.rar

    2020-05-18 09:29:39
    本实验利用MFC创建指定位置的隐藏目录,在查看和删除目录内容时需密码才可执行操作,实例简单,适用于初学者,简单易懂。
1 2 3 4 5 ... 20
收藏数 869
精华内容 347
热门标签