• About
    • Resume
A Game Developer also plays some guitar

Category Archives: Some Experiences

[译]优化你的手机游戏(没有延迟的,才是健康的)- 一篇给游戏美术设计师读的文章

May 5, 2013 10:27 pm / Leave a Comment / Benny Chen

[译者的话(新浪微博@滚石,欢迎大家一起讨论)]
由于公司目前没有专业的技术美术(Technical Artist),在最近的项目开发中碰了不少壁。于是我就特地做了一些关于美术方面优化的研究,听了一些讲座,阅读了一些文章,你即将阅读的这篇就是其中之一,我觉得不错,并在阅读中获得了共鸣,所以也就决定将这片文章翻译为中文。一来希望在公司中,对于我们的美术设计师作为一个参考,另外分享出来也希望,如果能有那么一点点帮助到跟我们遇到同样困扰的团队或者美术或者任何同仁们,我将万分荣幸。

由于我是一个程序猿,所以翻译中难免有些话或者美术专业术语译的比较二的地方,还请谅解。

原文地址:Optimising Your Mobile Game (Less Lag, More Awesome)

———————————————————————-

你是否曾经开发了一个手机游戏,在你把它递交给reviewer时,本来满心希望它像百米冲刺一样高速运行,结果却步履蹒跚,甚至最后慢得跟爬行一样。

这很烂,不是么?我们意识到这本来是可以避免的,但很多小的错误和疏忽不断堆积,最终造成了程序的瘫痪:它太慢了!

介于我们一直这么粗心犯错,我们一定是忘了给自己的”角色属性表“加上预知未来的能力了 🙂

如果一直这样,那我们开发的游戏——AlieNation,将变得根本不能玩。幸运的是,我们停止了错误,并开始搜集大家的想法,对游戏进行优化(优化成为了我们开发中的新王牌武器!)

alienation

为什么我们从2D转向3D

考虑到从测试玩家和本地媒体获得的认可,我们决定创建一个3D摧毁型游戏(destruction game),以填补市场上的空白。很惊讶大多数的摧毁型游戏都是2D的,但3D才是摧毁型游戏应该的样子,因为3D版本的感觉是如此的棒。当然,你需要做更多的工作来优化你的美术流水线(art pipeline)。

高效和优化让我在一些甚至不知晓的领域中感觉到从容温暖,所以在这里有一些事情要和你们分享,我铭记它们于脑中,并且一直提醒我的团队遵循它们,以保证我们的3D游能够持续运行流畅。

何时选择3D(何时保留2D)

作为一个3D游戏美术,我得到的第一个关键教训就是,让所有的东西尽可能的保持低模——不要使用额外的不必要的顶点,如果你确实要让你的模型拥有精度,把它放置在贴图中。比如说,我们的建筑物都是用简单的方块创建而成,然后贴上带有质感的细节纹理。

当我们要为Vogel教授(它只在我们游戏的GUI和HUD中出现)建模时,首先尝试的是3D模型,但是最终,我们还是把它分解为像Flash中那样的多层2D平面。它运行非常流畅,看上去也更棒,事实最终的结果比用3D模型赞非常多!使用带有动画的2D平面,可以很容易的创造细节,使得Vogel教授看起来非常酷,并吻合整个游戏的艺术风格。

