R.O.O.T.S






4.93/5 (21投票s)
R.O.O.T.S 是一款实时战略游戏,类似于 Galcon、Eufloria、Tentacle Wars 等游戏。
引言
我想介绍一个跨平台游戏的完整源代码,希望对大家有所帮助。我认为这是一种支持多种不同平台的一个简单方法。目前游戏在 Android 和 Windows 上运行良好。我计划在未来支持更多平台。
描述
R.O.O.T.S 是一款实时战略游戏。与 Galcon、Eufloria、Tentacle Wars 等游戏类似,但这款游戏具有许多不同的游戏玩法。
特点
- 它没有使用任何特殊的游戏引擎,仅使用了几个开源库。
- 基于 OpenGL ES 2.0 的可视化
- 所有纹理都是生成的
- GPU 上实时生成噪声纹理
- 使用高斯模糊的多通道渲染
- 每个树的独特形态基于分形算法
- 基于 Negamax 算法的 AI
- 支持 Unicode 的 OpenGL 字体渲染类
- 直接从 APK 文件加载游戏数据,包括流式音乐
技术信息
源代码中包含一些有趣的解决方案。
加载字体、着色器、声音和关卡数据
您可以直接从 APK 文件加载游戏数据。这有助于节省磁盘或 SD 卡上的空间。APK 是一个普通的 ZIP 文件。
首先,您需要在 Java 代码中获取 APK 文件的完整路径
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);
然后,调用原生方法 init(apkFilePath);
其次,使用 libzip 打开 ZIP 文件并在 C++ 代码中加载一些数据。这在 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; }
您也可以像流一样读取文件数据,例如 MusicPlayer 中使用的技术。在其他平台上,您可以将所有游戏资源简单地放入一个 ZIP 文件中。
渲染行星
它在 GLSL 片段着色器中使用变形来制造体积球形对象的错觉。首先,它生成纹理
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); }
x、y 坐标包含目标纹理坐标的偏移量。z 坐标模拟大气密度。这是一个在片段着色器中使用此纹理的示例
void main(void) { vec4 p = texture2D(tex0, texcoord1); gl_FragColor = texture2D(tex0, p.xy*0.5); }
tex0
是带有变形坐标的生成纹理,而 tex1
是表面纹理。在游戏中,tex1
使用生成的噪声纹理,着色器如下所示
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 算法
AI 使用修改后的 Negamax 算法,适用于两个或多个玩家。每个电脑玩家都有自己独立的 AI。它“拍摄”当前游戏情况,然后搜索最佳移动。之后循环重复。
所有 Mind
对象都在一个单独的后台线程中工作。
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; }
在 Android x86 模拟器上运行游戏
不幸的是,Intel 硬件加速执行管理器 (HAEM) 在 Linux 系统上不起作用。
但是好消息是,游戏在没有 HAEM 的情况下在模拟器上运行良好。