太阳神三国杀中,每当玩家出杀或吃桃子时,就会有一个动画效果,使界面非常生动绚丽.现在我们就分析一下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;
}

vs2008编译QT开源项目--太阳神三国杀源码分析(四) 动画相关推荐

  1. vs2008编译QT开源项目--太阳神三国杀源码分析(一) 项目编译及整体分析

    请参看 http://tieba.baidu.com/f?kz=1508964881 按照上面的网址教程,下载三国杀源码,swig工具,并下载最新的QT4.8.2 for vs2008.我本机已经安装 ...

  2. vs2008编译QT开源项目--太阳神三国杀源码分析(三) 皮肤

    太阳神三国杀的界面很绚丽,界面上按钮的图标,鼠标移入移出时图标的变化,日志和聊天Widget的边框和半透明等效果,既可以通过代码来控制,也可以使用皮肤文件qss进行控制.下面我们分析一下三国杀的qss ...

  3. vs2008编译QT开源项目--太阳神三国杀源码分析(二) 客户端添加武将

    接着上篇文章继续分析,我们来看看进入到roomScene(房间场景)后,点击add a robot按钮,是如何创建武将的.首先找到add to robot按钮的创建代码: add_robot = ne ...

  4. vs2008编译QT开源项目--太阳神三国杀源码分析(五) 分牌及出牌

    游戏开始时选择武将后,服务端统一控制向玩家分发初始手牌,并向客户端发生绘制手牌的命令.跟踪的流程如下:服务端向客户端发送命令drawCards及随后的一串使用加号(+)连接起来的牌编号(drawCar ...

  5. Mac下编译太阳神三国杀源码

    Mac系统版本是10.13.2,Qt版本是5.11,参考官方博文:太阳神三国杀 Mac 版构建指南 安装Qt5 下载最新的Qt5安装包qt-opensource-mac-x64-5.11.0.dmg, ...

  6. 开源项目-太阳神三国杀-QT4编译教程

    待更中 转载于:https://www.cnblogs.com/sinpener/p/10734692.html

  7. android studio 项目源码_这个标星 2.3k+ 的项目带你 Android 源码分析从入门到放弃...

    上次写了一篇 Android 开发者的福利,介绍几款看源码的工具 ,这篇文章主要介绍了三款看 Android 源码的工具,后台有很多同学留言问,有没有分析源码的. 分析源码的文章也很多,但大多数文章不 ...

  8. (4.2.40)阿里开源路由框架ARouter的源码分析

    一需求背景 1 Android原生方案的不足 2 自定义路由框架的适用场景 3 对自定义路由框架的设想 二ARouter的概述 三ARouter的引入和使用 四源码分析 1 arouter-annot ...

  9. 一个完整的python项目源码-一个Python开源项目-哈勃沙箱源码剖析(下)

    前言 在上一篇中,我们讲解了哈勃沙箱的技术点,详细分析了静态检测和动态检测的流程.本篇接着对动态检测的关键技术点进行分析,包括strace,sysdig,volatility.volatility的介 ...

最新文章

  1. priority queue
  2. 备考3次终于过信息系统项目管理师长篇心路历程
  3. 使用 Nginx 提升网站访问速度
  4. 2008 年 十大预测的总结 (只对了一半左右)
  5. Optional类的使用
  6. springmvc03 接收请求参数
  7. 用Nero刻录光盘失败,提示“无法执行结束轨道”
  8. 国内液压与气动标准大全
  9. pyodbc 操作SQL Server数据库
  10. matlab求随机过程的数学期望,密度函数已知,怎么用matlab求其数学期望和方差?...
  11. after meet KeyNi liu
  12. OBD系统系族分类规则
  13. 软件工程:第一章笔记下
  14. fragment 报Fragment already added错误有几种情况
  15. 我们是创业团队,实习前两月没钱,全栈每月800块!
  16. 聊聊P2P网贷平台的风险【站在平台的角度】
  17. 做过10亿次设计的人工智能「鹿班」,原来是他负责的!
  18. 【Python】采集3万张4K超清壁纸,实现定时自动更换桌面壁纸脚本(内含完整源码)
  19. 锁定行列的html插件,如何实现行列的锁定
  20. 我们快速处理公司CAD病爆发问题的过程

热门文章

  1. Apache shiro反序列化(CVE-2016-4437)复现
  2. 50台机器无盘服务器,以50台机器小吧为例看深度无盘快速布署的那些事.doc
  3. 成功的礼品公司的产品经营模式
  4. Arduino基础知识复习12.18
  5. splash安装和简单使用
  6. I lost my spoon
  7. html+css制作圣诞树
  8. Linux 监听进程状态
  9. qt mingw32编译项目报错:Nothing to be done for 'first'.
  10. 【转载】androidstudo如何跨越这个厚厚的墙,亲测有效 Could not resolve com.android.tools.build:gradle: