泡泡堂AI,泡泡堂游戏录制与播放开发心得

经过了一个月的有间断的研究和开发,我的泡泡堂从只有漏洞百出的AI版,到可以编辑地图的1.1版,最后到修正和提升了AI和改善了泡泡堂显示界面,并且加入泡泡堂录制和播放功能的泡泡堂1.2版终于面世了。也许大家对AI不大感兴趣,对游戏的录制和播放比较感兴趣,(或者对两者都不感兴趣:))。那我就简要说说AI,详细说说录制和播放吧。首先要说明一点的是,这个游戏最开始的框架不是我做起来的,因为我接触游戏开发也就一个月,这个游戏的基础版本是网上盛传的基于ASL引擎的汤祺大牛开发的。如果对源码有兴趣的大牛,可以直接在网上用“ASL 泡泡堂”作为关键字直接Google,相关代码是基于DX8.0,这个版本比较旧,找起来也比较费力。切入正题:

1、泡泡堂AI。可能说起来大家也不信,AI的引擎的核心部分,就是一个简单的宽度优先搜索,所以学好基础算法很重要。只考虑障碍物和目标地图的跟踪式的搜索。为什么寻路算法,不用A*呢?因为考虑到游戏的地图规模不大,A*节省的效率有限,实现之后维护起来也比较困难(对于我这样菜鸟级的水手)。

const int CGameAI::dir[][2] = {{0,-1},{0,1},{-1,0},{1,0}}; // 左,右,上,下 // 从一个地方(role_y,role_x),根据障碍物的情况trace_map,追踪到target_map的最近true位置 int CGameAI::TraceMoving(bool trace_map[][CELL_X + 1],const bool target_map[][CELL_X + 1]) { int head = 0 , rear = 0 , q[250][2] , yy , xx; int d[CELL_Y + 1][CELL_X + 1]; for(int i = 0 ; i < 4 ; i++){ if(MAP[role_y][role_x].Blind == btTent && (i + 1 == dtLeft || i + 1 == dtRight)){ continue; // 只有两个方向能离开帐篷 } if(role_y + dir[i][0] >= 1 && role_y + dir[i][0] <= CELL_Y && role_x + dir[i][1] >= 1 && role_x + dir[i][1] <= CELL_X){ bool flag = false; if(MAP[ role_y + dir[i][0] ][ role_x + dir[i][1] ].Blind == btTent){ // 只有上下两个方向能进帐篷 if((i + 1 == dtUp || i + 1 == dtDown) && trace_map[ role_y + dir[i][0] ][ role_x + dir[i][1] ] == false) flag = true; } else { if(trace_map[ role_y + dir[i][0] ][ role_x + dir[i][1] ] == false) flag = true; } if(flag == true){ q[rear][0] = role_y + dir[i][0] , q[rear][1] = role_x + dir[i][1] , trace_map[ role_y + dir[i][0] ][ role_x + dir[i][1] ] = true; d[ role_y + dir[i][0] ][ role_x + dir[i][1] ] = i + 1; rear++; } } } while(rear > head){ yy = q[head][0] , xx = q[head][1] , head++; if(target_map[yy][xx] == true){ return d[yy][xx]; // 找到目标 } for(int i = 0 ; i < 4 ; i++){ if(MAP[yy][xx].Blind == btTent && (i + 1 == dtLeft || i + 1 == dtRight)){ continue; // 只有两个方向能离开帐篷 } if(yy + dir[i][0] >= 1 && yy + dir[i][0] <= CELL_Y && xx + dir[i][1] >= 1 && xx + dir[i][1] <= CELL_X){ bool flag = false; if(MAP[ yy + dir[i][0] ][ xx + dir[i][1] ].Blind == btTent){ // 只有上下两个方向能进帐篷 if((i + 1 == dtUp || i + 1 == dtDown) && trace_map[ yy + dir[i][0] ][ xx + dir[i][1] ] == false) flag = true; } else { if(trace_map[ yy + dir[i][0] ][ xx + dir[i][1] ] == false) flag = true; } if(flag == true){ q[rear][0] = yy + dir[i][0] , q[rear][1] = xx + dir[i][1] , trace_map[ yy + dir[i][0] ][ xx + dir[i][1] ] = true; d[ yy + dir[i][0] ][ xx + dir[i][1] ] = d[yy][xx]; rear++; } } } } return 0; }

返回值是一个方向,如果返回0,表示这次搜索的目标没有达到。

