help on standalone game

A project to port LÖVE to Android handhelds
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

help on standalone game

Post by dudeabot »

well i have read a lot of threads.

first, id ilke to say im studying ni and might be able to help, but i would like to ask first. is the problem of standalone apk resolved? if not i might have a look at it, i just compiled the ndk sources, and love looks like such a cool engine that i have been wanting to mess with.

just bare with me since i am veeeryyyyy rusty with jni and NDK =)

thanks!
User avatar
T-Bone
Inner party member
Posts: 1492
Joined: Thu Jun 09, 2011 9:03 am

Re: help on standalone game

Post by T-Bone »

I don't think anyone has actually managed to get a stand alone apk yet. But it is theoretically possible. Maybe not in this version though.
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

Re: help on standalone game

Post by dudeabot »

ok i compiled my version, unfortunately it doesnt run, im using android-ndk-r8b and android sdk 4.0.3(im having exactly this error)

http://stackoverflow.com/questions/1217 ... ad-library

well anyway ill outline my path so anyone who can compile can test if it works or not:

1)find apk path on java:

Code: Select all

	String retrieveApkPath() {
		String apkFilePath = null;
		ApplicationInfo appInfo = null;
		PackageManager packMgmr = getPackageManager();
		try {
			appInfo = packMgmr.getApplicationInfo(getPackageName(), 0);
		} catch (NameNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException("Unable to locate assets, aborting...");
		}
		apkFilePath = appInfo.sourceDir;
		return (apkFilePath);
	}
2)PHYSFS_AddToSearchPath(apkPath, 1);i think this is done on lua level, especifically on boot.lua right where it boots the game source.. so you should access the filesystem from there and add the apkpath

from there on its just a matter of loading the .love file (hopefullly)

for example, create a folder assets and put your .love file there lets game.love, step 2) should make it visible to physfs so yuo can replace the boot code to load game.love instead of the argument
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

Re: help on standalone game

Post by dudeabot »

here is my progress so far

Code: Select all


love.filesystem.addToSearchPath(apkDir); -- apkDir sent via Java code, i also added addToSearchPath to lua-glue code.
print(love.filesystem.exists("/assets/game.love")) -- works :D, if you have copied game.love to assets folder
the problem is that its a zip inside another zip, and even worse, assets is a folder, so finding main.lua has been a hell for LOVE so far :P


------------------------------------------------------

meanwhile i found this hack that doenst require messing with NDK code, only java:

http://stackoverflow.com/questions/4447 ... -to-sdcard

the idea is to copy the .love game to assets folder then use that code to copy to sdcard and pass its location to JNI. the obvious fallback is that requires sdcard, also duplicates the game in memory..
User avatar
T-Bone
Inner party member
Posts: 1492
Joined: Thu Jun 09, 2011 9:03 am

Re: help on standalone game

Post by T-Bone »

It doesn't require a physical sd card though. Many phones without them still have the folder /mnt/sdcard for compatability. And Environment.getExternalStorageDirectory() should still work either way.
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

Re: help on standalone game

Post by dudeabot »

yes, only older phones would have issues..

mine doesnt have any problem (galaxy s2).. but still pretty hacky isnt it?
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

Re: help on standalone game

Post by dudeabot »

ok i have tested the "hack" and it works

DOWNLOAD: http://gjteam.com.br/love-native-android.apk

here are the files that I changed (i dont have git client here to make patches, if anyone knows a good client for windows..)

make sure you create a folder called "assets" inside the root of java project and copy your game to there, name it "game.love"

Image

AndroidManifest.xml

Code: Select all

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.schattenkind.nativelove"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.DELETE_CACHE_FILES"/>
    <uses-permission android:name="android.permission.READ_FRAME_BUFFER"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
            android:label="@string/app_name"
            android:name="LoveNative"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|fontScale">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

LoveNative.java

Code: Select all

package net.schattenkind.nativelove;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;

import android.app.Activity;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

public class LoveNative extends Activity implements SensorEventListener {
    /** Called when the activity is first created. */
	private LoveRenderView mGLView;
	private float mOldVolume = 1.f;

