周末杂想

许久没有更新,上一篇文章居然是两年以前发布的。

再往前推一点,从大学毕业开始算,现在竟然一晃已经4年。工作四年中,我只跳槽过一次。跳槽前我在西山居,跳槽后我来到了网易,算一算也有2年了。能够一直在喜欢的行业和喜欢的公司里从事着自己喜欢的工作,我觉得我是一个幸运的人。

虽然我已经年近中年,但我的内心还是个少年人。翻看之前的博客,看到以前的我,我一直没有变,我依旧保持着对游戏和对技术的热爱。我认为Coding是一门手艺活,要不断地练习,才能保证技艺精湛。说到底,还是我能从编码本身中获得乐趣。

但我的一部分还是变了。随着所接触的技术、项目和代码的增多,我变得不那么像以前那样愤世嫉俗。我逐渐意识到大部分的技术是会受到所处时代和环境的影响的,每种设计都有着自己的历史原因和理由。具体来说,我不再讨厌java,也不再那么喜欢C++。但我依旧保留着一个良好的习惯,那就是去研究透彻自己所用到的每种语言。编程语言对于程序员来说就好比工具对于工匠,工匠应该熟悉自己的工具,这是再自然不过的事情。

我变成了一个技术上的从简主义者。我认为简单的方法如果能解决问题,那就不必要搞的太复杂。我觉得if..else..就很好,线性查找就很好,数组就很好。这是懒惰,但并不是天真。简单的方法除了更具扩展性,更好维护,通常性能也更高。我认为复杂的方法应该留给复杂的问题。

我向来不太善于给文章收尾,那就这样吧。

睡不着觉一些杂思

直属老大给我评绩效时写了“执行力很强”,又说“我觉得你目前的问题不是写代码慢,而是有点太快了”,又说“我觉得你应该在写之前多想一想”。这和我想的有点不太一样,我前段时间一直觉得我自己效率低下,会被一些莫名其妙的问题卡住半天,因此浪费了大量的时间。

我一向以来都是个急性子,不过在大学以前也仅仅停留在急性子而已,上到大学以后才开始慢慢转变为“执行力很强”。产生这种转变的主要原因是我比较懒。听起来像是有点矛盾,不过我的懒主要是在于懒得做思考、懒得做计划。像电影,我肯定是要等有好评了才去看。像音乐这种,肯定是有人推荐的才去听。像学习提高、规划人生这种事情,我肯定是懒得去考虑的。

不过我这人胜在学习能力比较强,当有人指出道路后我能够迅速跟上。比如大学常常会在考试前最后两周把整个学期的课给学完,比如发现身边的大牛后果断抱其大腿获取书单,比如你如果今天和我说要去西藏旅游只要我经济允许我明天就能做好出发准备。久而久之以后便练就了“执行能力强”。

这么做也算是个人生选择吧。不做太远的规划,不做太多的思考,不过一旦发现目标后便迅速行动,且坚持。这样做坏处在于,我的成就始终比不过那些善于思考且同样善于行动的人。不过好处在于,这样活比较轻松,也不会累,且同时还能混个中上水平。

说到执行力,又想到了意志力。很多事情执行时是需要意志力去坚持的。从小到大,家父对我的评价都是“意志力极差”,且“非常容易受环境影响的一个人”。好吧,两个我都认。但有时我却惊讶的发现我的意志力并没有我想象中的那么差,我确实可以长时间地进行某一项枯燥的事情。这是个非常微妙的事情,想想看也与执行力有关。比如我今天计划做某事,然后对自己说:去做。于是我便去做了,这对我来说不是很难。当许许多多天的执行积分在一起,便成就了一段相对长的时间的坚持。至于说“易受环境影响”,没错,我确实容易受环境影响。但相应的,当我处于一个好的环境时,我受到的加成也比普通人要多。所以我这人比较懒,与其选择与自己的性格做抗争,不如去选择一个好的环境,轻松愉快何乐不为。

游戏引擎中多线程和网络的一些实践,近日一些小总结

自己整个游戏引擎真心不容易,即使是最简陋的,也需要花费很多的心机。我感觉我的思维就在不断地跳来跳去,今天还在考虑时间轴动画,第二天就发现有些地方需要和多线程装载一起考虑,第三天又跳去考虑网络。果然我的经验还是太浅了,感觉就是把大学没有掌握的知识都过了一遍。

一开始我准备使用python来作为游戏引擎的脚本,主要原因是boost::python已经很好地封装了C++和python的接口,使用起来非常方便。然而在多线程中这就出现问题了。因为Python的虚拟机使用GIL,这个玩意的存在使得Python中基本不可能存在真正的多线程,无法发挥多核的优势。不得已之下我只能转投Lua。一开始使用Lua我尝试这自己封装Lua和C++交互的接口,我希望可以在C++中调用Lua,然后又从Lua中调用回C++某个类的非静态成员函数。我发现要完成这样的工作,封装实在是太蛋疼了,我一度徘徊在崩溃的边缘……幸好我在网上找到了luabind,它使用了与boost::python类似的方法来封装C++和Lua的接口。谢天谢地!生活瞬间变得简单很多。

另外就是网络。我希望我的游戏引擎可以支持小型的P2P局域网对战,也就是类似CS和魔兽的那种。我遇到的第一个问题就是这样的一个引擎如何实现单机游戏。我的想法是,把单机看作是多人游戏的一个特殊版本(受GEA启发)。一开始尝试让服务器和客户端同时运行在一个进程的两个不同线程中,之间使用socket进行通讯。事实证明这样做……是不行的。程序运行起来会出“端口已经被打开”的错误。看来socket只能提供两个进程之间的通信,而在一个进程的两个线程中,由于使用的是同一个socket文件描述符,因此无法打开两次。

但毫无疑问魔兽争霸是一个单进程单线程的游戏。因为即使在游戏最卡的时候,魔兽CPU占用率都不会超过50%(我的是双核)。并且,在开启游戏时,我只能看见一个魔兽进程。我猜测CS就更是如此了,毕竟在那个年代,多核CPU是闻所未闻的概念。突然我觉得我陷入了思维的死路,在单机游戏时(或者说是作为多人游戏的主机时),服务端和客户端可以在两条不同的线程上,但它们完全没必要使用socket通信啊……

网络游戏的流程大概是这样的:1.服务端计算->2.服务端将计算结果包装为游戏事件(或之类的东西)->3.服务端将事件封成数据包,并将数据包发给客户端->4.客户端接包,解封成游戏事件->5.客户端处理游戏事件。如果是本机连本机的情况,步骤3和4是可以去除的,服务端和客户端之间可以直接使用一个双缓冲队列来完成数据交换。应该只需要一个if..else语句,就可以很好地区分是本机还是网络,如果是网络的话还是加入步骤3和4。


昨天是第一天去公司上班实习。不知道为什么,上班真的很累,晚上回到宿舍什么都不想干了。尽管由于我是第一天,一整天基本上是无所事事。我猜可能是因为换了陌生的环境以后人变得比较紧张,精神压力也大的原因吧。大概过几天就可以适应了。公司的环境还不错,饭堂伙食也很好。而且我环顾四周,发现貌似我们组的显示器比其他组的都要大一圈。CPU是i7四核3.5GHz;内存8G;显示器不知道是二十几寸,但我两个笔记本拼在一起应该都没它大。我猜开发组由于经常编译的原因,机器要好一点才行吧。

但……唯一让我蛋疼的是……我们组居然是在……用Java……噢……

对前一段时间的工作和学习的一些回顾

前一段时间的工作和学习基本上包括了三件事。第一件事情是编写自己的游戏引擎Saber Core,目前仅写完了基础的场景管理;第二件事情是阅读并翻译《Game Engine Architecture》,目前翻译到第二章;第三件事是实训,实训的内容是在四周内完成一个小型3D游戏,开发工作现在已接近尾声。

实训虽然我有点心不在焉,每天也很懒,但在做事的时候也是有动脑子的。在我的努力下,我们的游戏的绝大部分地方都完成了参数化,也就是可以通过读取外部XML文件来改变游戏的某些属性。我的本意是将所有游戏场景逻辑全部参数化,这样我们就可以像编写魔兽争霸地图那样编写外部文件,然后再让主程序去跑。不过由于时间(4周)、人员(4名开发人员中有两个Java程序员,写出来的C++代码可以用灾难来形容)和现有架构的原因,我放弃了全部参数化的想法。事实上,如果真的实现了全部参数化,那么我们的程序在某种程度上就更像“引擎”而不是游戏了,因为从模型文件到怪物属性,都是可以通过XML配置的。当然啦,游戏逻辑还是写死在C++里面的。嘛~勉勉强强可以算是个TD(塔防)类的游戏引擎了。

另外就是翻译的那本书。我一般直接简称为GEA了。GEA里面其实挺啰嗦的(各种英文从句,各种对前文的重复),不过如果仔细读的话,确实有很多受用的地方,这也是我翻译的乐趣之一。但随着读书章节的深入(我读书的进度自然是比翻译进度快很多的),以及刚好最近又用OGRE来完成了一个小游戏,总感觉之前了路子貌似有点歪了。我指的是几乎从头开始写一个游戏引擎,好像有些不现实。且不说我对图形学也就是个半吊子,一些高级的特效如光阻塞贴图HDL什么的完全没概念,也不知道该如何实现;但就从模块的角度来看,我是不可能所有模块都自己写的。比如音频,我不可能自己去写解码;比如I/O,我还是要用到DirectInput;比如物理模块,我更不可能自己写了。

国内对于游戏方面也有很多书籍,但大部分都在于教你如何使用OpenGL或者DirectX做游戏的图形部分。GEA之所以好,在于全面。GEA更多的是从一个更高层的角度,来观察游戏引擎中的各个模块如何装拼在一起,如何使整个复杂的系统高效而又井然有序。因此我是否应该停下仿照OGRE实现一个图像引擎(其实这才是我现在正在做的事),而改为使用OGRE作为组件,在其上搭建我的游戏引擎呢?这个问题值得考虑。

吐槽2

1.6章真是巨长的一篇,不过我估计今晚能翻译完吧。

今天在帮组员调试某个模块,他说怪物模型的朝向不对。我在初始化的时候设置了一下朝向,惊人的一幕出现了:其中一半的怪物原地转了个圈,剩下的一半纹丝不动。看到这个情景我真是眼皮一阵乱跳,鼠标滚轮网上滚了滚果然发现了三个重载。好吧孩子们你们有多喜欢重载?我看着大陀长得基本上一模一样的,明显就是Ctrl+C Ctrl+V过去的代码,问:为什么要这样写?难道不知道重复是大罪嘛?答曰:其中一个已经是弃之不用的,只是忘了删。我说:那你不介意我把那个注释掉吧?答曰:哦……哦,应该可以吧。嗯,然后我注释掉了那段代码,编译器果然报错了。

下午看另一个类,越看越觉得各种诡异。组员告知我,这是个单例!我大惊,满世界找getInstance,还真找到一个,不过隐藏得实在是好,让我甚至都不认为它是个getInstance。它长的大约是这样的:

XXManager->getXXManager(Maze* maze);

我有点无力吐槽,说,不是让你们别用单例么?先不说我们这弹丸点大的程序需不需要用到单例,我就说这奇葩的用法……(虽说这错误我也犯过……)

于是我很耐心地说,呐,你这个根本就不是单例,因为你获取实例的函数还要传一个Maze型,如果别人手上么有Maze怎么办呢?所以你应该先写个初始化函数,这般这般,再在这里加个断言,这般这般,然后把这些成员声明为静态,这般这般,很麻烦吧?你还是不要用单例了吧?此时项目经理插话了:单例模式可是使用最广泛的设计模式!好吧好吧,给我五分钟,我怕了你们了。于是我开始大刀阔斧地修了起来。那位童鞋(这个类的作者)看着我把他的类改得面目全非,忍不住说了句:其实为什么不重载一个getXXManager呢?一个需要传参另一个不需要就好啦。

我听的真是一阵头晕目眩差点昏过去。又T-M-D重载,你们脑子里塞的全都是重载吗?但像我这么成熟而又有耐心的人,当然不会爆粗。我很耐心地解释道:呐,如果你写了两个getInstance,那别人怎么知道是要调用需要传Maze的那个还是不用传Maze的那个呢?答曰:如果手头上有Maze就调用需要传Maze的那个。

我!操!这能沟通吗?我当时脑袋肯定是被门板夹了才有如此耐心,还尝试向他解释:你看你单例只有一个,如果我在这边把这个Maze实例传了进去,另一个人在其他地方把另一个Maze实例传了进去,那会发生什么事呢?答曰:在getXXManager中写个if语句,如果已经有Maze了就不许传。

这简直就是#¥%……&*(

最后我说:好吧,你的方法好极了,但看在我已经改了这么多的份上,你就按照我的方法来吧。

近日来的一些小吐槽

minecraft是个好游戏,我是在没日没夜地玩了4天并终于发烧后,将其删除的。

这也是我这么多天没更新博客的原因。为了玩游戏我可是废寝忘食,我甚至在实训的时候也在玩。因此没时间写代码没时间翻译是可以被理解的: )。我突然发现做开发经理还是挺轻松的,也就搭搭环境,设计下架构,在组员遇到难题时指导指导,正因如此我才有时间在上班时玩游戏呀~之前我都是在上班时干自己的事情。

作为开发经理,还需要做的事情就是和策划那边扯扯皮,说说“这个需求我们做不了”啊,“那个需求很难实现”啊之类的。还有就是应付应付项目经理一天三次的“这玩意怎么还不能运行”之类的无聊诘问。项目经理非常希望可以按照需求->设计->UML->编码的过程走一遍,于是我很无奈地和他双手一摊:“你说的这玩意我从来没在实践中用过,因此完全不懂怎么弄,你来教教我们呗~”于是开发经理很愉快地投降了。

实训这么多天我越发觉得Java使人脑残,一次还愤怒地写下了一篇文章“我为什么讨厌java”,不过没有post出来,因为写完后觉得自己图样图森破,也许再过两年看看自己写下的这些话有没有道理吧。嘛,当初在我做设计时两位写Java的童鞋觉得备受冷落,项目经理钦点说要让他们早点加进来。加就加呗。我切了一部分模块过去让他们去设计,他们两个设计的模块接口对不到一起去,还怪我当初没有定好接口。我擦内闹哪样这是,当时我就火起来想要脱口而出“咋不见Linux画类图”。但想我等成熟男士说话之前总要三思,因此我非常无辜地对他们说:“接口文档是吧,你们是想要电子版的还是纸质版的?”

唔,大概就这些吧。接下来要逐步恢复正轨了。玩物丧志,玩物伤身,切记,切记。

顺便一提,minecraft是用java写的,java还真是与我命中相克啊。

camera 3 (外一篇随笔)

我之前的设想是camera自身不带任何位置属性,完全通过父节点来赋予camera的位置和朝向信息。这样做的一个很麻烦的地方在于,操纵节点的方法和操纵camera的方法有些许不同。当我们想改变节点的朝向时,我们会调用节点的rotate方法;当我们想要改变camera的朝向时,实际上更常用的是通过调用SetLookAt()函数或者SetLookTo()函数来改变摄像机看向的地点。

悲剧在于,SceneNode缺少一个类似SetLookAt()或者SetUpDirection()这样的函数。如果是在Irrlicht里面,一切都不成问题。因为在Irrlicht中我们可以派生出CameraSceneNode,然后直接为CameraSceneNode增加相应的方法即可。但在组合的模式下,我能想到的方法无非就以下两种。

