为什么 Cocos2d-x 不用智能指针来管理内存?

编写 C/C++ 程序,管理内存是一门大学问,在 Cocos2d-x 中也不例外。说到内存操作,无非就是  new 、delete 、 malloc 、free ,而真正要管理好内存并用好它们,有着非常多的解决方案,我们看到 Cocos2d-x 中几乎所有封装的对象都继承于 Ref 类,并通过retain 和 release 等方法进行管理。这是一种非常多见的内存管理方法,它与Objective-C 的 retainCount、release 一样。但是看到 C++ 11 推出的更加完善的智能指针,总觉得 Cocos2d-x 没有跟上时代。可是从 Cocos2d-x 3.0的版本开始,已经有了很大的改进,全面支持 C++ 11 新特性。为什么偏偏不用智能指针来取代之前的CCObject ,而是改个名字叫 Ref ,真的是换汤不换药。

之前我也带着这个困惑敲着代码,直到有一天我想把原来封装的库绑定到 javascript 上时。我意识到了,全面使用智能指针来作为函数参数和返回值这样的封装真的是一个非常大的失误,因为这样根本无法直接绑定的 javascript 上,lua 也不行。所以我只能乖乖地改写代码。这可能就是 Cocos2d-x 不选择智能指针最大的原因吧。

当然,除了智能指针对脚本绑定不友好的这个原因外,还有一些其他因素。比如性能问题,我们看Ref类的源码会发现,仅仅维护一个 _referenceCount 变量,就可以完成内存的管理。而智能指针都干了什么?它附加了非常多的额外功能,如智能指针线程安全的特性导致的性能开销等等。

这可能仅仅是 Cocos2d-x 不用智能指针的初步解答,但我相信 Cocos2d-x 永远都不会选择智能指针来取代 Ref 的。

封装 Box2D 的”离线”版本

做游戏,哪有没用过 Box2D 的。但用过Box2D的人都应该会感觉到,它真的不是特别好用。最致命的问题就在于,它必须遵循固有的创建步骤和顺序,在创建任何刚体前必须先有 b2World 的实例 ,之后才能使用b2World的实例来创建刚体和及附加图形。有时,游戏对象需要暂时隐藏,或者是想在加载时进行一些必要的初始化都变得非常麻烦。

因此,我们很有必要对Box2D再进行一次封装,使其操作起来可以像操作显示列表一样简单。而这种封装的核心就是将创建数据缓存起来,与刚体本身连成聚合的关系。在开发时不需要关系它什么时候被创建,什么时候被释放。我们值需要关心它的缓存数据或者代理数据即可。而这种感觉特别像以前Web开发时的离线缓存,所以我称之为 Box2D 的“离线”版本。

经过大致的设计,可以出来这样一张图。

Screen Shot 2015-11-19 at 12.04.21 PM

这样一来我们物理对象的操作步骤就变成了这样。

    //创建物理空间
    PhysicsSpace space;
    
    //物理对象(刚体)
    std::shared_ptr object(new PhysicsObject());
    
    //添加图形
    std::shared_ptr rect(new PhysicsRectangle());
    std::shared_ptr cricle(new PhysicsCricle());

    rect->setWidth(100);
    rect->setHeight(100);
    
    cricle->setRadius(50);
    cricle->setX(30);
    cricle->setY(-100);
    
    object->addChild(rect);
    object->addChild(cricle);
    
    //将对象添加至空间
    space.addChild(object);

看,是不是用起来特别爽。是不是对自己的项目又有了重构的欲望。那就干紧动手吧!

都是为了Cocos2d,多语言版本这样做最简单

特别是海外市场,一款游戏往往需要支持多国语言。Cocos2d中我们如何简单高效得实现过多语言呢?

这里我们首先来分析一下文字的来源有哪些?
一、静态文字,如图片、UI编辑器里提前设计好的文字。
二、动态文字,如一些消息对话框,中间会嵌入玩家的一些信息,这些信息往往存在本地的数据库或配置文件里。
三、远程文字,比如说从服务器上抓下来的一封公告邮件。

现在我们来一条一条得实现多国语言。

静态文字,往往保存在UI界面中,这些文字大部分时候还会和动画夹杂在一起,由美术决定他的出场顺序以及摆放位置,特别是不同国家的语言词汇长度区别很大。听上去这部分好像不是很好处理呀,其实不然。

看这里的Logo
看这里的Logo
美术在编辑器相同的位置做了不同语言的版本
美术在编辑器相同的位置做了不同语言的版本,并标注 _cn 、 _en 这样的后缀
所以隐藏cn 显示en 就是这样的效果
所以隐藏cn 显示en 就是这样的效果

现在我们让程序来完成这件事,这只是一个示例

void UIBase::changeToEnglish(Vector<Node*>* children)
{
    for(auto& node:*children)
    {
        std::string flag=node->getName().c_str()+node->getName().size()-3;
        
        if (flag=="_cn") {
            node->setVisible(false);
        }else if(flag=="_en") {
            node->setVisible(true);
        }else if(flag=="_pr") {
            node->setVisible(false);
        }else if(flag=="_es") {
            node->setVisible(false);
        }
        
        changeToEnglish(&(node->getChildren()));
    }
}

这样一来在游戏中就可以根据不同的系统语言显示不同的静态文字了。

而动态文字,处理起来就更加简单了,把多国语言的文字写到一张表里,然后根据系统语言来读取相应的文本即可

Screen Shot 2015-11-17 at 5.42.32 PM
就像这样

最后就是远程的文字图片了,这完全需要服务器端的配合,提供多国语言的信息,接下来的处理方式就和动态文本一样了。

都是为了Cocos2d,成为接SDK的 “大神” 要一步一步来

接入 SDK 是一件费时费力还无聊的事,掌握一些基础知识然后文档看仔细一点一般都会比较顺利。我想到几条写几条,给新手一些帮助。

要不要选择 Cocos2d-x 版的 SDK?

很多国内的 SDK 提供商不仅会提供iOS和安卓的版本,有些还会专门针对 Cocos2d-x 制作一个版本。首先他们的初衷的确是好的,但是这种 SDK 往往都是千年不更新的。用它,你会郁闷,还是老老实实地自己封装吧。

文档应该看哪个?

很多SDK的文档网页上一个版本,下载后里面又附带一个版本。具体看哪个因SDK而异,但很多时候你都不得不两个都看。

Cocos端调用安卓的函数显示广告(或别的界面时),程序直接闪退

这是因为你没有在安卓的UI线程操作View界面对象,此时会出现异常。可以调用 Activity 的实例的 runOnUiThread 方法进行UI相关操作。

为什么Google Play 付费SDK 中的道具购买后就无法再打开购买窗口?

Google Play 中的道具购买够必须执行一次消耗才能再次购买,因此如果是由数量累计的道具可以直接卖完立即执行消耗,这样就直接避免了这个问题。

iOS上 OC 和 C++ 混编,头文件里中如何申明 OC 对象。

在 .h 文件中将类型申明为 void* 即可,在 .mm 文件中使用需要进行一下强制转换。

如果管理不同平台的文件?

一般可以建一个 platform 文件夹,然后再分相应的平台,外层可能需要一个统一的入口,这样一来就各平台和业务逻辑和游戏代码就相对分得比较清楚了。

都是为了Cocos2d,资源加密这个“手术”应该怎么动?

接着上一篇,我们知道了如何对资源进行加密处理,而对在Cocos2d-x中的解密却只字未提。现在我们就来好好说说解密这件事。

首先目前的Cocos2d-x版本并未预留接口让我们方便得插入解密代码,因此我们在这里需要对Cocos2d-x进行一个小小的“手术”,而这个手术主要围绕着几个名字带有 “FileUtils” 的文件。主要涉及改动的函数有 “getStringFromFile” 和 “getDataFromFile” ,可以从函数名看出 “getStringFromFile”  用于加载文本资源,而 “getDataFromFile” 用于加载二进制流资源。

接下来我们依次锁定这些文件
基础文件:
cocos2d/cocos/platform/CCFileUtils.h
cocos2d/cocos/platform/CCFileUtils.cpp
苹果:
cocos2d/cocos/platform/apple/CCFileUtils-apple.mm
安卓:
cocos2d/cocos/platform/android/CCFileUtils-android.cpp
微软:
cocos2d/cocos/platform/winrt/CCFileUtilsWinRT.cpp

由于 Cocos 已经明确得将资源加载分为了两类“文本”和“二进制”两类,因此我们先在 “CCFileUtils.h” 文件中创建两个拉姆达表达式:

public:
    std::function<std::string(std::string& data,std::string filename)> onStringDataDecode;
    std::function<void(cocos2d::Data& data,std::string filename)> onStreamDataDecode;

插入在该类的末尾即可。

然后我们打开 “CCFileUtils.cpp” 这个文件,在 “getStringFromFile” 函数改写成这样:

std::string FileUtils::getStringFromFile(const std::string& filename)
{
    Data data = getData(filename, true);
    if (data.isNull())
    	return "";
    
    std::string ret((const char*)data.getBytes());
    
    if (onStringDataDecode!=nullptr) {
        ret=onStringDataDecode(ret,filename);
    }
    
    return ret;
}

并将“getDataFromFile”函数改写成这样:

Data FileUtils::getDataFromFile(const std::string& filename)
{
    auto data= getData(filename, false);
    
    if (onStreamDataDecode!=nullptr) {
        onStreamDataDecode(data,filename);
    }
    return data;
}

其实这样就已经基本完成了,只是安卓winrt平台单独继承了 “FileUtils” 类,重写了几个方法,因此我们还需要再进行一下改动。
打开“ CCFileUtils-android.cpp”,进行以上一模一样的修改:

std::string FileUtilsAndroid::getStringFromFile(const std::string& filename)
{
    Data data = getData(filename, true);
    if (data.isNull())
    	return "";
    
    std::string ret((const char*)data.getBytes());
    
    if (onStringDataDecode!=nullptr) {
        ret=onStringDataDecode(ret,filename);
    }
    
    return ret;
}
Data FileUtilsAndroid::getDataFromFile(const std::string& filename)
{
    auto data= getData(filename, false);
    if (onStreamDataDecode!=nullptr) {
        onStreamDataDecode(data,filename);
    }
    return data;
}

然后是微软的“CCFileUtilsWinRT.cpp”文件,由于这里只重写了 “getStringFromFile” 函数,因此只需要修改它既可:

std::string CCFileUtilsWinRT::getStringFromFile(const std::string& filename)
{
    Data data = getData(filename, true);
	if (data.isNull())
	{
		return "";
	}
    std::string ret((const char*)data.getBytes());

	if (onStringDataDecode != nullptr) {
		ret = onStringDataDecode(ret, filename);
	}

    return ret;
}

我是不是忘说苹果的 “CCFileUtils-apple.mm” 文件了?这个文件的修改之所以放到末尾,是因为它并不修改 “getStringFromFile” 和 “getDataFromFile” 这两个函数, “CCFileUtils-apple.mm” 新添加了 “getValueMapFromFile” 和 “getValueMapFromData” 两个函数,从而可以直接在 iOS 平台直接调用 OC 原生的 API 解析 .plist 文件,而我们主需要对 “getValueMapFromFile” 函数进行一下修改就可以:

ValueMap FileUtilsApple::getValueMapFromFile(const std::string& filename)
{
    std::string fullPath = fullPathForFilename(filename);
    
    auto str= getStringFromFile(fullPath);
    return getValueMapFromData(str.c_str(),static_cast(str.size()));
    
    //注释掉使用oc原生解析plist文件的代码
    /*
    NSString* path = [NSString stringWithUTF8String:fullPath.c_str()];
    NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile:path];

    ValueMap ret;

    if (dict != nil)
    {
        for (id key in [dict allKeys])
        {
            id value = [dict objectForKey:key];
            addValueToDict(key, value, ret);
        }
    }
    return ret;*/
}