	//TODO: Add
	/*
	 * getSystemService(Context.SENSOR_SERVICE); and SensorManager
	 * getSystemService(Context.STORAGE_SERVICE); and all file stuff
	 * finish android_sensors in love.cpp (in real love)
	 * */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        copyAssets();
        
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        mSensorList = new LinkedList<Sensor>();

        String filePath = "/mnt/sdcard/game.love";

        //setContentView(R.layout.main);
        LoveJNI.setActivity(this);
        mGLView = new LoveRenderView(this, filePath);
        setContentView(mGLView);
        //LoveJNI.step();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
    	// hardware sound button handling
    	if (keyCode == KeyEvent.KEYCODE_MUTE)
    	{
    		LoveJNI.setDeviceAudioVolume(0f);
    	}
    	if (keyCode == KeyEvent.KEYCODE_VOLUME_UP)
    	{
    		LoveJNI.setDeviceAudioVolume(LoveJNI.getDeviceAudioVolume() + 0.1f);
    	}
    	if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
    	{
    		LoveJNI.setDeviceAudioVolume(LoveJNI.getDeviceAudioVolume() - 0.1f);
    	}

    	//Temporary fix for exiting the application
	//TODO Remove this when implemented into Love.cpp instead
    	if (keyCode == KeyEvent.KEYCODE_BACK) {
    		return super.onKeyDown(keyCode, event);
    	}

