![]() |
Platforms, Frameworks & Libraries »
Mobile Development »
Games
Intermediate
Bubble.NET GameBy BenoitMA Puzzle Bobble (aka Bust-A-Move) clone for Pocket PC using the .NET compact framework |
C#, Windows, .NET CF, .NET, MobileVS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
This is my contribution to the CodeProject compact framework contest. (and my first CodeProject article !)
I always loved the original Puzzle Bobble game. It's simple, yet addictive. Many variants are playable on many platforms, but the only clones I saw for Pocket Pc were not free. So why not create a free and open source .Net compact framework variant of this famous game ? I started this project from scratch at the beginning of April, when I saw that CodeProject organized a contest, but at the end of April, it wasn't finished. It was functional, but did not have any graphics, and only had a random level generator... Happily, they extended the deadline, so I had time to improve the whole thing :). Anyway, a lot of things could be enhanced further more:
It's worth to be noted that I currently do not own a Pocket PC, I borrowed my boss' ppc at the beginning of the project (thank Patrick !) just to see if the animation speed would be decent, but after that I only used the emulator, so I hope that it will perform nicely on a 'real' pocket pc ! In the 'Menu' button on the game screen, you'll find a 'Constant frame rate' entry, uncheck it if the game is too slow...
The main idea is based on the famous Puzzle Bobble game: the goal of the game is to destroy every bubble on the board. You have a limited time to launch a colored bubble on the board. The bubble will 'stick' on the first encountered bubble. If there's more than three bubble of the same color on the board, they explore, and every bubble that's not attached will fall as well. The ceiling will get down every 8 launched bubbles. You lose a live when a bubble reach the deadline (the red line).
There's a class for each important part of the game:
BubbleGame : This is the main class of the game, it
contain all the high-level features and game logic (most of it in the 'Go
' method)
Bubble
: This class represents a bubble on the in-memory game board, it hold some
useful info like the kind of bubble (its color), the position of the bubble on
the game board, and the rectangle of the bubble on the screen (the position of
the bubble sprite on the screen)
BubbleImages
: In this class, you'll find some static methods and fields that act like a
Bitmap cache of the Bubble images.
LauncherImages : Just like the BubbleImages
class, this class act as a Bitmap cache for the Launcher bitmaps.
BubbleSprite
: Represents a moving bubble (player bubble, falling and exploding bubbles),
this class keeps track of the direction and speed of each bubble, as well as
its position on screen and on the game board.
MovingSprites
: This class will hold all the destroyed and falling bubbles while they are
moving on screen.
GameBoard : This class represents the in-memory game
board. It has methods for loading a level in memory, for detecting Player
bubble collisions, detecting bubble neighbors and falling bubbles, as well as
detecting if a bubble has reached the deadline. It has a method called 'CreateBoardBitmap
' that will create a bitmap from the in-memory game board (with complete
background, walls, ceiling, and fixed bubbles). This bitmap is cached and used
during the game (by the BubbleGame
class) as the background, so we only have to draw the moving bubbles, not the
fixed ones.
AngleHelper
: This helper class is used to calculate the movement of the player bubble
sprite from its angle and distance.
GraphicsHelper
: This helper class contains static members used by various GDI+ methods (some
of them are only used for testing purpose...)
GXInput : This class come from a MSDN sample game
project, and is very useful to register all the hardware keys of the pocket pc.
Otherwise, when you press an hardware key, an application could be started and
appears on top of the game window...
One of the first problem I had on this project, was to handle the key presses more accurately than the key related events of the winforms...
Happily, there's a function in 'coredll.dll' that satisfied this need:
[DllImport("coredll.dll")]
public static extern int GetAsyncKeyState(int vkey);
We can use this function in the main game loop (the 'Go'
method of the 'BubbleGame' class):
if ((GetAsyncKeyState((int)System.Windows.Forms.Keys.Left) & 0x8000) !=0)
angle+=2; //x--;
if ((GetAsyncKeyState((int)System.Windows.Forms.Keys.Right) & 0x8000) !=0)
angle-=2; //x++;
This technique was used at the very beginning of this project, but
meanwhile, I read a series of excellent articles on MSDN that talked about game
development on Pocket PC with the .Net Compact Framework, and the author
addressed another issue: intercepting the hardware keys. Those keys are like
hot keys: they launch or activate an application directly. The feature is
useful in day-to-day use, but is very annoying for games, because sometimes we
want to use those keys for specific tasks. But we can't without some cumbersome
DllImport and P/Invoke ... That's where the GXInput is useful: it
comes from the sample game of a MSDN article (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/WrapGAPI3.asp)
, and enable us to register the hardware keys, and use them in our application
! In fact the GXInput do more than registering keys, but since I added this
library very lately in my project I did not use any other feature...
Otherwise, I had to work around a strange problem: in the Main game form, I
wanted a menu to appears at the bottom left corner. The form itself was
configured to fill the whole screen (MinimizeBox=false,
MaximizeBox= false, WindowState= Maximized). Without the menu, the
form was displayed as expected, but as soon as I added a menu, the window was
no longer in fullscreen mode (the top title, and the Start menu appeared, even
if I set the ControlBox as false...)
I worked around this problem by adding a Button at the bottom-left corner of
the form and by assigning a context menu that is displayed when we click on the
button...
The level design is saved as plain text file (I created a 'codeproject.lvl' sample file with ten levels), you can create your own levels by editing the sample file, or by creating new files (those files must have a .LVL extension), and they must be copied in the game directory. After that you can select the level file on the main menu.
The structure of the file is very simple: here is the first level:
[Level]
06,06,08,08,02,02,03,03
06,06,08,08,02,02,03,00
02,02,03,03,06,06,08,08
02,03,03,06,06,08,08,00
Each level begins with '[Level]', after that, we have a series of numbers indicating the bubble color of each cell:
00 = no bubble
01 = Black
02 = Blue
03 = Green
04 = Magenta
05 = Orange
06 = Red
07 = White
08 = Yellow
Each line can have up to 8 numbers, and each level can have up to 10 lines (such a level is hard to finish, because the last line is very near the deadline !!!). In future releases, there will be a level editor that will enable you to create levels more easily...
The GameBoard class contains an array of Bubble:
private Bubble[][] gameBoard = new Bubble[GRID_X_SIZE][];
Each Bubble has a kind (a color) and a position on grid, those bubbles are created
in the LoadLevel method of the GameBoard
class.
int[,] BubbleData= this.GetLevelData(LevelFile,LevelNum);
for (int x=0;x<GRID_X_SIZE;x++)
for (int y=0;y<GRID_Y_SIZE;y++)
{
if (x!=GRID_X_SIZE-1 || y%2==0)
{
if (BubbleData[x,y]!=0)
this.gameBoard[x][y] = new Bubble(x,y,(BubbleKind)BubbleData[x,y]);
}
}
Each time a bubble is created, its position on grid is specified in the
constructor, as well as its kind. From its position on the grid (the
GameBoard), we can calculate its position on screen (the
Rectangle containing the bubble on the game screen). The
UpdateBubbleRectangle method of the Bubble class update this
Rectangle if needed:
public void UpdateBubbleRectangle()
{ this.bubbleRectangle = new Rectangle(
(int)((this.gridX*this.bubbleBitmap.Width)+
GameBoard.X_POS_OFFSET +((this.bubbleBitmap.Width/2)
* (this.gridY%2))),
(int)(this.gridY*(this.bubbleBitmap.Height*GameBoard.ROW_DIST)
+GameBoard.Y_POS_OFFSET),
this.bubbleBitmap.Width, this.bubbleBitmap.Height); }
The on-screen position of the bubble is stored in a Rectangle with the following bounds:
bubbleBitmap.Width), then adding the
GameBoard X offset (the position of the gameboard on the screen), and finally
adding half the width of a bubble only if this bubble is on an odd row.
bubbleBitmap.Height*GameBoard.ROW_DIST), so each row 'overlaps' a little bit
the preceding row, and finally adding the GameBoard Y offset (this offset will
change each time the ceiling fall by one row - that is, every 8 fired bubbles).
CreateBoardBitmap()
of the GameBoard class is called and returns a
Bitmap object. This Bitmap is used as the 'full background' of the
game, since it's made up of the 'simple background' (the ceiling, floor, walls,
and background image) plus the images of the remaining bubbles of the current
level. This 'full background' will only be refreshed if the player bubble hit
another bubble and stick on the board, or if, after a collision, some bubbles
pops out or fall.
public Bitmap CreateBoardBitmap()
...
for (int x=0;x<GRID_X_SIZE;x++)
for (int y=0;y<GRID_Y_SIZE;y++)
{
oneBubble= this.gameBoard[x][y];
if (oneBubble!=null)
{
oneBubble.UpdateBubbleRectangle();
// update the bubble rectangle in case of board shift down
gameBoardGraphics.DrawImage(oneBubble.BubbleBitmap,
oneBubble.BubbleRectangle,0,0,
oneBubble.BubbleBitmap.Width, oneBubble.BubbleBitmap.Height ,
GraphicsUnit.Pixel,
BubbleImages.GetTranspImageAttr());
}
}
...
return gameBoardBitmap
On this 'full background' the launcher and the player sprite are drawn (as
well as falling and destroyed bubbles). The player sprite is managed by the
BubbleSprite class. This class has a BubbleRectangle
property that returns the rectangle containing the bubble on screen. (just like
the Bubble class, this property is used to know where to
paint the sprite on screen). It also has a PositionOnGrid
property that returns a Point which represents the
position of the sprite on the GameBoard. (it's the opposite of the UpdateBubbleRectangle
method of the Bubble class, and is primarily used in the
collision detection routine)
This detection occurs each time the player bubble moves, but is done in two phases:
PositionOnGrid method is used, as well as an helper
method named GetNeighbors, from the GameBoard
class, which returns an ArrayList
of the neighbors bubbles.
CheckSpriteCollision method of the
GameBoard class.
This test is done by calculating the distance between the player sprite and its neighbors:
dist = (Sprite.BubbleRectangle.X-OneBubble.BubbleRectangle.X)
*(Sprite.BubbleRectangle.X-OneBubble.BubbleRectangle.X)+
(Sprite.BubbleRectangle.Y-OneBubble.BubbleRectangle.Y)*
(Sprite.BubbleRectangle.Y-OneBubble.BubbleRectangle.Y);
If the distance is less than a specific amount, the player bubble must be added
on the gameboard.
After the player bubble is added to the gameboard, we need to test if this
bubble is surrounded by at least two bubble of the same kind, if it's the case
those bubbles must be destroyed. This test is done by the GetSameKindNeighbors
method of the GameBoard class, by calling the
GetNeighbors method for each bubble of the same kind as the player
sprite. Once all the same kind neighbors bubbles of the player bubble are
found, they are removed from the GameBoard and added to
the MovingSprites collection. This collection is used to
keep track of the falling or exploded bubbles, and to animate them.
If some bubbles were destroyed, we need to test if they leaved some 'floating'
bubbles on the GameBoard. The 'floating' bubbles are detected by recursively
calling GetNeighbors for each bubble of the first row
and marking them as 'FallChecked', so we know that they must remain on the
GameBoard. This is the role of the GetFallingBubbles() method
of the GameBoard class. Every other bubble is removed
from the GameBoard, and added to the MovingSprites
collection, for the falling animation effect.
Beside this, there's nothing really special: double-buffer was used to prevent flicker (there's a lot of articles that detail the use of double-buffering technique, so I won't dive into it...), and GDI+ was used to draw the sprites on screen...
And please, give me some feedback if you tested this game on a real Pocket PC, so i can fine-tune the code... Thanks !
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 14 Jun 2004 Editor: Nishant Sivakumar |
Copyright 2004 by BenoitM Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |