Unity game embedded in iOS application (integrated into one application) v2.0

One game embedded in APP
1.1 Development tool version
1.1.1 iOS development tool version Xcode Version 14.2 (14C18)
?Please add image description
1.1.2 The iOS project development language is Swift5
?
1.1.3 Unity development tool version 2022.3.8f1c1
?
1.2 Combining iOS projects and Unity projects

1.2.1 Create a new Swift language project on iOS
?

In the new iOS project, create a new folder Unity_Framework_Project. Subsequent projects exported by Unity will be placed in this folder.


1.2.2 In Unity project, switch to iOS platform
?

Unity exports the package and puts it into the folder Unity_Framework_Project in the iOS project.

Exported file


1.2.3 Import the game project into the iOS project


?
1.2.4 Make the following settings in Xcode
?
1.2.5 Add common code that Unity and iOS can pass values to each other
Create two new files in the unity project and add them

iOSUtilUnity3dBridge.h

#import <Foundation/Foundation.h>
//Packages that need to be introduced to call the recording permission
#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 to 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;
    }
    
    /// Notify ios to close the screen of the blocking game
    void iOSGetMessageChangeColor()
    {<!-- -->
        NSLog(@"iOSGetMessageChangeColor");
        //The oc layer sends a notification, and the swift layer will receive the notification and perform corresponding logical processing where needed.
        [[NSNotificationCenter defaultCenter] postNotificationName:@"iOSGetMessageChangeColor" object:nil];
    }


    //Get the recording permission status
    //The agreement is to return to the game, 1 represents authorization. 0 means unauthorized
    const char* getMicroRight()
    {<!-- -->
        AVAuthorizationStatus microPhoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
            switch (microPhoneStatus) {<!-- -->
                case AVAuthorizationStatusDenied:
                case AVAuthorizationStatusRestricted:
                {<!-- -->
                    // be rejected
                    NSString *string = [NSString stringWithFormat:@"%s", "0"];
                    return _MakeStringCopy([string UTF8String]);
                }
                    break;
                case AVAuthorizationStatusNotDetermined:
                {<!-- -->
                    // No pop-up window
                    NSString *string = [NSString stringWithFormat:@"%s", "0"];
                    return _MakeStringCopy([string UTF8String]);
                }
                    break;
                case AVAuthorizationStatusAuthorized:
                {<!-- -->
                    // authorized
                    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

The iOS project exported by Unity needs to adjust the position of iOSUtilUnity3dBridge.h as shown below

Add the following code

#import "iOSUtilUnity3dBridge.h"

- (NSString *)myiOSGetMessage;

- (NSString *)myiOSGetMessage
{<!-- -->
    return iOSGetMessage();
}

1.2.6 Add 2 classes to control the display of Unity games in the new screen of the 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()
        //The BundleId below is the BundleId in the target of the framework in Unity's ios project. This must be consistent with the package name inside.
        framework.setDataBundleId("com.unity3d.framework")
        framework.register(self)
        //If this sentence is not added, the program will not run.
        framework.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: nil)
    }


    // MARK: - NativeCallsProtocol
    func showHostMainWindow(_ color: String!) {<!-- -->
        delegate?.exitUnity()
    }
    
    //The following method will be triggered after calling Application.unload in Unity's CSharp code
    func unityDidUnload(_ notification: Notification!) {<!-- -->
        print("Message sent by Unity, message received by iOS = \(String(describing: framework.myiOSGetMessage()))")
        //Click Unity's Application.unload method to enter this method
        framework.unregisterFrameworkListener(self)
        //Call the exitUnity method in UnityViewController
        delegate?.exitUnity()
    }
    
    func showUnityView() {<!-- -->
        //iOS sends information to Unity
        framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "iOSToUnityStart")
        //Display the unity screen
        framework.showUnityWindow()
    }


}

UnityViewController.swift

import UIKit


class UnityViewController: UIViewController, UnityFrameworkDeallocator {<!-- -->


    var unityInstance:UnityFrameworkWrapper? = nil
    
    override func viewDidLoad() {<!-- -->
        super.viewDidLoad()
        startUnity()
    }
    
    //Load game screen
    func startUnity() {<!-- -->
        unityInstance = UnityFrameworkWrapper.init()
        unityInstance?.delegate = self
        unityInstance?.showUnityView()
    }
    
    //Exit the game screen
    func exitUnity() {<!-- -->
        let appDelegate = UIApplication.shared.delegate as? AppDelegate
        //If you don’t add the following code, you will get stuck when returning to the APP screen from the game.
        if let window = appDelegate?.window {<!-- -->
            window.makeKeyAndVisible()
        }
        //Return to the previous screen
        self.dismiss(animated: false, completion: nil)
    }


}

1.2.7 Create a new folder to store the generated UnityFramework

2

Compile and generate UnityFramework

find it


Add to iOS project



1.2.8 Delete the system’s Main and create a new started ViewController by yourself


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 The newly created ViewController code includes code for displaying the game screen and passing parameters.
?
?

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("New page displays game", 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("The current page displays the game", for: .normal)
        view.addSubview(buttonGoToGame)
        buttonGoToGame.addTarget(self, action: #selector(buttonGoToGameClick), for: UIControl.Event.touchUpInside)
        //Get notifications from the game and change the color
        NotificationCenter.default.addObserver(self, selector: #selector(ChangeColor), name:Notification.Name("iOSGetMessageChangeColor"), object: nil)
    }
    @objc func ChangeColor() {<!-- -->
        self.view.backgroundColor = .systemYellow
    }
    @objc func buttonGoToGameNewPageClick() {<!-- -->
        //New screen display
        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() {<!-- -->
        //Current screen display
        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")
        //Load an APP button on the game window, and the button can be clicked.
        buttonChangeGame.backgroundColor = .gray
        buttonChangeGame.setTitle("Game color change", 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")
    }
    //Exit the game screen
    func exitUnity() {<!-- -->
        let appDelegate = UIApplication.shared.delegate as? AppDelegate
        //If you don’t add the following code, you will get stuck when returning to the APP screen from the game.
        if let window = appDelegate?.window {<!-- -->
            window.makeKeyAndVisible()
        }
        self.unityInstance?.framework.appController().rootView.alpha = 0.0;
    }
}

1.2.10 The project APP is a vertical screen, so some settings have to be made.

2. The APP passes parameters to the game (while the APP is running and when the APP ends)

2.1 iOS ends, passing parameters when starting 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

2

framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "iOSToUnityStart")

Main Camera is the object received by Unity
IOSToUnity is a method mounted in the Main Camera script
iOSToUnityStart is the specific character passed to Unity

 public void IOSToUnity(string str)
    {<!-- -->
        Debug.Log("Unity receives IOS information" + str);
    }

2.2 Passing parameters to Unity while iOS is running
?
?

self.unityInstance?.framework.sendMessageToGO(withName: "Main Camera", functionName: "IOSToUnity", message: "unity_change_color")

Main Camera is the object received by Unity
IOSToUnity is a method mounted in the Main Camera script
unity_change_color is the specific character passed to Unity
2

Get the character unity_change_color passed by iOS and change the background color of Unity

 public void IOSToUnity(string str)
    {<!-- -->
        Debug.Log("Unity receives IOS information" + str);
        if (str == "unity_change_color")
            camera.backgroundColor = new Color(0 / 255, 255 / 255, 255 / 255);
    }

3. Game parameters are transferred to APP (during game running and at the end of game)

3.1 Unity ends and parameters are passed when starting iOS
Unity:

//Unity sends information to iOS
UnitySendMessageToiOS("unity_to_ios");
//Unity program closes
Application.Unload();

iOS:

Call the Application.Unload(); method in Unity. There is a method to receive in iOS

 func unityDidUnload(_ notification: Notification!) {<!-- -->
        print("Message sent by Unity, message received by iOS = \(String(describing: framework.myiOSGetMessage()))")
        //Click Unity's Application.unload method to enter this method
        framework.unregisterFrameworkListener(self)
        //Call the exitUnity method in UnityViewController
        delegate?.exitUnity()
    }

The myiOSGetMessage method is a method that was previously added to be common between Unity and iOS (for details, please refer to 1.2.5 Adding code to enable Unity and iOS to transfer values to each other).

3.2 Transferring parameters to iOS while Unity is running
Unity:

//Unity sends information to iOS
iOSGetMessageChangeColor();

iOS:

//Get game notifications and change APP color
NotificationCenter.default.addObserver(self, selector: #selector(ChangeColor), name:Notification.Name("iOSGetMessageChangeColor"), object: nil)

@objc func ChangeColor() {<!-- -->
    self.view.backgroundColor = .systemYellow
}

The iOSGetMessageChangeColor method is a method that was previously added to be common between Unity and iOS (for details, refer to 1.2.5 Adding code to enable Unity and iOS to transfer values to each other).

4. The game obtains APP permissions (taking recording permissions as an example)
4.1 When the game applies for recording permission, a window will pop up allowing the user to choose whether to grant permission to the APP.
?

 /// <summary>
    /// Apply for recording permission (recording for 1 second)
    /// </summary>
    void RequestRecordRight()
    {<!-- -->
        Microphone.Start(null, false, 1, 1);
        Microphone.End(null);
        Debug.Log("Apply for permission");
    }

4.2 The game calls the iOS API to confirm whether it has obtained the recording permission.
Unity:
?

 // Call this method, the corresponding method on iOS can receive it, and the return value is returned to Unity through the iOS method
    private static extern string getMicroRight();

/// <summary>
    /// To detect the recording permission, the game needs to apply to the APP. After the APP obtains whether it has permission, it returns to the game.
    /// </summary>
    /// <returns></returns>
    public IEnumerator CheckiOSRecordRight()
    {<!-- -->
        Debug.Log("getMicroRight() = " + getMicroRight());
        yield return new WaitForSeconds(0.5f);
#if UNITY_IOS
        //The recording permission is not turned on, remind the user, and cannot play games.
        if (getMicroRight() == "0")
        {<!-- -->
            Debug.Log("Recording permission is not authorized, please enable recording permission in the settings screen");
        }
        else
        {<!-- -->
            Debug.Log("Have recording permissions and implement related functions");
        }
#endif
    }

iOS:

The getMicroRight method is written in iOSUtilUnity3dBridge.h and iOSUtilUnity3dBridge.m

//Get the recording permission status
    //Agree to return to the game, 1 represents authorization. 0 means unauthorized
    const char* getMicroRight()
    {<!-- -->
        AVAuthorizationStatus microPhoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
            switch (microPhoneStatus) {<!-- -->
                case AVAuthorizationStatusDenied:
                case AVAuthorizationStatusRestricted:
                {<!-- -->
                    // be rejected
                    NSString *string = [NSString stringWithFormat:@"%s", "0"];
                    return _MakeStringCopy([string UTF8String]);
                }
                    break;
                case AVAuthorizationStatusNotDetermined:
                {<!-- -->
                    // No pop-up window
                    NSString *string = [NSString stringWithFormat:@"%s", "0"];
                    return _MakeStringCopy([string UTF8String]);
                }
                    break;
                case AVAuthorizationStatusAuthorized:
                {<!-- -->
                    // authorized
                    NSString *string = [NSString stringWithFormat:@"%s", "1"];
                    return _MakeStringCopy([string UTF8String]);
                }
                    break;
                default:
                    break;
            }
        
        NSString *string = [NSString stringWithFormat:@"%s", "0"];
        return _MakeStringCopy([string UTF8String]);
    }

When the permission is obtained on the iOS side, it will be returned to Unity in time. After Unity determines whether the recording permission has been obtained, it will then process the relevant logic.

Five operating effects
Please add image description