sdl 1.2 pacman problems

Started by
12 comments, last by Brain 8 years, 2 months ago

Ok, guys, I'm making a progress with my Pacman game. I now made the map using tiles and reading from file, and I made the collision. But now I have another problem.

The problem is the following:

In the original Pacman game, as you know, when you press an arrow key to make a turn AND you can't make the turn right now ( because there are walls around you ), the game SAVES your last pressed arrow key AND after 1 or 2 seconds, when you can actually make the turn, it makes it for you, depending on the last key you pressed. See the pictures below:

[attachment=30361:Pac_Before.png] [attachment=30362:Pac_After.png]

Ok, 1st step is to save the last pressed arrow key. I made an int arrow which saves the arrow keys like this:
if( SDLK_RIGHT ) { arrow = RIGHT }

if( SDLK_LEFT ) { arrow = LEFT } etc...

So now my last arrow key is saved.

Now I wrote this.

if ( hit_wall() == true )

{

if ( arrow = RIGHT )

{

velocityX = SPEED_NORMAL;

velocityY = 0;

}
etc... for all the other key presses
So now it will change the direction later when there is a space to make the turn, BUT....

But now the problem is that the ONLY time pacman makes a turn is when there is a collision.

Ok, see the left picture is when Up is PRESSED.

[attachment=30363:pac_bugged_before.png] [attachment=30364:pac_bugged_after.png]

So this is definitely not the solution.

I have the SDL_Rect get_tilebox() function which returns the tile rect, but when I actually call it, how can I check tilebox.x and tilebox.y.
I have ideas but the code is very very ugly, so I need something simple and efficient. so...as you can see by my username, I need Heelp!!!

( Here is a link to the original game - http://www.webpacman.com )

and here is all my code ( You want to see the pacman::movement stuff and pacman::handle_events maybe )


 
//The header files
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include "SDL/SDL_ttf.h"
#include "SDL/SDL_mixer.h"
#include <string>
#include <iostream>
 
//Screen dimensions
const int SCREEN_WIDTH = 630;
const int SCREEN_HEIGHT = 630;
const int SCREEN_BPP = 32;
 
//The frames per second
const int FRAMES_PER_SECOND = 100;
 
//Pacman stuff
const int PAC_DIAMETER = 23;
const int SPEED_NORMAL = 1;
 
//The direction status of the Pac
const int PAC_RIGHT = 1;
const int PAC_DOWN = 2;
const int PAC_LEFT = 3;
const int PAC_UP = 4;
 
//The different tile sprites
const int TILE_BLACK = 0;
const int TILE_BLUE = 1;
 
//The different TYPES of tiles
const int TILE_SPRITES = 2;
 
//Tile dimensions and number
const int TILE_WIDTH = 30;
const int TOTAL_TILES = 441;
 
SDL_Surface *screen = NULL;
SDL_Surface *tileSheet = NULL;
 
//In-game sprite surfaces
SDL_Surface *pacRight = NULL;
SDL_Surface *pacDown = NULL;
SDL_Surface *pacLeft = NULL;
SDL_Surface *pacUp = NULL;
 
//The event structure
SDL_Event event;
 
//
SDL_Rect clips[ TILE_SPRITES ];
 
//The font
TTF_Font *font = NULL;
 
//The color of the font
SDL_Color color[2] = { { 255, 255, 255 }, { 255, 0, 0 } };
 
class Tile
{
private:
    //The attributes of the tile
    SDL_Rect tilebox;
 
    //The type of tile
    int type;
 
public:
    //Initialize the variables
    Tile( int x, int y, int tileType );
 
    //Show the tile
    void show();
 
    //Get the tile type
    int get_type();
 
    //Get the collision box
    SDL_Rect get_tilebox();
 
};
 
class Pacman
{
 
private:
    //The Rect of Pacman
    SDL_Rect pacbox;
 
    //The offsets
    float offsetX;
    float offsetY;
 
    //The speed of moving
    float velocityX;
    float velocityY;
 
    //The position of pacman
    int arrow;
    int turnpac;
    int walkstatus;
    int transition;
 
 
public:
 
    //The constructor
    Pacman();
 
    //Handle input
    void handle_events();
 
    //Change the coordinates of Pacman when moving
    void movement( Tile *tiles[] );
 
    //Handles Pacman animation when walking
    void walk_anim();
 
    //\ Pacman on screen
    void show();
 
};
 
bool set_tiles( Tile *tiles[] )
{
    //The tile offsets
    int x = 0, y = 0;
 
    //Opening the text file with the 0s and 1s
    FILE* filePointer;
    filePointer = fopen("Pics/pacmanWholemap.txt", "rt");
 
    if( filePointer == NULL )
    {
        return false;
    }
 
    int t = 0, counter = 0, tileType = 0;
 
    //While going through all the characters...
    while( ( t = getc( filePointer ) )!= EOF )
        //If it sees zero, put 1st tile
    {
        if( t == '0' || t == '1' )
        {
            //Make the type of the tile = 0
            tileType = t - '0';
            tiles[ counter ] = new Tile ( x, y, tileType );
            counter++;
            if( counter == TOTAL_TILES )
            {
                break;
            }
 
            //Move to the next tile coordinates
            x += TILE_WIDTH;
 
            //If you are out of screen
            if( x >= 21*TILE_WIDTH )
            {
                x = 0;
                y += TILE_WIDTH;
            }
 
            if( y >= 21*TILE_WIDTH )
            {
                return true;
            }
        }
    }
}
 
class Timer
{
    private:
    //The clock time when the timer started
    int startTicks;
 
    //The timer status
    bool started;
 
    public:
    //Initializes variables
    Timer();
 
    //The various clock actions
    void start();
    void stop();
 
    //Gets the timer's time
    int get_ticks();
 
    //Checks the status of the timer
    bool is_started();
};
 
Timer::Timer()
{
    //Initialize the variables
    startTicks = 0;
    started = false;
}
 
void Timer::start()
{
    //Start the timer
    started = true;
 
    //Get the current clock time
    startTicks = SDL_GetTicks();
}
 
void Timer::stop()
{
    //Stop the timer
    started = false;
}
 
int Timer::get_ticks()
{
    //If the timer is running
    if( started == true )
    {
        //Return the current time minus the start time
        return SDL_GetTicks() - startTicks;
    }
 
    //If the timer isn't running
    return 0;
}
 
bool init()
{
    //Initialize all SDL subsystems
    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
    {
        return false;
    }
 
    //Set up the screen
    screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );
 
    //If there was an error in loading the screen
    if( screen == NULL )
    {
        return false;
    }
 
    //Initialize SDL_ttf
    if( TTF_Init() == -1 )
    {
        return false;
    }
 
    //Set the window caption
    SDL_WM_SetCaption( "Pacman", NULL );
 
    //If everything initialized fine
    return true;
}
 
SDL_Surface * load_image( std::string filename )
{
    //The image that's loaded
    SDL_Surface* loadedImage = NULL;
 
    //The optimized surface
    SDL_Surface* optimizedImage = NULL;
 
    //Load the image
    loadedImage = IMG_Load( filename.c_str() );
 
    //If the image loaded
    if( loadedImage != NULL )
    {
        //Create an optimized surface
        optimizedImage = SDL_DisplayFormat( loadedImage );
 
        //Free the old surface
        SDL_FreeSurface( loadedImage );
 
        //If the surface was optimized
        if( optimizedImage != NULL )
        {
            SDL_SetColorKey( optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( optimizedImage->format, 0, 0, 0 ) );
        }
    }
    //Return the optimized surface
    return optimizedImage;
}
 
bool load_files()
{
    //Load the font
    font = TTF_OpenFont( "lazy.ttf", 38 );
 
    //Load the background
 
    //Load the tiles sheet
    tileSheet = load_image( "Pics/blue red.png");
 
    //Load the pacman images
    pacRight = load_image( "Pics/pac_right.png" );
    pacDown = load_image( "Pics/pac_down.png" );
    pacLeft = load_image( "Pics/pac_left.png" );
    pacUp = load_image( "Pics/pac_up.png" );
 
    //If there was a problem in loading the sprites
 
 
    if( pacRight == NULL || pacDown == NULL || pacLeft == NULL || pacUp == NULL )
    {
        return false;
    }
 
    if( tileSheet == NULL )
    {
        return false;
    }
 
    //If everything loaded fine
    return true;
}
 
bool check_collision( SDL_Rect A, SDL_Rect B )
{
    //The sides of the rectangles
    int leftA, leftB;
    int rightA, rightB;
    int topA, topB;
    int bottomA, bottomB;
 
    //Calculate the sides of rect A
    leftA = A.x;
    rightA = A.x + A.w;
    topA = A.y;
    bottomA = A.y + A.h;
 
    //Calculate the sides of rect B
    leftB = B.x;
    rightB = B.x + B.w;
    topB = B.y;
    bottomB = B.y + B.h;
 
    //If any of the sides from A are outside of B
    if( bottomA <= topB )
    {
        return false;
    }
 
    if( topA >= bottomB )
    {
        return false;
    }
 
    if( rightA <= leftB )
    {
        return false;
    }
 
    if( leftA >= rightB )
    {
        return false;
    }
 
    //If none of the sides from A are outside B
    return true;
}
 
void clip_tiles()
{
    //Clip the sprite sheet
    clips[ TILE_BLACK ].x = 0;
    clips[ TILE_BLACK ].y = 0;
    clips[ TILE_BLACK ].w = TILE_WIDTH;
    clips[ TILE_BLACK ].h = TILE_WIDTH;
 
    clips[ TILE_BLUE ].x = TILE_WIDTH;
    clips[ TILE_BLUE ].y = 0;
    clips[ TILE_BLUE ].w = TILE_WIDTH;
    clips[ TILE_BLUE ].h = TILE_WIDTH;
 
}
 
bool hit_wall( SDL_Rect box, Tile *tiles[] )
{
    for( int t = 0; t < TOTAL_TILES; t++ )
    {
        if( tiles[ t ]->get_type() == TILE_BLUE )
        {
            if( check_collision( box, tiles[ t ]->get_tilebox() ) )
            {
                return true;
            }
        }
    }
    //If no collision was found
    return false;
}
 
void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL )
{
    //Holds offsets
    SDL_Rect offset;
 
    //Get offsets
    offset.x = x;
    offset.y = y;
 
    //Blit
    SDL_BlitSurface( source, clip, destination, &offset );
}
 
 
//void close_all( Tile *tiles[] )
void close_all( Tile *tiles[] )
{
    //Free the pacman surfaces
    SDL_FreeSurface( pacRight );
    SDL_FreeSurface( pacDown );
    SDL_FreeSurface( pacLeft );
    SDL_FreeSurface( pacUp );
 
    //Free the background and tiles surfaces
    SDL_FreeSurface( tileSheet );
 
    //Free the tiles
    for( int t = 0; t < TOTAL_TILES; t++ )
    {
        delete tiles[ t ];
    }
 
    //Close Font
    TTF_CloseFont( font );
 
    //Quit SDL_ttf
    TTF_Quit();
 
    //Quit SDL
    SDL_Quit();
 
}
 