        if(LoveJNI.onKeyDown(keyCode))
        	return true;
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event)
    {
    	//Temporary fix for exiting the application
	//TODO Remove this when implemented into Love.cpp instead
    	if (keyCode == KeyEvent.KEYCODE_BACK) {
    		return super.onKeyUp(keyCode, event);
    	}

    	if(LoveJNI.onKeyUp(keyCode))
        	return true;
        return super.onKeyUp(keyCode, event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
    	boolean processed = false;
    	if(event.getAction() == MotionEvent.ACTION_DOWN)
    	{
    		LoveJNI.onMouseDown((int)event.getX(), (int)event.getY());
    		processed = true;
    	}
    	else if(event.getAction() == MotionEvent.ACTION_UP)
    	{
    		LoveJNI.onMouseUp((int)event.getX(), (int)event.getY());
    		processed = true;
    	}
    	else if (event.getAction() == MotionEvent.ACTION_MOVE)
    	{
    		LoveJNI.onMouseMove((int)event.getX(), (int)event.getY());
    		processed = true;
    	}

    	int count = event.getPointerCount();
    	if(count > 0)
    	{
    		int x[] = new int[count];
    		int y[] = new int[count];

    		for(int i = 0; i < count; ++i)
    		{
    			x[i] = (int)event.getX(i);
    			y[i] = (int)event.getY(i);
    		}

    		if(event.getAction() == MotionEvent.ACTION_DOWN)
        	{
    			if(Build.VERSION.SDK_INT >= 8)
    				LoveJNI.onTouchDown(count, event.getActionIndex(), x, y);
    			else
    				LoveJNI.onTouchDown(count, -1, x, y);
    			processed = true;
        	}
        	else if(event.getAction() == MotionEvent.ACTION_UP)
        	{
        		if(Build.VERSION.SDK_INT >= 8)
        			LoveJNI.onTouchUp(count, event.getActionIndex(), x, y);
        		else
        			LoveJNI.onTouchUp(count, -1, x, y);
        		processed = true;
        	}
        	else if (event.getAction() == MotionEvent.ACTION_MOVE)
        	{
        		if(Build.VERSION.SDK_INT >= 8)
        			LoveJNI.onTouchMove(count, event.getActionIndex(), x, y);
        		else
        			LoveJNI.onTouchMove(count, -1, x, y);
        		processed = true;
        	}
    	}
    	if(processed)
    		return true;
    	return super.onTouchEvent(event);
    }

    @Override
    public void onBackPressed()
    {
        super.onBackPressed();
//        LoveJNI.deinit();
        mGLView.onPause();
    }

    @Override
    public boolean onKeyLongPress(int keyCode, KeyEvent event)
    {
        // TODO Auto-generated method stub
        return super.onKeyLongPress(keyCode, event);
    }

    // clean up love at onPause call - native memory is not protected afterwards
    // we need a solution to store the game state at that moment
    @Override
    public void onPause()
    {
    	//mGLView.onPause(); // TODO: FIX OpenGL cleanup
    	mGLView.stopRendering();
    	mOldVolume = LoveJNI.getDeviceAudioVolume();
    	LoveJNI.setDeviceAudioVolume(0.f);
    	mSensorManager.unregisterListener(this);
    	super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mGLView.continueRendering();
        LoveJNI.setDeviceAudioVolume(mOldVolume);
        for(int i = 0; i < mSensorList.size(); ++i)
        	mSensorManager.registerListener(this, mSensorList.get(i), SensorManager.SENSOR_DELAY_GAME);
        //mGLView.onResume(); // TODO: FIX OpenGL cleanup
    }


    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
    	super.onConfigurationChanged(newConfig);
    }

    @Override
    public void onDestroy()
    {
    	Log.i("LoveNative", "onDestroy");
    	LoveJNI.deinit();
    	super.onDestroy();
    }

    private int strToSensor(String name)
    {
    	if(name.equals("TYPE_ACCELEROMETER"))
    		return Sensor.TYPE_ACCELEROMETER;
    	else if(name.equals("TYPE_ALL"))
    		return Sensor.TYPE_ALL;
    	else if(Build.VERSION.SDK_INT >= 14 && name.equals("TYPE_AMBIENT_TEMPERATURE "))
    		return 0xd; //Sensor.TYPE_AMBIENT_TEMPERATURE
    	else if(Build.VERSION.SDK_INT >= 9 && name.equals("TYPE_GRAVITY"))
    		return 0x9; //Sensor.TYPE_GRAVITY
    	else if(name.equals("TYPE_GYROSCOPE"))
    		return Sensor.TYPE_GYROSCOPE;
    	else if(name.equals("TYPE_LIGHT"))
    		return Sensor.TYPE_LIGHT;
    	else if(Build.VERSION.SDK_INT >= 9 && name.equals("TYPE_LINEAR_ACCELERATION"))
    		return 0xa; //Sensor.TYPE_LINEAR_ACCELERATION
    	else if(name.equals("TYPE_MAGNETIC_FIELD"))
    		return Sensor.TYPE_MAGNETIC_FIELD;
    	else if(name.equals("TYPE_ORIENTATION"))
    		return Sensor.TYPE_ORIENTATION;
    	else if(name.equals("TYPE_PRESSURE"))
    		return Sensor.TYPE_PRESSURE;
    	else if(name.equals("TYPE_PROXIMITY"))
    		return Sensor.TYPE_PROXIMITY;
    	else if(Build.VERSION.SDK_INT >= 14 && name.equals("TYPE_RELATIVE_HUMIDITY"))
    		return 0xc; //Sensor.TYPE_RELATIVE_HUMIDITY
    	else if(Build.VERSION.SDK_INT >= 9 && name.equals(" TYPE_ROTATION_VECTOR"))
    		return 0xb; //Sensor.TYPE_ROTATION_VECTOR
    	else if(name.equals("TYPE_TEMPERATURE"))
    		return Sensor.TYPE_TEMPERATURE ;
    	else
    		return Sensor.TYPE_ALL;
    }

    private String SensorToStr(int type)
    {
    	switch(type)
    	{
    		case Sensor.TYPE_ACCELEROMETER:
    			return "TYPE_ACCELEROMETER";
    		case Sensor.TYPE_ALL:
    			return "TYPE_ALL";
    		case 0xd:
    			return "TYPE_AMBIENT_TEMPERATURE";
    		case 0x9:
    			return "TYPE_GRAVITY";
    		case Sensor.TYPE_GYROSCOPE:
    			return "TYPE_GYROSCOPE";
    		case Sensor.TYPE_LIGHT:
    			return "TYPE_LIGHT";
    		case 0xa:
    			return "TYPE_LINEAR_ACCELERATION";
    		case Sensor.TYPE_MAGNETIC_FIELD:
    			return "TYPE_MAGNETIC_FIELD";
    		case Sensor.TYPE_ORIENTATION:
    			return "TYPE_ORIENTATION";
    		case Sensor.TYPE_PRESSURE:
    			return "TYPE_PRESSURE";
    		case Sensor.TYPE_PROXIMITY:
    			return "TYPE_PROXIMITY";
    		case 0xc:
    			return "TYPE_RELATIVE_HUMIDITY";
    		case 0xb:
    			return "TYPE_ROTATION_VECTOR";
    		case Sensor.TYPE_TEMPERATURE:
    			return "TYPE_TEMPERATURE";
			default:
				return "UNKNOWN";
    	}
    }

    private SensorManager mSensorManager;
    private LinkedList<Sensor> mSensorList;
	public void disableSensor(String name)
    {
		int type = strToSensor(name);
		Sensor sensor = mSensorManager.getDefaultSensor(type);
		mSensorManager.unregisterListener(this, sensor);
		mSensorList.remove(sensor);
    }

	public void enableSensor(String name)
    {
		int type = strToSensor(name);
		Sensor sensor = mSensorManager.getDefaultSensor(type);
		mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
		mSensorList.add(sensor);
    }

	@Override
    public void onAccuracyChanged(Sensor sensor, int accuracy)
    {
	    // should not happen...
		// TODO: check if it does not happen :D

    }

	@Override
    public void onSensorChanged(SensorEvent event)
    {
	    LoveJNI.onSensorChanged(event.sensor.getName(), SensorToStr(event.sensor.getType()), event.values);
    }
	
	private void copyAssets() {
	    AssetManager assetManager = getAssets();
	    String[] files = null;
	    try {
	        files = assetManager.list("");
	    } catch (IOException e) {
	        Log.e("tag", "Failed to get asset file list.", e);
	    }
	    for(String filename : files) {
	        InputStream in = null;
	        OutputStream out = null;
	        try {
	          in = assetManager.open(filename);
	          out = new FileOutputStream("/sdcard/" + filename);
	          copyFile(in, out);
	          in.close();
	          in = null;
	          out.flush();
	          out.close();
	          out = null;
	        } catch(IOException e) {
	            Log.e("tag", "Failed to copy asset file: " + filename, e);
	        }       
	    }
	}
	private void copyFile(InputStream in, OutputStream out) throws IOException {
	    byte[] buffer = new byte[1024];
	    int read;
	    while((read = in.read(buffer)) != -1){
	      out.write(buffer, 0, read);
	    }
	}

//    @Override
//    public void onStop()
//    {
//    	android.os.Process.killProcess(android.os.Process.myPid());
//    }
}
User avatar
Petunien
Party member
Posts: 191
Joined: Fri Feb 03, 2012 8:02 pm
Location: South Tyrol (Italy)

Re: help on standalone game

Post by Petunien »

Dude, you're awesome! :D

Sorry, English isn't my native language, but I wanted to ask if I understood correctly.

You got it working, so a stand alone *.apk of your game would be playable on the phone, without "Browse for game" before?

If so, may I ask for a little little tutorial on how to do that? Because I get errors when changing the LoveNative.java.

:)
"Docendo discimus" - Lucius Annaeus Seneca
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

Re: help on standalone game

Post by dudeabot »

Petunien wrote:Dude, you're awesome! :D

Sorry, English isn't my native language, but I wanted to ask if I understood correctly.

You got it working, so a stand alone *.apk of your game would be playable on the phone, without "Browse for game" before?

If so, may I ask for a little little tutorial on how to do that? Because I get errors when changing the LoveNative.java.

:)

hey man! thanks!

exacty thats the idea, if you wanna check just download the apk

whats the erros you get?
dudeabot
Prole
Posts: 17
Joined: Tue Nov 13, 2012 2:28 am

Re: help on standalone game

Post by dudeabot »

Also you could test just replacing the .love file file by your game in my APK (in the other post)

of course the apk is signed by me, so it might not be interesting for sellin purposes, just for tests. you should want package yourself with the changes provided ;)
Locked

Who is online

Users browsing this forum: No registered users and 1 guest