繁体   English   中英

在自己的 ViewController 中嵌入 Unity

[英]Embed Unity inside iOS in own ViewController

使用 Unity 2019.3.0f3 及其Unity as a library功能,我尝试将 Unity 项目嵌入到我的 iOS 应用程序中。

Unity 官方只支持全屏渲染。 尽管如此,我正在寻找解决该限制的方法。
在以前的 Unity 版本中,我成功地使用了swift-unity来进行集成。 在这种方法中,很容易获得 Unity 渲染的视图(使用UnityGetGLView() )。 我在稳定性或资源方面没有问题。

使用新的库方法,每次我尝试访问UnityView ,unity 都会强制它完成Window作为keyWindow

我尝试使用访问我自己的 ViewController 中的 UnityView

if let unityView = UnityFramework.getInstance()?.appController()?.rootViewController.view {
    // insert subview at index 0 ensures unity view is behind current UI view
    view?.insertSubview(unityView, at: 0)
}

但这会立即激活完整的统一窗口并隐藏我的育儿UITabBarController

试图使UnityFramework.getInstance()?.appController()?.rootViewController成为我的UITabBarController的孩子失败,结果相同。

此外,无法添加子ViewController 似乎只能添加子视图。

有谁知道该窗口行为位于何处,或者我如何访问UnityView (或 RootViewController)并自由使用它?

从统一论坛找到了基于这种方法的问题的解决方案。 使用这种方法,我可以将UnityViewController用作我自己的TabBarController

该方法适用于 Unity 2019.3.0f3,但我不确定它是否适用于未来版本。 感觉 Unity 试图积极阻止这种使用。 然后我再次在库代码的注释中找到提示,表明至少考虑了修改后的 ViewController-Hierarchy,例如在UnityAppController+ViewHandling.h 但是说明不清楚,并且不存在带有提示名称的方法。


解决方案

1. 创建UnityEmbeddedSwift.swift

Unity 提供官方示例 App真是一团糟。 我最终使用了链接论坛帖子中UnityEmbeddedSwift.swift并添加了暂停功能。 此类将所有与 Unity 相关的功能封装在一个干净的类中。

//
//  UnityEmbeddedSwift.swift
//  Native
//
//  Created by NSWell on 2019/12/19.
//  Copyright © 2019 WEACW. All rights reserved.
//

//
//  Created by Simon Tysland on 19/08/2019.
//  Copyright © 2019 Simon Tysland. All rights reserved.
//

import Foundation
import UnityFramework

class UnityEmbeddedSwift: UIResponder, UIApplicationDelegate, UnityFrameworkListener {

    private struct UnityMessage {
        let objectName : String?
        let methodName : String?
        let messageBody : String?
    }

    private static var instance : UnityEmbeddedSwift!
    private var ufw : UnityFramework!
    private static var hostMainWindow : UIWindow! // Window to return to when exiting Unity window
    private static var launchOpts : [UIApplication.LaunchOptionsKey: Any]?

    private static var cachedMessages = [UnityMessage]()

    // MARK: - Static functions (that can be called from other scripts)

    static func getUnityRootViewController() -> UIViewController! {
        return instance.ufw.appController()?.rootViewController
    }

    static func getUnityView() -> UIView! {
        return instance.ufw.appController()?.rootViewController?.view
    }

    static func setHostMainWindow(_ hostMainWindow : UIWindow?) {
        UnityEmbeddedSwift.hostMainWindow = hostMainWindow
        let value = UIInterfaceOrientation.landscapeLeft.rawValue
        UIDevice.current.setValue(value, forKey: "orientation")
    }

    static func setLaunchinOptions(_ launchingOptions :  [UIApplication.LaunchOptionsKey: Any]?) {
        UnityEmbeddedSwift.launchOpts = launchingOptions
    }

    static func showUnity() {
        if(UnityEmbeddedSwift.instance == nil || UnityEmbeddedSwift.instance.unityIsInitialized() == false) {
            UnityEmbeddedSwift().initUnityWindow()
        }
        else {
            UnityEmbeddedSwift.instance.showUnityWindow()
        }
    }

    static func hideUnity() {
        UnityEmbeddedSwift.instance?.hideUnityWindow()
    }

    static func pauseUnity() {
        UnityEmbeddedSwift.instance?.pauseUnityWindow()
    }

    static func unpauseUnity() {
        UnityEmbeddedSwift.instance?.unpauseUnityWindow()
    }

    static func unloadUnity() {
        UnityEmbeddedSwift.instance?.unloadUnityWindow()
    }

    static func sendUnityMessage(_ objectName : String, methodName : String, message : String) {
        let msg : UnityMessage = UnityMessage(objectName: objectName, methodName: methodName, messageBody: message)

        // Send the message right away if Unity is initialized, else cache it
        if(UnityEmbeddedSwift.instance != nil && UnityEmbeddedSwift.instance.unityIsInitialized()) {
            UnityEmbeddedSwift.instance.ufw.sendMessageToGO(withName: msg.objectName, functionName: msg.methodName, message: msg.messageBody)
        }
        else {
            UnityEmbeddedSwift.cachedMessages.append(msg)
        }
    }

    // MARK - Callback from UnityFrameworkListener

    func unityDidUnload(_ notification: Notification!) {
        ufw.unregisterFrameworkListener(self)
        ufw = nil
        UnityEmbeddedSwift.hostMainWindow?.makeKeyAndVisible()
    }