一是给SceneNode增加SetLookAt()方法。这样做的话,对于不是camera的东西,我们也可以SetLookAt…

二是只给Camera增加SetLookAt()方法,当调用Camera的SetlookAt方法时,改变其父节点的朝向。这样做的麻烦之处在于,如果camera的父节点还挂载了其他物件,那么所有人都会一起改变朝向。

Ogre是这样解决这个问题的。camera拥有自己的position,lookAt,和upDirection。camera仅从父节点继承position属性,所以camera的真正位置是自己的position再加上父节点的position。对于朝向,camera和它的父节点是分离的。目前我采用的就是这种方法,这样做灵活性也提高了。例如,对于FPS游戏,我们可以直接将人物模型Entity和Camera都挂载在同一个SceneNode上,且将Camera的自身位置调整到模型的头部,非常方便。

顺便一提,虽然Ogre中的摄像机坐标系和节点坐标系相对分离,但所有的SceneNode依旧有一个SetLookAt方法……我猜Ogre应该是为了方便吧。


随笔

从上周起我们开始了坑爹的实训。朝九晚五,严格考勤。如果不是周六实验室不开门,坑爹的实训老师希望我们周六也继续实训。简直就是坑爹啊,让我等从来没有在11点之前起过床的人如何适应!

实训内容是用Ogre随便弄个3d游戏出来。我本来是抱着打打酱油的心态,谁知身不由己,变成了组里的负责人之一。除了要想这想那,还要每隔10分钟被人打断一次问各种问题,弄得时间全无 T_T。一天下来又累的要命,基本上回到宿舍已经不想看电脑。这几天除了翻译翻译文章,自己的项目一点没碰。今天早上起来打开工程,发现脑袋一片空白,完全不记得之前自己写过些啥了 T_T……日啊……

老师说使用SVN作为版本控制器,但是半天没搭起来。我等不及了就自己弄了个Git,结果组里除了我之外没有人用过Git……后来好不容易弄懂了Git的基础用法,老师的SVN终于搭起来了,一堆人眼巴巴的看着我说换回SVN……T_T

最初的时候为了快速开发,我提议使用Ogre的Python版本。后来有组员说Python学的蛋都碎了,还有组员抱怨说Python函数不支持重载。我思量着使用Python来开发大一点的面向对象工程确实比较吃力,就辛辛苦苦把C++版本的Ogre配好了说大家用C++吧。过了一天我去看看那个抱怨python不能重载的组员电脑前看看他进度如何,第一个画面就亮瞎了我的狗眼……一个本身做着create事情的函数叫做get也就算了,居然整整齐齐的有8个重载版本!从两个参数到五个参数不等。好家伙,怪不得抱怨python不能重载。我苦口婆心的说,你看,你这八个函数做的事情其实是一样的,你又不是类库作者,你写的函数还是给自己用的,你何苦花一个上午来写这么多重载……他却很认真地跟我说,可能会用到,可能会用到……当然啦,他之前是写Java的,这我一点都不感到奇怪。搞得我专门去问了另外一个写Java的人,你们Java程序员写程序是不是都习惯由Get/Set函数开始写起……

不过令我欣慰的是,第二天我再看到他的代码时,那八个重载已经只剩下四个了,显然他发现自己根本用不着这么多。我相信按照这个速率,再过两天他就会把剩下的三个也删了。

下一步的一些小计划

前一段时间思绪还是很混乱的关键是不知道自己以后能干啥。之前一路走来做的项目大多与游戏有关,但是后来慢慢地觉得一来老做游戏没意义,二来网易又不要我,所以开始考虑“转行”事宜。

我算是个比较纠结的人。我喜欢华丽的东西,对东西的外观又很看重,以至于大部分项目中我都客串了一把美工。悲剧在于,全世界的前端招的都是学Java,Js一类的人。对于java,我是挺反感的。对于js,我觉得这玩意过于简单。现在是个程序员都会帮自己整个主页,写几段htmlcssjs不在话下。让我拿这个全世界都会的东西养家糊口,实在是很虚。怎么办呢……最熟悉的语言是C++,可要用到这货的大概是在写操作系统,图像引擎,以及处理海量数据吧。鉴于国内没几个公司在做前两者,我只好硬着头皮去看看所谓的后台。

