Клиент-серверная игра на Qt

клиент-серверная игра, Qt

Здравствуйте)
Возникла задача: написать клиент-серверную игру. Проблем с игровой частью не возникло (за основу я взял уроки по геймдеву, где определил две роли: муха и паук). Каждый пользователь, который подключится к серверу, может выбрать одну из ролей. При нажатии на кнопку “Готово” рисуется поле, где с одной стороны муха, а напротив – паук. Движение паука реализовано аналогично движению мухи.
Сам механизм связи между клиентом и сервером я выбрал такой: каждый клиент перед началом игры подключается к серверу. Клиент1 посылает данные на сервер, где сервер должен их переслать на второй клиент, и точно такой же механизм для второго клиента. Посылаются данные в виде двух чисел: угла поворота и числа пикселей, на которое перемещается объект. Данные я посылаю при любом нажатии на клавишу движения. Например:

            if(GetAsyncKeyState(Qt::Key::Key_K)){
                setPos(mapToParent(0, 2));
                emit signalSend(angle,2)
}; 

При нажатии на клавишу “K” сам объект в текущем клиенте перемещается вниз, при этом осуществляется emit сигнала, который передаёт текущий угол и количество пикселей, на которое перемещается объект.
На этом этапе всё работает. Но вот с пересылкой сигнала от одного клиента к другому проблема. Муха, на удивление, отлично работает, а с пауком проблема. Паук отлично ходит вперёд и назад, синхронно “крутится” на обоих клиентах, но вот почему-то при нажатии двух кнопок (вперёд и в сторону) одновременно выходит задержка, которая в будущем увеличивается.
Код сервера:

Server::Server(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Server)
{
    ui->setupUi(this);
    tcpSocket=new QTcpSocket(this);
    thread1 = new QThread();
 
    connect(ui->stopping,SIGNAL(clicked(bool)),this,SLOT(on_stoping_clicked()));
    connect(this,&Server::signalToSendAnswer1,this,&Server::slotSendToClient1);
    connect(this,&Server::signalToSendAnswer2,this,&Server::slotSendToClient2);
 
}
 
Server::~Server()
{
    delete ui;
    server_status=0;
}
void Server::on_starting_clicked()
{
    ui->starting->setEnabled(false);
    ui->stopping->setEnabled(true);
    tcpServer = new QTcpServer(this);
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newuser()));
    if (!tcpServer->listen(QHostAddress::Any, ui->portText->text().toInt()) && server_status==0) {
        qDebug() <<  QObject::tr("Unable to start the server: %1.").arg(tcpServer->errorString());
        ui->textinfo->append(tcpServer->errorString());
    } else {
        server_status=1;
        qDebug() << tcpServer->isListening() << "TCPSocket listen on port";
        ui->textinfo->append(QString::fromUtf8("Сервер запущен!"));
        qDebug() << QString::fromUtf8("Сервер запущен!");
    }
}
 
void Server::on_stoping_clicked()
{
    ui->starting->setEnabled(true);
    ui->stopping->setEnabled(false);
    if(server_status==1){
        foreach(int i,SClients.keys()){
            QTextStream os(SClients<em class="d4pbbc-italic"></em>);
            os.setAutoDetectUnicode(true);
            os << QDateTime::currentDateTime().toString() << "\n";
            SClients<em class="d4pbbc-italic"></em>->close();
            SClients.remove(i);
        }
        tcpServer->close();
        ui->textinfo->append(QString::fromUtf8("Сервер остановлен!"));
        qDebug() << QString::fromUtf8("Сервер остановлен!");
        server_status=0;
    }
}
 
void Server::newuser()
{
    if(server_status==1){
        qDebug() << QString::fromUtf8("У нас новое соединение!");
        ui->textinfo->append(QString::fromUtf8("У нас новое соединение!"));
        tcpSocket=tcpServer->nextPendingConnection();
        if(firstClient==0){
            firstClient=tcpSocket->socketDescriptor();
            SClients[firstClient]=tcpSocket;
        }else{
            secondClient=tcpSocket->socketDescriptor();
            SClients[secondClient]=tcpSocket;
        }
        connect(tcpSocket,SIGNAL(disconnected()),this, SLOT(slotDisconnectClient()));
        connect(SClients[secondClient],SIGNAL(readyRead()),this, SLOT(slotReadClient2()));
        connect(SClients[firstClient],SIGNAL(readyRead()),this, SLOT(slotReadClient()));
    }
}
 
