I wanted to implement a special Game loop where the game actually only updates 25 frames a second while it is rendered at the top speed of the computer. I followed the article from dewitters game loop and have the interpolation setup correctly i believe (btw i'm using sdl2)...
const int TICKS_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
const int MAX_FRAMESKIP = 5;
Uint32 next_game_tick = SDL_GetTicks();
int loops;
float interpolation;
while (running)
{
loops = 0;
while (SDL_GetTicks() > next_game_tick && loops < MAX_FRAMESKIP)
{
Update();
next_game_tick += SKIP_TICKS;
loops++;
}
interpolation = float(SDL_GetTicks() + SKIP_TICKS - next_game_tick) / float(SKIP_TICKS);
Render(interpolation);
}
But I don't really understand how to implement the interpolation within the render call. i tried just setting the x and y position of my sprite relative to the interpolation...
interPos.x = pos.x + int (speed * interpolation);
interPos.y = pos.y + int (speed * interpolation);
link.Draw(ren, interPos, 0, false);
but this just made the main character sprite jitter all around. Any help appreciated!
Related
notes:
room1Collisions is a 1D array containing 1s and 0s representing collidable tiles.
+/- 0.1 is used so that the player can still move when against a collidable tile.
size is an SFML vector that holds the width and height of the player.
I understand that this code is not in great shape, but I'm trying to get collisions working to then refactor.
The issue (described below) occurs more frequently when the player collides diagonally
code:
void Player::Update(float dt) {
// 0 is a collidable tile
// change room1Collisions to a pointer of current level
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
{
// a is top left and b is bottom left
sf::Vector2u a(position.x/tileSize.x, (position.y+size.y/2)/tileSize.y);
sf::Vector2u b(position.x/tileSize.x, (position.y+size.y-0.1)/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.x >= 0) {
position.x -= speed * dt;
//animation.resumeAnimation();
}
direction = LEFT;
//animation.resumeAnimation();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
sf::Vector2u a((position.x+size.x)/tileSize.x, (position.y+size.y/2)/tileSize.y);
sf::Vector2u b((position.x+size.x)/tileSize.x, (position.y+size.y-0.1)/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.x+size.x <= tilemapBounds.x) {
position.x += speed * dt;
//animation.resumeAnimation();
}
direction = RIGHT;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
sf::Vector2u a((position.x+0.1)/tileSize.x, (position.y+(size.y/3))/tileSize.y);
sf::Vector2u b((position.x+size.x-0.1)/tileSize.x, (position.y+(size.y/3))/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.y >= 0) {
position.y -= speed * dt;
//animation.resumeAnimation();
}
direction = UP;
//animation.resumeAnimation();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
{
sf::Vector2u a((position.x+0.1)/tileSize.x, (position.y+size.y)/tileSize.y);
sf::Vector2u b((position.x+size.x-0.1)/tileSize.x, (position.y+size.y)/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.y+size.y <= tilemapBounds.y) {
position.y += speed * dt;
//animation.resumeAnimation();
}
direction = DOWN;
}
//animation.setAnimation(direction);
//animation.Update(dt, 0.2f);
//animation.setPosition(position);
box.setPosition(position);
}
The problem is that when the game is run on slower computers, sometimes the player will skip (?) a collision check and it becomes stuck in a collidable tile, preventing it from moving orthogonally with respect to the collidable tile. On faster computers this issue is not present unless the player speed (currently 30) is increased significantly.
Below is an image showing the player inside a collidable tile. Please note that in this example the player is not able to move left/right since those directions are perpendicular to the collidable tile, but can still move up.
What you need is to regulate the FPS so that it is consistent for all computers.
Frame rate regulation example(This uses the SDL2 library for SDL_GetTicks and SDL_Delay but there are probably alternatives to that depending on your graphics library):
const int FPS = 60;
const int frameDelay = 1000/FPS;
Uint32 frameStart;
int frameTime;
//Main game loop
while (true)
{
//Gets the amount of milliseconds elapsed since the program was first run
frameStart = SDL_GetTicks();
//YOUR MAIN GAME LOOP HERE
//Gets the amount of milliseconds that it took to run the game loop
frameTime = SDL_GetTicks() - frameStart;
//Checks if the game loop was run faster than the max frame time
if(frameDelay > frameTime)
{
//Delays the game loop so that it takes that the frame time always is the same
SDL_Delay(frameDelay - frameTime);
}
}
(Keep in mind that this is just an example there are lots of ways to regulate the FPS)
After implementing FPS regulation you might need to tweak the speed and collision sensitivity a little but once you have it it should be consistent on ALL computers
I'm trying to emulate the following ball. Notice the simple harmonic motion of the ball, with the very ends of the ball bounce having a smaller velocity compared to the velocity in the middle:
I'm able to implement a bouncing ball, however it's not simple harmonic motion:
The corresponding code is as follows:
Dot::Dot() {
//Initialize the offsets
mPosX = 300;
mPosY = 0;
//Initialize the velocity
mVelX = 0;
mVelY = 4;
}
void Dot::move() {
//Move the dot up or down
mPosY += mVelY;
//If the dot went too far up or down
if( ( mPosY < 0 ) || ( mPosY + DOT_HEIGHT > SCREEN_HEIGHT ) )
{
//Move back
mVelY = -mVelY;
}
}
I have a simple harmonic motion model, like so:
The corresponding code is as follows:
Dot::Dot() {
//Initialize the offsets
mPosX = 300;
mPosY = 0;
//Initialize the velocity
mVelX = 0;
mVelY = 0;
}
void Dot::move() {
time_t current_time;
current_time = time(NULL);
mPosY = int(((460) - 10) * sin(2.4 * 2 * 3.141592 / 60 * current_time + (SCREEN_HEIGHT / 2)
));
//const int SCREEN_HEIGHT = 480
}
The issues with this implementation are that:
(1). the ball image appears every now and then, rather than continuously like in the blue ball model I tried to emulate at the very beginning
(2). the ball goes well beyond the top frame of the window, rather than slowing down at the very top of the window, again like the blue ball model.
For (2), I understand that I need to add a phase shift, i.e x in A*sin(wt + x), however changing this value doesn't do anything to prevent the ball from disappearing at the top of the window.
Any ideas on how to solve these issues?
Edit: I was able to solve (1) by doing += to mPosY rather than =, such as:
mPosY += int(4 * cos(2.4 * 2 * 3.141592 / 60 * current_time + (SCREEN_HEIGHT / 2) ));
However, I'm still unable to get the ball to bounce up and down within the frame of the window I created.
I recommend using actual simple harmonic equations.
For example, if your display dimensions are (500, 500), the center Y is 250. from there say your equation is in the form of x = acos(nt + m) + c where x is displacement (meters), a is amplitude n is for the period, for example the period (T) = 2PI/n t is time (seconds) and m is for phase shift and c is for the center. That way when you need the velocity of the object, you have a function that follows along the lines of
double Velocity(double time){
double vel = derivative_of_displacement_equation(time);
return vel;
}
And so in the program, you adjust the equation to suit the display dimensions, then you set the objects X/Y coordinates as the value returned from the displacement equation (PLUS THE CENTER OFFSET, in this example, if the center is at the middle of the screen, you would set the Y coordinate to the equation PLUS 250). Keep in mind coordinates begin at (0,0) so your displacement equation (at least the part where it involves the proportional factor, which in this case is time), you make that negative instead.
Here is some code that I believe answers your question:
#include <SDL2/SDL.h>
#include <chrono>
#include <math.h>
#include <iostream>
const double PI = 3.14159265358979;
void draw_circle(SDL_Renderer *renderer, int x, int y, int radius, SDL_Color color)
{
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
for (int w = 0; w < radius * 2; w++)
{
for (int h = 0; h < radius * 2; h++)
{
int dx = radius - w; // horizontal offset
int dy = radius - h; // vertical offset
if ((dx*dx + dy*dy) <= (radius * radius))
{
SDL_RenderDrawPoint(renderer, x + dx, y + dy);
}
}
}
}
double Displacement(double time, double a, double n, double m, double c)
{
double displacement = a*cos(n*time + m) + c;
return displacement;
}
int main(int argc, char* argv[])
{
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("SHM", 0, 30, 500, 500, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);// | SDL_WINDOW_SHOWN);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED );
double timeDifference;
std::chrono::steady_clock::time_point start, finish;
start = std::chrono::steady_clock::now();
finish = start;
SDL_Event event;
bool running = true;
while (running){
while (SDL_PollEvent(&event)){
if (event.type == SDL_QUIT){
running = false;
break;
}
}
SDL_SetRenderDrawColor(renderer, 255,255,255,255);
SDL_RenderClear(renderer);
finish = std::chrono::steady_clock::now();
timeDifference = std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count();
timeDifference = timeDifference / 1000000000;
///The "-(250-20) is the center y (250) minus the radius of the circle (20), and its - out the front as negative a due to coordinates
double yPosition = round( Displacement(timeDifference, -(250-20), 2, 0, 250 ) );
draw_circle(renderer, 250, yPosition, 20, {255,0,0});
SDL_RenderPresent(renderer);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
In general you have a0 + a/2*cos (2*𝝥*t/T + 𝝫) where a0 is the vertical position of the half of the vertical travel, a is the height of the travel, t is time, T the period ie., the time to do a complete cycle for going and coming back to the same state or uple { position, momentum }, and 𝝫 the time shift, ie., the moment where the height is at zero of the cos.
So if you want the ball to be on the floor at t=0, you want cos at the minimum, ie., 𝝫 = -𝝥/2.
You want to manage your position in function of your game's time t, so you can decouple the time to compute (which depend on your compute calpabilities) and the game's time (that you want constant from a machine to another).
Therefore you want:
auto VerticalPosition(double t)
-> double { return CorrectedScreenHeight/2*(1 + cos(2*PI*t/T + phi)); }
And you define CorrectedScreenHeight = SCREEN_HEIGHT - DOT_HEIGHT, T and phi outside, as properties of your system.
Between two consecutive images, you increment t, in order to have the correct experienced time. Typically you have 60 images/s (WPF, DirectX, web, etc), hence a period of 1.0/60s between consecutive images, this goes in your function that modifies t. The speed of your ball then depend on T, that you can tune independently.
I am trying to get my tokens on a board game to fall slowly. Right now, they fall, but they fall so fast. How could I implement the timer function in my code? Right now I do a loop, that updates the y coordinate of glTranslate. But it is still too fast! the top y is the y coordinate where I press on the screen, and the bottomy is the coordinates of the lowest open spot for a token.
col =0;
double bottomy = 0;
int row = 0;
circlex = (double)x / width ;
circley = (double)y / height ;
row = board.getRow(col) + 1;
bottomy = 500 - (25*row);
for( double topy = y ; topy <= bottomy; topy += 2 ){
glTranslatef(circlex, circley, 0.0f);
circley += .0000000000000000001;
display();
}
r = board.makeMove(col);
You can use glutTimerFunc to execute a function at a regular time period. This has the signature
void glutTimerFunc(unsigned int msecs,
void (*func)(int value),
value);
For example if your drawing function was
void UpdateTokens(int time);
Then you could call an update every 0.5 seconds with the following call (where current_time was the current simulation time)
glutTimerFunc(500, UpdateTokens, current_time);
For more precise timing, I would recommend using <chrono> instead, and performing your timing using things like std::chrono::duration with a std::chrono::steady_clock.
The actual problem here is how glut works. Basically, the user only gets a image presented at the end of the main loop. As long as you do not return from the mouse function, nothing is presented on screen. You can solve the problem by transferring the work to the display function and distribute the translation across multiple frames:
global variables:
double circlex = 0, circley = 0, bottomy = 0;
bool isfalling = false;
int topy = 0;
mouse_func:
if (isfalling == false) //Prevents the user from clicking during an animation
{
circlex = (double)x / width ;
circley = (double)y / height ;
int row = board.getRow(col) + 1;
bottomy = 500 - (25*row);
topy = y;
isfalling = true;
}
display_func:
if (isfalling)
{
circley += .0000000000000000001;
topy += 2;
if (topy >= bottomy)
isfalling = false;
}
glTranslatef(circlex, circley, 0.0f);
display();
I'm making a 2D game with SFML in C++ and I have a problem with collision. I have a player and a map made of tiles. Thing that doesn't work is that my collision detection is not accurate. When I move player up and then down towards tiles, it ends up differently.
I am aware that source of this problem may be calculating player movement with use of delta time between frames - so it is not constant. But it smooths movement, so I don't know how to do it other way. I tried with constant speed valuses and to make collision fully accurate - speed had to be very low and I am not satisfied with that.
void Player::move() {
sf::Vector2f offsetVec;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
offsetVec += sf::Vector2f(0, -10);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
offsetVec += sf::Vector2f(0, 10);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
offsetVec += sf::Vector2f(-10, 0);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
offsetVec += sf::Vector2f(10, 0);
this->moveVec += offsetVec;
}
void Player::update(float dt, Map *map) {
sf::Vector2f offset = sf::Vector2f(this->moveVec.x * this->playerSpeed * dt,
this->moveVec.y * this->playerSpeed * dt);
sf::Sprite futurePos = this->sprite;
futurePos.move(offset);
if (map->isCollideable(this->pos.x, this->pos.y, futurePos.getGlobalBounds())) {
this->moveVec = sf::Vector2f(0, 0);
return;
}
this->sprite.move(offset);
this->pos += offset;
this->moveVec = sf::Vector2f(0, 0);
return;
}
In player position update I create future sprite object, which is object after applying movement, to get it's boundaries and pass it to collision checker. To collision checker I also pass player pos, because my map is stored in 2d array of tile pointers, so I check only these in player range.
bool Map::isCollideable(float x, float y, const sf::FloatRect &playerBounds) {
int startX = int(x) / Storage::tileSize;
int startY = int(y) / Storage::tileSize;
Tile *tile;
for (int i = startX - 10; i <= startX + 10; ++i) {
for (int j = startY - 10; j <= startY + 10; ++j) {
if (i >= 0 && j >= 0) {
tile = getTile(i, j);
if (tile != nullptr && playerBounds.intersects(tile->getGlobalBounds()))
return true;
}
}
}
return false;
}
Full project on Github
My solution
I have changed if statement in update function to while statement, which decreases my offset vector till no collision is present. I still have to make some adjustments, but general idea is:
void Player::update(float dt, Map *map) {
int repeats = 0;
sf::Vector2f offset = sf::Vector2f(this->moveVec.x * this->playerSpeed * dt,
this->moveVec.y * this->playerSpeed * dt);
sf::Sprite futurePos = this->sprite;
while (map->isCollideable(this->pos.x, this->pos.y, futurePos, offset)) {
offset = 0.7f * offset;
repeats++;
if (repeats > 5) {
this->moveVec = sf::Vector2f(0, 0);
return;
}
}
this->sprite.move(offset);
this->pos += offset;
this->moveVec = sf::Vector2f(0, 0);
return;
}
I also had to rework isCollideable method a little, so it accepts sf::Sprite and offset vector so it can calculate boundaries on it's own.
When the player collides with a tile, you should calculate the penetration, that is, the value of "how much the player went into the tile". When you have this value, nudge your player back that much.
This is just a thought but you could have some inaccuracies in your collision detection when you typecast the float x, and y to integers and then divide them. This could cause problems because some of the data in the float could be lost. If the float was 3.5 or 3.3 or 3.9 then it would become 3 which throws off your collision calculations.
Problem: I am attempting to update time using getTickCount without avail.
Situation: I currently obtain the timedifference using getTickCount and pass it into world update method parameters. However within the update method where I update the position, a large value is passed in (even though I have divided by 1000) and so the position adds an odd 4000 to the position vector.
Code below:
Simulation.CPP:
int Simulation::simControlLogic(HWND hWnd, keyEvent event)
{
/* TO DO: add relevant code */
if (event != QUIT)
{
previousTime = 0;
frameStartTime = GetTickCount();
if (previousTime == 0)
previousTime = frameStartTime;
timeDifference = (frameStartTime - previousTime) / 1000.0f; // this generates the difference between the last and current time
world.update(event, &graphics, timeDifference); // update parameters of virtual world
gameLoopDelay(frameStartTime);
simDisplayFrame(hWnd); // display frame
previousTime = frameStartTime; //the current time is set to the previous time for the next loop step
}
return 1;
}
looking into the world Update method:
int WorldData::update(keyEvent kEvent, GraphicsM * pGraphicsModule, float timeStep)
{
//updates the particle
for (int i = 0; i < 3; i++)
{
particles[i].Update(kEvent, pGraphicsModule, timeStep);
}
return 1;
}
looking into particles Update method:
void ParticleModel::Update(keyEvent kEvent, GraphicsM * pGraphicsModule, float timeStep)
{
move(timeStep);
}
looking into move method:
void ParticleModel::move(float timeStep)
{
velocity.y = 0.5F;
velocity.x = 0.5F;
acceleration.x = 0.0F;
acceleration.y = 0.0F;
pos.x += velocity.x * timeStep; //here is the problem. I get a large value e.g 79637.1788 causing pos.x to be ridiculously large
pos.y += velocity.y * timeStep; //here is the problem. I get a large value e.g 79637.1788 causing pos.y to be ridiculously large
}
After moving previousTime to the simulator constructor initializing it to 0, it worked. Thanks for replies