Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Implement Timestep and Gravity  (Read 3480 times)

0 Members and 1 Guest are viewing this topic.

Wnt2bsleepin

  • Guest
Implement Timestep and Gravity
« on: April 12, 2013, 01:06:35 am »
I have been making a side scroller game and am having some issues with moving the character and updating the game world. The movement seems choppy. Right now my game loop looks like this:

void Game::gameLoop()
{
    sf::Event currentEvent;
   
    while(_gameState != Game::Exiting)
    {
       _mainWindow.setView(camera);
       playerMove(playerPosition);
       applyGravity(playerPosition);
       

       
        if(_mainWindow.pollEvent(currentEvent)){
           
               
            switch(_gameState)
            {
                case Game::ShowingSplash:
                {
                    showSplashScreen();
                    break;
                }
                   
                case Game::ShowingMenu:
                {
                    _mainWindow.setView(mainView);
                    showMenu();
                    break;
                }
                   
                case Game::Playing:
                {
                   //Clear display and render world.
                    Game::Play();
                   
                   
                   
                    if(currentEvent.type == sf::Event::Closed)
                    {
                        _gameState = Game::Exiting;
                    }

                    if(currentEvent.type == sf::Event::KeyPressed)
                    {
                        cameraV.x = 0;
                        cameraV.y = 0;
                       
                        if(currentEvent.key.code == sf::Keyboard::Escape)
                        {
                            _gameState = Game::ShowingMenu;
                            //showMenu();
                             
                        }
                        else if(currentEvent.key.code == sf::Keyboard::Left){
                            //cameraV.x = -1;
                            playerPosition.x -= playerSpeed;
                        }
                        else if(currentEvent.key.code == sf::Keyboard::Right){
                            //cameraV.x = 1;
                            playerPosition.x += playerSpeed;
                        }
                        else if(currentEvent.key.code == sf::Keyboard::Space){
                           jumping = true;
                           jumpPlayer(playerPosition);
                           jumping = false;
                        }
                             
                    }
                    break;
                }
               
            }
           
        }
       
    }
}

 


and the character moves and jumps, it just seems sloppy. I am moving the characters by taking their current position and either incrementing or decrementing it.


My second question is gravity. It works right now well, but I cannot get it to move diagonally. If I am moving and I hit the jump button, the character stops moving to jump and doesn't continue to move even though the key is held down. Any help or guidance is much appreciated.

OniLinkPlus

  • Hero Member
  • *****
  • Posts: 500
    • View Profile
Re: Implement Timestep and Gravity
« Reply #1 on: April 12, 2013, 01:21:30 am »
There are MANY problems with your code.

First of all, you use an if statement when polling for events. This is very bad, as it will only catch one event, and other events will end up in the backlog. You REALLY should use a while loop instead.

Secondly, you're only moving the character if you pick up a KeyPressed event. Unless you have KeyRepeat enabled (and I recommend against using that as a solution), this will only move your character once at the first frame that the key is pressed. Your character won't continue moving, even if you hold the key down. What you should do instead is use sf::Keyboard to check the real-time state of each key.

Third, you aren't displaying the window at all. So how are you supposed to tell how the character is moving?

Fourth, you're putting all of the game logic inside the event loop. This is a TERRIBLE idea, as it means that the game won't update until it receives an event from somewhere.

Fifth, if you want us to help you further, provide a COMPLETE and MINIMAL copy of your code that reproduces your issues.
I use the latest build of SFML2

Wnt2bsleepin

  • Guest
Re: Implement Timestep and Gravity
« Reply #2 on: April 12, 2013, 01:40:26 am »
Sorry, I only posted the section I thought was relevant. Here is the entire main class.

//
//  game.cpp
//  Game
//
//  Created by Bryan Perino on 2/26/13.
//  Copyright (c) 2013 Bryan Perino. All rights reserved.
//

#include "game.h"
#include "splashscreen.h"
#include "mainmenu.h"
#include "level.h"
#include <cmath>
#include <algorithm>
#include "Player.h"

//Starts the game, displays the splash and menu screen.
static level* level_One;
float cameraSpeed = 20.0f;
float playerSpeed = .005f;
float gravity = .5;
bool jumping = false;
Player* player1;
sf::Clock* myClock;
sf::Vector2<float> playerPosition;
sf::Vector2<float> cameraV(1.0f,1.0f);
sf::View mainView; //Used for the menu
sf::View camera; //Used for the scrolling camera.
bool endRight = false;

sf::Vector2<float> cameraCenter;

int mapWidth;
int mapHeight;

Game::Bounds boundaryX;  //Boundary for width

