engo


Tutorial
Mobile

The engo game engine, and Go in general, has two ways to build for a mobile device: binding or building.

First, you’ll need to install Go Mobile.

Using gomobile build

To use gomobile build, simply go to the folder that you have your main package and run

gomobile build

A few caveats:

Using gomobile bind

Using bindings allows access to and from the Android system, and you can just build into your glue whatever functions you need to be called or to call. However, it is more complicated to initially setup. This is taken from here if you want to look at the code while reading this.

Setting up your game library

First thing to notice is the file game.go here. It’s an (almost) exact copy of main.go, except it’s a library instead of a main package. You’ll need to add

//+build !mobilebind

to the top (before package declaration) of your main, and then

//+build mobilebind

to the top of the library. The other differences are that when loading a file, you can’t keep assets in an asset folder for binding, so instead you’ll need to use go-bindata to turn your assets into binary data. Then to load the files use

	b, err := assets.Asset("icon.png")
	if err != nil {
		log.Panic("no such icon")
	}
	engo.Files.LoadReaderData("icon.png", bytes.NewReader(b))

and when you’re starting a game, your main becomes a start function that can be called from the androidglue package

func Start(width, height int) {
	opts := engo.RunOptions{
		Title:        "Mouse Demo",
		Width:        1024,
		Height:       640,
		MobileWidth:  width,
		MobileHeight: height,
	}
	engo.Run(opts, &DefaultScene{})
}

The additional options, as well as the width and height being passed in, are for Android to tell your game the actual dimensions of the screen.

Setting up the android glue

Next, you’ll need a package that handles communication between your Go library and Android. That’s where the package androidglue comes in! androidglue.go just contains exported functions that Android can call to communicate with your game, such as tell it when to start, stop, or when touch events occur. You could potentially add more to this, for handling when to trigger a handler for the Facebook SDK or to preload an intersitial ad.

//+build mobilebind

package androidglue

import (
	"github.com/Noofbiz/mousetests"

	"github.com/EngoEngine/engo"
)

var running bool

func Start(width, height int) {
	running = true
	mousetests.Start(width, height)
}

func Update() {
	engo.RunIteration()
}

func IsRunning() bool {
	return running
}

func Touch(x, y, action int) {
	engo.TouchEvent(x, y, action)
}

func Stop() {
	running = false
	engo.MobileStop()
}

And, that’s it for the Go side of things. All that’s left for Go is to use (inside the androidglue folder)

gomobile bind -target=android -tags=mobilebind

to compile the .aar library to import into Android Studio.

Setting up Android side

To setup the Android side, I used Android Studio. There’s also a way to do it via gradle and the command line, but I don’t know the exact details on doing that at the moment.

Start by creating a new project in Android Studio. Phone and Tablet with minimum SDK set to 15 and with no starting activity. In the AndroidManifest.xml, add a main activity. Mine ends up looking like this

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="io.engo.mousetests">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

and then to styles.xml I added the following to give the full screen to the gl surface view

		<item name="windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowContentOverlay">@null</item>

Next we have to add a main activity, by creating a MainActivity.java file.

package io.engo.mousetests;

import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    private GLSurfaceView glSurfaceView() {
        return (GLSurfaceView)this.findViewById(R.id.glview);
    }

    @Override
    protected void onPause() {
        super.onPause();
        this.glSurfaceView().onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        this.glSurfaceView().onResume();
    }
}

as well as a style xml for it. Here’s activity_main.xml in the layout folder

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="io.engo.mousetests.MainActivity"
    android:keepScreenOn="true" >
    <io.engo.mousetests.EngoGLSurfaceView
        android:id="@+id/glview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />
</RelativeLayout>

and finally, create the glSurfaceView. EngoGLSurfaceView.java

package io.engo.mousetests;

import android.content.Context;
import android.content.res.Resources;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.util.AttributeSet;
import android.view.MotionEvent;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import androidglue.*;

public class EngoGLSurfaceView extends GLSurfaceView {

    private class EngoRenderer implements Renderer {

        private boolean mErrored;

        @Override
        public void onDrawFrame(GL10 gl) {
            if (mErrored) {
                return;
            }
            try {
                Androidglue.update();
            } catch (Exception e) {
                Log.e("Go Error", e.toString());
                mErrored = true;
            }
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
        }
    }

    public EngoGLSurfaceView(Context context) {
        super(context);
        initialize();
    }

    public EngoGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    private void initialize() {
        setEGLContextClientVersion(2);
        setEGLConfigChooser(8, 8, 8, 8, 0, 0);
        setRenderer(new EngoRenderer());
    }

    @Override
    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        try {
            if (!Androidglue.isRunning()) {
                Androidglue.start(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
            }
        } catch (Exception e) {
            Log.e("Go Error", e.toString());
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        for (int i = 0; i < e.getPointerCount(); i++) {
            Androidglue.touch((int)e.getX(i), (int)e.getY(i), e.getActionMasked());
        }
        return true;
    }

    @Override
    public void onPause() {
        try {
            if (Androidglue.isRunning()) {
                Androidglue.stop();
            }
        } catch (Exception e) {
            Log.e("Go Error", e.toString());
        }
    }
}

Now the last part is to import the aar created by gomobile bind

File -> New -> New Module…

Pick Import .JAR/.AAR Package

Navigate to the .AAR package you created and import it

Then add it as a dependency to your android project

File -> Project Structure -> app -> on the dependencies tab

Then click the +, and under module dependencies you’ll find your imported androidglue library. Everything should build nicely and you can then run it in an emulator or a device!