现在,我们这个大手术已经完成了,我们使 “FileUtils” 类暴露出了 “onStringDataDecode” 和 “ onStreamDataDecode” 两个接口,我们只需要对这两个拉姆达进行赋值就完成了我们今天的目标。
比如在 AppDelegate 的 applicationDidFinishLaunching 函数中这么写:

    cocos2d::FileUtils::getInstance()->onStringDataDecode=[](
                std::string& str,
                std::string filename){
        
        auto pos= filename.rfind(".");
        std::string extension=filename.c_str()+pos+1;
        if (extension=="csv"||
                extension=="plist"||
                extension=="xml"||
                extension=="txt") {
            //文本解密
        }
        
        return str;
        
    };
    
    cocos2d::FileUtils::getInstance()->onStreamDataDecode=[](
                cocos2d::Data& data,
                std::string filename){
        
        auto pos= filename.rfind(".");
        std::string extension=filename.c_str()+pos+1;
        if (extension=="csv"||
                extension=="plist"||
                extension=="xml"||
                extension=="txt") {
            //文本解密
        }else if(extension=="jpg"||
                extension=="png"||
                extension=="gif"||
                extension=="tmx"){
            //二进制解密
        }

这样一来我们就可以随心所欲得加密解密我们的资源了。

都是为了Cocos2d,资源管理必须得有它

我们都知道,Cocos2d-x 创建项目后就给我们准备好了一个叫Resources的目录,如果为了简单,我们大可以直接将游戏资源直接存放至到该目录。但一旦到了项目后期,恐怕就不会那么简单咯。就比如说资源加密这个问题,如果不事先动动脑子的话难不成这一切都手动处理吗?再比如说图片压缩问题,难不成你真的要一张一张地用TexturePacker 处理吗?如果ios和安卓可以播放mp3,而winphone只能播放wav又该怎么管理文件?

没错,我们需要一个工具,来自动化得帮我们完成这份工作。

首先是技术选择,由于很多加密算法或者是一些库都很容易找到c++的版本,而且Cocos2d-x也是用c++写的。所以使用c++来编写逻辑最容易保证执行结果的统一。而cocos从业人员往往使用Windows或者Mac等多个系统。为了保证最高的环境适应性,所以我选择了Qt,它可以使用QML来设计界面绑定视图对象,使用js编写界面的业务逻辑,并且可以只用一行代码就注册c++的类使js能够直接访问,总而言之就是特别好用。

然后就是软件设计了,以我的风格,这个工具目前只会解决我的当下问题,所以亲们拿到代码以后需要修改代码来适应你们自己的项目。或则说我这里只提供思路,剩下的都得自己解决。

Screen Shot 2015-11-17 at 2.33.46 PM

整体分为 Runner 和 Property 两个部分,Runner 主要用于配置工作环境,从那里读取资源文件,并将处理完的文件写到哪里。还有文件的过滤等。然后按Run按钮就可以执行Property中的所有处理命令了。

点击➕,可以创建处理命令
点击➕,可以创建处理命令,这些命令算法都用c++编写

比如说对文本进行加密

注意过滤属性,可以选择需要加密的文件类型
注意过滤属性,可以选择需要加密的文件类型

或者是对二进制流(图片、动画等)进行加密

同样地选择图片和动画文件
同样地选择图片和动画文件

再比如调用系统命令来压缩纹理

没错,这是一个万能的命令
没错,这是一个万能的命令

保存完配置信息后,每次只需要单击一次Run,就可以将所有资源统统处理一遍。

同样的,我将这份代码上传到了github
https://github.com/birnfly/SafetyDataFactory

代码风格依然为 紧急模式。

那么现在问题来了,我们应该如何加载加密了的资源呢?请看下一篇。

都是为了Cocos2d,做动画还是flash的好

你是否被美术抱怨过?“CocosBuilder 在模拟器里真卡”、“Cocos Studio 怎么老有这样莫名其妙的BUG”、“刚刚的动画又出问题了,再给我半小时改改”。那就请你不要大惊妖怪,我们肯定都遇到过!

想当年,用 flash 制作动画是多么简单的一件事,无论怎么折腾都行,你想导成swf就可以导成swf,你想导入到swc就能放进去,或者你想输出序列帧也非常简单。如今它做的动画还能直接在html5浏览器上跑。而动画制作的过程,没有任何问题。你从来不需要为这件事而担心。

而现在,是不是有很想用回 flash 的冲动?当然可以,而且使用flash制作的动画还能运行在Unity3D上,这样以后可以随时移植到其他平台,而我随便一想就能给你提供两个解决方案。
一、你可以使用一个叫 GAF(http://gafmedia.com)的工具,可以直接将swf导出成gaf库可用的格式,然后你只需要将库加入到 Cocos2d-x 项目中即可。而 GAF 又分为免费版和收费版,我想作为一个公司不会为了几块钱而浪费太多时间吧。官方给出了详细的使用文档,这里我就不过多介绍了。
二、这是一种完全免费,并且一切都在自己掌控范围内的一个方法,因为都是开源的。我原先是从网上找了一个把 flash 动画转换成 CocosStudio 2.0 的动画csd文件的js插件,它有很多缺陷,如会自动将矢量原件图形都到出成csd文件,最后生成的动画嵌套了无数层。而且当时CocosStudio2.0 不支持时间轴分段,而后面的版本支持了,由于这个js插件版本过早也没找到后续的更新,所以只能自己修改这个js插件以满足后面的需求啦。

你可以下载该文件:CocosStudioPlugin

现在主要介绍这个js文件的用法

首先我们得有一个flash动画,并且在场景中被导出的动画是一个原件。
首先我们得有一个flash动画,并且在场景中被导出的动画是一个原件。
打开这个原件,我们可以看到时间轴的第一层标记着分段的动画标签
打开这个原件,我们可以看到时间轴的第一层标记着分段的动画标签
拖入这个脚本文件,点击运行
拖入这个脚本文件,点击运行
你可以看到一个导出后的CocosStudio项目
你可以看到一个导出后的CocosStudio项目
打开它是这个样子的
打开它是这个样子的
并且自动分割了动画
并且自动分割了动画

严格意义上来说这个插件不是我写的,我只写了后面的一小部分代码。所以使用过程中发现什么bug千万不要来找我^_^。大家可以自行解决,这样才会有提高嘛:-D。

都是为了Cocos2d,手写sql查询太麻烦了

我相信很多朋友都会遇到这样的需求,服务器端和客户端会共享很大一部分静态数据,用于同步两端战斗结果等各种逻辑的计算,而服务器端往往会将数据存入数据库,比如说mysql,如果我们本地再维护一张csv那是非常变扭的一件事。最好的解决方法就是将服务器了的数据导入到sqllite。于是我们前端就要开始编写那些无聊繁琐且重复的查询语句,而且每次表结构有修改,我们也得改改改。回想使用.net的时候,可以直接连接数据库生成实体进行数据操作,哪里改了刷新一下就好,还有酷炫的linq可以用很快的节奏编写代码。而java还有 hibernate 可以干类似的事情。

那C++有什么呢?想来想去也没想到,于是只好自己动手丰衣足食啦。

计划一天一夜的时间,采用紧急代码风格,设计一款可扩展并支持简单模板的实体生成器,并且仅解决当下的业务需求。

最后做出来是这样子的。

为了降低实践成本,采用了最熟悉的c#。刷刷刷5分钟搞定界面
为了降低时间成本,采用了最熟悉的c#。刷刷刷5分钟搞定界面
随意连上一张表,生成表结构
随意连上一张表,生成表结构,并且可以选择和查看要导出的表
Screen Shot 2015-11-17 at 11.24.13 AM
可以进行批量导出,并导出成单个文件
选择常用的编码
选择常用的编码
看,桌面上出现了这个
看,桌面上出现了这个
多个.h文件导出张这样
多个.h文件导出长这样
会生成类的应有属性
会生成类的所有属性,并且类型转换也是自动的
以及create方法
以及create方法
还有where函数,是不是有点像linq
还有where函数,是不是有点像linq
当然,所需要的一切你都可以自定义
当然,所需要的一切你都可以自定义

Screen Shot 2015-11-17 at 11.27.41 AM

Screen Shot 2015-11-17 at 11.27.49 AM

Screen Shot 2015-11-17 at 11.27.56 AM

这样一来,只需要配合相应的sqllite库以及事先写好的代码就可以把很多重复劳作节约下来,而且每次表结构有修改都可以一分钟内同步。其实这种东西微软早就已经用烂,还有好多好多值得学习。

然后,我想说,我是不会告诉你我已经把它传到了github上了。

https://github.com/birnfly/DataTableToCpp

注意:代码风格为紧急模式。

都是为了Cocos2d,如何让 Cocos2d-x 支持 OpenSSL

为何标题要这么写,我想“都是为了Cocos2d”很能表达我作为cocos开发者,我的心情。

对于一款游戏中,网络连接、资源存储都会考虑到数据安全的问题,此时你有两种选择,你可以自己编写一些简单的加密算法来满足一时之需,此方法的好处是不需要依赖任何外部力量(依赖库等等)同时可以降低非常多的学习成本。另一只方法则是依赖强大而复杂的加密库。此时我们想到了OpenSSL。
这里有多种方法可以让我们使用上强大的OpenSSL库。首先第一种方法就是进入OpenSSL官方网址(http://www.openssl.org/source/)下载到源码。然后逐一编译针对各种CPU和各种平台的库文件。这是相当麻烦的事情。我们可以参照这篇文章(http://x2on.de/2010/07/13/tutorial-iphone-app-with-compiled-openssl-1-0-0a-library/)来完成各平台版本的编译。

当我正在这么做时,突然想到Cocos2d-x中包含着一些比较常用的库如CURL,不就支持SSL吗?

于是乎第二种方法就诞生了,我打开项目中的cocos2d/external/curl/prebuilt目录。发现这里列出了各大平台的curl库文件,其中也包括OpenSSL库文件(libssl.a、libeay32.dll、ssleay32.dll等)。有了这些文件就好办多了。我们只需要找到项目引用所需的OpenSSL include文件和windows平台所需的lib文件即可。lib文件cocos2d-x引擎中已经提供了。而OpenSSL include文件则已经在OpenSSL开源项目中了。我们直接拷贝出来。在Xcode和Visual Studio以及安卓的Android.mk文件中添加index文件的路径。再将lib文件添加到Visual Studio项目中Linker>Input>Additional Dependencies中即可。

Picture3
注意libeay3s.lib和ssleay32.lib这两个文件,加上他们再配合相应的.h文件就可以直接使用啦
Picture2
注意openssl-master 安卓只要直接导入这些头文件就可以啦
Picture1
IOS 同样需要的,添加头文件的目录即可

在最后我又想到一种方法,也可以实现OpenSSL中的加密算法。就是利用各大平台的API直接调用,但是这样需要针对不同平台编写相似却有不同的代码。维护起来着实麻烦。最后项目使用上一种方法稳定的运行了。

为什么我总是拿AS3写半成品?

工作的前三年,我主要发展方向是 .net。中间涉及到网站、邮箱、桌面应用等等。同样的事情做久了肯定会觉得枯燥乏味。所以平时会写点有意思的东西,比如说游戏。而随着游戏业的发展,需要适应人们快节奏的生活,flash则是最好的选择。打开网页就能玩,打开flash就能敲代码。

记得上一回,电脑显卡被我玩烧了,能能将独立显卡上的显示器接口插到集成显卡上。我电脑性能本来就不差,即便是集成显卡一样也可以干很多事。而我关注到 flash 主要就是因为可以使用 CPU 来模拟3D效果。当时flash player停留在9.0的版本,还没发展到后来的flash player 11 支持GPU绘图。而那时最著名的 flash3D引擎就是PaperVision3D了,它还出版过很多相关书籍。在体验flash的魅力的同时我还发现了一本非常有用的书,它是真正带我入门的flash书籍。《Flash ActionScript 3.0 动画教程》从一开始简单的小球碰撞到后面的用代码编写透视根据顶点和索引数据绘制3D物体。真正得由浅到深地教会了我什么是游戏画面,什么是游戏画面背后的数据以及背后的逻辑。

除了几个简单的小游戏外,真正动手做的是一款属于我自己的Flash3D引擎,做它仅仅是因为兴趣。最初设想的目标就是能够解析obj格式的网格,并根据UV数据绘制简单的纹理。这仅仅是最初级demo级别的构想。而正因为仅仅是兴趣驱动,很快我就对它失去了兴趣。我接了网友介绍的 3D 在线展示场景的外包项目,当时5000块钱对我来说是一笔不小的数目,我直接转向PV3D,使用天空包围盒显示用鱼眼照相机拍摄的场景。之后一个月里你无论怎么回头看,我肯定就坐在那儿安静得写着无聊业务逻辑。

万万没想到的是,最后我还是做完了那个demo级别的3D引擎。记得没错的话纹理绘制还有严重的bug,至今那个swf和fla文件都找不着了,真的找了好久。

经过了很长一段的时间,那时已经工作了,体会到了加班狗的快乐。节假日一定会花大笔的时间去敲属于自己的代码。那时准备用 flash 写一款网页在线的坦克大战。参考的游戏原型是 2D CS(至今我依然认为把它移植到手游上是一个很不错的 idea )。flex 用来写地图编辑器,而这个地图编辑器就是我的噩梦。由于时间断断续续,我相信很多人会遇到和我一样的问题,“咦,之前的代码原来这么丑。要不重构一下吧”。就这样,我的地图编辑器写过一个又一个版本。的确代码越来越精炼了,但是东西迟迟做不完。后来深入学习敏捷开发之后,我才知道这就是我连故事都没列清楚就写代码的下场。

功能简介完善的坦克大战编辑器
功能简介完善的坦克大战编辑器

Screen Shot 2015-11-16 at 10.48.50 PM

地块以格子信息保存,原本资源是这样的
地块以格子信息保存,原本资源是这样的
绘制到一起会自动边缘融合
绘制到一起会自动边缘融合
而砖块层是这样的
而砖块层是这样的
绘制后会总动计算出阴影
绘制后会自动计算出阴影

万万没想到的是,最后竟然有人替我写完了这个坦克大战。

再到后来,flash 进入了3D时代。从flash player 11开始,flash 开始支持 GPU 绘图,并且可以通过 Alchemy 技术将C++代码直接编译成 AVM 虚拟机可执行的字节码。网页上流畅运行的虚幻三场景亮瞎了我的眼睛。我便开始关注新的 flash 3d 技术。此时有一款在 flash player 9 时代卡得像幻灯片一样的3D引擎 away3d 迈出了很大一步。它使用优秀的性能和开源的方式抓住了我们的眼球,顿时群里聊得热火朝天。然后我那万恶的地图编辑器又开始了,它支持地形编辑、场景布置、纹理绘制、碰撞生成、自动分割场景、lua脚本绑定等各种功能。

Screen Shot 2015-11-16 at 9.43.48 PM
随心所欲的画笔
自动拼接的道路
自动拼接的道路
随意扩展的地图
随意扩展的地图
万能的地形笔刷
万能的地形笔刷
自动根据地图高度生成碰撞区域
自动根据地图高度生成碰撞区域

Screen Shot 2015-11-16 at 9.45.39 PM

Screen Shot 2015-11-16 at 9.46.24 PM
绘制斜坡也非常简单
Screen Shot 2015-11-16 at 9.40.59 PM
随意控制物体

Screen Shot 2015-11-16 at 9.47.37 PM

万万没想到的是,无论我怎么优化,它在我的10年的安卓手机上始终不能跑到30fps。

再后来呢?听一位之前在adobe的员工跟我们说,原来好多页游公司都准备转3D,当时派了好多专业技术人员直接驻扎在那些公司里帮他们及时解决问题,但依然迟迟做不出东西。很快他们就意识到,转3D页游不如赶紧转手游来的赚钱呀!然后,away3d 也一年多没再更新了,adobe解散了。最后,手游真的吞噬了很大一部分页游市场。而手游从又从当时的乱世到现在的三足鼎立。今天的寒冬,大家都还好吗?