{"id":452,"date":"2009-10-09T16:56:25","date_gmt":"2009-10-09T20:56:25","guid":{"rendered":"http:\/\/warriormill.com\/?p=452"},"modified":"2011-12-14T21:37:02","modified_gmt":"2011-12-15T01:37:02","slug":"adroid-game-development-part-1-gameloop-sprites","status":"publish","type":"post","link":"https:\/\/warriormill.com\/2009\/10\/adroid-game-development-part-1-gameloop-sprites\/","title":{"rendered":"Android Game Development – Part 1 GameLoop & Sprites"},"content":{"rendered":"
Game Loop is the main part of a game engine, it cycles threw at given intervals updating the game. The Game loop in this section will be fairly simple, it will be a thread that fires off every millisecond.
\n[java]
\n\/**
\n*
\n*\/
\npackage com.warriormill.warriorengine;
\nimport java.util.concurrent.TimeUnit;
\nimport com.warriormill.warriorengine.drawable.SpriteTile;
\nimport android.content.Context;
\nimport android.graphics.Canvas;
\nimport android.util.AttributeSet;
\n\/\/import android.view.SurfaceHolder;
\n\/\/import android.view.SurfaceView;
\nimport android.view.View;<\/p>\n
\/**
\n* @author maximo guerrero
\n*
\n*\/
\npublic class GameEngineView extends View {
\nSpriteTile st;<\/p>\n
GameLoop gameloop;
\nprivate class GameLoop extends Thread
\n{
\nprivate volatile boolean running=true;
\npublic void run()
\n{
\nwhile(running)
\n{
\ntry{
\nTimeUnit.MILLISECONDS.sleep(1);
\npostInvalidate();
\npause();<\/p>\n
}
\ncatch(InterruptedException ex)
\n{
\nrunning=false;
\n}<\/p>\n
}<\/p>\n
}
\npublic void pause()
\n{
\nrunning=false;
\n}
\npublic void start()
\n{
\nrunning=true;
\nrun();
\n}
\npublic void safeStop()
\n{
\nrunning=false;
\ninterrupt();
\n}<\/p>\n
}
\npublic void unload()
\n{
\ngameloop.safeStop();<\/p>\n
}<\/p>\n
public GameEngineView(Context context, AttributeSet attrs, int defStyle) {
\nsuper(context, attrs, defStyle);
\n\/\/ TODO Auto-generated constructor stub
\ninit(context);<\/p>\n
}
\npublic GameEngineView(Context context, AttributeSet attrs) {
\nsuper(context, attrs);
\n\/\/ TODO Auto-generated constructor stub
\ninit(context);
\n}
\npublic GameEngineView(Context context) {
\nsuper(context);
\n\/\/ TODO Auto-generated constructor stub
\ninit(context);
\n}<\/p>\n
private void init(Context context)
\n{
\nst = new SpriteTile(R.drawable.buster, R.xml.buster, context);
\ngameloop = new GameLoop();
\ngameloop.run();<\/p>\n
}
\n@Override
\nprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
\n\/\/ TODO Auto-generated method stub
\n\/\/super.onMeasure(widthMeasureSpec, heightMeasureSpec);
\nSystem.out.println("Width " + widthMeasureSpec);
\nsetMeasuredDimension(100, 100);
\n}<\/p>\n
@Override
\nprotected void onDraw(Canvas canvas) {
\n\/\/ TODO Auto-generated method stub
\n\/\/super.onDraw(canvas);
\nst.setXpos(15);
\nst.setYpos(15);<\/p>\n
st.draw(canvas);
\ngameloop.start();<\/p>\n
}<\/p>\n
} The Current Game loop is really simple it will call draw on the items that implement the Drawable<\/strong> Interface.<\/p>\n The Sprite Tile class will be an object that extends the Drawable interface in the android api. Our Sprite will be composed of two files,\u00a0 a bitmap file (image of format type jpg, png, gif or bmp) and an xml file that describes its behavior.<\/p>\n Sprite sheet ( check out http:\/\/www.retrogamezone.co.uk for sample sprites):<\/p>\n <\/a><\/p>\n The sprite sheet is one image with all possible animaitions for our sprite<\/p>\n Xml Description: <collisionrect top="10" left="5" bottom="40" right="20" \/> Sprite Class: import java.util.ArrayList; import org.xmlpull.v1.XmlPullParser;<\/p>\n import android.content.Context; \/** private ColorFilter cf;<\/p>\n \/\/ Class contains Information about one frame \/\/takes resource ids for bitmaps and xmlfiles \/\/load bitmap and xml data animations= new Hashtable();<\/p>\n try if(eventType == XmlPullParser.START_DOCUMENT) { } else if(eventType == XmlPullParser.END_DOCUMENT) { } else if(eventType == XmlPullParser.START_TAG) { } } }<\/p>\n @Override @Override }<\/p>\n @Override \/\/updates the frame counter to the next frame FrameInfo frameinfo= animations.get(currentAnimation).sequence.get(currentFrame); } if( rect.top > as.collisionRect.bottom ) return true; return false; public void setXpos(int xpos) { public int getXpos() { public void setYpos(int ypos) { public int getYpos() { The class also implements a couple useful functions for checking the animations current state. Also note that the constructor take Resource-ID’s and not file paths. This is so that the developer has the choice of passing in images that have been compressed by the android api or raw images.<\/p>\n Final product: That’s it for part 1.<\/p>\n
\n[\/java]
\nA couple things to notice. We pause the thread until the screen has finished drawing, the reason this is done is that the thread will continue to fire off and the onDraw()<\/strong> function will never be called. Also Since we are using a generic thread and not a UI-Thread we call postInvalidate()<\/strong> function to let android know that our view has to be re drawn.<\/p>\nSprite Tile Class<\/h3>\n
\n[xml]
\n<?xml version="1.0" encoding="utf-8"?>
\n<animations>
\n<animation name="idle" canLoop="true" >
\n<framerect top="4" left="85" bottom="58" right="115" delayNextFrame="10" \/>
\n<framerect top="4" left="122" bottom="58" right="153" delayNextFrame="10" \/>
\n<framerect top="4" left="161" bottom="58" right="190" delayNextFrame="5" \/>
\n<framerect top="4" left="199" bottom="58" right="228" delayNextFrame="5" \/>
\n<framerect top="4" left="237" bottom="58" right="268" delayNextFrame="5" \/>
\n<framerect top="4" left="276" bottom="58" right="307" delayNextFrame="5" \/>
\n<framerect top="4" left="85" bottom="58" right="115" delayNextFrame="60" \/><\/p>\n
\n<\/animation>
\n<\/animations>
\n[\/xml]
\nThe xml description file contains the information needed to animation the sprite sheet. All of the Elements and there Attributes will have respective fields in our sprite class. Note that the frame rectangle is not equally uniform, this allows to compose an animation where the tile thats being drawn doesn’t have to be the same size. It also tells us how much time each frame will last on the screen, along with a rectangle for collision for that specific animation.<\/p>\n
\nThe following class will load, draw and animate the tilesheet.
\n[java]
\npackage com.warriormill.warriorengine.drawable;<\/p>\n
\nimport java.util.Hashtable;<\/p>\n
\nimport android.content.res.XmlResourceParser;
\nimport android.graphics.Bitmap;
\nimport android.graphics.BitmapFactory;
\nimport android.graphics.Canvas;
\nimport android.graphics.ColorFilter;
\nimport android.graphics.Rect;
\nimport android.graphics.drawable.Drawable;
\nimport android.util.Log;<\/p>\n
\n* @author maximo guerrero
\n*\/
\npublic class SpriteTile extends Drawable {
\nprivate Bitmap tileSheet; \/\/sprite tile sheet for all animations. rectangles are used to slip and only show parts of one bitmap
\nprivate Hashtable animations; \/\/all animation sequences for this sprite
\nprivate String currentAnimation="idle"; \/\/current animation sequence
\nprivate int currentFrame=0; \/\/current frame being played
\nprivate int xpos=100; \/\/ x position
\nprivate int ypos=100; \/\/ y position
\nprivate int waitDelay=0; \/\/ delay before the next frame<\/p>\n
\nprivate class FrameInfo
\n{
\npublic Rect rect = new Rect();
\npublic int nextFrameDelay =0;
\n}
\n\/\/Class encapsulates all the data for an animations sequence. List for frames, animcation name, if the sequence will loop and collission info
\nprivate class AnimationSequece
\n{
\npublic ArrayList sequence;
\npublic Rect collisionRect;
\npublic boolean canLoop =false;
\n@SuppressWarnings("unused")
\npublic String name="idle";
\n}<\/p>\n
\npublic SpriteTile(int BitmapResourceId, int XmlAnimationResourceId, Context context)
\n{
\nloadSprite(BitmapResourceId,XmlAnimationResourceId,context);
\n}<\/p>\n
\npublic void loadSprite(int spriteid, int xmlid, Context context) {
\ntileSheet = BitmapFactory.decodeResource(context.getResources(), spriteid);
\n\/\/load the xml will all the frame animations into a hashtable
\nXmlResourceParser xpp= context.getResources().getXml(xmlid);<\/p>\n
\n{
\nint eventType = xpp.getEventType();
\nString animationname="";
\nAnimationSequece animationsequence = new AnimationSequece();
\nwhile (eventType != XmlPullParser.END_DOCUMENT){<\/p>\n
\nSystem.out.println("Start document");<\/p>\n
\nSystem.out.println("End document");<\/p>\n
\nSystem.out.println("Start tag "+xpp.getName());
\nif(xpp.getName().toLowerCase().equals("animation"))
\n{
\nanimationname=xpp.getAttributeValue(null, "name");
\nanimationsequence = new AnimationSequece();
\nanimationsequence.name=animationname;
\nanimationsequence.sequence=new ArrayList();
\nanimationsequence.canLoop = xpp.getAttributeBooleanValue(null,"canLoop", false);
\n}
\nelse if(xpp.getName().toLowerCase().equals("framerect"))
\n{
\nFrameInfo frameinfo = new FrameInfo();
\nRect frame = new Rect();
\nframe.top = xpp.getAttributeIntValue(null, "top", 0);
\nframe.bottom = xpp.getAttributeIntValue(null, "bottom", 0);
\nframe.left = xpp.getAttributeIntValue(null, "left", 0);
\nframe.right = xpp.getAttributeIntValue(null, "right", 0);
\nframeinfo.rect = frame;
\nframeinfo.nextFrameDelay = xpp.getAttributeIntValue(null,"delayNextFrame", 0);
\nanimationsequence.sequence.add(frameinfo);
\n}
\nelse if(xpp.getName().toLowerCase().equals("collisionrect"))
\n{
\nRect colrect = new Rect();
\ncolrect.top = xpp.getAttributeIntValue(null, "top", 0);
\ncolrect.bottom = xpp.getAttributeIntValue(null, "bottom", 0);
\ncolrect.left = xpp.getAttributeIntValue(null, "left", 0);
\ncolrect.right = xpp.getAttributeIntValue(null, "right", 0);
\nanimationsequence.collisionRect=colrect;
\n}
\n}else if(eventType == XmlPullParser.END_TAG) {
\nif(xpp.getName().toLowerCase().equals("animation"))
\n{
\nanimations.put(animationname, animationsequence);
\n}
\n} else if(eventType == XmlPullParser.TEXT) {
\nSystem.out.println("Text "+xpp.getText());<\/p>\n
\neventType = xpp.next();
\n}
\n}
\ncatch (Exception e) {
\nLog.e("ERROR", "ERROR IN SPRITE TILE CODE:"+e.toString());
\n}
\nSystem.out.println("Sprite Loaded ");
\n}
\n\/\/Draw sprite onto screen
\n@Override
\npublic void draw(Canvas canvas) {
\ntry
\n{
\nFrameInfo frameinfo= animations.get(currentAnimation).sequence.get(currentFrame);
\nRect rclip = frameinfo.rect;
\nRect dest = new Rect(this.getXpos(), getYpos(), getXpos() + (rclip.right – rclip.left),
\ngetYpos() + (rclip.bottom – rclip.top));
\nif(cf!=null)
\n{
\n\/\/color filter code here<\/p>\n
\ncanvas.drawBitmap(tileSheet, rclip, dest, null);
\nupdate(); \/\/after drawing update the frame counter
\n}
\ncatch (Exception e)
\n{
\nLog.e("ERROR", "ERROR IN SPRITE TILE CODE:"+e.toString()+e.getStackTrace().toString());
\n}<\/p>\n
\npublic int getOpacity() {
\n\/\/ TODO Auto-generated method stub
\nreturn 100;
\n}<\/p>\n
\npublic void setAlpha(int alpha) {
\n\/\/ TODO Auto-generated method stub<\/p>\n
\npublic void setColorFilter(ColorFilter cf) {
\n\/\/ TODO Auto-generated method stub
\nthis.cf = cf;
\n}<\/p>\n
\npublic void update()
\n{
\nif(waitDelay==0)\/\/if done waiting
\n{
\n\/\/set current frame back to the first because looping is possible
\nif(animations.get(currentAnimation).canLoop && currentFrame == animations.get(currentAnimation).sequence.size()-1)
\ncurrentFrame=0;
\nelse
\n{
\ncurrentFrame++; \/\/go to next frame<\/p>\n
\nwaitDelay = frameinfo.nextFrameDelay; \/\/set delaytime for the next frame
\n}
\n}
\nelse
\n{
\nwaitDelay–; \/\/wait for delay to expire
\n}<\/p>\n
\n\/\/has this sprite collided with a rect
\npublic boolean hasCollided(Rect rect)
\n{
\nAnimationSequece as = animations.get(currentAnimation);
\nif( rect.right < as.collisionRect.left )
\nreturn false;
\nif( rect.left > as.collisionRect.right )
\nreturn false;<\/p>\n
\nreturn false;
\nif( rect.bottom < as.collisionRect.top )
\nreturn false;<\/p>\n
\n}
\n\/\/has animation finished playing, returns true on a animaiton that can loop for ever
\npublic boolean hasAnimationFinished()
\n{
\nAnimationSequece as = animations.get(currentAnimation);
\nif(currentFrame == as.sequence.size() -1 && !as.canLoop )
\nreturn true;<\/p>\n
\n}<\/p>\n
\nthis.xpos = xpos;
\n}<\/p>\n
\nreturn xpos;
\n}<\/p>\n
\nthis.ypos = ypos;
\n}<\/p>\n
\nreturn ypos;
\n}
\n}
\n[\/java]<\/p>\n
\n<\/a><\/p>\n