vs2008编译QT开源项目--太阳神三国杀源码分析(四) 动画
太阳神三国杀中,每当玩家出杀或吃桃子时,就会有一个动画效果,使界面非常生动绚丽.现在我们就分析一下QT中动画的原理,及实现方式,这里我们只分析吃桃子时的动画效果实现.由于三国杀有多个在线玩家同时游戏,因此当一个玩家吃了桃子,会将这个消息发送给服务器,服务器在分别通知每个在线玩家,使玩家界面出现吃桃子的动画.现在我们来跟踪一下代码的执行流程.在NativeClientSocket::init()
太阳神三国杀中,每当玩家出杀或吃桃子时,就会有一个动画效果,使界面非常生动绚丽.现在我们就分析一下QT中动画的原理,及实现方式,这里我们只分析吃桃子时的动画效果实现.由于三国杀有多个在线玩家同时游戏,因此当一个玩家吃了桃子,会将这个消息发送给服务器,服务器在分别通知每个在线玩家,使玩家界面出现吃桃子的动画.现在我们来跟踪一下代码的执行流程.
在NativeClientSocket::init()成员函数中,关联QtcpSocket的readyRead信号与其处理槽函数:connect(socket, SIGNAL(readyRead()), this, SLOT(getMessage()));在getMessage()函数中,每接收一条消息就触发message_got信号,Client类的构造函数中关联这个信号connect(socket, SIGNAL(message_got(char*)), this, SLOT(processServerPacket(char*)));在processServerPacket函数中,判断消息如果不是通知或请求,则调用processReply方法.在processReply方法中,判断消息字符串的首个字符,并根据消息内容查找对应的处理函数,此时消息内容为"animate",因此转而调用animate方法.这里有个小技巧,因为服务端和客户端之间的通信内容时字符串,那么如何根据发送的字符串找到相应的处理函数呢?将函数的名称和函数地址映射存入了一个QHash<QString, Callback> callbacks;成员变量中,在Client类的构造函数中进行初始化.
callbacks["addHistory"] = &Client::addHistory;
callbacks["animate"] = &Client::animate;
callbacks["judgeResult"] = &Client::judgeResult;
callbacks["setScreenName"] = &Client::setScreenName;
callbacks["setFixedDistance"] = &Client::setFixedDistance;
callbacks["transfigure"] = &Client::transfigure;
callbacks["jilei"] = &Client::jilei;
callbacks["cardLock"] = &Client::cardLock;
callbacks["pile"] = &Client::pile; ...... ......
其中Callback的定义为:typedef void (Client::*Callback)(const QString &);在根据函数名称查找函数地址时,只需要使用QHash类的value方法获取,而后调用:
Callback callback = callbacks.value(method, NULL);
if(callback){
QString arg_str = arg;
(this->*callback)(arg_str);//注意调用方式为(this->*callback)(参数列表),表示调用的是一个类实例(this)的成员函数,因为callback为函数指针,加星号降引用(好像可不加星号而直接调用,没有测试过).
现在就可以进入Client::animate成员函数了.解析出服务端发送的名称,及参数,触发animated信号.
void Client::animate(const QString &animate_str){
QStringList args = animate_str.split(":");
QString name = args.takeFirst();
emit animated(name, args);
}
在RoomScene构造函数中,关联了animated信号及处理槽:connect(ClientInstance, SIGNAL(animated(QString,QStringList)), this, SLOT(doAnimation(QString,QStringList)));doAnimation函数首先定义了一个静态QMap变量,并在首次调用的时候进行设置,将一个字符串命令与一个函数地址向映射,原理与callbacks相同,实现延时加载的目标,提高软件启动速度.接着根据传入的name参数到map中查找对应的函数地址,并调用.
void RoomScene::doAnimation(const QString &name, const QStringList &args){
static QMap<QString, AnimationFunc> map;
if(map.isEmpty()){
map["peach"] = &RoomScene::doAppearingAnimation;
map["jink"] = &RoomScene::animatePopup;
map["nullification"] = &RoomScene::doMovingAnimation;
map["analeptic"] = &RoomScene::doAppearingAnimation;
map["fire"] = &RoomScene::doAppearingAnimation;
map["lightning"] = &RoomScene::doAppearingAnimation;
map["typhoon"] = &RoomScene::doAppearingAnimation;
map["lightbox"] = &RoomScene::doLightboxAnimation;
map["huashen"] = &RoomScene::doHuashen;
map["indicate"] = &RoomScene::doIndicate;
map["hpChange"] = &RoomScene::animateHpChange;
}
AnimationFunc func = map.value(name, NULL);
if(func)
(this->*func)(name, args);
}
根据name的值可知,接下来调用了RoomScene::doAppearingAnimation成员函数,转而又调用了setEmotion函数.
if(name == "analeptic"
|| name == "peach")
{
setEmotion(args.at(0),name);
return;
}
setEmotion首先根据参数who获取对应的座位对象photo,如果找到座位则在对手的座位上播放动画,否则在本地的控制区播放动画.
void RoomScene::setEmotion(const QString &who, const QString &emotion ,bool permanent){
Photo *photo = name2photo[who];
if(photo){
photo->setEmotion(emotion,permanent);
return;
}
PixmapAnimation * pma = PixmapAnimation::GetPixmapAnimation(dashboard,emotion);
if(pma)
{
pma->moveBy(0,- dashboard->boundingRect().height()/2);
pma->setZValue(8.0);
}
}
先看setEmotion方法,参数emotion是动画名称,对应image/system/emotion/目录中的一个子目录名称,permanent指示是否永久播放动画.emotion_item是QGraphicsPixmapItem类型的指针,设置其显示,在其上播放动画,如果不是永久播放的动画则使用QTimer在2秒后隐藏动画图元emotion_item.接着就调用PixmapAnimation的静态成员函数GetPixmapAnimation来播放动画了.注意PixmapAnimation类是从QGraphicItem继承的(class PixmapAnimation : public QObject,public QGraphicsItem),动画的原理就是在桌位上创建一个PixmapAnimation实例,并使用计时器定时触发调用paint方法,在其上绘制不同的图片.
void Photo::setEmotion(const QString &emotion, bool permanent){
this->permanent = permanent;
if(emotion == "."){
emotion_item->hide();
return;
}
QString path = QString("image/system/emotion/%1.png").arg(emotion);//这个图片路径如果不存在,则emotion_item不生效.
emotion_item->setPixmap(QPixmap(path));
emotion_item->show();
if(emotion == "question" || emotion == "no-question")
return;
if(!permanent)
QTimer::singleShot(2000, this, SLOT(hideEmotion()));
PixmapAnimation::GetPixmapAnimation(this,emotion);
}
GetPixmapAnimation是一个静态函数,首先创建一个新的PixmapAnimation实例pma,并设置其图片所在路径加载路径中的图片,设置动画播放的位置,最后调用基类QObject的startTimer函数设置计时器,每50毫秒触发一次,动画完成时触发QObject的deleteLater函数,延时删除自己(在对象退出消息循环后进行自我删除).
PixmapAnimation* PixmapAnimation::GetPixmapAnimation(QGraphicsObject *parent, const QString &emotion)
{
PixmapAnimation *pma = new PixmapAnimation();
pma->setPath(QString("image/system/emotion/%1/").arg(emotion));
if(pma->valid())
{
if(emotion == "slash_red" ||
emotion == "slash_black" ||
emotion == "thunder_slash" ||
emotion == "peach" ||
emotion == "analeptic")
{
pma->moveBy(pma->boundingRect().width()*0.15,
pma->boundingRect().height()*0.15);
pma->setScale(0.7);
}
else if(emotion == "no-success")
{
pma->moveBy(pma->boundingRect().width()*0.15,
pma->boundingRect().height()*0.15);
pma->setScale(0.7);
}
pma->moveBy((parent->boundingRect().width() - pma->boundingRect().width())/2,
(parent->boundingRect().height() - pma->boundingRect().height())/2);
{
if(emotion == "fire_slash")pma->moveBy(40,0);
}
pma->setParentItem(parent);
pma->startTimer(50);
connect(pma,SIGNAL(finished()),pma,SLOT(deleteLater()));
return pma;
}
else
{
delete pma;
return NULL;
}
}
接着就进入了timerEvent函数,这是一个QObject定义的虚函数,如果对象调用了startTimer则会定时触发timerEvent,这里将其重写.
void PixmapAnimation::timerEvent(QTimerEvent *)
{
advance(1);
}
advance成员函数累加current成员变量,记录当前帧,调用update方法触发paint函数的调用,如果帧数超过总帧数,则重置为0,并触发finished信号.
void PixmapAnimation::advance(int phase)
{
if(phase)current++;
if(current>=frames.size())
{
current = 0;
emit finished();
}
update();
}
paint函数则直接根据current成员变量值获取对应的帧图片,进行绘制.frames的定义为QList<QPixmap> frames;在PixmapAnimation::setPath函数中,将指定路径中的所有png图片都加载到frames中.为了提高图片加载速度,setPath函数中加载图片是调用GetFrameFromCache从缓存中查找图片,如果没有则从磁盘上加载并存入缓存,QPixmapCache是提供这个功能的核心类.
void PixmapAnimation::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->drawPixmap(0,0,frames.at(current));
}
QPixmap PixmapAnimation::GetFrameFromCache(const QString &filename){
QPixmap pixmap;
if(!QPixmapCache::find(filename, &pixmap)){
pixmap.load(filename);
if(!pixmap.isNull())
QPixmapCache::insert(filename, pixmap);
}
return pixmap;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)