后台也真够蛋疼的,大部分情况是考验你对linux和unix的熟悉程度而不是编程水平。我看了几天的套接字,也自己写了写小验证程序,发现实在是看不下去。然而在一个很偶然的机会,我看到了一个叫做云风的人的博客。这家伙算是国内元老级的游戏开发者了,他的博客上有许多关于网游服务器端实现的讨论。看着看着我还是有很大感触的,原来游戏的服务器端这么复杂,而且和普通的后台服务端有很多的共性。我突然觉得,游戏也是软件的一种,而且还是一种非常复杂且坑爹的软件。也许我不应该放弃对游戏这块的追求,我可以将我从中所学到的知识运用到其他软件的开发中。

前天上网下载了开源的网游服务器端实现Mangos,然而由于一直在看云风的博客所以还没来得及研究。不过在看完云风关于游戏开发的博客后,我反而不想去研究Mangos了。原因很简单,缺乏环境。既然我找不到那种大型的数千个玩家同时接入服务器的环境,那么无论我如何研究都是在纸上谈兵。但是一些思想,比如进程间通信,比如负载均衡,这些都是共通的基础知识,需要从书中找到答案。

所以接下来我应该会干这么些事:
1. 重新把Ogre给安了,好好研究下其中的实现,这应该是目前来说对我帮助最大的。
2. 好好看看Unix编程艺术,Unix网络编程和操作系统,恶补一下自己对操作系统和进程的疏漏。
3. 做做编程珠玑和算法之美里面的题,这个是为了应付万恶的笔试。
4. 无聊的时候阅读下代码大全和程序员修炼之道。

要看很多书

今天是被电话吵醒的,申通快递的人嚷嚷着让我去拿快递,说我刚才给你发短信了你怎么这么晚还不来拿。我对申通一向无甚好感,回了一句你什么j8态度然后就挂电话继续睡。再次醒来发现已经是十二点半,突然有点后悔干嘛不早点起床去拿快递,其实是书,Unix编程艺术,我又要等多一天才有书看了。

这学期我心态变化还是蛮大的。一月份时感觉自己无所不能,意气风发,有种“没有我一个晚上解决不了的问题”的感觉。后来去腾讯面试,被虐了个七荤八素,才知道自己的底子有多么的薄弱,实在不足以支持自己骄傲的资本。再后来的各种笔试面试,弄得我身心俱崔。看着自己手里的小公司offer,再看看身边的人都去了网易腾讯,心中很是酸楚,感觉就要这样被拉开距离了。

幸好认识了亮哥,他说,你要看很多很多很多的书。亮哥算是良师益友这个级别的了,说的很多话都非常有道理。我这个人生性浮躁,最讨厌看书。不过被各种笔试面试虐了那么久,倒也发现了很多东西确实在书里面才有。再看看亮哥,书多得可以把自己埋起来= =。于是在亮哥的“怂恿”下,花了百多块钱买了基本书开始读了起来,没想到这样却发现了读书的乐趣。写代码是灰常累的工作,当你不熟的时候,你感觉写代码就像是在查字典;当你熟悉的时候,写代码又像是在练打字。相比起来看书就要轻松多了,一支笔一杯咖啡。我甚至习惯于在走廊上站着边吹风边看书。也许是之前各种项目的积累,我看书的速度非常的快。书中的知识和曾经的经验一相验证,理解起来非常舒服。

妖都最近风大,在走廊上真的非常舒服。

突然发现我已经一个月没回家了,昨天爸打电话给我,问我五一要不要一起去泡温泉。真是一件非常幸福的事情,泡着温泉看着书,当然是要去的。再过几天我生日,不知道会不会下很大的雨。去年下了很大的雨,和某小朋友一起过了个颇有意义的生日,挺值得怀恋。