0. 引子
前一阵子一直在制作一款小游戏,虽说最终的成果并不完美,但也算是花了不少精力,网上批评的声音不少,但更多的则是鼓励,这也让我们颇感欣慰,自省之余,也算拾得一些信心,总之一句话,继续奋力前行~ :)
游戏大概是个有些“另类”的音乐游戏,基本玩法仍属传统,画面则偏中国风,相对另类一些,不过其中最另类的,当算是每个场景中都会出现的池塘,以及池塘中那群“活蹦乱跳”的小鱼儿~
就池塘这个意像来讲,在很多的仿池塘游戏(应用)中都有,可以说是稀松平常,但是将其运用在音乐游戏中,恐怕就很少很少了(我怀疑是没有的:)),我想曾经尝试过这个游戏的朋友想必都会对其中的池塘有所注意,犹有甚者,不少朋友还对池塘中小鱼的程序实现方式颇有兴趣~
想来自己也确实应该花些时间总结整理一下之前项目中的代码,而其中关于小鱼的实现也算是待整理代码中比较重要的一个部分,既然有童鞋对此好奇,那么索性在此将其开源,总结复习之余,也给有兴趣的朋友一些参考 :)
1. 原理
就基本原理而言,小鱼的实现其实非常简单,就是将原本“整片”的精灵(Sprite)“分块”,然后通过一定的规律控制这些分块节点的运动,继而影响精灵的整体显示,产生诸如游动之类的运动方式,就这么简单 :)某种角度上来讲,确实有点像骨骼动画,只是比较特殊简陋罢了。之前制作时也曾尝试过逐帧动画之类的方法,但是结果都不尽人意,几番尝试下来还是采用了目前的方法,虽说稍稍复杂了些,但就结果来看还是值得的(如下图所示,黄点像素便是分割所得的节点) :)
3. 代码
好了,不多扯了,让我们马上来看看代码:
首先是最重要的FlagellumSprite:
/*!
\file FlagellumSprite.h
\brief flagellum like sprite, comprise of several nodes
*/
#ifndef __FLAGELLUM_SPRITE_H__
#define __FLAGELLUM_SPRITE_H__
#include <vector>
#include <map>
#include "cocos2d.h"
USING_NS_CC;
class FlagellumSpriteModifier;
class FlagellumSprite: public Node
{
public:
/*!
create with texture file
\param filename texture filename
\param nodeCount sprite node count (default is 8)
\return FlagellumSprite pointer (autoreleased)
*/
static FlagellumSprite* create(const char* filename, size_t nodeCount = 8);
/*!
create with texture object
\param texture texture object
\param nodeCount sprite node count (default is 8)
\return FlagellumSprite pointer (autoreleased)
*/
static FlagellumSprite* create(Texture2D* texture, size_t nodeCount = 8);
//! constructor for initialize
FlagellumSprite():m_texture(NULL),m_vertices(NULL),m_texCoords(NULL),m_debug(false) {}
//! destructor for releasing
virtual ~FlagellumSprite();
/*!
init with texture method
\param texture texture object
\param nodeCount sprite node count (default is 8)
\return true if success, otherwise false
*/
bool initWithTexture(Texture2D* texture, size_t nodesCount = 8);
//! reset method
//void reset();
//! update method
//virtual void update(float frameTime);
//! draw method
virtual void draw() override;
/*!
set node count
\param nodeCount sprite node count
*/
void setNodeCount(size_t nodeCount);
/*!
get node count
\return sprite node count
*/
size_t getNodeCount() const { return m_nodes.size(); }
/*!
set texture
\param texture texture ref
*/
void setTexture(Texture2D* texture);
/*!
get texture
\return texture ref
*/
Texture2D* getTexture() const { return m_texture; }
/*!
set debug flag
\param debug debug boolean
*/
void setDebug(bool debug) { m_debug = debug; }
/*!
get debug flag
\return debug boolean
*/
bool getDebug() const { return m_debug; }
/*!
add sprite modifier
\param modifier sprite modifier to add
\param priority modifier priority (default is 0)
*/
bool addModifier(FlagellumSpriteModifier* modifier, int priority = 0);
/*!
remove sprite modifier
\param modifier sprite modifier to remove
*/
bool removeModifier(FlagellumSpriteModifier* modifier);
/*!
clear all modifiers
*/
void clearModifiers();
/*!
update modifier
\note you have to call this every update
*/
void updateModifiers();
private:
// implement details here
};
#endif // __FLAGELLUM_SPRITE_H__
FlagellumSprite的作用便是将原本整体的精灵分割为指定的区块并负责正确显示他们,以上代码罗列了FlagellumSprite的头文件,其中的create、setTexture、setNodeCount等方法都是比较传统的函数,作用也比较明确,需要细讲一下的可能还是关于FlagellumSpriteModifier的那几个接口方法。
讲述之前,让我们先来看看FlagellumSpriteModifier:
/*!
\file FlagellumSpriteModifier.h
\brief base class of flagellum sprite modifier
*/
#ifndef __FLAGELLUM_SPRITE_MODIFIER_H__
#define __FLAGELLUM_SPRITE_MODIFIER_H__
#include <vector>
#include "cocos2d.h"
USING_NS_CC;
class FlagellumSpriteModifier: public Object
{
public:
/*!
pre modify callback interface
\param points points vector as in and out param
*/
virtual void preModify(std::vector<Point>& points) = 0;
/*!
modify callback interface
\param points points vector as in and out param
*/
virtual void onModify(std::vector<Point>& points) = 0;
/*!
post modify callback interface
\param points points vector as in and out param
*/
virtual void postModify(std::vector<Point>& points) = 0;
};
#endif // __FLAGELLUM_SPRITE_MODIFIER_H__
可以看到,FlagellumSpriteModifier仅是一个简单的基类型,约束的几个方法分别是preModify、onModify和postModify。如前所述,FlagellumSprite用以分割精灵并加以显示,但是其并不负责分割后所得节点的运动逻辑,也就是说,如果我们仅仅创建一个FlagellumSprite,所得结果和创建一个Sprite并无二致,所有进一步的FlagellumSprite“运动”都需要FlagellumSpriteModifier来帮忙,而FlagellumSpriteModifier的preModify、onModify和postModify便是用来完成这些“运动”;而FlagellumSprite为了配合FlagellumSpriteModifier,自然需要提供必要的支持接口,而这些接口也便是上面我们所见但仍未有讨论的接口,他们分别是:
addModifier : 向FlagellumSprite添加一个FlagellumSpriteModifier
removeModifier : 从FlagellumSprite移除一个FlagellumSpriteModifier
clearModifiers : 删除FlagellumSprite中所有FlagellumSpriteModifier
updateModifiers : 更新FlagellumSprite中所有FlagellumSpriteModifier
使用上,FlagellumSprite同普通的Node并无二致,大抵上便是这个样子:
// create flagellum sprite
FlagellumSprite* fish = FlagellumSprite::create("fish.png");
if (fish)
{
fish->setPosition(Point(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
fish->setRotation(-90);
fish->setAnchorPoint(Point(0.5f, 0.5f));
fish->setScale(1.5f);
fish->setDebug(true);
/*
{
FlagellumSpriteModifier* modifier = new FlagellumSpriteModifierShrink();
modifier->autorelease();
arraw->addModifier(modifier);
}
*/
{
FlagellumSpriteModifier* modifier = new FlagellumSpriteModifierSine();
modifier->autorelease();
fish->addModifier(modifier);
}
fish->setTag(1);
addChild(fish, 1);
}
要讲的就是这么多了,有兴趣的朋友可以在此取一份代码来看看(代码根据cocos2d-x-3.0版本进行了改写,并且运用了一点C++11的特性,在较老的版本中编译应该会有问题,注意一下),其中实现了两个简单的FlagellumSpriteModifier,感觉还有那么一点意思 :)当然,FlagellumSprite的想法并非我的独创,网上早已有了很多优秀的资源,这里就有一个非常好的参考,有兴趣的朋友可以仔细看看 :)
最后放一张示例gif :)
OK,that's it :)