接着上篇文章继续分析,我们来看看进入到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());
}