一 游戏嵌入APP
1.1 开发工具版本
1.1.1 iOS 开发工具版本Xcode Version 14.2(14C18)
1.1.2 iOS项目开发语言为Swift5
1.1.3 Unity开发工具版本 2022.3.8f1c1
1.2 iOS项目和Unity项目结合
1.2.1 iOS新建一个Swift语言的项目
在新建的iOS工程里,新建文件夹Unity_Framework_Project,后续Unity导出的工程放到这个文件夹里
1.2.2 Unity项目里,切到iOS平台
Unity将程序包导出,放入iOS工程里的文件夹Unity_Framework_Project。
导出的文件
1.2.3 把游戏工程导入iOS工程
1.2.4 在Xcode做如下设置
1.2.5 加入Unity和iOS可以相互传值的共通代码
unity项目里新建两个文件,加入
iOSUtilUnity3dBridge.h
#import <Foundation/Foundation.h>
//调用录音权限需要引入的包
#import <AVFoundation/AVFoundation.h>
@interface iOSUtilUnity3dBridge : NSObject
#ifdef __cplusplus
extern "C"{
#endif
const char* UnitySendMessageToiOS(const char *str);
NSString* iOSGetMessage();
void iOSGetMessageChangeColor();
const char* getMicroRight();
#ifdef __cplusplus
}
#endif
@end
iOSUtilUnity3dBridge.m
#import <Foundation/Foundation.h>
#import "iOSUtilUnity3dBridge.h"
#ifdef __cplusplus
extern "C"
{
#endif
NSString *returnString;
//NSString转char*
char* _MakeStringCopy(const char* str) {
if(str == NULL){return NULL;}
char* res = (char*)malloc(strlen(str)+1);
strcpy(res, str);
return res;
}
const char* UnitySendMessageToiOS(const char *str)
{
NSString *string1 = [[NSString alloc] initWithUTF8String:str];
NSString *string2 = [NSString stringWithFormat:@"%@", string1];
returnString = string2;
return _MakeStringCopy([string2 UTF8String]);
}
NSString* iOSGetMessage()
{
return returnString;
}
/// 通知ios关闭遮挡游戏的画面关闭
void iOSGetMessageChangeColor()
{
NSLog(@"iOSGetMessageChangeColor");
//oc层发出通知,swift层会在需要的地方接收到通知做对应的逻辑处理。
[[NSNotificationCenter defaultCenter] postNotificationName:@"iOSGetMessageChangeColor" object:nil];
}
//获取录音权限状态
//约定返回游戏,1代表授权。0代表未授权
const char* getMicroRight()
{
AVAuthorizationStatus microPhoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
switch (microPhoneStatus) {
case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
{
// 被拒绝
NSString *string = [NSString stringWithFormat:@"%s", "0"];
return _MakeStringCopy([string UTF8String]);
}
break;
case AVAuthorizationStatusNotDetermined:
{
// 没弹窗
NSString *string = [NSString stringWithFormat:@"%s", "0"];
return _MakeStringCopy([string UTF8String]);
}
break;
case AVAuthorizationStatusAuthorized:
{
// 有授权
NSString *string = [NSString stringWithFormat:@"%s", "1"];
return _MakeStringCopy([string UTF8String]);
}
break;
default:
break;
}
NSString *string = [NSString stringWithFormat:@"%s", "0"];
return _MakeStringCopy([string UTF8String]);
}
#ifdef __cplusplus
}
#endif
Unity导出的iOS项目,如下图需要调整iOSUtilUnity3dBridge.h的位置
添加如下代码
#import "iOSUtilUnity3dBridge.h"
- (NSString *)myiOSGetMessage;
- (NSString *)myiOSGetMessage
{
return iOSGetMessage();
}
1.2.6 添加2个类,控制Unity游戏在APP新画面里的显示
UnityFrameworkWrapper.swift
import Foundation
import UnityFramework
protocol UnityFrameworkDeallocator {
func exitUnity()
}
class UnityFrameworkWrapper: NSObject, UnityFrameworkListener {
static let shared = UnityFrameworkWrapper()
// MARK: - Properties
var framework: UnityFramework
var delegate: UnityFrameworkDeallocator?
// MARK: - init
override init() {
self.framework = UnityFramework.getInstance()
super.init()
//下面的BundleId是unity的ios工程里framework的target里的BundleId,这个要和里面的包名一致。
framework.setDataBundleId("com.unity3d.framework")
framework.register(self)
//这句不加,程序运行不起来
framework.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: nil)
}
// MARK: - NativeCallsProtocol
func showHostMainWindow(_ color: String!) {
delegate?.exitUnity()
}
//unity的CSharp代码里调用Application.unload后会触发下面的方法
func unityDidUnload(_ notification: Notification!) {
print("Unity发送的信息,iOS接收的信息 = \(String(describing: framework.myiOSGetMessage()))")
//点击Unity的Application.unload方法会进入到这个方法
framework.unregisterFrameworkListener(self)
//调用UnityViewController里的exitUnity方法
delegate?.exitUnity()
}
func showUnityView() {
//iOS向Unity发送信息
framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "iOSToUnityStart")
//显示unity画面
framework.showUnityWindow()
}
}
UnityViewController.swift
import UIKit
class UnityViewController: UIViewController, UnityFrameworkDeallocator {
var unityInstance:UnityFrameworkWrapper? = nil
override func viewDidLoad() {
super.viewDidLoad()
startUnity()
}
//加载游戏画面
func startUnity() {
unityInstance = UnityFrameworkWrapper.init()
unityInstance?.delegate = self
unityInstance?.showUnityView()
}
//退出游戏画面
func exitUnity() {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
//不加下面的代码,从游戏回到APP画面会卡主
if let window = appDelegate?.window {
window.makeKeyAndVisible()
}
//退回上一级画面
self.dismiss(animated: false, completion: nil)
}
}
1.2.7 新建一个文件夹存放生成的UnityFramework
编译生成UnityFramework
找到它
添加进iOS工程
1.2.8 删除系统的Main,自己新建一个启动的ViewController
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame:UIScreen.main.bounds)
window?.backgroundColor = .white
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
}
1.2.9 新建的ViewController代码包含有显示游戏画面和传参的代码
import UIKit
class ViewController: UIViewController, UnityFrameworkDeallocator {
var unityInstance:UnityFrameworkWrapper? = nil
let buttonChangeGame = UIButton(frame: CGRect(x: 250, y: 250, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let buttonGoToGameNewPage = UIButton(frame: CGRect(x: 50, y: 100, width: 150, height: 100))
buttonGoToGameNewPage.backgroundColor = .gray
buttonGoToGameNewPage.setTitle("新页面显示游戏", for: .normal)
view.addSubview(buttonGoToGameNewPage)
buttonGoToGameNewPage.addTarget(self, action: #selector(buttonGoToGameNewPageClick), for: UIControl.Event.touchUpInside)
let buttonGoToGame = UIButton(frame: CGRect(x: 50, y: 250, width: 150, height: 100))
buttonGoToGame.backgroundColor = .gray
buttonGoToGame.setTitle("当前页显示游戏", for: .normal)
view.addSubview(buttonGoToGame)
buttonGoToGame.addTarget(self, action: #selector(buttonGoToGameClick), for: UIControl.Event.touchUpInside)
//获得游戏的通知,改变颜色
NotificationCenter.default.addObserver(self, selector: #selector(ChangeColor), name:Notification.Name("iOSGetMessageChangeColor") , object: nil)
}
@objc func ChangeColor() {
self.view.backgroundColor = .systemYellow
}
@objc func buttonGoToGameNewPageClick() {
//新画面显示
let unityViewController = UnityViewController()
unityViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(unityViewController, animated: false)
self.unityInstance?.framework.appController().rootView.alpha = 1.0;
buttonChangeGame.alpha = 0.0
}
@objc func buttonGoToGameClick() {
//当前画面显示
unityInstance = UnityFrameworkWrapper.init()
unityInstance?.delegate = self
self.unityInstance?.framework.appController().rootView.frame = CGRect(x: 0, y: 500, width: 400, height: 200)
self.unityInstance?.framework.appController().rootView.alpha = 1.0
self.unityInstance?.framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "ios_to_unity")
//在游戏的window上加载一个APP的按钮,按钮可以点击。
buttonChangeGame.backgroundColor = .gray
buttonChangeGame.setTitle("游戏变色", for: .normal)
unityInstance?.framework.appController().window.addSubview(buttonChangeGame)
buttonChangeGame.addTarget(self, action: #selector(buttonChangeGameClick), for: UIControl.Event.touchUpInside)
buttonChangeGame.alpha = 1.0
}
@objc func buttonChangeGameClick() {
self.unityInstance?.framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "unity_change_color")
print("buttonChangeGameClick")
}
//退出游戏画面
func exitUnity() {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
//不加下面的代码,从游戏回到APP画面会卡主
if let window = appDelegate?.window {
window.makeKeyAndVisible()
}
self.unityInstance?.framework.appController().rootView.alpha = 0.0;
}
}
1.2.10 项目APP是竖屏,做的一些设置
二 APP传参给游戏(APP运行中和APP结束时)
2.1 iOS结束,启动Unity时传参
let unityViewController = UnityViewController()
unityViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(unityViewController, animated: false)
self.unityInstance?.framework.appController().rootView.alpha = 1.0;
buttonChangeGame.alpha = 0.0
framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "iOSToUnityStart")
Main Camera 是Unity接收的对象
IOSToUnity 是挂载在Main Camera脚本里的方法
iOSToUnityStart 是传给Unity的具体字符
public void IOSToUnity(string str)
{
Debug.Log("Unity接收IOS信息" + str);
}
2.2 iOS运行中向Unity传参
self.unityInstance?.framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "unity_change_color")
Main Camera 是Unity接收的对象
IOSToUnity 是挂载在Main Camera脚本里的方法
unity_change_color 是传给Unity的具体字符
获得iOS传给的字符unity_change_color,改变Unity的背景色
public void IOSToUnity(string str)
{
Debug.Log("Unity接收IOS信息" + str);
if (str == "unity_change_color")
camera.backgroundColor = new Color(0 / 255, 255 / 255, 255 / 255);
}
三 游戏传参给APP(游戏运行中和游戏结束时)
3.1 Unity结束,启动iOS时传参
Unity:
//Unity发送信息给iOS
UnitySendMessageToiOS("unity_to_ios");
//unity程序关闭
Application.Unload();
iOS:
Unity里调用Application.Unload();方法。在iOS里有一个接收的方法
func unityDidUnload(_ notification: Notification!) {
print("Unity发送的信息,iOS接收的信息 = \(String(describing: framework.myiOSGetMessage()))")
//点击Unity的Application.unload方法会进入到这个方法
framework.unregisterFrameworkListener(self)
//调用UnityViewController里的exitUnity方法
delegate?.exitUnity()
}
myiOSGetMessage方法是前面添加Unity和iOS共通里处理好的方法(具体参考1.2.5 加入代码做到Unity和iOS相互传值的共通代码)。
3.2 Unity运行中向iOS传参
Unity:
//Unity发送信息给iOS
iOSGetMessageChangeColor();
iOS:
//获得游戏的通知,改变APP颜色
NotificationCenter.default.addObserver(self, selector: #selector(ChangeColor), name:Notification.Name("iOSGetMessageChangeColor") , object: nil)
@objc func ChangeColor() {
self.view.backgroundColor = .systemYellow
}
iOSGetMessageChangeColor方法是前面添加Unity和iOS共通里处理好的方法(具体参考1.2.5 加入代码做到Unity和iOS相互传值的共通代码)。
四 游戏获取APP的权限(以录音权限为例说明)
4.1 游戏申请录音权限,会弹窗让用户选择是否赋权限给APP
/// <summary>
/// 申请录音权限(录音1秒)
/// </summary>
void RequestRecordRight()
{
Microphone.Start(null, false, 1, 1);
Microphone.End(null);
Debug.Log("申请权限");
}
4.2 游戏调用iOS的API确认是否获取了录音权限
Unity:
// 调用此方法,iOS端对应方法可以接收到,返回值通过iOS的方法返回给Unity
private static extern string getMicroRight();
/// <summary>
/// 检测录音权限,需要游戏向APP申请,APP获取是否有权限后,再返回给游戏
/// </summary>
/// <returns></returns>
public IEnumerator CheckiOSRecordRight()
{
Debug.Log("getMicroRight() = " + getMicroRight());
yield return new WaitForSeconds(0.5f);
#if UNITY_IOS
//录音权限未开启,提醒用户,并且不能玩游戏。
if (getMicroRight() == "0")
{
Debug.Log("录音权限未授权,请在设置画面开启录音权限");
}
else
{
Debug.Log("具有录音权限,实现相关功能");
}
#endif
}
iOS:
getMicroRight方法是写在iOSUtilUnity3dBridge.h和iOSUtilUnity3dBridge.m里
//获取录音权限状态
//约定返回游戏,1代表授权。0代表未授权
const char* getMicroRight()
{
AVAuthorizationStatus microPhoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
switch (microPhoneStatus) {
case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
{
// 被拒绝
NSString *string = [NSString stringWithFormat:@"%s", "0"];
return _MakeStringCopy([string UTF8String]);
}
break;
case AVAuthorizationStatusNotDetermined:
{
// 没弹窗
NSString *string = [NSString stringWithFormat:@"%s", "0"];
return _MakeStringCopy([string UTF8String]);
}
break;
case AVAuthorizationStatusAuthorized:
{
// 有授权
NSString *string = [NSString stringWithFormat:@"%s", "1"];
return _MakeStringCopy([string UTF8String]);
}
break;
default:
break;
}
NSString *string = [NSString stringWithFormat:@"%s", "0"];
return _MakeStringCopy([string UTF8String]);
}
在iOS端获取到权限会及时返回到Unity,Unity拿到是否获取到录音权限后,再处理相关逻辑。
五 运行效果