我的世界【安卓城市建造类手机游戏】最好玩的城市建造类游戏大全
我的世界:城市建造类游戏专题
城市建设类游戏是一种比较讲究策略的游戏,玩家想要较好的运营城市需要掌握影响城市发展的各种因素,这是游戏的难点所在。比如模拟城市等游戏,堵车会影响城市游客的数量从而影响到经济的发展,如果开赌场会给城市带来人气,但也会造成城市的交通拥堵,如何权衡这些影响城市的发展因素是非常重要的。
温馨提醒:鼠标移动到图标上可预览截图&&&&
最好玩的城市建造类游戏推荐
等级:10次下载48.19MB简体推荐理由:游戏画面精美,一起加入游戏帮助杰尔塔能顺利通关拯救凯吧!
等级:15次下载48.01MB简体推荐理由:如果你喜欢城市岛屿和早期的大亨游戏,又喜欢建造城市的话,那你一定要玩我们的第三代城市建造游戏--城市岛屿3:模拟城市!
等级:10次下载11.42MB简体推荐理由:维持乐高系列一贯的创作风格,主人公还是我们所熟悉的积木人们,在这个世界里可以扮演多种角色。
等级:5次下载124MB简体推荐理由:面对统治天空之城的莫里亚斯教团,拥有未知使命传承的空贼团的一次强夺行动拉开了大陆秩序颠覆之战的序幕。
等级:56次下载17MB简体推荐理由:《像素人 YYYYY》是一款简单休闲的游戏。游戏操作很简单,在上方的颜色掉下来之前点击XX把颜色去掉。看看你能够坚持几个回合吧。会越来越快的哦~
等级:1193次下载10.3MB简体推荐理由:这款游戏,说它不是游戏,是因为它并没有什么特定的玩法,也没有游戏情节,也没有游戏规则,说它是游戏,因为同样能够消磨你的时间,让你体...
等级:29次下载139.1MB简体推荐理由:《英雄之城2》是一款集战争策略、角色扮演、模拟经营为一体的手机游戏,延续《英雄之城》的成功之后,蜗牛公司将这款游戏转战到移动端,大师...
等级:698次下载9.11MB简体推荐理由:在现代社会中,并存着人类和口袋妖怪。人类与它们和谐相处,并且有许多和口袋妖怪有关的职业,像培育师、训练师等。
等级:604次下载21.25MB简体推荐理由:在游戏中,有九位人物,分别为刺客、盗贼、魔术师、国王、主教、商人、建筑师、军阀和皇后。而你每个回合可以请一位人物帮助你增加自己的资...
等级:532次下载25.96MB简体推荐理由:《大都市 Megapolis》是一款模拟经营类游戏。你要作为城市的市长,负责设计城市的主要基础设施,如机场,铁路,港口,制造业等。让大都市变...
等级:18次下载3.76MB简体推荐理由:一款跳跃题材的小游戏,作为城市的英雄完成所有的任务,拯救我们的城市吧!
等级:6427次下载13.7MB简体推荐理由: 《MineCraft》是一款3D的第一人称沙盘游戏,所呈现的世界并不是华丽的画面与特效,而是注重在游戏性上面。玩家在游戏中做着「建设」与「破坏」
大多数平凡人面对命运的掌控都是无能为力的,只能逆来顺受任其调遣,正因如此,在虚幻世界无所不能的游戏才得以施展其才华,将人们那不敢想不可说的愿望悉心打造,任君采撷,比如让你成其为权倾一时的一城之长,白手起家,缔造繁华。今天第九软件网安卓城市建造类手机游戏专题就为大家盘点那些精品城市建造手游,一起来看看吧!
等级:7次下载121MB简体推荐理由:《原始部落》是一款部落题材的冒险类手机网游。《原始部落》讲述的是,来自远古的一次巨变,一个发生在广袤大陆上的故事。
等级:6次下载13.17MB简体推荐理由:“部落”是中小微企业或团队工作分享的最佳选择!“聪明的企业创建部落”----赛斯 高汀(Seth Godin)有效避免员工因使用即时聊天工具带来的工作效率
等级:12次下载45.7MB简体推荐理由:一款由魔兽世界为背景的手游,游戏之前叫魔兽萌萌哒,在测试结束后,改名为部落萌萌哒,喜欢魔兽的玩家可以来试试这款手游喔。
等级:30次下载43.4MB简体推荐理由:Clash of Clans中文名叫部落冲突、部落战争,是一款史诗般的战斗策略类游戏。
等级:36次下载45.6MB简体推荐理由:游戏以历史人物乱入为背景,结合时下最流行的卡牌SLG形式,支持万人同时即时占领战以及模拟实战中的国战,让你切实感受到只存在于想象中的战斗
等级:14次下载48.8MB简体推荐理由:以古罗马为时代背景,玩家通过建设城市、召集军团、联合盟友、征服野蛮人等方式组建自己的帝国,并可以击败其他玩家和对手,夺得“凯撒”的荣耀
等级:5次下载52.7MB简体推荐理由:这里有幽默有趣的的原始人类、也有奇形怪状的原始建筑和前所未见的史前生物,但真正需要注意的是那隐藏在黑暗中的邪恶势力!
等级:7次下载83.3MB简体推荐理由:带您进入一个激荡人心的奇幻世界,华丽的3D卡通风格,丰富的英雄人物,横板即时的战斗方式和震撼的魔法效果,给您最极致的游戏体验。
等级:128次下载38.8MB简体推荐理由:《神龙部落2 Dragon Village 2》是一款模拟经营类的游戏,作为《神龙部落》的续作,新作中有100多种龙等你来驯服,
等级:225次下载11.43MB简体推荐理由:游戏中玩家要保卫自己的城堡不受敌人的侵扰,然后进攻敌人城堡获得胜利!
等级:87次下载0.5MB简体推荐理由:主角因为失忆而忘记了一切,在游戏中不断找回乘坐班机的原因和过去的身世。
等级:191次下载47MB简体推荐理由:只要你有欲望,《欲望之城》就有你的一席之地。填补欲望的沟壑,唯有《欲望之城》。
专题推荐 prev next网络游戏程序员须知 调试多人联机游戏 - CSDN博客
网络游戏程序员须知 调试多人联机游戏
本文为作者原创或翻译,转载请注明,不得用于商业用途。
首发链接:
欢迎大家再次来到我的博客,上次的博客简单的讲了一下关于的内容。这周我想再谈一下怎么调试联机对战游戏。
作为一个网络游戏程序员,我已经在好几个网络游戏中担任过开发任务了。现在回想起来,当我第一次将一个单机版游戏做成联机版的时候,在我面前的第一个难题就是如何进行调试。这玩意比起单机版的着实是不容易调试。
在后面的工作经历中,我又在不同的公司不同的组里开发联机模块,当我和我的同事们谈起联机模块的调试时,我发现这块事实上有很多不同的技术,但是这些技术似乎从来没有被系统的整理过,也没有文档化,当然几乎没有一家公司会全面的使用这些技术了。
所以我想写这篇联机对战的调试教程还是很有必要的。希望这里提到的那些小技巧也许会对大家当前的工程起到帮助。 by rellikt
说起调试,单机版游戏的调试技巧我想大家已经很熟练了。我们可以在屏幕或者控制台输出调试信息,在log文件中导出调试信息,在IDE中设置断点,条件断点,数据断点,然后单步调试。但是在联机调试中我们能这么做吗?
事实上一旦我们这么做,其他的机器很快就会因为连接超时而断开连接,我们的调试也就无功而返了,这点的确是很恼人的。事实上我们要调试的情况就是有多台机器在同时异步的跑同一个程序,一旦我们在一台机器上进入断点,其他机器就会很快发现连接超时,然后断开连接,通常这个时间不会超过30秒,要在那么短的时间内调试bug简直是不可能的。
怎么解决这个问题呢?你首先想到的肯定是在两台机器上同时打断点,进入单步调试。这种技巧在2人联机的时候也许是可行的。但是当人数更多时,我们就会发现这个方法完全不给力了。那怎么办呢?我们需要同步调试来模拟我们在单人游戏时的情况。
同步调试的基本概念就是,我们会在每帧广播一个用来维持连接的脉动包到其他机器,然后在每个机器上都针对每个连接建立一个时间累计器,每次收到脉动包的时候,我们把对应连接的时间累计器清零,当我们发现某个时间累积器值超过一定范围的时候,我们自动阻塞进程。以下是一个大致的实现:
float timeSinceLastHeartbeat[MaxPlayers] = { 0.0f, ... };
while ( true )
&&& const float deltaTime = GetFrameTime();
&&& bool waiting =
&&& while ( waiting )
&&&&&&& SendHeartbeatPacket();
&&&&&&& while ( true )
&&&&&&&&&&& int fromPlayerId = -1;&&&& &&&&&&&&&&& int packetSize = 0;
&&&&&&&&&&& unsigned char packet[1024];
&&&&&&&&&&& if ( !RecievePacket( fromPlayerId, packet, packetSize ) )
&&&&&&&&&&&&&&&
&&&&&&&&&&& if ( IsGamePacket() )
&&&&&&&&&&&&&&& ProcessGamePacket( fromPlayerId, packet, packetSize );
&&&&&&&&&&& else if ( IsHeartbeatPacket() )
&&&&&&&&&&&&&&& timeSinceLastHeartbeat[fromPlayerId] = 0.0f;
&&&&&&& waiting =
&&&&&&& for ( int i = 0; i & MaxP ++i )
&&&&&&&&&&& if ( timeSinceLastHeartbeat[i] & 0.1f && !IsLocalPlayer(i) )
&&&&&&&&&&& {
&&&&&&&&&&&&&&& waiting =
&&&&&&&&&&& }
&&& SendGamePacket();
&&& GameUpdate( deltaTime );
&&& for ( int i = 0; i & MaxP ++i )
&&&&&&& timeSinceLastHeartbeat[i] += deltaT
这里我们要注意,即使是在等待脉动包的时候,我们还是会不停的发自己的脉动包,因为如果不这么做,就会产生嵌套死锁的等待,这样的话就再也回不来了。by rellikt
好了,我们把这段代码加入到工程里面以后,我们就会发现一旦我们在一台机器上进入断点,其他机器也会在0.1秒以后停下来,然后我们在继续当前机器的进程以后,其他机器也会继续。这样我们的联机调试就和单机调试很像了。
当然这里展示的只是一个简陋的版本,比如当有一台机器的帧率小于10帧的时候,其他机器也会慢,当一台机器死机的时候,其他机器也会死机。我们在实际工程中要对这些情况进行完善和维护,例如加入一些宏等来开关这个特性,这里不做太多的拓展了,大家因地制宜的自己去实现吧。
导出日志文件
如果你是职业游戏程序员并且在正规公司内工作过,那么你肯定已经接触过这方面的内容了。至少你需要在游戏正式运行版崩溃的时候能够拿到一个关于指令堆栈的日志文件,这样你才能知道从哪里开始解决问题。在屏幕上或者日志文件中同时做输出有时候是很有效的办法,至少当GD发现问题的时候,你能从他的屏幕上一目了然的知道一些问题的线索。
事实上怎么在不同的平台上输出日志文件和堆栈信息是很专业的问题,我这里也不可能展开太多。不过鉴于大多数游戏都会开发win32版本,如果你想在windows上做这个可以参考这篇,mac平台的话,我们一般可以直接用它自带的崩溃日志,事实上类unix的系统都会带有这个功能,你可以把这些导出的文件用GDB进行调试,不过一般到这步都需要比较强的调试能力了。当然你也可以订制自己的单元测试模块,把需要的信息和相关的关联起来输出,这也是一个不错的主意。
我们还有一个不错的主意就是建立一个自定义的信息堆栈。这个技巧在内存调试的时候也会经常用到,我们的基本思路就是在代码进出程序堆栈的时候给代码段打上一个自制的tag,这样在出错的时候,我们就可以在调用堆栈信息之外再拿到一个自定义的tag信息,而这个tag信息往往更为有用,这个tag可以记录文件名,模块名,包名等。by rellikt
下面是我简单做的一个实现:
const char MaxInfoLength = 256;
const char MaxInfoDepth = 64;
static int g_infoStackDepth = 0;
static char g_infoStack[MaxInfoDepth][MaxInfoLength + 1];
void push_info_stack( const char * string )
&&& assert( g_infoStackDepth & MaxInfoDepth );
&&& strncpy( &g_infoStack[g_infoStackDepth][0], string, MaxInfoLength );
&&& g_infoStack[g_infoStackDepth][MaxInfoLength] = '';
&&& g_infoStackDepth++;
void pop_info_stack()
&&& assert( g_infoStackDepth & 0 );
&&& g_infoStack[g_infoStackDepth][0] = '';
&&& g_infoStackDepth--;
class InfoPushPopHelper
&&& InfoPushPopHelper( const char * string )
&&&&&&& push_info_stack( string );
&&& ~InfoPushPopHelper()
&&&&&&& pop_info_stack();
#define INFO( format, ... ) /
char buffer[MaxInfoLength+1]; /
snprintf( buffer, MaxInfoLength+1, format, __VA_ARGS__ ); /
InfoPushPopHelper infoPushPop( buffer );
注意到我们这里写的那个helper类,这个类的存在使我们在代码段压栈弹栈的时候可以打上合适的tag,进行简单的信息记录。全局的infostack则可以在我们进入断点的时候给我们提供额外的信息。
void processWad( const char wadFilename[] )
&&& INFO( "processing wad: %s", wadFilename );
&&& WadLoader loader( wadFilename );
&&& while ( true )
&&&&&&& WadResource * resource = loader.GetNextResource();
&&&&&&& if ( !resource )
&&&&&&& switch ( resource-&GetType() )
&&&&&&& case WadResource::Image: processImage( resource );
&&&&&&& case WadResource::Mesh: processMesh( resource );
&&&&&&& // etc...
void processImage( WadResource * resource )
&&& INFO( "processing image: %s", resource-&GetFileName());
&&& //etc&
以上这段代码大致为我们展示了怎么使用这个额外的信息堆栈,这端代码可以让我们得到是哪个文件的载入使我们崩溃的信息。我们只要简单的把我们每个收发的包的信息填入,就可以得到一个联机调试的额外信息堆栈了。事实上编写一些有趣的字符串来描述整个游戏过程是很有趣的,至少比那些单调的调用堆栈信息有趣。你可以写类似&我A了一下飞龙,然后游戏崩溃。&这样的日志,是不是觉得很酷啊?by rellikt
现在你有一个很好的同步调试机制和额外日志系统了,接下来就是邀请你的同事在休息的时候陪你一起做测试了。当然他们有可能不是那么愿意,那么你只好编写一些AI bot来帮你完成这个任务了,事实上网络调试是整个游戏调试中最繁琐的部分,要做各种压力测试,环境测试。因此一个良好的日志系统和高质量的崩溃报告绝对是物有所值的,当然能够混合单元测试和功能测试那就更有效了。
过程记录和回档
光有以上这些技巧有时候还是不够的。有些时候我们遇到的bug可能在好多帧前面就已经发生了,只是在这帧才碰巧爆发,因此面对完全正常的错误日志,你往往会一筹莫展。又有时候,我们的堆栈信息已经被污染了,因此我们得到的仅仅是一些垃圾信息,完全的于事无补。更绝的是,我们有时候还会碰到一些不定发生的bug,他们可能调试几周才能遇上一次,或者在本地无法重现,在测试那边就很容易发生。以上这些bug往往会令你完全的泪流满面,不知所措。
那么我们有什么好的办法来应对这些疑难杂症吗?我们一般遇到这些bug的时候,往往只能简单的给程序加上更多的log标签或者assert信息,希望测试在再次遇到这个bug的时候,我们加上的这些标签能够带来有用的信息,当然如果他们无法带来有用的信息,我们一般只能继续加,直到幸运女神光临我们的一天。
那么我们是不是可以有更好的办法呢?比如我们可以记录下来玩家的操作信息,然后当他们遇到这个bug的时候,我们就可以把记录日志导出,然后用这个日志记录的操作去重现这个bug,这是不是会好点的。事实上我们有时候就是这么做的。我们称这种技术叫全程回放。基本的思路就是把游戏中所有的不确定因素记录下来,然后把它们作为替代在游戏重演的时候加入进去。这样我们就可以在调试工具中完整重现我们要的游戏过程了。by rellikt
我们一般记录的内容会是每次的输入,每个随机数的种子,每帧的时间,每个包的内容等。事实上如果你能采用这个这个技术,那么你不需要任何输入和联网就能完成一个多人对战游戏的全过程,事实上这才是真正我们想要的调试。
来看一个典型的多人对战游戏的模型:
while ( true )
&&& SendPackets( socket );
&&& while ( true )
&&&&&&& int packetSize = 0;
&&&&&&& unsigned char packet[1024];
&&&&&&& if ( !socket.RecievePacket( packet, packetSize ) )
&&&&&&& assert( packetSize & 0 );
&&&&&&& Game::ProcessPacket( packet, packetSize );
&&& float frameTime = Timer::getFrameTime();
&&& Game::Update( frameTime );
现在让我们看看怎么可以加入全程回放技术。我们这里在做记录和回放时用到的技术是不一样的。在做记录的时候,我们需要把不同的数值写入记录文件,然而在回放的时候,我们需要把记录文件中的值读出然后取代不同的数值。by rellikt
void journal_int( int & value );
void journal_bool( bool & value );
void journal_float( float & value );
void journal_bytes( unsigned char * data, int & size );
bool journal_playback();
bool journal_recording();
下面是实际中的一个简单应用:
while ( true )
&&& SendPackets( socket );
&&& while ( true )
&&&&&&& int packetSize = 0;
&&&&&&& unsigned char packet[1024];
&&&&&&& bool result =
&&&&&&& if ( !journal_playback() )
&&&&&&&&&&& result = socket.RecievePacket( packet, packetSize );
&&&&&&& journal_bool( result );
&&&&&&& if ( !result )
&&&&&&&&&&& journal_bytes( packet, packetSize );
&&&&&&& assert( packetSize & 0 );
&&&&&&& Game::ProcessPacket( packet, packetSize );
&&& float frameTime = Timer::getFrameTime();
&&& journal_float( frameTime );
&&& Game::Update( frameTime );
注意这里我们在检测到play_back()为true的时候会屏蔽掉接收包的函数,用我们自己从记录文件中读取到的内容做代替,这就是我们这里可以不启用网络就可以做联机调试的本质原因。
你一定意识到了全程回放技巧的价值,我们现在可以重演游戏直到发生bug的位置。甚至我们可以在修复bug以后再次用那个会产生死机的操作来验证我们的修复是否有效。不用怀疑,全程回放肯定是调试多人联机游戏的终极利器,但是要启用这个终极兵器,我们要付出的代价当然也是极大的。维护成本肯定是相当高的,我们必须记录下所有的不确定因素,一旦中间有任何差错,你就必须慢慢的回溯直到找到差错的地方。任何做过网络开发的程序员肯定能明白这个过程是多么的痛苦。
另外一个痛苦的地方就是对于多线程的情况,有时候你根本无法对多线程的游戏进行合适的全程回放。一般的情况是你可以把所有的包含不确定因素的代码放到一些辅助线程中,然后把那些确定性好的代码加入主线程。然后在进程通信完毕的时候做记录或者替换。同样在处理异步的输入和输出的时候,你也可以用这个办法。但是要注意,你加入的全程回放代码可能会导致一些性能的下降,然后如果在代码结构发生变化的时候,每次维护这个系统也是一件苦差事。 by rellikt
事实现在多数公司都不会在底层自己去实现这个系统了,他们会花钱购买一些自动化测试工具来做系统层面上的全程回放,除非游戏有录像功能的要求,否则这个系统的维护是相当费时费力的。我以前用过一个叫Replay Director的工具,不过现在好像这个工具已经放弃对PC或360游戏的支持了,现在它只支持java程序。
作为终极兵器,这个系统的确有其价值,当然也充满了坎坷,如果你想在自己的工程中尝试,那么首先要记得把所有的不确定因素放到一个主线程中,这样才好做模拟。当然如果你懒得去实现的话,我想前面介绍的同步调试技巧和额外日志文件技巧就已经很好用了。
本文已收录于以下专栏:
相关文章推荐
去gameloft笔试,有一个题目是说:
叫你去设计一个FPS(第一人称射击游戏),你是要用TCP呢还是要用UDP,说明理由 。
这是两篇网上找到的文章,写非常不错。
本文为作者原创或翻译,转载请注明,不得用于商业用途。
首发链接:http://blog.csdn.net/rellikt/archive/2010/08/...
本文为作者原创或翻译,转载请注明,不得用于商业用途。
首发链接:http://blog.csdn.net/rellikt/archive/2010...
本文为作者原创或翻译,转载请注明,不得用于商业用途。
首发链接:http://blog.csdn.net/rellikt/archive/2010...
本文为作者原创或翻译,转载请注明,不得用于商业用途。
首发链接:http://blog.csdn.net/rellikt/archive/2010/08/...
游戏运行效果:客户端和服务器端的同步(运行稍有延迟现象)
游戏场景自行建立,只需要能体现出来即可,如一个地面一个预设物体即可
启用Unity3D新建一项目,在场景中新建两...
version14 ( csdn下载地址请猛击这里 )
主要内容:多玩家登陆 显示周围玩家
所属部分:服务端 客户端
代码地址 /changjix...
他的最新文章
讲师:何宇健
讲师:董岩
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)