随着技术的发展,越来越多的APP期望拥有3D,AR的能力。要达到这个目标可以选择使用原生开发,也可以使用Unity成熟的3D开发技术链,通过嵌入的方式将Unity容器嵌入到APP中。这里介绍的是通过嵌入Unity容器的方式来实现APP的3D,AR能力的。
Unity集成到iOS应用的本质是将Unity中所有用到的资源,模型,代码打包成一个framework动态库,然后嵌入到APP的内部。APP启动后加载Unity页面时,会加载这个framework动态库和里面的所有资源。然后在APP中展示这个Unity页面。
Unity集成到iOS应用的官方提供的方案是以Workspace的方式集成,具体步骤如下:
1.Unity项目通过Unity Editer 导出一个iOS项目,这个iOS项目里包含了整个Unity项目的所有代码和资源,可以直接运行在iOS系统上。
2.用Xcode创建一个原生的iOS项目
3.用Xcode创建一个Workspace, 然后通过add file 的形式将这2个项目添加到一个Workspace下面。
4.修改Unity-iPhone项目打包设置,使其打包出适合嵌入的UnityFramework动态库,
5.修改NativeiOSApp项目配置,添加UnityFramework动态库。
对Workspace下面的2个项目进行配置
Unity-iPhone创建
在生成UnityFramework动态库前,需要将Unity项目导出一个iOS项目,导出的过程如下:
1.点击File->Building Settings进入到选择导出页面
2.选择要导出的平台,这里选择iOS,然后点击右下角的"Switch Plateform"按钮,然后点击上面的“Add Open Scenes”添加场景
3.点击“Build”,导出iOS项目
Unity-iPhone设置
关闭以Workspace方式打开的Xcode,单独打开Unity-iPhone项目
1.在Unity-iPhone项目中,设置Data 这个Group的 Target Memebership, 将里面的UnitFramework项打勾,表示这个Data在生成Framework静态库时也要作为其中的一员,放置进去。
2.将NativeCallProxy.h头文件从Framework的 protect组移动到public组,将头文件开放出来。路径为: Unity-iPhone / Libraries / Plugins / iOS / NativeCallProxy.h
3.手机连接电脑,运行,生成静态库。
NativeiOSApp原生项目设置
以Workspace方式打开项目
1.在NativeiOSApp Target - General - Frameworks, Libraries, and Embedded Content 通过点击+ 将UnityFramework设置嵌入到项目中
2.在NativeiOSApp Target - Build Phases - Link Binary With Libraries中,点击-,将UnityFramework移除,表示禁止在项目链接时将UnityFramework链接到可执行文件中,它只作为内嵌在APP中的静态库在运行时加载使用。
3.手机连接电脑,运行,看到Demo运行的效果。
Unity与原生通信
Unity调用原生
1.首先在原生中实现代理协议NativeCallProxy中的方法(注册由谁来处理unity的调用)和添加暴露给unity的方法。
NativeCallProxy.h文件中方法的声明
1 2 3 4 5 6 | __attribute__ ((visibility( "default" ))) @ interface FrameworkLibAPI : NSObject // call it any time after UnityFrameworkLoad to set object implementing NativeCallsProtocol methods +( void ) registerAPIforNativeCalls:(id @end |
NativeCallProxy.cpp文件中方法的实现
1 2 3 4 5 6 7 8 9 | @implementation FrameworkLibAPI id +( void ) registerAPIforNativeCalls:(id { api = aApi; } @end |
给Unity暴露的原生调用方法
1 2 3 | extern "C" { void showHostMainWindow( const char * color) { return [api showHostMainWindow:[NSString stringWithUTF8String:color]]; } } |
2.然后在原生的Unity启动时,注册这个处理的对象
1 2 3 4 5 6 7 8 9 | - ( void )initUnity { [self setUfw: UnityFrameworkLoad()]; // Set UnityFramework target for Unity-iPhone/Data folder to make Data part of a UnityFramework.framework and uncomment call to setDataBundleId // ODR is not supported in this case, ( if you need embedded and ODR you need to copy data ) [[self ufw] setDataBundleId: "com.unity3d.framework" ]; [[self ufw] registerFrameworkListener: self]; [NSClassFromString( @"FrameworkLibAPI" ) registerAPIforNativeCalls:self]; } |
3.在Unity中,调用iOS的原生方法
Cube.cs脚本中声明在APP中存在方法showHostMainWindow
1 2 3 4 5 6 | #if UNITY_IOS || UNITY_TVOS public class NativeAPI { [DllImport( "__Internal" )] public static extern void showHostMainWindow( string lastStringColor); } #endif |
Cube.cs脚本中添加Unity 2d按钮点击事件,调用iOS原生方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void OnGUI() { GUIStyle style = new GUIStyle( "button" ); style.fontSize = 45; if (GUI.Button( new Rect(10, 300, 600, 100), "Show Main With Color" , style)) showHostMainWindow(); } void showHostMainWindow() { #if UNITY_ANDROID try { AndroidJavaClass jc = new AndroidJavaClass( "com.unity.mynativeapp.SharedClass" ); jc.CallStatic( "showMainActivity" , lastStringColor); } catch (Exception e) { appendToText( "Exception during showHostMainWindow" ); appendToText(e.Message); } #elif UNITY_IOS || UNITY_TVOS NativeAPI.showHostMainWindow(lastStringColor); #endif } |
原生调用Unity
点击原生发送消息按钮,发消息给Unity。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | self.btnSendMsg = [UIButton buttonWithType: UIButtonTypeSystem]; [self.btnSendMsg setTitle: @"Send Msg" forState: UIControlStateNormal]; [self.btnSendMsg addTarget: self action: @selector(sendMsgToUnity) forControlEvents: UIControlEventPrimaryActionTriggered]; - ( void )sendMsgToUnity { [[self ufw] sendMessageToGOWithName: "Cube" functionName: "ChangeColor" message: "yellow" ]; } /* goName: 场景中的游戏物体GameObject name: 这个游戏物体挂载的脚本中的一个方法 msg: 参数 */ - ( void )sendMessageToGOWithName:( const char *)goName functionName:( const char *)name message:( const char *)msg { UnitySendMessage(goName, name, msg); } void UnitySendMessage( const char * obj, const char * method, const char * msg); |
Cube.cs脚本中的方法实现
1 2 3 4 5 6 7 8 9 10 11 12 | string lastStringColor = "" ; void ChangeColor( string newColor) { appendToText( "Changing Color to " + newColor ); lastStringColor = newColor; if (newColor == "red" ) GetComponent else if (newColor == "blue" ) GetComponent else if (newColor == "yellow" ) GetComponent else GetComponent } |
Unity热更新
在Unity项目中使用纯C#构建的渐渐不能适应移动端的需求,移动端通常通过添加热更新插件来实现热更新功能。这个过程起关键作用的是Lua和起对应的解释器。
lua是解释型语言,并不需要事先编译成块,而是运行时动态解释执行的。
这样LUA就和普通的游戏资源如图片,文本没有区别,因此可以在运行时直接从WEB服务器上下载到持久化目录并被其它LUA文件调用。
Lua热更新解决方案是通过一个Lua热更新插件(如ulua、slua、tolua、xlua等)来提供一个Lua的运行环境以及和C#进行交互。
所以lua热更新的流程可以简单理解为:将逻辑代码使用脚本实现,再将脚本转化为文本资源,最后以更新资源的形式来更新程序,以此来实现热更新。
注意:
在iOS应用内的Unity容器中,Untiy的C#资源和Lua资源都是在Unity容器内部进行加载的。如果Unity采用的Lua热更新,则需要原生侧在Lua脚本和资源下载完成后通知Unity进行加载展示。
Demo地址
NativeiOSApp项目地址:https://github.com/zhfei/uaal-example
将 Unity 集成到原生 iOS 应用程序中官方文档:https://docs.unity3d.com/cn/2020.2/Manual/UnityasaLibrary-iOS.html