Låt oss diskutera olika sätt att påskynda din produktutveckling.

Visst, då gör vi det
Betyg 4,9/5 på Clutch

MOP

  • Tjänster

  • Produkter

  • Människor

  • Blogg

  • Låt oss bygga

Mer

  • Fallstudier

  • Utmärkelser

  • Kundutlåtanden

  • Lediga tjänster

  • Teknik

Socialt

  • LinkedIn

  • X

  • Instagram

  • Facebook

  • Dribbble

© 2026 Ministry of Programming

  • Integritetspolicy
Tillbaka till bloggen

Kommunicera med ett Unity-spel som är inbäddat i en SwiftUI iOS-app

Dino Trnka
12 mars 2021Programvaruarkitektur och -utvecklingLäsningstid: 12 minuter
Kommunicera med ett Unity-spel som är inbäddat i en SwiftUI iOS-app

Om du följde mitt tidigare inlägg om att starta ett Unity-spel från en SwiftUI-app, kunde du framgångsrikt integrera ett Unity-spel i ditt SwiftUI-projekt och ladda eller lossa det med en knapptryckning.

Vår Swifty-resa slutar dock inte där, för det finns inte mycket användning för den här funktionaliteten utan att faktiskt kunna kommunicera mellan de två apparna. Kanske behöver du skicka lite spel- eller spelarinformation från iOS till Unity-sidan för att initiera spelet ordentligt. Eller så behöver du skicka tillbaka spelarens poäng till den inbyggda appen när du är klar med spelet.

Allt detta är möjligt med Unity-ramverket, och med bara en liten inställning kan du skapa tvåvägskommunikation utan problem. Låt oss se hur!

Obs: Vi fortsätter precis där vi slutade, så om du inte redan har gjort det, se till att följa med till den första delen av artikeln, där alla projekt är inställda.

Vad vill vi åstadkomma?

För att kunna implementera kommunikation mellan iOS-appen och Unity-spelet måste vi skapa något som är lite mer komplext än en app med en knapp, men ändå tillräckligt enkelt för att inte bli distraherade av spelmekaniken.

Förkommunikationen mellan iOS och Unity ska vi alltså ” go ” med den här enkla idén: Vårt Unity-spel kommer att innehålla en enda boll som kan byta färg beroende på vilket meddelande vi skickar från den inbyggda iOS-sidan. På den inbyggda iOS-sidan kommer vi att implementera tre knappar som är märkta med olika färgnamn:röd,grönochblå. Var och en av dessa knappar startar samma Unity-spel. Men varje knapp skickar ett annat meddelande till spelet, så om användaren trycker på denrödaknappen blir bollen röd, den blå knappen gör bollen blå och så vidare.

För Unity → iOS-kommunikationen gör vi tvärtom. Vi lägger till en knapp i Unity-spelet och håller reda på hur många gånger den har tryckts in genom att skicka ett meddelande från Unity till iOS vid varje knapptryckning.

Dessa användningsfall är mycket enkla och representerar inte ett riktigt spel, men det räcker för att vi ska kunna ta en titt på hur kommunikationen fungerar. Nu när vi vet vad vi ska göra, låt oss komma till den roliga delen!

iOS → Unity-kommunikation

Låt oss skapa ett nytt Unity-projekt. I det projektet lägger du till en Avsluta spelet knappen och få den att bara ringa Applikation.avlastning()på exakt samma sätt som vi gjorde i föregående inlägg. Om du vill kan du återanvända det tidigare Unity-projektet, eftersom det redan har funktionen för Quit-knappen.

Lägg sedan till ett spelobjekt i form av en sfär och döp det till Ball.

Vår glänsande boll är inget speciellt för tillfället, men det kommer den att bli när vi ger den lite funktionalitet. Så låt oss skapa ett nytt skript som heter BallBehavior.cs och bifoga det till vårt spelobjekt Ball. Detta skript måste ha följande kod:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallBehavior : MonoBehaviour
{
    void SetBallColor(string color)
    {
        if (color == "red") GetComponent<Renderer>().material.color = Color.red;
        else if (color == "blue") GetComponent<Renderer>().material.color = Color.blue;
        else if (color == "green") GetComponent<Renderer>().material.color = Color.green;
        else GetComponent<Renderer>().material.color = Color.white;
    }
}

