小议游戏控制台

69 篇文章 5 订阅

>_ 引子

  近来一直在搞iOS平台游戏,所用引擎则是cocos2dx,不少时间接触下来,感觉是愈来愈喜欢了:),虽然起初引擎稍显简陋,目前也仍然和商业引擎存在差距,但鉴于引擎“资质优异”、社区活跃,每次更新都有不少令人欣喜的特性,再加上全世界热心Programmer们的各类扩展,cocos2dx(或者说cocos2d家族吧,也许更确切些)确实是愈来愈强大了,而自己则像前面所说的,愈来愈喜欢他了:)

  可惜虽然cocos2dx用的顺手,但是随着开发的深入,自己倒是发现了另一个相较引擎使用可能更加费时的工作,那就是游戏调整:譬如简单的一个界面Slide,虽然内部的逻辑简单,但是想要得到一个令人满意的操作感觉,仍然需要不少时间的来回调整,传统的硬编码“技术”早已过时,Config之类的参数配置支持早已成为“标配”,但即便如此,很多时候我们仍然需要重启游戏以再次读取数据,脚本的出现进一步扩展了游戏的“自由度”,但往往也不过是更高级的Config罢了,即便做到了热更新,我们仍然需要付出来回转换编辑的代价,而目前移动平台上的“真机调试”,则限制更大,仅为调整一个参数我们可能就需要重新生成游戏多次,所耗费的大量无意时间着实令人厌烦;另外的,一些逻辑的动态调整也很难完成,譬如Render信息,很多时候非常有用,但也有不少时间我们并不关心,如何方便的开关也是一个难题,尤其是在上述的真机调试中,问题更加明显……

  每每遇到此类问题的时候,我都会不由自主的怀念起以前使用过的CE中的游戏控制台,相比上面所言的种种方法,运用这类游戏中内建的Console来调整一个参数那便非常的方便了,简单的几下按键就可以完成,迅速高效直观,这也是为什么目前大多数的PC游戏或者引擎都内建有Console的原因,只是可惜的是,cocos2dx目前并未内建支持,网上稍稍google了一阵,确实也发现了不少控制台的类库实现(譬如这个),但往往都仅针对PC平台,无论设计和实现上都与我的需求有所距离,大抵都不能简单的实行“拿来主义”……

  思来想去,觉得与其成天怨天尤人,不如借鉴一下“前辈们”的劳动成果,自己简单的来实现一个移动端的Console,一方面算是解决当前问题、锻炼己身技术,另一方面也算给有过类似问题困扰的朋友一些参考吧……

  >_ 设计

  游戏控制台大概是起源于大神约翰卡马克Quake,从他以后有不少人对此做了一些扩展或者改变,但相互之间的操作机制都基本类似,某种程度上说,这些游戏控制台中的操作也很类似于OS中的命令行,拿CE中的Console为例,大概的操作分为以下几种:

  ~:唤出游戏控制台

  

  普通字符:输入字符

  Backspace:删除当前光标前的字符

  Enter:确认输入

  ↑:上一个历史输入

  

  ↓:下一个历史输入

  ← : 前移光标

  

  → : 后移光标

  Tab : 自动补全(前缀)

  

  就PC输入而言,上述操作方便快捷,非常流畅,但是想要将他们一股脑儿的搬到移动平台上,亦或者说cocos2dx上,那便有些令人犯难了:

  首先,cocos2dx虽然支持Text Input,其扩展Edit Box功能更全,但是从功能上来讲其支持的程度非常有限,譬如箭头、Tab之类的按键操作,即便在其Win32平台的实现中也并未提供(被简单过滤掉了,当然,你可以修改源码……),而在像iOS一般的移动平台中,原生的键盘甚至都不提供这些按键,想要获取这些按键信息基本没门(当然,你可以自己实现一个定制键盘……);再者,如何唤出控制台也是个问题,相比PC上简单的一个~按键,移动端则一般都没有提供类似的输入机制,如何有效的开启移动平台上的控制台也值得思考……

  不过好在这些问题从相对的角度来考虑,很多便不再是问题了:诚然,在移动平台上我们并没有完整的按键支持,但是相应的,PC平台上也欠缺移动平台上提供的其他操作(譬如Touch),如果我们不再纠结于按键,而改用其他操作(譬如Touch)来控制Console的话,那么很多问题便解决了,毕竟PCPC,移动平台是移动平台,有些事情尽管内部原理一致,但是实际实施时也要因地制宜才可 :)基于以上观点,新版的Console操作修改如下:

  特定按钮(或者固定Touch范围): 开启控制台

  普通字符:输入字符

  Backspace:删除当前光标前的字符

  Enter:确认输入

  向上Slide:上一个历史输入

  

  向下Slide:下一个历史输入

  向左Slide: 前移光标

  

  向右Slide: 后移光标

  Tap: 自动补全(模糊)

  此处除了操作方式有所改变以外,自动补全功能也根据我的个人经验作了一些修改,就CE中的自动补全而言,其采用的是标准的前缀匹配:即譬如你输入了Rel,系统便可以自动匹配到Reload,但是如果输入了Rle或者Rlo之类的字符串则无法匹配(因为不是Reload前缀)。实际使用中,我一般可以大概记得某个命令中的一些字符,但是并不能够完全准确无误的记住这些命令的前缀,再加上时有发生的输入误差,往往导致自动补全功能表现的不尽人意……为了Console的顺畅使用,在此我便索性将游戏控制台中一般的前缀匹配修改为模糊匹配,以期能够得到更好的使用和匹配效果 :)

