Клиент-серверная игра на 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) ситуация не меняется.
Как быть с моей проблемой?) Что можно сделать? Заранее спасибо
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.Вам це подобається? Поділіться в соціальних мережах!
- molni99
- 26 жовтня 2024 р. 01:37
C++ - Тест 004. Указатели, Массивы и Циклы
- Результат:80бали,
- Рейтинг балів4
- molni99
- 26 жовтня 2024 р. 01:29
C++ - Тест 004. Указатели, Массивы и Циклы
- Результат:20бали,
- Рейтинг балів-10
Добрый день, Александр.
В первую очередь откажитесь от TCP, он не используется для активных игр в принципе, поскольку будет пытаться передать информацию до тех пор, пока передача не будет успешной. Для этих целей используют протокол UDP.
А с лагами обычно борются с помощью систем предсказаний. И это достаточно обширная область. К своему сожалению я пока не работаю в данной области, чтобы дать достаточно компетентный совет. Но постараюсь высказать свои мысли, может это поможет.
Например, в вашем случае, можно при нажатии кнопки пересылать направление и скорость движения. То есть с математической точки зрения вы будете пересылать вектор движения.
А при отпускании клавиши, или изменении комбинации клавиш можно передавать координаты положения игрока, где произошло изменение вектора, и новый вектор движения. Таким образом, кстати, можно будет снизить количество передаваемых пакетов.
Конечно, всё равно остаётся при данном совете вопрос, почему проблема происходит при одновременном нажатии клавиш для паука. Но тут хочу заметить, что Вы отметили проблему только в случае с пауком. Поэтому хотелось бы увидеть код как для мухи, так и для паука. Подозреваю, что возможна разница в коде, и где-то есть ошибка в обработке клавиш.
Вот и для меня очень странным показался тот факт, что с мухой нет ну абсолютно никаких проблем (значит задержки как таковой и нету), да и при одновременном нажатии на две клавиши (со стороны мухи) и одной клавиши (со стороны паука) сервер ни чуточку не подвисает и всё синхронно отображается. Спасибо вам за советы) Вот код, возможно Вы найдёте ещё какие-либо ошибки:
spider.h
spider.cpp
triangle.h (муха)
triangle.cpp
Всё обрабатывается в классе – Widget
widget.cpp
Будут ли лаги?
При этом, хочу заметить, что никаких потоков я не использую, на удивление всё работает шустро и без задержек (к примеру у моих одногруппников, подобная задача на Visual Studio требует создание отдельного потока под каждое подключение, иначе сервер просто виснет).
Хочу вам ещё раз выразить свою благодарность за все ваши старания, обучающие уроки и помощь)