经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
iOS使用Unity容器动态加载3D模型
来源:cnblogs  作者:滴水微澜  时间:2024/3/7 9:10:44  对本文有异议
项目背景
我们的APP是一个数字藏品平台,里面的很多藏品需要展示3D模型,3D模型里面可能会包含场景,动画,交互。而对应3D场景来说,考虑到要同时支持iOS端,安卓端,Unity是个天然的优秀方案。
对于Unity容器来说,需要满足如下的功能:
1.在APP启动时,需要满足动态下载最新的模型文件。
2.在点击藏品查看模型时,需要根据不同的参数展示不同的模型,并且在页面消失后,自动卸载对应的模型。
 
如果要实现上面说的功能则是需要使用Unity的打包功能,将资源打包成AssetBundle资源包,然后把ab包进行上传到后台服务器,然后在APP启动时从服务器动态下载,然后解压到指定的目录中。
当用户点击藏品进入到Unity容器展示3D模型时,则可以根据传递的模型名称和ab包名,从本地的解压目录中加载对应的3D模型。
 
AssetBundle打包流程
创建AB打包脚本
AB包打包是在Editer阶段里。
首先要创建一个Editer目录并把脚本放置到这个目录下面,注意它们的层级关系:Assert/Editor/CS脚本,这个层级关系是固定的,不然会报错。
0
脚本实现如下:
  1. using UnityEditor;
  2. using System.IO;
  3.  
  4.  
  5. /// <summary>
  6. ///
  7. /// </summary>
  8.  
  9. public class AssetBundleEditor
  10. {
  11. //1.编译阶段插件声明
  12. [MenuItem("Assets/Build AssetBundles")]
  13. static void BuildAssetBundles() {
  14. string dir = "AssetBundles";
  15. if (!Directory.Exists(dir)) {
  16. //2.在工程根目录下创建dir目录
  17. Directory.CreateDirectory(dir);
  18. }
  19. //3.构建AssetBundle资源,AB资源包是一个压缩文件,可以把它看成是一个压缩的文件夹,里面
  20. //可能包含多个文件,预制件,材质,贴图,声音。
  21. BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.iOS);
  22. }
  23. }

设置需要打包的资源

可以在Project选中一个资源(预制件,材质,贴图,声音等),然后在Inspector下面的AssetBundle设置打包成的名称和后缀。如果名称带层级的如:scene/cube,那么打出来的AB包会自己添加一个scene目录,然后在目录下存在了cube资源包。
AB包可以存在依赖关系,比如GameObjectA和GameObjectB共同使用了Material3, 然后它们对应的AssetBundle名称和后缀分别为cube.ab, capsule.ab, share.ab。
虽然GameObjectA中包含了Material3资源,但是 AssetBundle在打包时如果发现Material3已经被打包成了share.ab, 那么就会只打GameObjectA,并在里面设置依赖关系就可以了。
0
 
使用插件工具进行打包
1.从gitHub上下载源码,然后将代码库中的Editor目录下的文件复制一份,放到工程Target的Assets/Editor目录下。打开的方式是通过点击Window->AssetBundle Browser进行打开
 
0
2.打包时,可以选择将打出的ab包内置到项目中,勾选Copy StreamingAssets ,让打出的内容放置在StreamingAssets目录下,这样可以将ab资源内置到Unity项目中。
 
3.通过上面的操作会完成资源打包,然后将打包的产物压缩上传到后台。
0
 
 
 
AssetsBundle资源包的使用
APP启动时,下载AssetBundle压缩包, 然后解压放置在沙盒Documents/AssetsBundle目录下,当点击APP中的按钮进入到Unity容器页面时,通过包名加载对应的ab包进行Unity页面展示。
  1. /// <summary>
  2. ///读取原生沙盒Documents/AssetsBundle目录下的文件,Documents/AssetsBundle下的文件通过Native原生下载的资源
  3. /// </summary>
  4. /// <param name="abName">Documents/AssetsBundle下的ab文件</param>
  5. /// <returns>读取到的字符串</returns>
  6. public static AssetBundle GetNativeAssetFromDocumentsOnProDownLoad(string abName)
  7. {
  8. string localPath = "";
  9. if (Application.platform == RuntimePlatform.Android)
  10. {
  11. localPath = "jar:file://" + Application.persistentDataPath + "/AssetsBundle/" + abName;
  12. }
  13. else
  14. {
  15. localPath = "file://" + Application.persistentDataPath + "/AssetsBundle/" + abName;
  16. }
  17. UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(localPath);
  18. var operation = request.SendWebRequest();
  19. while (!operation.isDone)
  20. { }
  21. if (request.result == UnityWebRequest.Result.ConnectionError)
  22. {
  23. Debug.Log(request.error);
  24. return null;
  25. }
  26. else
  27. {
  28. AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
  29. return assetBundle;
  30. }
  31. //UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(localPath);
  32. //yield return request.Send();
  33. //AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
  34. //return assetBundle;
  35.  
  36. }

注意:当离开Unity容器时需要卸载里面加载的ab包

  1. public void TestUnLoadGameObject()
  2. {
  3. UnLoadGameObjectWithTag("NFT");
  4. }
  5.  
  6. public void UnLoadGameObjectWithTag(string tagName)
  7. {
  8. GameObject go = GameObject.FindWithTag(tagName);
  9. if (go) {
  10. Destroy(go, 0.5f);
  11. } else
  12. {
  13. Debug.Log(go);
  14. }
  15. }
  16.  
  17. public void UnLoadAllGameObjectWithTag(string tagName)
  18. {
  19. GameObject[] gos = GameObject.FindGameObjectsWithTag(tagName);
  20. foreach (GameObject go in gos) {
  21. Destroy(go, 0.5f);
  22. }
  23.  
  24. }

 

模型的相关设置
手势支持
对于加载完成后的模型需要添加手势支持,允许用户旋转,缩放查看,不能说只能静止观看。这里添加手势控制脚本用于支持手势功能。
0
模型实现成功后,把实例对象设置到GestureController组件的Target上面,实现模型的手势支持。
 
加载Unity内置ab资源包的脚本实现:
  1. public void TestLoadStreamingAssetBundle() {
  2. LoadStreamingAssetBundleWithABName("cube.ab", "Cube", "NFT");
  3. }
  4.  
  5. public void LoadStreamingAssetBundleWithABName(string abName, string gameObjectName, string tagName)
  6. {
  7.  
  8. AssetBundle ab = FileUtility.GetNativeAssetFromStreamingAssets(abName);
  9. GameObject profab = ab.LoadAsset<GameObject>(gameObjectName);
  10. profab.tag = tagName;
  11. Instantiate(profab);
  12.  
  13.  
  14. GestureController gc = GameObject.FindObjectOfType<GestureController>();
  15. gc.target = profab.transform;
  16.  
  17. ab.Unload(false);
  18. }

 Unity场景切换的脚本实现:

  1. //接收原生事件:切换场景
  2. public void SwitchScene(string parmas)
  3. {
  4. Debug.Log(parmas);
  5. Param param = new Param();
  6. Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
  7. Debug.Log(res.name);
  8.  
  9. Debug.Log("------------");
  10. for (int i = 0; i < SceneManager.sceneCount; i++) {
  11. Scene scene = SceneManager.GetSceneAt(i);
  12. Debug.Log(scene.name);
  13. }
  14.  
  15. SceneManager.LoadScene(res.name, LoadSceneMode.Single);
  16.  
  17. Debug.Log("------------");
  18. for (int i = 0; i < SceneManager.sceneCount; i++)
  19. {
  20. Scene scene = SceneManager.GetSceneAt(i);
  21. Debug.Log(scene.name);
  22. }
  23. }

 

Unity导出iOS项目
构建UnityFramework动态库
 
0
 
0
此时将得到一个iOS 工程。
 
原生与Unity通信
创建原生与Unity通信接口,并放置到Unity项目中。
0
 
