Game Programming Using QT第三章

(01) 游戏目的

学习使用Qt 集成开发环境开发带有图形交互接口的程序。

Learn to use Qt to develop applications with a graphical user interface using the Qt Creator IDE.

熟悉Qt的核心函数,系统属性,信号槽机制。

Get familiar with the core Qt functionality, property system, and the signals and slots mechanism。

(02) 开始创建游戏

下载安装完成Qt Creator,之后,选择Welcome -> New Project -> Application


(03) 选择Choose之后,命名为tictactoe,选择Next


(04) 新建类名TicTacToeWidget, 父类Qwidget

(05) Next -> Finsh,系统会自动生成代码

1. tictactoe.pro 文件目录结构

#-------------------------------------------------
#
# Project created by QtCreator 2017-03-11T15:01:01
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = tictactoe
TEMPLATE = app


SOURCES += main.cpp\
        tictactoewidget.cpp

HEADERS  += tictactoewidget.h
2. tictactoewidget.h 头文件

#ifndef TICTACTOEWIDGET_H
#define TICTACTOEWIDGET_H

#include <QWidget>

class TicTacToeWidget : public QWidget
{
    Q_OBJECT

public:
    TicTacToeWidget(QWidget *parent = 0);
    ~TicTacToeWidget();
};

#endif // TICTACTOEWIDGET_H
3. tictactoewidget.cpp 源码文件

We can set a parent for an object in two ways. One way is to call the setParent method defined in QObject that accepts a QObject pointer.

The other way is to pass a pointer to the parent object to  the QWidget constructor of the child object.

#include "tictactoewidget.h"

TicTacToeWidget::TicTacToeWidget(QWidget *parent)
    : QWidget(parent)
{
}

TicTacToeWidget::~TicTacToeWidget()
{

}

4. main.cpp 主文件

QApplication is a singleton class that messages the whole application. In particular, it is responsible for processing event that come from within the application or from external sources.

#include "tictactoewidget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TicTacToeWidget w;
    w.show();

    return a.exec();
}
(06) 点击右下方三角按钮运行生成如下结果,

一个空的窗口,窗口的名字为命名的程序名tictactoe

(07)新建窗口完成之后,需要向空白窗口中添加组件。

In Qt, we group objects (such as widgets) into parent-child relationships. This scheme is defined in the superclass of QWidget - QObject.

What is important now is that each object can have a parent object and an arbitrary number of children.

If it doesn't have a parent, then it becomes a top-level window that can be usually be dragged around, resized, and closed.

(08) 布局layout

When we set a layout on a widget, we can start adding widgets and even other layouts, and the mechanism will resize and reposition them according to the rules that we specify.

Qt comes with a predefined set of layouts that are derived from the QLayout class.

QHBoxLayout

QVBoxLayout

QGridLayout

QFormLayout 创建表格形式的布局

To use a layout, we need to create an instance of it and pass a pointer to a widget that we want it to manage.

(09) 可以在构造函数中添加按钮

TicTacToeWidget::TicTacToeWidget(QWidget *parent)
    : QWidget(parent)
{
    QHBoxLayout *layout = new QHBoxLayout;
    QPushButton *button1 = new QPushButton;
    QPushButton *button2 = new QPushButton;
    button1->setText("QT");
    button2->setText("Program");
    layout->addWidget(button1);
    layout->addWidget(button2);
    setLayout(layout);
}
生成如下界面:

(10) 设置布局的属性,增加以下两个属性,可以看到按钮的位置发生变化

    layout->setSpacing(50);
    layout->setMargin(100);

Note that, even though we didn't explictly pass the parent widget pointer, adding a wdiget to a layout makes it reparent the newly added widget to the widget that the layout manages.

(11) size policy

Decide how a widget is to be resized by a layout.

A button has vertical size policy of Fixed, which means that the height of the widget will not change from the default height regardless how much space there is available.

Ignore

Fixed: the default size is the only allowed size of the widget

Preferred

Minimum

Maximum

Expanding

MinimumExpanding

(12) implementing a tic-tac-toe game board

1. Our additions create a list that can hold pointers to instances of the QPushButton class, which is the most commonly used button class in Qt.

