vs2008编译QT开源项目三国杀(五篇文章)

请参看 http://tieba.baidu.com/f?kz=1508964881

按照上面的网址教程,下载三国杀源码,swig工具,并下载最新的QT4.8.2 for vs2008.我本机已经安装好了vs2008和QT4.7,因此下载QT4.8.2后直接安装,并在vs2008的QT菜单中点击QT Options子菜单,设置默认的QT/Win版本为4.8.2.使用vs2008打开QSanguosha.pro工程文件,转换为QSanguosha.sln.这时编译程序报无法找到fmodex.lib文件,这个文件是directx的声音文件库.搜索三国杀源码目录,可以找到,直接在项目属性中设置lib搜索路径,添加"./lib"即可成功编译.

后面逐步分析源码。

一、启动界面

从main函数中开始跟踪,找到如下代码

MainWindow *main_window = new MainWindow;

Sanguosha->setParent(main_window);
    main_window->show();

在MainWindow类的构造函数中,创建连接对话框和配置对话框实例,并将其exec()/show()槽与Action的triggered信号关联,Action触发时显示对话框,并将对话框的信号与相应处理槽函数关联,一行代码搞定,代码简洁高效.

connection_dialog = new ConnectionDialog(this);
    connect(ui->actionStart_Game, SIGNAL(triggered()), connection_dialog, SLOT(exec()));
    connect(connection_dialog, SIGNAL(accepted()), this, SLOT(startConnection()));

config_dialog = new ConfigDialog(this);
    connect(ui->actionConfigure, SIGNAL(triggered()), config_dialog, SLOT(show()));
    connect(config_dialog, SIGNAL(bg_changed()), this, SLOT(changeBackground()));

connect(ui->actionAbout_Qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));

接着创建启动场景(start_scene),并创建启动画面中的10个启动按钮,将10个Action对象存入一个QList中,其中每个Action都对应创建一个按钮(Button类,继承与QGraphicsObject),并添加到启动场景(start_scene)中.

StartScene *start_scene = new StartScene;

QList<QAction*> actions;
    actions << ui->actionStart_Game
            << ui->actionStart_Server
            << ui->actionPC_Console_Start
            << ui->actionReplay
            << ui->actionConfigure
            << ui->actionGeneral_Overview
            << ui->actionCard_Overview
            << ui->actionScenario_Overview
            << ui->actionAbout
            << ui->actionAcknowledgement;

foreach(QAction *action, actions)
        start_scene->addButton(action);

创建一个QGraphicView对象,并显示在主窗体的中心位置,设置view的场景为启动场景.

view = new FitView(scene);

setCentralWidget(view);
    restoreFromConfig();
 //让view显示start_scene
    gotoScene(start_scene);

二、Button类

启动界面的按钮效果很酷,鼠标滑过有动画效果,并且有声音,和大型网游效果很像.其实现很简单,Button类是从QGraphicObject继承的,在其内部处理鼠标事件和自绘.首先看Button的构造函数,里面直接调用了一个Init成员函数,Init函数中设置Button可接收焦点,可接收鼠标悬停事件,并根据构造函数的title参数创建一个QGraphicsPixmapItem对象,在其上drawText按钮的标题文字,在当前对象的位置之上显示这个图像,注意这个图像对象是在Button的构造函数中show出来的,因此其总会在Button实例的上方,但其不能接受焦点和鼠标事件,因此不影响Button对象对鼠标事件的处理.接着加载指定的按钮图像,并缩放为目标大小,存储在outimg成员中.

setFlags(ItemIsFocusable);

setAcceptHoverEvents(true);
    setAcceptedMouseButtons(Qt::LeftButton);

title = new QPixmap(size.toSize());
    title->fill(QColor(0,0,0,0));//填充完全透明的黑色,这样只能显示绘制的文字,其他部分不会覆盖底层图元
    QPainter pt(title);
    pt.setFont(font);
    pt.setPen(Config.TextEditColor);
    pt.setRenderHint(QPainter::TextAntialiasing);
    pt.drawText(boundingRect(), Qt::AlignCenter, label);

