[英]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.但是说明不清楚,并且不存在带有提示名称的方法。
UnityEmbeddedSwift.swift
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
}
}
AppDelegate.swift
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
}
RootTabBarController.swift
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()
}
}
}
Main.storyboard
Main.storyboard
Modify the storyboard to start with the RootTabBarController
.修改故事板以从
RootTabBarController
开始。
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:
您需要做的就是:
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.