首页 > 其他分享 >iOS使用Unity容器动态加载3D模型

iOS使用Unity容器动态加载3D模型

时间:2024-03-06 18:13:14浏览次数:33  
标签:Log unityFramework self iOS Unity Debug void 3D

项目背景 我们的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 脚本实现如下:

using UnityEditor;
using System.IO;


/// <summary>
///
/// </summary>

public class AssetBundleEditor 
{
    //1.编译阶段插件声明
    [MenuItem("Assets/Build AssetBundles")]
    static void BuildAssetBundles() {
        string dir = "AssetBundles";
        if (!Directory.Exists(dir)) {
            //2.在工程根目录下创建dir目录
            Directory.CreateDirectory(dir);
        }
        //3.构建AssetBundle资源,AB资源包是一个压缩文件,可以把它看成是一个压缩的文件夹,里面
        //可能包含多个文件,预制件,材质,贴图,声音。
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.iOS);
    }
}

设置需要打包的资源

可以在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进行打开 插件工具地址:https://github.com/Unity-Technologies/AssetBundles-Browser   0 2.打包时,可以选择将打出的ab包内置到项目中,勾选Copy StreamingAssets ,让打出的内容放置在StreamingAssets目录下,这样可以将ab资源内置到Unity项目中。   3.通过上面的操作会完成资源打包,然后将打包的产物压缩上传到后台。 0       AssetsBundle资源包的使用 APP启动时,下载AssetBundle压缩包, 然后解压放置在沙盒Documents/AssetsBundle目录下,当点击APP中的按钮进入到Unity容器页面时,通过包名加载对应的ab包进行Unity页面展示。
   /// <summary>
    ///读取原生沙盒Documents/AssetsBundle目录下的文件,Documents/AssetsBundle下的文件通过Native原生下载的资源
    /// </summary>
    /// <param name="abName">Documents/AssetsBundle下的ab文件</param>
    /// <returns>读取到的字符串</returns>
    public static AssetBundle GetNativeAssetFromDocumentsOnProDownLoad(string abName)
    {
        string localPath = "";
        if (Application.platform == RuntimePlatform.Android)
        {
            localPath = "jar:file://" + Application.persistentDataPath + "/AssetsBundle/" + abName;
        }
        else
        {
            localPath = "file://" + Application.persistentDataPath + "/AssetsBundle/" + abName;
        }
        UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(localPath);
        var operation = request.SendWebRequest();
        while (!operation.isDone)
        { }
        if (request.result == UnityWebRequest.Result.ConnectionError)
        {
            Debug.Log(request.error);
            return null;
        }
        else
        {
            AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
            return assetBundle;
        }
        //UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(localPath);
        //yield return request.Send();
        //AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
        //return assetBundle;

    }

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

   public void TestUnLoadGameObject()
    {
        UnLoadGameObjectWithTag("NFT");
    }

    public void UnLoadGameObjectWithTag(string tagName)
    {
        GameObject go = GameObject.FindWithTag(tagName);
        if (go) {
            Destroy(go, 0.5f);
        } else
        {
            Debug.Log(go);
        }
        
    }

    public void UnLoadAllGameObjectWithTag(string tagName)
    {
        GameObject[] gos = GameObject.FindGameObjectsWithTag(tagName);
        foreach (GameObject go in gos) {
            Destroy(go, 0.5f);
        }

    }

 

模型的相关设置 手势支持 对于加载完成后的模型需要添加手势支持,允许用户旋转,缩放查看,不能说只能静止观看。这里添加手势控制脚本用于支持手势功能。 0 模型实现成功后,把实例对象设置到GestureController组件的Target上面,实现模型的手势支持。   加载Unity内置ab资源包的脚本实现:
   public void TestLoadStreamingAssetBundle() {
        LoadStreamingAssetBundleWithABName("cube.ab", "Cube", "NFT");
    }

    public void LoadStreamingAssetBundleWithABName(string abName, string gameObjectName, string tagName)
    {

        AssetBundle ab = FileUtility.GetNativeAssetFromStreamingAssets(abName);
        GameObject profab = ab.LoadAsset<GameObject>(gameObjectName);
        profab.tag = tagName;
        Instantiate(profab);


        GestureController gc = GameObject.FindObjectOfType<GestureController>();
        gc.target = profab.transform;

        ab.Unload(false);
    }

 Unity场景切换的脚本实现:

    //接收原生事件:切换场景
    public void SwitchScene(string parmas)
    {
        Debug.Log(parmas);
        Param param = new Param();
        Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
        Debug.Log(res.name);

        Debug.Log("------------");
        for (int i = 0; i < SceneManager.sceneCount; i++) {
            Scene scene = SceneManager.GetSceneAt(i);
            Debug.Log(scene.name);
        }

        SceneManager.LoadScene(res.name, LoadSceneMode.Single);

        Debug.Log("------------");
        for (int i = 0; i < SceneManager.sceneCount; i++)
        {
            Scene scene = SceneManager.GetSceneAt(i);
            Debug.Log(scene.name);
        }
    }

 

