65.9K
CodeProject 正在变化。 阅读更多。
Home

R.O.O.T.S

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (21投票s)

2012年10月31日

Apache

2分钟阅读

viewsIcon

40582

downloadIcon

1695

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&gt0 ? 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 的情况下在模拟器上运行良好。

© . All rights reserved.