Tro det eller ej, men den här koden är allt som behövs från Unity-sidan för att upprätta iOS → Unity-kommunikation. Den inbyggda appen behöver helt enkelt anropa SetBallColor metod genom att använda UnityFramework SDK.

Låt oss exportera det här spelet från Unity nu. Vi kommer att exportera det i en mapp med namnet UnityBallExport.

Kom ihåg att varje gång du exporterar ditt Unity-spel måste du upprepa integrationsstegen:

  • Dra det exporterade projektets Unity-iPhone.xcodeproj-fil till din huvudsakliga iOS-apps XCode-arbetsyta,
  • (Åter)Importera UnityFramework.framework-biblioteket i XCode,
  • Välj mappen Data och markera rutan Target Membership bredvid UnityFramework.

Se avsnittet Ansluta Unity med iOS i föregående artikel för mer information om hur du gör detta.

Nu är det dags att modifiera vår SwiftyUnity projekt. Vårt huvudmål här är att anropa SetBallColor som vi just har implementerat på Unity-sidan från iOS-appen. Som du minns laddar vi Unity-spelet från vår ContentView.swift fil. Vi kommer att ställa in bollens färg direkt efter att vi har visat Unity-spelet.

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(action: {
            Unity.shared.show()

            // Implement a way to call Unity's SetBallColor() method here

        }) {
            Text("Launch Unity!")
        }
    }
}

En viktig sak att tänka på är att spelet kanske inte har initialiserats när vi skickar meddelandet, vilket kan leda till att meddelandet går förlorat. Därför kommer vi att implementera en cachemekanism som lagrar alla väntande meddelanden, så att de kan skickas när spelet är fulladdat.

Öppna filen Unity.swift som är vår huvudsakliga ingångspunkt till spelet. Låt oss lägga till en ny struktur som heter UnityMessage, som kommer att innehålla alla data vi behöver för att skicka meddelanden till Unity.

import Foundation
import UnityFramework

class Unity: UIResponder, UIApplicationDelegate {

    private struct UnityMessage {
        let objectName: String?
        let methodName: String?
        let messageBody: String?
    }

    private var cachedMessages = [UnityMessage]()
    
    
    . . .
    
    
    func sendMessage(
        _ 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 isInitialized {
            ufw?.sendMessageToGO(
                withName: msg.objectName,
                functionName: msg.methodName,
                message: msg.messageBody
            )
        } else {
            cachedMessages.append(msg)
        }
    }
    
}

Nu har vi ett sätt att skicka meddelanden till Unity genom att använda skicka meddelande metod som anropar Unity Framework skicka meddelande tillGO metoden (GO står för Game Object). Som du ser kontrollerar appen om Unity har initialiserats, och om så är fallet skickas meddelandet omedelbart. I annat fall lagras meddelandet i cachen så att det kan skickas senare.

Vi måste nu hantera mekanismen för att skicka och städa upp de cachade meddelandena. Med hänsyn till detta bör din Unity.swift-fil se ut så här:

import Foundation
import UnityFramework

class Unity: UIResponder, UIApplicationDelegate {

    // The structure for Unity messages
    private struct UnityMessage {
        let objectName: String?
        let methodName: String?
        let messageBody: String?
    }

    private var cachedMessages = [UnityMessage]() // Array of cached messages

    static let shared = Unity()

    private let dataBundleId: String = "com.unity3d.framework"
    private let frameworkPath: String = "/Frameworks/UnityFramework.framework"

    private var ufw : UnityFramework?
    private var hostMainWindow : UIWindow?

    private var isInitialized: Bool {
        ufw?.appController() != nil
    }

    func show() {
        if isInitialized {
            showWindow()
        } else {
            initWindow()
        }
    }

