Reputation: 3336
I am self learning SDL, OpenGL etc with C++. Tutorials online have helped me create a box that move around the screen and a ledge that the box can not pass through...
I haven't been successful in finding a tutorial that explains and shows you how to make the box jump or hop around. Ideally i want my box to move along and jump up onto the ledge similar to old school mario.
I'm not sure if this can be done with SDL?
I'm guessing something needs to be added to make the box return to ground level after the up is pressed...
I have experimented with the up
/down
keys and tried to make the box return to the ground after its pressed but it didn't work.
I've tried setting the box to the ground but then the up key doesn't work.
I've also tried once up has been pressed and the box has moved to make the box go down by the same distance...
I kind of get how it needs to be implemented (i think), i just dont know enough C++ to write it or if it can even be done.
Currently i can move left or right and up, but he stays floating in the air :(
Here is my code:
#include "SDL.h"
#include "SDL_image.h"
#include <string>
const int SCREEN_WIDTH = 480;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;
const int FRAMES_PER_SECOND = 20;
const int SQUARE_WIDTH = 20;
const int SQUARE_HEIGHT = 20;
SDL_Surface *square = NULL;
SDL_Surface *screen = NULL;
SDL_Event event;
SDL_Rect wall;
class Square{
private:
SDL_Rect box;
int xVel, yVel;
public:
Square();
void handle_input();
void move();
void show();};
class Timer{
private:
int startTicks;
int pausedTicks;
bool paused;
bool started;
public:
Timer();
void start();
void stop();
void pause();
void unpause();
int get_ticks();
bool is_started();
bool is_paused();};
SDL_Surface *load_image( std::string filename ){
SDL_Surface* loadedImage = NULL;
SDL_Surface* optimizedImage = NULL;
loadedImage = IMG_Load( filename.c_str() );
if( loadedImage != NULL )
{
optimizedImage = SDL_DisplayFormat( loadedImage );
SDL_FreeSurface( loadedImage );
if( optimizedImage != NULL )
{
SDL_SetColorKey( optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( optimizedImage->format, 237, 145, 33 ) );//optimizedImage->format, 0, 0xFF, 0xFF
}
}
return optimizedImage;}
void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL ){
SDL_Rect offset;
offset.x = x;
offset.y = y;
SDL_BlitSurface( source, clip, destination, &offset );}
bool check_collision( SDL_Rect A, SDL_Rect B ){
int leftA, leftB;
int rightA, rightB;
int topA, topB;
int bottomA, bottomB;
leftA = A.x;
rightA = A.x + A.w;
topA = A.y;
bottomA = A.y + A.h;
leftB = B.x;
rightB = B.x + B.w;
topB = B.y;
bottomB = B.y + B.h;
if( bottomA <= topB ){return false;}
if( topA >= bottomB ){return false;}
if( rightA <= leftB ){return false;}
if( leftA >= rightB ){return false;}
return true;}
bool init(){
if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ){return false;}
screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );
if( screen == NULL ){return false;}
SDL_WM_SetCaption( "Move the Square", NULL );
return true;}
bool load_files(){
square = load_image( "square.bmp" );
if( square == NULL ){return false;}
return true;}
void clean_up(){
SDL_FreeSurface( square );
SDL_Quit();}
Square::Square(){
box.x = 50;
box.y = 360;
box.w = SQUARE_WIDTH;
box.h = SQUARE_HEIGHT;
xVel = 0;
yVel = 0;}
void Square::handle_input(){
if( event.type == SDL_KEYDOWN ){
switch( event.key.keysym.sym ){
case SDLK_UP: yVel -= SQUARE_HEIGHT / 2; break;
//case SDLK_DOWN: yVel += SQUARE_HEIGHT / 2; break;
case SDLK_LEFT: xVel -= SQUARE_WIDTH / 2; break;
case SDLK_RIGHT: xVel += SQUARE_WIDTH / 2; break;}}
else if( event.type == SDL_KEYUP ){
switch( event.key.keysym.sym ){
case SDLK_UP: yVel += SQUARE_HEIGHT / 2; break;
//case SDLK_DOWN: yVel -= SQUARE_HEIGHT / 2; break;
case SDLK_LEFT: xVel += SQUARE_WIDTH / 2; break;
case SDLK_RIGHT: xVel -= SQUARE_WIDTH / 2; break;}}}
void Square::move(){
box.x += xVel;
if( ( box.x < 0 ) || ( box.x + SQUARE_WIDTH > SCREEN_WIDTH ) || ( check_collision( box, wall ) ) ){
box.x -= xVel;}
box.y += yVel;
if( ( box.y < 0 ) || ( box.y + SQUARE_HEIGHT > SCREEN_HEIGHT ) || ( check_collision( box, wall ) ) ){
box.y -= yVel;}}
void Square::show(){
apply_surface( box.x, box.y, square, screen );}
Timer::Timer(){
startTicks = 0;
pausedTicks = 0;
paused = false;
started = false;}
void Timer::start(){
started = true;
paused = false;
startTicks = SDL_GetTicks();}
void Timer::stop(){
started = false;
paused = false;}
void Timer::pause(){
if( ( started == true ) && ( paused == false ) ){
paused = true;
pausedTicks = SDL_GetTicks() - startTicks;}}
void Timer::unpause(){
if( paused == true ){
paused = false;
startTicks = SDL_GetTicks() - pausedTicks;
pausedTicks = 0;}}
int Timer::get_ticks(){
if( started == true ){
if( paused == true ){
return pausedTicks;}
else{return SDL_GetTicks() - startTicks;}
}return 0;}
bool Timer::is_started(){return started;}
bool Timer::is_paused(){return paused;}
int main( int argc, char* args[] ){
bool quit = false;
Square mySquare;
Timer fps;
if( init() == false ){return 1;}
if( load_files() == false ){return 1;}
wall.x = 130;
wall.y = 300;
wall.w = 220;
wall.h = 20;
while( quit == false ){
fps.start();
while( SDL_PollEvent( &event ) ){
mySquare.handle_input();
if( event.type == SDL_QUIT ){
quit = true;}}
mySquare.move();
SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 1, 1, 1 ) );
SDL_FillRect( screen, &wall, SDL_MapRGB( screen->format, 237, 145, 33 ) );
mySquare.show();
if( SDL_Flip( screen ) == -1 ){return 1;}
if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ){
SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );}}
clean_up();
return 0;}
Upvotes: 4
Views: 7018
Reputation: 1245
You need to apply some very basic physics. There are libraries available, but for a simple case like this, only minimal code is required.
You already have xVel and yVel variables. When UP key is pressed, box "jumps" up, you add value to yVel. Gravity is a constant force that affects velocity of the box. Box accelerates downwards. In practice you apply gravity to velocity in every frame in move() function.
yVel -= GRAVITY; // Zero in space, small value on Moon, big value on Jupiter
box.y += yVel;
Notice that when box hits the floor, you reverse the y-velocity. This is fine, but with gravity applied, gravity will accumulate and box ends up bouncing with ridiculous speed. Box should lose "energy" when it bounces, so velocity might be cut to half for example.
Problem in your code is that you are using integer values. Gravity is a weak force, so to make it look real, it should be small value. With integer values, this value might be impossible to find. (Without toying around with fixed point values, but that's an another topic.)
So consider changing xVel and yVel to floats. Of course, also position of box should use floats.
For really elaborate solutions with way more accurate physics and taking frame rate in to account, check: https://gamedev.stackexchange.com/questions/15708/how-can-i-implement-gravity
Upvotes: 3
Reputation: 3350
I'm quite sure old-school Mario uses some hand-crafted state-based function for jumping, and nothing remotely resembling real physics or even sine/parabola curves in the pure sense (though they may use such math for the apex of the jump)
The inputs and states you want to consider are something like this: STATE_PRESSED, STATE_DECELERATE, STATE_DESCEND, STATE_FREEFALL.
STATE_PRESSED would do a linear upward movement for as long as the user presses the button or until a timeout occurs. This allows the user to control jump height while capping max jump height. STATE_DECELERATE is usually where the parabolic function comes into play. But keep in mind that many platformers slow the rate of deceleration slightly during this state if the user continues to keep the jump button pressed. You can tell when any game does this if you implicitly find yourself holding the jump button through to the apex of the jump in order to get max height.
STATE_DESCEND is pretty obvious, and again a parabolic function probably suffices. The state should shift to STATE_FREEFALL once a specific speed is reached -- at which point speed no longer increases. Neither of these states is affected by user button presses.
You can roll the last two states together if you want, though at the point where I've made a state machine I prefer keeping the states clearly defined. It can help also for other parts of your game that may want to check the player's current jumping or falling status, for example.
Trivia: The original Mario games on the NES actually used hand-crafted lookup tables due to the old NES CPU being rather ill-equipped to perform anything beyond basic addition/subtraction, but they resemble exponential/parabolic curves well-enough.
Upvotes: 3
Reputation: 409216
You can use a sinus curve for the jump speed. It's quick at first but slows down until the speed is zero at the top of the jump, and then the motion follows the same curve (but opposite, so slow first) down. If only theUp key was pressed you only change the Y position, otherwise if Left or Right was pressed as well then you change X position as well.
If the sprite collides with an object on the way up, before reaching the top of the jump, you reverse the up-down direction immediately. The speed down starts at the same speed as when the sprite collided, and accelerates along the same sinus curve until it's back on the "ground".
The exact equation for acceleration and speed you can experiment with.
Upvotes: 1
Reputation: 8109
you should checkout box2d 2D physics engine which is developed in C++ and is open source. BOX2D
Upvotes: 1