I am trying to create the possibility to play the game and for the same solution/app to be able to connect to a player to watch them play.
I will be using TCP to establish connection and UDP to send updates on the following -
1. Player Position
2. Enemy Position (based on synchronous game time since pattern is predictable)
3. Bullet Position
At the moment I have the following code, if server you have full control and can play the game, if viewer/client so far you can only see, still need to implement netcode.
int main()
{
sf::Texture mPlayerTexture;
sf::Sprite mPlayer;
sf::Texture mBackgroundTexture;
sf::Sprite mBackground;
Bullet bullet(0, bulletSpeed);
sf::RenderWindow window(sf::VideoMode(game->getWindowWidth(), game->getWindowHeight()), "SFML Application");
//static const sf::Time TimePerFrame;
window.setVerticalSyncEnabled(true);
//window.setFramerateLimit(60); // Added this
if (!mPlayerTexture.loadFromFile("Media/Textures/PixelSpaceships/blue_01.png"))
{
// Handle loading error
}
if (!mBackgroundTexture.loadFromFile("Media/Textures/Space.jpg")) {
// Handle loading error
}
// Set Texture and Position for Background
mBackground.setTexture(mBackgroundTexture);
mBackground.setOrigin(game->getWindowWidth() / 2.f, game->getWindowHeight() / 2.f);
mBackground.setPosition(game->getWindowWidth() / 2.f, game->getWindowHeight() / 2.f);
// Set Texture and Position for Player
mPlayer.setTexture(mPlayerTexture);
mPlayer.setOrigin(24.f, 24.f);
mPlayer.setPosition(game->getWindowWidth() / 2.f, game->getWindowHeight() / 2.f);
//enemy.enemyInstantiator(2, 100.f, 50.f);
//enemyInstantiator(10, 100.f, 150.f);
// Statistics Initialisation
sf::Font mFont;
sf::Text mStatisticsText;
mFont.loadFromFile("Media/Sansation.ttf");
mStatisticsText.setFont(mFont);
mStatisticsText.setPosition(5.f, 5.f);
mStatisticsText.setCharacterSize(10);
// Enemy Creation // Separate method in main
std::vector<Enemy> *enemyList = new std::vector<Enemy>();
float xPosition = 100.f;
float yPosition = 50.f;
float yOffset = 50.f;
float xOffset = 60.f;
// New Original Instantiator
for (int i = 0; i < NUMBER_OF_ALIENS; i++) {
Enemy alien(i, enemySpeed);
alien.getSprite().setOrigin(24.f, 24.f);
alien.setLocation(xPosition + (i * xOffset), yPosition);
alien.getSprite().setRotation(180.f);
enemyList->push_back(alien);
//std::cout << "I have " << enemyList->size() << " spaceships" << std::endl;
}
//std::cout << "I have " << enemyList->size() << " spaceships";
// Enemy creation with rows
/*
int tracker = 0;
for (int i = 0; i < NUMBER_OF_ALIENS; i++) {
Enemy alien(i, enemySpeed);
alien.getSprite().setOrigin(24.f, 24.f);
alien.getSprite().setPosition(xPosition + (i * xOffset), yPosition);
alien.getSprite().setRotation(180.f);
if (i >= NUMBER_OF_ALIENS / 2) {
// set position for remainder, problem is that every loop is overiding position and drawing last batch of 5
for (int j = 0; j < NUMBER_OF_ALIENS / 2; j++) {
alien.getSprite().setOrigin(24.f, 24.f);
alien.getSprite().setPosition(xPosition + (j * xOffset), yPosition + yOffset);
alien.getSprite().setRotation(180.f);
//std::cout << "Enemy inside 2nd for loop position " << tempEnemy.getPosition().x << " " << tempEnemy.getPosition().y << std::endl;
// we were not pushing each and every tempEnemy but only the last one when exiting the for loop, therefore we were only printing the last one
enemyList->push_back(alien);
tracker++;
if (tracker >= NUMBER_OF_ALIENS / 2) {
break;
}
}
}
//std::cout << "Enemy inside 1st for loop position " << tempEnemy.getPosition().x << " " << tempEnemy.getPosition().y << std::endl;
enemyList->push_back(alien);
}
*/
/*
//create a an array of enemys
Enemy alienArray = new Enemy[];
for (int i = 0; i < NUMBER_OF_ALIENS; i++)
{
Enemy alien(i, enemySpeed);
alien.setLocation(i * 50.f + 50.f, alien.getSprite().getGlobalBounds().height / 2);
alienArray;
}
*/
//game->run();
// Measure elapsed time
sf::Clock clock;
sf::Time timeSinceLastUpdate = sf::Time::Zero;
// Measure time to reach end of screen
sf::Clock gameTime;
sf::Time timeToHitEdge;
char userChoice = NULL;
// something here or within the window is open needs to happen to select whether to view or play
// menu needs to happen over here or within
while (window.isOpen())
{
if (userChoice == NULL){
do {
std::cout << "User Choice - " << userChoice << std::endl;
std::cout << "Server or client? (s/c)" << std::endl;
std::cin >> userChoice;
} while (userChoice != 's' && userChoice != 'c');
//system("pause");
}
if (userChoice == 's' || userChoice == 'c') { // start of main if // else start the whole program
// Puts the time counter back to zero and also returns the time elapsed since the clock was started
sf::Time deltaTime = clock.restart();
timeSinceLastUpdate += deltaTime;
while (timeSinceLastUpdate > TimePerFrame) // fixed time steps // userchoice != NULL otherwise it will start immeadiately
{
timeSinceLastUpdate -= TimePerFrame; // solves problem with variable delta time, each frame is unique
processEvents(window);
//update(TimePerFrame);
// Start of if server
if (userChoice == 's'){
// Player 1 movement
sf::Vector2f movement(0.f, 0.f);
if (mIsMovingUp)
movement.y -= playerSpeed;
if (mIsMovingDown)
movement.y += playerSpeed;
if (mIsMovingLeft)
movement.x -= playerSpeed;
if (mIsMovingRight)
movement.x += playerSpeed;
// shooting logic
if (mIsShooting) {
if (!bullet.isAlive()) // && !gameOver
{
bullet.spawn(true);
bullet.setLocation(mPlayer.getPosition().x - 13.f, mPlayer.getPosition().y - 24.f); // mPlayer.getPosition().x + 24 (offset) if origin is not set to center
}
}
// ** These two parts need to be put in an if statement, if server, direct movement, if client, move needs to happen with data received
// from server (Server - main player streamer; Client - viewer)
// Possibly this will be the data that needs to be sent to server
mPlayer.move(movement * TimePerFrame.asSeconds()); // Time.deltaTime frame independent movement
// for each player eventually
screenBound(mPlayer);
// screenbound enemies or else check that they do not go off bounds and change direction
// **
} // end of if server
else {
// Handle control through incoming network data
// vector x = datacoming in
// Hypothetically mPlayer.move(data coming in);
// bullet spawn
// setlocation from data packet
}
// Enemy Behaviour // Ideally move to separate method
sf::Vector2f enemyMovement(1.f, 0.f);
float yOffset = 30.f;
float xOffset = 60.f;
// Be careful with iterators as when deleting it will automatically jump to the next item in vector
for (std::vector<Enemy>::iterator i = enemyList->begin(); i != enemyList->end(); ++i) // CAUTION & solved everything again - lesson in C++, if we don't get address, we always create a copy and modify it, which is not what we want
{
//std::cout << "Direction before working move " << direction << std::endl;
i->getSprite().move(enemyMovement * direction); // Issue detected direction only changing sign if greater or less than screen bounds once
if (i->getSprite().getPosition().x + i->getSprite().getLocalBounds().width / 2 > game->getWindowWidth() ||
i->getSprite().getPosition().x - i->getSprite().getLocalBounds().width / 2 < game->getWindowWidth() - game->getWindowWidth()) { // this will be 0
direction = -(direction);
// Time stamp over here for network calculations
//std::cout << "Direction inside if statement " << direction << std::endl;
// another for loop move all down by yOffset
for (std::vector<Enemy>::iterator i = enemyList->begin(); i != enemyList->end(); ++i) {
i->getSprite().setPosition(i->getSprite().getPosition().x, i->getSprite().getPosition().y + yOffset);
//enemy.move(0.f, enemy.getPosition().y + yOffset);
}
//enemy.setPosition(enemy.getPosition().x, enemy.getPosition().y + yOffset); // y axis is inverted in SFML
//return 0; // move to separate method
}
// This takes care of everything on both sides
if ((i->getSprite().getPosition().y > game->getWindowHeight() - mPlayer.getLocalBounds().height)) {
//removeEnemy(enemy);
// ALIVE is false, then draw based on whether alien is alive or not
std::cout << "Everyone should be dead game over!" << std::endl;
return 0; // change state to paused
}
}
}
updateStatistics(deltaTime, mStatisticsText);
// ** Start of if server
if (userChoice == 's'){
for (std::vector<Enemy>::iterator i = enemyList->begin(); i != enemyList->end(); ++i) {
// Test player and alien collision
if (CollisionManager::collidesWith(mPlayer, *i) && i->isAlive()) { // *i it points to the enemy, and will give me an enemy
std::cout << "You're dead!" << std::endl;
mPlayerIsAlive = false;
return 0; //-> I'm dead gameOver = true;
}
// Test collision between bullets and aliens
if (CollisionManager::collidesWith(bullet, *i) && i->isAlive())
{
i->kill();
//enemyList->erase(i); // issue here, we are trying to do this in order to get rid of rendering problems .. shit just move it out of way
//i->setLocation(700.f, 10.f);
bullet.kill();
bullet.setLocation(700.f, 0.f); // temporary solution for dealing with not actually killing but stop drawing bullet
// without above line, the bullet will not be drawn but still exist on screen
}
/*
v.erase(std::remove_if(v.begin(), v.end(),
[](int i) { return i < 10; }), v.end());
*/
}
}
// ** end of if server
int deadEnemies = 0;
for (std::vector<Enemy>::iterator i = enemyList->begin(); i != enemyList->end(); ++i) {
if (!(i->isAlive())) {
deadEnemies++;
}
if (deadEnemies >= NUMBER_OF_ALIENS) {
std::cout << "You win!" << std::endl;
return 0; // Set state win
}
}
std::cout << "Total Dead Enemies - " << deadEnemies << std::endl;
//for (Enemy& enemy : enemyList) {
// if (enemy.isAlive()) {
// enemy.draw(window);
// }
//}
//enemy.render(alienArray, mWindow);
/*
for (int i = 0; i < mEnemies.size(); i++)
{
sf::Sprite temp = mEnemies.at(i);
mWindow.draw(temp);
}
*/
// Render All
// Remember draw order is important
window.clear();
window.draw(mBackground);
window.draw(mPlayer);
window.draw(mStatisticsText);
for (std::vector<Enemy>::iterator i = enemyList->begin(); i != enemyList->end(); ++i) {
if (i->isAlive()) {
i->draw(window);
std::cout << "Enemy " << i->getID() << " position - " << i->getLocation().x << " " << i->getLocation().y << std::endl;
}
}
if (bullet.isAlive()) // && !gameOver // for game state and win / lose screen representation
{
//draw bullet
bullet.draw(window);
//move bullet
bullet.getSprite().move(0.f, -20);
}
//test collision with bullet and boundary
if (bullet.getSprite().getPosition().y < 0)
bullet.kill();
window.display();
} // end of main second if
}
return 0;
}
The issue I have is that if user takes x seconds to select an option, the game in the background would have already started and enemy movement would have been running in the background.
This creates an issue were once the player/viewer goes to the main game, the enemies are shown in the current position in time instead of the start position, i.e. when the game starts enemies should be at the top.
I know that the issue is related to timeSinceLastUpdate > TimePerFrame, as probably this will be far greater when actually starting the game.
In the screenshot I left the game running for 10 seconds before selecting an option. Enemies should be at the very top on start, however at the moment this is not the case.