    func setHostMainWindow(_ hostMainWindow: UIWindow?) {
        self.hostMainWindow = hostMainWindow
    }

    private func initWindow() {
        if isInitialized {
            showWindow()
            return
        }

        guard let ufw = loadUnityFramework() else {
            print("ERROR: Was not able to load Unity")
            return unloadWindow()
        }

        self.ufw = ufw
        ufw.setDataBundleId(dataBundleId)
        ufw.register(self)
        ufw.runEmbedded(
            withArgc: CommandLine.argc,
            argv: CommandLine.unsafeArgv,
            appLaunchOpts: nil
        )

        sendCachedMessages() // Added this line
    }

    private func showWindow() {
        if isInitialized {
            ufw?.showUnityWindow()
            sendCachedMessages() // Added this line
        }
    }

    private func unloadWindow() {
        if isInitialized {
            cachedMessages.removeAll() // Added this line
            ufw?.unloadApplication()
        }
    }

    private func loadUnityFramework() -> UnityFramework? {
        let bundlePath: String = Bundle.main.bundlePath + frameworkPath

        let bundle = Bundle(path: bundlePath)
        if bundle?.isLoaded == false {
            bundle?.load()
        }

        let ufw = bundle?.principalClass?.getInstance()
        if ufw?.appController() == nil {
            let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
            machineHeader.pointee = _mh_execute_header

            ufw?.setExecuteHeader(machineHeader)
        }
        return ufw
    }

    // Main method for sending a message to Unity
    func sendMessage(
        _ 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 isInitialized {
            ufw?.sendMessageToGO(
                withName: msg.objectName,
                functionName: msg.methodName,
                message: msg.messageBody
            )
        } else {
            cachedMessages.append(msg)
        }
    }

    // Send all previously cached messages, if any
    private func sendCachedMessages() {
        if cachedMessages.count >= 0 && isInitialized {
            for msg in cachedMessages {
                ufw?.sendMessageToGO(
                    withName: msg.objectName,
                    functionName: msg.methodName,
                    message: msg.messageBody
                )
            }

            cachedMessages.removeAll()
        }
    }
}

extension Unity: UnityFrameworkListener {

    func unityDidUnload(_ notification: Notification!) {
        ufw?.unregisterFrameworkListener(self)
        ufw = nil
        hostMainWindow?.makeKeyAndVisible()
    }
}

Jag har lagt till kommentarer i den här filen så att du kan hålla reda på vad som har lagts till jämfört med den senaste. Som du kan se, skickaCachadeMeddelanden ser till att eventuella cachade meddelanden skickas efter att spelet har initierats, så att inget går förlorat.

Låt oss nu gå tillbaka till vår ContentView.swift så att vi kan använda den här funktionen genom att skapa våra röda, blå och gröna knappar. Jag lade till vymodifierarna bara för att få knapparna att se lite snyggare ut, så bry dig inte om dem.

import SwiftUI

private struct ButtonViewModifier: ViewModifier {
    var color: Color

    func body(content: Content) -> some View {
        content.frame(width: 200, height: 50).background(color)
    }
}

private struct TextViewModifier: ViewModifier {
    func body(content: Content) -> some View {
        content.font(.title).foregroundColor(Color.white)
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer()

            Button(action: {
                Unity.shared.show()
                Unity.shared.sendMessage(
                    "Ball",
                    methodName: "SetBallColor",
                    message: "red"
                )
            }) {
                Text("Red").modifier(TextViewModifier())
            }
            .modifier(ButtonViewModifier(color: Color.red))

            Spacer()

            Button(action: {
                Unity.shared.show()
                Unity.shared.sendMessage(
                    "Ball",
                    methodName: "SetBallColor",
                    message: "blue"
                )
            }) {
                Text("Blue").modifier(TextViewModifier())
            }
            .modifier(ButtonViewModifier(color: Color.blue))
            
            Spacer()

            Button(action: {
                Unity.shared.show()
                Unity.shared.sendMessage(
                    "Ball",
                    methodName: "SetBallColor",
                    message: "green"
                )
            }) {
                Text("Green").modifier(TextViewModifier())
            }
            .modifier(ButtonViewModifier(color: Color.green))

            Spacer()
        }
    }
}

Var och en av dessa knappar anropar Unity.shared.sendMessage metod. Den första parametern är namnet på vårt spelobjekt i Unity, vilket är Boll i det här fallet. Den andra parametern är namnet på metoden i skriptet som är kopplat till spelobjektet, vilket är SetBallColor. Den tredje parametern är det meddelande vi vill skicka, vilket är färgens namn. Tänk på att meddelandet måste vara av strängtypså om du vill skicka en annan typ måste du linda in den i ett snöre och linda upp den på Unity-sidan.

Låt oss testa detta genom att trycka på knapparna:

Nu fungerar det! Bollen tar emot meddelandet och ändrar färg. Beroende på vilken knapp du trycker på ändrar bollen färg till röd, blå eller grön. Detta avslutar vår iOS → Unity-kommunikation.

Unity → iOS-kommunikation

Att skicka meddelanden åt andra hållet är lite knepigare, men det kommer inte att stoppa oss! Låt oss återgå till vårt mål: Vi måste hålla reda på hur många gånger knappen i Unity-spelet har tryckts in genom att skicka ett meddelande tillbaka till iOS vid varje knapptryckning.

Låt oss skapa ett nytt Unity-projekt och lägga till en enda knapp i scenen. Skapa också en ny mapp inuti Tillgångar mapp och namnge den Plugins.

Vi lämnar Unity för ett ögonblick eftersom vi behöver göra en del konfigurationer på iOS-sidan. I SwiftyUnity-projektet lägger du till en ny Objective-C-fil som heter NativeCallProxy.

När XCode erbjuder sig att skapa en Objective-C-brygghuvud, klicka på knappen Create Bridging He ader och låt XCode konfigurera allt så att Objective-C-kod också kan nås från projektet.

Viktigt: XCode skapade automatiskt en fil med namnet NativeCallProxy.m, men vi vill att tillägget ska vara .mm. Byt därför namn på filen till NativeCallProxy.mm.

Hittills har vi 2 viktiga nya filer i iOS-projektet: NativeCallProxy.mm och SwiftyUnity-Bridging-Header.h.

Lägg till ytterligare en fil och döp den till NativeCallProxy.h. Redigera dessa filer så att de får följande innehåll:

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import <UnityFramework/NativeCallProxy.h>
#import <Foundation/Foundation.h>

@protocol NativeCallsProtocol
@required
- (void) sendMessageToMobileApp:(NSString*)message;
// other methods
@end

__attribute__ ((visibility("default")))
@interface FrameworkLibAPI : NSObject
+(void) registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi;

@end
#import <Foundation/Foundation.h>
#import "NativeCallProxy.h"

@implementation FrameworkLibAPI

id<NativeCallsProtocol> api = NULL;
+(void) registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi
{
    api = aApi;
}

@end

extern "C"
{
    void sendMessageToMobileApp(const char* message)
    {
        return [api sendMessageToMobileApp:[NSString stringWithUTF8String:message]];
    }
}

Vi kommer att använda skicka meddelande till MobileApp metod för vår Unity → iOS-kommunikation. Vi kommer att ringa ett samtal genom att trycka på knappen som vi skapade i vår Unity-scen och placera en lyssnare för den händelsen på den inbyggda sidan.

Nu kommer den intressanta delen: Vi måste flytta NativeCallProxy.h och NativeCallProxy.mm till Unity-projektet, eftersom vi kommer att anropa dem från UnityFramework! Vi kommer dock att lämna SwiftyUnity-Bridging-Header.h i XCode.

Så, låt oss flytta NativeCallProxy.h och NativeCallProxy.mm inuti Plugins som vi skapade i förväg i Unity-projektet. Vi behöver inte dessa i iOS-projektet, eftersom de ändå kommer att packas inuti det exporterade Unity-projektet, så ta gärna bort dem från SwiftyUnity-projektet.

Nu är det dags att koppla ihop allt. Låt oss skapa ett C#-skript som heter ButtonBehavior.cs och fylla det med följande kod:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.UI;
using UnityEngine;

public class NativeAPI {
    [DllImport("__Internal")]
    public static extern void sendMessageToMobileApp(string message);
}

public class ButtonBehavior : MonoBehaviour
{
    private int pressCount;

    void Start () {
        pressCount = 0;
    }

    public void ButtonPressed()
    {
        pressCount++;
        NativeAPI.sendMessageToMobileApp("The button has been tapped " + pressCount.ToString() + " times!");
    }
}

Den "magiska" händelsen äger rum inuti ButtonPressed() metod. Den anropar NativeAPI.sendMessageToMobileApp() med ett meddelande som kommer att tas emot på den inbyggda iOS-sidan.

Allt som återstår nu för Unity-sidan är att ansluta detta skript till själva knappen, så lägg till skriptet ButtonBehavior.cs till vår knapp och anslut lyssnaren ButtonPressed.

Nu är det dags! Låt oss bygga det här spelet. Vi kommer att namnge det UnityButtonExport och integrera det i vår XCode-arbetsyta precis som vi gjorde tidigare. Kom ihåg att varje gång du exporterar ett Unity-spel som ett iOS-projekt måste du:

  • Dra det exporterade projektets Unity-iPhone.xcodeproj-fil till din huvudsakliga iOS-apps XCode-arbetsyta,
  • (Åter)Importera UnityFramework.framework-biblioteket i XCode,
  • Välj mappen Data och markera rutan Target Membership bredvid UnityFramework.

Viktigt: Den här gången måste vi göra ytterligare en konfiguration utöver det vanliga. Kom ihåg NativeCallProxy filer från Unity-spelet Plugins mapp? Du kommer att se dem i den exporterade Enhet-iPhone projekt nu.

Du måste välja den NativeCallProxy.h inuti Bibliotek/Plugins mappen för Enhet-iPhone projekt och ändra UnityFrameworks målmedlemskap från Projekt till Allmänheten. Glöm inte detta steg!

Det sista steget är att koppla en lyssnare från appen till skicka meddelande till MobileApp metod. Låt oss skapa ett exempel på en vymodellklass som kommer att registrera en lyssnare för våra Unity-meddelanden. Skapa en ny Swift-fil, namnge den ViewModel.swiftoch fyll i den med följande kod:

import Foundation

class ViewModel: NSObject, ObservableObject, NativeCallsProtocol {

    override init() {
        super.init()

        NSClassFromString("FrameworkLibAPI")?.registerAPIforNativeCalls(self)
    }

    func sendMessage(toMobileApp message: String) {
        print(message)
    }
}

Vi kommer också att förenkla vår ContentView.swift, så att den bara startar spelet:

import SwiftUI
import UnityFramework

struct ContentView: View {

    let viewModel = ViewModel()

    var body: some View {
        VStack {
            Button(action: {
                Unity.shared.show()
            }) {
                Text("Launch Unity")
            }
        }
    }
}

Efter så mycket arbete är det äntligen dags att testa kommunikationen. Kör iOS-projektet och tryck på "Press Me!"-knappen. Meddelandet från Unity ska skrivas ut till dina XCode-loggar.

Kom ihåg att du måste använda en fysisk iPhone-enhet (jag använder också en i exemplet gif som följer, jag strömmar bara till skärmen).

Phew! Det var en hel del arbete. Du kan dock tillämpa denna inställning på alla typer av kommunikation du behöver! 🤩 Dessa exempel är mycket enkla men med den här baslinjen kan du implementera mer komplex logik som att skicka eller ta emot ett meddelande på varje bildruta eller till och med använda en anpassad Bluetooth-kontroller för spelet, som skulle hanteras på iOS-sidan och skickas över till Unity.

Jag hoppas att du gillade vår lilla resa. Jag önskar dig massor av skoj med ditt Unity-äventyr! 🤓


Om du gillade den här artikeln, kolla in del 1:

➡️ Starta ett Unity-spel från en SwiftUI iOS-app