现在每次我看到它时(下图的左下角就是Vogel教授),还会忍不住幸福热泪 ;-(

vogelingame

不要在贴图(纹理)优化上偷懒!

贴图大小是最容易被大多数美术设计师忽视优化的,要知道,贴图会占用非常非常多的空间!

通过跟几个设计师聊天,我发现他们中很多人都习惯于创建他们觉得舒适的尺寸,比如512×512,这符合常理,没什么错误,但唯一的问题就是,设计师不会在导出贴图之前,将它们降低到一个更加合理的尺寸。

一个更小更高效的贴图文件,在放置到游戏中后,可以和一个超大的贴图文件看起来效果一样好!但是如果设计师所有的工作都是在错误的贴图尺寸中进行的,很不幸,所有设计师花费了大量时间绘制的漂亮美术细节,在游戏中被降低尺寸后,会被无情的抹杀 🙁

为了完全避免这个问题,我建议设计师应该在降低尺寸后对贴图做一些额外处理,亦或者是在创建时直接使用和在游戏中一样的分辨率。因为这样的话,设计师才能够精确掌控贴图中的每一个像素点。

文件格式,贴图重用和Shaders

PNG相比于其它很多格式没那么“邪恶”。相比于JPEG的有损压缩,它的压缩是无损的;它虽然对于Alpha透明的处理不像TGA那么出色,但是它既压缩文件同时又能保证足够好的Alpha映射,确实比其它的格式胜出一筹。

texfilesize

很多团队的教训是类似的:

不合理的贴图尺寸 + 不合理的文件格式 = 占用了太多的纹理空间 = 更慢的游戏 = 找到纹理并降低尺寸 = 丢失细节 = 浪费时间 = 非常沮丧的美术设计师 = 非常愤怒的美术总监 🙁

另外一个关于贴图非常有用的建议是——尽可能的重用它们。很显然,这个不光对贴图,对其它任何游戏资源都应该如此。在我们创建Alienation的过程中,我们没有创建新的贴图用于几乎很难被看到的武器和金属物体上,而重用了建筑物的贴图用在这些非常小而琐碎的物体上。只需要一点UV技巧,你永远不会发觉它们的贴图其实是被重用的。

非常重要的一点,你要选择合适的Shader程序。当然,法线高光Shader(Bumped Specular Shader)用到物体上时,看起来太他妈的赞了,但是应该使用特定的手机定制shader,让游戏更高效。在Unity中,这比较容易做到,因为Unity内置了一整套专门针对手机的shader。

什么时候采用Alpha贴图,什么时候使用Mesh(网格模型)

Alpha贴图的卓越优点是它可以让一个物体的部分透明,非常容易,并且也相对的比较经济(cheaply)[作者注:这里说“经济”是相对的,是从处理消耗(processing cost)的角度说的。每一个像素,多边形,shader,light,粒子和骨骼,都会消耗你的游戏的处理性能。]

然而,在有些情况下,mesh比alpha贴图要更高效。举例说,当我们为汽车创建一个轮子时,为了表现轮子中间的缺口,建立轮子的网格模型要比使用贴图+透明的方式,来得高效的多。

我意识到,在这里我有点自相矛盾了,因为之前我说过,要把更多的细节放在贴图中,而不是网格模型上。记住一点,没有绝对的优化技术,如果你拥有这样的能力可以在特定时候选择使用最合理的优化技术,就造就了你不仅仅是一个游戏美术设计师,而且是一个伟大的牛逼的设计师 😀

thumbsup

粒子系统,draw calls,还有物理

对于任何游戏,视觉上的反馈(visual feedback)对于吸引玩家是异常重要的。如果对于玩家的操作缺少反馈,游戏会变得非常无聊,令人昏睡。

说到反馈:粒子效果是一个非常好的给玩家传递信息的方式。可能是一个子弹打到墙上产生的火花粒子效果,又或者是子弹射击一个纳粹分子的血溅粒子效果。

但是对于粒子,有些事情很容易犯错。第一点是对于粒子的贴图大小,对于贴图大小问题,本文的前半部分已经提到,如果你跳过了那部分,强烈建议你回到前面去看一看,我保证那部分内容能让你马上在贴图优化上变成一个更性感的人(而不是一个胡乱贴图的人):D

对于粒子系统的第二点是,很多人没有意识到,增加粒子个数,根本不会增加粒子效果的牛逼度——只会让游戏变得更慢。一个人可以用两种方式创建粒子效果:第一种是用尽可能少的发射数量,来达到一个最好的效果;另一种人就是搞2000个粒子,然后…慢慢跑吧…

你很少在现实游戏中见到过一个发射器包含2000个粒子效果,唯一的原因就是draw calls。

draw call是针对场景中每一个不同的物体而创建的(多一个draw call就会多增加一点游戏的处理时间)。对于粒子系统而言,在draw call上,很容易超载。

我喜欢制作一些粒子效果,但是每当我看到一些初级的设计师,看到一个漂亮而高效的粒子效果问:“只有10个粒子?太少了,这个如果用2000个粒子会更好”,我就巨他妈的沮丧,甚至肉体难受 🙁

particles

对于上图,厄….这…..你为什么要这么搞,非常差劲的初级美术。

物理

没有什么比准确的射击到一个建筑物,并看到它优雅壮丽的倒下更酷了。

为什么会这样?所有的这一切都是因为甜美而性感的物理系统。

当说到物理系统时,程序猿可是这方面的专家。所以接下来的一段话,可能是一种程序猿专用的非常难以解析的语言,所以如果您是一位美术,在这里就忍忍吧 😉

在每一帧,物理系统都会更新很多次,物理会更新受其影响物体的位置,速度和其他一切跟运动相关的属性。每一帧物理的更新次数会直接影响到游戏的运行速度。所以加快游戏运行的一个方式就是减少每一帧物理系统被更新的次数。

这当然是一种可以尝试的方式,但是带来的坏处是会影响你的物理系统的准确性,所以平衡好每帧的更新次数,以获得最佳并最高效的物理效果是非常重要的。

一个例子,当我们减少了每帧物理更新的次数时,我们游戏中的一个球体可能穿过了一道墙,这…我觉得球穿墙可是不真实的,你呢?

所以要调节到一个最优的物理更新帧率是不容易的,这可花了我们公司的程序猿们好一段时间。

光照和天空盒

两件事情我会快速过下——光照和天空盒。

关于天空盒,很称奇的一点是其实你根本不需要使用它们作为天空!(我对吗?!?!)

一个非常棒的方式是在天空中包含背景艺术(background art),这使得能很经济简单的加入非常多的背景艺术和气氛到游戏中。

光照有点神秘,所以我直接了当,对于手机,最好使用平行光(directional light),或者如果你够幸运,加几盏点光(point light)也可以。在Alienation游戏中,在对light做了日以继日的研究后(我撒谎了,其实我就是google了一下unity light;-)),我发现聚光灯(spotlight)在手机游戏上是最消耗性能的光源,慎用。

结束这一切

本文中提到的所有建议,独立采纳其中的一条并不会让你的游戏马上得到改观。

但是如果你综合采纳所有的这些优化,你的游戏品质一定会马上得到质的提高,到时候,嗯哼,等着幸福的哭吧~

——————————————————————————

[译者的话]
个人觉得,这些建议非常好,并在游戏开发中时刻提醒自己遵循它们。但是因为大家开发的游戏都是各式各样的,使用的引擎也完全不同。不要把某种优化建议当成教条,应该选择最适合你们团队的。尽量多使用游戏引擎的profiling,以及时观察分析游戏中影响游戏系能的因素,而不要教条式的麻木follow各种优化建议。

Posted in: 3D, Game Programming, Some Experiences, Uncategorized, Unity

为SyntaxHighlighter添加新语言

September 4, 2011 1:36 pm / 1 Comment / Benny Chen

因为经常要在博客里贴一些Lua代码,但是所使用的SyntaxHighlighter插件默认不支持Lua语言,所以去研究了一下如何为SyntaxHighlighter添加并激活一个新的语言,这里将过程和有同样需求的童鞋分享。(因为我添加的是Lua语言,下面的过程描述会以Lua为例,在添加你所需要的语言时,你只要将相应的项更换为你的自定义设置即可)

1. 从这篇博客里寻找所需要的语言:http://www.undermyhat.org/blog/2009/09/list-of-brushes-syntaxhighligher/;
2. 下载对应的shBrushXXX.js脚本,比如我下载的是shBrushLua.js,它看起来像这样:

SyntaxHighlighter.brushes.Lua = function()
{
	var keywords =	'break do end else elseif function if local nil not or repeat return and then until while this';
	var funcs = 'math\\.\\w+ string\\.\\w+ os\\.\\w+ debug\\.\\w+ io\\.\\w+ error fopen dofile coroutine\\.\\w+ arg getmetatable ipairs loadfile loadlib loadstring longjmp print rawget rawset seek setmetatable assert tonumber tostring';

	this.regexList = [
		{ regex: new RegExp('--\\[\\[[\\s\\S]*\\]\\]--', 'gm'),		css: 'comments' },
		{ regex: new RegExp('--[^\\[]{2}.*$', 'gm'),			    css: 'comments' },	// one line comments
		{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,     css: 'string' },    // strings
		{ regex: SyntaxHighlighter.regexLib.singleQuotedString,     css: 'string' },    // strings
		{ regex: new RegExp(this.getKeywords(keywords), 'gm'),		css: 'keyword' },	// keyword
		{ regex: new RegExp(this.getKeywords(funcs), 'gm'),		    css: 'func' },		// functions
		];
}

SyntaxHighlighter.brushes.Lua.prototype	= new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes.Lua.aliases = ['lua'];

3. 使用FTP工具登陆到WordPress空间,进入到wp-content/plugins目录,新建一个目录,取一个有意义的名字,比如syntaxhighlighter-lua;
4. 将shBrushLua.js上传到新创建的目录;
5. 在该目录创建一个另一个shBrushLua.php文件,添加如下内容:

<?php
/*
Plugin Name: SyntaxHighlighter Evolved: Lua 
Description: Adds support for the Lua language to the SyntaxHighlighter Evolved plugin.
Author: Benny 
Version: 1.0.0
*/
 
// SyntaxHighlighter Evolved doesn't do anything until early in the "init" hook, so best to wait until after that
add_action( 'init', 'syntaxhighlighter_lua_regscript' );
 
// Tell SyntaxHighlighter Evolved about this new language/brush
add_filter( 'syntaxhighlighter_brushes', 'syntaxhighlighter_lua_addlang' );
 
// Register the brush file with WordPress
function syntaxhighlighter_lua_regscript() {
    wp_register_script( 'syntaxhighlighter-brush-lua', plugins_url( 'shBrushLua.js', __FILE__ ), array('syntaxhighlighter-core'), '1.1.1' );
}
 
// Filter SyntaxHighlighter Evolved's language array
function syntaxhighlighter_lua_addlang( $brushes ) {
    $brushes['lua'] = 'lua';
    return $brushes;
}
?>

6. 文件都准备完了,OK,进入到WordPress后台管理的Plugins下,应该能看到新添加的一项syntaxhighlighter-lua,激活它。

Done! It should work now!

其实新添加的js和php文件也可以放到SyntaxHighlighter插件本身的目录下,但是让它独立成插件的好处是,当SyntaxHighlighter升级时,你的个人配置不会因为覆盖而丢失。

Posted in: Some Experiences / Tagged: lua, new brush, plugin, syntaxhighlighter, wordpress, 新语言, 添加

小心DLL链接静态库时的内存错误

September 2, 2010 10:05 pm / 2 Comments / Benny Chen

最近写的模块,在独立的应用程序中测试是没问题的,但把它装配成DLL后,再在另一个应用程序中调用时却出现了内存错误。程序的模块链接关系大概是这样的:

module就是我所写的模块,在这里被封装为DLL,因为要使用json相关的功能,该DLL链接了一个静态库 (jsoncpp.lib)。最后在应用程序中导入并使用module.dll,同时因为在应用程序中也需要用到json,所以应用程序也链接了jsoncpp.lib。

以下用一些伪代码来描述这些模块间的调用关系,以具现出这个错误。

jsoncpp.lib为c++提供了功能齐全的json操作,其核心的类是Json::Value。(阅读本篇文章你无需了解太多json)

module.dll中导出了一个接口:

//ModuleClass.h
#include "json/value.h"

#if defined MODULE_EXPORTS
#define MODULE_EXPORTS __declspec(dllexport)
#else
#define MODULE_EXPORTS __declspec(dllimport)
#endif

class ModuleClass
{
public:
	MODULE_EXPORTS void AllocSomeMemory( Json::Value &root )
	{
		// 这将申请一些内存,因为会new出一个Json::Value,并append到root上
		root.append( "testString" );
	}
};

应用程序:

#include "json/value.h"
#include "ModuleClass.h"
int main()
{
	Json::Value root;
	ModuleClass::AllocSomeMemory( root );
}

在Debug模式下,当main函数执行完毕,对Json::Value root进行析构时,程序便出现了异常。分析下,很显然,调用ModuleClass::MallocMemoryHere时申请的内存,是在module.dll中申请的,而对这些内存的析构则是在应用程序(.exe)中进行的(析构root会同时析构append在root上的所有子Json::Value)。不过,这是异常的真正原因么?

追踪到异常的出错点:dbgheap.c文件中那句ASSERT语句。

/*
* If this ASSERT fails, a bad pointer has been passed in. It may be
* totally bogus, or it may have been allocated from another heap.
* The pointer MUST come from the 'local' heap.
*/
_ASSERTE(_CrtIsValidHeapPointer(pUserData));

注释中的最后一句话”The pointer MUST come from the ‘local’ heap“引起了我的警惕,难道对于内存的申请和释放不是在同一个heap上,除了‘local’ heap还有一个什么heap么。

去MSDN上搜索了关于_CrtIsValidHeapPointer,似乎找到了答案,以下这段话是MSDN上对于_CrtIsValidHeapPointer的介绍:

The _CrtIsValidHeapPointer function is used to ensure that a specific memory address is within the local heap. The local heap refers to the heap created and managed by a particular instance of the C run-time library. If a dynamic-link library (DLL) contains a static link to the run-time library, it has its own instance of the run-time heap, and therefore its own heap, independent of the application’s local heap. When _DEBUG is not defined, calls to _CrtIsValidHeapPointer are removed during preprocessing.

注意字体加粗的部分,这不正应对我的情形么?!错误不在于DLL中申请的内存在EXE中释放,而在于如果这个DLL拥有一个静态链接,它就会拥有独立的运行时堆,独立于应用程序的堆。这样对于内存申请和释放并不是在同一个堆上进行的,当然出错了。

解决:虽然MSDN上最后说,如果把项目改成release的,这个ASSERT就将避免,但这是放纵内存泄露,最好的解决办法是将静态链接也改成动态链接,这样就使得DLL能够和应用程序共享同一个堆,错误也得以避免。

于是,我修改了jsoncpp的项目配置,生成jsoncpp的动态链接库,而不是使用静态库,重新导入到module.dll中,错误解决。

Posted in: C++, Some Experiences / Tagged: _CrtIsValidHeapPointer, DLL, 静态库

Onine Courses

August 1, 2010 9:19 pm / 2 Comments / Benny Chen

第一次听说online course是同事给我推荐的MIT的讲algorithm的课程,随后自己上youtube看了几集,真是大赞。并不是说崇洋,而是国外的老师确实讲得好,条理清晰,而且不乏个人风格,幽默风趣。更难得的,计算机的课程他们居然都是全部只用黑板,而很少用slides。并不是说slides不好,而是他们不像国内的老师,讲课通篇都是过PPT,发催眠曲。online course真是个好东西,让我们这些土鳖能够不用留洋就能感受到国外计算机的课程,况且还是像MIT,Stanford这样的名校。通过这些课程,不仅能领略到国外老师的风采,还能学英语,因为如果听不懂,有些课程还配置了整个课程的transcript,真是非常优质的学习材料。

不过这些课程,对于我这样的已经走出学校的人来说,只能粗看感受一下精华了,不可能真的跟着课程一集一集的细嚼慢咽,已经过了全额学习的那个时期了,要是早几年在大学里能发现这样的材料就好了(或许那时候也没有)。

http://see.stanford.edu/see/courses.aspx

Stanford的关于computer science的课程,课程不多,但都非常详细完整的提供了video, handout, assignment, transcript,甚至还有考试试题和答案。

http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/

MIT的,课程很丰富,但只有部分提供了video,推荐Introduction to Algorithms.

Posted in: Some Experiences / Tagged: MIT, Stanford, 在线课程

Linux下DB2的问题总结

May 3, 2010 6:31 pm / Leave a Comment / Benny Chen

1. 使用db2cc命令启动db2 control center时出现 – Error: Can’t connect to X11 window server using ‘0.0’ as the value of the DISPLAY

解决方法:
– su root
– xhost +

关于xhost: http://linux.about.com/library/cmd/blcmdl_xhost.htm

2. 使用control center操控数据库时抛出SQL4414N错误

解决方法:
– su dasusr1
– db2admin start

3. 在用db2move导出数据库时出现如下的错误

Application code page not determined, using ANSI codepage 1208
Error opening list file. Terminating …
**Error occured while opening a file.
解决方法:
db2的用户对于当前文件夹没有写权限,使用chown改变owner或者chmod改变权限。
chown db2inst1:db2iadm1 $directory_name -R

4. 如何卸载db2

(1). 删除所有关联的instance
$DB2DIR/instance/db2idrop InstName
(2). 删除db2
$DB2DIR/install/db2_deinstall -a

5. $DB2DIR的include目录下为何只有一个asn.h

原因是在安装db2的时候,可能选用了typical模式,则不会安装这些头文件。使用custom 模式,并在select features to install中选中SDK

6. 如何查看db2的版本

db2level

Posted in: Some Experiences / Tagged: db2, linux

Game Engine Framework

February 4, 2010 1:44 pm / 2 Comments / Benny Chen
FoC of Game

在这次找工作的过程中,有一道笔试题让我印象深刻,题目只有简单的一句话:please use pseudo code to write a game engine framework(请用伪代码写出一个游戏引擎的框架)

当时看到这道题,完全是一种头皮发麻的感觉,虽然我对游戏引擎还算比较熟悉,但它可是个“巨象”般的庞大结构,要在短短的90分钟的笔试时间内把它“摸”完(而且90分钟也不只这一道题),那可真是天方夜谭。所以我当时在试卷上留下的结果就是,草草的画了几个模块图了事。

之后我才意识到,我根本就完全没有理解这道题,或者说,是完全没有理解一个词的意义——framework。受游戏引擎庞大印象的牵连,我把framework想得太大了。

Framework对于IT人士来说貌似是个挺时髦的词,这个词也经常被我们挂在嘴边,我也如此。但问题是,我几乎从来没有去好好的留心过或者深究过,到底什么才是framework,什么样一个东西才能被称为framework,它的准确定义又是什么呢。

看一般英汉字典里对于framework的解释:

structure giving shape and support  框架; 结构

这只是给了我们framework的中文翻译而已,几乎还是没有给我们什么有用的信息。

记住一句话:有困难,找Wiki。当我看到Wiki上对software framework精确的定义时,突然间,一切都明白了。

原文URL:http://en.wikipedia.org/wiki/Software_framework

A software framework, in computer programming, is an abstraction in which common code providing generic functionality can be selectively overridden or specialized by user code providing specific functionality. Frameworks are a special case of software libraries in that they are reusable abstractions of code wrapped in a well-defined API, yet they contain some key distinguishing features that separate them from normal libraries.

Software frameworks have these distinguishing features that separate them from libraries or normal user applications:

1. inversion of control – In a framework, unlike in libraries or normal user applications, the overall program’s flow of control is not dictated by the caller, but by the framework.
2. default behavior – A framework has a default behavior. This default behavior must actually be some useful behavior and not a series of no-ops.
3. extensibility – A framework can be extended by the user usually by selective overriding or specialized by user code providing specific functionality
4. non-modifiable framework code – The framework code, in general, is not allowed to be modified. Users can extend the framework, but not modify its code.

这段话的大概意思是这样,首先表明了software framework是一种抽象体,这个抽象体通过一些公共代码提供了通用的系统功能,使用framework的用户可以在此基础上进行编码,以覆盖或者特化该系统中的相关功能。接着,这段话还说明framework也是一种库,但跟传统的软件库又不同。framework的特点被总结为以下4点:

  1. framework完全决定了程序的控制流,该控制流绝对不会被调用framework的用户(callee)改变,这就是framework跟传统的软件库不同的地方;
  2. framework提供默认行为,且这些默认行为绝对不是无意义的行为;
  3. 用户可以扩展framework的行为;
  4. framework本身的代码不允许被用户改变。

看到这里,我几乎是只有抱头痛哭的份了。因为,从开始学最基础的DirectX技术起,那每一个Demo中,不管多小的Demo,几乎就包含了这样的一种框架,抑或是说,一种游戏引擎框架。只要理解了framework的意思,这道题就根本不是什么问题。我是完全被“游戏引擎”这4个字吓住了,这道题的目的根本不是要你真正的写一个正规的游戏引擎(那是扯蛋),只要弄出一个基本架子即可,达到“麻雀虽小五脏俱全”的效果。

几乎所有的游戏或者说所有的图形渲染的程序,都是遵循下面这样一个过程,一个再熟悉不过的控制流。

FoC of Game

游戏的整个控制流就可以到这么简单,除了图上所描述的东西之外,再加上一系列的消息处理,就可以组成一个基本的游戏框架了。按照wiki上所描述的基本软件framework的4大特点,再来看看游戏引擎framework是如何体现的。

  1. 很显然,游戏的控制流如上图所示,使用framework的用户无法更改;
  2. 游戏引擎framework当然提供默认的行为,比如Initialize部分封装默认的窗口初始化和一系列的设备初始化等过程;
  3. Update和Render,是framework使用者真正进行艺术创造的地方,是游戏开发者的画板。
  4. 游戏引擎framework的代码同样不允许用户修改。

用代码来表现framework是很简单的,在面向对象的语言中一般用抽象类,既提供一些默认的实现,又提供抽象接口供用户扩展。而用户要使用这个framework,只需自定义一个类继承该抽象类,并提供各个抽象方法的实现。用户所需做的只是做一系列的填空题,当然填的如何精彩这就完全取决于用户了。

写这篇文章的真正目的不是为了说明一个游戏引擎的framework是怎么样的,只是借此来强化framework的概念,这样当我们下次再说出这个词的时候,能够知其所以然,而不是自欺欺人。

如果对游戏引擎framework感兴趣,这里有一个简单的游戏引擎framework的代码可供参考: http://gpwiki.org/index.php/SDL:Tutorials:Simple_Engine_Framework

Posted in: Game Programming, Some Experiences / Tagged: framework, game engine, 框架, 游戏引擎

Quotes from “Clean Code: A Handbook of Agile Software Craftsmanship” (1)

January 20, 2010 5:57 pm / 2 Comments / Benny Chen

最近在读这本Robert Martin的Clean Code,中文译本是《代码简洁之道》,我觉得非常好看。我一直比较注重写出简洁出色的代码,并且一直以为自己在这方面做的还不错,但随着这本书的阅读,虽然我的一些观点和想法被得到验证,但另外一些却完全被颠覆。也让我意识到离一个出色的专业程序员还有多大的差距。

We’ve all looked at the mess we’ve just made and then have chosen to leave it for another day. We’ve all felt the relief of seeing our messy program work and deciding that a working mess is better than nothing. We’ve all said we’d go back and clean it up later. Of course, in those days we didn’t know LeBlanc’s law: Later equals never.

