ios使用unity容器动态加载3d模型-kb88凯时官网登录

来自:
时间:2024-03-07
阅读:

项目背景

我们的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脚本,这个层级关系是固定的,不然会报错。

ios使用unity容器动态加载3d模型

脚本实现如下:

using unityeditor;
using system.io;
/// 
///
/// 
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,并在里面设置依赖关系就可以了。

ios使用unity容器动态加载3d模型

使用插件工具进行打包

1.从github上下载源码,然后将代码库中的editor目录下的文件复制一份,放到工程target的assets/editor目录下。打开的方式是通过点击window->assetbundle browser进行打开

插件工具地址:

ios使用unity容器动态加载3d模型

2.打包时,可以选择将打出的ab包内置到项目中,勾选copy streamingassets ,让打出的内容放置在streamingassets目录下,这样可以将ab资源内置到unity项目中。

3.通过上面的操作会完成资源打包,然后将打包的产物压缩上传到后台。

ios使用unity容器动态加载3d模型

assetsbundle资源包的使用

app启动时,下载assetbundle压缩包, 然后解压放置在沙盒documents/assetsbundle目录下,当点击app中的按钮进入到unity容器页面时,通过包名加载对应的ab包进行unity页面展示。

   /// 
    ///读取原生沙盒documents/assetsbundle目录下的文件,documents/assetsbundle下的文件通过native原生下载的资源
    /// 
    /// documents/assetsbundle下的ab文件
    /// 读取到的字符串
    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);
        }
    }

模型的相关设置

手势支持

对于加载完成后的模型需要添加手势支持,允许用户旋转,缩放查看,不能说只能静止观看。这里添加手势控制脚本用于支持手势功能。

ios使用unity容器动态加载3d模型

模型实现成功后,把实例对象设置到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(gameobjectname);
        profab.tag = tagname;
        instantiate(profab);
        gesturecontroller gc = gameobject.findobjectoftype();
        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动态库

ios使用unity容器动态加载3d模型

ios使用unity容器动态加载3d模型

此时将得到一个ios 工程。

原生与unity通信

创建原生与unity通信接口,并放置到unity项目中。

ios使用unity容器动态加载3d模型

nativecallproxy.h文件创建通信协议

#import 
@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) aapi;
@end

 nativecallproxy.mm文件实现如下:

#import "nativecallproxy.h"
@implementation nativecallproxy
id api = null;
  (void)registerapifornativecalls:(id) 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的详细流程可以参考文章:。

然后其他需要拥有unity能力的app就可以集成此动态库,展示unity视图。

原生与unity通信交互

首先定义一套接口,用于规定原生到unity发送消息时,参数对应的意义。

ios使用unity容器动态加载3d模型

然后在场景中添加dispatchgo游戏对象,在此对象上面添加dispatchgo组件,dispatchgo组件用于接收原生发送过来的消息,并进行逻辑处理。

ios使用unity容器动态加载3d模型

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();
        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();
            launity.loadstreamingassetbundlewithabname(res.packagename, res.name, res.tag);
        }
        else {
            loadassetutility launity = gameobject.findobjectoftype();
            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();
        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

extern int argcapp;
extern char ** argvapp;
@interface unityscenemanager()
@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];
}
返回顶部
顶部
网站地图