MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C与Unity3D引擎交互技术解析

2021-10-072.2k 阅读

一、Objective - C 与 Unity3D 引擎交互基础

Objective - C 是一种面向对象的编程语言,主要用于 macOS 和 iOS 开发。Unity3D 则是一款跨平台的游戏开发引擎,被广泛应用于游戏及其他交互式应用的创建。当我们想要在基于 Objective - C 的 iOS 项目中整合 Unity3D 的游戏内容,或者利用 Unity3D 的强大功能为 iOS 应用增添交互性时,就需要掌握两者之间的交互技术。

1.1 交互的基本原理

Objective - C 与 Unity3D 之间的交互基于消息传递机制。在 Unity3D 中,可以通过特定的 API 注册方法,使得这些方法能够被 Objective - C 代码调用。同样,Objective - C 也可以通过回调等方式将信息传递给 Unity3D。

从底层实现来看,Unity3D 运行在一个独立的进程空间中(在 iOS 应用内)。Objective - C 通过调用 Unity3D 暴露的接口,实现对 Unity3D 场景、对象以及脚本的操作。而 Unity3D 则通过监听特定的消息,来接收来自 Objective - C 的数据并作出相应处理。

1.2 环境搭建

首先,确保你已经安装了最新版本的 Xcode,因为它是进行 Objective - C 开发的主要工具。同时,下载并安装 Unity3D 编辑器,建议使用较新的稳定版本,以获取更好的兼容性和功能支持。

在 Unity3D 项目设置中,将目标平台设置为 iOS。在 Xcode 项目中,需要将 Unity3D 生成的 Xcode 工程文件导入。通常,Unity3D 在导出 iOS 项目时,会生成一个包含 Unity 相关代码和资源的 Xcode 工程。

二、从 Objective - C 调用 Unity3D 方法

2.1 UnitySendMessage 函数

Unity3D 提供了 UnitySendMessage 函数,用于从外部(如 Objective - C)向 Unity3D 中的对象发送消息。该函数的定义如下:

void UnitySendMessage(const char* objectName, const char* methodName, const char* param);
  • objectName:Unity3D 场景中目标对象的名称。
  • methodName:目标对象上要调用的方法名称。
  • param:传递给该方法的字符串参数,如果不需要传递参数,可以传入空字符串 ""

在 Unity3D 脚本中,首先要确保目标对象上存在要调用的方法。例如,在 C# 脚本中:

using UnityEngine;

public class MessageReceiver : MonoBehaviour
{
    public void ReceiveMessageFromObjectiveC(string param)
    {
        Debug.Log("Received message from Objective - C: " + param);
    }
}

在 Objective - C 代码中调用该方法:

#import <UIKit/UIKit.h>
// 假设 Unity 相关头文件路径已正确设置
#import "UnityAppController.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    const char* objectName = "MessageReceiverObject";
    const char* methodName = "ReceiveMessageFromObjectiveC";
    const char* param = "Hello from Objective - C";
    
    UnitySendMessage(objectName, methodName, param);
}

@end

上述代码中,在 Unity3D 场景中有一个名为 MessageReceiverObject 的对象,挂载了 MessageReceiver 脚本。Objective - C 通过 UnitySendMessage 函数向该对象发送消息,调用 ReceiveMessageFromObjectiveC 方法,并传递参数。

2.2 UnitySendMessage 函数的局限性及替代方案

虽然 UnitySendMessage 函数简单易用,但它存在一些局限性。例如,该函数只能传递字符串类型的参数,对于复杂的数据结构(如数组、自定义对象等)传递不便。而且,它基于字符串匹配方法名,在编译期无法检查方法是否存在,容易导致运行时错误。

为了解决这些问题,可以使用 UnitySendMessageEx 函数(如果 Unity 版本支持),或者通过定义接口类来实现更类型安全的调用。

定义接口类的方式如下: 在 Unity3D 中,创建一个 C# 接口:

using UnityEngine;

public interface IObjectiveCCallable
{
    void CallFromObjectiveC(int data);
}

然后让具体的脚本实现该接口:

using UnityEngine;

public class InterfaceImplementor : MonoBehaviour, IObjectiveCCallable
{
    public void CallFromObjectiveC(int data)
    {
        Debug.Log("Received data from Objective - C: " + data);
    }
}

在 Objective - C 中,通过反射机制找到实现该接口的对象并调用方法:

#import <UIKit/UIKit.h>
#import "UnityAppController.h"

// 假设 Unity 提供了获取实现特定接口对象的方法
id GetObjectImplementingInterface(const char* interfaceName);

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    id unityObject = GetObjectImplementingInterface("IObjectiveCCallable");
    
    if (unityObject) {
        // 假设 Unity 提供了调用特定方法的函数
        UnityCallMethod(unityObject, "CallFromObjectiveC", 123);
    }
}

@end

这种方式在编译期可以进行类型检查,提高了代码的健壮性,并且可以传递更多类型的数据。

三、从 Unity3D 调用 Objective - C 方法

3.1 使用插件

在 Unity3D 中调用 Objective - C 方法,通常需要创建一个 iOS 插件。首先,在 Xcode 中创建一个新的 iOS 静态库项目。

假设创建了一个名为 ObjectiveCPlugin 的静态库项目,在其中定义一个要被 Unity3D 调用的方法:

#import <Foundation/Foundation.h>

@interface ObjectiveCPlugin : NSObject

+ (NSString*)getMessageFromObjectiveC;

@end

@implementation ObjectiveCPlugin

+ (NSString*)getMessageFromObjectiveC {
    return @"Hello from Objective - C plugin";
}

@end

编译该静态库项目,生成 .a 文件。然后在 Unity3D 项目中创建一个 Plugins/iOS 文件夹(如果不存在),将生成的 .a 文件以及头文件 ObjectiveCPlugin.h 放入该文件夹。

在 Unity3D 的 C# 脚本中调用该方法:

using UnityEngine;
using System.Runtime.InteropServices;

public class CallObjectiveC : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern string ObjectiveCPlugin_getMessageFromObjectiveC();

    void Start()
    {
        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            string message = ObjectiveCPlugin_getMessageFromObjectiveC();
            Debug.Log("Message from Objective - C: " + message);
        }
    }
}

上述代码中,通过 DllImport 特性导入 Objective - C 方法,在 iOS 平台下调用该方法获取消息。

3.2 回调机制

除了直接调用插件方法,还可以通过回调机制实现 Unity3D 与 Objective - C 的交互。在 Objective - C 中,定义一个回调函数类型:

typedef void (^UnityCallback)(NSString* message);

然后在 Objective - C 类中添加一个接受回调的方法:

#import <Foundation/Foundation.h>

@interface CallbackHandler : NSObject

+ (void)registerCallback:(UnityCallback)callback;

@end

@implementation CallbackHandler

static UnityCallback currentCallback;

+ (void)registerCallback:(UnityCallback)callback {
    currentCallback = callback;
}

// 模拟某个事件触发回调
+ (void)simulateEvent {
    if (currentCallback) {
        currentCallback(@"Event occurred in Objective - C");
    }
}

@end

在 Unity3D 中,通过 C# 脚本来注册回调并处理结果:

using UnityEngine;
using System;
using System.Runtime.InteropServices;

public class CallbackReceiver : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void CallbackHandler_registerCallback(Action<string> callback);

    [DllImport("__Internal")]
    private static extern void CallbackHandler_simulateEvent();

    void Start()
    {
        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            Action<string> callback = (message) =>
            {
                Debug.Log("Received callback from Objective - C: " + message);
            };

            CallbackHandler_registerCallback(callback);
            CallbackHandler_simulateEvent();
        }
    }
}

这种回调机制在处理异步事件或实时响应 Objective - C 中的状态变化时非常有用。

四、数据传递与类型转换

4.1 基本数据类型传递

在 Objective - C 与 Unity3D 交互过程中,基本数据类型的传递相对简单。如前面例子中,传递整数、字符串等类型的数据都有涉及。

对于整数类型,在 Objective - C 中是 NSIntegerint 等,在 Unity3D 的 C# 中是 int。例如,从 Objective - C 传递一个整数到 Unity3D: Objective - C 代码:

#import <UIKit/UIKit.h>
#import "UnityAppController.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    const char* objectName = "DataReceiverObject";
    const char* methodName = "ReceiveInteger";
    int data = 42;
    char param[20];
    sprintf(param, "%d", data);
    
    UnitySendMessage(objectName, methodName, param);
}

@end

Unity3D C# 脚本:

using UnityEngine;

public class DataReceiver : MonoBehaviour
{
    public void ReceiveInteger(string param)
    {
        int data = int.Parse(param);
        Debug.Log("Received integer from Objective - C: " + data);
    }
}

4.2 复杂数据类型传递

传递复杂数据类型,如数组、结构体等,相对复杂一些。以传递数组为例: 在 Objective - C 中创建一个数组并传递:

#import <UIKit/UIKit.h>
#import "UnityAppController.h"

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    const char* objectName = "ArrayReceiverObject";
    const char* methodName = "ReceiveArray";
    
    NSArray* array = @[@1, @2, @3];
    NSMutableString* param = [NSMutableString string];
    for (NSNumber* number in array) {
        [param appendFormat:@"%ld,", (long)number.integerValue];
    }
    if ([param length] > 0) {
        [param deleteCharactersInRange:NSMakeRange([param length] - 1, 1)];
    }
    
    UnitySendMessage(objectName, methodName, [param UTF8String]);
}

@end

在 Unity3D 中接收并解析数组:

using UnityEngine;
using System;

public class ArrayReceiver : MonoBehaviour
{
    public void ReceiveArray(string param)
    {
        string[] parts = param.Split(',');
        int[] array = new int[parts.Length];
        for (int i = 0; i < parts.Length; i++)
        {
            array[i] = int.Parse(parts[i]);
        }
        Debug.Log("Received array from Objective - C: " + string.Join(",", array));
    }
}

对于结构体,需要在 Objective - C 和 Unity3D 两边定义相同结构的结构体,并且在传递时进行序列化和反序列化操作。例如: Objective - C 结构体定义:

typedef struct {
    int id;
    NSString* name;
} User;

在传递结构体时,需要将其成员数据转换为字符串形式传递。在 Unity3D 中,同样定义类似的结构体,并根据接收到的字符串数据进行反序列化。

五、性能优化与注意事项

5.1 性能优化

  • 减少频繁交互:频繁地在 Objective - C 和 Unity3D 之间传递消息会带来性能开销,尽量批量处理数据传递,避免在循环中多次调用交互方法。
  • 优化数据序列化与反序列化:对于复杂数据类型的传递,优化序列化和反序列化算法,减少 CPU 开销。例如,使用更高效的字符串解析方法,或者采用二进制序列化方式。
  • 缓存对象引用:在 Unity3D 中,如果多次从 Objective - C 调用同一个对象的方法,可以在 Unity3D 脚本中缓存该对象的引用,避免每次都通过名称查找对象。

5.2 注意事项

  • 内存管理:在跨平台交互时,要注意内存管理。例如,在 Objective - C 中创建的对象,如果传递给 Unity3D,要确保在适当的时候释放内存,避免内存泄漏。同样,Unity3D 中分配的资源,如果传递给 Objective - C,也要合理管理。
  • 版本兼容性:Unity3D 和 Xcode 的版本不断更新,要注意不同版本之间交互接口的变化。在升级版本时,仔细检查文档和代码,确保兼容性。
  • 错误处理:在交互过程中,添加完善的错误处理机制。例如,在调用 UnitySendMessage 时,如果目标对象或方法不存在,应该进行适当的错误提示,避免程序崩溃。

通过以上对 Objective - C 与 Unity3D 引擎交互技术的详细解析,包括交互基础、方法调用、数据传递、性能优化及注意事项等方面,开发者可以更深入地掌握两者之间的交互技巧,开发出更强大、高效的 iOS 应用或游戏。在实际项目中,根据具体需求灵活运用这些技术,能够实现丰富的功能和良好的用户体验。