unity启动高级.net功能模块

2024-03-15 14:58

本文主要是介绍unity启动高级.net功能模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

重要说明:

首先,目前的unity3d只支持.net3.5的功能,最新的测试版貌似可以使用.net的高级功能,但也还不能发布使用。为此这里有一个可以实现类似功能的方案--利用unity3d,进程启动另一个微软的中间软件,在unity3d中向其传入参数,并利用反射将开发的dll加载到其中,最后将得到的结果返回到unity3d。这样的过程有点复杂,但我想如果封装的好,应该还是比较容易使用的。在说一个开发这个例子的背景,前些日子,利用windows自带的api可以实现打开窗口选择文件,但由于使用参数复杂,封装了两个有简单参数的类,但目前发现不稳定,会出现崩溃的意外,而且还不能正常显示一些基本信息。为此伤透了脑筋。如果是微软自己的软件来调用这些接口应该不会出现这些问题,更何况winform和wpf已经把这样常用的功能封装好了,只需要调用就好了。无奈我的大unity3d还需要购插件才能使用类似的选择文件的功能。其次,由于实际开发过程中需要一些联合unity和wpf或winform的功能,如果将winform做成3.5到是可以放置到unity中使用,但这样做的情况是丑到爆的界面。如果将使用wpf封装一些界面模块的话,应该就是不能使用了,这时如果用wpf开发一个启动器专门用来加载其相关的dll,然后将最后的结果传回到unity就好了。当然本文只使用了winform开发了一个启动器,还有大量的工作需要去完善和实现,最跟本的还是要得项目能用上才有时间继续开发了。

下面将介绍这个模块的使用方法,以打开文件和通信为例子,但并不是说这个模块只有这点功能,如果需要可以自行扩展。最后会后讲一下程序中的重点和难点,然后会附上github源码地址,如果有兴趣自己去研究一下,如果能扩展和提出宝贵的意见更好。

一、最终功能简单实现:

1.可以使用所有的window自带的dialog(文件选择,颜色选择)


(图一.颜色和路径选择后)


(图二、打开文件选择框)

2.可实时通信交互数据


(图三、利用外部面板来操作unity3d对象)

二、以打开文件夹选择为例的使用方法说明:

1.在unity3d的中定义中间程序的相对路径位置

程序中有一些需要使用协程进行等待的功能,同时在运行和结束时自动注册和注销一些信息,所以将这部分功能写到了一个继承于Monobehaiver的脚本中,但使用时,用了一个静态的脚本,便于调用,初始化路径相关的功能都放置到这个静态脚本中,而有关于通信模块的调用的事件的分发调用都放置到了这个组件脚本中。

下面是程序路径配制方法,在FormLoaderUser脚本中