NativeCallProxy.h文件创建通信协议
  1. #import <Foundation/Foundation.h>
  2.  
  3. @protocol NativeCallsProtocol
  4.  
  5. @required
  6.  
  7. /// Unity调用原生
  8. /// - Parameter params: {"FeatureName":"下载资源", "params": "参数"}
  9. - (void)callNative:(NSString *)params;
  10. @end
  11.  
  12. __attribute__ ((visibility("default")))
  13.  
  14.  
  15. @interface NativeCallProxy : NSObject
  16. // call it any time after UnityFrameworkLoad to set object implementing NativeCallsProtocol methods
  17. + (void)registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi;
  18. @end

 NativeCallProxy.mm文件实现如下:

  1. #import "NativeCallProxy.h"
  2.  
  3. @implementation NativeCallProxy
  4. id<NativeCallsProtocol> api = NULL;
  5. + (void)registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi
  6. {
  7. api = aApi;
  8. }
  9.  
  10. @end
  11.  
  12.  
  13. extern "C" {
  14. void callNative(const char * value);
  15. }
  16.  
  17.  
  18. void callNative(const char * value){
  19. return [api callNative:[NSString stringWithUTF8String:value]];
  20. }

 原生的Delegate的实现

  1. #pragma mark - NativeCallsProtocol
  2. - (void)callNative:(NSString *)params {
  3. NSLog(@"收到Unity的调用:%@",params);
  4. }

 

 Unity调用原生
  1. //重要声明,声明在iOS原生中存在下面的方法,然后C#中可以直接进行调用
  2. [DllImport("__Internal")]
  3. static extern void callNative(string value);
  4.  
  5.  
  6. public void changeLabel(string textString) {
  7. tmpText.text = textString;
  8. }
  9.  
  10. public void btnClick() {
  11. Debug.Log(tmpInput.text);
  12. callNative(tmpInput.text);
  13. }
然后根据工程设置,生成UnityFramework。创建UnityFramework的详细流程可以参考文章:https://www.cnblogs.com/zhou--fei/p/17622488.html
然后其他需要拥有Unity能力的APP就可以集成此动态库,展示Unity视图。
 
原生与Unity通信交互
首先定义一套接口,用于规定原生到Unity发送消息时,参数对应的意义。
0
 
然后在场景中添加DispatchGO游戏对象,在此对象上面添加DispatchGO组件,DispatchGO组件用于接收原生发送过来的消息,并进行逻辑处理。
0
 
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.SceneManagement;
  5.  
  6.  
  7. public class Param {
  8. public string packageName { get; set; }
  9. public string name { get; set; }
  10. public string tag { get; set; }
  11. public string type { get; set; }
  12. public string isAll { get; set; }
  13. }
  14.  
  15. public class DispatchGO : MonoBehaviour
  16. {
  17.  
  18. //接收原生事件
  19. public void DispatchEvent(string parmas) {
  20. Debug.Log(parmas);
  21. //事件分发
  22.  
  23. ChangeLabel cl = GameObject.FindObjectOfType<ChangeLabel>();
  24. cl.changeLabel(parmas);
  25. }
  26.  
  27. //接收原生事件:加载模型
  28. public void LoadModel(string parmas)
  29. {
  30. Debug.Log(parmas);
  31. Param param = new Param();
  32. Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
  33. Debug.Log(res.packageName);
  34. Debug.Log(res.name);
  35. Debug.Log(res.tag);
  36. Debug.Log(res.type);
  37.  
  38. if (res.type == "0")
  39. {
  40. LoadAssetUtility laUnity = GameObject.FindObjectOfType<LoadAssetUtility>();
  41. laUnity.LoadStreamingAssetBundleWithABName(res.packageName, res.name, res.tag);
  42. }
  43. else {
  44. LoadAssetUtility laUnity = GameObject.FindObjectOfType<LoadAssetUtility>();
  45. laUnity.LoadNativeAssetBundleWithABName(res.packageName, res.name, res.tag);
  46. }
  47. }
  48.  
  49. //接收原生事件:卸载模型
  50. public void UnLoadModel(string parmas)
  51. {
  52. Debug.Log(parmas);
  53. Param param = new Param();
  54. Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
  55.  
  56. UnLoadAssetUtility unLAUnity = GameObject.FindObjectOfType<UnLoadAssetUtility>();
  57. if (res.isAll == "1")
  58. {
  59. unLAUnity.UnLoadAllGameObjectWithTag(res.tag);
  60. }
  61. else {
  62. unLAUnity.UnLoadGameObjectWithTag(res.tag);
  63. }
  64. }
  65.  
  66. //接收原生事件:切换场景
  67. public void SwitchScene(string parmas)
  68. {
  69. Debug.Log(parmas);
  70. Param param = new Param();
  71. Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
  72. Debug.Log(res.name);
  73.  
  74. Debug.Log("------------");
  75. for (int i = 0; i < SceneManager.sceneCount; i++) {
  76. Scene scene = SceneManager.GetSceneAt(i);
  77. Debug.Log(scene.name);
  78. }
  79.  
  80. SceneManager.LoadScene(res.name, LoadSceneMode.Single);
  81.  
  82. Debug.Log("------------");
  83. for (int i = 0; i < SceneManager.sceneCount; i++)
  84. {
  85. Scene scene = SceneManager.GetSceneAt(i);
  86. Debug.Log(scene.name);
  87. }
  88. }
  89.  
  90. // Start is called before the first frame update
  91. void Start()
  92. {
  93. }
  94.  
  95. // Update is called once per frame
  96. void Update()
  97. {
  98. }
  99. }