void Server::slotReadClient()
{
   tcpSocket = (QTcpSocket*)sender();
      QDataStream in(tcpSocket);
      in.setVersion(QDataStream::Qt_4_2);
      for (;;) {
          m_nNextBlockSize = 0;
          if (!m_nNextBlockSize) {
              if (tcpSocket->bytesAvailable() < sizeof(quint16)) {
                  break;
              }
              in >> m_nNextBlockSize;
          }
 
          if (tcpSocket->bytesAvailable() < m_nNextBlockSize) {
              break;
          }
          qreal angle;
          qreal move;
          in>>angle>>move;
          //if(tcpSocket->socketDescriptor()==firstClient){
              emit signalToSendAnswer2(angle,move);
          //}
//          else if(tcpSocket->socketDescriptor()==secondClient){
//              emit signalToSendAnswer1(angle,move);
//          }
 
          m_nNextBlockSize = 0;
      }
}
void Server::slotReadClient2()
{
   tcpSocket = (QTcpSocket*)sender();
      QDataStream in(tcpSocket);
      in.setVersion(QDataStream::Qt_4_2);
      for (;;) {
          m_nNextBlockSize = 0;
          if (!m_nNextBlockSize) {
              if (tcpSocket->bytesAvailable() < sizeof(quint16)) {
                  break;
              }
              in >> m_nNextBlockSize;
          }
 
          if (tcpSocket->bytesAvailable() < m_nNextBlockSize) {
              break;
          }
          qreal angle;
          qreal move;
          in>>angle>>move;
          emit signalToSendAnswer1(angle,move);
 
          m_nNextBlockSize = 0;
      }
}
 
void Server::slotSendToClient2(qreal angle, qreal move)
{
    tcpSocket=SClients[secondClient];
    QByteArray arrBlock;
    QDataStream serverSendStream(&arrBlock, QIODevice::WriteOnly);
 
    serverSendStream << quint16(0) << angle<< move;
 
    serverSendStream.device()->seek(0);
    serverSendStream << quint16(arrBlock.size() - sizeof(quint16));
    tcpSocket->write(arrBlock);
}
 
void Server::slotSendToClient1(qreal angle, qreal move)
{
    tcpSocket=SClients[firstClient];
    QByteArray arrBlock;
    QDataStream serverSendStream(&arrBlock, QIODevice::WriteOnly);
 
    serverSendStream << quint16(0) << angle<< move;
 
    serverSendStream.device()->seek(0);
    serverSendStream << quint16(arrBlock.size() - sizeof(quint16));
    tcpSocket->write(arrBlock);
}
 
void Server::slotDisconnectClient()
{
    
}

Пока что код абсолютно не универсален, и он предусматривает лишь один правильный случай: первый подключившийся клиент – муха, а второй – паук, но с универсализацией я потом разберусь, я пока не могу понять, почему сервер виснет? Почему именно на пауке? И почему он не виснет, если двигать пауком просто вперёд и назад(и крутить его в разные стороны). Передача данных на сервер со стороны мухи и паука абсолютно идентична, обработка данных на сервере и пересылка второму клиенту также ничем не отличается, но при этом муха может свободно двигаться в любых направлениях, а стоит только нажать одновременно две клавиши у паука, как образуется задержка в движении. При этом я выделю ещё несколько интересных наблюдений:
1. Если сразу же пошевелить пауком (две клавиши одновременно), то образуется задержка. При этом на клиенте, который играет за паука, паук уже прошел какое-то расстояние (естественно, без задержки), а на втором клиенте(который муха), паук ещё не прошел то же расстояние. Если на клиенте, который играет пауком, остановиться – то паук остановиться на обоих клиентах. Но если продолжить движение пауком, то без задержки будет двигаться паук на клиенте, где выбран сам паук(что весьма логично)), а на втором клиенте паук будет повторять те движения, которые ещё не успел сделать, в следствии задержки. Как по мне, то это особенность Tcp соединения, в котором данные в любом случае должны дойти в полном объёме.
2. Если после п. 1 попробовать пошевелить мухой – сервер виснет)
3. Сервер не виснет даже если шевелить мухой (в разных направлениях, зажимая две клавиши одновременно) и одновременно нажимая на паука (вперёд или назад, или только крутиться).
4. Изменение таймера (промежутков, между тиками) ничего не меняет. При любом тике таймера (а таймер напрямую связан с количеством передач информации на сервер, так как обработка нажатий клавиш, как и emit сигнала на сервер, происходит при сигнале timeout) ситуация не меняется.
Как быть с моей проблемой?) Что можно сделать? Заранее спасибо

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

6
Evgenii Legotckoi
  • Oct. 10, 2016, 12:55 p.m.

Добрый день, Александр.

В первую очередь откажитесь от TCP, он не используется для активных игр в принципе, поскольку будет пытаться передать информацию до тех пор, пока передача не будет успешной. Для этих целей используют протокол UDP.

А с лагами обычно борются с помощью систем предсказаний. И это достаточно обширная область. К своему сожалению я пока не работаю в данной области, чтобы дать достаточно компетентный совет. Но постараюсь высказать свои мысли, может это поможет.

Например, в вашем случае, можно при нажатии кнопки пересылать направление и скорость движения. То есть с математической точки зрения вы будете пересылать вектор движения.
А при отпускании клавиши, или изменении комбинации клавиш можно передавать координаты положения игрока, где произошло изменение вектора, и новый вектор движения. Таким образом, кстати, можно будет снизить количество передаваемых пакетов.

Конечно, всё равно остаётся при данном совете вопрос, почему проблема происходит при одновременном нажатии клавиш для паука. Но тут хочу заметить, что Вы отметили проблему только в случае с пауком. Поэтому хотелось бы увидеть код как для мухи, так и для паука. Подозреваю, что возможна разница в коде, и где-то есть ошибка в обработке клавиш.

    Вот и для меня очень странным показался тот факт, что с мухой нет ну абсолютно никаких проблем (значит задержки как таковой и нету), да и при одновременном нажатии на две клавиши (со стороны мухи) и одной клавиши (со стороны паука) сервер ни чуточку не подвисает и всё синхронно отображается. Спасибо вам за советы) Вот код, возможно Вы найдёте ещё какие-либо ошибки:
    spider.h

    #ifndef SPIDER_H
    #define SPIDER_H
     
    #include <QObject>
    #include <QGraphicsItem>
    #include <QGraphicsScene>
    #include <QPainter>
    #include <QTimer>
    #include <QDebug>
    #include <windows.h>
     
    class Spider : public QObject, public QGraphicsItem
    {
        Q_OBJECT
    public:
        explicit Spider(bool isActive, QObject *parent = 0);
        ~Spider();
        void pause();   // Сигнал для инициализации паузы
     
    signals:
        void signalSend(qreal angle,qreal move);
     
    public slots:
     
    protected:
        QRectF boundingRect() const;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
     
    private:
        qreal angle;        // Угол поворота графического объекта
        qreal move;
        int steps;          // Номер положения ножек паука
        int countForSteps;  // Счетчик для изменения полоэения ножек
        QTimer      *timer;     // Внутренний таймер паука, по которому инициализируется его движение
        bool isActivePlayer=false;
     
    public slots:
        void slotGameTimer();   // Слот игрового таймера паука
        void slotAutoSpider(qreal angle, qreal move);  // Слот для автоматического управления пауком
    };
     
    #endif // SPIDER_H

    spider.cpp

    #include "spider.h"
     
    Spider::Spider(bool isActive, QObject *parent) :
        QObject(parent), QGraphicsItem()
    {
        angle = 0;      // Задаём угол поворота графического объекта
        steps = 0;      // Задаём стартовое положение ножек мухи
        move = 0;
        countForSteps = 0;      // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки
        setRotation(angle);     // Устанавливаем угол поворота графического объекта
     
       // timer = new QTimer();   // Инициализируем игровой таймер паука
        // подключаем сигнал таймера к игровому слоту паука
        //timer->start(15);       // Запускаем таймер
        this->isActivePlayer = isActive;    //проверяем, является ли данный клиент активным (управляем ли мы пауком)
    }
     
    Spider::~Spider()
    {
     
    }
     
    QRectF Spider::boundingRect() const
    {
        return QRectF(-40,-50,80,100);
    }
     
    void Spider::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        painter->setPen(QPen(Qt::black, 2));
        if(steps == 0){     // Первое положение ножек
            // Left 1
            painter->drawLine(-24,-45,-28,-35);
            painter->drawLine(-28,-35,-22,-10);
            painter->drawLine(-22,-10,0,0);
            // Right 1
            painter->drawLine(24,-45,28,-35);
            painter->drawLine(28,-35,22,-10);
            painter->drawLine(22,-10,0,0);
     
            // Left 2
            painter->drawLine(-35,-38,-30,-18);
            painter->drawLine(-30,-18,-25,-3);
            painter->drawLine(-25,-3,0,0);
            // Right 2
            painter->drawLine(35,-38,30,-18);
            painter->drawLine(30,-18,25,-3);
            painter->drawLine(25,-3,0,0);
     
            // Left 3
            painter->drawLine(-35,38,-30,18);
            painter->drawLine(-30,18,-25,3);
            painter->drawLine(-25,3,0,0);
            // Right 3
            painter->drawLine(35,38,30,18);
            painter->drawLine(30,18,25,3);
            painter->drawLine(25,3,0,0);
     
            // Left 4
            painter->drawLine(-24,45,-28,35);
            painter->drawLine(-28,35,-22,10);
            painter->drawLine(-22,10,0,0);
            // Right 4
            painter->drawLine(24,45,28,35);
            painter->drawLine(28,35,22,10);
            painter->drawLine(22,10,0,0);
        } else if (steps == 1){     // Второе положение ножек
            // Left 1
            painter->drawLine(-23,-40,-24,-30);
            painter->drawLine(-24,-30,-19,-9);
            painter->drawLine(-19,-9,0,0);
            // Right 1
            painter->drawLine(20,-50,23,-40);
            painter->drawLine(23,-40,15,-12);
            painter->drawLine(15,-12,0,0);
     
            // Left 2
            painter->drawLine(-30,-35,-27,-24);
            painter->drawLine(-27,-24,-23,-5);
            painter->drawLine(-23,-5,0,0);
            // Right 2
            painter->drawLine(40,-27,35,-10);
            painter->drawLine(35,-10,28,-1);
            painter->drawLine(28,-1,0,0);
     
            // Left 3
            painter->drawLine(-40,27,-35,10);
            painter->drawLine(-35,10,-28,1);
            painter->drawLine(-28,1,0,0);
            // Right 3
            painter->drawLine(30,35,27,24);
            painter->drawLine(27,24,23,5);
            painter->drawLine(23,5,0,0);
     
            // Left 4
            painter->drawLine(-20,50,-27,30);
            painter->drawLine(-27,30,-20,12);
            painter->drawLine(-20,12,0,0);
            // Right 4
            painter->drawLine(23,40,24,30);
            painter->drawLine(24,30,19,9);
            painter->drawLine(19,9,0,0);
        } else if (steps == 2){     // Третье положение ножек
            // Left 1
            painter->drawLine(-20,-50,-23,-40);
            painter->drawLine(-23,-40,-15,-12);
            painter->drawLine(-15,-12,0,0);
            // Right 1
            painter->drawLine(23,-40,24,-30);
            painter->drawLine(24,-30,19,-9);
            painter->drawLine(19,-9,0,0);
     
            // Left 2
            painter->drawLine(-40,-27,-35,-10);
            painter->drawLine(-35,-10,-28,-1);
            painter->drawLine(-28,-1,0,0);
            // Right 2
            painter->drawLine(30,-35,27,-24);
            painter->drawLine(27,-24,23,-5);
            painter->drawLine(23,-5,0,0);
     
            // Left 3
            painter->drawLine(-30,35,-27,24);
            painter->drawLine(-27,24,-23,5);
            painter->drawLine(-23,5,0,0);
            // Right 3
            painter->drawLine(40,27,35,10);
            painter->drawLine(35,10,28,1);
            painter->drawLine(28,1,0,0);
     
            // Left 4
            painter->drawLine(-23,40,-24,30);
            painter->drawLine(-24,30,-19,9);
            painter->drawLine(-19,9,0,0);
            // Right 4
            painter->drawLine(20,50,27,30);
            painter->drawLine(27,30,20,12);
            painter->drawLine(20,12,0,0);
        }
     
        painter->setPen(QPen(Qt::black, 1));
        // Левое Жвало
        QPainterPath path1(QPointF(0, -20));
        path1.cubicTo(0, -20, -5, -25, -3, -35);
        path1.cubicTo(-3,-35,-15,-25,-8,-17);
        path1.cubicTo(-8,-17,-5,15,0,-20 );
        painter->setBrush(Qt::black);
        painter->drawPath(path1);
     
        // Правое Жвало
        QPainterPath path2(QPointF(0, -20));
        path2.cubicTo(0, -20, 5, -25, 3, -35);
        path2.cubicTo(3,-35,15,-25,8,-17);
        path2.cubicTo(8,-17,5,15,0,-20 );
        painter->setBrush(Qt::black);
        painter->drawPath(path2);
     
        // Голова
        painter->setBrush(QColor(146, 115, 40, 255));
        painter->drawEllipse(-10,-25,20,15);
        // Тушка
        painter->drawEllipse(-15, -15, 30, 30);
        // Жопка
        painter->drawEllipse(-20, 0, 40,50);
        painter->setPen(QPen(Qt::white,3));
        painter->drawLine(-10,25,10,25);
        painter->drawLine(0,35,0,15);
        // Левое глазище
        painter->setPen(QPen(Qt::black,1));
        painter->setBrush(Qt::red);
        painter->drawEllipse(-8,-23,6,8);
        // Правое глазище
        painter->setBrush(Qt::red);
        painter->drawEllipse(2,-23,6,8);
     
        Q_UNUSED(option);
        Q_UNUSED(widget);
    }
     
    void Spider::slotGameTimer()
    {
        if(isActivePlayer){
            if(GetAsyncKeyState(Qt::Key::Key_A) ||
               GetAsyncKeyState(Qt::Key::Key_D) ||
               GetAsyncKeyState(Qt::Key::Key_W) ||
               GetAsyncKeyState(Qt::Key::Key_S) ||
               GetAsyncKeyState(VK_SPACE)
               )
            {
                /* Поочерёдно выполняем проверку на нажатие клавиш
                 * с помощью функции асинхронного получения состояния клавиш,
                 * которая предоставляется WinAPI
                 * */
                if(GetAsyncKeyState(Qt::Key::Key_A)){
                    angle -= 5;        // Задаём поворот на 5 градусов влево
                    setRotation(angle); // Поворачиваем объект
                    emit signalSend(angle,0);
                }
                if(GetAsyncKeyState(Qt::Key::Key_D)){
                    angle += 5;        // Задаём поворот на 5 градусов вправо
                    setRotation(angle); // Поворачиваем объект
                    emit signalSend(angle,0);
                }
                if(GetAsyncKeyState(Qt::Key::Key_W)){
                    setPos(mapToParent(0, -2));
                    emit signalSend(angle,-2);
     
                }
                if(GetAsyncKeyState(Qt::Key::Key_S)){
                    setPos(mapToParent(0, 2));
                    emit signalSend(angle,2);
     
                }
    //            if(GetAsyncKeyState(VK_SPACE)){
    //                if(isShot){
    //                    emit signalBomb(QPointF(this->x(),this->y()),angle);
    //                    emit signalShot(false);
    //                }
     
    //            }
     
                // Двигаем ножками, Dance, dance, Spidy !!!
                countForSteps++;
                if(countForSteps == 6){
                    steps = 1;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 12){
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 18){
                    steps = 2;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 24) {
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                    countForSteps = 0;
                }
            }
        }
        else{
            setRotation(angle);
            setPos(mapToParent(0,move));
            if (move!=0){
                move=0;
                countForSteps++;
                if(countForSteps == 6){
                    steps = 1;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 12){
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 18){
                    steps = 2;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 24) {
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                    countForSteps = 0;
                }
            }
        }
        if(this->x() - 10 < -400){
            this->setX(-390);       // слева
        }
        if(this->x() + 10 > 400){
            this->setX(390);        // справа
        }
     
        if(this->y() - 10 < -400){
            this->setY(-390);       // сверху
        }
        if(this->y() + 10 > 400){
            this->setY(390);        // снизу
        }
    }
    void Spider::slotAutoSpider(qreal angle, qreal move)
    {
        this->angle=angle;
        this->move=move;
    }

    triangle.h (муха)

    #ifndef TRIANGLE_H
    #define TRIANGLE_H
     
    #include <QObject>
    #include <QGraphicsItem>
    #include <QPainter>
    #include <QGraphicsScene>
     
    /* Подключаем библиотеку, отвечающую за использование WinAPI
     * Данная библиотека необходима для асинхронной проверки состояния клавиш
     * */
    #include <windows.h>
     
    class Triangle : public QObject, public QGraphicsItem
    {
        Q_OBJECT
    public:
        explicit Triangle(bool isActive,QObject *parent = 0);
        ~Triangle();
     
    signals:
        void signalShot(bool shot);
        void signalBomb(QPointF start, qreal angle);
        void signalCheckItem(QGraphicsItem *item);
        void signalSend(qreal angle,qreal move);
     
    public slots:
        void slotGameTimer(); // Слот, который отвечает за обработку перемещения треугольника
        void slotShot(bool shot);
        void slotAutoFly(qreal angle, qreal move);  // Слот для автоматического управления пауком
     
    protected:
        QRectF boundingRect() const;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
     
    private:
        qreal angle;    // Угол поворота графического объекта
        qreal move=0;
        int steps;          // Номер положения ножек мухи
        int countForSteps;  // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки
        bool isShot=false;
        bool isActive= false;
     
    };
     
    #endif // TRIANGLE_H

    triangle.cpp

    #include "triangle.h"
     
    Triangle::Triangle(bool isActive,QObject *parent) :
        QObject(parent), QGraphicsItem()
    {
        angle = 0;      // Задаём угол поворота графического объекта
        steps = 1;      // Задаём стартовое положение ножек мухи
        countForSteps = 0;      // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки
        setRotation(angle);     // Устанавливаем угол поворота графического объекта
        this->isActive=isActive;
    }
     
    Triangle::~Triangle()
    {
     
    }
     
    QRectF Triangle::boundingRect() const
    {
        return QRectF(-40,-50,80,100);   /// Ограничиваем область, в которой лежит треугольник
    }
     
    void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        painter->setPen(QPen(Qt::black, 2));
     
        if(steps == 0){     // Первое положение ножек
            // Left 1
            painter->drawLine(-24,-37,-22,-25);
            painter->drawLine(-22,-25,-17,-15);
            painter->drawLine(-17,-15,-10,-5);
            // Right 1
            painter->drawLine(37,-28,28,-18);
            painter->drawLine(28,-18,24,-8);
            painter->drawLine(24,-8,10,-5);
     
            // Left 2
            painter->drawLine(-35,-20,-25,-11);
            painter->drawLine(-25,-11,-14,-5);
            painter->drawLine(-14,-5,0,5);
            // Right 2
            painter->drawLine(37,-12,32,-4);
            painter->drawLine(32,-4,24,2);
            painter->drawLine(24,2,0,5);
     
            // Left 3
            painter->drawLine(-35,35,-26,24);
            painter->drawLine(-26,24,-16,16);
            painter->drawLine(-16,16,0,0);
            // Right 3
            painter->drawLine(37,26,32,17);
            painter->drawLine(32,17,24,8);
            painter->drawLine(24,8,0,0);
        } else if (steps == 1){     // Второе положение ножек
            // Left 1
            painter->drawLine(-32,-32,-25,-22);
            painter->drawLine(-25,-22,-20,-12);
            painter->drawLine(-20,-12,-10,-5);
            // Right 1
            painter->drawLine(32,-32,25,-22);
            painter->drawLine(25,-22,20,-12);
            painter->drawLine(20,-12,10,-5);
     
            // Left 2
            painter->drawLine(-39,-15,-30,-8);
            painter->drawLine(-30,-8,-18,-2);
            painter->drawLine(-18,-2,0,5);
            // Right 2
            painter->drawLine(39,-15,30,-8);
            painter->drawLine(30,-8,18,-2);
            painter->drawLine(18,-2,0,5);
     
            // Left 3
            painter->drawLine(-39,30,-30,20);
            painter->drawLine(-30,20,-20,12);
            painter->drawLine(-20,12,0,0);
            // Right 3
            painter->drawLine(39,30,30,20);
            painter->drawLine(30,20,20,12);
            painter->drawLine(20,12,0,0);
        } else if (steps == 2){     // Третье положение ножек
            // Left 1
            painter->drawLine(-37,-28,-28,-18);
            painter->drawLine(-28,-18,-24,-8);
            painter->drawLine(-24,-8,-10,-5);
            // Right 1
            painter->drawLine(24,-37,22,-25);
            painter->drawLine(22,-25,17,-15);
            painter->drawLine(17,-15,10,-5);
     
            // Left 2
            painter->drawLine(-37,-12,-32,-4);
            painter->drawLine(-32,-4,-24,2);
            painter->drawLine(-24,2,0,5);
            // Right 2
            painter->drawLine(35,-20,25,-11);
            painter->drawLine(25,-11,14,-5);
            painter->drawLine(14,-5,0,5);
     
            // Left 3
            painter->drawLine(-37,26,-32,17);
            painter->drawLine(-32,17,-24,8);
            painter->drawLine(-24,8,0,0);
            // Right 3
            painter->drawLine(35,35,26,24);
            painter->drawLine(26,24,16,16);
            painter->drawLine(16,16,0,0);
        }
        // Усики
        QPainterPath path(QPointF(-5,-34));
        path.cubicTo(-5,-34, 0,-36,0,-30);
        path.cubicTo(0,-30, 0,-36,5,-34);
        painter->setBrush(Qt::NoBrush);
        painter->drawPath(path);
     
        painter->setPen(QPen(Qt::black, 1));
        // Тушка
        painter->setBrush(Qt::black);
        painter->drawEllipse(-15, -20, 30, 50);
        // Голова
        painter->drawEllipse(-15, -30, 30, 20);
        // Глазища
        painter->setBrush(Qt::green);
        painter->drawEllipse(-15, -27, 12, 15);
        painter->drawEllipse(3, -27, 12, 15);
     
        // Левое крылище
        QPainterPath path2(QPointF(-10, -10));
        path2.cubicTo(-18, -10, -30, 10, -25, 35);
        path2.cubicTo(-25,35,-20,50,-15,40);
        path2.cubicTo(-15,40,0,20,-3,5 );
        path2.cubicTo(-3,5, -8,8,-10,-10);
        painter->setBrush(Qt::white);
        painter->drawPath(path2);
     
        // Правое крылище
        QPainterPath path3(QPointF(10, -10));
        path3.cubicTo(18, -10, 30, 10, 25, 35);
        path3.cubicTo(25,35,20,50,15,40);
        path3.cubicTo(15,40,0,20,3,5 );
        path3.cubicTo(3,5, 8,8,10,-10);
        painter->setBrush(Qt::white);
        painter->drawPath(path3);
     
        Q_UNUSED(option);
        Q_UNUSED(widget);
    }
     
    void Triangle::slotGameTimer()
    {
        /* Проверяем, нажата ли была какая-либо из кнопок управления объектом.
         * Прежде чем считать шажки
         * */
        if(isActive){
            if(GetAsyncKeyState(Qt::Key::Key_J) ||
               GetAsyncKeyState(Qt::Key::Key_L) ||
               GetAsyncKeyState(Qt::Key::Key_I) ||
               GetAsyncKeyState(Qt::Key::Key_K) ||
               GetAsyncKeyState(VK_SPACE)
               )
            {
                /* Поочерёдно выполняем проверку на нажатие клавиш
                 * с помощью функции асинхронного получения состояния клавиш,
                 * которая предоставляется WinAPI
                 * */
                if(GetAsyncKeyState(Qt::Key::Key_J)){
                    angle -= 5;        // Задаём поворот на 5 градусов влево
                    setRotation(angle); // Поворачиваем объект
                    emit signalSend(angle,0);
                }
                if(GetAsyncKeyState(Qt::Key::Key_L)){
                    angle += 5;        // Задаём поворот на 5 градусов вправо
                    setRotation(angle); // Поворачиваем объект
                    emit signalSend(angle,0);
                }
                if(GetAsyncKeyState(Qt::Key::Key_I)){
                    setPos(mapToParent(0, -2));
                    emit signalSend(angle,-2);     /* Продвигаем объект на 5 пискселей вперёд
                                                     * перетранслировав их в координатную систему
                                                     * графической сцены
                                                     * */
                }
                if(GetAsyncKeyState(Qt::Key::Key_K)){
                    setPos(mapToParent(0, 2));
                    emit signalSend(angle,2);      /* Продвигаем объект на 5 пискселей назад
                                                     * перетранслировав их в координатную систему
                                                     * графической сцены
                                                     * */
                }
                if(GetAsyncKeyState(VK_SPACE)){
                    if(isShot){
                        emit signalBomb(QPointF(this->x(),this->y()),angle);
                        emit signalShot(false);
                    }
     
                }
                // Двигаем ножками, Dance, dance, Baby !!!
                countForSteps++;
                if(countForSteps == 4){
                    steps = 2;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 8){
                    steps = 1;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 12){
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 16) {
                    steps = 1;
                    update(QRectF(-40,-50,80,100));
                    countForSteps = 0;
                }
            }
        }
        else{
            setRotation(angle);
            setPos(mapToParent(0,move));
            if (move!=0){
                move=0;
                countForSteps++;
                if(countForSteps == 6){
                    steps = 1;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 12){
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 18){
                    steps = 2;
                    update(QRectF(-40,-50,80,100));
                } else if (countForSteps == 24) {
                    steps = 0;
                    update(QRectF(-40,-50,80,100));
                    countForSteps = 0;
                }
            }
        }
        //создаём небольшую область перед мухой для обнаружения элементов
        QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
                                                                   << mapToScene(0, 0)
                                                                   << mapToScene(-20, -20)
                                                                   << mapToScene(20, -20));
            foreach (QGraphicsItem *item, foundItems) {
                if (item == this)
                    continue;
                emit signalCheckItem(item);
            }
     
        /* Проверка выхода за границы поля
         * Если объект выходит за заданные границы, то возвращаем его назад
         * */
        if(this->x() - 10 < -400){
            this->setX(-390);       // слева
        }
        if(this->x() + 10 > 400){
            this->setX(390);        // справа
        }
     
        if(this->y() - 10 < -400){
            this->setY(-390);       // сверху
        }
        if(this->y() + 10 > 400){
            this->setY(390);        // снизу
        }
    }
     
    void Triangle::slotShot(bool shot)
    {
        this->isShot=shot;
    }
     
    void Triangle::slotAutoFly(qreal angle, qreal move)
    {
        this->angle=angle;
        this->move=move;
    }

    Всё обрабатывается в классе – Widget
    widget.cpp

    #include "widget.h"
    #include "ui_widget.h"
     
    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
        _name = "";
        _sok = new QTcpSocket(this);
        connect(_sok,SIGNAL(readyRead()),this,SLOT(recieveInfo()));
        connect(ui->buttonReady,&QPushButton::clicked,this,&Widget::slotBegin);
        connect(ui->buttonConnect,SIGNAL(clicked(bool)),this,SLOT(on_pbConnect_clicked()));
    }
     
    Widget::~Widget()
    {
        delete ui;
    }
    void Widget::slotBegin()
    {
        ui->radioFly->setEnabled(false);
        ui->radioSpider->setEnabled(false);
     
        this->resize(800,800);          /// Задаем размеры виджета, то есть окна
        this->setFixedSize(800,800);    /// Фиксируем размеры виджета
     
        scene = new QGraphicsScene();   /// Инициализируем графическую сцену
     
        ui->graphicsView->setScene(scene);  /// Устанавливаем графическую сцену в graphicsView
        ui->graphicsView->setRenderHint(QPainter::Antialiasing);    /// Устанавливаем сглаживание
        ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по вертикали
        ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по горизонтали
     
        scene->setSceneRect(-300,-300,600,600); /// Устанавливаем область графической сцены
     
        if(ui->radioFly->isChecked()){
            triangle = new Triangle(true);
            triangle->setPos(-150,0);
     
            spider = new Spider(false);
            spider->setPos(150,0);
            scene->addItem(triangle);
            scene->addItem(spider);
            isFly=true;
        }
        else if(ui->radioSpider->isChecked()){
            spider = new Spider(true);
            spider->setPos(-150,0);
     
            triangle = new Triangle(false);
            triangle->setPos(150,0);
            scene->addItem(triangle);
            scene->addItem(spider);
            isFly=false;
        }
     
        timer = new QTimer();
        timer2 = new QTimer();//общий игровой таймер
        timerCreateSnipers = new QTimer();        //таймер для создания прицелов
     
        connect(triangle,&Triangle::signalCheckItem,this,&Widget::slotDeleteSnipers);     //слот удаления прицелов
        connect(timerCreateSnipers,&QTimer::timeout,this,&Widget::slotCreateSnipers);     //подключаем слот создания прицелов для мухи
        connect(timer, &QTimer::timeout, triangle, &Triangle::slotGameTimer);             //общий игровой слот (движение мухи)
        connect(timer2, &QTimer::timeout, spider, &Spider::slotGameTimer);                   //общий игровой слот (движение паука)
        connect(triangle, &Triangle::signalBomb, this, &Widget::slotBomb);                //слот, передающий сам выстрел(добавление бомбы)
        connect(triangle,&Triangle::signalShot,triangle,&Triangle::slotShot);             //слот, передающий возможность выстрела
        connect(this,&Widget::signalShot,triangle,&Triangle::slotShot);
        connect(triangle,&Triangle::signalSend,this,&Widget::slotSend);
        connect(spider,&Spider::signalSend,this,&Widget::slotSend2);
        connect(this,&Widget::signalChangeSpider,spider,&Spider::slotAutoSpider);
        connect(this,&Widget::signalChangeFly,triangle,&Triangle::slotAutoFly);
     
        timer->start(10);
        timer2->start(10);
        timerCreateSnipers->start(10000);
    }
     
    void Widget::slotBomb(QPointF start,qreal angle)
    {
        scene->addItem(new Bomb(start,angle));
    }
     
    void Widget::slotDeleteSnipers(QGraphicsItem *item)
    {
        foreach (QGraphicsItem *sniper,snipers) {
            if(sniper == item){
                scene->removeItem(sniper);   // Удаляем со сцены
                snipers.removeOne(item);     // Удаляем из списка
                delete sniper;               // Вообще удаляем
                emit signalShot(true);       // Вызываем сигнал выстрела(true)
     
            }
        }
    }
    void Widget::slotCreateSnipers()
    {
        ShotItem *snipers = new ShotItem(); // Создаём прицел
        scene->addItem(snipers);      // Помещаем его в сцену со случайной позицией
        snipers->setPos((qrand() % (401)) * ((qrand()%2 == 1)?1:-1),
                      (qrand() % (401)) * ((qrand()%2 == 1)?1:-1));
        snipers->setZValue(-1);       /* Помещаем прицел ниже Мухи, то есть Муха
                                     * на сцене будет выше прицелов
                                     * */
        this->snipers.append(snipers);       // Добавляем прицел в Список
    }
     
    void Widget::onSokDisplayError(QAbstractSocket::SocketError socketError)
    {
        switch (socketError) {
        case QAbstractSocket::RemoteHostClosedError:
            break;
        case QAbstractSocket::HostNotFoundError:
            QMessageBox::information(this, "Error", "The host was not found");
            break;
        case QAbstractSocket::ConnectionRefusedError:
            QMessageBox::information(this, "Error", "The connection was refused by the peer.");
            break;
        default:
            QMessageBox::information(this, "Error", "The following error occurred: "+_sok->errorString());
        }
    }
     
    void Widget::on_pbConnect_clicked()
    {
        _sok->connectToHost(ui->lineIp->text(),ui->linePort->text().toUInt());
        ui->buttonConnect->setEnabled(false);
        ui->buttonDisconnect->setEnabled(true);
        connect(_sok,SIGNAL(connected()),this,SLOT(slotConnected()));
        connect(_sok,SIGNAL(readyRead()),this,SLOT(recieveInfo()));
    }
     
    void Widget::on_pbDisconnect_clicked()
    {
        _sok->disconnectFromHost();
        ui->buttonConnect->setEnabled(true);
        ui->buttonDisconnect->setEnabled(false);
    }
     
    void Widget::recieveInfo()
    {
        QDataStream in(_sok);
            in.setVersion(QDataStream::Qt_4_2);
            for (;;) {
                if (!_blockSize) {
                    if (_sok->bytesAvailable() < sizeof(quint16)) {
                        break;
                    }
                    in >> _blockSize;
                }
     
                if (_sok->bytesAvailable() < _blockSize) {
                    break;
                }
                qreal   angle;
                qreal   move;
                //QString str;
     
                in >> angle >> move;
                //int a = move;
                //QMessageBox::information(this,"тайтл",QString::number(move));
                if(!isFly){
                    emit signalChangeFly(angle,move);
                }
                else{
                    emit signalChangeSpider(angle,move);
                }
                _blockSize = 0;
            }
    }
     
    void Widget::slotConnected()
    {
     
    }
     
    void Widget::slotSend(qreal angle, qreal move)
    {
        QByteArray arrBlock;
        QDataStream serverSendStream(&arrBlock, QIODevice::WriteOnly);
     
        serverSendStream << quint16(0)<<angle<<move;
     
        serverSendStream.device()->seek(0);
        serverSendStream << quint16(arrBlock.size() - sizeof(quint16));
     
        _sok->write(arrBlock);
    }
     
    void Widget::slotSend2(qreal angle, qreal move)
    {
        QByteArray arrBlock;
        QDataStream serverSendStream(&arrBlock, QIODevice::WriteOnly);
     
        serverSendStream << quint16(0)<<angle<<move;
     
        serverSendStream.device()->seek(0);
        serverSendStream << quint16(arrBlock.size() - sizeof(quint16));
     
        _sok->write(arrBlock);
    }

     

      Как поведёт себя паук если закоментировать if(GetAsyncKeyState(VK_SPACE)) ?
      Будут ли лаги?
        Абсолютно ничего не изменилось
          АП
          • Oct. 14, 2016, 9 p.m.
          • The answer was marked as a solution.
          Евгений, спасибо большое за ваши советы)Я всё же разобрался, и у меня получилось избавиться от этих тормозов. Причина оказалась до жути банальная: для передачи данных от первого клиента ко второму и от второго к первому я использовал один и тот же сокет) А поскольку я использовал не QUdpSocket, а QTcpSocket, то абсолютно все данные должны были передаться. Из-за этого возникала задержка (пока передавались данные от первого клиента ко второму, данные со второго клиента стояли в условной “очереди”), из-за этого они всё же передавались но с накапливающейся задержкой)
          При этом, хочу заметить, что никаких потоков я не использую, на удивление всё работает шустро и без задержек (к примеру у моих одногруппников, подобная задача на Visual Studio требует создание отдельного потока под каждое подключение, иначе сервер просто виснет).
          Хочу вам ещё раз выразить свою благодарность за все ваши старания, обучающие уроки и помощь)
            Ну что ж. Хорошо, что всё разрешилось. Успехов Вам.

              Comments

              Only authorized users can post comments.
              Please, Log in or Sign up
              e
              • ehot
              • March 31, 2024, 2:29 p.m.

              C++ - Тест 003. Условия и циклы

              • Result:78points,
              • Rating points2
              B

              C++ - Test 002. Constants

              • Result:16points,
              • Rating points-10
              B

              C++ - Test 001. The first program and data types

              • Result:46points,
              • Rating points-6
              Last comments
              k
              kmssrFeb. 8, 2024, 6:43 p.m.
              Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
              Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
              EVA
              EVADec. 25, 2023, 10:30 a.m.
              Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
              J
              JonnyJoDec. 25, 2023, 8:38 a.m.
              Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
              G
              GvozdikDec. 18, 2023, 9:01 p.m.
              Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
              Now discuss on the forum
              a
              a_vlasovApril 14, 2024, 6:41 a.m.
              Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
              Павел Дорофеев
              Павел ДорофеевApril 14, 2024, 2:35 a.m.
              QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
              f
              fastrexApril 4, 2024, 4:47 a.m.
              Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…
              AC
              Alexandru CodreanuJan. 19, 2024, 11:57 a.m.
              QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…

              Follow us in social networks