title_item = new QGraphicsPixmapItem(this);
    title_item->setPixmap(*title);
    title_item->show();

......

QImage bgimg("image/system/button/button.png");
    outimg = new QImage(size.toSize(),QImage::Format_ARGB32);

qreal pad = 10;

int w = bgimg.width();
    int h = bgimg.height();

int tw = outimg->width();
    int th  =outimg->height();

qreal xc = (w - 2*pad)/(tw - 2*pad);
    qreal yc = (h - 2*pad)/(th - 2*pad);

for(int i=0;i<tw;i++)
        for(int j=0;j<th;j++)
        {
            int x = i;
            int y = j;

if( x>=pad && x<=(tw - pad) ) x = pad + (x - pad)*xc;
            else if(x>=(tw-pad))x = w - (tw - x);

if( y>=pad && y<=(th - pad) ) y = pad + (y - pad)*yc;
            else if(y>=(th-pad))y = h - (th - y);

QRgb rgb = bgimg.pixel(x,y);
            outimg->setPixel(i,j,rgb);
        }

Button类的paint虚方法重载实现很简单,直接绘制outimg图像,并根据动画效果需要在图像上方绘制一个白色半透明的矩形区域.

QRectF rect = boundingRect();

painter->drawImage(rect,*outimg);
    painter->fillRect(rect,QColor(255,255,255,glow*10));

为了实现动画效果,鼠标划入时触发的hoverEnterEvent事件中设置按钮拥有焦点,播放声音,并调用QObject::startTimer函数启动定时器,在timerEvent事件中调用update函数触发重绘,并增减glow变量,调整按钮上方绘制的矩形区域的透明度----当按钮拥有焦点时增加可见度,呈现淡白色朦胧效果,失去焦点则减少可见度,直到使按钮图片完全显示出来.

void Button::hoverEnterEvent(QGraphicsSceneHoverEvent *){
    setFocus(Qt::MouseFocusReason);

#ifdef AUDIO_SUPPORT

if(!mute)
        Sanguosha->playAudio("button-hover");

#endif

if(!timer_id)timer_id = QObject::startTimer(40);
}

void Button::timerEvent(QTimerEvent *)
{
    update();
    if(hasFocus())
    {
        if(glow<5)glow++;
    }else
    {
        if(glow>0)glow--;
        else if(timer_id)
        {
            QObject::killTimer(timer_id);
            timer_id = 0;
        }
    }
}

三、声音

太阳神三国杀中声音很流畅亮丽.实现采用开源跨平台的游戏声音引擎fmod,详细内容请参见:http://baike.baidu.com/view/656662.htm.内部将fmod操作封装在Sound类中,这个类很简单,数行代码而已.

class Sound;

static FMOD_SYSTEM *System;
static FMOD_SOUND *BGM;
static FMOD_CHANNEL *BGMChannel;

class Sound{
public:
    Sound(const QString &filename)
        :sound(NULL), channel(NULL)
    {
        FMOD_System_CreateSound(System, filename.toAscii(), FMOD_DEFAULT, NULL, &sound);
    }

~Sound(){
        if(sound)
            FMOD_Sound_Release(sound);
    }

void play(){
        if(sound){
            FMOD_RESULT result = FMOD_System_PlaySound(System, FMOD_CHANNEL_FREE, sound, false, &channel);

if(result == FMOD_OK){
                FMOD_Channel_SetVolume(channel, 1.000/*Config.EffectVolume*/);
                FMOD_System_Update(System);
            }
        }
    }

bool isPlaying() const{
        if(channel == NULL)
            return false;

FMOD_BOOL is_playing = false;
        FMOD_Channel_IsPlaying(channel, &is_playing);
        return is_playing;
    }

private:
    FMOD_SOUND *sound;
    FMOD_CHANNEL *channel;
};

在项目启动时初始化fmod:

FMOD_RESULT result = FMOD_System_Create(&System);