Pacman::Pacman()
{
    //Initialize the Rect variables
    pacbox.x = 63;
    pacbox.y = 33;
    pacbox.w = PAC_DIAMETER;
    pacbox.h = PAC_DIAMETER;
 
    //Initialize movement variables
    velocityX = SPEED_NORMAL;
    velocityY = 0;
    walkstatus = PAC_RIGHT;
 
    arrow = 0;
    transition = 0;
}
 
void Pacman::handle_events()
{
    //If a key was pressed
    if( event.type == SDL_KEYDOWN )
    {
        //Set the velocity
        if( event.key.keysym.sym == SDLK_RIGHT )
        {
            arrow = PAC_RIGHT;
        }
        if( event.key.keysym.sym == SDLK_DOWN )
        {
            arrow = PAC_DOWN;
        }
 
        if( event.key.keysym.sym == SDLK_LEFT )
        {
            arrow = PAC_LEFT;
        }
 
        if( event.key.keysym.sym == SDLK_UP )
        {
            arrow = PAC_UP;
        }
    }
}
 
void Pacman::movement( Tile *tiles[] )
{
    //Move
    pacbox.x += velocityX;
    pacbox.y += velocityY;
 
    for( int t = 0; t < TOTAL_TILES; t++ )
    {
        if( tiles[ t ]->get_type() == TILE_BLACK )
        {
 
        }
    }
 
 
    //Check Pac - walls collision
    if( hit_wall( pacbox, tiles ) )
    {
        if( velocityX != 0 && velocityY == 0 )
        {
            pacbox.x -= velocityX;
        }
 
        if( velocityY != 0 && velocityX == 0 )
        {
            pacbox.y -= velocityY;
        }
 
        if( arrow == PAC_RIGHT )
        {
            velocityX = SPEED_NORMAL;
            velocityY = 0;
 
        }
 
        if( arrow == PAC_DOWN )
        {
            velocityX = 0;
            velocityY = SPEED_NORMAL;
            arrow = 0;
        }
 
        if( arrow == PAC_LEFT )
        {
            velocityX = -SPEED_NORMAL;
            velocityY = 0;
            arrow = 0;
        }
 
        if( arrow == PAC_UP )
        {
            velocityX = 0;
            velocityY = -SPEED_NORMAL;
            arrow = 0;
        }
}
 
    //Keep the Pac in bounds
    if( pacbox.x < - PAC_DIAMETER )
    {
        if( transition == 0 )
        {
            pacbox.x = SCREEN_WIDTH;
            transition ++;
        }
    }
 
    if( pacbox.x > SCREEN_WIDTH )
    {
        if( transition == 0 )
        {
            pacbox.x = -PAC_DIAMETER;
            transition ++;
        }
    }
    else
    {
        if( transition == 1 )
        {
            transition = 0;
        }
    }
 
    if( pacbox.y < 0 || pacbox.y + PAC_DIAMETER > SCREEN_HEIGHT )
    {
        pacbox.y -= velocityY;
    }
 
 
    //Check velocity and set position
    //If Pacman is moving left
    if( velocityX > 0 )
    {
        //Set the animation to left
        walkstatus = PAC_RIGHT;
    }
 
    if( velocityX < 0 )
    {
        //Set the animation to left
        walkstatus = PAC_LEFT;
    }
 
    if( velocityY < 0 )
    {
        //Set the animation to left
        walkstatus = PAC_UP;
    }
 
    if( velocityY > 0 )
    {
        //Set the animation to left
        walkstatus = PAC_DOWN;
    }
}
 