Unity导出iOS项目 构建UnityFramework动态库   0   0 此时将得到一个iOS 工程。   原生与Unity通信 创建原生与Unity通信接口,并放置到Unity项目中。 0   NativeCallProxy.h文件创建通信协议
#import <Foundation/Foundation.h>

@protocol NativeCallsProtocol

@required

/// Unity调用原生
/// - Parameter params: {"FeatureName":"下载资源", "params": "参数"}
- (void)callNative:(NSString *)params;
@end

__attribute__ ((visibility("default")))


@interface NativeCallProxy : NSObject
// call it any time after UnityFrameworkLoad to set object implementing NativeCallsProtocol methods
+ (void)registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi;
@end

 NativeCallProxy.mm文件实现如下:

#import "NativeCallProxy.h"

@implementation NativeCallProxy
id<NativeCallsProtocol> api = NULL;
+ (void)registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi
{
    api = aApi;
}

@end


extern "C" {
void callNative(const char * value);
}


void callNative(const char * value){
    return [api callNative:[NSString stringWithUTF8String:value]];
}

 原生的Delegate的实现

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

 

 Unity调用原生
   //重要声明,声明在iOS原生中存在下面的方法,然后C#中可以直接进行调用
    [DllImport("__Internal")]
    static extern void callNative(string value);


    public void changeLabel(string textString) {
        tmpText.text = textString;
    }

    public void btnClick() {
        Debug.Log(tmpInput.text);
        callNative(tmpInput.text);
    }
然后根据工程设置,生成UnityFramework。创建UnityFramework的详细流程可以参考文章:https://www.cnblogs.com/zhou--fei/p/17622488.html。 然后其他需要拥有Unity能力的APP就可以集成此动态库,展示Unity视图。   原生与Unity通信交互 首先定义一套接口,用于规定原生到Unity发送消息时,参数对应的意义。 0   然后在场景中添加DispatchGO游戏对象,在此对象上面添加DispatchGO组件,DispatchGO组件用于接收原生发送过来的消息,并进行逻辑处理。 0  
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;


public class Param {
    public string packageName { get; set; }
    public string name { get; set; }
    public string tag { get; set; }
    public string type { get; set; }
    public string isAll { get; set; }
}

public class DispatchGO : MonoBehaviour
{

    //接收原生事件
    public void DispatchEvent(string parmas) {
        Debug.Log(parmas);
        //事件分发

        ChangeLabel cl = GameObject.FindObjectOfType<ChangeLabel>();
        cl.changeLabel(parmas);
    }

    //接收原生事件:加载模型
    public void LoadModel(string parmas)
    {
        Debug.Log(parmas);
        Param param = new Param();
        Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
        Debug.Log(res.packageName);
        Debug.Log(res.name);
        Debug.Log(res.tag);
        Debug.Log(res.type);

        if (res.type == "0")
        {
            LoadAssetUtility laUnity = GameObject.FindObjectOfType<LoadAssetUtility>();
            laUnity.LoadStreamingAssetBundleWithABName(res.packageName, res.name, res.tag);
        }
        else {
            LoadAssetUtility laUnity = GameObject.FindObjectOfType<LoadAssetUtility>();
            laUnity.LoadNativeAssetBundleWithABName(res.packageName, res.name, res.tag);
        }
    }

    //接收原生事件:卸载模型
    public void UnLoadModel(string parmas)
    {
        Debug.Log(parmas);
        Param param = new Param();
        Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;

        UnLoadAssetUtility unLAUnity = GameObject.FindObjectOfType<UnLoadAssetUtility>();
        if (res.isAll == "1")
        {
            unLAUnity.UnLoadAllGameObjectWithTag(res.tag);
        }
        else {
            unLAUnity.UnLoadGameObjectWithTag(res.tag);
        }
    }

    //接收原生事件:切换场景
    public void SwitchScene(string parmas)
    {
        Debug.Log(parmas);
        Param param = new Param();
        Param res = JsonDataContractJsonSerializer.JsonToObject(parmas, param) as Param;
        Debug.Log(res.name);

        Debug.Log("------------");
        for (int i = 0; i < SceneManager.sceneCount; i++) {
            Scene scene = SceneManager.GetSceneAt(i);
            Debug.Log(scene.name);
        }

        SceneManager.LoadScene(res.name, LoadSceneMode.Single);

        Debug.Log("------------");
        for (int i = 0; i < SceneManager.sceneCount; i++)
        {
            Scene scene = SceneManager.GetSceneAt(i);
            Debug.Log(scene.name);
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

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

        case 103:
        {
            NSDictionary *params = @{
                @"tag":@"NFT",
                @"isAll":@"1"
            };
            [ad.unityFramework sendMessageToGOWithName:"DispatchGO" functionName:"UnLoadModel" message:[self serialJsonToStr:params]];
        }
            break;
        case 104:
        {
            NSDictionary *params = @{
                @"name":@"DemoScene"
            };
            [ad.unityFramework sendMessageToGOWithName:"DispatchGO" functionName:"SwitchScene" message:[self serialJsonToStr:params]];
        }
            break;

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

    //重要声明,声明在iOS原生中存在下面的方法,然后C#中可以直接进行调用
    [DllImport("__Internal")]
    static extern void callNative(string value);

    public void btnClick() {
        Debug.Log(tmpInput.text);
        callNative(tmpInput.text);
    }

 

原生端创建Unity容器

在APP启动时,对UnityFramework进行初始化。
@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [UnitySceneManager sharedInstance].launchOptions = launchOptions;
    [[UnitySceneManager sharedInstance] Init];
    return YES;
}

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

extern int argcApp;
extern char ** argvApp;

@interface UnitySceneManager()<UnityFrameworkListener, NativeCallsProtocol>

@end

@implementation UnitySceneManager
#pragma mark - Life Cycle
+ (instancetype)sharedInstance {
    static UnitySceneManager *shareObj;
    static dispatch_once_t onceKey;
    dispatch_once(&onceKey, ^{
        shareObj = [[super allocWithZone:nil] init];
    });
    return shareObj;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (instancetype)copyWithZone:(struct _NSZone *)zone {
    return self;
}

#pragma mark - Private Method
- (void)Init {
    [self initUnityFramework];
    [NativeCallProxy registerAPIforNativeCalls:self];
}

- (void)unloadUnityInternal {
    if (self.unityFramework) {
        [self.unityFramework unregisterFrameworkListener:self];
    }
    self.unityFramework = nil;
}

- (BOOL)unityIsInitialized {
    return (self.unityFramework && self.unityFramework.appController);
}
// MARK: overwrite

#pragma mark - Public Method
- (void)initUnityFramework {
    UnityFramework *unityFramework = [self getUnityFramework];
    self.unityFramework = unityFramework;
    [unityFramework setDataBundleId:"com.zhfei.framework"];
    [unityFramework registerFrameworkListener:self];
    [unityFramework runEmbeddedWithArgc:argcApp argv:argvApp appLaunchOpts:self.launchOptions];
}

- (UnityFramework *)getUnityFramework {
    NSString* bundlePath = nil;
    bundlePath = [[NSBundle mainBundle] bundlePath];
    bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];

    NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
    if ([bundle isLoaded] == false) [bundle load];

    UnityFramework* ufw = [bundle.principalClass getInstance];
    if (![ufw appController])
    {
        // unity is not initialized
        [ufw setExecuteHeader: &_mh_execute_header];
    }
    return ufw;
}

#pragma mark - Event

#pragma mark - Delegate
#pragma mark - UnityFrameworkListener
- (void)unityDidUnload:(NSNotification*)notification {
    
}

- (void)unityDidQuit:(NSNotification*)notification {
    
}

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

#pragma mark - Getter, Setter

#pragma mark - NSCopying

#pragma mark - NSObject

#pragma mark - AppDelegate生命周期绑定
- (void)applicationWillResignActive {
    [[self.unityFramework appController] applicationWillResignActive: [UIApplication sharedApplication]];
}

- (void)applicationDidEnterBackground {
    [[self.unityFramework appController] applicationDidEnterBackground: [UIApplication sharedApplication]];
}

- (void)applicationWillEnterForeground {
    [[self.unityFramework appController] applicationWillEnterForeground: [UIApplication sharedApplication]];
}

- (void)applicationDidBecomeActive {
    [[self.unityFramework appController] applicationDidBecomeActive: [UIApplication sharedApplication]];
}

- (void)applicationWillTerminate {
    [[self.unityFramework appController] applicationWillTerminate: [UIApplication sharedApplication]];
}


@end
Unity容器的原生实现,其实也是在一个普通的ViewController里面包含了Unity视图的View。
#import "UnityContainerViewController.h"
#import "UnitySceneManager.h"

@interface UnityContainerViewController ()

@end

@implementation UnityContainerViewController
#pragma mark - Life Cycle
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self setupUI];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    UnitySceneManager *ad = [UnitySceneManager sharedInstance];
    ad.unityFramework.appController.rootView.frame = self.view.bounds;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    UnitySceneManager *ad = [UnitySceneManager sharedInstance];
    [ad.unityFramework pause:NO];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    UnitySceneManager *ad = [UnitySceneManager sharedInstance];
    [ad.unityFramework pause:YES];
}