if(result == FMOD_OK){
        FMOD_System_Init(System, 100, 0, NULL);
    }

在项目结束时释放fmod:

if(System){
        SoundCache.clear();
        FMOD_System_Release(System);

System = NULL;
    }

注意,fmod需要6个头文件:fmod.h,fmod_codec.h,fmod_dsp.h,fmod_errors.h,fmod_memoryinfo.h,fmod_output.h,以及一个lib文件fmodex.lib,一个dll文件fmodex.dll.可以直接将上面的类和8个文件移植到自己的项目中使用,测试通过.唯一需要注意的是Sound对象的析构函数中会结束音频播放,因此如果声明了一个临时变量,需要等待声音播放完毕才能跳出Sound对象的作用域,否则声音未等播放已经结束了.

四、如何进入到RoomScene

进入游戏后需要首先点击Start Server按钮建立服务端,再点击Start game菜单,重新启动一个进程,在新进程中点击Start game按钮,弹出连接窗体,输入服务器IP地址及用户名后可以加入到游戏中,直接进入正式游戏界面.这里创建了两个进程,第一个是服务端,第二个是客户端.为了跟踪第二个exe进程,需要首先直接启动一个exe进程,在启动第二个进程后,点击vs2008的调试菜单--附加到进程,找到第二个三国杀进程,即可在源码中设置断点跟踪了.这里描述一下客户端建立游戏的过程.

点击Start game按钮后,弹出一个连接窗体,窗口对象的accepted信号与startConnection槽相关联,点击连接按钮后,触发这个函数,创建Client类的实例,在其version_checked信号的响应函数checkVersion中,判断客户端与服务端的版本号是否匹配,如果匹配则与服务端建立连接,客户端对象的server_connected信号触发enterRoom函数,进入到游戏界面.