void Pacman::show()
{
    if( walkstatus == PAC_RIGHT )
    {
        apply_surface( pacbox.x, pacbox.y, pacRight, screen );
    }
 
    if( walkstatus == PAC_DOWN )
    {
        apply_surface( pacbox.x, pacbox.y, pacDown, screen );
    }
 
    if( walkstatus == PAC_LEFT )
    {
        apply_surface( pacbox.x, pacbox.y, pacLeft, screen );
    }
 
    if( walkstatus == PAC_UP )
    {
        apply_surface( pacbox.x, pacbox.y, pacUp, screen );
    }
}
 
Tile::Tile( int x, int y, int tileType )
{
    //Get the offsets
    tilebox.x = x;
    tilebox.y = y;
 
    //Set the collision box
    tilebox.w = TILE_WIDTH;
    tilebox.h = TILE_WIDTH;
 
    //Get the tile type
    type = tileType;
}
 
void Tile::show()
{
    //Show the tile
    apply_surface( tilebox.x, tilebox.y, tileSheet, screen, &clips[ type ] );
}
 
int Tile::get_type()
{
    return type;
}
 
SDL_Rect Tile::get_tilebox()
{
    return tilebox;
}
 
int main( int argc, char* args[] )
{
 
    bool quit = false;
 
    Tile *tiles[ TOTAL_TILES ];
 
    //The tiles that will be used
    for( int t = 0; t < TOTAL_TILES; t++ )
    {
        tiles[t] = NULL;
    }
 
    //Initialize the SDL stuff
    if( init() == false )
    {
        return 1;
    }
 
    //Load the files
    if( load_files() == false )
    {
        return 2;
    }
 
    //Clip the tile sheet
    clip_tiles();
 
    //Set the tiles
    set_tiles( tiles );
 
    //The timer
    Timer fps;
 
    //The pacman
    Pacman pacman1;
 
    //While the user hasn't quit
    while( quit == false )
    {
        //While there's events to handle
        while( SDL_PollEvent( &event ) )
        {
            pacman1.handle_events();
            //If the user has pressed Esc or X
            if( event.type == SDL_QUIT || event.key.keysym.sym == SDLK_ESCAPE )
            {
                //Quit the program
                quit = true;
            }
        }
 
        //Moves Pac
        pacman1.movement( tiles );
 
 
        //Apply the tiles as background
        for( int t = 0; t < TOTAL_TILES; t++ )
        {
            if( tiles[ t ] != NULL )
            {
                tiles[ t ]->show();
            }
        }
 
        //Show the Pac on the screen
        pacman1.show();
 
        //Update the screen
        if( SDL_Flip( screen ) == -1 )
        {
            return 1;
        }
 
        //Cap the frame rate
        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( 1000 / FRAMES_PER_SECOND  - fps.get_ticks() - 7 );
        }
    }
 
    //Clean up
    close_all( tiles );
 
    return 0;
}
 
