游戏开始时选择武将后,服务端统一控制向玩家分发初始手牌,并向客户端发生绘制手牌的命令.跟踪的流程如下:服务端向客户端发送命令drawCards及随后的一串使用加号(+)连接起来的牌编号(drawCards 91+50+112+100+42+133+43+78).客户端解析这个命令串后调用drawCards函数(原理在上面的系列分析中已经阐述),并使用后面的字符串作为参数,在drawCards函数中解析出牌的编号,根据牌的编号得到Card对象实例,加入到cards列表及ClientPlayer类型全局对象(Self)的手牌列表.接着触发cards_drawed信号.
void Client::drawCards(const QString &cards_str){
QList<const Card*> cards;
QStringList card_list = cards_str.split("+");
foreach(QString card_str, card_list){
int card_id = card_str.toInt();
const Card *card = Sanguosha->getCard(card_id);
cards << card;
Self->addCard(card, Player::Hand);
}
pile_num -= cards.length();
updatePileNum();
emit cards_drawed(cards);
}
roomScene类的构造函数中关联了cards_drawed信号:connect(ClientInstance, SIGNAL(cards_drawed(QList<const Card*>)), this, SLOT(drawCards(QList<const Card*>)));在这个信号处理函数中,遍历每张牌,生成新的CardItem对象.注意,Card类是一个从QObject继承的,而CardItem是从QGraphicsObject继承而来,因此CardItem可以加入到场景中并在视图中显示.接着设置显示位置和使能,加入到玩家的控制区域(dashboard,翻译为仪表盘),最后写日志信息.
void RoomScene::drawCards(const QList<const Card *> &cards){
foreach(const Card * card, cards){
CardItem *item = new CardItem(card);
item->setPos(room_layout->drawpile);
item->setEnabled(false);
dashboard->addCardItem(item);
}
log_box->appendLog("#DrawNCards", Self->getGeneralName(), QStringList(), QString(), QString::number(cards.length()));
}
Dashboard::addCardItem方法将代表牌的每个CardItem加入到自己的子列表中,使牌的图像可以显示在仪表盘中,并调整显示位置,添加到card_items中进行管理,设置card_item的信号处理曹函数.这里可以看到card_item的鼠标点击等信号的处理绑定.
void Dashboard::addCardItem(CardItem *card_item){
card_item->filter(filter);
if(ClientInstance->getStatus() == Client::Playing)
card_item->setEnabled(card_item->getFilteredCard()->isAvailable(Self));
else{
card_item->setEnabled(true);
card_item->setEnabled(false);
}
card_item->setPos(mapFromScene(card_item->pos()));
card_item->setParentItem(this);
card_item->setRotation(0.0);
card_item->setFlags(ItemIsFocusable);
card_item->setZValue(0.1);
card_items << card_item;
connect(card_item, SIGNAL(clicked()), this, SLOT(onCardItemClicked()));
connect(card_item, SIGNAL(thrown()), this, SLOT(onCardItemThrown()));
connect(card_item, SIGNAL(enter_hover()), this, SLOT(onCardItemHover()));
connect(card_item, SIGNAL(leave_hover()), this, SLOT(onCardItemLeaveHover()));
sortCards(sort_type);
handcard_num->setText(QString::number(Self->getHandcardNum()));
//handcard_num->parentItem()->show();
}
看一下onCardItemClicked曹函数,这个函数没有参数,但借助于QT的信号曹机制,可以使用sender()获得信号的发出对象,即被点击的那张牌.借助设置牌的选择标志,触发card_selected信号,激活处理函数enableTargets(在roomScene构造函数中绑定connect(dashboard, SIGNAL(card_selected(const Card*)), this, SLOT(enableTargets(const Card*)));)这个函数去调整出牌界面的确定按钮的使能.
void Dashboard::onCardItemClicked(){
CardItem *card_item = qobject_cast<CardItem *>(sender());
if(!card_item)
return;
if(view_as_skill){
if(card_item->isPending()){
card_item->unselect();
pendings.removeOne(card_item);
}else{
card_item->select();
pendings << card_item;
}
updatePending();
}else{
if(card_item->isPending()){
unselectAll();
emit card_selected(NULL);
}else{
unselectAll();
card_item->select();
//card_item->goBack();
selected = card_item;
emit card_selected(selected->getFilteredCard());
}
}
}
出牌界面的确认按钮为ok_button,其点击事件处理函数为:
void RoomScene::doOkButton(){
if(!ok_button->isEnabled())
return;
useSelectedCard();
}
void RoomScene::useSelectedCard(){
switch(ClientInstance->getStatus()){
case Client::Playing:{
const Card *card = dashboard->getSelected();
if(card)
useCard(card);
break;
}
......
}
过去客户端的状态,如果是正在游戏状态,则调用useCard函数:
void RoomScene::useCard(const Card *card){
if(card->targetFixed() || card->targetsFeasible(selected_targets, Self))
ClientInstance->onPlayerUseCard(card, selected_targets);
enableTargets(NULL);
}
onPlayerUseCard函数通知服务端玩家所出的牌和目标玩家:
void Client::onPlayerUseCard(const Card *card, const QList<const Player *> &targets){
if(card == NULL){
replyToServer(S_COMMAND_USE_CARD, Json::Value::null);
// request("useCard .");
}else{
Json::Value targetNames(Json::arrayValue);
foreach(const Player *target, targets)
targetNames.append(toJsonString(target->objectName()));
replyToServer(S_COMMAND_USE_CARD, toJsonArray(card->toString(), targetNames));
//if(target_names.isEmpty())
// request(QString("useCard %1->.").arg(card->toString()));
//else
// request(QString("useCard %1->%2").arg(card->toString()).arg(target_names.join("+")));
if(status == Responsing)
card_pattern.clear();
}
setStatus(NotActive);
}