#pragma mark - Private Method
- (void)setupUI {
    self.view.backgroundColor = [UIColor whiteColor];
    UnitySceneManager *ad = [UnitySceneManager sharedInstance];
    
    UIView *rootView = ad.unityFramework.appController.rootView;
    rootView.frame = [UIScreen mainScreen].bounds;
    [self.view addSubview:rootView];
    [self.view sendSubviewToBack:rootView];
}

 

 

 

 

标签:Log,unityFramework,self,iOS,Unity,Debug,void,3D
From: https://www.cnblogs.com/zhou--fei/p/18057212

相关文章

  • 【教程】uni-app iOS打包解决profile文件与私钥证书不匹配问题
    摘要当在uni-app中进行iOS打包时,有时会遇到profile文件与私钥证书不匹配的问题。本文将介绍如何解决这一问题,以及相关的技术细节和操作步骤。引言在uni-app开发过程中,iOS打包是一个常见的操作。然而,有时会出现profile文件与私钥证书不匹配的错误提示,导致打包失败。为了解决这一......
  • ios pod导入 提示头文件找不到,M1、2芯片问题
    项目pod导入Masonry成功,但是头文件无法引入,提示找不到或者有一下报错解决方案1、将设置中的UserScriptSandboxing修改为No2、找不到头文件修改HeaderSearchPaths配置"${PODS_CONFIGURATION_BUILD_DIR}/Masonry""${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.fr......
  • American Aptio主板BIOS看不到优盘设备的一种解决办法
    启动项列表里只有硬盘和网络设备,看不到优盘设备尝试后找到的一种解决办法:Chipset->PCH-IOConfiguration->USBConfiguration:EHCI1和EHCI2两个通道关闭其中一个,即一个设置为Enable,另一个Disable。同时关闭或开启都会导致看不到优盘设备。设置完成后重新启动进入BIOS,就能......
  • 基于unity和c#的障碍跑酷游戏的二次开发
    一、设计背景近年来,虚拟现实技术取得了突飞猛进的发展,为游戏行业带来了新的机遇。通过将跑酷游戏与虚拟现实技术相结合,可以为玩家提供更加真实、沉浸式的游戏体验,让玩家仿佛置身于现实世界中的跑酷场景中。现代游戏越来越注重玩家之间的互动和竞技。跑酷游戏可以设置多人在线模......
  • Unity3D 常用得内置函数(Cg与GLSL)详解
    Cg和GLSL是Unity3D中常用的着色器语言,通过使用这两种语言,开发者可以实现各种精美的视觉效果。本文将详细介绍Unity3D中常用的一些内置函数,并给出相应的技术详解和代码实现。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大......
  • DELL EMC unity demo
    1.unity介绍Unity属于DellEMC推出的中端存储阵列,其既支持文件存储,也支持块存储,还拥有丰富的数据服务功能,旨在满足客户对灵活性、实惠性和简洁性的需求。DellEMCUnity产品线包括Hybrid(SSD+机械硬盘)类型的300/400/500/600型号,以及全闪存的300F/400F/500F/600F型号,以及......
  • Pokemon Go自動走路 iOS/Android 在家玩寶可夢不用出門 不用移動
    有時您可能想知道如何在不動的情況下玩PokemonGo。好消息是,我們將為您介紹PokemonGo自動步行,以偽造GPS位置。PokemonGo欺騙器將作為位置變換器引入。閱讀更多有關如何在PokemonGo中無需步行即可移動的方法。第1部分.是否可以不動地玩PokemonGO 在家玩寶可夢......
  • wpf 3D,Viewort3D,
    <Windowx:Class="WpfApp5.Views.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.micro......
  • Unity引擎关于APP后台下载支持的实现问题
    1)Unity引擎关于APP后台下载支持的实现问题2)Prefab对DLL中脚本的引用丢失3)UnityDOTS资源加载问题4)UnitySendMessage和_MultiplyMatrixArrayWithBase4x4_NEON调用导致崩溃这是第376篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更......
  • 寶可夢Pokemon Go虛擬搖桿iOS/Android免費下載
    PokemonGo仍然是迄今為止最成功的手機遊戲之一。這麼多年過去了,這款遊戲讓玩家以不同的方式參與其中。也許這款遊戲最大的吸引力在於其基於位置的設計,要求你去散步並將相機對準寶可夢。這是一種有趣的方法,但在某些情況下,它也可能是遊戲的主要限制。如果你不能出去,你就不能玩......