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

来源:互联网 发布:淘宝天弘基金赎回后 编辑:程序博客网 时间:2024/04/28 23:33

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

        add_robot = new Button(tr("Add a robot"));
        add_robot->setParentItem(control_panel);
        add_robot->setPos(room_layout->button1_pos);

        fill_robots = new Button(tr("Fill robots"));
        fill_robots->setParentItem(control_panel);
        fill_robots->setPos(room_layout->button2_pos);

        connect(add_robot, SIGNAL(clicked()), ClientInstance, SLOT(addRobot()));
        connect(fill_robots, SIGNAL(clicked()), ClientInstance, SLOT(fillRobots()));

这里创建了两个按钮,我们只分析add to robot,下面两行代码将按钮的clicked信号与addRbot及fillRobots槽关联,我们只分析addRbot槽的实现,其简单的向服务端发送请求:

void Client::addRobot(){
    request("addRobot .");
}

void Client::request(const QString &message){
    if(socket)
        socket->send(message);
}

这里socket是一个纯虚类ClientSocket的指针,是使用ClientSocket的子类NativeClientSocket的实例来初始化的,因此调用如下函数:

void NativeClientSocket::send(const QString &message){
    socket->write(message.toAscii());
    socket->write("\n");
}

这个socket是QtcpSocket类,将创建一个武将(反贼)的命令发送给你服务端.按照网络通信的约定,服务端接收到客户端请求后,要做出应答,因此客户端会等待服务端的信息返回并处理.在NativeClientSocket类的构造函数中调用了init()方法,init设置了QtcpSocket的readyRead信号的响应槽:connect(socket, SIGNAL(readyRead()), this, SLOT(getMessage()));在getMessage中,读取服务端返回的每行内容,并触发message_got信号.

void NativeClientSocket::getMessage(){
    while(socket->canReadLine()){
        buffer_t msg;
        socket->readLine(msg, sizeof(msg));       

        emit message_got(msg);
    }
}

在Client类的构造函数中,将message_get信号与处理槽函数进行关联:

        connect(socket, SIGNAL(message_got(char*)), recorder, SLOT(record(char*)));
        connect(socket, SIGNAL(message_got(char*)), this, SLOT(processServerPacket(char*)));

第一个槽用于记录通信日志,第二个槽函数处理服务端返回的命令.

跟踪processServerPacket槽函数,最终调用:

void Client::processServerPacket(char *cmd){
    if (m_isGameOver) return;
    QSanGeneralPacket packet;
    if (packet.parse(cmd))
    {
        if (packet.getPacketType() == S_SERVER_NOTIFICATION)
        {
            CallBack callback = m_callbacks[packet.getCommandType()];
            if (callback) {           
                (this->*callback)(packet.getMessageBody());
            }
        }
        else if (packet.getPacketType() == S_SERVER_REQUEST)
            processServerRequest(packet);
    }
    else processReply(cmd);
}

这个函数首先尝试解析服务端返回的命令,看是否是服务端的通知信息或请求信息,并进行响应处理.否则调用processReply函数处理服务端响应.

void Client::processReply(char *reply){   
    if(strlen(reply) <= 2)
        return;

    static char self_prefix = '.';
    static char other_prefix = '#';

    if(reply[0] == self_prefix){
        // client it Self
        if(Self){
            buffer_t property, value;
            sscanf(reply, ".%s %s", property, value);
            Self->setProperty(property, value);
        }
    }else if(reply[0] == other_prefix){
        // others
        buffer_t object_name, property, value;
        sscanf(reply, "#%s %s %s", object_name, property, value);
        ClientPlayer *player = getPlayer(object_name);
        if(player){
            player->setProperty(property, value);
        }else
            QMessageBox::warning(NULL, tr("Warning"), tr("There is no player named %1").arg(object_name));

    }else{
        // invoke methods
        buffer_t method_name, arg;
        sscanf(reply, "%s %s", method_name, arg);
        QString method = method_name;

        if(replayer && (method.startsWith("askFor") || method.startsWith("do") || method == "activate"))
            return;

        static QSet<QString> deprecated;
        if(deprecated.isEmpty()){
            deprecated << "increaseSlashCount" // replaced by addHistory
                    << "addProhibitSkill"; // add all prohibit skill at game start
        }

        Callback callback = callbacks.value(method, NULL);
        if(callback){
            QString arg_str = arg;
            (this->*callback)(arg_str);
        }else if(!deprecated.contains(method))
            QMessageBox::information(NULL, tr("Warning"), tr("No such invokable method named \"%1\"").arg(method_name));
    }
}