void Game::Start()
{
    if(_gameState != Uninitialized)
        return;
   
    _mainWindow.create(sf::VideoMode(1024,768), "Save The Plumber");
    mainView = _mainWindow.getDefaultView(); //Used for the menu.
    _mainWindow.setFramerateLimit(60);
    camera = _mainWindow.getDefaultView(); //Starts game with camera centered.
    cameraCenter = camera.getCenter(); //Gets the default center of the camera.
   
   
    _gameState = Game::ShowingSplash;

    myClock = new sf::Clock();
   
    level_One = new level();

    mapWidth = level_One->getmapWidth();
    mapHeight = level_One->getmapHeight();

    boundaryX.max = mapWidth;
    boundaryX.min = 0;
    player1 = new Player();
    int playerStartY = (level_One->getmapHeight() - level_One->getWorldHeight()) - (player1->playerHeight/2);
   
 
    player1->playerSprite.setOrigin(player1->playerSprite.getTexture()->getSize().x/2, player1->playerSprite.getTexture()->getSize().y/2);
    playerPosition.x = (player1->playerWidth)/2;
    playerPosition.y = playerStartY;
    player1->setPosition(playerPosition);

   
   
   
    while(!isExiting())
    {
       
        gameLoop();
       
    }
   
    _mainWindow.close();
}

bool Game::isExiting()
{
    if(_gameState == Game::Exiting)
        return true;
    else
        return false;
}

void Game::gameLoop()
{
    sf::Event currentEvent;
   
    while(_gameState != Game::Exiting)
    {
       _mainWindow.setView(camera);
       playerMove(playerPosition);
       applyGravity(playerPosition);
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)){
            playerPosition.x -= playerSpeed;
        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
            playerPosition.x += playerSpeed;

        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Space)){
            jumping = true;
            jumpPlayer(playerPosition);
            jumping = false;
        }

       
        while(_mainWindow.pollEvent(currentEvent)){
           
               
            switch(_gameState)
            {
                case Game::ShowingSplash:
                {
                    showSplashScreen();
                    break;
                }
                   
                case Game::ShowingMenu:
                {
                    _mainWindow.setView(mainView);
                    showMenu();
                    break;
                }
                   
                case Game::Playing:
                {
                   
                    Game::Play();
                   
                   
                   
                    if(currentEvent.type == sf::Event::Closed)
                    {
                        _gameState = Game::Exiting;
                    }

                    if(currentEvent.type == sf::Event::KeyPressed)
                    {
                       
                        if(currentEvent.key.code == sf::Keyboard::Escape)
                        {
                            _gameState = Game::ShowingMenu;
                            //showMenu();
                             
                        }
                    }
                    break;
                }
               
            }
           
        }
       
    }
}

void Game::showSplashScreen()
{
    SplashScreen splashcreen;
    splashcreen.Show(_mainWindow);
    _gameState = Game::ShowingMenu;
   
}


void Game::showMenu()
{
   
    MainMenu mainMenu;
    MainMenu::MenuResult result = mainMenu.Show(_mainWindow);
    switch(result)
    {
         
        case MainMenu::Exit:
            _gameState = Game::Exiting;
            break;
        case MainMenu::Play:
            _gameState = Game::Playing;
            break;
    }
}



void Game::Play(){
     
    _mainWindow.clear();
   
    level_One->drawMap(_mainWindow, player1->playerSprite);

}

//Updates View position
void Game::updatePost(sf::Vector2<float>& cameraV){


        camera.move(cameraV.x * cameraSpeed, cameraV.y * cameraSpeed);

        float viewLeft = camera.getCenter().x - camera.getSize().x/2;
        float viewRight = camera.getCenter().x + camera.getSize().x/2;
   
       

        if(viewLeft < boundaryX.min){
            camera.setCenter(cameraCenter);
        }

        else if(viewRight >= boundaryX.max){
            camera.setCenter(mapWidth - (camera.getSize().x/2),camera.getCenter().y);
            endRight = true;
        }
   
        if(viewRight <= boundaryX.max){
            endRight = false;
        }

        else if(viewLeft >=  boundaryX.min){
            //When there is nothing left to scroll left.
        }

}

