2D games have always been very popular. Many of you must have played 2D games like ‘Super Mario’ in there childhood. In the PC and console world most of the games have progressed to a 3D format. With the advent of mobile gaming the 2D games have returned back with full force. Many people like side scrolling 2D games in fact even more than 3D games. Thus 2D game is an essential part of the mobile gaming arena. In this chapter we will discuss 2D mobile gaming in depth. The reason for concentrating on 2D games is that almost all good mobile games these days are 2D action games.
In this chapter we will first study mobile 2D games in general. We will also discuss about the game engines in different APIs. Features of 2D games
Every 2D game normally contains the following:
· Sprites Sprites are the essential elements of an arcade game. As we have discussed before, sprites are the objects which could move, hit, run fight and do all sorts of things that real world objects do. They can be animated as well as non – animated. There actions may be controllable by the player as well as non-controllable by the player. They may be positive in their action or they may be negative.
Actually, we can compare the sprites to the actors in a film. Thus sprites are the most important part of any arcade game. Without sprites there can be no arcade game. The sprites need to perform many functions and thus are normally programmed as a separate class. Also generally all the frames of a Sprite are contained in one PNG image. The figure shows how this has been done in the game ‘SKJ Gubbare’ that is available commercially including among others Reliance Infocomm’s RWORLD and Tata Indicom’s network. Sprites are normally of the following types:
o Main Hero Sprite o Supporting Character Sprites o Main Villain Sprite o Secondary Villain Sprites o Tertiary Villain Sprites
· Story The story is a very important component of a game. In the storyboard we try to define the role, importance and characteristics of each stage and sprite in the game. The following questions help to define the story better: · What is the aim of the main character in the game? · What is the starting point of the game? · What is the ending point of the game? · Which are the different stages in the game? · What is the starting point of each stage in the game? · What is the ending point of each stage in the game? · What are the main scenes within each stage? · Which sprites are involved in each scene in a stage? · What will be the special features of background of each stage? · What will be the unique things in the game that will make the game interesting?
Getting answers to these questions is very important before starting the programming and art work development for a game.
· Multiple Stages Every game normally has multiple levels. Each level has to have something exciting about it in order for the player to keep playing it. The levels should be designed keeping in mind the over all objective of the game. The thing to keep in mind, particularly when designing arcade games on mobile phones, is that the resources are highly constrained. On many devices the maximum possible memory for a game is 64 kb memory. In some black and white handsets the maximum application size has to be restricted to 32 kb. Thus the design should be simple and the art work should be creatively re-used as much as possible. Any game should have more than one level in order for the user to be truly involved. Crossing a level is one of the biggest carrots a player can have for playing the game. The background may remain the same or change according to the demand of the story. Either way the main point is that there should be at least 3 to 4 levels to make the game exciting. There are currently games in the market, which have more than 12 levels. Thus the number of levels should be kept at the maximum possible according to your time and budget. Careful planning has to be done for each stage. We should ordinarily break every stage into different scenes. We should then plan each scene individually. This process should be repeated for each stage. For each scene we should define the following: · Scene entry · Scene exit · Entertainment value · Sprites involved in a scene · Scene background After defining each scene in a stage we should carefully plan there sequence within a stage. We should plan them in such a way that the continuity of the stage does not break. It is here that you should plan for warp holes. What are Warp Holes? Warp holes are those areas which let a character to jump to a particular place in another stage without going through the process of finishing all intermediate stages. // Photo for multiple scenes in multiple stages with warp holes
· Background Background consists of the trees, clouds, the road on which sprites move etc. These may be animated as well as static and do not take as much resources as a sprite normally requires. They are in fact the sets of our games. As we discussed earlier the background should be decided for each scene and stage. Careful consideration needs to be given to the background planning. We should also keep in mind that a particular game will have to be ported to different handsets with different screen sizes. Thus we should take that also into consideration while designing the backgrounds. Generally in 2D games the background is composed of a set of tiles that are repeatedly used. The image strip below contains the 5 tiles that are repeatedly used in ‘SKJ Gubbare’.
· Goal The characters of the games normally have certain characteristics which help in defining the goal and bring an element of fighting spirit within the player. Simple things such as points can increase the level of fulfillment a player has from the game. Thus points should normally be included. Like a voyage without destination is useless so is a game without a goal.
Certain times the story can be made more interesting by introducing stage wise secondary goals. For example collecting a key at the end of every stage could make the stage more interesting.
· Resources Resources are an integral part of every game. The main character can have the following types of resources: · Points · Multiple chances · Energy levels · Weapons
Even the villain sprites can have the following: · Multiple chances · Energy levels · Weapons
When planning for player and villain resources we should keep in mind the entertainment value that a player will derive from them. A villain sprite having a large store of energy will make the things difficult for the player and increase excitement! Thus generally to make the game difficult the same villain sprites could have more resources in advanced stages.
Technical Details In this section we will discuss the technical aspects of a game engine. We will discuss in detail each technical aspect from the point of view of MIDP 2.0, MIDP 1.0 and Nokia UI API (In case required).
· Sprite Sprites have certain characteristics. These characteristics determine the technical scope of the actions of sprites. These characteristics are just indicative and certain characteristics can be added or deleted according to the nature of the game. The sprites usually have the following characteristics:
§ Position § Movement § Animation § Collision § Power variables
Since sprites are complex entities we generally write a custom class in MIDP 1.0 for them. This helps in reducing the complexity of the actual gaming code. MIDP 2.0 has gaming API which contains the Sprite class. For the benefit of readers who are interested in programming for MIDP 1.0 I have included the code for the Sprite class in this section. We will also discuss the various variables and methods in our custom Sprite class. This sprite class includes full scale industry level code and could be used without modification in any of your practical projects. I would even recommend those readers who are primarily interested in MIDP 2.0 game development to read this section as it will enhance there understanding of the behavior of sprites.
Sprite Class The Sprite class discussed here is the sub class of Layer. The class Layer has been created in order for the TiledLayer class to extend the Layer class. The TiledLayer class is used for background functionality. We will discuss this class in detail later on when we will cover the topic of background image programming.
/* * Layer.java * * Created on May 20, 2002, 4:44 PM */
import javax.microedition.lcdui.*;
/** * * @author Saurabh Jain * @version 1.0.0 */ public abstract class Layer { // Declaring all the private variables private int x ; private int y ; private int width ; private int height ; private boolean visible ;
/***********************************/ // Getter methods /***********************************/ public int getX() { return this.x ; }
public int getY() { return this.y ; }
public int getWidth() { return this.width ; }
public int getHeight() { return this.height ; }
public boolean getVisible() { return this.visible ; }
/***********************************/ // Setter methods /***********************************/ public void setVisible(boolean visible ) { this.visible = visible ; }
public void setWidth(int width) { this.width = width ; }
public void setHeight(int height) { this.height = height ; }
public void setPosition( int x, int y ) { this.x = x ; this.y = y ; }
/***********************************/ // Other methods /***********************************/ public boolean isVisible() { return this.visible ; }
public void move( int x, int y ) { this.x += x ; this.y += y ; }
public abstract void paint( Graphics g ) ; }
/* * Sprite.java * * Created on May 20, 2002, 4:45 PM */
import javax.microedition.lcdui.*;
/** * * @author Saurabh Jain * @version 1.0.0 */ public class Sprite extends Layer { // Declaring the Instance variables private Image image ; private int frame ; private int state ; private Sprite sprite ;
public Sprite( Image img, int frWidth, int frHeight ) { this.image = img ; this.setWidth(frWidth) ; this.setHeight(frHeight) ; }
// Bounding box collision public boolean collidesWith(Sprite sprite ) { if(this.getVisible() == false || sprite.getVisible() == false) { return false ; }
int lx = this.getX() ; int ly = this.getY() ; int lwidth = this.getWidth() ; int lheight = this.getHeight() ;
int spx = sprite.getX() ; int spy = sprite.getY() ; int spwidth = sprite.getWidth() ; int spheight = sprite.getHeight() ;
if (lx <= spx && ly <= spy && lx + lwidth >= spx && ly + lheight >= spy) { return true ; } else if (lx <= spx + spwidth && ly <= spy && lx + lwidth >= spx + spwidth && ly + lheight >= spy) { return true ; } else if (lx <= spx && ly <= spy + spheight && lx + lwidth >= spx && ly + lheight >= spy + spheight) { return true ; } else if (lx <= spx + spwidth && ly <= spy + spheight && lx + lwidth >= spx + spwidth && ly + lheight >= spy + spheight) { return true ; } else { return false ; } } /***********************************************/ // Getter methods /***********************************************/ public int getFrame() { return this.frame ; }
public int getState() { return this.state ; }
/***********************************************/ // Setter methods /***********************************************/ public void setFrame( int fr ) { this.frame = fr ; }
public void setState( int state ) { this.state = state ; }
public void paint(Graphics g) { if(this.getVisible() == true) { int lx = g.getClipX() ; int ly = g.getClipY() ;
int lWidth = g.getClipWidth() ; int lHeight = g.getClipHeight() ;
g.setClip( this.getX(),this.getY(),this.getWidth(),this.getHeight()) ; g.drawImage( this.image,this.getX() -( frame * this.getWidth()), this.getY(), Graphics.TOP | Graphics.LEFT ) ; g.setClip( lx,ly,lWidth,lHeight ) ; } } }
Sprite Class Variables In this sub-section we will discuss the variables of the sprite class. Sprites usually have the following variables:
Image Image refers to the image file that represents the graphics of the sprite. These graphics should be carefully worked upon in order to make the game more entertaining. MIDP 1.0 supports only PNG (Portable Network Graphics) files. So all your artwork should always be converted into PNG format first in order to be used. Also care should be taken to make sure that the PNG image taken is a 32 bit image. If your image is a 24 bit PNG image, you code will throw an exception.
Another thing that should be carefully worked out is the number of frames your sprite will have. In order to save space it is recommended to insert all the frames in one PNG file in one line. Every frame should normally be of equal width and height. Saving all the frames of a particular sprite in one file usually saves a lot of precious space as the header information of PNG file is then commonly used by all the frames.
This is a picture of how you should save your sprites: In this example the fish is having 3 frames. We will use the setFrame() method which we have discussed later on in this chapter for setting the correct frame for the sprite from this picture.
X position X position refers to the current x co-ordinate of the sprite. This is used by the paint method and the collision detection methods. A change in this variable moves the sprite horizontally.
Y position Y position refers to the current y co-ordinate of the sprite. This is used by the paint method and the collision detection methods. A change in this variable moves the sprite vertically.
Width Width refers to the width of the frames. Normally it is recommended to set the same width for all the frames so that one int variable is sufficient to hold this information.
Height Height refers to the height of the frames. It is also recommended to be same for all the frames of a particular sprite for avoiding unnecessary complexity in the code.
Visibility Status This is a boolean variable which is normally used for determining whether the sprite is currently visible or not. The paint method uses it to determine whether it should draw the sprite on the canvas.
Frame Frame refers to the current frame of the sprite. This variable should normally be an int. It stores the current frame number and is used by the paint method of the sprite to determine the frame which has to be painted at the current x and y co-ordinate.
State State is normally an int variable. It is a variable that could have many uses. It could be used to code whether the sprite is in child state or adult state. Or it can also be used to determine whether the sprite is in injured or dormant state. We will use state variable for a very important task.
You can have other variables depending upon the needs of your game. Some of these essential characteristics could also be left in order to make way for more art work. Each of these is having an important utility of there own and thus are further discussed in detail.
Sprite methods: There are certain methods which normally implement the common functionality of the sprites. These methods although recommended are not to be used compulsorily. There inclusion or exclusion is dependent on the nature of the sprite. Also there contents are dependent on the nature of the functionality of the sprite. These methods have been given below.
Constructor The constructors of sprites are like the constructor of any other class in terms of functionality. In the constructor of sprite, a sprite object is created with the above discussed variables.
CollidesWith (Sprite sprite) CollidesWith () method is the method which detects the collision of the sprite with the other sprites. Collisions are one of the most important parts of any arcade game. Proper collision detection is a must for a good game. There are many different kinds of collision detections. None of them is currently being supported with an inbuilt mechanism in Mobile Information Device Profile 1.0. Mobile Information Device Profile 2.0 supports 2 types of collision detection, namely:
· Bounding rectangle collision detection · Bit-masked collision detection i.e. pixel level collision detection
The following are the different types of collision detections that are generally used:
i. Bounding rectangle collision detection This is simple and fast collision detection technique. In the code illustrated above we have covered ‘bounding rectangle collision detection’.
ii. Collision detection with multiple levels This is a complex collision technique which takes more time and memory but is more effective in catching hold of collisions, then the above discussed technique. Here a sprite is divided in to different areas called levels. The largest area is the root level and has no other parent level. Each level can have other levels in them. Levels of the sprite are compared with each of the other sprite for knowing whether any two levels have collided. The collision for each zone is implemented in a similar manner to the above discussed technique. The following is the code snippet for this collision technique: protected int levels[][] = {0, 0, 10, 14}, {3, 2, 4, 11}};
public int collide(Sprite sprite) { for (int i=0; I<levels.length; i++) { if ((levels[i][0] + levels[I][2]) > sprite.getX() && levels[i][0] < sprite.getX() && (levels[i][1] + levels[I][3] > sprite.getY() && levels[i][1] < sprite.getY()) return i; } return -1; }
iii. Collision detection with multiple areas
Here the sprite’s area is divided in to rectangular areas. Here different areas are compared with each other to know the collision. The greatness of this technique is that even very small areas like the nose could be made into separate rectangles that could be checked. This gives tremendous boost to the results of collision results. The code snippet is as follows: protected int areas[][] = {2, 1, 6, 3}, {3, 4, 4, 5}, {0, 9, 10, 14}};
public boolean collide(Sprite sprite) { for (int i=0; I<areas.length; i++) { if ((areas[i][0] + areas[I][2]) > sprite.getX() && areas[i][0] < sprite.getX() && (areas[i][1] + areas[I][3] > sprite.getY() && areas[i][1] < sprite.getY())
return true; } return false; }
iv. Bit-masked collision detection Bit masked collision detection is the best form of collision detection in terms of collision results. It can check at the pixel level. But at the same time it is not as fast as the above given techniques.
We typically use bounding rectangle collision as it is the most fast and the easiest to understand.
Paint Paint method is used for painting the real frame image of the sprite on to the graphics object that is provided to it.
Move Move is a very important method as it enables the sprites to perform various actions. The move method is a simple method which changes the x and y co-ordinates of the sprites by the number of pixels given in its parameters.
Getter methods The getter methods for most of the above discussed methods should be written according to there use in the games. A pure object oriented approach though good on PC should be discarded in mobile phone programming due to memory constraints. Thus only those methods which are useful should be added.
Setter methods Setter methods should also be similarly written according to there use in order to save space.
Thus we see how we can create a Sprite class in MIDP 1.0. As for MIDP 2.0, it defines a Sprite class of its own which we have discussed earlier. Thus now you know the workings of Sprite class quite extensively.
· TiledLayer The TiledLayer class is the class that helps us in drawing complex 2D backgrounds from a few tiles. The TiledLayer class is the special sub class of Layer class. It divides the background into cells of fixed width and height much like MS Excel’s cells. The only difference is that each cell of TiledLayer is of same size. Here we will discuss the MIDP 1.0 implementation of TiledLayer class. The complete code of TiledLayer class in MIDP 1.0 is as follows:
/* * TiledLayer.java * * Created on May 20, 2002, 4:48 PM */
import javax.microedition.lcdui.*;
/** * * @author Saurabh Jain * @version 1.00 */ public class TiledLayer extends Layer { private int columns ; private int rows ; private Image image ; private int tWidth ; private int tHeight ; private int[][] cells ; private int tIndex ;
// Constructor public TiledLayer( int col, int row, Image img, int tileW, int tileH ) { this.columns = col ; this.rows = row ; this.image = img ; this.tWidth = tileW ; this.tHeight = tileH ;
this.cells = new int[row][col] ; }
public void fillCells( int col, int row, int nCol, int nRow, int tIndex ) { tIndex-- ;
for( int i=0; i<nRow; i++ ) { for( int j=0; j<nCol; j++ ) { cells[row+i][col+j] = tIndex ; } } }
public void setCell( int col, int row, int tIndex ) { tIndex-- ;
if(col < this.columns && row <= this.rows) { cells[row][col] = tIndex ; } }
public void paint(Graphics g) { int lx = g.getClipX() ; int ly = g.getClipY() ;
int lWidth = g.getClipWidth() ; int lHeight = g.getClipHeight() ;
for( int i=0; i < this.rows; i++ ) { for( int j=0; j < this.columns; j++ ) { g.setClip( j * this.tWidth, i * tHeight,tWidth,tHeight ) ; g.drawImage( this.image,j * this.tWidth - (cells[i][j] * tWidth), i * tHeight, Graphics.TOP|Graphics.LEFT ) ; g.setClip( lx,ly,lWidth,lHeight ) ; } } } }
The TiledLayer class contains the following variables:
o image This contains the background PNG image with all the background tiles in the same file. The background is converted into repetitive tiles and each unique tile is added to the image. Thus a complex background can be reduced from large pictures to small pictures.
Full Background of SKJ Aqua Tiled background of SKJ Aqua
Thus a lot of space could be saved if we properly convert a large background into a tiled image having unique tiles
Columns is an int variable containing the total number of columns in the TiledLayer.
Rows is an int variable containing the total number of rows in the TiledLayer.
Width of each individual tile.
Height of each individual tiles
It is an array of int data type. This is a two dimensional array which contains the correct placement of the correct tile in the grid. TiledLayer has the following methods: o TiledLayer(int row, int col, Image img, int tileW, int tileH) Sets the above mentioned variables except cells[][] with these parameters.
o fillCells(int row, int col, int nCol, int nRow, int tIndex ) Fills the nCol number of columns and nRow number of rows from the row and col given with the tIndex specified into the cell [][] array.
o getCell(int row, in col) Gets the tIndex at a particular cell in the cell[][] based on the parameters given.
o setCell(int col, int row, int tIndex) Sets the tIndex at a particular cell in the cell[][] based on the given column and row number.
o paint(Graphics g) This method performs the actual rendering of the tiles in the TiledLayer class.
· LayerManager The LayerManager class is useful for managing sprites specially when there are many sprites in one game. The main job of the LayerManager is to set the current view window and then call the paint method to draw all the sprites. The paint method of this class is called in the paint method of the main game class. The paint method of LayerManager in turns calls the paint methods of all the sprites appended to it.
LayerManager uses a vector for storing all the layers appended to it. The current view window is set using the set clip method based on the x, y, width, height variables that user can set using the setter methods for these.
The following is the complete code of the LayerManager class.
/* * LayerManager.java * * Created on May 20, 2002, 4:44 PM */
import javax.microedition.lcdui.*; import java.util.Vector ;
/** * * @author Saurabh Jain * @version 1.00 */ public class LayerManager { // Basic Functionality private Vector layers ; private int x ; private int y ; private int width ; private int height ;
// Constructor public LayerManager() { this.layers = new Vector() ; }
// Appending layer l public void append(Layer l) { this.layers.addElement(l) ; }
// Setting the rectangular view window public void setViewWindow( int x, int y, int width, int height ) { this.x = x ; this.y = y ; this.width = width ; this.height = height ; }
// paint method public void paint(Graphics g) { g.setClip( this.x,this.y,this.width,this.height ) ;
for(int i = this.layers.size() - 1; i >= 0 ; i--) { ((Layer)(this.layers.elementAt(i))).paint(g) ; } } }
The following are the explanations for each of the global variables of our custom LayerManager class:
· layers This variable is a vector containing a list of all the Layers which this LayerManager has to keep track of.
· x This int variable keeps track of the starting x co-ordinate of the LayerManager’s current view window.
· y This int variable keeps track of the starting y co-ordinate of the LayerManager’s current view window.
· width This int variable keeps track of the width of the LayerManager’s current view window.
· height This int variable keeps track of the height of the LayerManager’s current view window.
The LayerManager class contains the following methods: · public LayerManager() This is the constructor for the LayerManager class.
· public void append(Layer l) This method helps in appending a Layer in the LayerManager.
· public void setViewWindow( int x, int y, int width, int height ) This method helps us set the current view window for the LayerManager. This helps in cases where we want to display all the layers in a specified viewing area on the screen.
· public void paint(Graphics g) This method actually paints the layers on the basis of the actual view window. |
Mobile Technology > Java ME (J2ME) > Book - Mobile Phone Programming using Java ME (J2ME) > UNIT V >