ntop
Posted on September 6, 2018
These days, I'm porting our Android game Shoot Stack to iOS. It's a game written in Golang with our own game engine. This article will give some tips and our experiences in poring Golang game to iOS. If you have ever used the GoMobile you know that gomobile build
can build .app
and install directly to an iOS device. But if you want to integrate third-party SDK, like ads, analytics, it's impossible with gomobile build
. We need a way to build library, then we can use it in xcode and gomobile bind
just do this.
Here is our project structure:
✗ tree .
.
├── arena.go
├── audio.go
├── build.md
├── color.go
├── file.go
├── flash_message.go
├── gameover.go
├── input_glfw.go
├── input_mob.go
├── main.go
├── particle.go
├── ready.go
└── welcome.go
yeah, it's all in a main package. Using gomobile bind
command, we can build .framework
file for xcode. But we still need a way to invoke the main
method of our lib. All xcode project has a main.h
file:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
So we can invoke our lib's main method here. As I said above, all my code stay in a main
package, but GoMobile cann't bind file in main
(it's just not supported). So I do have to change a little to my project:
✗ tree .
.
├── ios
│ └── ios.go
└── main
├── game
│ ├── arena.go
│ ├── audio.go
│ ├── build.md
│ ├── color.go
│ ├── file.go
│ ├── flash_message.go
│ ├── gameover.go
│ ├── input_glfw.go
│ ├── input_mob.go
│ ├── main.go
│ ├── particle.go
│ ├── ready.go
│ └── welcome.go
└── main.go
I put the source code in main/game
package, then I can still use gomobile build
to build Desktop and Android. Then I created ios/ios.go
file, it's not a main file, so I can build it with gomobile bind
, the content:
package ios
import (
"korok.io/korok/gfx/dbg"
"korok.io/korok"
"korok.io/korok/math/f32"
"main/game"
)
func Run() {
dbg.DEBUG = dbg.None
option := korok.Options{
Width:360,
Height:640,
Clear:f32.Vec4{0,0,0,1},
}
korok.Run(&option, &game.StartScene{})
}
The method Run
is same as the main.main()
:
func main() {
dbg.DEBUG = dbg.None
option := korok.Options{
Width:360,
Height:640,
Clear:f32.Vec4{0,0,0,1},
}
korok.Run(&option, &StartScene{})
}
It's just created to easy the bind process. Now, we can use gomobile bind -target=ios ios
to build a .framework
. If all is OK, next, we can integrate the .framework
to our xcode project(just drag it to xcode). Modify the main.h
file as following:
#import "Ios/Ios.h"
int main(int argc, char * argv[]) {
IosRun();
}
Build & Run, now the project (with Go inside) works.
How to add ads SDK?
Now, we have successfully created a library and used it in xcode project. If we want to integrate Ad SDK, we still need add some lifecycle hook in the original .m
file used by GoMobile. In the package x/mobile/app
, we can find a file darwin_ios.m
, this is the file GoMobile used to build iOS application, there are two @interface
defined here:
@interface GoAppAppController : GLKViewController<UIContentContainer, GLKViewDelegate>
@end
@interface GoAppAppDelegate : UIResponder<UIApplicationDelegate>
@end
I will hook the lifecycle method, so that I can use it in the xcode project:
@implementation GoAppAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
[self kkDidFinishLaunching];
return YES;
}
- (void)viewDidLoad {
...
[self kkViewDidLoad];
}
Then create a file (ad.m
) in the xcode project, add the following code:
@implementation GoAppAppDelegate(AD)
-(void)kkDidFinishLaunching {
NSLog(@"hi, implemenmt method");
}
@end
@interface GoAppAppController(AD)
@end
@implementation GoAppAppController(AD)
- (void)kkViewDidLoad {
NSLog(@"view did load2..");
@end
Here we used the Category in Obj-C language. It'll implement the method we used in 'darwin_ios.h'.
Build & Run, you should see the log info printed in Console.
Here is a snapshot of our game (with ad, but I'll remove it when release):
Other problem
Life is not easy, in fact, it takes days to build a actual game and run it on my iPhone6. There is a problem I just don't know how to solve it, the problem is when the Application suspended and reactive to the foreground the App will freeze. I have tested the basic example provide by GoMobile, it also have the problem, it's bug. In the basic example, it uses switch e.Crosses(lifecycle. StageVisible)
to manage app lifecycle, if you change the lifecycle. StageVisible
to lifecycle. StageAlive
, it'll freeze.
I have digged into the problem for serval days. It seems that GoMobile use a custom loop to draw GL command, and call:
[EAGLContext setCurrentContext:ctx];
[ctx presentRenderbuffer:GL_RENDERBUFFER];
to swap buffer, but this code will fail if the app suspended and activated again. I tried serval ways to solve the problem, but it's not that easy as I thought, at last, I given up and created an issue in Github: application freeze after resumed from suspend state on iOS . But I still need a way to work around the bug, after some research in the git commit, I found that the old implementation --- just use the system loop instead of Go loop. So I rollback to the old implementation:
self.glview.enableSetNeedsDisplay = NO;
CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- (void)render:(CADisplayLink*)displayLink {
[self.glview display];
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
drawloop();
}
//export drawloop
func drawloop() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
for workAvailable := theApp.worker.WorkAvailable();;{
select {
case <-workAvailable:
theApp.worker.DoWork()
case <-theApp.publish:
theApp.publishResult <- PublishResult{}
return
case <-time.After(50 * time.Millisecond): // incase the method blocked!!
return
}
}
}
I also found the same(mostly) implementation in Cocos2DX and Ebiten. Finally, all the problem solved!! I'm uploading the Game to Apple Store Connect, you can download it in several days or weeks(you know AppStore review is not easy, too).
Posted on September 6, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.