这样的经历真是太普遍了,我们写了一堆烂代码并且实现了某个功能,颇有成就感,但同时我们也意识到这是一堆垃圾代码,并且提醒自己等未来有空了一定要回来收拾这堆垃圾。但——Later equals never,我们再也没回来过。
所以,垃圾要及时处理,不然就这样遗臭下去了。

Replace the temptation to create noise with the determination to clean your code. You’ll find it makes you a better and happier programmer.

程序员避免忧郁症的好方法就是遵循简洁之道。

And now note the word that Bjarne uses to describe the consequence of that inelegance. He uses the word “tempt.” There is a deep truth here. Bad code tempts the mess to grow! When others change bad code, they tend to make it worse.

当面对一堆烂代码时,很少有人愿意去收拾这堆烂摊子,结果就是让它们烂到极致.(让我想起之前做过的一个flash项目,接手时是噩梦般的代码,我只能东补西凑,到最后功能加的越多,越是恐怖的混乱,几乎完全无法维护)

You get the drift. Indeed, the ratio of time spent reading vs. writing is well over 10:1. We are constantly reading old code as part of the effort to write new code. Because this ratio is so high, we want the reading of code to be easy, even if it makes the writing harder. Of course there’s no way to write code without reading it, so making it easy to read actually makes it easier to write.

这和我以前一直所想的观点正好契合,写只有1次,而读会有N次(N会随着不同的项目有着巨大的差别),所以为了那更多更多次的读,多花时间在写上是绝对值得的。而Martin给出了读写的比例至少在10:1的样子,他还提出了“要想写容易,得先容易读“。

It was the bad code that brought the company down.

烂代码让这家公司歇菜了!烂代码的威力如此恐怖,对于这点,我坚信不疑。

Clean code is simple and direct. Clean code reads like well-written prose.

简洁的代码读起来应该像优美的散文。

One difference between a smart programmer and a professional programmer is that the professional understands that clarity is king. Professionals use their powers for good and write code that others can understand.

聪明的程序员不等于专业的程序员。

In short, a programmer who writes clean code is an artist who can take a blank screen through a series of transformations until it is an elegantly coded system.

我热爱艺术,所以我热爱简洁漂亮的代码。我梦想成为一个艺术家,从程序员入手是个不错的选择。

Posted in: Some Experiences / Tagged: clean code, 代码简洁之道

关于对std::vector的遍历

June 30, 2009 11:40 pm / 1 Comment / Benny Chen

static

上图是通过Instancing渲染了10000个低精度模型(低于200个面),有skin动画,但是人物没有AI。在实验室Geforce 8800GT的显卡上fps可以跑到80帧。

接着,我给人群加上点简单的AI,每个人物进行向一个目标点移动,于是我在每帧更新的时候添加了如下的这些代码。代码中,MeshInstance是instance的类,对应于一个人物实例,Move是移动人物实例的简单AI函数。对于所有的Instancing数据,我使用一个vector列表存储——m_vpInstancingData。代码通过vector的iterator(迭代器)遍历所有的instance,对每个instance执行Move函数。

for( vector< MeshInstance* >::iterator i = m_vpInstancingData.begin(); i != m_vpInstancingData.end(); i ++ )
{
    ( *i )->Move();
}

结果,加上这段代码之后,程序的效率居然骤降,如下图,fps只剩下44帧。这让我很是纳闷,因为在加上代码之前,CPU基本上是空闲的,因为所有的骨骼蒙皮+渲染全部都是GPU扛着,而在CPU加上一个10000次的for循环后,整体效率大打折扣。它的杀伤力有这么大么……CPU不太可能这么低能。

1

然后,我把(*i)->Move()这行代码注释掉了,仍然只有40多帧,即一个只是10000次的空for循环,仍然是效率的瓶颈,10000次的Move根本不是问题。

难道是迭代器在影响效率?于是把代码改成了下面这样,不用迭代器遍历vector,而直接使用数组形式访问vector来遍历。

for( int i = 0; i < NUM_INSTANCE; i ++ )
{
    m_vpInstancingData[i]->Move();
}

再次执行之后,fps又回归80帧!!

2

对于vector的遍历,一直以来一直都是通过迭代器遍历,但对于大型vector它居然会如此的影响效率,也是到今天才刚发现。但是STL的设计本来就是奔着方便高效的啊,迭代器不至于效率影响这么大吧,可能与Debug模式有关。于是,我做了一个小实验,代码如下。

#include <iostream>
#include <vector>
#include <time.h>