>_ 实现

  OK,基于上面的Console设计,我们来简单看一下实现吧 :

  结构上来说,Console代码实现遵循MVC框架,其中的ModelConsoleDataManagerViewConsoleViewController则是ConsoleController,让我们简单的一个个浏览一下:

  首先,让我们来看一看两个Console的基本结构:ConsoleVariableConsoleCommand

顾名思义,ConsoleVariable其实就是控制台参数,而ConsoleCommand则代表控制台命令,实现过程中我曾经试图将这两者统一为ConsoleElement之类的结构,不过后来简单尝试之后,发现ConsoleVariableConsoleCommand的相关概念还是有不少区别,统一接口虽然在实现上简洁了一些,但在使用上不甚清晰,所以最终还是分开了,这便导致目前不少接口必须同时支持两个类型,稍稍有些冗余:)OK,闲话少叙,让我们来看看他们的头文件:

//! console variable

class ConsoleVariable

{

public:

    ConsoleVariable(const std::stringnameconst ConsoleValuevalueConsoleVarFunc varFunc = NULLconst std::stringhelp = "");

    //! set variable name

void SetName(const std::stringname);

//! get variable name

const std::string& GetName() const;

//! set variable value

void SetValue(const ConsoleValuevalue);

//! get variable value

const ConsoleValue& GetValue() const;

//! set variable help

void SetHelp(const std::stringhelp);

//! get variable help

const std::string& GetHelp() const;

//! set variable func

void SetFunc(ConsoleVarFunc varFunc);

//! get variable func

ConsoleVarFunc GetFunc() const;

    // implement details here

// ......

};

  可以看到,ConsoleVariable其实非常简单,实现上仅是一个简单的SetterGetter,结构上大概由Name(命名)、Value(数值)、Func(回调函数)和Help(帮助信息)组成。而ConsoleCommand则更加简单,同样仅有一些存取函数以及一个简单的执行函数,并且组成更简单:分别是Name(命名)、Func(回调函数)和Help(帮助信息):

//! console commands

class ConsoleCommand

{

public:

ConsoleCommand(const std::stringnameConsoleCmdFunc cmdFuncconst std::stringhelp = "");

//! set command name

void SetName(const std::stringname);

//! get command name

const std::string& GetName() const;

//! set command function

void SetFunc(ConsoleCmdFunc cmdFunc);

//! get command function

ConsoleCmdFunc GetFunc() const;

//! set command help

void SetHelp(const std::stringhelp);

//! get command help

const std::string& GetHelp() const;

//! execute command

void Execute(const ConsoleCmdArgscmdArgs);

    // implement details here

    // ......

};

OKConsole基本结构介绍完毕,接下来就是我们MVC框架中的Model了,即ConsoleDataManager:

class ConsoleDataManager

{

public:

//! simple singleton implementation

static ConsoleDataManager* GetSingleton();

public:

~ConsoleDataManager();

//! init method

bool Init();

//! release method

//! call this before quit

void Release();

//! add console variable

ConsoleVariable* AddCVar(const std::stringnameint valueConsoleVarFunc varFunc = NULLconst std::stringhelp = "");

ConsoleVariable* AddCVar(const std::stringnamefloat valueConsoleVarFunc varFunc = NULLconst std::stringhelp = "");

ConsoleVariable* AddCVar(const std::stringnameconst std::stringvalueConsoleVarFunc varFunc = NULLconst std::stringhelp = "");

//! get console variable

ConsoleVariable* GetCVar(const std::stringname);

//! remove console variable

void RemoveCVar(const std::stringname);

//! add console command

ConsoleCommand* AddCCmd(const std::stringnameConsoleCmdFunc cmdFuncconst std::stringhelp = "");

//! get console command

ConsoleCommand* GetCCmd(const std::stringname);

//! remove console command

void RemoveCCmd(const std::stringname);

//! get similar cvars, return values are arranged by similarity

std::vector<CVarSimilarInfo> GetSimilarCVars(const std::stringname);

//! get similar ccmds, return values are arranged by similarity

std::vector<CCmdSimilarInfo> GetSimilarCCmds(const std::stringname);

//! dump console variable

void DumpCVar(IConsoleVariableDumperdumper);

//! dump console command

void DumpCCmd(IConsoleCommandDumperdumper);

    // implement details here

    // ......

};

  相信大家根据上面注释已经大概了解了ConsoleDataManager的基本接口结构,需要特别提一下的便是以下几点:1.ConsoleDataManager是一个Singleton,获取十分简单,但是释放就不那么直观了,目前的实现比较简单,我们需要在退出程序时(或者其他适当时刻)释放ConsoleDataManager的资源,即调用其Release方法;2.GetSimilarCVarsGetSimilarCCmds)返回所有与所给字符串参数相似的字符串,并且按照相似度升序排序(即最相似的字符串置于最后),返回方式使用了简单的std::vector by value的方式,效率不高,有时间可以尝试一下C++11右值引用  ,或者直接修改接口:)

  好了,接下来便是MVC中的View,即ConsoleView

//! console view interface

class ConsoleView

{

public:

virtual ~ConsoleView() {};

//! set console action delegate

virtual void SetActionDelegate(ConsoleActionDelegatedelegate) = 0;

//! get console action delegate

virtual ConsoleActionDelegate* GetActionDelegate() = 0;

//! set line max char count

virtual void SetLineMaxCharCount(size_t maxCount) = 0;

//! get line max char count

virtual size_t GetLineMaxCharCount() const = 0;

//! output an new line

virtual void OutputLine(const std::stringline) = 0;

//! output input string

virtual void OutputInput(const std::stringinput) = 0;

//! set prompt position

virtual void SetPromptPos(size_t pos) = 0;

//! get prompt position

virtual size_t GetPromptPos() const = 0;

};

  可以看到,ConsoleView仅是一个虚类(或者说接口类吧),实际中我们必须实现自己的ConsoleView才能正确显示我们的Console信息,而源码中的类型ConsoleViewCocos2dx便是ConsoleViewcocos2dx版本实现,虽然期间细节不少,但在概念上来讲也仅仅是实现了上面的接口定义,并没有什么特别的地方,有兴趣进一步了解的朋友可以参看源码 :)

  最后,让我们来看看ConsoleController,即MVC中的Controller

class ConsoleControllerpublic ConsoleActionDelegate

{

public:

virtual ~ConsoleController() {};

//! init method

virtual bool Init() = 0;

//! release method

virtual void Release() = 0;

//! ConsoleActionDelegate

//

//virtual bool OnEvent(const ConsoleActionData& data) = 0;

//

// NOTE: now we just support one view for simple and clear

//! set view

virtual void SetView(ConsoleViewview) = 0;

//! get view

virtual ConsoleView* GetView() = 0;

};

  可以看到,ConsoleControllerConsoleView一样,也仅是一个接口类,实际使用中我们仍然需要实现自己的Controller才能达到对Console的控制,不过好在Controller的控制逻辑基本相似,所以具体实现中除了有上面的ConsoleController以外,还有一个ConsoleControllerBase,其直接继承于ConsoleController,并封装了这些基本的Console操作,譬如如何处理输入、删除等等,当然其中细节也有不少,但概念上都是用于完成这些操作,有兴趣的朋友可以仔细看看代码,在此就不细述了 :)

  好了,程序的基本结构我们大概过了一遍,接下来的问题便是如何使用它了,一般情况下,你需要首先将GameConsole中的各个源码文件加入你的cocos2dx工程,然后在适当位置调用:

  

// add game console at the top

//

addChild(ConsoleCocos2dxLayer::create(), TOP_Z_ORDER);

//

  然后就结束了,就这么简单 :

  目前代码简单实现了两个参数和两个命令以供测试,有兴趣的朋友可以试一试 :)

  renderInfo : (参数)显示或者关闭Render信息

  gameFPS : (参数)设定游戏FPS

  Dump : (命令)输出所有参数和命令

  EXIT : (命令)退出程序

  最后,相关源码可以在此取得,and have fun with console :)

 

>_ 花絮

  1. 曾经考虑过同时支持多个ControllerView的代码框架,后来觉得基本没有必要,除了增加一些开发难度,偶尔可以装装X以外,基本没有什么实际用途,所以很快便放弃了;再者目前的已有代码实现都以可读性和整洁性为第一指导准则,待优化的地方还有不少,BUG自然也不太可能避免,如果有兴趣的朋友优化了实现、找到了BUG亦或者实现了更好的Console,请务必告知,以便改正学习 :)

  2. 目前因为条件所限,代码仅在Win32iOS上测试了一番,其他平台问题暂时不得而知。就Win32平台而言,操作感觉个人觉得马马虎虎(不过模糊补全功能还是很对我的胃口:)),流畅度上还是不及传统的全键盘操作,虽然修改引擎基本可以实现这项功能,不过考虑到所需付出(个人不太喜欢侵入性代码……),觉得还是作罢为好;另外的iOS平台,操作就比较费劲了,由于cocos2dx内部过滤了键盘存在时的Touch信息,导致每次我都必须关闭键盘才能触发ConsoleTouch逻辑,十分不便,在此我曾尝试去除了这些Touch过滤,相应Console的操作便非常之流畅了(虽然是侵入式代码……),有兴趣的朋友可以试试。

  

  3. 关于Console外观,似乎长久以来都是那副黑底白字的模样,的确这在某种程度上说也足够了,不过因为个人原因,我还是倾向于更加美观一些的样子,譬如这样:

  

  当然,我们还可以做得更好一些,譬如这样:

  

  甚至个人文艺色彩更重一些(纯属个人倾向而已……),BTW,此处期待一下FF10高清中文版 :)

  

>_ exit

  OK,要说的就这么多了,最后还是那句:下次再见吧~~~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值