在iOS原生侧,本地通过使用unityFramework的sendMessageToGOWithName方法从原生想Unity发送消息。

  1. case 103:
  2. {
  3. NSDictionary *params = @{
  4. @"tag":@"NFT",
  5. @"isAll":@"1"
  6. };
  7. [ad.unityFramework sendMessageToGOWithName:"DispatchGO" functionName:"UnLoadModel" message:[self serialJsonToStr:params]];
  8. }
  9. break;
  10. case 104:
  11. {
  12. NSDictionary *params = @{
  13. @"name":@"DemoScene"
  14. };
  15. [ad.unityFramework sendMessageToGOWithName:"DispatchGO" functionName:"SwitchScene" message:[self serialJsonToStr:params]];
  16. }
  17. break;

Unity通过调用iOS中协议声明的方法void callNative(string value); 进行调用。

  1. //重要声明,声明在iOS原生中存在下面的方法,然后C#中可以直接进行调用
  2. [DllImport("__Internal")]
  3. static extern void callNative(string value);
  4.  
  5. public void btnClick() {
  6. Debug.Log(tmpInput.text);
  7. callNative(tmpInput.text);
  8. }

 

原生端创建Unity容器

在APP启动时,对UnityFramework进行初始化。
  1. @implementation AppDelegate
  2.  
  3.  
  4. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  5. // Override point for customization after application launch.
  6. [UnitySceneManager sharedInstance].launchOptions = launchOptions;
  7. [[UnitySceneManager sharedInstance] Init];
  8. return YES;
  9. }