    // MARK: - Private functions (called within the class)

    private func unityIsInitialized() -> Bool {
        return ufw != nil && (ufw.appController() != nil)
    }

    private func initUnityWindow() {
        if unityIsInitialized() {
            showUnityWindow()
            return
        }

        ufw = UnityFrameworkLoad()!
        ufw.setDataBundleId("com.unity3d.framework")
        ufw.register(self)
//        NSClassFromString("FrameworkLibAPI")?.registerAPIforNativeCalls(self)

        ufw.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: UnityEmbeddedSwift.launchOpts)

        sendUnityMessageToGameObject()

        UnityEmbeddedSwift.instance = self
    }

    private func showUnityWindow() {
        if unityIsInitialized() {
            ufw.showUnityWindow()
            sendUnityMessageToGameObject()
        }
    }

    private func hideUnityWindow() {
        if(UnityEmbeddedSwift.hostMainWindow == nil) {
            print("WARNING: hostMainWindow is nil! Cannot switch from Unity window to previous window")
        }
        else {
            UnityEmbeddedSwift.hostMainWindow?.makeKeyAndVisible()
        }
    }

    private func pauseUnityWindow() {
        ufw.pause(true)
    }

    private func unpauseUnityWindow() {
        ufw.pause(false)
    }

    private func unloadUnityWindow() {
        if unityIsInitialized() {
            UnityEmbeddedSwift.cachedMessages.removeAll()
            ufw.unloadApplication()
        }
    }

    private func sendUnityMessageToGameObject() {
        if (UnityEmbeddedSwift.cachedMessages.count >= 0 && unityIsInitialized())
        {
            for msg in UnityEmbeddedSwift.cachedMessages {
                ufw.sendMessageToGO(withName: msg.objectName, functionName: msg.methodName, message: msg.messageBody)
            }

            UnityEmbeddedSwift.cachedMessages.removeAll()
        }
    }

    private func UnityFrameworkLoad() -> UnityFramework? {
        let bundlePath: String = Bundle.main.bundlePath + "/Frameworks/UnityFramework.framework"

        let bundle = Bundle(path: bundlePath )
        if bundle?.isLoaded == false {
            bundle?.load()
        }

        let ufw = bundle?.principalClass?.getInstance()
        if ufw?.appController() == nil {
            // unity is not initialized
            //            ufw?.executeHeader = &mh_execute_header

            let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
            machineHeader.pointee = _mh_execute_header

            ufw!.setExecuteHeader(machineHeader)
        }
        return ufw
    }
}

2.修改AppDelegate.swift

设置UnityEmbeddedSwift所需的窗口和启动选项

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        UnityEmbeddedSwift.setHostMainWindow(window)
        UnityEmbeddedSwift.setLaunchinOptions(launchOptions)

        return true
    }

3. 创建RootTabBarController.swift

这个类设置层次结构。
在调用UnityEmbeddedSwift.showUnity()后立即使用UnityRootViewController很重要。
Tab-Switching 不是很好,但如果缺少它,Unity 将在加载过程中暂停(或冻结?)。 时间似乎取决于 Unity-Projects 加载时间。 对于小型项目,它可能会更快,而对于大型项目则需要更多时间。

import UIKit

class RootTabBarController: UITabBarController, UITabBarControllerDelegate {

    var unityNC: UINavigationController?
    var nativeNC: UINavigationController?

    override func viewDidLoad() {
        super.viewDidLoad()

        delegate = self

        // start unity and immediatly set as rootViewController
        // this loophole makes it possible to run unity in the same window
        UnityEmbeddedSwift.showUnity()
        let unityViewController = UnityEmbeddedSwift.getUnityRootViewController()!
        unityViewController.navigationItem.title = "Unity"

        unityNC = UINavigationController.init(rootViewController: unityViewController)
        unityNC?.tabBarItem.title = "Unity"

        let nativeViewController = UIViewController.init()
        nativeViewController.view.backgroundColor = UIColor.darkGray
        nativeViewController.navigationItem.title = "Native"

        nativeNC = UINavigationController.init(rootViewController: nativeViewController)
        nativeNC?.tabBarItem.title = "Native"

        viewControllers = [unityNC!, nativeNC!]

        // select other tab and reselect first tab to unfreeze unity-loading
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            self.selectedIndex = 1

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: {
                self.selectedIndex = 0
            })
        })
    }

    // MARK: - UITabBarControllerDelegate

    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        // pause unity if unity-tab is not selected
        if viewController != unityNC {
            UnityEmbeddedSwift.pauseUnity()
        } else {
            UnityEmbeddedSwift.unpauseUnity()
        }
    }
}

4.修改Main.storyboard

修改故事板以从RootTabBarController开始。

故事板:TabBarController 中的 Unity

对于仍然对防止冻结感兴趣的任何人,我都建立在aalmigthy 的回答之上:

不需要在标签之间添加的TabBar控制器和开关。 您需要做的就是:

  • 将 Unity 视图添加为子视图
  • 将子视图发送回

这是修改后的ViewController类(不需要标签栏):

import UIKit

class HybridViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        UnityEmbeddedSwift.showUnity()
        
        let uView = UnityEmbeddedSwift.getUnityView()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            self.view.addSubview(uView!)
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
                self.view.sendSubviewToBack(uView!)
            })
        })
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM