简体   繁体   English

在自己的 ViewController 中嵌入 Unity

[英]Embed Unity inside iOS in own ViewController

Using Unity 2019.3.0f3 and its Unity as a library feature I'm trying to embed a Unity project inside my iOS application.使用 Unity 2019.3.0f3 及其Unity as a library功能,我尝试将 Unity 项目嵌入到我的 iOS 应用程序中。

Unity officially only supports full screen rendering. Unity 官方只支持全屏渲染。 Nevertheless I'm looking for a way around that restriction.尽管如此,我正在寻找解决该限制的方法。
In previous versions of Unity i successfully used swift-unity to do the integration.在以前的 Unity 版本中,我成功地使用了swift-unity来进行集成。 Within this approach it is easy to just get the View where Unity is rendering to (using UnityGetGLView() ).在这种方法中,很容易获得 Unity 渲染的视图(使用UnityGetGLView() )。 I had no problems regarding stability or resources.我在稳定性或资源方面没有问题。

Using the new library approach, every time I try to access the UnityView , unity forces it's complete Window as keyWindow .使用新的库方法,每次我尝试访问UnityView ,unity 都会强制它完成Window作为keyWindow

I tried accessing the UnityView in my own ViewController using我尝试使用访问我自己的 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)
}

But that immediately activates the complete unity-window and hides my parenting UITabBarController .但这会立即激活完整的统一窗口并隐藏我的育儿UITabBarController

Trying to make the UnityFramework.getInstance()?.appController()?.rootViewController a child of my UITabBarController failed with the same result.试图使UnityFramework.getInstance()?.appController()?.rootViewController成为我的UITabBarController的孩子失败,结果相同。

Furthermore it is not possible to add a child ViewController .此外,无法添加子ViewController Only adding subviews seems possible.似乎只能添加子视图。

Does anybody know where that window-behaviour is located or how i can access the UnityView (or the RootViewController) and use it freely?有谁知道该窗口行为位于何处,或者我如何访问UnityView (或 RootViewController)并自由使用它?

I found a solution to the problem based on this approach from the unity forum .从统一论坛找到了基于这种方法的问题的解决方案。 Using this approach I'm able to use the UnityViewController as a child in my own TabBarController .使用这种方法,我可以将UnityViewController用作我自己的TabBarController

The approach is working for Unity 2019.3.0f3, but I'm not sure if it will work in future versions.该方法适用于 Unity 2019.3.0f3,但我不确定它是否适用于未来版本。 It feels like Unity tries to actively prevent such use.感觉 Unity 试图积极阻止这种使用。 Then again I found hints in comments in the library-code that would suggest that a modified ViewController-Hierarchy was at least contemplated eg in UnityAppController+ViewHandling.h .然后我再次在库代码的注释中找到提示,表明至少考虑了修改后的 ViewController-Hierarchy,例如在UnityAppController+ViewHandling.h But the instructions are unclear and methods with the hinted names don't exist.但是说明不清楚,并且不存在带有提示名称的方法。


Solution解决方案

1. Create UnityEmbeddedSwift.swift 1. 创建UnityEmbeddedSwift.swift

The official example App provided by Unity is a real mess. Unity 提供官方示例 App真是一团糟。 I ended up using the UnityEmbeddedSwift.swift from the linked forum post with additions for pausing.我最终使用了链接论坛帖子中UnityEmbeddedSwift.swift并添加了暂停功能。 This class encapsulates all Unity-related functionality in one clean class.此类将所有与 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. Modify AppDelegate.swift 2.修改AppDelegate.swift

Sets window and launch options needed by UnityEmbeddedSwift设置UnityEmbeddedSwift所需的窗口和启动选项

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

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

        return true
    }

3. Create RootTabBarController.swift 3. 创建RootTabBarController.swift

This class sets up the hierarchy.这个类设置层次结构。
It is important to use the UnityRootViewController right after calling UnityEmbeddedSwift.showUnity() .在调用UnityEmbeddedSwift.showUnity()后立即使用UnityRootViewController很重要。
The Tab-Switching is not nice, but if it is missing Unity will pause (or freeze?) during loading. Tab-Switching 不是很好,但如果缺少它,Unity 将在加载过程中暂停(或冻结?)。 The timing seems to depend on the Unity-Projects loading time.时间似乎取决于 Unity-Projects 加载时间。 It can be faster for small projects and needs more time for larger 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. Modify Main.storyboard 4.修改Main.storyboard

Modify the storyboard to start with the RootTabBarController .修改故事板以从RootTabBarController开始。

故事板:TabBarController 中的 Unity

For anyone who is still interested in preventing the freezing, I am building on top of aalmigthy's answer :对于仍然对防止冻结感兴趣的任何人,我都建立在aalmigthy 的回答之上:

You do not need to add a TabBar controller and switch between the tabs.不需要在标签之间添加的TabBar控制器和开关。 All you need to do is:您需要做的就是:

  • Add the Unity view as a subview将 Unity 视图添加为子视图
  • Send the subview to back将子视图发送回

Here's the modified ViewController class (no need for a tab bar):这是修改后的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.

相关问题 您可以在 android/ios 应用程序中嵌入统一应用程序吗? - Can you embed a unity application inside an android/ios app? iOS ViewController调用容器内的子ViewController - ios viewcontroller to call child viewcontroller inside container 如何在ViewController Xcode中嵌入Unity项目(目标c / swift) - How Embed Unity Project in ViewController Xcode (objective c/swift) 在框架iOS中嵌入框架 - Embed frameworks inside a framework iOS iOS:在UITabbarController内部显示登录viewcontroller或“另一个” viewcontroller吗? - iOS: Show login viewcontroller or “another” viewcontroller inside of UITabbarController? 导航到标签栏问题内的ViewController-iOS - navigating to viewcontroller inside tabbar issue - ios iOS提供ViewController并在editActionsForRowAt内部传递数据 - IOS present Viewcontroller and pass data inside editActionsForRowAt 在iOS Playground应用程序的viewcontroller内部将视图居中 - Center a view inside viewcontroller in iOS Playground app iOS ZXingWidget - 在自己的 ViewController.view 中使用 ZXingWidgetViewController 的视图作为子视图 - iOS ZXingWidget - use view of ZXingWidgetViewController in own ViewController.view as subview 如何将unity ios项目与另一个ios项目嵌入 - How to embed unity ios project with another ios project
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM