A "simple" step-by-step tutorial on how to make a bridge between Unity's C# and Xcode's Objective-C. Either way.
- First set up a new scene in Unity and add a plane with a simple sphere above it. So we can interact with it later.
- Make sure the purple
Sphere
has aRigidBody
andUse Gravity
is enabled.
π‘ You can add some materials to it, so it stands out. And maybe move the
Sphere
a bit up (on the y-axis).
If you run the scene the purple sphere would drop on the white plane.
Next up we're going to add a GUI button to send information to Objective-c
- Add a
UI -> Button
- It should be aligned to the left-bottom
- Rename the button's text to your liking.
- Next stop, create a new C# Script, name it
BehaviourScript
or anything to your liking
Open the script and use this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;
public class BehaviourScript : MonoBehaviour {
public GameObject ball;
public Button button;
[DllImport ("__Internal")]
private static extern void calledFromUnity();
void Start () {
button.onClick.AddListener (Tap);
}
void Tap() {
calledFromUnity();
}
void Bounce(string direction) {
ball.GetComponent<Rigidbody>().AddForce(transform.up * 300);
}
}
Code blocks explained:
using System.Runtime.InteropServices;
Used for external communication.
[DllImport ("__Internal")]
private static extern void calledFromUnity();
This creates a reference to the c function calledFromUnity()
, which will be implemented in your Xcode project later on.
void Start () {
button.onClick.AddListener (Tap);
}
void Tap() {
calledFromUnity();
}
Would listen for a click event on the button
. When a click occurs the c-function calledFromUnity()
will be called and invoked in (Obj-)C.
void Bounce(string direction) {
ball.GetComponent<Rigidbody>().AddForce(transform.up * 300);
}
This would make the ball bounce. Bounce(string)
will be called from Obj-c / Xcode.
Now go back into Unity and add the BehaviourScript
to your plane and connect Button
and GameObject
.
At this stage you would have a button which would do nothing (yet) and have a function which isn't called (yet).
- Create a new folder called
Plugin
in your project - Create a new folder called
iOS
inPlugin
- Create a new file called
plugin.mm
in thatiOS
folder
β οΈ It's important to add these files to this specific folder. Adding them from Xcode would overwrite theplugin.mm
once you (re)build the Unity project.
plugin.mm
should contain the following code:
extern "C" {
void calledFromUnity() {
[[NSNotificationCenter defaultCenter] postNotificationName:@"UnityNotification" object:nil];
}
}
This is where the calledFromUnity()
function we declared earlier would return. We make the default NSNotificationCenter
post a notification with the name "UnityNotification"
, this way we can intercept it in our own UIViewController
later on.
π‘ There are a few options regarding dispatching methods from C to Objective-C's main view, but for now we'll stick to this.
- Open
Build Settings
:File -> Build Settings
or pressβ§βB
. - Use the following settings:
- Press
Build
- Wait ...
- After a while your Xcode project should be created
- Open
Unity-iPhone.xcodeproj
- Create a new Objective-C class in the
Classes
folder nameAppController
.
π‘ Make sure you have a
.mm
extension for the implementation file and it's linked to theUnity-iPhone
target.
- Make sure
AppController
extendsUnityAppController
The two files will look like this:
// .mm
#import "AppController.h"
@implementation AppController {
UIButton *btn;
}
- (void)startUnity:(UIApplication *)application {
[super startUnity:application];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationFired:) name:@"UnityNotification" object:nil];
UIViewController *vc = [UIViewController new];
[vc.view setFrame:self.window.bounds];
[vc.view addSubview:self.rootViewController.view];
[vc addChildViewController:self.rootViewController];
[self.window setRootViewController:vc];
btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:@"To Unity" forState:UIControlStateNormal];
[btn setBackgroundColor:[UIColor whiteColor]];
[btn setTintColor:[UIColor orangeColor]];
[btn setFrame:CGRectMake(10, 10, 100, 50)];
[btn addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:btn];
}
- (void)tapButton:(UIButton *)sender {
UnitySendMessage("Plane", "Bounce", "Up");
}
- (void)notificationFired:(NSNotification *)notification {
NSString *originalTitle = [btn titleForState: UIControlStateNormal];
[btn setTitle:@"π" forState:UIControlStateNormal];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[btn setTitle:originalTitle forState:UIControlStateNormal];
});
}
@end
Lets have a look at each code blocks:
@implementation AppController {
UIButton *btn;
}
A btn
is declared so we can use it among the whole class instance.
- (void)startUnity:(UIApplication *)application {
[super startUnity:application];
Since AppController
extends UnityAppController
and startUnity:
is called when the Unity view is loaded, this method will be invoked once the Unity controller is started.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationFired:) name:@"UnityNotification" object:nil];
- (void)notificationFired:(NSNotification *)notification {
NSString *originalTitle = [btn titleForState: UIControlStateNormal];
[btn setTitle:@"π" forState:UIControlStateNormal];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[btn setTitle:originalTitle forState:UIControlStateNormal];
});
}
Listen for the "UnityNotification"
notification to be fired and call the notificationFired:
method.
notificationFired:
will change the title of the button and after 1 second set it back to the original one.
UIViewController *vc = [UIViewController new];
[vc.view setFrame:self.window.bounds];
[vc.view addSubview:self.rootViewController.view];
[vc addChildViewController:self.rootViewController];
[self.window setRootViewController:vc];
This would create a new UIViewController
and places the original unity viewcontroller in it. This way we can add our own UI on top of Unity.
btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:@"To Unity" forState:UIControlStateNormal];
[btn setBackgroundColor:[UIColor whiteColor]];
[btn setTintColor:[UIColor orangeColor]];
[btn setFrame:CGRectMake(10, 10, 100, 50)];
[btn addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:btn];
Creates the actual button and places it on the newly created UIViewController
- (void)tapButton:(UIButton *)sender {
UnitySendMessage("Plane", "Bounce", "Up");
}
The button tap will invoke tapButton:
and will fire UnitySendMessage
with 3 parameters:
"Plane"
: The name of theGameObject
in Unity"Bounce"
: The name of the function in theBehaviourScript.cs
Unity C# Script."Up"
: The parameter to be used in theBounce(string)
function. (For now this hasn't any effect, you can place any string in it)
- Open
main.mm
(also in the Classes folder) and change this line:
const char* AppControllerClassName = "UnityAppController";
to
const char* AppControllerClassName = "AppController";
This way it will load the AppController
class instead of the UnityAppController
class.