In this section you'll implement what I like to call a "virtual camera," using imaging Lingo, in order to continually scroll the two background images. The first step in accomplishing this is to create images that can be looped.
Take a look at the following image, which is what my brother Brian gave me for the ground asset:
This image is 1200 pixels wide, and the game's Stage is 800 pixels wide. Placed on Stage, with the left edge of the image aligned with the left edge of the Stage, the lighter portion of the image will be what is showing:
Now, if the image is scrolled to the left until you reach the end, or right edge of the image, what is showing on Stage will not be what showed when you first started scrolling. If you were to reset the image back to the start to loop it, you would see a visible jump, which isn't acceptable. To seamlessly scroll the image, the final section being displayed must match the start section.
The easiest way to do this is to copy the first 800-pixel-wide section of the image and paste it at the end of the image, resulting in an image that is 2000 pixels wide. This is what was done with the two background images, the result of which is shown in the ground image that follows:
As you can see, the second, lighter section of the image now matches the first section, so scrolling the image will be seamless.
Now, let's create a behavior that will use imaging Lingo to scroll the image. The behavior will include a speed property, set within a getPropertyDescriptionList handler so that the same behavior can be used on both the ground and the mountains. The mountains are further in the background, so you want them to move slower than the ground in front of them.
Stop the movie if it is playing and make sure the internal cast is active. Right-click the ground sprite and select Script from the context menu. Delete the default mouseUp handler, then create the property declarations, the getPropertyDescriptionList handler, and the beginSprite handler as shown:
property sp, myRect, original property lastTime, moveAmount, myTime on getPropertyDescriptionList pList = [:] addProp pList, #myTime, [#comment:"Time to scroll (in ms):", #format:#float, #default:30000] return pList end on beginSprite me sp = sprite(me.spriteNum) myRect = sp.member.rect myRect.right = 800 moveAmount = (sp.member.width - 800) / myTime original = sp.member.image.duplicate() lastTime = _system.milliseconds end
First, the getPropertyDescriptionList (GPDL) handler is used to get the value for the myTime property. This value will be used to calculate the distance to move per millisecond in the beginSprite handler. Using the GPDL will allow the same behavior to be used on multiple sprites.
Within the beginSprite handler, the property sp is set to the current sprite, making it quicker to reference the sprite later. Next, myRect is set to the rect of the sprite's member. If you select the ground member in the cast and check the Property inspector, you will see that the dimensions of the bitmap are 2000x112. The rect of that cast member is therefore rect(0, 0, 2000, 112). Remember, a rect's order is as follows: rect(left, top, right, bottom)
After getting the rect of the member, the next line sets the rect's right to 800, which is the width of the Stage. You now have a rect the width of the Stage and the height of the bitmap. This rect is your virtual camera and will be slid along the length of the bitmap. Each time it's moved, you'll use imaging Lingo to copy pixel the section of the bitmap the rect is over onto the Stage. The following image shows the rect, in its starting location. (I have slightly offset it so that you can see it better.)
Next, the moveAmount is calculated like it was in the alien ship behavior. The moveAmount is the amount to move per millisecond and is the total distance to move divided by the total timethe distance being the width of the bitmap minus 800. You subtract 800 from the distance because the first 800 pixels of the image are already in view. You can leave out the slight compensation if you like. Doing that will simply make the scroll a bit faster, which can easily be corrected by having it take more time.
The next line creates a duplicate of the cast member's image, and stores it in the original property. You will use this image to copy from because it is much faster to copy from an image in RAM than it is to copy directly from a cast member.
Finally, the lastTime property is set to the current value of the milliseconds. This will be used to calculate the time elapsed between frame updates, so that you know how much to scroll the image.
Now let's complete the behavior by creating the enterFrame handler.
After the beginSprite handler, create the following enterFrame handler:
on enterFrame me currTime = _system.milliseconds timePassed = currTime - lastTime amtToMove = timePassed * moveAmount myRect = myRect + rect(amtToMove, 0, amtToMove, 0) sp.member.image.copyPixels(original, rect(0,0,800,myRect.bottom), myRect) if myRect.right > sp.member.rect.right then myRect.right = 800 myRect.left = 0 end if lastTime = _system.milliseconds end
The first three lines inside the handler calculate the amount to move based on the number of milliseconds that have elapsed since the last frame update. The amount to move is the number of pixels to slide the virtual camera along the original bitmap. In order to modify the original rect, stored in myRect, you can use Lingo's ability to add rects together:
myRect = myRect + rect(amtToMove, 0, amtToMove, 0)
Here myRect is incremented using rect(amtToMove, 0, amtToMove, 0). By only modifying the left and right properties, the rect slides "forward" by the value stored in amtToMove.
The next line uses Lingo's copyPixels function to do the actual scrolling. What happens here is you are copying a section from the original image and placing it into the section of the sprite being seen on the Stage. Recall that copyPixels works like so:
destination_image.copyPixels(source_image, destination_rect, source_rect)
You are copying directly into the sprite's member from the image stored in the original property. The destination rect is
rect(0, 0, 800, myRect.bottom)
This rect is the portion of the bitmap seen on Stage. Using myRect.bottom for the rect's bottom allows you to scroll variable height bitmapsas is the case with the ground and mountains.
Now, the source rectangle that you are copying from is the rect stored in myRect, which is the rect that you are continually incrementing, or sliding along the length of the image.
As you increment myRect the copyPixels function copies that portion of the image into the section of the member being seen on Stage. In this manner the bitmap appears to scroll across the screen.
The if statement that follows the copyPixels function checks to see if the moving rectangle has progressed beyond the edge of the bitmap. If it has, it is reset back to the beginning, creating the looping effect:
if myRect.right > sp.member.rect.right then myRect.right = 800 myRect.left = 0 end if
Finally, lastTime is reset to the current value of the milliseconds in order to again calculate the elapsed time between frames.
Because you are using the copyPixels function to copy directly into the cast member, you will need to reset the member's image on endSprite so that the original image is retained.
Add the following endSprite handler to the behavior:
on endSprite me sp.member.image = original end
Now, when the movie stops, or the playhead leaves the sprite's span, the member's image will reset back to the original. This is possible because you used the duplicate() function to actually create a new image object, instead of merely creating a pointer to the original.
Name the script ground_scroll and close the script window. In the alert that appears, choose Use Defaults.
The ground sprite will scroll along at the default time of 30000 milliseconds.
Rewind and play the movie.
The ground now scrolls, making the game appear much more dynamic. All you need to do now is attach the scroll behavior to the mountains.
Stop the movie and drag the ground_scroll behavior from the internal cast and drop it onto the mountains sprite in channel 1. Set the time to scroll to 45000 and press OK to close the dialog. Rewind and play the movie.
Both the ground and mountains scroll now, with the mountains scrolling a little slower than the ground. This gives a good feeling of distance, although you should feel free to play with the speed to get it to your liking.
Stop the movie and then save it.
Now that the enemies, ground, and mountains are all animated, it's time to add your player's ship. In the next lesson you will add the ship, and the Lingo necessary to control it with the arrow keys on your keyboard.