This very simple tutorial will quickly bring your games to life by drawing the frames of animation from a row of frames drawn consecutively in the same file. We will see how to draw each of five frames one after the other and after we have drawn the last frame, loop back to the first. I have just updated this tutorial and added an extra code listing to the end which gives an example of how you might encapsulate the animation functionality in a class.
Admittedly Bob isn’t the highest quality graphic and his sprite sheet represents an extremely basic walking motion but once you have this technique in your game programming tool-kit you can swap Bob out for some really high-quality sprite sheets and create some graphical masterpieces. There are many free examples to be found with a quick Web search.
Setting up the sprite sheet animation project
To get started create a new project in Android Studio, call it Sprite Sheet Animation, and name the Activity SpriteSheetAnimation. Now download the sprite sheet below and add it to the drawable folder of your Android Studio project. Be sure to keep the file name the same. bob.png. Please don’t be confused by the fact that Bob is facing the opposite way in the sprite sheet to that of the explanatory diagrams later in the project. I did the explanatory diagrams first then realized that Bob would be moonwalking backward if I didn’t amend the Building a simple game engine code. And I wanted to keep the code consistent between the two projects so that just the new sprite sheet animation code stands out.
Below you can see a representation of bob.png. We can see the height and width of each frame as well as the progression through a simple walking animation. Note that the actual bob.png file background is transparent not white. It just looks white above because of the white background of this page. With this simple sprite sheet animation solution, the width and height of each frame perhaps surprisingly, not relevant. What is key however is the fact that all the widths are the same and that the ratio of the width to the height is important for the sake of maintaining the visual consistency of the displayed image. So take a quick look before moving on.
Notice in the next, quite large code block that it is nearly identical to the code from the Building a simple game engine project. Review the code below and refer to the comments for a reminder but if anything is unclear refer to that previous project to understand what is going on. Make note of where we will be adding new code to animate Bob to make him look like he is walking. I have added some comments to mark these places.
[widgets_on_pages id=”udemy_advert_java_2″]
We will add a bunch of new member variables just after the existing member variables shown below. We will add a few lines of code in the constructor
SpriteSheetAnimation() just after we create the bitmap from the bob.png file. We will add a new method called
getCurrentFrame that will work the magic of our sprite sheet animation and finally, we will change the way that we draw the bitmap to the screen in the
draw method. Notice in the code below the original call to
drawBitmap is commented out and ready for our sprite sheet-friendly code.
The starting code from the Simple game engine project
So, delete the entire contents of your SpriteSheetAnimation.java code file with the exception of the package declaration, review, and add the code below in its place.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class SpriteSheetAnimation extends Activity { // Our object that will hold the view and // the sprite sheet animation logic GameView gameView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize gameView and set it as the view gameView = new GameView(this); setContentView(gameView); } // Here is our implementation of GameView // It is an inner class. // Note how the final closing curly brace } // is inside SpriteSheetAnimation // Notice we implement runnable so we have // A thread and can override the run method. class GameView extends SurfaceView implements Runnable { // This is our thread Thread gameThread = null; // This is new. We need a SurfaceHolder // When we use Paint and Canvas in a thread // We will see it in action in the draw method soon. SurfaceHolder ourHolder; // A boolean which we will set and unset // when the game is running- or not. volatile boolean playing; // A Canvas and a Paint object Canvas canvas; Paint paint; // This variable tracks the game frame rate long fps; // This is used to help calculate the fps private long timeThisFrame; // Declare an object of type Bitmap Bitmap bitmapBob; // Bob starts off not moving boolean isMoving = false; // He can walk at 150 pixels per second float walkSpeedPerSecond = 250; // He starts 10 pixels from the left float bobXPosition = 10; // New variables for the sprite sheet animation // When the we initialize (call new()) on gameView // This special constructor method runs public GameView(Context context) { // The next line of code asks the // SurfaceView class to set up our object. // How kind. super(context); // Initialize ourHolder and paint objects ourHolder = getHolder(); paint = new Paint(); // Load Bob from his .png file bitmapBob = BitmapFactory.decodeResource(this.getResources(), R.drawable.bob); } @Override public void run() { while (playing) { // Capture the current time in milliseconds in startFrameTime long startFrameTime = System.currentTimeMillis(); // Update the frame update(); // Draw the frame draw(); // Calculate the fps this frame // We can then use the result to // time animations and more. timeThisFrame = System.currentTimeMillis() - startFrameTime; if (timeThisFrame >= 1) { fps = 1000 / timeThisFrame; } } } // Everything that needs to be updated goes in here // In later projects, we will have dozens (arrays) of objects. // We will also do other things like collision detection. public void update() { // If bob is moving (the player is touching the screen) // then move him to the right based on his target speed and the current fps. if(isMoving){ bobXPosition = bobXPosition + (walkSpeedPerSecond / fps); } } // Draw the newly updated scene public void draw() { // Make sure our drawing surface is valid or we crash if (ourHolder.getSurface().isValid()) { // Lock the canvas ready to draw canvas = ourHolder.lockCanvas(); // Draw the background color canvas.drawColor(Color.argb(255, 26, 128, 182)); // Choose the brush color for drawing paint.setColor(Color.argb(255, 249, 129, 0)); // Make the text a bit bigger paint.setTextSize(45); // Display the current fps on the screen canvas.drawText("FPS:" + fps, 20, 40, paint); // Draw bob at bobXPosition, 200 pixels //canvas.drawBitmap(bitmapBob, bobXPosition, 200, paint); // New drawing code goes here // Draw everything to the screen ourHolder.unlockCanvasAndPost(canvas); } } // If SimpleGameEngine Activity is paused/stopped // shutdown our thread. public void pause() { playing = false; try { gameThread.join(); } catch (InterruptedException e) { Log.e("Error:", "joining thread"); } } // If SimpleGameEngine Activity is started theb // start our thread. public void resume() { playing = true; gameThread = new Thread(this); gameThread.start(); } // The SurfaceView class implements onTouchListener // So we can override this method and detect screen touches. @Override public boolean onTouchEvent(MotionEvent motionEvent) { switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) { // Player has touched the screen case MotionEvent.ACTION_DOWN: // Set isMoving so Bob is moved in the update method isMoving = true; break; // Player has removed finger from screen case MotionEvent.ACTION_UP: // Set isMoving so Bob does not move isMoving = false; break; } return true; } } // This is the end of our GameView inner class // This method executes when the player starts the game @Override protected void onResume() { super.onResume(); // Tell the gameView resume method to execute gameView.resume(); } // This method executes when the player quits the game @Override protected void onPause() { super.onPause(); // Tell the gameView pause method to execute gameView.pause(); } } |
Now, in the code that follows, we will add all the required extra member variables.
Adding the sprite sheet control variables
Firstly we have two int variables frameWidth and frameHeight for holding the width and height of the frame. You might notice that we initialize these two variables to two values which are different to the actual sizes of the frames in the bob.png file as shown in the previous image. As we will see this doesn’t matter and you can make Bob’s frames almost any size you like.
Next, we declare another int called frameCount and initialize it to 5 which is the number of frames in the sprite sheet. Then we declare and initialize currentFrame to 0. This variable will perhaps unsurprisingly keep track of which frame is being drawn and as we want to start with frame zero we initialize it accordingly.
The variable lastFrameChangeTime will hold a value in milliseconds representing the time that the last frame was changed. The int variable frameLengthInMilliseconds is how long we would like each frame of animation to last. 100 milliseconds is one-tenth of a second.
After this, we declare and initialize an object of type Rect which will hold the coordinates (the four corners) of the current frame within our sprite sheet, so we name it frameToDraw and initialize it with 0 (left), 0(top), frameWidth(right) and frameHeight(bottom) which after we have loaded and scaled bob.png will be the exact first frame of animation.
Finally, in this block of code, we declare and initialize a RectF which is exactly the same as a Rect except it can hold floating-point numbers as well. This is ideal because it will hold Bob’s x location which is indeed a float. We name the RectF whereToDraw and initialize it with bobXPosition(left), 0 (top), bobXPosition + frameWidth(right), and frameHeight(bottom). Note the difference between the whereToDraw and frameToDraw objects. The whereToDraw object will be the screen coordinates where we will draw the frame defined by frameToDraw.
Add the code below that we have just discussed at the end of all the previous member variables but before the constructor method just after the // New variables for the sprite sheet animation comment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// New variables for the sprite sheet animation // These next two values can be anything you like // As long as the ratio doesn't distort the sprite too much private int frameWidth = 100; private int frameHeight = 50; // How many frames are there on the sprite sheet? private int frameCount = 5; // Start at the first frame - where else? private int currentFrame = 0; // What time was it when we last changed frames private long lastFrameChangeTime = 0; // How long should each frame last private int frameLengthInMilliseconds = 100; // A rectangle to define an area of the // sprite sheet that represents 1 frame private Rect frameToDraw = new Rect( 0, 0, frameWidth, frameHeight); // A rect that defines an area of the screen // on which to draw RectF whereToDraw = new RectF( bobXPosition, 0, bobXPosition + frameWidth, frameHeight); |
Now we can add some code in the constructor.
Scaling the sprite sheet to match the frame size
When Android loads a bitmap from a file it scales it differently depending upon the device the program is running on. So it is very unlikely that the bitmapBob = BitmapFactory.decodeResource... line of code which loads bob.png will correspond with our various frameWidth and frameHeight variables. It is possible to put the bob.png file within a specially named folder within the drawable folder which will then instruct Android not to mess with the scale. However, as our game coding progresses we want to respond to things like screen resolution so we will begin to see part of how we will do so here. In the next block of code which we must add right after the previously mentioned bitmapBob = BitmapFactory.decodeResource... line of code, we take our new Bitmap object, bitmapBob and scale it using Bitmap.createScaledBitmap and scale it to the size of frameWidth * frameCount, frameHeight. Now we have a sprite sheet that is exactly the correct size for all the variables we declared previously.
Add the code below, just below the bitmapBob = BitmapFactory.decodeResource... line of code.
1 2 3 4 5 6 7 |
// Scale the bitmap to the correct size // We need to do this because Android automatically // scales bitmaps based on screen density bitmapBob = Bitmap.createScaledBitmap(bitmapBob, frameWidth * frameCount, frameHeight, false); |
Now we need a new method that will keep track of our frames and loop through them at the rate contained in frameLengthInMilliSeconds (100 milliseconds per frame).
Getting the current frame
The solution is the getCurrentFrame method that we will now discuss. After the method signature, we initialize time to whatever the time is. Then we check whether the player is currently moving Bob because if he isn’t we know that no matter how much time has elapsed we don’t want to change the frame. If isMoving is true we check if time is greater than the time we last changed the frame of animation added to the length of time we want each frame to be visible. If it is then we set lastFrameChangeTime to the current time ( time) ready for the next time we call this method. Then we simply increment the current frame with currentFrame ++.
Next, we check to see if currentFrame has exceeded the frameCount and if it has it is time to go back to the first frame and currentFrame is set to zero. Now outside of all the if statements we want to set frameToDraw to hold the four corners of whatever the current frame of animation is.
This is achieved by setting frameToDraw.left to currentFrame multiplied by frameWidth and frameToDraw.right to whatever frameToDraw.left now is + frameWidth. The Rect frameToDraw now holds the coordinates of the current frame. Enter the new method getCurrentFrame. It can go anywhere within the SpriteSheetAnimation class as long as it is not inside another method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public void getCurrentFrame(){ long time = System.currentTimeMillis(); if(isMoving) {// Only animate if bob is moving if ( time > lastFrameChangeTime + frameLengthInMilliseconds) { lastFrameChangeTime = time; currentFrame ++; if (currentFrame >= frameCount) { currentFrame = 0; } } } //update the left and right values of the source of //the next frame on the spritesheet frameToDraw.left = currentFrame * frameWidth; frameToDraw.right = frameToDraw.left + frameWidth; } |
Finally, we will get to draw Bob.
Drawing the frames of animation
In the next block of code, you can see the old call to canvas.drawBitmap has been commented out. Immediately after this, we call whereToDraw.set and initialize it with the coordinates on the screen where we want to draw a frame of Bob. We use bobXPosition (for left), (for top), bobXPosition + frameWidth (for right) and frameHeight (for the bottom).
Then we call getCurrentFrame which loads the coordinates of the current frame of animation into frameToDraw. The last line of code calls an overloaded version of drawBitmap to draw the appropriate frame from the sprite sheet to the appropriate coordinates on the screen. Take a look at the drawing below to understand this last line of code.
Overloaded methods are methods that can take multiple different parameters but still have the same name. Now would be a good time to start exploring the official Android documents for the first time if you haven’t ever done so before. Here you can see all the different versions of drawBitmap and the arguments you can use to get stuff drawn to the screen with it. Getting used to the dry, technical but extremely useful official documentation is invaluable. Here is a link to the ins and outs of the Canvas class which includes explanations for several overloaded versions of drawBitmap.
Add these final lines of code in the draw method at the position indicated by the commented-out code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Draw bob at bobXPosition, 200 pixels //canvas.drawBitmap(bitmapBob, bobXPosition, 200, paint); // New drawing code goes here whereToDraw.set((int)bobXPosition, 0, (int)bobXPosition + frameWidth, frameHeight); getCurrentFrame(); canvas.drawBitmap(bitmapBob, frameToDraw, whereToDraw, paint); |
Final thoughts
You can now run the project and watch Bob stroll confidently and fairly smoothly across the screen. We will use this technique for sprite sheet animation in an upcoming game project where we will see how to wrap this functionality up in a class of its own so it can be used for multiple different animated game objects while only writing the code once. Sprite sheet animation is also discussed at length in my book Android Game Programming by Example.
complete coded listing from SpriteSheetAnimation.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class SpriteSheetAnimation extends Activity { // Our object that will hold the view and // the sprite sheet animation logic GameView gameView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize gameView and set it as the view gameView = new GameView(this); setContentView(gameView); } // Here is our implementation of GameView // It is an inner class. // Note how the final closing curly brace } // is inside SpriteSheetAnimation // Notice we implement runnable so we have // A thread and can override the run method. class GameView extends SurfaceView implements Runnable { // This is our thread Thread gameThread = null; // This is new. We need a SurfaceHolder // When we use Paint and Canvas in a thread // We will see it in action in the draw method soon. SurfaceHolder ourHolder; // A boolean which we will set and unset // when the game is running- or not. volatile boolean playing; // A Canvas and a Paint object Canvas canvas; Paint paint; // This variable tracks the game frame rate long fps; // This is used to help calculate the fps private long timeThisFrame; // Declare an object of type Bitmap Bitmap bitmapBob; // Bob starts off not moving boolean isMoving = false; // He can walk at 150 pixels per second float walkSpeedPerSecond = 250; // He starts 10 pixels from the left float bobXPosition = 10; // New for the sprite sheet animation // These next two values can be anything you like // As long as the ratio doesn't distort the sprite too much private int frameWidth = 100; private int frameHeight = 50; // How many frames are there on the sprite sheet? private int frameCount = 5; // Start at the first frame - where else? private int currentFrame = 0; // What time was it when we last changed frames private long lastFrameChangeTime = 0; // How long should each frame last private int frameLengthInMilliseconds = 100; // A rectangle to define an area of the // sprite sheet that represents 1 frame private Rect frameToDraw = new Rect( 0, 0, frameWidth, frameHeight); // A rect that defines an area of the screen // on which to draw RectF whereToDraw = new RectF( bobXPosition, 0, bobXPosition + frameWidth, frameHeight); // When the we initialize (call new()) on gameView // This special constructor method runs public GameView(Context context) { // The next line of code asks the // SurfaceView class to set up our object. // How kind. super(context); // Initialize ourHolder and paint objects ourHolder = getHolder(); paint = new Paint(); // Load Bob from his .png file bitmapBob = BitmapFactory.decodeResource(this.getResources(), R.drawable.bob); // Scale the bitmap to the correct size // We need to do this because Android automatically // scales bitmaps based on screen density bitmapBob = Bitmap.createScaledBitmap(bitmapBob, frameWidth * frameCount, frameHeight, false); // Set our boolean to true - game on! //playing = true; } @Override public void run() { while (playing) { // Capture the current time in milliseconds in startFrameTime long startFrameTime = System.currentTimeMillis(); // Update the frame update(); // Draw the frame draw(); // Calculate the fps this frame // We can then use the result to // time animations and more. timeThisFrame = System.currentTimeMillis() - startFrameTime; if (timeThisFrame >= 1) { fps = 1000 / timeThisFrame; } } } // Everything that needs to be updated goes in here // In later projects we will have dozens (arrays) of objects. // We will also do other things like collision detection. public void update() { // If bob is moving (the player is touching the screen) // then move him to the right based on his target speed and the current fps. if(isMoving){ bobXPosition = bobXPosition + (walkSpeedPerSecond / fps); } } public void getCurrentFrame(){ long time = System.currentTimeMillis(); if(isMoving) {// Only animate if bob is moving if ( time > lastFrameChangeTime + frameLengthInMilliseconds) { lastFrameChangeTime = time; currentFrame++; if (currentFrame >= frameCount) { currentFrame = 0; } } } //update the left and right values of the source of //the next frame on the spritesheet frameToDraw.left = currentFrame * frameWidth; frameToDraw.right = frameToDraw.left + frameWidth; } // Draw the newly updated scene public void draw() { // Make sure our drawing surface is valid or we crash if (ourHolder.getSurface().isValid()) { // Lock the canvas ready to draw canvas = ourHolder.lockCanvas(); // Draw the background color canvas.drawColor(Color.argb(255, 26, 128, 182)); // Choose the brush color for drawing paint.setColor(Color.argb(255, 249, 129, 0)); // Make the text a bit bigger paint.setTextSize(45); // Display the current fps on the screen canvas.drawText("FPS:" + fps, 20, 40, paint); // Draw bob at bobXPosition, 200 pixels //canvas.drawBitmap(bitmapBob, bobXPosition, 200, paint); whereToDraw.set((int)bobXPosition, 0, (int)bobXPosition + frameWidth, frameHeight); getCurrentFrame(); canvas.drawBitmap(bitmapBob, frameToDraw, whereToDraw, paint); // Draw everything to the screen ourHolder.unlockCanvasAndPost(canvas); } } // If SimpleGameEngine Activity is paused/stopped // shutdown our thread. public void pause() { playing = false; try { gameThread.join(); } catch (InterruptedException e) { Log.e("Error:", "joining thread"); } } // If SimpleGameEngine Activity is started theb // start our thread. public void resume() { playing = true; gameThread = new Thread(this); gameThread.start(); } // The SurfaceView class implements onTouchListener // So we can override this method and detect screen touches. @Override public boolean onTouchEvent(MotionEvent motionEvent) { switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) { // Player has touched the screen case MotionEvent.ACTION_DOWN: // Set isMoving so Bob is moved in the update method isMoving = true; break; // Player has removed finger from screen case MotionEvent.ACTION_UP: // Set isMoving so Bob does not move isMoving = false; break; } return true; } } // This is the end of our GameView inner class // This method executes when the player starts the game @Override protected void onResume() { super.onResume(); // Tell the gameView resume method to execute gameView.resume(); } // This method executes when the player quits the game @Override protected void onPause() { super.onPause(); // Tell the gameView pause method to execute gameView.pause(); } } |
Encapsulating the animation in an Animation class
A few people have asked how you might wrap the animation up into a class. Here is some code that is one way of doing it, there are others. I haven’t got time at the moment to fully explain or add code changes for the code that uses it but you will see that most of the variables and method names remain the same or similar to the tutorial and code above. Obviously, you will need to make changes to the code that uses the class yourself. I hope this helps a bit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import android.content.Context; import android.graphics.Bitmap; import android.graphics.Rect; public class Animation { Bitmap bitmapSheet; String bitmapName; private Rect sourceRect; private int frameCount; private int currentFrame; private long frameTicker; private int framePeriod; private int frameWidth; private int frameHeight; int pixelsPerMetre; Animation(Context context, String bitmapName, float frameHeight, float frameWidth, int animFps, int frameCount, int pixelsPerMetre){ this.currentFrame = 0; this.frameCount = frameCount; this.frameWidth = (int)frameWidth * pixelsPerMetre; this.frameHeight = (int)frameHeight * pixelsPerMetre; sourceRect = new Rect(0, 0, this.frameWidth, this.frameHeight); framePeriod = 1000 / animFps; frameTicker = 0l; this.bitmapName = "" + bitmapName; this.pixelsPerMetre = pixelsPerMetre; } public Rect getCurrentFrame(long time, float xVelocity, boolean moves){ if(xVelocity!=0 || moves == false) {// Only animate if the object is moving or it is an object which doesn't move if (time > frameTicker + framePeriod) { frameTicker = time; currentFrame++; if (currentFrame >= frameCount) { //if (currentFrame >= frameCount) { currentFrame = 0; } } } //update the left and right values of the source of //the next frame on the spritesheet this.sourceRect.left = currentFrame * frameWidth; this.sourceRect.right = this.sourceRect.left + frameWidth; return sourceRect; } } |
Hi John,
I am trying to do a similar animation. Instead of a spritesheet, I have 10 sprites (png images) per movement state like running, jumping etc. I have stored these sprites in a Hashmap of vectors containing sprites with key as movement state. Now, what will be the most efficient way to flip the sprite to animate it in opposite direction?
Thanks
Hi Nick,
I am not sure if a HashMap is the best way to do this. As it is designed to be referred to by key it is less efficient and more awkward to iterate in order/reverse order than simply traversing a spritesheet by its coordinates. I would suggest using Gimp(free) or Photoshop if you have it to make a spritesheet with equal size frames in one image file then you can use simple addition and subtraction multiplied by the frame size to switch between frames of animation.
A HashMap can be useful for storing and keeping track of multiple different spritesheets, especially when those same spritesheets are used by multiple different objects (to avoid having duplicate spritesheets in memeory). I am sure there is a way to do what you want, perhaps defining int constants to represent each key. If you want to use Java Collections then List might be more appropriate than HashMap but I suggest going the spritesheet route.
Good luck, I would be interested to here how you get on and which way you choose to solve this.
Hi John,
The hashmap solution is working smooth right now but I will try to make a sprite sheet and observe the performance improvement. Also, I want to ask that hat would be the most efficient way to move bob facing left i.e. flipping the sprite while moving towards left direction?
Hi Nick,
Hopefully this example code will help. Of course you will have to do this for each and every bitmap, unless you have a spritesheet then you do it once. Also, depending upon what you are aiming for (memory efficiency versus CPU efficiency) consider storing the reversed copy of the bitmap so as not to have to keep performing the flip.
// Rotate a bitmap
// Create a Matrix object that will multiply every pixel by -1 on the x axis (reverse it)
// Code assumes their is a bitmap called originalBitmap that needs to be reversed
Matrix flipper = new Matrix();
flipper.preScale(-1, 1);
// Create a new bitmap based on the flipped original bitmap
Bitmap newBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), flipper, false)
// Draw the new bitmap
canvas.drawBitmap(newBitmap, x, y, paint);
Thanks John, It’s perfect.
Hi John
I’m struggling to understand what the following lines of code do:frameToDraw.left = currentFrame * frameWidth;
frameToDraw.right = frameToDraw.left + frameWidth;. Also from Simple Game Engine you had this code: fps = 1000 / timeThisFrame; what is the 1000 for?
the last thing I’m struggling to understand is the following line of code:bobXPosition = bobXPosition + (walkSpeedPerSecond / fps);
kind regards
Clifford
Hi, Clifford,
I will elaborate a little to try and make things clearer.
frameToDraw.left = currentFrame * frameWidth;
If you multiply currentFrame by frameWidth, then you have the starting horizontal position in the sprite-sheet of the current frame of animation.
frameToDraw.right = frameToDraw.left + frameWidth;
If you add frameWidth to the previously calculated frameToDraw.left then you have the ending horizontal position in the spite-sheet of the current frame of animation.
fps = 1000 / timeThisFrame;
As timeThisFrame is a measurement in milliseconds, dividing by 1000 gives a result in seconds.
bobXPosition = bobXPosition + (walkSpeedPerSecond / fps);
This line of code moves the horizontal position of bob ( bobXPosition) by the target walk speed(walkSpeedPerSecond). As the calculation takes place each and every frame we divide by fps. For more on this and the previous line of code, take a look at this Building a simple game engine.
Hope this helps a bit.
Good day
Thanks John.
kind regards
Hey john i made it with this tutorial bob is having a smooth walk, but just one question, why is bob starting position at the top of the screen, its starting point is over the fps counter, and in the last tutorial it started in the middle of the screen,just that jhon! thanks!
No reason. Feel free to change his position.
Hi,
there’s something I don’t understand, at a first glance. I read in the code:
private int frameWidth = 100;
private int frameHeight = 50;
should’t the height be bigger than width?
Hi Mauro,
Not sure what I was thinking when I chose these values. The code produces a squashed, fat character. This is counterintuitive so sorry for any confusion. Swap them around for a tall, skinny character. Thanks for pointing it out.
Hi! How do I make the character jump using a ontouch event? I have a class for a character and the animation of walking how am i going to make the character jump?
Hi Cletus,
To actually provide a working example would need another tutorial but here are some pointers. Add a Boolean perhaps, isJumping. Make the onTouchEvent set the Boolean. In the update method move the sprite upwards when isJumping is true. You can also record the time that the jump was started, store it in a long variable. Each frame check whether the appropriate amount of time has elapsed and if it has set isJumping back to false. Of course, this raises the question of falling. You can easily add an isFalling Boolean which you set to true when the jump has ended but you then need to do collision detection to determine when the player has landed on a platform(or whatever). See the Breakout tutorial for simple collision detection with rectangles example. Hope this is enough to get you started.
Where am I going to put it on the character class or the game class?
For the purpose of adapting this code I would put it all in GameView as that would make sense alongside the variables that control walk speed and horizontal position etc. If you take things further and work on a full featured long term project then you will probably be better having them all encapsulated in a player class.
You give great tutorials! I’ve been learning Java through another book, but this is what I really needed for help with an Android game. They helped me out so much I bought your ebook! Thanks!
Thanks very much Shane. I hope you build some great stuff and have a lot of fun with it.
Hello John!
I noticed for this code that you have used only one java class? How would you implement the sprite sheet if it was its own class?
The class wouldn’t be much different to this code. The wheretodraw and getcurrentframe would be in it as well as encapsulating the variables associated with those methods. I have got the code for this somewhere and will try and dig it out and post it. Give me a little while though.
Awesome! I have been trying to figure this out for awhile but with no success and I would appreciate it.
Hi Lance,
I have added a suggestion to the end of the tutorial. Hope this helps a bit.
Thanks! I’ll give it a go.
I just wanted to let you know that I have completed the code, thanks again!
I am getting all 5 of the pictures in one frame and no animation can anyone tell me where i went wrong
Hi Boreas,
Look closely at the call to drawBitmap. It is the line which actually draws the graphics on the screen. Make sure you are using the same method parameters as it is these values which could cause the symptoms yiu describe. If the call to drawBitmap is correct the next likely cause is the code that initializes the vales in the call each frame. If you still can’t see which part of the code causes the problem, ty copy & pasting the whole thing. I hope this helps a bit.
thank you i got it sorted out now i had the order of calls messed up.
Hello,
I would like to know how to get a Y value for my player right now he is in the the top right of the screen, and I want him to be at the bottom any help would be fantastic.
Hi Neil,
You can create a new variable perhaps bobYPosition and initialize it with values between zero(top of the screen) and the maximum vertical resolution of your device(bottom of the screen).
Then change this line of code to include your new variable.
// A rect that defines an area of the screen
// on which to draw
RectF whereToDraw = new RectF(
bobXPosition, bobYPosition,
bobXPosition + frameWidth,
bobYPosition + frameHeight);
Hope this helps.
I tried using bobYPosition, but It does not set it in the middle.
Do you mean it is off-center a little? If I understand you correctly it is because the origin (the place in the bitmap that is drawn at the specified coordinates of the screen) is the top left pixel of the bitmap, not the center pixel.
Hi John,
Is it okay to use some of your codes, and modify it for my project? Thanks!
Hi Jean, You can make as many games as you like! I hope you do. Can I request you don’t reproduce/republish the code especially in tutorial form? Good luck and be sure to let us know how your projects are coming along.
Hello
I would like to know. How can you get the screen height & width? So that I could set the animation when Bob went over the screen boundary and does not go beyond it
Hi Noms,
Try:
// Get a Display object to access screen details
Display display = getWindowManager().getDefaultDisplay();
// Load the resolution into a Point object
Point size = new Point();
display.getSize(size);
Now use:
size.x
size.y
Hope this helps.
What part should I code so that when bob exceeds my screen, I it will not go beyond the screen and go into hiding.
Try wrapping the following line in an if statement that stops the line from executing when the bobXPosition is too high.
bobXPosition = bobXPosition + (walkSpeedPerSecond / fps)
I tried adding a background, why does it still has a black background?
bg = new Background(BitmapFactory.decodeResource(getResources(), R.drawable.programming));
Are you drawing the background every frame? Are you drawing the background AFTER clearing the screen?
I’m trying to add an obstacle for the bob to jump over. But then the when i insert it, it won’t move (from right to left of the screen) like an obstacle that bob should pass over.
obstacle.drawBitmap(obstaclebmp,obstacleframeToDraw,obstaclewhereToDraw, paint);
The obstacle should be animated but i’m a bit confuse because it’s just animates frame by frame when I touch the screen.
Is there anyway(like e-mail, chat), I could ask you for help regarding about this? I’m trying to learn and create a game for self-study. Thanks
Hi Zeng, I hope to add more social features and perhaps a forum in the near future. I will do my best to answer any questions through these comments until then.
It depends what you are trying to achieve. If the obstacle is a moving object, perhaps like an enemy then you can move it the same way as you change Bob’s horizontal coordinates. If it is stationary then it depends whether you want a single screen in which the object doesn’t move and Bob moves towards or away from it. If you want a scrolling world where Bob stays in the centre of the screen and the whole world moves relative to his movement then you need a viewport as discussed in this viewport tutorial.
Or perhaps I have misunderstood your question.
Hi John.
The obstacle would be a moving a object and I’m trying to do it the same way as bob’s horizontal coordinates. But im really confuse on what part does bob moves in a horizontal way.
This is the line line that alters bob’s horizontal position.
bobXPosition = bobXPosition + (walkSpeedPerSecond / fps);
Then in the dram method the call to drawBitmap uses whatever the current value of bobXPosition is. Your new object will need a variable to keep track of its x position as well.
Hi John
can you please give me the source code.
Hi there, Unfortunately I lost track of the source code for this a while ago. It is all on the page however and copy & pasting should work.