In tictactoewidget.h

private:
    QList<QPushButton *> board;
2. The next step is to create a method that will help us create all the buttons and use a layout to manage their geometries.

void TicTacToeWidget::setupTicTacToeBoard()
{
    QGridLayout *gridLayout = new QGridLayout;

    for (int row = 0; row < 3; ++row) {
        for (int column = 0; column < 3; ++column) {
            QPushButton *button = new QPushButton;
            button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
            button->setText("");
            // Set button size width & height.
            button->setFixedSize(100, 62);

            gridLayout->setMargin(40);
            gridLayout->setSpacing(20);

            gridLayout->addWidget(button, row, column);
            board.append(button);   // C++ STL algorithm
        }
    }
    // Tell our widget that gridLayout is going to manage its size.
    setLayout(gridLayout);
}
The code creates a loop over rows and columns of the board. In each iteration, it creates an instance of the QPushButton class and sets the button's size policy to Minimum/Minimum so that we resize the widget, buttons also get resized. A button is assigned a single space as its content so that it gets the correct initial size. Then, we add the button to the layout in row and column. At the end, we store the pointer to the button in the list that  was declared earlier. This lets us reference any of the buttons later on.

运行效果:


(14)新增一个public方法,当需要重新开始游戏的时候清楚之前的内容。

void TicTacToeWidget::initTicTacToeGame()
{
    for (int i = 0; i < 9; i++)
        board.at(i)->setText("");
}
(15)重点 Qt meta-objects

Most of the special functionality that Qt offers revolves around the QObject class and the meta-object paradigm.

The paradigm says that with every QObject subclass, there is a special object associated that contains information about that class.

It allows us to make runtime queries to learn useful things about the class - the class name, superclass, constructors, methods, fields, enumerations, and so on.

The meta-object is generated for the class at compile time when three conditions are met:

* The class is a descendant of QObject

* It contains a special Q_OBJECT macro in a private section of its definition.

* Code of the class is preprocessed by a special Meta-Object Compiler (moc) tool.

(16)重点 Signals and Slots

信号与槽

Signal and slots can be used with all classes that inherit QObject. A signal can be connected to a slot, member function, or functor (which includes a regular global function).

When an object emits signal, any of these entities that are connected to that signal will be called.

A signal slot connection is defined by the following four attributes:

* An object that changes its state (sender)

* A signal in the sender object

* An object that contains the function to be called (receiver)

* A slot in the receiver

(17) 信号与槽举例

A sample class implementing some signals and slots looks like as shown in the following code:

class ObjectWithSignalAndSlots : public QObject {
Q_OBJECT
public:
    QObjectWithSignalAndSlots(QObject *parent = 0) : QObject(parent) {
    }
public slots:
    void setValue(int v) {}
signals:
    void valueChanged(int);
};
Signals and slots can be connected and disconnected dynamically using the connect() and disconnect() statements.

(18)使用lambda表达式

connect(pushButton, SIGNAL(clicked()), [](){std::cout << "clicked!" << std::endl;});

(19) 问答题

1. 答案选2

2. valid 2, 4.

You can only make a connection between a signal and slot that have matching signatures, which means that they accept the same types of arguments (any type casts are not allowed, and type names have to match exactly) with the exception that the slot can omit an arbitrary number of last arguments.

(20) 每次选手点击按钮时需要根据是哪个选手改变该按钮的内容,同时检查是否该选手赢得比赛。

When the user clicks on a button, the clicked() signal is emitted.

When a slot is invoked, a pointer to the object that caused the signal to be sent is accessible through a special method in QObject called sender().

void TicTacToeWidget::someSlot() {
    QObject *btn = sender();
    int index = board.indexOf(btn);
    QPushButton *button = board.at(index);
}
(21)  更好的方法获取发signal的对象

QSignalMapper

void TicTacToeWidget::setupTicTacToeBoard()
{
    QGridLayout *gridLayout = new QGridLayout;
    QSignalMapper *mapper = new QSignalMapper(this);

    for (int row = 0; row < 3; ++row) {
        for (int column = 0; column < 3; ++column) {
            QPushButton *button = new QPushButton;
            button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
            button->setText("");
            // Set button size width & height.
            button->setFixedSize(100, 62);

            gridLayout->setMargin(40);
            gridLayout->setSpacing(20);

            gridLayout->addWidget(button, row, column);
            board.append(button);   // C++ STL algorithm

            mapper->setMapping(button, board.count() - 1);
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
            connect(mapper, SIGNAL(mapped(int)), this, SLOT(handleButtonClick(int)));
        }
    }
    // Tell our widget that gridLayout is going to manage its size.
    setLayout(gridLayout);
}
What the mapper will do is that it will then find the mapping of the sender of the signal and emit another signal - mapped() - with the mapped number as its parameter.

This allows us to connect to that signal with a slot (handleButtonClick) that takes the index of the button in the board list.

(22)程序基本框架搭建完成

#ifndef TICTACTOEWIDGET_H
#define TICTACTOEWIDGET_H

#include <QWidget>
#include <QHBoxLayout>
#include <QPushButton>

enum Player {
    Invalid, Player1, Player2, Draw
};

class TicTacToeWidget : public QWidget
{
    Q_OBJECT

public:
    TicTacToeWidget(QWidget *parent = 0);
    void initTicTacToeGame();

    Player currentPlayer() const { return mCurrentPlayer; }
    void setCurrentPlayer(Player p) {
        if (mCurrentPlayer == p) return;
        mCurrentPlayer = p;
        emit currentPlayerChanged(p);
    }

    Player checkWinCondition(int row, int column);

    ~TicTacToeWidget();

signals:
    void currentPlayerChanged(Player);
    void gameOver(Player);

public slots:
    void handleButtonClick(int);
private:
    void setupTicTacToeBoard();
    QList<QPushButton *> board;
    Player mCurrentPlayer;
};

#endif // TICTACTOEWIDGET_H

#include "tictactoewidget.h"
#include <QSignalMapper>

TicTacToeWidget::TicTacToeWidget(QWidget *parent)
    : QWidget(parent)
{
    setupTicTacToeBoard();
}

void TicTacToeWidget::initTicTacToeGame()
{
    for (int i = 0; i < 9; i++)
        board.at(i)->setText("");
}

Player TicTacToeWidget::checkWinCondition(int row, int column)
{
    return Invalid;
}

TicTacToeWidget::~TicTacToeWidget()
{

}

void TicTacToeWidget::handleButtonClick(int index)
{
    // out of bounds check
    if (index < 0 || index >= board.size()) return ;

    // invalid move
    QPushButton *button = board.at(index);
    if (button->text() != "") return ;

    button->setText(currentPlayer() == Player1 ? "XX" : "OO");
    Player winner = checkWinCondition(index / 3, index %3);
    if (winner == Invalid) {
        setCurrentPlayer((currentPlayer() == Player1) ? Player2 : Player1);
        return ;
    } else {
        emit gameOver(winner);
    }
}

void TicTacToeWidget::setupTicTacToeBoard()
{
    QGridLayout *gridLayout = new QGridLayout;
    QSignalMapper *mapper = new QSignalMapper(this);

    for (int row = 0; row < 3; ++row) {
        for (int column = 0; column < 3; ++column) {
            QPushButton *button = new QPushButton;
            button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
            button->setText("");
            // Set button size width & height.
            button->setFixedSize(100, 62);

            gridLayout->setMargin(40);
            gridLayout->setSpacing(20);

            gridLayout->addWidget(button, row, column);
            board.append(button);   // C++ STL algorithm

            mapper->setMapping(button, board.count() - 1);
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
            connect(mapper, SIGNAL(mapped(int)), this, SLOT(handleButtonClick(int)));
        }
    }
    // Tell our widget that gridLayout is going to manage its size.
    setLayout(gridLayout);
}

输出:

每次点击按钮都会变换对手,还没有实现检测胜利的算法,每次暂时都是返回Invalid.


(23)检查是否取胜的算法,横,竖,左斜,右斜,检查计数是否为3,如果为3,返回胜利者。

