/*Some basic inertia / friction on a single body. Gives a strong sense of realism. Makes you want to play around with two or more bodies and collision detection, but that can only lead to a time consuming broken re-implementation of a subset of Box2D... right? */ #include "common.h" #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT WINDOW_WIDTH #define WINDOW_CENTER_X (WINDOW_WIDTH / 2) #define WINDOW_CENTER_Y (WINDOW_HEIGHT / 2) #define WINDOW_HEIGHT WINDOW_WIDTH #define RECT_WIDTH (WINDOW_WIDTH / 50) #define RECT_HEIGHT RECT_WIDTH #define MAX_RECT_POS (WINDOW_WIDTH - RECT_WIDTH) /* * Accelerations in meters per second. * * I like to think like this: how long will it take to cross the screen * starting from speed 0 with this constant acceleration? * * Remember that: * * x = acc * t^2 / 2 * * so if: * * - `acc = MAX_RECT_POS * 2.0` it takes 1 second to cross the screen * - `acc = MAX_RECT_POS` it takes sqrt(2) seconds to cross the screen * - `acc = MAX_RECT_POS / 2.0` it takes 2 seconds to cross the screen **/ #define ACC_USER_METERS_PER_SECOND (MAX_RECT_POS) #define ACC_FRICTION_METERS_PER_SECOND (MAX_RECT_POS / 2.5) #define ACC_GRAVITY_METERS_PER_SECOND (MAX_RECT_POS / 1.5) typedef struct { double x, y, speed_x, speed_y ; } PlayerState; void init_state( PlayerState *player_state, double *user_acc_x, double *user_acc_y, unsigned int *last_time ) { player_state->x = WINDOW_WIDTH / 2.0; player_state->y = WINDOW_HEIGHT / 2.0; player_state->speed_x = 0.0; player_state->speed_y = 0.0; /* Encode if the movement keys are initially pressed or not. */ *user_acc_x = 0.0; *user_acc_y = 0.0; *last_time = SDL_GetTicks(); common_fps_init(); } double get_new_coord(double x, double step) { x += step; x = fmod(x, MAX_RECT_POS); if (x < 0) x = MAX_RECT_POS; return x; } /* A constant force in the direction opposite to movement, * or stop the movement if it is too slow. * */ double apply_constant_friction(double speed, double dt) { double dv; dv = ACC_FRICTION_METERS_PER_SECOND * dt; if (fabs(speed) < dv) speed = 0.0; else speed -= copysign(dv, speed); return speed; } int main(void) { PlayerState player_state; SDL_Event event; SDL_Rect rect, rect_sun; SDL_Renderer *renderer; SDL_Window *window; int quit = 0; unsigned int current_time, last_time; double dt, acc_x, acc_y, user_acc_x, user_acc_y; SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO); SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer); SDL_SetWindowTitle(window, "arrow keys: accelerate | esc: reset | q: quit"); rect.w = RECT_WIDTH; rect.h = RECT_HEIGHT; rect_sun.w = RECT_WIDTH; rect_sun.h = RECT_HEIGHT; rect_sun.x = WINDOW_CENTER_X; rect_sun.y = WINDOW_CENTER_Y; main_loop: init_state(&player_state, &user_acc_x, &user_acc_y, &last_time); while (!quit) { while (SDL_PollEvent(&event) == 1) { if (event.type == SDL_QUIT) { quit = 1; } else if (event.type == SDL_KEYDOWN) { switch (event.key.keysym.sym) { case SDLK_LEFT: user_acc_x = -1; break; case SDLK_RIGHT: user_acc_x = 1; break; case SDLK_UP: user_acc_y = -1; break; case SDLK_DOWN: user_acc_y = 1; break; case SDLK_ESCAPE: goto main_loop; case SDLK_q: goto quit; } } else if (event.type == SDL_KEYUP) { switch (event.key.keysym.sym) { case SDLK_LEFT: case SDLK_RIGHT: user_acc_x = 0; break; case SDLK_UP: case SDLK_DOWN: user_acc_y = 0; break; } } } current_time = SDL_GetTicks(); if (current_time != last_time) { dt = (current_time - last_time) / 1000.0; /* Player movement. */ acc_x = user_acc_x * ACC_USER_METERS_PER_SECOND; acc_y = user_acc_y * ACC_USER_METERS_PER_SECOND; /* Other forces. */ { /* Constant gravity down. */ /*acc_y += ACC_GRAVITY_METERS_PER_SECOND;*/ /* Constant gravity to center of screen, the "sun". */ { double dx = player_state.x - WINDOW_CENTER_X; double dy = player_state.y - WINDOW_CENTER_Y; double angle = atan2(dy, dx); double hyp = hypot(dx, dy); /* TODO Failed attempt at force proportional to inverse square of distance. */ /*double mod = hyp > 0.001 ? hyp : 1.0;*/ /*double mod2 = mod * mod;*/ acc_x -= cos(angle) * ACC_GRAVITY_METERS_PER_SECOND; acc_y -= sin(angle) * ACC_GRAVITY_METERS_PER_SECOND; } } /* Update speed. */ player_state.speed_x += dt * acc_x; player_state.speed_y += dt * acc_y; /* Constant friction - decelerates very quickly - speed is limited to a maximum Ideally this force should be added to the acceleration simply, but we must consider the case where the object stops completely because of it separately. */ /*player_state.speed_x = apply_constant_friction(player_state.speed_x, dt);*/ /*player_state.speed_y = apply_constant_friction(player_state.speed_y, dt);*/ /* Friction proportional to speed: - decelerates very quickly - speed is limited to a maximum Does not feel very natural. */ /* player_state.speed_x *= 0.999; */ /* player_state.speed_y *= 0.999; */ /* Update position. */ player_state.x = get_new_coord(player_state.x, player_state.speed_x * dt); player_state.y = get_new_coord(player_state.y, player_state.speed_y * dt); /* Render. */ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); rect.x = player_state.x; rect.y = player_state.y; SDL_RenderFillRect(renderer, &rect); SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); SDL_RenderFillRect(renderer, &rect_sun); SDL_RenderPresent(renderer); /* Update time. */ last_time = current_time; } common_fps_update_and_print(); } quit: SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_SUCCESS; }