分享
 
 
 

Drawing & Animation II

王朝vb·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Drawing & Animation

Using the Win32 GDI #2

This three part tutorial first appeared some years ago on the old VBExplorer.com . Since then several errors and bugs have been discovered by various users on the VBExplorer.com Forums. This version reflects the changes made to overcome the bugs and erros. The text will note where a bug correction and / or update has been done, and of course also why.

A big 'Thank you' goes out to all the people reading and reporting the bugs in the previous version of this tutorial. Please provide comments, questions, bug reports etc. at the VBExplorer.com forums in the Graphics & Game Programming section.

Backbuffering, AutoRedraw and Refresh

In the previous section we used the AutoRedraw property and the Refresh function to force a copy of the form to be stored in memory and then updated. The same thing can of course be accomplished on a picture box.

There is also an alternative to this scheme, namely BackBuffering. Backbuffering is a simple technique, where you keep a 慶opy?of the gaming field (display area) in a non-visible area. All the sprites and other drawings are drawn into the backbuffer, which is then drawn all at once onto the display area.

If we could view the process it would look something like this:

Step 1: Draw all the masks and sprites onto the non-visible back buffer

Step 2: Draw everything in the back buffer to the visible area

So why does this produce flickerless animation? The main reason is that we only use one Blit operation to draw everything we need from the backbuffer to the front display area. This disables all the intermediate updates, which might occur between each blit operation, and thus produces a clean drawing. Get it? Basically you are doing all the work with the sprites and masks off screen, where the game player doesn't see it and blitting to the play area once. The other way all the operations take place in the visible play area.

Which method is then the best method? Well, it depends on your type of application and the design you have made. The sample project BACKBUF found in BACKBUF.ZIP demonstrates both methods and times the amount of time elapsed. We tested both of the methods with this sample project and with some additional projects. All the empirical data from the tests conclusively demonstrate that neither method is faster. The best would of course then be the AutoRedraw ?Refresh method, since it does not use the extra picture box to store the backbuffer in. But it also depends on the size of the drawing area you have. If it is big, then the Refresh ?Autoredraw method -might appear slightly slower than the backbuffering scheme. So the choice is really up to you, test your game with both schemes and use the one you like best.

Sprite Animations and StretchBlt

Now we know how to move a sprite around the window, but usually that is not enough to form a complete game. Sometimes there is also a need to change the actual sprite image, in order to accommodate certain conditions of a game.

The actual implement of such a scenario is actually quite simple, it is just a matter of changing the actual sprite picture. So if we had a need of a small ball Note: Will change this to a small animated character instead rotating around the screen, we would simply make each rotation in a drawing program and draw each in a specified order. Very much like a little cartoon animation block.

In the sample project ANIMATION contained in ANIMATION.ZIP we will do just that, create a ball with shifting colors. The first thing to note is that so far we have used a picture box to hold each separate bitmap. If we were to do that for each sprite in a game with several animated sprites, we would be using up quite a number of picture boxes. So instead, we will draw all the animation frames of the sprite in just one bitmap, and only draw the part of this bitmap that we need. The bitmap would look like this:

Notice the apparent black sprite, which actually represents a black circle.

We already know that the sprite is 64 pixel wide and 64 pixels high (this was of course preset when the sprites were made). We also know that we need to feed the BitBlt function with information on only the upper left corner of the sprite, and the dimensions of the sprite. So it is just a simple matter of moving this upper left point from each frame to the other. Since the image only has one row of sprites, the Y position will be constant (always 0). So the only really challenging thing is to move the X position a given distance in pixels with each new frame we blit. To accomplish we must keep an eye out for the current frame, which is to be displayed. Using a simple variable as a frame counter does this. This frame counter is updated every time the frame is changed, so it will always have a value equal to the current frame. If we also use this variable as a multiplier to the constant width of the sprite, we actually get exactly what we want.

As you can see from the illustration, then the upper-left X position of a frame is equal to the FrameNumber ?1 multiplied by the width of the sprite. So by using this scheme we move the X position by an amount which is equal to the width of the sprite, on each update of the frame.

FrameNumber = (FrameNumber Mod MaxFrames) + 1

This actually ensures that the FrameNumber variable will never be more than MaxFrames and never less than 1. The method also produces a problem, since when we use it to get the X positions we will get values in the range from 64 (Frame 1) to 640 (Frame 10). These values represent the rightmost X position of the sprites, which is of no use to the BitBlt function. The solution is of course to simply subtract 1 from the FrameNumber when we calculate the X position, and thereby getting X values in the range of 0 to 576, or all the leftmost positions of the sprite frames.

The same thing could be employed to keep the sprite from moving out of bounds of the window:

X = (X Mod Me.ScaleWidth) + 1

Y = (Y Mod Me.ScaleHeight) + 1

So now that we have all this wonderful background information, it should be simple to implement in a timer event and draw the thing:

Private Sub TimerAnimation_Timer()

Static X As Long

Static Y As Long

'Clear the form, since we do not have a background

Me.Cls

'Draw the mask

BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picMask.hDC, _

(FrameNumber - 1) * SpriteWidth, 0, vbSrcAnd

'Draw the sprite

BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picSprite.hDC, _

(FrameNumber - 1) * SpriteWidth, 0, vbSrcPaint

'Update frame number

FrameNumber = (FrameNumber Mod MaxFrames) + 1

'Update drawing positions

X = (X Mod Me.ScaleWidth) + 1

Y = (Y Mod Me.ScaleHeight) + 1

'Force an update of the form

Me.Refresh

End Sub

We employ the usual scheme of first drawing the mask and then the actual sprite. As you can see from the picture box with the masks, we have created a mask for all the sprites, and draw these mask frames in the same manner as the sprites. This would not be necessary in real life with this particular sprite example. Since each frame of this sprite's animation sequence is the same shape and therefore has the same transparent and visible areas we could have used the same mask for each circle.

Run the project and press the Start button. Observe how the sprite changes color as it moves down over the form.

More on Timing

This is all fine and good, but you may run into a situation where you do not want the same frame change (rate) as the timer interval. For example let's imagine that you have created a scene with some slow blinking lights which you want to blink once every second. If you were to blink the lights using the game loop, which we'll say is firing once every 20 milliseconds, the lights would blink so fast you wouldn't be able to see it. The problem here is to delay the blinking of the lamp, so it will only blink, or Blit, once each second and not every time the game loop is fired. That is why we will use the GetTickCount API, to check if a second has elapsed, and if it has, fire the changing of the lamp. The GetTickCount() function returns the number of milliseconds since Windows was started. By calling this function and using it to check the elapsed time since a frame was last updated, we can determine whether or not it is time to change the frame. For this task we need two variables, one to keep track of the current elapsed time, and one to keep track of the time since the last frame was updated. In the the ANIMATION project these are declared as LastTick (for the last time since a frame was updated) and CurrentTick (for the current time). A constant, representing the defined time we want between each frame update, is also needed. In the project the constant FrameTime is used for this. It is set to a value of 1 second.

The process of determining whether frame should be updated or not, is very simple. We first get the current time and store it in the CurrentTick variable. Then the time since the last frame update is subtracted. The result is then compared against the defined interval between the frames and if it is greater, the frame is updated. In the code if would look like this (The bold areas):

Private Sub TimerAnimation_Timer()

Static X As Long

Static Y As Long

'Clear the form, since we do not have a background

Me.Cls

'Draw the mask

BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picMask.hDC, _

(FrameNumber - 1) * SpriteWidth, 0, vbSrcAnd

'Draw the sprite

BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picSprite.hDC, _

(FrameNumber - 1) * SpriteWidth, 0, vbSrcPaint

'Check to see if we need to update th frame

If CurrentTick - LastTick > FrameTime Then

FrameNumber = (FrameNumber Mod MaxFrames) + 1

LastTick = GetTickCount()

End If

'Update drawing positions

X = (X Mod Me.ScaleWidth) + 1

Y = (Y Mod Me.ScaleHeight) + 1

'Force an update of the form

Me.Refresh

End Sub

If you run the sample project now, you can observe that the sprite is moved a small distance before the frame of the sprite is updated.

This is just one way to control the frame rate of a given sprite animation. You could also have used the traveled distance of the sprite to change the frame, or more commonly a user action could trigger a frame change.

We may want to use a blinking lamp as part of the demo project since we use the example and the graphical representation may be very helpful. We can also encourage them to change the value of FrameRate or add a slider control so that they can see how they can control the blinking-Burt.

StretchBlt

There is also another way of animating a sprite, which does not require extra bitmaps, but simply changes the drawn sprite directly. The function for this is the StretchBlt function, which, as the name implies, can stretch or shrink a sprite.

The StretchBlt function is very similar to the BltBit function. Both require a source and destination DC, and they both do raster operations. The difference is that StretchBlt will stretch or shrink the size of the source rectangle to fit the size of the destination rectangle. The declaration is as follows:

Declare Function StretchBlt Lib "gdi32" (ByVal hdc As Long, _

ByVal x As Long, ByVal y As Long, _

ByVal nWidth As Long, ByVal nHeight As Long, _

ByVal hSrcDC As Long, ByVal xSrc As Long, _

ByVal ySrc As Long, ByVal nSrcWidth As Long, _

ByVal nSrcHeight As Long, ByVal dwRop As Long _

) As Long

With StretchBlt you can perform some tricks, that might otherwise require a new bitmap.

The Sample project STRETCHBLT found in STRETCHBLT.ZIP moves a sprite around the screen, stretching and shrinking it as it goes.

We have two picture boxes in the sample project, serving as storage for the sprites. The idea of the program is quite simple, stretch the sprite to a size of 96 pixels and then shrink it to 32 pixels while the sprite moves across the form.

Private Sub TimerStretch_Timer()

Static X As Long

Static Y As Long

'Clear the form, since we have no background

Me.Cls

If Shrinking Then

Stretch = Stretch - 2

Else

Stretch = Stretch + 2

End If

If Stretch < 32 Then Shrinking = False

If Stretch > MaxStretch Then Shrinking = True

'Stretch the sprite onto the form

StretchBlt Me.hdc, X, Y, Stretch, Stretch, picMask.hdc, 0, 0, _

SpriteWidth, SpriteHeight, vbSrcAnd

StretchBlt Me.hdc, X, Y, Stretch, Stretch, picSprite.hdc, 0, 0, _

SpriteWidth, SpriteHeight, vbSrcPaint

X = (X Mod Me.ScaleWidth) + 2

Y = (Y Mod Me.ScaleHeight) + 2

. Force update of the form

Me.Refresh

End Sub

The first thing that is done in code is to check the stretching variable. This variable can be in one of two states, Shrinking or Stretching. To identify the different states we'll create a Boolean variable named Shrinking and set it to either True of False, depending on the desired state. The Stretch variable is allowed to be either 32 pixels less or more than the original sprite size, if they get out of this range, the state is changed, and the opposite stretch action will be used.

The sprite is drawn in the usual way, first the mask and then the sprite. But instead of using the BitBlt function we call the StretchBlt function, and set the destination width and height to the value of the stretch variable.

As you can observe, the StretchBlt can be a useful function for doing simple tricks on a sprite. You should use it cautiously though, since it may be a little slower than the ordinary BitBlt function, since some cycles are used when it stretches or shrinks an image.

Using and creating memory DCs

The follwing section has been updated to reflect bug fixes.

In the previous version the bitmap handle was deleted in the GenerateDC function. This apparently cause some leaks as the created DC was not deleted correctly. Furthermore there were some wrong constant declaration and a error check which was faulty.

So far we have been using picture boxes as storage areas for the sprites. This does not come without a price since the picture box adds additional time and resource overhead to the animating scheme. This problem can be overcome by simply creating our own Device Contexts to hold the bitmaps. These Device Contexts reside in memory and allow us to perform the required operations without resorting to PictureBoxes. Before doing anything, some more specific information is required on what a Device Context is, and what can be done with it.

A Device Context is a Windows structure, with several important and useful attributes. Each of these attributes has a default value, which is set when the device context is created. The most important attribute of device context to us, is the Bitmap attribute. This attribute is initially set to nothing (meaning there is no bitmap associated with the device context). This attribute can be set by using the SelectObject API function.

To create a memory device context we use the API function CreateCompatibleDC, which returns a memory DC (a long value). In order to select the bitmap into the device context we need a handle to the specific bitmap. This handle can be obtained by using the LoadImage function. This function can load a bitmap from file, and return a handle to the loaded bitmap. The last thing to do is to select the handle of the loaded bitmap into our newly created memory DC, and we now have a useable device context for blitting.

The MEMORYDC sample, in the MEMORYDC sub-directory of the Chap1 directory, demonstrates the steps we have just outlined and will be exploring next, in creating a compatible device context and selecting a bitmap.

Let's put all of the required code into a reusable function which will take care of the creation of a memory device context for us and just requires a filename to the actual bitmap. The function will either return a device context (long value) or 0 if something went wrong.

The code for our function looks like this:

Public Function GenerateDC(FileName As String, ByRef MemDC As Long, ByRef hBitmap As Long) As Long

'Create a Device Context, compatible with the screen

MemDC = CreateCompatibleDC(0)

If MemDC = 0 Then

GenerateDC = 0

Exit Function

End If

'Load the image

hBitmap = LoadImage(0, FileName, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE Or LR_LOADFROMFILE Or _ LR_CREATEDIBSECTION)

If hBitmap = 0 Then 'Failure in loading bitmap

DeleteDC MemDC

Exit Function

End If

'Throw the Bitmap into the Device Context

SelectObject MemDC, hBitmap

'Return OK

GenerateDC = 1

End Function

A device context created with the CreateCompatibleDC must be deleted by calling the DeleteDC API function and the Bitmap handle returned from LoadImage much be deleted with the DeleteObject API. So let's create a reusable function for this called DeleteGeneratedDC. This function takes two arguments, a DC to be deleted and the Bitmap Handle for the corresponding DC.

Private Function DeleteGeneratedDC(hBitmap As Long, MemDC As Long) As Long

DeleteGeneratedDC = DeleteDC(MemDC)

DeleteObject hBitmap

End Function

Run the sample project. Press the Load bitmap button. The bitmaps are now loaded and ready for use. We use the usual BltBit function to Blit them from the memory context and into the device context of the form. Press the Draw the sprite button, and observe how the sprite is blitted transparently onto the form.

One word of advice when using this scheme to create a memory device context: Be observant of the scope of the variables which you store the device contexts in. If the variable goes out of scope, and you have not deleted it with the DeleteDC function, you抣l start losing resources. So always delete the created device contexts to be on the safe side.

End of Part II of the three part Drawing and Animation Tutorial by Burt Abreu & S鴕en Skov. Download all Samples]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有