Player TicTacToeWidget::checkWinCondition(int row, int column)
{
    QString current = board.at(row * 3 + column)->text();
    int count = 0;
    // check horizontal sequence
    for (int c = 0; c < 3; ++c) {
        if (board.at(row*3 + c)->text() == current)
            ++count;
    }
    if (count == 3) return currentPlayer();
    count = 0;
    // check vertical sequence
    for (int r = 0; r < 3; ++r) {
        if (board.at(r * 3 + r)->text() == current)
            ++count;
        if (count ==3) return currentPlayer();
    }

    // if the move was done on diagonal, check the diagonal
    count = 0;
    if (row == column) {
        for (int c = 0; c < 3; ++c) {
            if (board.at(c*3+c)->text() == current)
                ++count;
        }
        if (count == 3) return currentPlayer();
    }
    count = 0;
    if (row + column == 2) {
        for (int c = 0; c < 3; ++c) {
            if (board.at((2-c)*3+c)->text() == current)
                ++count;
        }
        if (count == 3) return currentPlayer();
    }
    // check if therer are unoccupied fields left
    foreach(QPushButton *button, board)
        if (button->text() == "") return Invalid;
    return Draw;
}

TicTacToeWidget::~TicTacToeWidget()
{

}

void TicTacToeWidget::handleButtonClick(int index)
{
    // out of bounds check
    if (index < 0 || index >= board.size()) return ;

    // invalid move
    QPushButton *button = board.at(index);
    if (button->text() != "") return ;

    button->setText(currentPlayer() == Player1 ? "XX" : "OO");
    Player winner = checkWinCondition(index / 3, index %3);
    if (winner == Invalid) {
        setCurrentPlayer((currentPlayer() == Player1) ? Player2 : Player1);
        return ;
    } else {
        emit gameOver(winner);
    }
}

void TicTacToeWidget::setupTicTacToeBoard()
{
    QGridLayout *gridLayout = new QGridLayout;
    QSignalMapper *mapper = new QSignalMapper(this);

    for (int row = 0; row < 3; ++row) {
        for (int column = 0; column < 3; ++column) {
            QPushButton *button = new QPushButton;
            button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
            button->setText("");
            // Set button size width & height.
            button->setFixedSize(100, 62);

            gridLayout->setMargin(40);
            gridLayout->setSpacing(20);

            gridLayout->addWidget(button, row, column);
            board.append(button);   // C++ STL algorithm

            mapper->setMapping(button, board.count() - 1);
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
            connect(mapper, SIGNAL(mapped(int)), this, SLOT(handleButtonClick(int)));
        }
    }
    // Tell our widget that gridLayout is going to manage its size.
    setLayout(gridLayout);
}
(24) 属性

To create a property, we first need to declare it in a private section of a class that inherits QObject using a special Q_PROPERTY macro, which lets Qt know how to use the property.

class Tower : public QObject {
	// enable meta-object generation	
	Q_OBJECT
	// declare the property
	Q_PROPERTY(double height READ height)
public:
	Tower(QObject *parent = 0) : QObject(parent) {
		m_height = 6.28;
	}
	double height() const { return m_height; }
private:
	// internal member variable holding the property value
};
(25) 写属性

Q_PROPERTY(double height READ height WRITE setHeight)
(26) 使用属性

1. 使用height(), setHeight(int) 方法

2.  A generic property getter (which return the property value) is a method called property. Its setter counterpart is setProperty.

(27)本文中的property

Q_ENUMS(Player)
    Q_PROPERTY(Player currentPlayer READ currentPlayer WRITE setCurrentPlayer NOTIFY currentPlayerChanged)

Since we want to use an enumeration as a type of a property, we have to inform Qt's meta-object system about the enum.

(28) 使用Qt designer创建GUI界面

New File or Project -> Qt -> Qt designer Form Class.

main worksheet

widget box

Object Inspector & property editor

Action Editor & Signal/Slot Editor

(29) 编辑dialog

通过Tools -> Form Editor -> Preview 预览编辑框。

(30) 对编辑框进行润色

加快捷键

1. 在需要的快捷键面前加上&字符

2. main worksheet上面的Edit Buddies

可以看到Player中的P含有下划线,表示成功。

(31) Signals and slots

1. press F3 or Edit Widgets from the toolbar

2. 选择Go To Slots.

3. 需要双击,才会弹出界面。


(32)向程序中添加dialog

按照上面的操作,增加updateOKButtonState() slot,

实现

void Dialog::updateOKButtonState()
{
    bool pl1NameEmpty = ui->player1Name->text().isEmpty();
    bool pl2NameEmpty = ui->player2Name->text().isEmpty();

    QPushButton *okButton = ui->buttonBox->button(QDialogButtonBox::Ok);

    okButton->setDisabled(pl1NameEmpty || pl2NameEmpty);
}
(33) 获取,设置dialog的player1Name palyer2Name变量的属性

    Q_PROPERTY(QString player1Name READ palyer1Name WRITE setPlayer1Name)
    Q_PROPERTY(QString player2Name READ player2Name WRITE setPlayer2Name)

    void setPlayer1Name(const QString &p1name);
    void setPlayer2Name(const QString &p2name);
    QString player1Name() const;
    QString player2Name() const;

void Dialog::setPlayer1Name(const QString &p1name)
{
    ui->player1Name->setText(p1name);
}

void Dialog::setPlayer2Name(const QString &p2name)
{
    ui->player2Name->setText(p2name);
}

QString Dialog::player1Name() const
{
    return ui->player1Name->text();
}

QString Dialog::player2Name() const
{
    return ui->player2Name->text();
}
(34) Main Window,主窗口




...

(40)习题

习题1: 1

习题2: 1

习题3: 2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
A complete guide to designing and building fun games with Qt and Qt Quick 2 using associated toolsets About This Book Learn to create simple 2D to complex 3D graphics and games using all possible tools and widgets available for game development in Qt Understand technologies such as QML, Qt Quick, OpenGL, and Qt Creator, and learn the best practices to use them to design games Learn Qt with the help of many sample games introduced step-by-step in each chapter Who This Book Is For If you want to create great graphical user interfaces and astonishing games with Qt, this book is ideal for you. Any previous knowledge of Qt is not required, however knowledge of C++ is mandatory. What You Will Learn Install Qt on your system Understand the basic concepts of every Qt game and application Develop 2D object-oriented graphics using Qt Graphics View Build multiplayer games or add a chat function to your games with Qt's Network module Script your game with Qt Script Program resolution-independent and fluid UI using QML and Qt Quick Control your game flow as per the sensors of a mobile device See how to test and debug your game easily with Qt Creator and Qt Test In Detail Qt is the leading cross-platform toolkit for all significant desktop, mobile, and embedded platforms and is becoming more popular by the day, especially on mobile and embedded devices. Despite its simplicity, it's a powerful tool that perfectly fits game developers' needs. Using Qt and Qt Quick, it is easy to build fun games or shiny user interfaces. You only need to create your game once and deploy it on all major platforms like iOS, Android, and WinRT without changing a single source file. The book begins with a brief introduction to creating an application and preparing a working environment for both desktop and mobile platforms. It then dives deeper into the basics of creating graphical interfaces and Qt core concepts of data processing and display before you try creating a game. As you progress through the chapters, you'll learn to enrich your games by implementing network connectivity and employing scripting. We then delve into Qt Quick, OpenGL, and various other tools to add game logic, design animation, add game physics, and build astonishing UI for the games. Towards the final chapters, you'll learn to exploit mobile device features such as accelerators and sensors to build engaging user experiences. If you are planning to learn about Qt and its associated toolsets to build apps and games, this book is a must have. Style and approach This is an easy-to-follow, example-based, comprehensive introduction to all the major features in Qt. The content of each chapter is explained and organized around one or multiple simple game examples to learn Qt in a fun way. Table of Contents Chapter 1: Introduction to Qt Chapter 2: Installation Chapter 3: Qt GUI Programming Chapter 4: Qt Core Essentials Chapter 5: Graphics with Qt Chapter 6: Graphics View Chapter 7: Networking Chapter 8: Scripting Chapter 9: Qt Quick Basics Chapter 10: Qt Quick Appendix: Pop Quiz Answers
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值