ios - 将 Unity 嵌入 iOS 自己的 ViewController 中

标签 ios unity-game-engine

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

Unity官方仅支持全屏渲染。尽管如此,我正在寻找绕过该限制的方法。
在以前版本的Unity中我成功使用了swift-unity进行整合。在这种方法中,可以轻松获取 Unity 渲染到的 View (使用 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。似乎只能添加 subview 。

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

最佳答案

我根据 this approach from the unity forum 找到了问题的解决方案。使用这种方法,我可以将 UnityViewController 用作我自己的 TabBarController 中的子级。

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

<小时/>

解决方案

1。创建UnityEmbeddedSwift.swift

official example App provided by Unity真是一团糟。我最终使用了 linked forum post 中的 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 非常重要。
选项卡切换不太好,但如果缺少它,Unity 将在加载过程中暂停(或卡住?)。 时间似乎取决于 Unity 项目的加载时间。对于小型项目来说可能会更快,而对于较大的项目则需要更多时间。

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

修改 Storyboard以从 RootTabBarController 开始。

Storyboard: Unity inside TabBarController

关于ios - 将 Unity 嵌入 iOS 自己的 ViewController 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59428747/

相关文章:

ios - 多部分请求 - 使用 UIImage 进行编码的结构

iphone - NSOperationQueue 的独特工作

ios - 在 iOS 应用程序上运行泄漏会引发 "Cannot allocate mmap"错误

c# - Unity C# 四元数 : Swap y with z axis

unity-game-engine - Unity 完整编辑器变成粉红色 - Unity 编辑器错误

c# - Unity 使用触摸旋转相机 - 移动

ios - 让 UIImageView 充当 Facebook 登录按钮

ios - 从 Xamarin 表单导航到 Xamarin Native

c# - Unity 使用脚本 C# 更改文本

c# - 跟踪游戏对象的子级数量