Ebitengine is a Go game development engine. Games can be built for Linux, MacOS, Windows, Android, iOS and Nintendo Switch.

I did some basic experiments during a few vacation days. This post is about something which wasn’t obvious to me how to implement and took me some hours to figure out. Hopefully it may spare you some time.

Under the hood Ebitengine uses gomobile to create bindings for languages like Java and C/C++.

This article is especially interesting if you need to call Go functions from Java or C/C++ and vice-versa.

What is the problem

I make a Go game which runs on Android, but I’d like to show Ads using 3rd party SDK like Unity or Facebook Audience Network. This means I’ll have to integrate a Java or ObjC library in the native app. For example, on Android it could be implemented in the showAds method below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

import go.Seq;
import com.mycompany.mygame.mobile.EbitenView;

public class MainActivity extends AppCompatActivity {
    protected EbitenView gameView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Seq.setContext(getApplicationContext());

        gameView = new EbitenView(getApplicationContext());
        setContentView(gameView);
    }

    @Override
    protected void onPause() {
        super.onPause();
        gameView.suspendGame();
    }

    @Override
    protected void onResume() {
        super.onResume();
        gameView.resumeGame();
    }

    public void showAds() {
    	...
    }
}

The problem is that MainActivity doesn’t know when to call show ads, as it doesn’t know anything about the internal state of the game - game updates and state changes happen in the Go code. So, at any point in time, only the Go side knows if it’s time to show an advert. Now the problem comes down to how the Go code can call the showAds() Java method?

Solution

My current solution is presented below and it works. I don’t feel it optimal since it requires to keep a global variable reference to the game object, but for now I haven’t found an improvement (and I don’t know if it’s an issue at all).

It shows how to call Go functions from Java as well as Java functions from Go.

Go

First, we must tell the Go side what function it should call to show ads, as it doesn’t know anything about the existence of the MainActivity.showAds method. This means I must call a Go function from Java to register the callback.

To do that I define an exported Go function in the mobile.go file which will be used to produce the bindings. The function will be exported as static class method on the Java side.

I also define an interface for the object that I expect to receive as an argument. The object must implement a method called showAds and will be implemented on the Java side. The exported function RegisterAdsCallback is used from Java to give this object to the Go side.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package mobile

import (
    ...
)

var g *game.Game

func init() {
    var err error
    g, err = game.New()
    if err != nil {
        panic(err)
    }
    mobile.SetGame(g)
}

type AdsCallback interface {
    ShowAds()
}

func RegisterAdsCallback(callback AdsCallback) {
    g.RegisterAdsCallback(func() { callback.ShowAds() })
}

Now when I produce the Java library containing the game with ebitenmobile, the class Mobile will have this RegisterAdsCallback static method, which can be used from Java to register the showAds method.

1
ebitenmobile bind -target android -androidapi 19 -javapkg com.mycompany.mygame -o /path/to/androidapp/libs/mygame.aar /path/to/mobile.go

Java

In Java we implement the callback and give it to the Go side.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.mycompany.mygame.mobile.AdsCallback;
import com.mycompany.mygame.mobile.EbitenView;
import com.mycompany.mygame.mobile.Mobile;

public class MainActivity extends AppCompatActivity {
    EbitenView gameView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Seq.setContext(getApplicationContext());

        Mobile.registerAdsCallback(new AdsCallback() {
            @Override
            public void showAds() {
                Log.d("mygame", "show ads");
            }
        });

        gameView = new EbitenView(getApplicationContext());
        setContentView(gameView);
    }

    ...
}

Usage in Go

Whenever the Go game decides that it’s time to show ads, it can invoke game.showAds():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package game

type Game struct {
    adsCallback func()
}

func New() (*Game, error) {
    return &Game{}, nil
}

func (g *Game) Update() error {
    ...
}

func (g *Game) Draw(screen *ebiten.Image) {
    ...
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    ...
}

func (g *Game) RegisterAdsCallback(callback func()) {
    g.adsCallback = callback
}

func (g *Game) showAds() {
    g.adsCallback()
}