这段代码的调用也非常简单,像人一样考虑游戏就行了。

if(willBeDangerous[role_y][role_x] == false){ // 安全区域 if(ROLE[AIRole].GetAbility().Vehicle != vtFastUFO && ROLE[AIRole].GetAbility().Vehicle != vtSlowUFO){ // 没有UFO // 首先判断对手是不是trap了,能不能弄破对手 if(ROLE[1 - AIRole].GetState()->Type() == rsTraped){ trace = ToBeatOpponent(map, willBeDangerous); if(trace != 0) return trace * IsLayPopo; } // 反正是不能靠过去弄破对手,索性找宝物 trace = ToFindTreasure(map, willBeDangerous); if(trace != 0) return trace * IsLayPopo; // 宝物也没有,就找箱子 trace = ToFindBox(map, willBeDangerous); if(trace != 0){ if(trace != dtStill) return trace * IsLayPopo; else { // 可能要移动一些盒子才能移动,不过事先要判断一下 if(IsExplodedAround(map) == 0){ // 这里有一种可能,就是被箱子困住了,要推箱子才能出去 trace = KickPopoToEscape(map, ctBox); if(trace != 0) return trace * IsLayPopo; // 在小范围内找一个安全的地方来放泡泡 trace = FindLayPopoCell(map, AIRole); if(trace != 0) return trace * IsLayPopo; } else return trace * IsLayPopo; } } // 发现箱子也没有了,找对手炸 trace = ToFindOpponent(map, willBeDangerous); if(trace != 0) return trace * IsLayPopo; } else { // 有UFO的时候,因为不能捡宝物,直接寻找对手 trace = RideUFOToOpponent(map, willBeDangerous); if(trace != 0) return trace * IsLayPopo; } } else { // 有危险(要分有UFO和没有UFO的情形) if(ROLE[AIRole].GetAbility().Vehicle != vtFastUFO && ROLE[AIRole].GetAbility().Vehicle != vtSlowUFO){ // 没有UFO // 普通逃跑 trace = ToEscape(map, willBeDangerous); if(trace != 0) return trace * IsLayPopo; // 踢泡泡逃跑 if(ROLE[AIRole].GetAbility().bKick == true){ trace = KickPopoToEscape(map, ctPopo); if(trace != 0) return trace * IsLayPopo; } } else { // 有UFO的逃跑方式不同 return RideUFOToEscape(map, willBeDangerous); } } return dtStill * IsLayPopo;

贴一段寻找宝物的代码:

// 寻找宝物 int CGameAI::ToFindTreasure(const int map[][CELL_X + 1],const bool willBeDangerous[][CELL_X + 1]) { bool gift_map[CELL_Y + 1][CELL_X + 1]; // gift的障碍物地图 bool target_map[CELL_Y + 1][CELL_X + 1]; // 目标地图 for(int i = 1 ; i <= CELL_Y ; i++) for(int j = 1 ; j <= CELL_X ; j++){ gift_map[i][j] = ( (map[i][j] != ctCanGo) | willBeDangerous[i][j] ); target_map[i][j] = (MAP[i][j].Type == itGift); } gift_map[role_y][role_x] = true; return TraceMoving(gift_map,target_map); }

相信大家也对这个AI有大概的了解,估计大家想知道的只是录制和播放。录制的文件很小,平均的录制是一秒钟60帧,一分钟只占用1.3k以下的存储空间,一个泡泡堂三分钟的游戏,录制出来也不过4k,跟一幅35*35的BMP格式的图片那么大。

 

2、泡泡堂录制与播放。录制的方法,我试过用avi来录制,这样我就不用写播放器的代码,结果录制1分钟就要10M空间。这个还不算,录制avi,需要把视频流压缩保存,这个时间是游戏时间的一半,玩了三分钟,压缩成avi,就要一分多钟。录制过程中如果追求帧率高还很耗cpu,把游戏都卡住了。就这样耗了两天,无果。我原来就知道魔兽可以录制游戏,听玩的同学说大概是录操作流,而且录制的过程不占cpu,录完不占空间。看了几个讨论录制游戏到底录什么的帖子,终于尝试录制操作流的想法,具体的过程请看一个帖子:http://topic.csdn.net/u/20090811/03/a40a24a0-5a1b-4a7e-a01f-45c1b4f64520.html。

 

AI版:

http://download.csdn.net/source/1520251

1.1版:

http://download.csdn.net/source/1537489

1.2版:

已经上传,正在审阅。

 

你可能感兴趣的:(算法)