UnitySceneManager的主要实现逻辑如下:#import "UnitySceneManager.h"#import <UnityFramework/NativeCallProxy.h>

  1. extern int argcApp;
  2. extern char ** argvApp;
  3.  
  4. @interface UnitySceneManager()<UnityFrameworkListener, NativeCallsProtocol>
  5.  
  6. @end
  7.  
  8. @implementation UnitySceneManager
  9. #pragma mark - Life Cycle
  10. + (instancetype)sharedInstance {
  11. static UnitySceneManager *shareObj;
  12. static dispatch_once_t onceKey;
  13. dispatch_once(&onceKey, ^{
  14. shareObj = [[super allocWithZone:nil] init];
  15. });
  16. return shareObj;
  17. }
  18.  
  19. + (instancetype)allocWithZone:(struct _NSZone *)zone {
  20. return [self sharedInstance];
  21. }
  22.  
  23. - (instancetype)copyWithZone:(struct _NSZone *)zone {
  24. return self;
  25. }
  26.  
  27. #pragma mark - Private Method
  28. - (void)Init {
  29. [self initUnityFramework];
  30. [NativeCallProxy registerAPIforNativeCalls:self];
  31. }
  32.  
  33. - (void)unloadUnityInternal {
  34. if (self.unityFramework) {
  35. [self.unityFramework unregisterFrameworkListener:self];
  36. }
  37. self.unityFramework = nil;
  38. }
  39.  
  40. - (BOOL)unityIsInitialized {
  41. return (self.unityFramework && self.unityFramework.appController);
  42. }
  43. // MARK: overwrite
  44.  
  45. #pragma mark - Public Method
  46. - (void)initUnityFramework {
  47. UnityFramework *unityFramework = [self getUnityFramework];
  48. self.unityFramework = unityFramework;
  49. [unityFramework setDataBundleId:"com.zhfei.framework"];
  50. [unityFramework registerFrameworkListener:self];
  51. [unityFramework runEmbeddedWithArgc:argcApp argv:argvApp appLaunchOpts:self.launchOptions];
  52. }
  53.  
  54. - (UnityFramework *)getUnityFramework {
  55. NSString* bundlePath = nil;
  56. bundlePath = [[NSBundle mainBundle] bundlePath];
  57. bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
  58.  
  59. NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
  60. if ([bundle isLoaded] == false) [bundle load];
  61.  
  62. UnityFramework* ufw = [bundle.principalClass getInstance];
  63. if (![ufw appController])
  64. {
  65. // unity is not initialized
  66. [ufw setExecuteHeader: &_mh_execute_header];
  67. }
  68. return ufw;
  69. }
  70.  
  71. #pragma mark - Event
  72.  
  73. #pragma mark - Delegate
  74. #pragma mark - UnityFrameworkListener
  75. - (void)unityDidUnload:(NSNotification*)notification {
  76. }
  77.  
  78. - (void)unityDidQuit:(NSNotification*)notification {
  79. }
  80.  
  81. #pragma mark - NativeCallsProtocol
  82. - (void)callNative:(NSString *)params {
  83. NSLog(@"收到Unity的调用:%@",params);
  84. }
  85.  
  86. #pragma mark - Getter, Setter
  87.  
  88. #pragma mark - NSCopying
  89.  
  90. #pragma mark - NSObject
  91.  
  92. #pragma mark - AppDelegate生命周期绑定
  93. - (void)applicationWillResignActive {
  94. [[self.unityFramework appController] applicationWillResignActive: [UIApplication sharedApplication]];
  95. }
  96.  
  97. - (void)applicationDidEnterBackground {
  98. [[self.unityFramework appController] applicationDidEnterBackground: [UIApplication sharedApplication]];
  99. }
  100.  
  101. - (void)applicationWillEnterForeground {
  102. [[self.unityFramework appController] applicationWillEnterForeground: [UIApplication sharedApplication]];
  103. }
  104.  
  105. - (void)applicationDidBecomeActive {
  106. [[self.unityFramework appController] applicationDidBecomeActive: [UIApplication sharedApplication]];
  107. }
  108.  
  109. - (void)applicationWillTerminate {
  110. [[self.unityFramework appController] applicationWillTerminate: [UIApplication sharedApplication]];
  111. }
  112.  
  113.  
  114. @end
Unity容器的原生实现,其实也是在一个普通的ViewController里面包含了Unity视图的View。
  1. #import "UnityContainerViewController.h"
  2. #import "UnitySceneManager.h"
  3.  
  4. @interface UnityContainerViewController ()
  5.  
  6. @end
  7.  
  8. @implementation UnityContainerViewController
  9. #pragma mark - Life Cycle
  10. - (void)viewDidLoad {
  11. [super viewDidLoad];
  12. // Do any additional setup after loading the view.
  13. [self setupUI];
  14. }
  15.  
  16. - (void)viewDidLayoutSubviews {
  17. [super viewDidLayoutSubviews];
  18. UnitySceneManager *ad = [UnitySceneManager sharedInstance];
  19. ad.unityFramework.appController.rootView.frame = self.view.bounds;
  20. }
  21.  
  22. - (void)viewWillAppear:(BOOL)animated {
  23. [super viewWillAppear:animated];
  24. UnitySceneManager *ad = [UnitySceneManager sharedInstance];
  25. [ad.unityFramework pause:NO];
  26. }
  27.  
  28. - (void)viewWillDisappear:(BOOL)animated {
  29. [super viewWillDisappear:animated];
  30. UnitySceneManager *ad = [UnitySceneManager sharedInstance];
  31. [ad.unityFramework pause:YES];
  32. }
  33.  
  34.  
  35. #pragma mark - Private Method
  36. - (void)setupUI {
  37. self.view.backgroundColor = [UIColor whiteColor];
  38. UnitySceneManager *ad = [UnitySceneManager sharedInstance];
  39. UIView *rootView = ad.unityFramework.appController.rootView;
  40. rootView.frame = [UIScreen mainScreen].bounds;
  41. [self.view addSubview:rootView];
  42. [self.view sendSubviewToBack:rootView];
  43. }

 

 

 

 

原文链接:https://www.cnblogs.com/zhou--fei/p/18057212

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号