I tried to make a simple Pong with 1 vs 1 over the local area network and it works just fine but it get slightly desynced after a while. It looks like 2 balls and 4 "flippers" or whatever I should call em.
Any ideas on how to sync the games better?
I've synchronized the two players and the ball.
I can't provide a screenshot as the game alter between frames, making it impossible for a camera or a monitor to actually capture it.
I'm fairly new to programming so any advice is appreciated.
Code:
ballP.x = inputSend3.x + ballV.x; //ball position + velocity
ballP.y = inputSend3.y + ballV.y; //ball position + velocity
playerP.y = inputSend1; //player position
opponentP.y = inputSend2; //player two position
//Changes values of player and opponent using arrowkeys and W/S.
globalMutex.lock(); //sends values to sync
inputSend1 = playerP.y;
inputSend2 = opponentP.y;
inputSend3.x = ballP.x;
inputSend3.y = ballP.y;
globalMutex.unlock();
sync();
void sync(void)
{ //sync values between server and client
sf::Packet packetSend;
globalMutex.lock();
packetSend << inputSend3.x << inputSend3.y << inputSend2 << inputSend1;
globalMutex.unlock();
socket.send(packetSend);
sf::Packet packetReceive;
socket.receive(packetReceive);
packetReceive >> inputSend3.x >> inputSend3.y >> inputSend2 >> inputSend1;
}
I found some example code online so I personally don't know what the difference between mutex is and not.
The problem with the screenshot is that it alter between frames so a screenshot will not be able to show the multiple position (same as when you circle the mouse the eye can see 8-10 pointers at the same time because of refresh rate).
I got it working though and the error was simply because both the client and server were sending the same data causing multiple inputs. I fixed it by simply telling the server to send ball position and right flipper while the client only sends information about the left one.
Heres my code for future reference, feel free to give me advice on how to make it better other than implement better ball logic and actually using headers, classes and functions :P
Oh, and another thing, I can't make a release static on the way the project is set up so I can't share my game with people that don't have visual studio installed on their pc. It do however work if I exclude network and audio for some reason. Any ideas?
#ifdef SFML_STATIC
#pragma comment(lib, "glew.lib")
#pragma comment(lib, "freetype.lib")
#pragma comment(lib, "jpeg.lib")
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "gdi32.lib")
#endif // SFML_STATIC
#include <SFML/Graphics.hpp>
#include <cmath>
#include <iostream>
#include <ostream>
#include <fstream>
#include <sstream>
#include <SFML/Audio.hpp>
#include <SFML/Network.hpp>
const int FPS = 60, WINDOWWIDTH = 320, WINDOWHEIGHT = 180;
const unsigned short PORT = 5000;
const std::string IPADDRESS("123.456.789.1");
const float PI = 3.14159265359, shapeRadius = 5;
int score = 0;
int inputSend1 = 0, inputSend2 = 0;
bool serverPick = false;
sf::RenderWindow window(sf::VideoMode(WINDOWWIDTH, WINDOWHEIGHT), "Pong");
sf::Vector2f ballV, ballP, playerV, playerP, opponentP, inputSend3;
sf::CircleShape shape(shapeRadius);
sf::Texture brickTxt;
sf::Sprite brickSpr, opponentSpr, middleSpr;
sf::Event event;
sf::Font font1;
sf::Text scoreText;
sf::TcpSocket socket;
sf::Mutex globalMutex;
sf::SoundBuffer buffer;
sf::Sound sound;
void windowCloser(), sync(void), server(void), client(void);
//main
int main(int argc, char* argv[])
{
if (!brickTxt.loadFromFile("resources/brick.png"));
if (!font1.loadFromFile("resources/arial.ttf"));
if (!buffer.loadFromFile("resources/jump.wav"));
sound.setBuffer(buffer);
sf::Thread* thread = 0;
char who = 'a';
while (who != 'S' && who != 'C')
{
std::cout << "Server or Client? S/C ";
std::cin >> who;
toupper(who);
if (who == 'S')
server();
if (who == 'C')
client();
}
brickSpr.setTexture(brickTxt);
opponentSpr.setTexture(brickTxt);
middleSpr.setTexture(brickTxt);
shape.setFillColor(sf::Color::White);
ballP.x = WINDOWWIDTH/2;
ballP.y = WINDOWHEIGHT/2;
playerP.x = 0;
playerP.y = WINDOWHEIGHT / 2;
opponentP.x = WINDOWWIDTH - opponentSpr.getGlobalBounds().width;
opponentP.y = WINDOWHEIGHT / 2;
ballV.x = 3;
ballV.y = 3;
middleSpr.setScale(0.5, 6.7);
middleSpr.setPosition(WINDOWWIDTH / 2, 0);
thread = new sf::Thread(&sync);
thread->launch();
if (thread)
{
thread->wait();
delete thread;
}
//main loop
while (window.isOpen())
{
event;
windowCloser();
ballP.x = inputSend3.x + ballV.x;
ballP.y = inputSend3.y + ballV.y;
shape.setPosition(ballP.x, ballP.y);
playerP.y = inputSend1;
opponentP.y = inputSend2;
playerP.y = playerP.y + playerV.y;
opponentP.x = WINDOWWIDTH - opponentSpr.getGlobalBounds().width;
brickSpr.setPosition(playerP.x, playerP.y);
opponentSpr.setPosition(opponentP.x, opponentP.y);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
playerP.y -= 5;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
{
playerP.y += 5;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
opponentP.y -= 5;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
{
opponentP.y += 5;
}
if (shape.getPosition().x < -10)
{
score = 0;
ballV.x = -ballV.x;
ballP.x = (WINDOWWIDTH / 2);
ballP.y = (WINDOWHEIGHT / 2);
}
if (shape.getPosition().x > WINDOWWIDTH + 10)
{
score = 0;
ballV.x = -ballV.x;
ballP.x = (WINDOWWIDTH / 2);
ballP.y = (WINDOWHEIGHT / 2);
}
if (shape.getPosition().y > (WINDOWHEIGHT-WINDOWHEIGHT))
{
ballV.y = -ballV.y;
}
if (shape.getPosition().y < (WINDOWHEIGHT-shapeRadius*2))
{
ballV.y = -ballV.y;
}
//collision
if (brickSpr.getGlobalBounds().intersects(shape.getGlobalBounds()))
{
sound.play();
ballP.x = 6;
ballV.x = -ballV.x;
score++;
}
if (opponentSpr.getGlobalBounds().intersects(shape.getGlobalBounds()))
{
sound.play();
ballP.x = WINDOWWIDTH - 12;
ballV.x = -ballV.x;
score++;
}
std::ostringstream scoreString;
scoreString << score;
scoreText.setString(scoreString.str());
scoreText.setFont(font1);
scoreText.setColor(sf::Color::White);
scoreText.setPosition((WINDOWWIDTH / 2)+10, 0);
if (!serverPick)
{
globalMutex.lock();
inputSend1 = playerP.y;
globalMutex.unlock();
}
if (serverPick)
{
globalMutex.lock();
inputSend2 = opponentP.y;
inputSend3.x = ballP.x;
inputSend3.y = ballP.y;
globalMutex.unlock();
}
sync();
window.clear();
window.draw(shape);
window.draw(brickSpr);
window.draw(opponentSpr);
window.draw(middleSpr);
window.setFramerateLimit(FPS);
window.draw(scoreText);
window.display();
}
return 0;
}
void windowCloser()
{
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
}
void sync(void)
{
if (serverPick)
{
sf::Packet packetSend;
globalMutex.lock();
packetSend << inputSend3.x << inputSend3.y << inputSend2;
globalMutex.unlock();
socket.send(packetSend);
sf::Packet packetReceive;
socket.receive(packetReceive);
packetReceive >> inputSend1;
}
if (!serverPick)
{
sf::Packet packetSend;
globalMutex.lock();
packetSend << inputSend1;
globalMutex.unlock();
socket.send(packetSend);
sf::Packet packetReceive;
socket.receive(packetReceive);
packetReceive >> inputSend3.x >> inputSend3.y >> inputSend2;
}
}
void server(void)
{
sf::TcpListener listener;
listener.listen(PORT);
listener.accept(socket);
std::cout << "New client connected: " << socket.getRemoteAddress() << std::endl;
serverPick = true;
}
void client(void)
{
if (socket.connect(IPADDRESS, PORT) == sf::Socket::Done)
{
std::cout << "\nConnected\n" << std::endl;
serverPick = false;
}
else
{
std::cout << "Can't connect.";
}
}