Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Quest of Dungeons was made in C++ and OpenGL, it runs on Windows, Mac and Linux in native code. I never made anything for Android, in fact I never even used one before this, so when I decided to port QoD to it, I had no idea of what to expect.
Quest of Dungeons was made in C++ and OpenGL, it runs on Windows, Mac and Linux in native code. iOS is not a problem either since I just have a thin layer of Objective-C to access some functionality like touch, accelerometer, etc and the rest of the code just runs.
I never made anything for Android, in fact I never even used one before this, so when I decided to port QoD to it, I had no idea of what to expect.
Back in 2010 I did some research and the NDK was pretty bad, I don't think it even had stl at the time, but things have changed since then. I decided to jump in and try to port the whole thing. As a primary goal I want to keep as much native code as possible, because it's already working, porting it to Java could only lead to further bugs in the process.
Tools
After some digging I found that you can use Visual Studio to code for Android (oh happy dayyy).
Visual Studio 2010: http://www.visualstudio.com/pt-br/visual-studio-homepage-vs.aspx
VS-Android: https://code.google.com/p/vs-android/
Android NDK, Revision 10 : https://developer.android.com/tools/sdk/ndk/index.html
Android SDK : http://developer.android.com/sdk/index.html
ant-1.9.4: http://ant.apache.org/bindownload.cgi
jdk 1.8.0: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Dependencies
My next concern were some dependencies that I have on the other versions:
OpenAL - for sound card access
Vorbis - for OGG load/parsing
FreeType 2 - for ttf loading
TinyXML - XML parser
Fortunately all of these were possible to compile to Android.
Application
As an entry point I used android_native_app_glue which comes with the NDK, this way you can skip all Java (I have 0 lines of java code in the game)
Another important thing is this line, telling our manifest file that we have no Java code
Resource Loading
Usually you just need to do fopen(filename) to open a file. For each platform I would just have to concern with knowing the root folder of where the resources are. But Android works differently, all resources are compressed and you need to use AAssetManager to load them. This proceeds to unpack the file and retrieve a pointer to it. At least this is what I managed to understand from the process.
AAsset* asset = AAssetManager_open(android_asset_manager, filename, 0);
But this caused a big problem, some of the dependencies, like TinyXML receive a string and proceeds to load file file using fopen(). This meant remaking all frameworks, which is not a very good perspective.
I had some code using fopen and the fact that some external libs used it too posed a problem. I actually spent a couple of days contemplating what would be the best approach but after some research I found a very simple solution.
#define fopen(name, mode) android_file_open(name, mode)
FILE* android_file_open(const char* fname, const char* mode);
Now every fopen will be replaced with android_file_open
As for the function itself it will try to open the file with AssetManager and return a FILE*
I also did a small hack that if you try to open the file with write permissions 'w', it will use the standard fopen, that way we can properly write files to the user documents, for save game data and other stuff.
FILE* android_file_open(const char* fname, const char* mode)
{
if(mode[0] == 'w'){
#undef fopen
return fopen(fname,mode);
}
#define fopen(name, mode) android_fopen(name, mode)
AAsset* asset = AAssetManager_open(android_asset_manager, fname, 0);
if(asset) return funopen(asset, android_file_read, android_file_write, android_file_seek, android_file_close);
#undef fopen
return fopen(fname,mode);
}
static int android_file_read(void* cookie, char* buf, int size) {
return AAsset_read((AAsset*)cookie, buf, size);
}
static int android_file_write(void* cookie, const char* buf, int size) {
return 0;
}
static fpos_t android_file_seek(void* cookie, fpos_t offset, int whence) {
return AAsset_seek((AAsset*)cookie, offset, whence);
}
static int android_file_close(void* cookie) {
AAsset_close((AAsset*)cookie);
return 0;
}
Now just adding this line to the makefile to force the game/libraries to pre-include our file before everything else
LOCAL_CFLAGS := -g -include android_file_open.h
So now all my fopen code works perfectly without any change.
OpenGL
I was using GLES for the iOS version already so I had no problems with the code itself, I did however had some problems with performance on older devices, for example 2010 devices with version 2.3 (the game targets 2.3+)
Especially on this function glCopyTexSubImage2D.
The game uses some 2d soft shadows for giving a nice atmosphere, it's updated once in a while and I just used glCopyTexSubImage2D with a 256x256 texture instead of a VBO because it worked perfectly on everything else (including all iOS devices), but turns out this is very slow on older Android devices, so I ended up adding an option to the game to disable those effects. This way, newer devices can have all bells and whistles and older devices can still run the game fine. One of the testing devices was a Samsung Galaxy S1 and it went from 5 FPS to 30, which is more then acceptable to play. All 4.0+ devices should have no problem running this, and even if they have they can just disable the option.
Application cycle
There is a strong possibility the GL view is trashed when the app goes to background, to save resources the view is recreated each time you go back from background to the app. This poses a problem since all gl objects, textures, shaders, VBOs are destroyed too. There is no secure way to ensure the view never gets destroyed while your app is running so I had to make sure each time the view is recreated I re-load all required assets once again. Really not happy with reloading everything again but its the only secure way, if you detect the view was destroyed and the app is still running you have no other choice. For this game it's not a big deal, I have few resources and they are fast to load so the user can't even tell. If I had a bigger game with lots of big textures I would probably had to show a loading screen again.
Screen size
The different screen sizes for Android can pose a problem, but you can detect screen density and size and scale your game or take proper actions according to what you have.
Here's how you can detect it in C++
int density = AConfiguration_getDensity(engine->app->config);
int screen_size = AConfiguration_getScreenSize(engine->app->config);
switch (density)
{
case ACONFIGURATION_DENSITY_DEFAULT:
case ACONFIGURATION_DENSITY_LOW:
case ACONFIGURATION_DENSITY_MEDIUM:
case ACONFIGURATION_DENSITY_HIGH:
break;
}
switch (screen_size)
{
case ACONFIGURATION_SCREENSIZE_SMALL:
case ACONFIGURATION_SCREENSIZE_NORMAL:
case ACONFIGURATION_SCREENSIZE_LARGE:
case ACONFIGURATION_SCREENSIZE_XLARGE:
break;
}
You can use screen size to detect if the current device is a tablet or a phone.
Final
Now that I finished the port it doesn't seem that I had to change that much, in fact the game code it self is nearly untouched, just the engine was altered to accommodate for Android. Porting took me ~ 2 months (I have a day job) and while working on a expansion for the game. It's not all chaos in the Android universe, although the hardware fragmentation poses some very serious problems, some devices may be too slow to run even the simpler games.
If you have questions about anything else I didn't cover on this article, feel free to ask.
In case you are interested in the game, it's out on Steam, App Store and Google Play, feel free to check it out: http://www.questofdungeons.com
[This is a repost from my personal blog at http://www.david-amador.com ]
Read more about:
BlogsYou May Also Like