connect(connection_dialog, SIGNAL(accepted()), this, SLOT(startConnection()));  //构造函数中连接窗体返回触发startConnection
//startConnection函数启动Client对象,并设置信号与槽的连接
void MainWindow::startConnection(){
    Client *client = new Client(this);
    connect(client, SIGNAL(version_checked(QString,QString)), SLOT(checkVersion(QString,QString)));
    connect(client, SIGNAL(error_message(QString)), SLOT(networkError(QString)));
}
//checkVersion中比较版本号,并进入到游戏界面
void MainWindow::checkVersion(const QString &server_version, const QString &server_mod){
    QString client_mod = Sanguosha->getMODName();
    if(client_mod != server_mod){
        QMessageBox::warning(this, tr("Warning"), tr("Client MOD name is not same as the server!"));
        return;
    }
    Client *client = qobject_cast<Client *>(sender());
    QString client_version = Sanguosha->getVersionNumber();
    if(server_version == client_version){
        client->signup();
        connect(client, SIGNAL(server_connected()), SLOT(enterRoom()));
        if(qApp->arguments().contains("-hall")){
            HallDialog *dialog = HallDialog::GetInstance(this);
            connect(client, SIGNAL(server_connected()), dialog, SLOT(accept()));
        }
        return;
    }
......

再看一下核心函数enterRoom.设置好服务端IP地址并登陆成功后,触发这个函数.首先将这个IP地址保存在Config中.设置相关Action的Enabled属性使相应按钮和菜单失效变灰.创建RoomScene对象,进行相关设置.最后调用gotoScene(room_scene);切换到游戏界面.

五、游戏界面的创建

游戏界面的元素完全创建在RoomScene场景类中,只要打开游戏查看效果并对照代码和image\system目录中的图片,即可分析出对应界面是如何创建出来的.下面逐一解读.首先根据从游戏服务端获取的玩家总数,生成代表每个异地玩家的图标.

//创建代表其他玩家的头像,不用创建当前玩家
    int i;
    for(i = 0; i < player_count - 1;i++){
        Photo *photo = new Photo;
        photos << photo;
        addItem(photo);
        photo->setZValue(-0.5);
    }

接着创建操作面板,这个操作面板包括界面上的按钮区域,还有当前玩家的装备区和手牌区域.

//添加右下方的操作面板及按钮
    {
        createControlButtons();
        QGraphicsItem *button_widget = NULL;
        if(ClientInstance->getReplayer() == NULL){
            QString path = "image/system/button/irregular/background.png";
            button_widget = new QGraphicsPixmapItem(QPixmap(path));
   //四个不规则按钮
            ok_button->setParentItem(button_widget);
            cancel_button->setParentItem(button_widget);
            discard_button->setParentItem(button_widget);
            trust_button->setParentItem(button_widget);
        }
  
        // create dashboard 仪表盘 包括玩家装备和手牌区域
        dashboard = new Dashboard(button_widget);
        dashboard->setObjectName("dashboard");
        //dashboard->setZValue(0.8);
        addItem(dashboard);

调用createStateItem();函数创建选择反贼和英雄的两个按钮.

创建聊天区域控件:

chat_box = new QTextEdit;
        QSize chat_box_size = room_layout->chat_box_size;
        chat_box_size.rwidth() += widen_width;
        chat_box->resize(chat_box_size);
        chat_box->setObjectName("chat_box");

chat_box_widget = addWidget(chat_box);

输入聊天信息的textEdit控件:

chat_edit = new QLineEdit;
        chat_edit->setFixedWidth(chat_box->width());
        chat_edit->setObjectName("chat_edit");
  右边的系统信息显示框:

chat_widget = new ChatWidget();
        chat_widget->setX(chat_box_widget->x()+chat_edit->width() - 77);
        chat_widget->setY(chat_box_widget->y()+chat_box->height() + 9);
        chat_widget->setZValue(-0.2);
        addItem(chat_widget);

最底部的两个ComboBox:

sort_combobox = new QComboBox;

sort_combobox->addItem(tr("No sort"));
        sort_combobox->addItem(tr("Sort by color"));
        sort_combobox->addItem(tr("Sort by suit"));
        sort_combobox->addItem(tr("Sort by type"));
        sort_combobox->addItem(tr("Sort by availability"));

connect(sort_combobox, SIGNAL(currentIndexChanged(int)), dashboard, SLOT(sortCards(int)));
    }

connect(Self, SIGNAL(pile_changed(QString)), this, SLOT(updatePileButton(QString)));

// add role combobox
    role_combobox = new QComboBox;
    role_combobox->addItem(tr("Your role"));
    role_combobox->addItem(tr("Unknown"));
    connect(Self, SIGNAL(role_changed(QString)), this, SLOT(updateRoleComboBox(QString)));

进入游戏界面的生成基本上介绍完毕,下面将分多个文章分别介绍各个类的作用和实现机制.

http://blog.csdn.net/henreash/article/details/7817792

时间: 2024-08-06 23:11:57

vs2008编译QT开源项目三国杀(五篇文章)的相关文章

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

太阳神三国杀的界面很绚丽,界面上按钮的图标,鼠标移入移出时图标的变化,日志和聊天Widget的边框和半透明等效果,既可以通过代码来控制,也可以使用皮肤文件qss进行控制.下面我们分析一下三国杀的qss文件. 在main.cpp中可以看到如下几句关键代码: QDir::setCurrent(qApp->applicationDirPath());//设置当前目录为程序的可执行文件所在目录 //设置皮肤    QFile file("sanguosha.qss");    if(fi

Android开源项目第五篇——优秀个人和团体篇

本文为那些不错的Android开源项目第五篇——优秀个人和团体篇,主要介绍那些乐于分享并且有一些很不错的开源项目的个人和组织(公司) Android开源项目系列汇总已完成,包括: Android开源项目第一篇——个性化控件(View)篇 Android开源项目第二篇——工具库篇 Android开源项目第三篇——优秀项目篇 Android开源项目第四篇——开发及测试工具篇 Android开源项目第五篇——优秀个人和团体篇 Follow大神,深挖大神的项目和following,你会发现很多.长期更新

Android开源项目第四篇——开发及测试工具篇

本文为那些不错的Android开源项目第四篇——开发工具篇,主要介绍Android开发工具和测试工具相关的开源项目. Android开源项目系列汇总已完成,包括: Android开源项目第一篇——个性化控件(View)篇 Android开源项目第二篇——工具库篇 Android开源项目第三篇——优秀项目篇 Android开源项目第四篇——开发及测试工具篇 Android开源项目第五篇——优秀个人和团体篇 1.Buck facebook开源的Android编译工具,效率是ant的两倍.主要优点在于

Android开源项目第三篇——优秀项目篇

本文为那些不错的Android开源项目第三篇——优秀项目篇,主要介绍那些还不错的完整Android项目. Android开源项目系列汇总已完成,包括: Android开源项目第一篇——个性化控件(View)篇 Android开源项目第二篇——工具库篇 Android开源项目第三篇——优秀项目篇 Android开源项目第四篇——开发及测试工具篇 Android开源项目第五篇——优秀个人和团体篇 记录的项目主要依据是项目有意思或项目分层规范比较好. Linux 项目地址:https://github

可能是国内最火的开源项目 —— C/C++ 篇

高性能 TCP/UDP/HTTP 通信框架 HP-Socket https://www.oschina.net/p/hp-socket 评分:9.8,收藏:1404 HP-Socket 是一套通用的高性能 TCP/UDP/HTTP 通信框架,包含服务端组件.客户端组件和Agent组件,广泛适用于各种不同应用场景的 TCP/UDP/HTTP 通信系统,提供 C/C++.C#.Delphi.E(易语言).Java.Python 等编程语言接口.HP-Socket 对通信层实现完全封装,应用程序不必关

【转】高手速成android开源项目【View篇】

主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.ProgressBar及其他如Dialog.Toast.EditText.TableView.Activity Animation等等. 一.ListView android-pulltorefresh 一个强大的拉动刷新开源项目,支持各种控件下拉刷新ListView.ViewPager.WevView.ExpandableListVie

CSDN首页&gt; 移动开发 直接拿来用!最火的Android开源项目(完结篇)

此前,CSDN移动频道推出的GitHub平台上“最受欢迎的开源项目”系列文章引发了许多读者的热议,在“直接拿来用!最火的Android开源项目”系列文章(一).(二)中,我们也相继盘点了40个GitHub上最受欢迎的Android开源项目.对于GitHub上如此众多的项目,有人不断Mark,有人分享自己的经验,有人心生“看得眼花缭乱,果然是需要终身学习的时代”的感慨,不管怎么样,如果能让你真的有所学习有所收获,我们的目的也就达到了. 今天我们将继续介绍GitHub上另外34个非常受欢迎的Andr

直接拿来用!最火的Android开源项目(完结篇)

摘要:截至目前,在GitHub“最受欢迎的开源项目”系列文章中我们已介绍了40个Android开源项目,对于如此众多的项目,你是Mark.和码友分享经验还是慨叹“活到老要学到老”?今天我们将继续介绍另外34个Android开源项目. 此前,CSDN移动频道推出的GitHub平台上“最受欢迎的开源项目”系列文章引发了许多读者的热议,在“直接拿来用!最火的Android开源项目”系列文章(一).(二)中, 我们也相继盘点了40个GitHub上最受欢迎的Android开源项目.对于GitHub上如此众

GitHub上最火的Android开源项目(完结篇)

摘要:截至目前,在GitHub“最受欢迎的开源项目”系列文章中我们已介绍了40个Android开源项目,对于如此众多的项目,你是Mark.和码友分享经验还是慨叹“活到老要学到老”?今天我们将继续介绍另外34个Android开源项目. 此前,CSDN移动频道推出的GitHub平台上“最受欢迎的开源项目”系列文章引发了许多读者的热议,在“直接拿来用!最火的Android开源项目”系列文章(一).(二)中, 我们也相继盘点了40个GitHub上最受欢迎的Android开源项目.对于GitHub上如此众