//Updates player position.
void Game::playerMove(sf::Vector2<float> &position){
    float playerWidth = player1->playerWidth;
    cameraV.x = 0;
    cameraV.y = 0;
   
    //Player is nearing the edge of the camera.
    if(position.x + playerWidth/2  > camera.getCenter().x + (camera.getSize().x/2) - 100 && !endRight){
        //Can't Scroll anymore right
        player1->updatePlayer(position);
        cameraV.x = 1;
        updatePost(cameraV);
    }
   
    //Stop the player from moving beyond map border.
    else if(position.x - playerWidth/2 < 0){
        position.x = (player1->playerWidth)/2;
        player1->updatePlayer(position);
    }
   
    //Stop player from moving back.
    else if(position.x - playerWidth/2 < camera.getCenter().x - (camera.getSize().x/2)){
        position.x = camera.getCenter().x - (camera.getSize().x/2) + playerWidth/2;
        player1->updatePlayer(position);
    }
   
    //Stop player from moving out of map right.
    else if(position.x + playerWidth/2 > mapWidth && endRight){
        position.x = mapWidth - (playerWidth/2);
        player1->updatePlayer(position);
    }

    else{
        //Move the player normally;
        player1->updatePlayer(position);
    }
    //std::cout << "Player position: " << player1->getPosition().x << std::endl;

}

void Game::applyGravity(sf::Vector2<float> &position){
    float playerYBound = position.y + (player1->playerHeight/2);
    if(playerYBound < level_One->getworldFloor()){
        position.y += 5;
        playerMove(position);
    }
   
}

void Game::checkBounds(sf::Vector2<float> &position){
   
    if(position.y - player1->playerHeight/2 <= level_One->getworldFloor()){
        position.y = (level_One->getworldFloor()) + player1->playerHeight/2;
        playerMove(position);
    }
   
}

void Game::jumpPlayer(sf::Vector2<float> &position){
    position.y -= 40.0f;
    playerMove(position);
}

Game::GameState Game::_gameState = Uninitialized;
sf::RenderWindow Game::_mainWindow;
 

I moved the keyboard events outside of the event loop to check if the key is pressed down. I don't know if you wanted a more complete example. The project has a lot of files, but I could probably put something together to demonstrate what is going on.

OniLinkPlus

  • Hero Member
  • *****
  • Posts: 500
    • View Profile
Re: Implement Timestep and Gravity
« Reply #3 on: April 12, 2013, 01:48:52 am »
Like I said, we need a COMPLETE and MINIMAL copy of your code that reproduces the problem if you want us to help more. When I say complete, I mean it must be enough that we can compile it. By minimal, I mean it needs to be a single source file with everything except for the bare minimum to see the bug in action delete.
I use the latest build of SFML2

Wnt2bsleepin

  • Guest
Re: Implement Timestep and Gravity
« Reply #4 on: April 12, 2013, 03:22:11 am »
I made a small program to represent what I am talking about. It follows the same logic that I am applying to my other game.

Main.cpp
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Network.hpp>
#include "main.h"
#include <iostream>


int main (){
    Game* myGame = new Game();
    myGame->Play();
    return 0;
}

void Game::Play(){
   
    //Moveable Circle Object
    myCircle.setRadius(100);
    myCircle.setOutlineColor(sf::Color::Red);
    myCircle.setFillColor(sf::Color::Red);
    sf::Vector2<float> position;
    position.x = myCircle.getRadius()/2;
    position.y = 20;
    myCircle.setPosition(position.x, position.y);
    myCircle.setOrigin(myCircle.getRadius()/2, myCircle.getRadius()/2);
   
    //Rectangle representing floor
    sf::RectangleShape myRect;
    sf::Vector2<float> rectSize;
    rectSize.x = 1025.0f;
    rectSize.y = 400.0f;
    myRect.setSize(rectSize);
    myRect.setFillColor(sf::Color::Blue);
    myRect.setPosition(0,400);
   
   
    Game::_mainWindow.create(sf::VideoMode(1024,768), "Example");
   
    while(Game::_mainWindow.isOpen()){
        _mainWindow.clear();
        applyGravity(position);
        sf::Event event;
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
            myCircle.setPosition(position.x += .5f, position.y);
        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)){
            myCircle.setPosition(position.x -= .5f, position.y);
        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)){
            myCircle.setPosition(position.x, position.y -= .5f);
        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Space)){
            jumpPlayer(position);
        }
       
       
       
        while (_mainWindow.pollEvent(event))
        {
            // "close requested" event: we close the window
            if (event.type == sf::Event::Closed)
                _mainWindow.close();
        }
       
        _mainWindow.draw(myCircle);
        _mainWindow.draw(myRect);
        _mainWindow.display();
    }

   
}

void Game::applyGravity(sf::Vector2<float> &position){
    float playerYBound = position.y + (myCircle.getRadius()/2);
    float mapFloor = (1024 - 728);
    std::cout << "Ball Position" << position.y << std::endl;
    if(playerYBound < mapFloor){
        position.y += 5;
        myCircle.setPosition(position);
    }
   
}

void Game::jumpPlayer(sf::Vector2<float> &position){
    position.y -= 10.0f;
    myCircle.setPosition(position.x, position.y);
}


sf::CircleShape Game::myCircle;
sf::RenderWindow Game::_mainWindow;
 

main.h
class Game
{

    public:
   
    static void Play();
   
    static void applyGravity(sf::Vector2<float> &position);

    static sf::CircleShape myCircle;

    static sf::RenderWindow _mainWindow;
   
    static void jumpPlayer(sf::Vector2<float> &position);

       
};


 

This should create a ball that you can move around the screen. It should start in the origin (0,0) and move down to the floor of the map. The problem is that the motions seems to be jerky. I was able to fix some of the movement issues by moving the logic out of the game loop.

void Game::gameLoop()
{
    sf::Event currentEvent;
   
    while(_gameState != Game::Exiting)
    {
        //std::cout << "Is right being pressed " << sf::Keyboard::isKeyPressed(sf::Keyboard::Right) << std::endl;
       _mainWindow.setView(camera);
       playerMove(playerPosition);
       applyGravity(playerPosition);
       
        if(_gameState == Game::Playing){
            Game::Play();
        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)){
            playerPosition.x -= playerSpeed;
            playerMove(playerPosition);

        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
            playerPosition.x += playerSpeed;
            playerMove(playerPosition);
        }
       
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Space)){
            jumping = true;
            jumpPlayer(playerPosition);
            jumping = false;
        }
       
       
       
        while(_mainWindow.pollEvent(currentEvent)){
           
               
            switch(_gameState)
            {
                case Game::ShowingSplash:
                {
                    showSplashScreen();
                    break;
                }
                   
                case Game::ShowingMenu:
                {
                    _mainWindow.setView(mainView);
                    showMenu();
                    break;
                }
                   
                case Game::Playing:
                {
                   
                    //Game::Play();
                   
                   
                   
                    if(currentEvent.type == sf::Event::Closed)
                    {
                        _gameState = Game::Exiting;
                    }

                    if(currentEvent.type == sf::Event::KeyPressed)
                    {
                       
                        if(currentEvent.key.code == sf::Keyboard::Escape)
                        {
                            _gameState = Game::ShowingMenu;
                            //showMenu();
                             
                        }
                    }
                    break;
                }
               
            }
           
        }
       
    }
}
 

Luis

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: Implement Timestep and Gravity
« Reply #5 on: April 18, 2013, 04:36:03 am »
To make your movement less choppy, look into http://en.wikipedia.org/wiki/Delta_timing

foobarbaz

  • Jr. Member
  • **
  • Posts: 53
    • View Profile
Re: Implement Timestep and Gravity
« Reply #6 on: April 18, 2013, 11:42:49 am »
As Luis said, the choppiness problem will be solved by delta timing. The basic idea is that you multiply a delta time (change in time) value by the corresponding speed. So, you'd calculate deltaTime in seconds as a float and multiply it by whatever speed you want, for instance 100 pixels per second.

Code: [Select]
sf::Clock deltaClock;
float deltaTime = 0.f;
int lastFrameTime = 0;

sf::Vector2f velocity(0.f, 100.f);

while (myGameIsRunning)
{
            deltaTime = ((float)deltaClock.getElapsedTime().asMilliseconds()-lastFrameTime)/1000.f;
            lastFrameTime = deltaClock.getElapsedTime().asMilliseconds();

           // When moving objects
           characterSprite.move(velocity*deltaTime);
}

I hope that helps. Now, for gravity and movement and whatnot. Off the top of my head, what I would do is have multiple 2D vectors that get summed together for the player's final speed. So, you'll have a movement vector that is modified when keypresses are detected and a gravity vector for handling gravity/jumping. I'll try to code this up, but keep in mind it's more pseudo-code than code you can compile.

Code: [Select]
sf::Vector2f playerSpeed;
sf::Vector2f gravity;

while (myGameIsRunning)
{
            playerSpeed = sf::Vector2f(0.f, 0.f); // Reset the player's speed each frame so we can add to it with each keypress
            if (leftDown) playerSpeed += sf::Vector2f(-100.f, 0.f);
            if (rightDown) playerSpeed += sf::Vector2f(-100.f, 0.f);
            if (upPressed) gravity = sf::Vector2f(0.f, -200.f); // up we go :)

            gravity.y -= 75.f*deltaTime;
            if (gravity.y > 200.f) gravity.y = 200.f; // Don't let gravity get too fast

            playerSprite.move((playerSpeed+gravity)*deltaTime);
}

And that should do it! Sorry I'm too lazy to actually go through your code and make it work, I'm about to go to sleep, have class early tomorrow! Hope this helps
« Last Edit: April 18, 2013, 11:44:29 am by DrSuperSocks »