6--Sprite Animation with Android

If you followed the series so far we are pretty knowledgable in handling touches, displaying images and moving them around.

目前为止,我们已经知道了处理触摸时间,画图,移动他们

But a moving image it’s a pretty dull sight as it looks really fake and amateurish. To give the characters some life we will need to do more than that. That is what animation is all about. A rock is an inanimate object and even if it is thrown, it doesn’t change its shape. A human on the other hand, is very animated. Try throwing one and you’ll see twitching limbs and even screams in the air.

但是移动的图片看起来太傻了,为了给角色新的生命力,我们需要做的更多。那就是动画的功用。一块石头是没有动作的物体,即使它被扔出去,也不会改变形状,相对来说,人是会动的,扔出去一个人,他胳膊会动,甚至会叫喊。

Let’s just resort to examining walking which is pretty complex in its own. Imagine a human crossing your way (just in 2D). You’ll notice different displays of the body. Left foot in front, right hand in front while the oposite limbs are behind. This slowly changes so the left foot remains behind while the right progresses along with the body. But at one point the cycle repeats. If you don’t close your eyes you see the person progressing smoothly. If you close your eyes and keep them closed for a bit and open them again the person already went on and is in a different position. Try blinking quite rapidly and you will see something like the old black and white comedies. That is low frame rate. More on FPS here.

 

Actually we do want a low frame rate walking for this tutorial, just like this.



上面的角色看起来有点狡诈,它是从monkey island里面找出来的,她是elaine marley
这个被叫做精灵,就是一个简单的二维图形或者动画

为了做上面的动作,我们需要走路的每一帧

这是一个150像素宽的图形,每一帧是30像素

看下图,它解释的更好一些

为了显示上面的动画,我们需要把每一帧加载成独立的图形,然后以有规律的间隔来显示他们。或者我们可以把整个图片加载进来,然后用android提供的切割方法,只是显示对应的帧
这样做有点麻烦,我们知道我们有5帧,每一帧是30个像素,我们定义一个矩形,他的宽度是一帧,高度是图形的高度。

下图显示了我是如何切割前两帧的,其余您自己完成吧

下面开始我们的工程吧,我们需要前面几章的知识,主要是game loop和图形显示的的内容。

我们需要一个对象来做动作,我们用elaine,所以我创建了ElaineAnimated.java的类

01 public class ElaineAnimated {  

02    

03     private static final String TAG = ElaineAnimated.class.getSimpleName();  

04    

05     private Bitmap bitmap;      // the animation sequence  

06     private Rect sourceRect;    // the rectangle to be drawn from the animation bitmap  

07     private int frameNr;        // number of frames in animation  

08     private int currentFrame;   // the current frame  

09     private long frameTicker;   // the time of the last frame update  

10     private int framePeriod;    // milliseconds between each frame (1000/fps)  

11    

12     private int spriteWidth;    // the width of the sprite to calculate the cut out rectangle  

13     private int spriteHeight;   // the height of the sprite  

14    

15     private int x;              // the X coordinate of the object (top left of the image)  

16     private int y;              // the Y coordinate of the object (top left of the image)  

17    

18 } 

提倡私有的属性,按时有些需要注意的

  • bitmap是包含所有帧的图形,本文中的第二个图片
  • sourceRect是所选的矩形,是下面图片中的蓝色小窗口,矩形一次移动一帧
  • frameticker 是行走序列中最近的帧变换的java时间戳,注意这个不是游戏的FPS,是行走的FPS,如果我们要elaine在一秒走完,需要5帧,因为我们的图形有5帧,为了得到一个平滑的动画,我们需要30帧,但是这不是重点
  • frameperiod代表了一帧需要显示的时间,如果循环在一秒完成,那么这个值是0.2秒,每一帧显示了0.2秒

构造函数如下

01 public ElaineAnimated(Bitmap bitmap, int x, int y, int width, int height, int fps, int frameCount) {  

02         this.bitmap = bitmap;  

03         this.x = x;  

04         this.y = y;  

05         currentFrame = 0;  

06         frameNr = frameCount;  

07         spriteWidth = bitmap.getWidth() / frameCount;  

08         spriteHeight = bitmap.getHeight();  

09         sourceRect = new Rect(0, 0, spriteWidth, spriteHeight);  

10         framePeriod = 1000 / fps;  

11         frameTicker = 0l;  

12     } 

 

I’m assuming that the frames are the same width so I calculate the width of the rectangle by dividing the width of the image with the number of frames. I also pass in the fps which is again, the frames per second of the walk cycle not the game FPS.

 

Elaine will have an update method of her own as she is an animated object and she needs to look good and she is in charge of dragging her feet. Because the period of the game update cycle and Elaine’s one might be (in this case is) different we pass the actual game time as a variable so we know when we need to display the next frame.
For example the game runs very fast and the update is called every 20 milliseconds and we need to update the frame every 200ms, then the progression of the frame will happen at every 10th game update.

Here’s the code:

01 public void update(long gameTime) {  

02     if (gameTime > frameTicker + framePeriod) {  

03         frameTicker = gameTime;  

04         // increment the frame  

05         currentFrame++;  

06         if (currentFrame >= frameNr) {  

07             currentFrame = 0;  

08         }  

09     }  

10     // define the rectangle to cut out sprite  

11     this.sourceRect.left = currentFrame * spriteWidth;  

12     this.sourceRect.right = this.sourceRect.left + spriteWidth;  

13 } 


The update is called from the main game panel (check previous entries how that works). This is the update method of the MainGamePanel class.

1 public void update() {  

2     elaine.update(System.currentTimeMillis());  

3 } 


 

The update method is simple (Elaine’s). It increments the frame if the passed in time (which is the system time when the update method was called) is greater than the last time (frameTicker) the frame was updated plus the period of the next update.
If the next frame is beyond the last, we reset the cycle.

After all that area from which the image will be cut out is defined as the sourceRect.
That’s it. Now let’s go on to display it.

1 public void draw(Canvas canvas) {  

2         // where to draw the sprite  

3         Rect destRect = new Rect(getX(), getY(), getX() + spriteWidth, getY() + spriteHeight);  

4         canvas.drawBitmap(bitmap, sourceRect, destRect, null);  

5     } 


at is all. We set the destination rectangle as to where to draw the cut out image. It is at Elaine’s position (X and Y set in the constructor).

 
1 canvas.drawBitmap(bitmap, sourceRect, destRect, null); 

tells android to cut out the image defined by sourceRect from the image contained in bitmap and draw it into the rectangle on the canvas defined by destRect.

The draw is called from the game panel’s render method triggered by the game loop (check previous entries).

The MainGamePanel.java differs slightly from the one from previous chapters. I got rid of all the droidz and added just Elaine.

01 private ElaineAnimated elaine;  

02    

03     public MainGamePanel(Context context) {  

04         //* ... removed ... */  

05    

06         // create Elaine and load bitmap  

07         elaine = new ElaineAnimated(  

08                 BitmapFactory.decodeResource(getResources(), R.drawable.walk_elaine)  

09                 , 10, 50    // initial position  

10                 , 30, 47    // width and height of sprite  

11                 , 5, 5);    // FPS and number of frames in the animation  

12    

13         // create the game loop thread  

14         thread = new MainThread(getHolder(), this);  

15    

16         //* ... removed ... */  

17     } 


 

Elaine is instantiated in the panel’s constructor and is given an initial positon of (X=10, Y=50). I pass in the width and the height of the sprite too but that is ignored anyway, but you can modify the code.
The FPS is very important and the number of frames too. FPS says how many frames are to be shown in one second. The last parameter is the number of frames in the cycle.

The thread and activity classes haven’t changed at all. You can find them in the download as they are quite long to be pasted. The image is named walk_elaine.png and it was copied to /res/drawable-mdpi/ so android can pick it up automatically.

If you run the application you should be seeing Elaine performing walking cycles in one place. We should have used jumping as that can be performed in one place but you get the idea.

 

Elaine Walking

Elaine Walking

 

Enhancement

To make some neat additions modify Elaine’s draw method so it displays the original image containing the sprites from which the frames are extracted.

1 public void draw(Canvas canvas) {  

2     // where to draw the sprite  

3     Rect destRect = new Rect(getX(), getY(), getX() + spriteWidth, getY() + spriteHeight);  

4     canvas.drawBitmap(bitmap, sourceRect, destRect, null);  

5     canvas.drawBitmap(bitmap, 20, 150, null);  

6     Paint paint = new Paint();  

7     paint.setARGB(50, 0, 255, 0);  

8     canvas.drawRect(20 + (currentFrame * destRect.width()), 150, 20 + (currentFrame * destRect.width()) + destRect.width(), 150 + destRect.height(),  paint);  

9 } 


 

This just displays the image at (20, 150) and creates a new paint object so we can paint over the current frame on the original image.
The method setARGB creates a semi-transparent green paint. The first value is 50 which means it’s 75% transparent. 0 is completely transparentwhile 255 is fully opaque.
After everything was drawn we paint a rectangle of the size of a frame onto the original image so you see which frame is being displayed in the motion.

 

Walking with Current Frame Painted

Walking with Current Frame Painted

 

That’s it. Run it and you have your first sprite animation.


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值