2010年8月12日
假设有这样一个管理对象的窗口 ActorManager,其实现大概为
class Actor;
class ActorManager
{
public:
void update()
{
for (actors_t::const_iterator itr = m_actors.begin(); itr != m_actors.end(); ++itr)
{
Actir* actor = itr->second;
actor->update();
}
}
void add(Actor* actor)
{
m_actors[actor->get_id()] = actor;
}
void remove(Actor* actor)
{
m_actors.erase(actor->get_id());
}
private:
typedef std::map<int, Actor*> actors_t;
actors_t m_actors;
};
而Actor类的实现是这样:
class Actor
{
public:
void update()
{
// ...
}
有一天,在给Actor添加逻辑的时候,update函数变成了这样
void update()
{
// ...
update_buff_effect();
// ...
}
再往下
class Actor
{
// ...
private:
void update_buff_effect()
{
// ...
apply_hp(-100);
if (get_hp() <= 0)
{
die();
return;
}
// ...
}
然后……
private:
void die()
{
// ...
ActorManager::getInstance().remove(this);
// ...
}
在写下ActorManager的时候并没有想到会在update循环里删除对象,而实际上却有几次遇到类似的问题。
有些问题没有这么明显,但也都是出在遍历容器对象的过程中,某个执行函数删除了窗口里的对象,从而导致迭代器失效。
修改的方法很简单,给ActorManager添加一个待删除对象列表
在remove方法中并不真正删除对象,而是等到update中循环结束后再删除对象。
代码看起来会是这样:
class Actor;
class ActorManager
{
public:
void update()
{
m_is_looping = true;
for (actors_t::const_iterator itr = m_actors.begin(); itr != m_actors.end(); ++itr)
{
Actir* actor = itr->second;
actor->update();
}
m_is_looping = false;
if (!m_removed_actors.empty())
{
for (removed_actors_t::const_iterator itr = m_removed_actors.begin();
itr != m_removed_actors.end(); ++itr)
{
Actor* actor = *itr;
m_actors.erase(actor->get_id());
}
m_removed_actors.clear();
}
}
void add(Actor* actor)
{
m_actors[actor->get_id()] = actor;
}
void remove(Actor* actor)
{
if (!m_is_looping)
m_actors.erase(actor->get_id());
else
m_removed_actors.push_back(actor);
}
private:
typedef std::map<int, Actor*> actors_t;
actors_t m_actors;
typedef std::vector<Actor*> removed_actors_t;
removed_actors_t m_removed_actors;
bool m_is_looping;
};
没有给add也加保护的原因是,不会在update函数内向ActorManager添加新对象。
当然,有可能在其他地方会有这样的需求,同样也做类似的保护即可。
问题虽然不大,但是几次碰到类似的错误了。记录之,并强制要求自己,
在遇到会对容器内的对象做for…处理时,一定要谨慎的检查一下remove接口。
2010年8月8日
总体来说,如果是想做一个比较简单的虚拟现实服务,拿sfs来做还是很方便的,省去了前期构造服务器网络,实现数据库接口,数据同步等等一些基础功能的时间,可以一上来就直奔主题,开发自己项目相关的功能.
Sfs的接口封装也比较简单,基本上看到接口名就能知道是做什么用的,参数是什么意义,而且他的文档也比较详细,对于非服务器开发专业人员也比较方便.sfs的定义也主要在此,比如他最早支持的flash客户端api.
而随着sfs的成功,也开始将目标转向了目前新兴的iphone, android, sillverlight, unity等 客户端.这次使用c# client api做了一个winform测试程序,使用起来也是非常的方便.
第一次拿sfs做商业项目,也还是遇到了一些问题,总结一下,另外还有一些未完善的地方,后面再花时间继续实现.
CustomLogin的处理在zone extension里,必须先join room才能使用xt message.所以需要先写一个zone extension,在这里处理登录验证,注意验证成功后需要向客户端发送room list, 客户端必须收到room list消息才能做后面的操作,这是sfs限制死了的.
本来我想在extension里让客户端验证成功后直接join room,未果,调试良久才发现,客户端未收到room list,不能join room.
Sfs的user variables只支持bool, integer, string类型,其他类型的数据不能同步.这个问题困扰了我差不多一个小时,最后在客户端一步步跟踪属性同步过程时才发现,其他类型的variable都被忽略掉了.
于是,为了同步float坐标数据,我不得不加上了float.toString(),数据量又大了不少.
Sfs消息定义的方式,不要像sample里那样,直接写字符串名字,改用枚举或者常量定义.每个extension name和command name都只有一个字节,在枚举中也可以定义出128个,完全够用.
这样可以省不少带宽占用量,可以在client上开启debug message开关,看一下一个简单的消息发送会占多少字节.
如果想同时在eclipse和netbeans下对一个项目进行开发,注意文件编码的问题.eclipse下创建的文件默认编码为gbk,netbeans下创建的文件默认为utf-8,而在eclipse下导入文件时他并不会自动检测文件的编码,所以,你需要在文件的属性里手动设置一下编码方式.另外,netbeans下要在文件中显示中文字符只能使用utf-8,使用gbk会出乱码,如果出现此类错误,修改一下文件编码方式以及指定新的编码即可.
要关于利用IDE编译环境.在项目最开始的一周里,我使用trace来进行调试,一次次的通过trace打印出中间变量,然后再修改代码,再启动服务器,再看trace信息……在我快要陷入崩溃绝望之时,终于,决定建一个好用的集成调试环境.在尝试eclipse失败后,我成功的在netbeans中打下了断点,单kh步跟踪,于是,整个世界变得清静多了……
关于如何在netbeans中调试extension,可以参考我之前的一篇文章
sfs中服务器与客户关通信的协议有三种:xml,json和raw string,使用json会比xml节省不少字节,但其仍然占用比较大的带宽开销.如果是数据交互量比较小的应用,这个问题不会太大,但对于即时战斗类MMO来说,可能会成为一个比较大的问题.尝试了一下使用raw string的方式传递结构体,但是没找到比较好的方法,以后有机会再继续,如果可能的话,结合google proto buffer和raw string方式,将会是一个比较好的方案.
在处理extension message时,按照示例所提供的方法,先取出cmd,然后使用equal的方式一个个进行比较,然后转到对应的处理函数.对于消息种类比较小的应用来说还没发现问题,但是对于有大量自定义消息的应用,这里就需要修改一下了.
Sfs的db extension目前看起来只能在当前线程中处理,并且是阻塞式的,暂时没有尝试使用多个线程操作数据库,也没有把数据库操作改为异步操作.这也是未来需要进一步改进的地方.
暂时不清楚SFS是否会为每个room开启一个单独的线程,或者是每个extension一个线程.如果zone里房间数比较多的话,分多个线程处理也是需要的,未来也需要继续考虑一下多线程的问题.
Sfs的实现是把一个room当成了一个广播单元,这对于开房间类的游戏来说没有问题,但是如果相用它来做MMO就需要注意一下,因为user variables的同步是以整个room为单位的,也就是当一个房间里人数达到几百人甚至几千人的时候,某个玩家的进入和退出房间消息,属性修改消息的广播量都会是巨大的.但是在没有源码的情况下想要修改这个不大容易,也就是限制了sfs的应用环境.
2010年8月7日
这本书虽然是讲述.net框架设计的一些规范,不过仍然有一些通用的设计准则可以参考
命名规范:
这些只有在用于公开暴露给外界的API时才是必需的
标识符大小写规则:
1.要把PascalCasing用于由多个单词构成的名字空间,类型以及成员的名字
2.要把camelCasing用于参数的名字
3.不要把闭合形式的复合词中每个单词的首字母大写,比如 callback, endpoint 等等,可以查阅英语词典来确定复合词是不是闭合的
4.不要使用匈牙利命名法。原因有几点,一是发明它的ms公司都已经明确要求在新的库在不要使用这种命名法,二是变量名前加类型标识符是个很不好的习惯,在开发过程中有可能会随时修改这些变量的类型定义,三是新的编辑器中不需要用m_前缀来确定其类型,不过对于内部实现的变量来说,用一个前缀也许会让变量的查找更方便,比如用一个_前缀
5.不要使用未被广泛接受的首字母缩写词,如何确定某个缩写词是否众所周知有个好方法,到google上搜索一下,如果前几条都是你所期望的内容,那么它就是众所周知的了
关于命名:
1.要用名词或名词短语来给类和结构体命名,使用PascalCasing的大小写风格,类名字不要加 C,但是接口前需要加 I,这是个特例
2.用形容词短语来给接口命名,在少数情况下也可以使用名词或名词短语
3.考虑在派生类的末尾使用基类的名字,比如 class FileStream : public Stream
4.用动词或动词短语来命名方法,比如 int CompareTo();
5.要用肯定性的短语(CanSeek而不是CantSeek)来命名布尔属性,可以加Is,Can,Has等前缀,要确保使用时的测试语句读起来通顺,比如
if (collection.Contains(item)) 就比 if (collection.IsContained(item)) 要通顺得多
此外,要优先选择主动语态而不是被动语态,比如
if (stream.CanSeek()) 就比 if (steam.IsSeekable()) 要强得多
6.要用现在时和过去时来赋予事件名以之前和之后的概念,不要用Before或After这样的前后缀,比如 Closing, Closed而不是AfterClose
使用规范:
1.优先使用集合,避免使用数组
2.考虑使用不规则数组,而不要使用多维数组,也就是优先使用int [][] jagedArray这样的数组,避免使用 int [,] multiDimArray这样的类型
3.要用最泛的类型来作为参数类型,大多数以集合为参数的成员都使用IEnumerable<T> 接口
2010年8月6日
困扰了两天的问题终于解决,原因只是我的显卡驱动太老,安装最新的驱动就一切OK了,汗
如果你也遇到了同样的问题,不防也试试最新的显卡驱动,也许可以省下两天的郁闷时间
一个是在InitEGL中调用eglMakeCurrent时,会报告EGL_DEVICE_LOST错误,我尝试着将这个消息忽略掉之后没有任何问题
另一个是在Draw中调用glGetIntegerv(GL_VIEWPORT, (GLint*)&viewPort);时返回的viewPort值是个错误值,我又尝试着为viewPort直接再赋一次值,{0,0,480,800},又OK了
不过这只是部分OK,Sample跑起来后我只能看到变化的背景色,cube们哪去了?
翻遍了bada的forum也没有遇到同样的问题,只是看到几个同样说EGL_DEVICE_LOST错误的,也同样没有找到解决方法
忽然间,我想会不会跟显卡有关,我这新装的win7系统还没有装过显卡驱动,用的是自带的。于是,下载,安装,再打开bada IDE,于是,这个困扰了我两天的问题就这么神奇般的消失了……
最后,贴张运行效果图以示纪念,虽然只是Sample的效果图,没有我的半行代码,不过这也困扰了我两天,不是么
2010年7月22日
结束了两个星期的用trace进行SmartFoxserver Extension调试的痛苦历史之后,我决定再来尝试一下怎么在IED环境下进行远程调试。
上一次打算在Eclipse下进行,不知道哪一个环节出错,没能成功,这次改用NetBeans吧,正好赶上NetBeans新版本发布,来试用一次。
按照这里描述的方法,很快把Eclipse下创建的工程导入到了NetBeans下,不过遇到了点小问题,一堆的乱码。检查了下发现,Eclipse下创建的文件编码为ANSI,在NetBeans下不识别其中的中文注释,把文件改成UTF-8后问题解决,可后来我再试图在Eclipse下打开这些文件时问题又出现了,Eclipse只识别ANSI编码的文件?似乎不大可能吧,不过一时也没找到解决方法,暂时放弃,改用NetBeans吧。
按照论坛上的方法做就行,不过可能因为版本更新的原因,实际做的时候还是有点差别
Step 1
Download the Netbeans IDE for java development. (47mb SE version should be fine!)
这一步就是这样了
Step 2
Install and open Netbeans. Create a project from existing java source pointing to where your extensions are. Should be placed in src subfolder to be nice.
(I for sure could not make it work if the source was not in a subfolder)
Right click on project and choose properties->libraries. Add all jar files from your smartfox installation lib dir.
在NetBeans下导入原来Eclipse创建的文件,只需要src目录即可
导入libraries时注意,按照这里的方法,只需要导入3个jar文件,就是这样
Step 3
Goto Files tab. Expand nbbuild.xml. Click on -post-compile target.
Copy and paste the following code: (Note: adjust todir to point to your smartfox program extension directory)
<copy todir="C:\Programmer\SmartFoxServerPRO_1.6.6\Server\javaExtensions">
<fileset dir="${build.dir}/classes/"/>
</copy>
我的NetBeans下没有nbbuild.xml,倒是有一个build.xml,不过里面没内容
再看了下,原来引用的build-impl.xml,上面有个简单的介绍,在build.xml里添加如下内容即可:
<target name="-post-compile">
<copy todir="D:\SmartFoxServer\Server\javaExtensions">
<fileset dir="${build.dir}/classes/"/>
</copy>
</target>
根据你的SmartFoxServer安装目录进行修改即可
Step 4
Create a new batchfile called SmartFoxDebug?.bat
Copy and paste the following content into it: (Again adjust to your installation directory)
C:\Programmer\SmartFoxServerPRO_1.6.6\Server\wrapper.exe -c "C:\Programmer\SmartFoxServerPRO_1.6.6\Server\conf\wrapper.conf"
这一步不需要,在我安装的目录下直接有个smarfoxService.bat批处理文件,用它就行了
Step 5
Add the following lines to your wrapper.conf
Code:
# runtimedebug
wrapper.java.additional.3=-Xdebug
wrapper.java.additional.4=-Xnoagent
wrapper.java.additional.5=-Xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=n
这里的意思是要在SmartFoxServer里开户远程调试的监听,在我安装的SFS版本里原来比这多了一项配置,所以最终是这样的:
# Java Additional Parameters
wrapper.java.additional.1=-server
wrapper.java.additional.2=-Dfile.encoding=UTF-8
wrapper.java.additional.3=-Djava.util.logging.config.file=logging.properties
wrapper.java.additional.4=-Xdebug
wrapper.java.additional.5=-Xnoagent
wrapper.java.additional.6=-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n
Step 6
Choose Run->Build main project (F11) in Netbeans.
If build is ok, click the batch file. Now select debug->attach debugger and choose port 8888. Debugger console should say "User program running".
Now you can place breakpoints and trigger your extensions normally.
这里就是最终我们要实现的效果了,挂接SmartFoxServer的远程JVM进行调试,配置项在 调试(D) 下的 连接调试器(A) ,如果你跟我一样安装的是中文版NetBeans的话
贴张图,就是这样,注意端口号,是上面配置的那个,不是SFS对客户端连接的监听商品号9393哦
好了,先启动SFS,用上面说的那个批处理文件,你会看到一行提示信息,JVM远程调试的监听已打开
然后连接调试器
在NetBeans里打个断点看看,O了
2010年7月21日
无边界程序设计理念这个提法来自于这里。
“Android的应用只是一个虚的概念,并没有实际的入口,这个不像Window平台上的应用程序的概念,Android更多的是提供组件(Components)的概念。突出请求和服务,突出组件个体,弱化边界,系统的各个组件可以自由的无边界的交流,服务请求者直接发出请求,不论这个对象在何处和属于谁的,组件是自由独立的个体,一个应用程序可以直接请求使用其他的应用的的组件,这个是Android应用框架设计的核心理念,其他的一切都是在为这个核心理念服务。”
“让程序员忽略应用的概念,甚至彻底的抛弃进程这样的概念,程序员看到的就是一个一个的组件,应用程序员利用这些组件来架构成一个所谓的应用,那么设计者首先要考虑的是什么呢?我想应该是一个抽象的应用模型,在这个模型下产生概念和接口。”
“Android中你可以开始一个Activity,但是没有权利消灭一个Activity,这是个体权利的体现,个体的消灭是由系统决定的,这个就是Android中Activity蕴含的人文意义。”
这段总结说的非常好,从我近期对Android开发的一些基本了解来看,也确实是这样。在Android平台下开发,你不需要从头开始构建一个应用,你可以使用系统提供好的一些功能,或者是别人的应用程序中已实现的部分功能,拿来组装自己的应用,当然,你做好的应用也可以把部分功能暴露给别人来使用。具体说来,就是Activity与Intent的使用。
基于这样一种模式,Android开发更像是堆积木,当然,这是理想状态下的。Google最近不是就推出了一款通过web上的组件拖拽就能生成一个自定义的应用程序的工具么,当然,我只看了下介绍,还没有机会亲自体验,不过,这终将是未来的趋势。
当然,这对于开发人员来说是好事,你可以更加快速的把自己的想法变成现实,你不用亲力亲为的去做每一件细小的事情。
对于手机使用者来说,这同样是好事。使用统一的Activity构建出来的应用,不论是外观还是使用方式上都是完全统一的,用户拿到一个新应用时基本上不会有太大的使用方面的成本。
而对于Google来说,这当然也是好事。Google可以把他的云计算服务都做成一个个的组件,应用开发者们可以随意组合上自己的应用中,这对于Google推广自己的服务将会是一件非常有益的事。
当然,目前来说,组件的提供还并不算完美,现在来开发一个应用,还是需要自己做很多工作,仅有少部分服务可以直接调用Google提供的Intent,而调用别人的Activity很难得到保证,比如对别人的应用的依赖,比如接口的保证,等等。另外,目前各手机厂商在定制Android时都提供了自己的UI sence,而各家也都不一样,再加上Android原生的,这也使得UI的统一越发的难,不知道未来将会如何处理。
2010年6月29日
明天一定是属于移动互联网的,这点已越来越不容否定。
在移动互联网到来的时候,能够做点什么呢?一点不成熟的想法,简单记录之。
门户,让每个用移动终端设备的人,不论是iphone,android这样的智能手机,还是ipad,平板,抑或是WebOS,Google,MeGoo的上网本,他们打开设备后第一个运行的软件,门户软件。
从这里,他们可以使用移动互联网的绝大多数服务,比如说,查找本地信息,浏览感兴趣的新闻和博客,看看好友们的最新动向,等等。他们不需要打开浏览器,不需要记住一个个烦人的网址,也不需要在Google或是Baidu输入一些精挑细选的关键字,他们所要做的仅仅只是打开这个门户软件,然后,一切尽在眼前,而且,你使用这个软件的次数越多,他越能了解你的脾性,越来越能猜到你想干什么。
UCWeb在这方面已经先走了很大一步。现在的ucweb已不再只是一个单一的浏览器,打开UC,你已能如我前面描述的那样,完成大部分想做的事,包括新闻聚合,包括博客订阅,包括生活信息查询,当然也包括QQ,MSN等聊天工具的整合以及校内,开心网等入口。
基于地理位置的信息提供,可以与娱乐相结合,做成一个集社交、生活于一身的休闲应用,你可以在这里找到你感兴趣的一些信息,而且,是根据真实位置过滤好的,也就是说,你看到的绝大多数信息都会是你感兴趣的。
在机锋上,找到一个叫帮飘信的软件,有那么一点意思。
还有,完全娱乐化的社区平台,可以利益于移动互联网的优异特性,随时随地都可以接入,不用花太多时间,与认识的不认识的朋友有那么一点交互,比如在地铁上,临睡前,办公室开小差的时候,都可以。内容简单但不失乐趣的娱乐小应用。
人人网,开心网,白社会,Facebook,或者是摩尔庄园,浪漫庄园,再或者是各类三国web game,其实,这些本身都可以从智能终端设备接入,当然,除了iphone以外。不过,这些专为pc设计的娱乐平台到了手机上还是会有些问题,包括视觉效果的问题,交互性的问题。我觉得,做一个专为移动终端平台设计的摩尔庄园,嗯,挺有点意思。
2010年6月22日
想捣腾一下Android开发,结果第一个问题就纠缠了好久。
关于TabHost的大多数例子里都是把所有的Tab页设置到了同一个Activity,而实际制作中我们希望为每个Tab页使用一个不同的xml布局,正好Android Tutorials中有一个Tab Layout的例子,可是Google的人漏掉了很重要的一个步骤。
例子在android-sdk目录下 docs/resources/tutorials/views/hello-tabwidget.html
按照Tutorial做完后,始终无法正确运行,尝试着不断修改,当把TabContent指定为xml中配置的view时就不会有问题,但我希望每个Tab页是一个单独的单元,有自己的xml布局,有自己的Activity类实现。终于,在继续求助于Google之后找到了问题所在:需要为每个自己定义的Activity到AndroidManifest.xml中声明一下,具体就是这样:
<activity android:name=".ArtistsActivity"></activity>
<activity android:name=".AlbumsActivity"></activity>
<activity android:name=".SongsActivity"></activity>
上面的name是自己声明的Android类名,做相应的修改即可。
这里有一个详细的说明:
http://stackoverflow.com/questions/2209406/issues-with-android-tabhost-example
Google Code上也有人贴出了这个issue,并且有解决方法:
http://code.google.com/p/android/issues/detail?id=4183
也许你也遇到了这个同样的问题,希望能少走点弯路 :)
2010年4月21日
“游戏分两种,一种是生活中玩的,另一种是生活在其中的”这是DOOM启世录的第一句话。
“约翰。罗梅洛,王牌程序员”罗梅洛的第一个称号,虽然是他自封的,但绝不过誉。当然,在他遇上卡马克后他也会变得谦虚起来。
卡马克,一个进过少管所,也呆过天才班的神奇小子兼只上过两个学期大学的火箭科学家。
天才程序员:
罗梅洛在他还是孩子时,就给自己的公司取好了名字:顶级思想软件 Capital Ideas。雷恩,另外一个听着重金属,看着地下漫画,玩着游戏长大的叛逆程序员,一样有他的公司,不过与罗梅洛一样,也是公司里的光杆司令,他的公司叫蓝山科技 Blue Mountain。
当他们俩相遇的时候,两个光杆司令合并成了一家新公司:深思软件 Ideas from the Deep,也就是ID的前身。
罗梅洛,卡马克,雷恩,这三个程序员在软盘杂志社相遇的那一天,也同样开始了PC游戏史上的一段传奇历史。很快,王牌程序员就领教了天才小子的厉害。天才小子也欣赏王牌程序员身上的艺术气质,还有他的设计创意。
汤姆,二十五岁的程序员,与罗梅洛一样,也是个喜剧演员,同时也是一个优秀的游戏设计师,还是一个不错的超现实主义漫画家。
艾德里安,一个黑色主题艺术家,他其实只想在软盘杂志社做个兼职美术师,赚着比其他地方稍多点的薪水,直到他遇到了罗梅洛跟卡马克。
机会永远只会留给有所准备的人,这话一点不假。
翻一翻他们的履历,也就不会憎恶上帝太过于眷顾这群小伙子。
走出第一步:
在那个晚上,汤姆与卡马克山寨了超级马里奥,卡马克的天才技术让另外两位程序员不得不叹服。
“罗梅洛被这突如其来的惊喜惊呆了,他动弹不得,甚至站都站不起来。直到几个小时后卡马克回到办公室,他才有力气说话。他只有一件事要告诉他这个朋友,这个编程天才,这个绝配般的搭档:
‘不用想了,我们走人’”
当然,这只是罗梅洛的一句冲动话,就像他以前经常有过的那样,不时的会冲动。
可这一次,罗梅洛的冲动开始当真了。
“1990年9月20号这个宿命的早晨,这个早晨对他们俩都是一个意义重要的时刻。卡马克用他非凡的专注解决了一个迫切紧要的挑战,罗梅洛则预见了这成果可能带来的一切。就仿佛,卡马克制作好调色板,然后罗梅洛用这调色板描绘出一个未来。”
“他们已经有了一个梦幻团队:卡马克,天才小子和图像领域的领头羊;罗梅洛,游戏制作的多面手和公司的啦啦队长;艾德里安,沉迷于黑暗主题的艺术家;汤姆,游戏设计师和超现实主义漫画家;还有雷恩,虽然不那么令人满意,但也还是个不错的程序员。”
不过冲动归冲动,生活还要继续。
天才小子们的价值在还没有被认可之前,他们也一样要打工挣钱买盒饭。
所以,与很多的创业团队一样,他们“借”用着公司的资源,做着自己的事。在那个时刻,PC机是奢侈品,想要回家自己干都是一件非常困难的事,于是,他们便在周末把公司的电脑“搬”回家去干。
终于,指挥官基恩在他们手下诞生了。
不过,这还只是迈出了第一步,一小步而已。这时可不能轻举妄动。
有句话说的好,
公司的事再大也是小事,
自己的事再小也是大事。
为未来的机会作好准备,怎么去做?
当然不是说在公司里拿着薪水却干着自己的私事,但是,每天八小时之外的时间,有没有好好利用起来,这也就决定了人生不同的走向。
找对好搭档:
罗梅洛,卡马克与艾德里安,可以称作是ID的铁三角。
而罗梅洛与卡马克二人又是这铁三角里的重心所在,也是游戏合伙人搭档的典范。
“罗梅洛和卡马克在性格上有个很显著的不同,那就是他们对待时光的态度,正是这种内在秉性的差异,使他们成为最佳拍档,也使他们无可挽回的决裂。
卡马克只活在当前时刻。专注,是他力量的源泉。
罗梅洛则完全相反,他沉浸在所有时光里:过去,现在,将来。他不止是充满激情,他还付诸行动。”
这一静一动的关系正好互补。
不仅如此,卡马克的技术与罗梅洛的创意更是天才搭档。
手术师约翰,被看作是地球上有史以来最牛B的程序员。
引擎师约翰,被誉为摇滚之神,这当然不是因为他的摇滚乐。
当卡马克创造出了新的技术,罗梅洛总能发现这技术的应用之处,并能把他的潜力挖掘到最深。而当后来罗梅洛离开ID之后,卡马克只能失望于他所创作出来的史上最强大的引擎,却没有人能来推动他。
“尽管卡马克从未公开表示过他怀念过去的ID,怀念罗梅洛那令人眼花缭乱的构思和设计,但卡马克清楚地知道,现在的ID已不再是从前的ID,现在的ID已经没有了自发的动力。”
不过,太大的性格差异最终也会产生问题,
“罗梅洛想要建立一个帝国,而我只想安静的写程序。”许多年以后,当卡马克回忆他与罗梅洛的分离时,他这样说道。
我们所看到的,所传诵的,永远只是天才的故事。
自己不是罗梅洛,当然不敢奢望能与卡马克这样的天才合作。
自己也不是卡马克,于是也找不到罗梅洛这样的伟大设计师。
但是,在寻找搭档的时候,一个与自己能够优势互补的人会更合适。
另外,性格上有一些差异也能让团队未来不至于走向一个死胡同,至于最终的决裂,这个可以进行控制。
一定会有挫折:
与所有充满激情的创业团队一样,他们的第一次也遭遇了挫折。当他们借用公司的机器,经过几个周末的通宵努力,把PC版的马里奥寄给任天堂时,得到的答复却是:小伙子们,干的不错,但我们对PC机没有任何兴趣。
没关系,机会一定会降临到那些有所准备的人身上。
虽然任天堂拒绝了这几个天才小子的热情,但终有人能看出这群天才们的价值。
米勒主动联系上了罗梅洛,并向他展示了共享软件市场的美好前景,并且给罗梅洛开出了35%的高比例分成,而且还提前为他们支付了订金。
把游戏按照共享软件的模式去卖,先出一个免费的关卡,如果还想继续玩后面的内容,那么,付钱。这种模式在现在仍然还流行着。
由此,继续在周末借用着公司的电脑,指挥官基恩诞生了,这是他们的第一笔收入。
当然,在他们“借”用公司电脑的同时,他们也没有忘了给公司做一些东西,以对得起拿的薪水,还有不引起老板的注意。也在此时,第一次出现了有人离开,雷恩,已经多次掉队的成员,罗梅洛没有显示更多的宽容。
当指挥官基恩上市以后,ID每月的收入为一万到两万美元,这是在1991年,而那一年,卡马克二十岁,罗梅洛二十三。
最终,ID有了三位创始人:罗梅洛,卡马克和艾德里安。
在遇到重大选择的时候,每个人都有自己的出发点,自己的判断标准,应该尊重这些不同之处。虽然汤姆和杰伊一开始没有走出来,但他们仍然是朋友,很好的朋友,每个周末仍会来ID玩玩龙与地下城。以后的日子里,他们还会走到一起。
培养出默契:
在ID正式成立后的第一个项目,德军总部3D开发的日子里,卡马克与罗梅洛的默契正式开始建立。
“卡马克不是一个多愁善感的人,他的记忆里没有多少感情方面的东西,但他记住了这个画面,记住了这不同寻常的一刻,以后的日子里,他愿意回想起:在一个暴雨滂沱的夜晚,罗梅洛淌过齐腰深的河水,只为了赶回来工作”
搭档之间的默契,正是通过这样的小事,通过并肩作战培养出来的。
当然,默契也不代表任何时候都能够统一。
在制作德军总部3D的过程中,ID的人第一次在游戏设计上发生了的分歧。汤姆试图游说卡马克给场景中添加可以缩进去的墙,而卡马克以破坏软件设计为由拒绝添加。当罗梅洛也试图再次说服时,卡马克的回应是:这件事提都不用提!
是的,在ID这样的梦幻团队里,在天才程序员卡马克那里,依然也有程序员不能做,或者不愿意做的事。程序员都是固执的,有时候带有很明显的偏见。
不过,如果你能让他自己认识到这事是应该做的,他也很乐于被你说服。最终,卡马克在某个晚上主动做好了一个可以缩进去的墙。
让合适的人做合适的事:
在指挥官基恩占据共享软件排行榜首位一年之后,瑞恩来到了ID,并告诉他们,卡马克的技术在业界是首屈一指的,公司应该有人来彻底发掘这技术的价值。最终,瑞恩成为了ID的经理。
让合适的人做合适的事。
王牌程序员罗梅洛虽然是一个充满激情的多面手,但他最在行的还是游戏业务,还是设计师,还是程序员,关于怎么打理生意,当然需要更合适的人。
卡马克,永远只爱写他的程序,永远不在意生意那摊子烂事。
汤姆,这时汤姆终于加入了进来,他那源源不断的奇思妙想,使其当之无愧的成为了ID的主设计师。他也有他最重要的事要做。
所以,找一个CEO,ID不止一次找过CEO。
“就像所有艺术家和程序员一样,他们不喜欢为生意上的事情烦心,他们更乐于专注于自己的领域。而且,他们越投入游戏制作,他们就越厌烦那些堆积如山的例行公事。公司需要有个新的负责人,这次,他们又主动去给自己找CEO了。他们的老朋友,杰伊,以5%的股分加入了ID。”
理性人的思考应该是以利益最大化为目标,让合适的人做合适的事显然能让利益最大化。
当然,每一个有心做事的人,或多或少的都有一些野心,有那么一些控制欲,如何协调好这个关系,需要费点心思。
每个人都是舵手:
指挥官基恩,出自汤姆的设计,包括故事的背景,人物的形象,还有那些游戏特性。这时,卡马克与罗梅洛忠实的做着手术师:程序员,让汤姆的奇思妙想得以实现。
而下一次,ID的舵手转移到了罗梅洛身上。
德军总部3D,可以说是早期主视角3D游戏的最有影响力的代表作之一,这一切的灵感都来自于罗梅洛,而这天才的实现则出自卡马克,在这个最激动人心的时刻,他们只有四个人,罗梅洛,卡马克,汤姆和艾德里安。是的,很多名字已经成为了历史。
德军总部3D的巨大成功让ID正式独立出来,成为一家完全独立的工作室,自己负责从开发到发行的所有事务。但工作室成员还是那么几位。
到下一次,DOOM的时代,公司的重心转到了卡马克。
每一个游戏制作人大概都会有一个理想中最完美的游戏,让每个人都能实现自己的理想也许不大现实,但是,让每个人都能充当一回舵手,带着大家向自己指引的方向前进一段时间,这将会让每个人都觉得自己的人生更有意义。
但是,每个人做事的风格不一样,这里面也会有问题。
卡马克的固执会把主设计师的设计文档骂得一无是处:“游戏的故事背景,就好比色情片里的情节,虽然要有,但根本不是关键”,也可以随意更改目标,让汤姆的设计文档变成废纸。
但是,罗梅洛却能很好的找到自己的位置,充分发挥着天才程序员创造出来的东西,试验着新引擎的特性,挖掘其潜能。
终于,没过多久,卡马克向罗梅洛建议解雇汤姆,这意味着,公司将第一次出现创始人的离开。
一个合理的保护制度:
早在ID成立之初,他们就坐在一起讨论过,所有人都诚心地同意,ID的命运,而不是他们任何人的命运,才是最重要的。无谓的内耗会给公司带来多么致命的伤害,他们不希望ID毁于这样的事情。
他们达成两点协议:首先,解雇一个持股人需要公司其他持股人的一致同意,那时,艾德里安,罗梅洛,汤姆,卡马克是最主要的持股人,凯文和杰伊也有少量股分。其次,一旦被解雇,这个人就失去他所有的股分,公司的未来和他再没有任何关系。
罗梅洛希望能再给汤姆一次机会,他说服了卡马克。可是,没有人再能忍受汤姆,所有人都要求马上解雇汤姆。罗梅洛只好同意。
在全体持股人付方上,汤姆走进去的时候,大家都低头坐在桌边。卡马克开口说到,公司显然不能再这样下去了,我们要求你离职。
汤姆的离开需要一个继任者,37岁的桑迪来到了ID。他接手罗梅洛的事,成了ID的游戏设计师,罗梅洛又回到了程序员,兼音效师,兼打理生意。
什么时候能够收手:
DOOM的成功让ID成就了事业上的辉煌,但也造成了罗梅洛与卡马克的分离。罗梅洛不仅想把公司做大,他还有其他动机,乐趣。罗梅洛热爱游戏,他活着就是为了玩游戏,他现在的生活就是DOOM的世界。
卡马克看着屏幕上闪烁的光标,沉默不语。曾经有多少个夜晚,罗梅洛就坐在他身边,和他一起完善引擎,和他一起调试排错,直到东方渐白。今夜,卡马克目送着那印有“制作者”的背影消失在门口。
创业什么时候是个头,什么时候能够坐享成果。毕竟这么努力是为了有更好的生活,不是每个人都能做苦行僧,当团队有了一定的成果时,应该允许大家选择新的生活方式,不应该太过于苛责。
在罗梅洛离开ID后曾经评价过卡马克,说他是苦行僧。虽然这时他的言论带有一些偏见,但也能反应一些事实。
“在公司有钱以后,依然把公司弄的非常简陋,依然不懂得享受,还是日夜达旦的在那里写他的程序,最要命的是,依然还要求别人也如他一样。”
应该清楚,辛苦努力的目标是什么。
不是每个人都能让自己的一生满是辛苦,我想,大多数人愿意这么辛苦的根本目的还是希望能够在不久的将来不需要再辛苦,所以,什么时候收手,这也是值得讨论的。
分开不一定是坏事:
在罗梅洛忙于死亡竞赛的时候,卡以克为ID扩充了两位新人:图形学泰斗亚伯拉什和关卡设计师麦基。
卡马克的苛责终于让设计师们不堪忍受,罗梅洛拨通了好朋友汤姆的电话,打算创立一家以设计为主导的公司。麦基,也不再是神童麦基,而是孤独者麦基。
Quake刚结束,罗梅洛在ID的日子即将结束,卡马克觉得他没有尽到他的工作。虽然艾德里安试图反对,但谁让卡马克才是ID的价值核心呢。如果卡马克离开,那么ID将不复存在。
卡马克与罗梅洛之间的分歧太大了,对于什么是制作游戏以及游戏应该如何制作,他们都有各自的观点;卡马克认为罗梅洛已不再是程序员,罗梅洛认为卡马克不再是玩家;卡马克只想有一个小公司,而罗梅洛想做大。
当卡马克看着罗梅洛毅然转身离去时,他没有看到一丝留恋或悲伤,相反,他觉得这对罗梅洛是一种解脱。卡马克相, 罗梅洛又踏上了新的征程。
也许,每个人都有他自己对未来的期望,每个人都有他对如何做好一件事的看法,不一定能够获得完全的认同,求同存异,当差异实在太大时,分开其实对每个人都是最好的选择。
两天后,罗梅洛在ID的办公室告诉世界,我已决定离开ID软件,成立一家有着不同目标的游戏公司,我不会从ID带走任何人。
第二天,卡马克更新了他的日志,罗梅洛已离开ID,我会竭尽全力。
一场死亡竞赛曲终人散。
2010年4月19日
Dharmesh Shah的文章原文我没有找到,不过GameLook上的几点精华归纳也足够了,很值得参考。另外,这篇文章再次提到了合伙人章程这本书,可惜dangdang上依然缺货 :(
游戏目前已经不是个人英雄能够全盘搞定的项目,所以,合作是必然的。当然,如果有足够的魅力,凭着自己的个人品牌价值,或者足够打动投资人的策划案,直接拉来投资,然后雇佣其他人来干活,这样就不用考虑创始人合作的问题。但是,对于绝大多数的技术创业人员来说,这还不是那么的靠谱,还是需要与合伙人一道从零开始努力。
合伙人之间面临的第一个问题,也是最重要,最根本的问题是如何分配利益。
当然,你可以很容易的说,根据付出来分配收益。可事实上,这个的可操作性并不强,付出如何衡量,每个人的重要性如何界定,这都是很难量化评价的。很多时候,这大概也只能靠大家来协调,来让每个人都能基本满意吧。或者,有那么一两个有魅力的合伙人,来给大家算上一笔明白账,并且让大家能够相信这个未来确实是美好的。
至少,很多人的建议都是,平均分配不是一个好主意。
如何分配利益,我有过一个粗浅的想法。
每个有意合作的人,拿出一个对未来三年后的最低利益期望,这个期望值要比自己在目前工作情况下,所能获得的最大收益要高,具体高多少那就是自己的预期,另外再有一个稍高一些的期望值。我想,如果是诚心合作做事的人,这个期望值的提出不会太离谱。我也认为,有可能在这些人的期望值之间获得一个平衡。当然,这需要协调者做比较大的努力,也许在这个过程中会有人因为不满意而退出,但从一开始达成平衡要比将来再扯皮要好得多。
第二个问题,如何做决策的问题。
先不考虑在引入投资人后的投票权问题,单单只说合伙人之间。最乐观的期望是让每个人都能参与到决策的过程中来,让每个人都真切的感受到自己在创业的核心里。但实际上,这实行起来可能也是不大可能,或者效率会很低下,或者引发的矛盾会更多。
也许,一两个强有力的决策者能让这个过程更顺畅,但是,这个决策绝对不能影响到其他每个人的利益。
也许,找对了人,这并不会是多大的一个问题。
然后是成员离开的问题。
看到这一点,我又想起了DOOM启示录。这本书也是一本好书,我认真的翻过两遍。如果失去了做游戏的激情,那就看看这本书吧。看看那一伙充满激情的年轻人是怎样走到一起,怎样通力合作,怎样创造出一个又一个奇迹,当然,也要看看最终他们是遇到了怎样的问题而不得不分道扬镳的故事。
对于每一个合伙人,即使有一天不能再一起合作,不能同时走到最后,那也依然还是非常好的朋友。至少,当初是因为相互信任,有共同的理想与目标才走到一起的。罗梅洛和卡马克在很多年后不也还是共同走进了那个会场么。当然,这个期望也可能太过于理想化,看看业内的情况大概就能知道,一旦离开,基本就成了对手,这还是比较好的情况。不过,期望总是美好的。
如果真的很不幸,有人必须要离开,不管是出于怎样的原因,主动的还是被动的,当然要保证留下来的人和留下来的项目不受损害。不论离开的这个人是谁。罗梅洛从id的离开其实也是很友好的,甚至可以说是多赢的。这种方法也未尝不可以借鉴。
有关每个人投入的问题。
这个可能会比较棘手一些。尤其是当合伙人中有人面临比较大的生活压力时,比如有家族,有小孩。这个又涉及到另外一个问题,什么样的人适合创业的问题。
并不是每一个想法的实现都能一帆风顺,也许这个努力奋斗的过程会相当艰难,也许在很长的一段时间里大家都只能靠在别的地方打工来生活,这时,如何去衡量每个人的付出,如何去评价每个人有多投入,会是一个更大的问题。也许,这还会给未来的利益分配埋下争执隐患,也许,这也会成为一些人离开的直接或间接原因。但是,这也可能是一部分创业团队的必经之路吧。