using std::vector;
using std::cout;
using std::endl;

#define MAX_NUM 1000000

int _tmain(int argc, _TCHAR* argv[])
{
    vector< int > vIntList;
    for ( int i = 0; i < MAX_NUM; i ++ )
    {
        vIntList.push_back( i );
    }

    clock_t start, end;
    double duration;

    start = clock();
    for ( vector< int >::iterator i = vIntList.begin(); i != vIntList.end(); i ++ )
    {
        ( *i ) ++;
    }
    end = clock();
    duration = ( double )( end - start ) / CLOCKS_PER_SEC;
    cout << duration << "seconds" << endl;

    start = clock();
    for ( int i = 0; i < MAX_NUM; i ++ )
    {
        vIntList[i]++;
    }
    end = clock();
    duration = ( double )( end - start ) / CLOCKS_PER_SEC;
    cout << duration << "seconds" << endl;

    return 0;
}

在Debug模式下执行的结果
debug

Release模式下
Release

可见,在Release版本中,它们几乎是一样快的。而在Debug版本中,可能因为迭代器需要额外的很多检查工作,所以比数组形式访问慢了很多很多……所以,对于采用哪种方式对vector进行遍历,效率怎样,如果最终是要发布为release版本的,那么这个问题大可不必担心。

Posted in: Computer Graphics, Some Experiences / Tagged: vector, 数组, 迭代器, 遍历

std::bad_alloc?

June 29, 2009 5:34 pm / Leave a Comment / Benny Chen

今天遇到一诡异的Bug,在某段代码之后,只要一执行vector的push_back函数,就报出异常——std::bad_alloc。

bad_alloc?这一般是new申请内存不足而扔出的异常么,内存耗光?绝对不可能。

折腾了很久,最后发现的错误根源是如此的愚蠢。

对于类的一个成员变量 QuadNode *m_pChild[4],我对它的初始化居然是这样的:

ZeroMemory( m_pChild, sizeof( QuadNode ) * 4 );

很显然,sizeof( QuadNode )绝对是大错特错,因为m_pChild是一个指针数组,而不是QuadNode的数组。

其实这个错误不值得拿出来一说,只不过是Bug所表现出来的症状有些奇特,所以留文以作警示。

Posted in: Some Experiences

LinkedIn

Milan Petrovic

Categories

  • In My Life (25)
    • A Day in the Life (8)
    • English Learning (2)
    • Learn a Word (7)
    • Something In The Way (8)
  • Music Heaven (8)
    • Guitar (1)
    • In Concert (1)
    • Lyrics (3)
  • OK Computer (54)
    • 3D (3)
    • C++ (10)
    • Computer Graphics (15)
    • Game Programming (23)
    • iOS (6)
    • Linux (1)
    • Lua (9)
    • My Projects (3)
    • Some Experiences (9)
    • Talking in Code (2)
    • Unity (2)
  • Quotations (2)
  • Uncategorized (1)
  • Visca Barça (24)
    • FCB BJ (5)

Recent Posts

  • [译]优化你的手机游戏(没有延迟的,才是健康的)- 一篇给游戏美术设计师读的文章
  • 新浪微博API for MOAI
  • 稍后继续
  • Unity Developer ++
  • Another Thread @ Moai Forum
  • 1st Day of Golden Week
  • 为SyntaxHighlighter添加新语言
  • 基于Lua的State Pattern
  • Class Diagram of Pacman
  • 基于Moai的Pacman

Recent Comments

  • 约修亚_RK on 为SyntaxHighlighter添加新语言
  • 爱装的小男孩 on 小心DLL链接静态库时的内存错误
  • happyfire on Game Loop的几种实现方式
  • William on 新浪微博API for MOAI
  • Benny Chen on 新浪微博API for MOAI
  • your man on 新浪微博API for MOAI
  • 你家男人 on 稍后继续
  • 逍遥 on 关于对std::vector的遍历
  • papa on Unity Developer ++
  • T客网 ︱ Techpot » Blog Archive » iOS开发与OpenGL ES相关问题整理(1) on iOS开发与OpenGL ES相关问题整理(1)

Tags

2d 3D 3dsmax 3ds max air Apply architecture Asia tour barca Beijing bilbao binary search blocked bob boost bruce springsteen C++ capo CGContextDrawImage Champions League Change DLL DX10 eval exporter flash framework frustum culling game game engine iniesta ios linux lua Moai opengles pacman plug-in plugin 北京 导出插件 崩溃 巴萨 游戏引擎 踢球
© Copyright 2026 - A Game Developer
Infinity Theme by DesignCoral / WordPress