基于Cocos2d-x 3.6 打造体素游戏
发表于2016-05-28
1、什么是体素游戏
最大名鼎鼎的当然是《Minecraft》(《我的世界》), 虽然最常冠以沙盒游戏的的分类,但整个世界、角色等都是采用立方体体素打造的,我们也将其分类在体素游戏之内,Voxel(体素)是voluempixel的缩写,是指构成由立体渲染对象构成世界的最小单位。体素不光是单指立方体,也包括各种其他几何体,比如:球体、圆柱体、甚至是复杂几何体的迭代形式。如下图场景就是分别用球体与圆柱体打造:


当然随着《Minecraft》的风靡,体素游戏发展到了更高高度,各种体素工具和体素游戏引擎也逐渐出现,虽然不是作为本文重点,但简单介绍2个体素引擎(体素工具在本文的工具篇再详细介绍):
VoxelFarm Engine (简称VFE): http:http://voxelfarm.com
Atomontage Engine (无限细节技术): http://www.atomontage.com
Voxlap Engine(一个牛人在93年就开始开发的小引擎): http://advsys.net/ken/voxlap/voxlap05.htm
2、打造一个体素游戏
言归正传,本文意在讲述如何用简单快捷的方式打造我们自己的体素游戏,由于手机的火爆,很多简单的游戏也在手机上取得了成功,这类游戏都有着极简的设计和明朗色块的体素,这其中比较有代表性的就是《天天过马路》了,当然也有用2D像素Isometric化模拟的体素游戏,如《cloud path》, 出于追随潮流我也开始打造了一个自己的体素游戏,先来做个广告:《Rainbow' End》(《彩虹尽头》)28天开发完成,已经在App Store和Google play上线,欢迎体验。

本文也是针对这款游戏从工具、引擎、表现方面给大家来讲述:
一、工具篇
制作体素的工具有很多,当然也可以用3dmax,maya等传统建模工具制作,但不符合快速开发的原则,所以为了在一个月内开发完成,只能花时间去寻找更多的工具。
Painter3D
Sproxel
Qubicle Constructor
VoxelShop
Qblock (在线制作,可以看到其他玩友上传的作品来寻找灵感)
MagicaVoxel Editor (本文使用,重点推荐)
本文采用 MagicalVolex Editor 0.93 进行开发(目前版本0.96.3) 注意:0.93更早的版本没有对相同体素导出时进行顶点优化,会导致游戏中顶点数偏高,游戏性能下降。 MagicalVolex Editor工具使用教程本文不讲,可参考网站提供的教学视频学习。

将通过MagicalVolex Editor建好的体素模型通过export导出选项中的obj导出,再通过3dMax或(Blender3D, Cinema4D, Maya)等传统建模工具的任何一种将obj文件导出成fbx文件备用。(当然本身一些引擎是支持obj文件格式的,包括Cocos2d-x,之所以转换下是为了使用Cocos2d-x下的模型的c3b或c3t格式,考虑以后引擎升级可能的模型优化)。
二、引擎篇
U3D + MagicaVoxel Editor是可以非常简单的进行使用开发的,本文不做赘述,由于正版U3D发行问题和作者本身也想测试下cocos2dx 3D部分的性能,所以选择了Cocos2d-x 3.6版本进行。
首先是模型转化,将fbx文件通过Cocos2d-x提供的工具fbx-conv进行转化,生成c3b模型文件和纹理文件(注意:这里可以对文件进行优化,可以让游戏中所有使用的模型在MagicalVolex Editor下使用相同的调色版,这样导出的索引色是相同的,这就意味着所有模型可以公用一张纹理文件,可以大大减少io读取时间和显存使用。)
然后初始化3D场景,包括创建天空盒,创建3D摄像机,创建天光,给场景添加灯光,是为了弥补体素对象色彩单一导致的显示比较平的问题,添加灯光来区别体素不同面的间隔,相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 | void HelloWorld::spawnExplosion( const cocos2d::Vec3& pos) { auto explosion = PUParticleSystem3D::create( "explosionSystem.pu" ); if (!explosion) return ; explosion->setCameraMask((unsigned short )CameraFlag::USER1); explosion->setPosition3D(pos); explosion->setScale(2.0f); this ->addChild(explosion); explosion->startParticleSystem(); } |
彩虹对象采用了ParitcleUniverse的条带系统实现,对象的定义和实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | class RibbonTrail : public cocos2d::Node, public cocos2d::BlendProtocol { RibbonTrail(); virtual ~RibbonTrail(); public : static RibbonTrail* create( const std::string &textureFile, float width, float length); bool initWithFile( const std::string &path, float width, float length); virtual void update( float delta); virtual void draw(cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t flags) override; // overrides virtual void setBlendFunc( const cocos2d::BlendFunc &blendFunc) override; virtual const cocos2d::BlendFunc &getBlendFunc() const override; cocos2d::PURibbonTrail* getTrail() const { return m_pTrail; } private : cocos2d::PURibbonTrail* m_pTrail; cocos2d::BlendFunc m_BlendFunc; }; RibbonTrail* RibbonTrail::create( const std::string &textureFile, float width, float length) { if (textureFile.length() < 4) CCASSERT( false , "invalid filename for texture file" ); auto ribbonTrail = new (std:: nothrow ) RibbonTrail(); if (ribbonTrail && ribbonTrail->initWithFile(textureFile, width, length)) { ribbonTrail->autorelease(); return ribbonTrail; } CC_SAFE_DELETE(ribbonTrail); return nullptr; } RibbonTrail::RibbonTrail() :m_pTrail(nullptr) { m_BlendFunc = {GL_SRC_ALPHA , GL_ONE}; } RibbonTrail::~RibbonTrail() { m_pTrail = nullptr; } bool RibbonTrail::initWithFile( const std::string &path, float width, float length ) { m_pTrail = new (std:: nothrow ) PURibbonTrail( "RibbonTrail" , path); if (m_pTrail) { m_pTrail->setNumberOfChains(1); m_pTrail->setMaxChainElements(100); m_pTrail->setTrailLength(length); m_pTrail->setUseVertexColours( true ); m_pTrail->setInitialColour(0, Vec4(1, 1, 1, 1)); //m_pTrail->setColourChange(0, Vec4(0.8, 0.8, 0.8, 0.8)); m_pTrail->setInitialWidth(0, width); m_pTrail->setDepthTest( true ); m_pTrail->setDepthWrite( true ); return true ; } return false ; } void RibbonTrail::update( float delta) { if (m_pTrail) m_pTrail->update(delta); } void RibbonTrail::draw(Renderer* renderer, const Mat4& transform, uint32_t flags) { if (m_pTrail) m_pTrail->render(renderer, transform, m_BlendFunc); } const BlendFunc& RibbonTrail::getBlendFunc() const { return m_BlendFunc; } void RibbonTrail::setBlendFunc( const BlendFunc &blendFunc) { m_BlendFunc = blendFunc; } |

相关粒子全部代码下载:https://github.com/lwwhb/cocos2dx3.6_voxel_tutorial
三、后记
以目前Cocos2d-x 3.6版本提供的3D部分封装,是可以完全满足一个简单3D游戏的需求,而且包体相对U3D实现要小得多,但引擎部分仍然有很多地方需要加强,比如材质系统,没有材质系统的渲染,只能针对每个对象手动设置shader实现。另外Cocos2d-x中针对Sprite3D的合批操作也没有办法实现,需要修改引擎或采用模型的instancing来去进行速度优化。但总体来说的性能一个简单的体素游戏Cocos2d-x是可以应付的,大家可以放心使用。
本文版权归tinyflare.com所有,欢迎转载,但必须保留此段声明,且在文章页面明显位置给出原文连接。