Pacman collision detection question

Started by
6 comments, last by Aardvajk 17 years, 9 months ago
Like a say i am making a 2D pacman game, using c++ and DirectX9. I have a maze that is made from an array and then the tiles are calculated and drawn to the screen, my player sprite is set at a certain position and then drawn and animated. the problem i have is figuring out the collision detection for the player and the walls. //********** Tile Globals //Constants const BLANK_TILE = 0; const NORMAL_PILL_TILE = 1; const POWER_PILL_TILE = 2; const WALL_TILE = 3; static IDirect3DSurface9* g_pTileSurface = NULL; char g_szErrorMsg[256]; char g_Sectors[] = {3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 3,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,2,3, 3,0,3,3,0,3,3,3,3,0,3,0,3,3,3,3,0,3,3,0,3, 3,0,0,0,0,0,3,3,3,0,3,0,3,3,3,0,0,0,0,0,3, 3,0,3,3,3,0,0,0,0,0,0,0,0,0,0,0,3,3,3,0,3, 3,2,0,0,0,0,3,0,3,3,3,3,3,0,3,0,0,0,0,2,3, 3,3,3,3,3,0,3,0,0,0,3,0,0,0,3,0,3,3,3,3,3, 0,0,0,0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,0,0,0, 3,3,3,3,3,0,3,0,0,0,0,0,0,0,3,0,3,3,3,3,3, 0,0,0,0,0,0,3,0,3,3,0,3,3,0,3,0,0,0,0,0,0, 3,3,3,3,3,0,0,0,3,0,0,0,3,0,0,0,3,3,3,3,3, 0,0,0,0,3,0,3,0,3,3,3,3,3,0,3,0,3,0,0,0,0, 3,3,3,3,3,0,3,0,0,0,0,0,0,0,3,0,3,3,3,3,3, 3,2,0,0,0,0,3,0,3,3,3,3,3,0,3,0,0,0,0,2,3, 3,0,3,3,3,0,0,0,0,0,3,0,0,0,0,0,3,3,3,0,3, 3,0,0,0,0,0,3,3,3,0,3,0,3,3,3,0,0,0,0,0,3, 3,3,0,3,3,0,3,0,0,0,0,0,0,0,3,0,3,3,0,3,3, 3,0,0,0,3,0,0,0,3,3,3,3,3,0,3,0,3,0,0,0,3, 3,0,3,0,3,0,3,0,0,0,3,0,0,0,3,0,3,0,3,0,3, 3,2,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,2,3, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, }; the rest of the functions are as follows: [Edited by - Prog101 on July 5, 2006 9:09:25 AM]
Advertisement
////////////////////////////////////////////////////// // Place Tile() ////////////////////////////////////////////////////// HRESULT Tile_Place(IDirect3DSurface9* pBackBuffer, IDirect3DSurface9* pTileSurface, int TileNumber, int DstCol, int DstRow, int numTileCols, int numMapCols, int tileSize, int xOffset, int yOffset) { RECT SrcTileRect; POINT DstPoint; SrcTileRect.left = TileNumber2SourceX(TileNumber, numTileCols, tileSize); SrcTileRect.right = SrcTileRect.left + tileSize; SrcTileRect.top = TileNumber2SourceY(TileNumber, numTileCols, tileSize); SrcTileRect.bottom = SrcTileRect.top + tileSize; DstPoint.x = Column2X(DstCol, tileSize, numMapCols) + xOffset; DstPoint.y = Row2Y(DstRow, tileSize) + yOffset; HRESULT rslt = g_pDevice->UpdateSurface(pTileSurface, &SrcTileRect, pBackBuffer, &DstPoint); return rslt; } ////////////////////////////////////////////////////// // ColumnRow2Sector() ////////////////////////////////////////////////////// int ColumnRow2Sector(int c, int r, int numCols) { return r * numCols + c; } ////////////////////////////////////////////////////// // TileNumber2SourceX() ////////////////////////////////////////////////////// int TileNumber2SourceX(int TileNumber, int numCols, int tileSize) { return (TileNumber - ((int)(TileNumber / numCols)) * numCols) * tileSize; } ////////////////////////////////////////////////////// // TileNumber2SourceY() ////////////////////////////////////////////////////// int TileNumber2SourceY(int TileNumber, int numCols, int tileSize) { return ((int)(TileNumber / numCols)) * tileSize; } ////////////////////////////////////////////////////// // Column2X() ////////////////////////////////////////////////////// int Column2X(int col, int tileSize, int numCols) { return (col - ((int)((col / numCols)) * numCols)) * tileSize; } ////////////////////////////////////////////////////// // Row2Y() ////////////////////////////////////////////////////// int Row2Y(int r, int tileSize) { return r * tileSize; } 


and in the render function:

//****************************************Render the tiles to the screen for (int Row = 0; Row < MAPROWCOUNT; ++Row) { for (int Col = 0; Col < MAPCOLUMNCOUNT; ++Col) { int iSector = ColumnRow2Sector(Col, Row, MAPCOLUMNCOUNT); int TileNumber = g_Sectors[iSector]; rslt = Tile_Place(g_pBackSurface, g_pTileSurface, TileNumber, Col, Row, COLUMNSINTILEFILE, MAPCOLUMNCOUNT, TILESIZE, XOFFSET, YOFFSET); if (FAILED(rslt)) { strcpy(g_szErrorMsg, "Error drawing tiles."); PostQuitMessage(WM_QUIT); } } } 

and this is how i create and draw the player and the tiles in the init function

//initialize the tiles	rslt = g_pDevice->CreateOffscreenPlainSurface(64, 64, D3DFMT_X8R8G8B8,D3DPOOL_SYSTEMMEM,&g_pTileSurface,NULL);	if (FAILED(rslt))	{				strcpy(g_szErrorMsg, "Error creating bitmap surface.");				PostQuitMessage(WM_QUIT);	}	rslt = D3DXLoadSurfaceFromFile(g_pTileSurface, NULL, NULL,		"./textures/Image.bmp", NULL, D3DX_DEFAULT, D3DCOLOR_RGBA(0,0,0,0), NULL);	if (FAILED(rslt))	{				strcpy(g_szErrorMsg, "Couldn't load bitmap file.");				PostQuitMessage(WM_QUIT);	}	//Create our main Player sprite	D3DXCreateTextureFromFileEx(g_pDevice,  "./textures/PacManSprite.bmp", D3DX_DEFAULT, D3DX_DEFAULT,		D3DX_DEFAULT, D3DUSAGE_RENDERTARGET, D3DFMT_UNKNOWN,	D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT,	D3DCOLOR_XRGB(0,0,0), NULL, NULL, &g_pPlayerSpriteTexture);	//Set the rect to crop the 1st image of the player	player.right = 32; player.left = 0; player.top = 0; player.bottom = 32;	g_Player.SetSourceRect(player);		if(FAILED(rslt))	{		return FALSE;	}	//// Create our sprite, setting the color key and translation vector	g_Player.SetColourKey(g_pDevice, D3DCOLOR_XRGB(243,0,0));	g_Player.SetTranslation(D3DXVECTOR2(500.0f, 658.0f));	rslt=g_Player.Load(g_pDevice, g_pPlayerSpriteTexture);	if(FAILED(rslt))	{		return FALSE;	}	// set the frame data	g_Player.curFrame = 0;	g_Player.numFrames = 4;	// set the move data	g_Player.moveX = 50.0;	g_Player.moveY = 50.0;



[Edited by - Prog101 on July 5, 2006 8:31:46 AM]
Are you storing your player position only in screen co-ords?

As pacman is a tile based game, if you store your player location in world space then checking collision is trivial. To display the player you translate from world space to screen space, again this operation is fairly straight forward as the background in pacman doesnt move so that you know 0,0 will always be at 32,32 on the screen (for example), then if the player is at 5,2 and each tile is 32 by 32 pixels the translation from world space to screen space is <((PlayerX * TileWidth) + TileMapOriginX),((PlayerY * TileHeight) + TileMapOriginY)>, where TileMapOriginX and Y are the location of 0,0 in this case, so world location 5,2 would equate to screen location 192, 96 in this example.

Sorry for the rant if you already knew this, but if you ARE storing your player location in world space then you just look up the location in the array of walls to check for a collision.
What i want to happen exactly is for the player to come to any wall in the array and for the player to stop and not be able to go through it?
Here's my $.02.

I would represent the player, walls, pellets and ghosts (assuming the usual Pacman cast of characters) using axis-aligned bounding boxes. For the player, pellets and ghosts these would be used to determine intersection for the purpose of eating, being eaten, and so forth. For the walls, you would use a simple static separating axis test to prevent the player from passing through obstacles. Although 'separating axis test' may sound somewhat technical if you're not already familiar with it, for AABBs the test is quite simple and reduces to just a few lines of code.

I'm pretty sure this approach would give you exactly the behavior you're after (or at least behavior that is typical for a Pacman-type game).
I'd like to point out an often overlooked feature of PacMan collisions:

if you press a button to make a turn, but do it too early so that you hit a wall, then PacMan continues moving forward while 'leaning' against that wall until it is possible to make the direction change.
(basically it remembers your last requested direction change and keeps trying if it failed)
This makes sharp turns into narrow hallways easier (all of them are narrow).

Without this feature it becomes nearly impossible to get the perfect keypress timing to make PacMan change direction.
As the poster above mentions, Pac-Man movement and collision actually turns out to be quite a complex problem (kind of).

Here's how I did it (note: I was using tile maps):

The player needs to keep track of 3 things:
- Current Position
- Current Direction
- Target Direction

Each frame the position is changed by the current direction. The target direction changes when ever the player presses a direction key. This is needed for the reasons descibed by the poster above.

Now, every time the player's position lands on or passes over the center of a tile I check if the tile in the target direction of the player is open (ie. isn't a wall). If it is not, the the Current Direction becomes the Target Direction.

The are however a few edge cases that ugly the code up a bit:

- What happens when their are walls in the target direction and the current direction?
- What happens if the press in the oppositie direction that we are moving?

I have the code at home (I'm at work atm), so if you are still having trouble I can post it (it was written in Java for cellphones though, but it should translate to any language quite easily).

Matt
__________________________________[ Website ] [ Résumé ] [ [email=contact[at]matthughson[dot]com]Contact[/email] ][ Have I been Helpful? Hook me up! ]
Another pacman approach is to make the pacman move a whole map square each move, even if this is done a pixel at a time, so wherever it stops, it is always centrally aligned on a given map square.

This solves the turning-corners problem another way and can also simplify stuff like route-finding for the ghosts and checking for pacman-to-ghost and ghost-to-ghost collisions.

This topic is closed to new replies.

Advertisement