callbacks是一个QHash类型的变量,存储了Client的成员函数名称和函数指针对.点击Add a robot按钮后返回的方法名称为addPlayer,从哈希表中获取对应的方法地址后,调用方法,进入addPlayer成员函数:

void Client::addPlayer(const QString &player_info){
    QStringList texts = player_info.split(":");
    QString name = texts.at(0);
    QString base64 = texts.at(1);
    QByteArray data = QByteArray::fromBase64(base64.toAscii());
    QString screen_name = QString::fromUtf8(data);
    QString avatar = texts.at(2);

    ClientPlayer *player = new ClientPlayer(this);
    player->setObjectName(name);
    player->setScreenName(screen_name);
    player->setProperty("avatar", avatar);

    players << player;

    alive_count++;

    emit player_added(player);
}

这个函数解析出玩家的名称,别名,选择的武将名称等信息,创建ClientPlayer对象,并加入到players列表中,最后触发player_added信号.

在roomScene类的构造函数中关联了player_added信号:connect(ClientInstance, SIGNAL(player_added(ClientPlayer*)), SLOT(addPlayer(ClientPlayer*)));

void RoomScene::addPlayer(ClientPlayer *player){
    int i;
    for(i=0; i<photos.length(); i++){
        Photo *photo = photos[i];
        if(photo->getPlayer() == NULL){
            photo->setPlayer(player);
            name2photo[player->objectName()] = photo;

            if(!Self->hasFlag("marshalling"))
                Sanguosha->playAudio("add-player");

            return;
        }
    }
}

这个函数中从玩家列表(photos)中查询还没有被占用的座位位置,调用座位类photo的setPlayer方法,给这个座位设置玩家.

void Photo::setPlayer(const ClientPlayer *player)
{
    this->player = player;

    if(player){
        connect(player, SIGNAL(general_changed()), this, SLOT(updateAvatar()));
        connect(player, SIGNAL(general2_changed()), this, SLOT(updateSmallAvatar()));
        connect(player, SIGNAL(kingdom_changed()), this, SLOT(updateAvatar()));
        connect(player, SIGNAL(ready_changed(bool)), this, SLOT(updateReadyItem(bool)));
        connect(player, SIGNAL(state_changed()), this, SLOT(refresh()));
        connect(player, SIGNAL(phase_changed()), this, SLOT(updatePhase()));
        connect(player, SIGNAL(drank_changed()), this, SLOT(setDrankState()));
        connect(player, SIGNAL(action_taken()), this, SLOT(setActionState()));
        connect(player, SIGNAL(pile_changed(QString)), this, SLOT(updatePile(QString)));

        mark_item->setDocument(player->getMarkDoc());//设置显示内容
    }

    updateAvatar();
}

上面的代码设置座位的玩家,并关联玩家的信号.关键代码为updateAvatar函数,设置座位上显示的玩家名称,武将头像等信息.

void Photo::updateAvatar(){
    if(player){
        const General *general = player->getAvatarGeneral();
        avatar_area->setToolTip(general->getSkillDescription());
        bool success = avatar.load(general->getPixmapPath("small"));//加载武将头像
        QPixmap kingdom_icon(player->getKingdomIcon());
        kingdom_item->setPixmap(kingdom_icon);
        kingdom_frame.load(player->getKingdomFrame());

        if(!success){//加载头像失败则绘制武将的名字
            QPixmap pixmap(General::SmallIconSize);
            pixmap.fill(Qt::black);
            QPainter painter(&pixmap);

            painter.setPen(Qt::white);
            painter.setFont(Config.SmallFont);
            painter.drawText(0, 0, pixmap.width(), pixmap.height(),
                             Qt::AlignCenter,
                             Sanguosha->translate(player->getGeneralName()));

            avatar = pixmap;
        }

    }else{
        avatar = QPixmap();
        kingdom_frame = QPixmap();

        avatar_area->setToolTip(QString());
        small_avatar_area->setToolTip(QString());

        ready_item->hide();
    }

    hide_avatar = false;
    kingdom_item->show();

    update();
}

这个函数根据服务端传回的信息,加载武将头像信息,如果加载失败,则进行绘制,显示武将名字.如果是清除玩家信息,则设置空头像和空提示信息.最后调用update方法,触发paint方法.

void Photo::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    Pixmap::paint(painter, option, widget);

    if(!player)
        return;

    painter->setPen(Qt::white);
    QString title = player->screenName();
    painter->drawText(QRectF(0,0,132,19), title, QTextOption(Qt::AlignHCenter));//绘制玩家名称

    static QPixmap wait_frame("image/system/wait-frame.png");//局部静态变量 武将头像外边框
    if(kingdom_frame.isNull())
        painter->drawPixmap(3, 13, wait_frame);//绘制武将头像外边框

    if(hide_avatar)
        return;

    // avatar related
    painter->drawPixmap(5, 15, avatar);//绘制武将头像,如果加载头像失败,则avatar中为武将名称
    painter->drawPixmap(86, 30, small_avatar);//绘制小头像

    // kingdom related
    painter->drawPixmap(3, 13, kingdom_frame);//绘制武将头像的外边框 标识武将所述国--魏蜀吴

    if(player->isDead()){
        int death_x = 5;

        if(death_pixmap.isNull()){
            QString path = player->getDeathPixmapPath();
            death_pixmap.load(path);

            if(path.contains("unknown"))
                death_x = 23;
            else
                death_pixmap = death_pixmap.scaled(death_pixmap.size() / (1.5));
        }

        painter->drawPixmap(death_x, 25, death_pixmap);//绘制武将死亡的图像
    }

    int n = player->getHandcardNum();
    if(n > 0){
        painter->drawPixmap(2, 68, handcard);
        painter->drawText(8, 86, QString::number(n));//绘制武将手牌数
    }

    QString state_str = player->getState();
    if(!state_str.isEmpty() && state_str != "online"){
        painter->drawText(1, 100, Sanguosha->translate(state_str));//绘制玩家状态
    }

    drawHp(painter);//绘制武将的生命值(血)

    if(player->getPhase() != Player::NotActive){//延迟加载武将状态图像
        static QList<QPixmap> phase_pixmaps;
        if(phase_pixmaps.isEmpty()){
            QStringList names;
            names << "round_start" << "start" << "judge" << "draw"
                    << "play" << "discard" << "finish";

            foreach(QString name, names)
                phase_pixmaps << QPixmap(QString("image/system/phase/%1.png").arg(name));
        }

        int index = static_cast<int>(player->getPhase());
        QPixmap phase_pixmap = phase_pixmaps.at(index);
        painter->drawPixmap(115, 120, phase_pixmap);//绘制玩家当前动作
    }

    drawEquip(painter, weapon, 0);//绘制装备
    drawEquip(painter, armor, 1);
    drawEquip(painter, defensive_horse, 2);
    drawEquip(painter, offensive_horse, 3);

    chain_icon->setVisible(player->isChained());//如果武将被铁索连环,则设置其chain_icon可见
    back_icon->setVisible(! player->faceUp());
}

原创粉丝点击