在上一篇中,我们介绍了各种游戏对象的功能及类的集成关系,现在我们来看看GameObject的源代码
碰撞体
GameObject.h
class GameObject : public Sprite
{
public:
GameObject();
virtual void setBodySize(const Size& s);
virtual void setBodySize(float w, float h);
virtual const Size& getBodySize();
virtual const Size& getOrignBodySize() const;
virtual void setBodyCenter(const Vec2& v);
virtual void setBodyCenter(float x, float y);
virtual const Vec2& getBodyCenter() const;
//获取世界坐标下的body的位置和大小
virtual Rect getBodyBox() const;
protected:
//用于碰撞检测的刚体大小和位置
Vec2 m_bodyCenter; //刚体的中心点坐标(相对于精灵锚点的坐标)
Size m_bodySize; //刚体的宽高
};
GameObject.cpp
void GameObject::setBodySize(const Size& s)
{
m_bodySize = s;
}
void GameObject::setBodySize(float w, float h)
{
setBodySize(Size(w, h));
}
const Size& GameObject::getBodySize()
{
return m_bodySize;
}
void GameObject::setBodyCenter(const Vec2& v)
{
m_bodyCenter = v;
}
void GameObject::setBodyCenter(float x, float y)
{
m_bodyCenter = Vec2(x, y);
}
const Vec2& GameObject::getBodyCenter() const
{
return m_bodyCenter;
}
//获取世界坐标下的body的位置和大小
Rect GameObject::getBodyBox() const
{
Vec2 pos = getPosition();
return Rect(pos.x + m_bodyCenter.x - m_bodySize.width * getAnchorPoint().x,
pos.y + m_bodyCenter.y - m_bodySize.height * getAnchorPoint().y,
m_bodySize.width,
m_bodySize.height);
}
碰撞体的定义很简单,中心坐标+宽高,然后加上常见的get/set方法。其中比较有用的是getBodyBox()方法。由于碰撞体的中心坐标是相对于Sprite锚点的坐标,所以如果要用来判断两个碰撞体是否发生碰撞(是否有重叠区域),必须要获取两个碰撞体在世界坐标下的位置和大小,这时就要调用getBodyBox()方法来得到Rect对象,然后再调用Rect的bool intersectsRect(const Rect& rect)方法来判断两个碰撞体是否发生了碰撞。
暂停/恢复
GameObject.h
virtual void pause() override;
virtual void resume() override;
void pause(Node *pNode);
void resume(Node *pNode);
GameObject.cpp
void GameObject::pause()
{
this->pause(this);
}
void GameObject::resume()
{
this->resume(this);
}
void GameObject::pause(Node *pNode)
{
Node::pause();
for (auto p : pNode->getChildren())
{
p->pause();
}
}
void GameObject::resume(Node *pNode)
{
Node::resume();
for (auto p : pNode->getChildren())
{
p->resume();
}
}
调用pause和resume的同时,会调用所有子节点的pause和resume。这样,当玩家飞机暂停时,僚机作为它的子节点,也会跟着暂停。
初始化
先看代码
bool GameObject::initSpriteWithFileList(const std::vector<std::string>& fileList, float dura)
{
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(fileList.at(0));
if (NULL == frame)
{
DEBUG_LOG("Error get frame of '%s'", fileList.at(0).c_str());
CCASSERT(frame, "Error get frame");
}
Sprite::initWithSpriteFrame(frame);
//动画
if (fileList.size() > 1)
{
Animation* animation = Animation::create();
animation->setDelayPerUnit(dura);
for (unsigned i = 0; i < fileList.size(); i++)
{
SpriteFrame* pFrame = CCSpriteFrameCache::getInstance()->getSpriteFrameByName(fileList[i]);
if (NULL == pFrame)
{
continue;
}
animation->addSpriteFrame(pFrame);
}
//设置重复
Animate* animate = Animate::create(animation);
Repeat* repeat = Repeat::create(animate, CC_REPEAT_FOREVER);
m_pAnimateSequence = Sequence::create(repeat, NULL);
m_pAnimateSequence->retain();
runAction(m_pAnimateSequence);
}
return true;
}
这个函数是通过帧序列来初始化。这里有两个输入参数:fileList是图片列表,dura是每张图片之间个时间间隔。如何是骨骼动画呢?看下面这个代码:
bool GameObject::initArmature(const std::string& armatureName, float scale)
{
if (armatureName.length() <= 0)
{
return true;
}
m_pArmature = cocostudio::Armature::create(armatureName);
m_pArmature->setPosition(getContentSize() / 2);
m_pArmature->getAnimation()->play(GlobalData::getInstance()->getArmatureData(armatureName)->defaultAction);
m_pArmature->setScale(scale);
addChild(m_pArmature);
return true;
}
首先通过骨骼动画的名称armatureName创建骨骼动画,然后执行默认动作defaultAction(defaultAction是从配置文件中获取的,配置文件的读写将在以后详述)。最后把骨骼动画添加到Sprite上。
这里就有一个疑问了,为什么既要支持帧序列动画,又要支持骨骼动画呢?我们知道骨骼动画的表现形式比帧序列动画更丰富,但是随之而来的问题就是骨骼动画更占资源。如果只有一个简单的动画,或者像子弹那样速度比较快并且数量比较多的游戏对象,应该尽量使用帧序列动画,甚至对于子弹来说,只用单张图片来表现就可以了,根本用不着动画。而对于玩家飞机、boss来说,因为涉及到变形,那就不得不用骨骼动画了。
另外,这里也有一个可以优化的地方。我们可以把GameObject继承自Node,当游戏对象是序列帧动画时,就添加一个Sprite子节点,如果是骨骼动画,就添加一个Armature子节点。