Witam. Mam pewien problem, który od jakiegoś czasu trzyma mnie w tym samym miejscu.
Tworzę sobie gierkę z wykorzystaniem protokołu
TCP*, platformówkę inspirowaną grą "King Artur's Gold". Oczywiście gra nie będzie tak bardzo dynamiczna i rozbudowana, jak KAG, głównie chodzi o temat rozgrywki: 'dwie drużyny, które walczą między sobą'. W założeniach miałyby to być niewielkie bitwy 3vs3 + kilku obserwatorów, którzy jedynie by patrzeli, jak inni grają.
Nadszedł czas na dodanie ruchu. Postanowiłem zastosować enum z listą czynności i przypisywać do obiektu wartość przy kliknięciu w odpowiedni klawisz, który następnie wysyłam serwerowi. Czyli np. kliknięcie przycisku 'A' ustawi czynność na "MOVE" i kierunek na "LEFT"(enum z kierunkiem), a puszczenie klawisza ustawiałoby czynność na "STAND". Następnie serwer ustawia u siebie tą czynność i w jednej metodzie aktualizuje dane(pozycje) wszystkich klientów w sposób, jaki każe mu czynność(czyli np. ruch w lewo = (client[i].player).position=-(client[i].player).vX)), client również aktualizuje pozycję, ale co 1/3 sekundy wysyła zapytanie i sprawdza, czy pozycja jest taka sama, jak w serwerze. Przy okazji również odbiera pozycje innych klientów i je ustawia.
I tutaj rodzi się problem, ponieważ client posiada pętlę stałokrokową, która działa, jakby było 60 fps, a serwer posiada jedynie selector czekający 0.2 sekundy. W takim wypadku w serwerze pozycje będą się szybciej aktualizować, a nie mogę ograniczyć prędkości pętli, tak jak w kliencie, ponieważ selector zatrzymuje pętlę o 0.2 sekundy i szybkość aktualizowania również będzie się nie zgadzać z szybkością w klientach. Rozwiązaniem byłoby pewnie usunięcie selectora, ale w takim razie zmuszony byłbym na ustawienie socketów na tryb nieblokujący. Myślałem też o wrzuceniu metody odbierającej dane w osobny wątek, ale coś wtedy nie działa.
Co waszym zdaniem powinienem zrobić?
*Tak, wiem, że dynamiczne gierki powinny używać UDP. Dlatego postanowiłem swój projekt ograniczyć w dynamice, aby TCP wytrzymało.
KOD:server.hpp - klasa serwera, trzymająca vector clientów i parę rzeczy.
namespace rha {
class cServer {
private:
cNetworkManager manager;
std::vector < cClient > vClients;
public:
sf::SocketSelector selector;
bool listening( sf::TcpListener * listener, unsigned int port );
bool findNewConnection( sf::TcpListener * listener, tgui::Gui & gui );
void serveClients( tgui::Gui & gui );
};
}
server.cpp - Odbieranie danych w serwerze.
void rha::cServer::serveClients( tgui::Gui & gui ) {
tgui::ChatBox::Ptr cBoxCopy = gui.get( "ChatBox1" );
tgui::ListBox::Ptr lBoxCopy = gui.get( "ListBox1" );
if( !vClients.empty() ) {
for( short i = 0; i < vClients.size(); ++i ) {
if( selector.isReady( * vClients[ i ].socket ) ) {
if( manager.receivePacket( vClients[ i ].socket ) ) {
switch( manager.getLastTypePacket() ) {
case rha::typePacketsInClient::QUESTION_CLIENT_DATA: {
sf::Int32 x, y;
if( manager.getLastPacket() >> x >> y ) {
if(( vClients[ i ].player ).action == cPlayer::MOVE ) {
if(( vClients[ i ].player ).direction == cPlayer::RIGHT ) x =( vClients[ i ].player ).x +( vClients[ i ].player ).vX;
else if(( vClients[ i ].player ).direction == cPlayer::LEFT ) x =( vClients[ i ].player ).x -( vClients[ i ].player ).vX;
std::cout << x << " SEND " << y << std::endl;
( vClients[ i ].player ).x = x;
( vClients[ i ].player ).y = y;
}
sf::Packet packet;
packet << rha::typePacketsInServer::INFO_CLIENTS_DATA <<( vClients[ i ].player ).x <<( vClients[ i ].player ).y;
manager.sendPacket( vClients[ i ].socket, packet ); break;
}
}
case rha::typePacketsInClient::QUESTION_PLAYER_ACTION: {
int converterAction, converterDirection;
if( manager.getLastPacket() >> converterAction >> converterDirection ) {
( vClients[ i ].player ).action = static_cast < cPlayer::eAction >( converterAction );
( vClients[ i ].player ).direction = static_cast < cPlayer::eDirection >( converterDirection );
} break;
}
case rha::typePacketsInClient::STOP_PLAYER_ACTION: {( vClients[ i ].player ).action = cPlayer::STAND; break; }
}
}
}
}
}
}
client.hpp //Budowa klasy klienta, która trzyma też obiekt managera(do wysyłania i odbierania pakietów) i
namespace rha {
class cClient {
public:
enum eStatus { NOTCONNECT, WATCHING, PLAYING };
std::vector < cOtherPlayer > vOtherPlayers;
cPlayer player;
private:
cServerInfo server;
std::string nick;
eStatus status;
public:
cNetworkManager manager;
sf::TcpSocket socket;
cClient();
void connect( sf::IpAddress ip, unsigned short port, std::string nick );
bool login( std::string nick );
bool join();
void managePlayer( sf::Event event ); int test = 0;
void updateAll( sf::RenderWindow & window );
void drawAll( sf::RenderWindow & window );
void disconnect();
inline bool getStatus() { return status; }
};
}
client.cpp - Ustawianie czynności i jej wysyłanie.
void rha::cClient::managePlayer( sf::Event event ) {
switch( event.type ) {
case sf::Event::KeyPressed: {
if(( event.key ).code == sf::Keyboard::A ||( event.key ).code == sf::Keyboard::D ) {
player.setAction( cPlayer::eAction::MOVE );
if(( event.key ).code == sf::Keyboard::A )
player.setDirection( cPlayer::eDirection::LEFT );
else player.setDirection( cPlayer::eDirection::RIGHT );
} else if(( event.key ).code == sf::Keyboard::W )
player.setAction( cPlayer::eAction::JUMP );
if( status == PLAYING ) {
sf::Packet packet;
packet << rha::typePacketsInClient::QUESTION_PLAYER_ACTION << int( player.getAction() ) << int( player.getDirection() );
if( !manager.sendPacket( & socket, packet ) ) disconnect();
}
break;
}
case sf::Event::KeyReleased: {
if(( event.key ).code == sf::Keyboard::A ||( event.key ).code == sf::Keyboard::D )
player.setAction( cPlayer::eAction::STAND );
if( status == PLAYING ) {
if( !manager.sendRawPacket( & socket, rha::typePacketsInClient::STOP_PLAYER_ACTION ) ) disconnect();
}
break;
}
case sf::Event::MouseWheelMoved: {
float x = player.view.getSize().x +( -( event.mouseWheel ).delta * 100 );
float y = player.view.getSize().y +( -( event.mouseWheel ).delta * 100 );
if( x < 800 && x > 100 && y < 600 && y > 75 )( player.view ).setSize( x, y ); break;
}
default: break;
}
}
client.cpp - Aktualizacja gracza i odbieranie pakietów.
void rha::cClient::updateAll( sf::RenderWindow & window ) {
if( manager.receivePacket( & socket ) ) {
switch( manager.getLastTypePacket() ) {
case INFO_CLIENTS_DATA: {
sf::Int32 x, y;
if( manager.getLastPacket() >> x >> y ) {
player.registeredX = x;
player.registeredY = y;
player.x = x; player.y = y;
player.gX = x, player.gY = y;
break;
}
}
}
}
( player.textureAnimation ).setAnimation( 2 );
( player.textureAnimation ).serveAnimation();
( player.view ).setCenter( player.gX, player.gY );
test += 1;
if( test >= 20 ) {
player.performAction();
sf::Packet packet;
packet << rha::QUESTION_CLIENT_DATA << player.x << player.y;
if( manager.sendPacket( & socket, packet ) );
std::cout << "send: " << player.x << " " << player.y << std::endl;
test = 0;
}
if( !vOtherPlayers.empty() ) {
for( int i = 0; i < vOtherPlayers.size(); ++i ) {
vOtherPlayers[ i ].performAction();
( vOtherPlayers[ i ].textureAnimation ).serveAnimation();
}
}
}
[/i]