public static void InitEnviroment(){GameObject go = new GameObject(typeof(FormLoaderBehavier).ToString());behaiver = go.AddComponent<FormLoaderBehavier>();string path = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Demo")) + "FormLoader/FormLoader/bin/Debug/FormLoader.exe";Behaiver.TryOpenHelpExe(path);}

2.编写第三方dll

由于中间程序只负责启动的功能,主要的逻辑将在具体的dll中实现,下面是我写的对话框dll中的一个选择文件对话框的方法。要注意的一点是,直接传字符串过来始终会出现乱码,暂时还没有解决这个问题,直接传byte[]会出现新的问题,所以这部分暂时用这个方法来进行中文字符串的处理。

        private string ConverByte(object titlebytes){JSONArray array = JSONArray.Parse(titlebytes.ToString()).AsArray;byte[] byts = new byte[array.Count];for (int i = 0; i < byts.Length; i++){byts[i] = (byte)array[i].AsInt;}string title = System.Text.Encoding.UTF8.GetString(byts);return title;}public string OpenFileDialog(object titlebytes, string filter, string initialDirectory){string title = ConverByte(titlebytes);string resultFile = "";try{OpenFileDialog openFileDialog1 = new OpenFileDialog();openFileDialog1.InitialDirectory = initialDirectory;openFileDialog1.Filter = filter;// "txt files (*.txt)|*.txt|All files (*.*)|*.*" ;openFileDialog1.FilterIndex = 2;openFileDialog1.Title = title;openFileDialog1.RestoreDirectory = true;if (openFileDialog1.ShowDialog() == DialogResult.OK){// Insert code to read the stream here.resultFile = openFileDialog1.FileName;}}catch (Exception ex){MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);}return resultFile;}
3.在unity通过中间程序调用以上的dll

   由于中间程序可能会出现问题,首先可以进行不通过中间程序的方法,确保dll和unity中的方法都能正常运行,Test方法调用后在编辑器模式下可以直接打开.net 2.0的winform
界面 ,如图四所示:
public static void OpenFileDialog(string title, FileType fileType, string initialDirectory, Action<string> onReceive){List<byte> byts = new List<byte>(Encoding.UTF8.GetBytes(title));string path = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Demo")) + "Demo/FileDialogHelp/FileDialogHelp/bin/Debug/FileDialogHelp.dll";string clsname = "FileDialogHelp.FileDialog";string methodname = "OpenFileDialog";string filter = "";switch (fileType){case FileType.Txt:filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";break;default:break;}object[] aregument = new object[] { byts, filter, initialDirectory };DllFuction pro = new DllFuction(path, clsname, methodname, aregument);string text = JsonConvert.SerializeObject(pro);Behaiver.AddSendQueue(ProtocalType.dllfunction, text);Behaiver.RegisteReceive(ProtocalType.dllfunction, onReceive);//Test(text);}

 static void Test(string x){DllFuction protocal = JsonConvert.DeserializeObject<DllFuction>(x);System.Reflection.Assembly asb = System.Reflection.Assembly.LoadFrom(protocal.dllpath);var cls = asb.GetType(protocal.classname);var method = cls.GetMethod(protocal.methodname);var instence = Activator.CreateInstance(cls);string back = (string)method.Invoke(instence, protocal.argument);Debug.Log(back);}


(图四,在编辑器模式下不通过中间程序反射得到的打开文件窗口界面)

4.异步调用

在view 层,可以直接关联一个按扭来触发打开界面事件,如下所示,当选择完成后会自动将title的信息设置好,但直得到注意的是,这了防止unity因为通信卡死,主程序已经将

这部分功能写在了线程中,如果在选择文件之前,title已经被销毁了,就会出现问题,所以在程序中为了防止这问题,最好是要判断回调时候是否还有需要。当然一个成熟的功能模块应该也要在内部判断异常的机制。

  private void openFileBtnClicked(){FormLoaderUser.OpenFileDialog("打开文件选择窗口", FormLoaderUser.FileType.Txt, "C://", (x) =>{title.text = x;});}
三、双向通信模块为例说明可扩展性
以上的对话框打开的实现有多种方法,本文介绍的是相对复杂的实现方式,但也是目前我所知最稳定的解决方式,但用其作为本模块功能介绍的目的并不是要实现一个窗口打开的功能,而是要介绍一种unity中调用window窗体的可行的方案。下面将介绍在unity中发信息到winfom,和在winform中控制unity对象的方式,而注意的是这里的winform不是一个独立的程序,而是一个基于中间程序的dll模块!

1.封装一个简单的form(图五)

(图五,一个简单的form界面)

这个form启动器中,向form注册了状态改变的事件,当进行点击按扭的操作时,需要在这个dll中去定义一个和unity3d通信的协议,注意到如果用其他dll时,这并不会对中间程序产生任何需要修改的功能需求,下面是这个form的所有代码,就是告诉unity3d中的一个简单对象应该怎么变化

public partial class MainForm : Form{public MainForm(){InitializeComponent();}public Action<string> changedaction;private void turnBig(object sender, EventArgs e){var obj = new { methodName = "turnBig" };string txt = JsonConvert.SerializeObject(obj);if(changedaction != null){changedaction.Invoke(txt);}else{MessageBox.Show("changedaction == null");}}private void turnSmall(object sender, EventArgs e){var obj = new { methodName = "turnSmall" };string txt = JsonConvert.SerializeObject(obj);if (changedaction != null){changedaction.Invoke(txt);}else{MessageBox.Show("changedaction == null");}}private void toLeft(object sender, EventArgs e){var obj = new { methodName = "toLeft" };string txt = JsonConvert.SerializeObject(obj);if (changedaction != null){changedaction.Invoke(txt);}else{MessageBox.Show("changedaction == null");}}private void toRight(object sender, EventArgs e){var obj = new { methodName = "toRight" };string txt = JsonConvert.SerializeObject(obj);if (changedaction != null){changedaction.Invoke(txt);}else{MessageBox.Show("changedaction == null");}}private void toDown(object sender, EventArgs e){var obj = new { methodName = "toDown" };string txt = JsonConvert.SerializeObject(obj);if (changedaction != null){changedaction.Invoke(txt);}else{MessageBox.Show("changedaction == null");}}private void toUp(object sender, EventArgs e){var obj = new { methodName = "toUp" };string txt = JsonConvert.SerializeObject(obj);if (changedaction != null){changedaction.Invoke(txt);}else{MessageBox.Show("changedaction == null");}}public void SetLable(string txt){label1.Text = txt;}internal void RegisterHolder(Action<string> onStateChange){this.changedaction = onStateChange;}}2.将unity3d中的一个Cube脚本关联到模块中在程序的运行初其就将需要操作的对向注册到了这个通信模块中,只要form中触发了对应的事件,那么unity中可以触发相应的效果。public static void ReigsterCube(Cube cube){try{behaiver.RegisteReceive(ProtocalType.communicate, (txt) =>{Debug.Log(txt);JSONNode node = JSONNode.Parse(txt);string methodName = node.AsObject["methodName"].Value;var method = cube.GetType().GetMethod(methodName);method.Invoke(cube, null);});}catch (Exception e){Debug.Log(e);}}cube脚本挂到场景的一个对象上面就好public class Cube : MonoBehaviour {void Start(){FormLoaderUser.ReigsterCube(this);}public void turnBig(){transform.localScale *= 2;}public void turnSmall(){transform.localScale *= 0.5f;}public void toLeft(){transform.localPosition += Vector3.left;}public void toRight(){transform.localPosition += Vector3.right;}public void toDown(){transform.localPosition += Vector3.down;}public void toUp(){transform.localPosition += Vector3.up;}
}



3.在unity3d中触发winform的事件

同理,只要在这个窗体打开的时候注册好了相应的事件,在unity可以简单的这样发送信息,效果如图六所示,当前,有一点需要注意,目前还没有解决通过dll加载时会出现中文乱码的问题,如果有方案解决,一定尽快更新代码。

    /// <summary>/// dll中的事件回调/// </summary>/// <param name="state"></param>private void OnHolderStateChanged(string state){dataSender.SendMessage(ProtocalType.communicate.ToString(), state);//由于在unityedior模式下不支持信息传回所以用读写的方式if (writeData){string path = System.IO.Directory.GetCurrentDirectory() + "/" + ProtocalType.communicate.ToString() + ".txt";System.IO.File.WriteAllText(path, state);}}

(图六,在unity3d中向已经打开的模块发信息)

四、要点总结

1.中间程序加载说明

中间程序启动后,只是等待信息接收和分发,并不会消耗多少内存和cpu,下面是这个中间程序的启动是属于窗口程序切换模块的一中,我已经将这部分功能放置到了一个dll中(windowswitch),于是调用一个新的程序只需要关心传入什么参数等不需要关心进程启动,然后怎么获取参数,关闭进程等问题

  public void TryOpenHelpExe(string exePath){windowswitch = new WindowSwitchUnity();string path = exePath;// Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Demo")) + "FormLoader/FormLoader/bin/Debug/FormLoader.exe";if (System.IO.File.Exists(path)){
#if UNITY_EDITORif (windowswitch.OpenChildWindow(path, false, "1"))
#elseif (windowswitch.OpenChildWindow(path, false))
#endif{//打开子应用程序StartCoroutine(DelyRegisterSender());}}else{Debug.LogError("exe not fond");}sendThread.Start();}
需要说明的事,sendmessage还有个问题目前没有解决,暂时无法向unity的编辑器发送信息,只有打包出来之后才能相互沟通,所以暂时,在编辑器模式下只写了一个模拟的实时通信的功能(信息保存到txt中再读取),"1"这个参数就是告诉中间程序应该是以模拟方式运行还是直接发回信息

2.中间程序批量处理dll

unity中序列化的字符串,在中间程序中的解析,下面是一个简单的调用dll中的一个方法,并获取返回值的功能。这个方法写在一个专门处理具体信息解析的脚本中,然后在一个管理程序中进行注册。

Win32API.SetWindowPos(currWin,-1,0,0,0,0, Win32API.SetWindowPosFlags.DoNotRedraw);
这句代码的作用就是将窗口置于最前端显示,不然打开了窗口,怕是找不到、
public void InvokeSimpleMethodByProtocal(string x){Win32API.SetWindowPos(currWin,-1,0,0,0,0, Win32API.SetWindowPosFlags.DoNotRedraw);try{DllFuction protocal = JsonConvert.DeserializeObject<DllFuction>(x);Assembly asb = Assembly.LoadFrom(protocal.dllpath);var cls = asb.GetType(protocal.classname);var method = cls.GetMethod(protocal.methodname);var instence = Activator.CreateInstance(cls);string back = (string)method.Invoke(instence, protocal.argument);dataSender.SendMessage(ProtocalType.dllfunction.ToString(), back);//由于在unityedior模式下不支持信息传回所以用读写的方式if (writeData){string path = System.IO.Directory.GetCurrentDirectory() + "/" + ProtocalType.dllfunction.ToString() + ".txt";System.IO.File.WriteAllText(path, back);}}catch (Exception e){MessageBox.Show(e.Message);}}
以上这种方法的注册是这样写的,分别用于处理不同的dll类型
  /// <summary>/// 注册执行事件/// </summary>private void RegistEntry(){exeHelp = new ExecuteHelp(swi.Current,dataSender,writeData);Win32API.SendMessage(swi.Current, Win32API.WM_ACTIVATE, Win32API.WA_ACTIVE, IntPtr.Zero);dataReceiver.RegisterEvent(ProtocalType.dllfunction.ToString(), exeHelp.InvokeSimpleMethodByProtocal);dataReceiver.RegisterEvent(ProtocalType.lunchholder.ToString(), exeHelp.LunchCommnuicateHolder);dataReceiver.RegisterEvent(ProtocalType.communicate.ToString(), exeHelp.RegisterCommuateModle);}

3.  不显示主窗口的中间程序

 由于sendmessage的实现是基于窗体程序的,如果没有主窗体这功能貌似有点问题,handle找不到,所以研究了下不显示窗体的启动方式,自己定义个HideOnStartupApplicationContext继承于ApplicationContext,在unity中找到了handle后将窗体隐藏了就好,这样:

   public HideOnStartupApplicationContext(string[] datas){mainForm = new Form();mainForm.WindowState = FormWindowState.Minimized;mainForm.Show();swi = new WindowSwitchWinform();if (swi.OnOpenedByParent(ref datas)){try{if (datas.Length > 0) writeData = int.Parse(datas[0]) == 1;}catch (Exception e){MessageBox.Show(e.Message);}RegistMessageTrans(parent);}mainForm.Hide();}
五、参考资源:

1.通信核心模块来源

这个大神写的sendmessage功能,实现了unity3d中和其他进程进行通信的方案

unity3d进程通信利用WM_COPYDATE和HOOK

2.常用winapi功能说明

这个网址有所以window开发的api,及c#调用的方法,比较于详细

PInvoke.net

3.本程序相关的dll源码地址

这是个人开发的模块功能,github上有不少开源功能,欢迎收藏

通信功能的封装及demo --sendmessage_unity

窗口切换功能的封装及demo -- windowswitch

4.c#使用json必需品

非常方便的进行json的转换

Newtonsoft.Json

5.隐藏窗口程序的实现方法

WinForm 之 程序启动不显示主窗体


这篇关于unity启动高级.net功能模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/812349

相关文章

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

2、PF-Net点云补全

2、PF-Net 点云补全 PF-Net论文链接:PF-Net PF-Net (Point Fractal Network for 3D Point Cloud Completion)是一种专门为三维点云补全设计的深度学习模型。点云补全实际上和图片补全是一个逻辑,都是采用GAN模型的思想来进行补全,在图片补全中,将部分像素点删除并且标记,然后卷积特征提取预测、判别器判别,来训练模型,生成的像

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud