Please Sign up or sign in to vote.

Introduction

I want to introduce full source code of cross-platform game and hope it will be interesting someone. I think it is simple way to support many different platforms. Currently the game works perfectly on Android and Windows. I am going to support some other platforms in the future.

Description

R.O.O.T.S is a real-time strategy game. Similar to games like Galcon, Eufloria, Tentacle Wars, but this game has a lot of different gameplay.

Features

It doesn't use any special game engine, only several open-source libraries

Visualization based on OpenGL ES 2.0

All textures are generated

Real-time generation of noise texture on GPU

Multipass rendering with Gauss blur

Unique form of a each tree based on fractal algorithm

AI based on negamax algorithm

Class for rendering of font on OpenGL with unicode support

Direct loading game data from APK file, including streaming music

Technical Information

There are some interesting solutions in the source code.

Load fonts, shaders, sounds and level data

You can load the game data from a APK file directly. It provides to conserve space on disk or sd-card. The APK is a ordinal ZIP file.

First you need to receive the full file path to your APK file in Java code:

String apkFilePath = null; ApplicationInfo appInfo = null; PackageManager packMgmr = context.getPackageManager(); try { appInfo = packMgmr.getApplicationInfo(getClass().getPackage().getName(), 0); } catch (NameNotFoundException e) { e.printStackTrace(); throw new RuntimeException("Unable to locate assets, aborting..."); } apkFilePath = appInfo.sourceDir; init(apkFilePath);

There is invocation of native method init(apkFilePath); Second, use the libzip to open zip file and load some data in C++ code. It is implemented in the class ResourceManager :

ResourceManager::ResourceManager(const char *apkFilename): archive(0) { archive = zip_open(apkFilename, 0, NULL); if(archive) { int numFiles = zip_get_num_files(archive); for (int i=0; i<numFiles; i++) { const char* name = zip_get_name(archive, i, 0); if (name == NULL) { LOGE("Error reading zip file name at index %i : %s", zip_strerror(APKArchive)); return; } } } } ResourceManager::file ResourceManager::open(const char *filename) { zip_file *zfile = zip_fopen(archive, filename, 0); return file(filename, zfile); } unsigned int ResourceManager::file::read(void *buf, unsigned int size) { if(!zfile) return 0; ssize_t rs = zip_fread(zfile, buf, size); return rs>0 ? rs : 0; }

You can also read the file data like a stream, this technique used in the MusicPlayer for example. On other platforms you can simple collect all game resources into one ZIP file.

Rendering Planets

It uses a deformation in GLSL fragment shader to make illusion of volumetric spherical object. At the beginning it generate the texture:

void Render::generatePlanetTexture(int size) { std::vector<unsigned char> buffer(size*size*4); memset(&buffer[0], 0, buffer.size()); int size2 = size/2; int cidx = size2*size+size2; for(int y=0; y<size2; ++y) for(int x=0; x<size2; ++x) { float d = std::min(sqrtf(float(x*x+y*y)) / size2, 1.0f); int idx0 = cidx+y*size+x; int idx1 = cidx-1+y*size-x; int idx2 = cidx-size-y*size+x; int idx3 = cidx-size-1-y*size-x; float xx = float(x+0.5f)/size; float yy = float(y+0.5f)/size; if(d<1.0f && d>0.0f) { float w = (1.0f-cosf(d*PI_2))/d; xx*=w; yy*=w; } buffer[idx0*4] = buffer[idx2*4] = (unsigned char)((0.5f+xx)*255.0f); buffer[idx1*4] = buffer[idx3*4] = (unsigned char)((0.5f-xx)*255.0f); buffer[idx0*4+1] = buffer[idx1*4+1] = (unsigned char)((0.5f+yy)*255.0f); buffer[idx2*4+1] = buffer[idx3*4+1] = (unsigned char)((0.5f-yy)*255.0f); unsigned char c = (unsigned char)(d*d*d*d*d * 255.0f); buffer[idx0*4+2] = buffer[idx1*4+2] = buffer[idx2*4+2] = buffer[idx3*4+2] = c; } ... planetTexture.init(&buffer[0], size, size, 4); }

The x, y coordinates contain offset of target texture coordinates. The z-coordinate simulate the atmospheric density. There is example to use this texture in fragment shader:

void main(void) { vec4 p = texture2D(tex0, texcoord1); gl_FragColor = texture2D(tex0, p.xy*0.5); }

The tex0 is our generated texture with deformation coordinates and tex1 is the surface texture. In the game for the tex1 used generated noise texture and the shader look like that:

void main(void) { vec4 p = texture2D(tex0, texcoord1 ); float r = max(texture2D(tex1, p.xy*0.5+vec2(fract(index*23.0)*0.5, fract(index*7.0)*0.5) ).x - 0.5, 0.0); float g = max(texture2D(tex1, p.xy*0.5+vec2(fract(index*17.0)*0.5, fract(index*13.0)*0.5) ).x - 0.5, 0.0); float b = max(texture2D(tex1, p.xy*0.5+vec2(fract(index*3.0)*0.5, fract(index*29.0)*0.5) ).x - 0.5, 0.0); vec3 c = vec3(r, g, b)*0.7*(1.0-p.z) + vec3(p.z*0.25, p.z*0.25, p.z)*0.5; gl_FragColor = vec4(c, 1.0); }

AI Algorithm

For AI use a Negamax modified algorithm for number of players two or more. Each computer player has their own independent AI. It makes a "shot" of the current game situation and then searchs for the best move. After that the loop is repeated.

All Mind objects work in the one separate background thread.

float Mind::alphaBeta(int pIdx, int depth, float alpha, float beta) { if(state == ST_ABORT) return -F_INFINITY; if(isLooser(pIdx)) return -F_INFINITY; if(depth == 0) return evaluate(pIdx); std::vector<Mind::Move moves; calcMoves(pIdx, moves); float score = -F_INFINITY; for(unsigned i = 0; i < moves.size(); ++i) { makeMove(pIdx, moves[i]); for(size_t p = 0; p < genuses.size(); ++p) { if(p != pIdx && !isLooser(p)) { float eval = -alphaBeta(p, depth-1, -beta, -alpha); if(eval > score) { score = eval; if(score > alpha) { alpha = score; if(alpha >= beta) { undoMove(); return alpha; } } } } } undoMove(); if(state == ST_ABORT) return score; } return score; }

Run Game on Android x86 Emulator

Unfortunately the Intel Hardware Accelerated Execution Manager (HAEM) doesn't work on Linux system.

However the good news, the game works perfectly on emulator without the HAEM.