Advertisement

Perhaps instead of using traditional collision consider using a "rail" system and responding to directional input by setting an "intent" direction?

Without having looked at the code I'm guessing that your entity is experiencing a variant of the bullet-through-paper problem, where there's no frame during which he's perfectly aligned with the intersection in order to be able to make his turn.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This is not the reason because const int NORMAL_SPEED is set to 1, so the pacman can go through every possible coordinate. The problem is that I just don't know how to say: Scan for a possible road where you can make a turn, and if there is such a road, make the turn you wanted to make earlier

I know there are many ways you can do these things but if I decided to do a pacman game I would have made much more use of the fact that it's a tile-based game. The way I see it pacman's movement is from the center of one tile to the center of another tile.

Each time pacman reach the center of a tile I would check to see if it is possible to start move pacman in the new direction. If not, then continue in the current direction if possible.

if center of tile has been reached
	if can move in new direction
		start move in new direction
	else if can move in current direction
		continue moving in current direction
	else
		stop

Note that when you check to see if it is possible to move from one tile to the next you don't need to do full-blown collision detection. If you know the coordinates of the tile you're on you can easily calculate the coordinates of the next tile and check what type it is (walkable or not).
Store your pacman position in grid coordinates only. Animation moves pacman between grid coordinates, using interpolation to move between the centre of each grid coordinate.

At any time, you can change direction where permitted so if you're about to hit a ghost you could double back.

Collision is still checked each frame to detect eating of pills, fruit and being killed by a ghost.

I implemented a similar rail system for the crates in my puzzle game last year in C++. The crates move automatically but can have their direction changed at junctions based on player input same basically as you're trying to do here.

Let me know if this helps!

In the original game there was a little more to the controller then what has been presented here. One of my AI projects in collage was to recreate the AI of pac-man. For those interested here is the link I found on the article I used to research the game:

http://www.gamasutra.com/view/feature/132330/the_pacman_dossier.php

Now then if my memory serves me Pac-Man had a small advantage because he could pre-turn before the turn so that he would gain 3-4 pixels on a ghost. This was a small advantage that many players would take advantage so their could either eat a ghost or evade one. So just doing a simple rail detection on Pac-Man isn't fair to the player.

So here is how I did it with my project. I create a grid in the game so that Pac-Man could wonder the map and hit collisions. Once I had the grid made it was a matter of either using an A* algorithm to create a generic path from where I was to the direction I wanted to turn. The hard part is finding the next square in the direction you want to turn but this was done by getting the row or column of the direction I wanted to move. Then just moving in the same velocity of Pac-Man to find the next grid path. Once you calculate the path it should be easy for the game to handle the animation and the turn properly. This is more of a predictive algorithm idea instead of a reactive. If you want some code example let me know I'll try and dig up my old project.

Also as a small optimization on my A* algorithm I only supported 4 direction to be able to move on Pac-Man and 3 on the ghosts. According to the dossier it explains that the ghosts don't reverse their direction.

Ok guys, I finally made it after nearly 4 hours of suffering, but the problem is my CODE IS UGLY AS HELL. I can't even believe it's me who wrote this code. I saw that you wanted from me to implement some rail system and grid.... I tried, but - too hard for me......and this 'interpolation' stuff ( searched google, no idea what this is ),. So I actually made a 'for' loop that checks all the tiles, and if there is a RED tile( I can move on red tiles, walls are blue ), yeah, if there is a RED tile near my coordinates ( not too far and not too close!! ) then I check If there is a intentKey saved ( I called it 'arrow' ) and if the intention is to go on this way, go. I will post my code, if you can just skim through the code and see if you can do it in less ugly way, I'll be grateful.


void Pacman::make_turn( Tile *tiles[] )
{
    //Making a Rectangle that takes the nearest tile coordinates
    SDL_Rect roadbox;
 
    //Goes through all the tiles on the map
    for( int t = 0; t < TOTAL_TILES; t++ )
    {
        //If there is a red tile
        if( tiles[ t ]->get_type() == TILE_RED )
        {
            //Assign this tile's coordinates to SDL_Rect roadbox
            roadbox = tiles[ t ]->get_tilebox();
            
            //Checking if there is a tile above pacman
            //Check if the x coordinates are the same and if the tile is not too far
            if( roadbox.x == pacbox.x && roadbox.y >= pacbox.y - ( 4*TILE_WIDTH )/3 )
            {
                //Check if the tile is not too close
                /*If I don't make this check, the tile on which Pacman is stepping NOW
                gets into the equation and it doesn't work at all as expected */
                if( roadbox.y <= pacbox.y - ( 2*TILE_WIDTH )/3 && arrow == PAC_UP )
                {
                    //If there is a tile above Pacman, not too close, not too far, lower the Y so pacman goes up
                    velocityX = 0;
                    velocityY = -SPEED_NORMAL;
                    break;
                }
            }
 
            //Now following the same logic, I do it for all the other directions
            //If there is a tile below the Pac guy
            if( roadbox.x == pacbox.x && roadbox.y <= pacbox.y + ( 4*TILE_WIDTH )/3 )
            {
                if( roadbox.y >= pacbox.y + ( 2*TILE_WIDTH )/3 && arrow == PAC_DOWN )
                {
                    velocityX = 0;
                    velocityY = SPEED_NORMAL;
                    break;
                }
            }
 
            //If there is a tile on the left of Pacman
            if( roadbox.y == pacbox.y && roadbox.x >= pacbox.x - ( 4*TILE_WIDTH )/3 )
            {
                if( roadbox.x <= pacbox.x - ( 2*TILE_WIDTH )/3 && arrow == PAC_LEFT )
                {
                    velocityX = -SPEED_NORMAL;
                    velocityY = 0;
                }
            }
 
            if( roadbox.y == pacbox.y && roadbox.x <= pacbox.x + ( 4*TILE_WIDTH )/3 )
            {
                if( roadbox.x >= pacbox.x + ( 2*TILE_WIDTH )/3 && arrow == PAC_RIGHT )
                {
                    velocityX = SPEED_NORMAL;
                    velocityY = 0;
                }
            }
        }
    }
}

Yeah, and the code is speed dependant. The speed needs to be divisible by the TILE_WIDTH which is 30 or its gonna miss the if( something==something ) stuff, bullet-through-paper, as someone said earlier. BAD
Moreover, the code works only if you have pressed an arrow before the turn, if you actually haven't pressed anything, and the pacman stops at some particular wall, you can't make a turn now because you missed the if( a == b) stuff, and now you are bugged and you can't move.So it is ugly code and it also doesn't work.

If the player hasn't made choice by the time they reach a junction you could Make one for them. Always choose for example the next clockwise direction so there's some gameplay mechanical pattern to it.

What does original pacman do?
If the player hasn't made choice by the time they reach a junction you could Make one for them. Always choose for example the next clockwise direction so there's some gameplay mechanical pattern to it.

What does original pacman do?

This topic is closed to new replies.

Advertisement