Tutorial: AS3 Basic Blitting #2 : Rotation – Part 1
In this tutorial will will explore basic blitting from a tile sheet to a single blit canvas. We will do so by implementing a common animation problem – rotation. We will also demonstrate the simple math tricks that are used to find the starting point to copy from the tile sheet given the tile number and width and height of the sprite to be copied.
As part of my 24 hour game challenge, I am creating a game similar to Atari 2600 Air-Sea Battle mixed with the game Point Blank.
This has opened up an opportunity for me to deliver a tutorial based on one of the game display concepts that I need to achieve: Blitting an object around a center point in space. I want to tackle radial rotation around a center point, and we will get there in part 2. We must first take a look at simple rotation using a sprite sheet before we get to that slightly more advanced topic. As you will see, they are very much related.
First, here is the tile sheet we will be using. The enemy in this case is a helicopter that is a simplified version of one that I will be using for my game. In this version, the helicopter has one frame of animation and 36 different angles it can be positioned in. We will do this in both the clockwise and counter clockwise directions. This gives us 72 different frames of animation that must be pre-rendered. There certainly are other ways to do the same thing, but they are more advanced. In this instance we will use the basic sprite sheet method of blitting from png sheet to the screen canvas. Whatever angle the helicopter is rotated in will be the angle that it is traveling and it will do its traveling around a center point on the screen. I would usually have more than one frame for the helicopter (maybe have the rotor look like it’s spinning for example). I have left out the second frame of animation for this basic tutorial so what we have below are the 36 frames needed to render the helicopter at angles 0-359 with 10 degrees in between each step. I have split the clockwise and counter clockwise rotations into two separate sheets. I have done this purely out of convenience. This certainly could have been on one sprite sheet, but splitting it into two makes finding the right sprite to use easier.
Here is the clockwise sheet:
Here is the counter clockwise sheet:
These were both created in Fireworks. I started by pixel drawing the first frame (actually with a cool green pallet, but it didn’t work well on a black background). I then copied it to the subsequent tiles and rotated it 10 degrees each time. One note on rotating a bitmap, always make sure you use the original tile for each new rotated tile. For example, I rotated the first tile by 10 degrees to make the second tile. I did NOT rotate the second tile by 10 degrees to make the third, but rotated the first by 20 degrees. Unlike rotating vectors in Flash, bitmaps degenerate into what can only be described as PIXEL MUSH on each rotation because of deformations. If you look closely even at the single deformations I have created, all but the direct right angles look a tab bit strange close up.
Also, I have chosen a pretty ugly pink sprite because it will contrast well with the black background we are going to use in our demo. My pretty green sprites were hard to pick out from the background because I used a green that was too dark in places.
The basics of circular motion
We have covered the basics of blitting and blitting with transparency before, so if you want to look up the details, feel free. We will cover them in summary a little later. The first example we will run through is how to do a simple blit of our sprite sheet corresponding to the angle we want to rotate our sprite. It will end up looking like the helicopter is simply rotating in place, This is really easy if we are using display objects, we just need to set the rotation property with a value of -180 through 180. But with blitting, we need to use a different approach. That is why I spent the time creating the rudimentary sprite sheets above. We can simulate rotation simply by running through the sprites in order like a flip book animation. That is exactly what we have done before in the earlier blitting lessons. In this case, we will take it a step further and force our blitted sprite to appear to point in the direction of a calculated angle.
In the clockwise sprite sheet above, the first sprite cell is a helicopter pointed to the right. That sprite will correspond to the angle 0. The last sprite in the sheet (#35, 0 relative) corresponds to angle 350 (360 is the same as 0). If we know our angle, we can display the right sprite cell to correspond to the direction we want to helicopter to face.
To rotate an object using math, rather than the built in functions for display objects, we need to first choose a center point for our object. In this case I have chosen 50x and 50y as the center point. We then choose a radius from that center point (the distance from the center) that we want out object to be placed. If we choose a radius of 0, then our object will appear to rotate in place like an Asteroids ship. If we choose a radius other than 0, our object will appear to rotate AROUND the center point at the radius distance.
Some Math that you won’t need for this example, but will prepare you for part 2
So, for our first example, we will have the object appear to rotate in place. To find the x and y values for the location of our object in space, we use the cosine and sine of our angle (cos for x, sin for y) and multiply by the radius. We add this to our start position 50x and 50y to get the location in space for our object.
Because we are going to be rotating the object in place, our radius is 0. This calculation is completely unneeded for a radius of 0 because it will always return 0 to be added to both the x and y value, leaving your object exactly where it is, but rotating it on the center point. Here is the calculation anyway:
x=centerX+Math.cos(angle) * radius; or x=50+Math.cos(0)0; or in other words, 50;
y=centerY+Math.sin(angle) * radius; or y=50+Math.sin(0)0; or in other words 50;
For this lesson the math is not as important because we are simply rotating on a center point. In lesson 2 we will complicate this more by actually using a radius to give the illusion of radial circular motion.
That is where we will start our first example we will leave out this calculation because we just want to blit the object around a center point.
Here is what we are going to try and accomplish:
/downloads/blog/2008_blit_around_point/blit_around_a_point_1.swf
Obviously there is nothing earth shattering about simply rotating an object in Flash. Our version uses a high-speed blitting technique rather than the standard display object rotation. I only have 36 frames of animation, so the spinning helicopter looks a little choppy. Plus, I may have been off a pixel or two from my center point on a couple frames, but you get the general idea of when we are doing this.
Here is the code:
[cc lang=”javascript” width=”550″]
package
{
import flash.display.Bitmap;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.display.BitmapData;
import flash.geom.*;
import flash.events.*;
public class BlitRadial1 extends MovieClip
{
trace(“class radial 1”);
var tileSheet:BitmapData;
var backgroundBD:BitmapData;
var backgroundRect:Rectangle;
var backgroundPoint:Point;
var canvasBD:BitmapData;
var canvasBitmap:Bitmap;
var tileWidth:int;
var tileHeight:int;
var heliRect:Rectangle;
var heliPoint:Point;
var heliX:int;
var heliY:int;
var heliTilesLength:int;
var animationIndex:int;
var animationCount:int;
var animationDelay:int;
var spritesPerRow:int;
public function BlitRadial1() {
tileWidth=36;
tileHeight=36;
backgroundBD=new BitmapData(400,400,false,0x000000);
backgroundRect=new Rectangle(0,0,400,400);
backgroundPoint = new Point(0, 0);
canvasBD=new BitmapData(400,400,false,0x000000);
canvasBitmap=new Bitmap(canvasBD);
tileSheet=new heli_sheet(360,144);
heliRect=new Rectangle(0,0,36,36);
heliX=50;
heliY=50;
heliTilesLength=36;
animationIndex=0;
animationCount=0;
animationDelay=3;
spritesPerRow=10;
heliPoint = new Point(heliX, heliY);
addChild(canvasBitmap);
addEventListener(Event.ENTER_FRAME, gameLoop);
}
public function gameLoop(e:Event) {
drawBackground();
drawHeli();
}
private function drawBackground():void {
canvasBD.copyPixels(backgroundBD,backgroundRect, backgroundPoint);
}
private function drawHeli():void {
if (animationCount == animationDelay) {
animationIndex++;
animationCount = 0;
if (animationIndex == heliTilesLength){
animationIndex = 0;
}
}else{
animationCount++;
}
trace(“animationIndex=” + animationIndex);
heliRect.x = int(animationIndex % spritesPerRow)tileWidth;
heliRect.y = int(animationIndex / spritesPerRow)tileHeight;
canvasBD.copyPixels(tileSheet,heliRect, heliPoint);
}
}
}
[/cc]
This does seem like a lot of code to simply rotate an object, but there is a lot going on here that can be useful for more complicated projects.
Let’s go through some of this code in detail code to ensure you understand the basics of blitting from a tile sheet as well as the basic math involved. We have added a concept to our blitting in this example. We now have 36 sprite sheet tiles to use and that will necessitate some simple sprite sheet math needed to find the location of the sprite to blit onto the canvas.
The Sprite Sheet
The sprite sheet is embedded in our .fla library with a class name of “heli_sheet”. It is a 360×144 tile sheet with the 36 frames of animation needed just for the clockwise motion of the helicopter.
We bring it into use like this: [cc lang=”javascript” width=”550″]tileSheet=new heli_sheet(360,144);[/cc]
The basics of tile sheet blitting can be found in this tutorial.
The Canvas
We will be using a single canvas as our drawing surface for all objects (in this case we have just one object); We need a 400×400 blank canvasBD BitmapData object and we need a way to display it on the screen, so we also have a canvasBitmap display object that is added to our displayList
[cc lang=”javascript” width=”550″]
canvasBitmap=new Bitmap(canvasBD);[/cc]
The Helicopter
If this was a more elaborate game, we would have some sort of helicopter object to hold all of the properties and many of the methods used for it. In this simple case, we just have a few global variables that are used for that purpose:
[cc lang=”javascript” width=”550″]
heliRect=new Rectangle(0,0,36,36);
heliX=50;
heliY=50;
heliPoint = new Point(heliX, heliY);
[/cc]
The heliX and heliY do nothing more than give us a location for the the top left corner of our helicopter object. This is also the center point of any radial circular rotation we might want to display. Since this first example if just rotating an object in place, we don’t need any other calculations to find the x and y values for the helicopter. They will be heliX and heliY (50,50) nd will not change.
The heliRect variable is used for our blitting of the helicopter to the canvasBD. It defines a rectangle of that starts at 0,0 and goes to 36x and 36y. It tells the blitting method how much of the sprite sheet to put on the canvasBD.
The heliPoint is always the current x and y value where we want the sprite to be blit onto the screen. In this case is will remain 50,50 the entire time.
The Background
The background we are using is a simple black 400×400 square. We don’t need to do anything difficult to create this other than specify it when we create out background BitmapData object:
[cc lang=”javascript” width=”550″]
backgroundBD=new BitmapData(400,400,false,0x000000);
backgroundRect=new Rectangle(0,0,400,400);
backgroundPoint = new Point(0, 0);[/cc]
The backgroundPoint and the backgroundRect are used when blitting them to the canvas. The point will always be 0,0 (the top left of the screen) and the rect will always be a 400×400 rectangle covering the entire background.
Using blitting from a tile sheet with multiple rows and columns
In the previous blitting tutorials we have covered how to blit a finite row of tiles in a loop to create a simulated animation. In this example we will take it a bit further and show how to blit from all 4 rows to the canvas, not just from one row. The size of each tile we are going to use is 36×36 and we have 36 tiles. There is no extra significance for the use of the number 36 and it is just a coincidence that I used that number for the tile width and height as well as the number of angles for a rotation.When we have more than one row of tiles to blit from (there are three full rows of 10 and one row of 6 sprite tiles) we can use a simple math trick to tell the program where to start cutting from the tile sheet to paste onto the canvas.
[cc lang=”javascript” width=”550″]
heliRect.x = int((animationIndex % spritesPerRow))tileWidth;
heliRect.y = int((animationIndex / spritesPerRow))tileHeight;
canvasBD.copyPixels(tileSheet,heliRect, heliPoint);
[/cc]
The heliRect.x and heliRect.y values need to be updated each time a new frame of animation is to be shown. Note: In the above example I do it on EVERY frame, but this can be optimized further to have it only calculated when a new frame is to be shown.
What we need to know here first is the current frame of animation we want to display. That is represented by animationIndex and is a number from 0-35. We also need to know the maximum number of tiles per row in our spriteSheet. That is represented by the spritesPerRow variable. Since animationIndex starts ar 0, the first sprite tile to blit is 0:
(you will notice that I decided to break out the green helicopters for this example – I didn’t want them to go to waste – Maybe Steve’s entry on the Temptation of the Unreleased to explains why)
To find the starting x value on the spriteSheet to copy from, we use this simple formula:
x=int(animationIndex % spritesPerRow)tileWidth;
For tile 0 on our sheet, the calculation would look like this:
x=int(0 % 10)36.
0/10 = 0 with 0 remainder, so 0 % 10 =0; 0*36=0; Our start x location is 0;
To find the starting y value on the spriteSheet to copy from, we use this simple formula:
y=(animationIndex / spritesPerRow) * tileHeight
For tile 0 on our sheet, the calculation would look like this:
y=int(0/10)36.
0/10 =0, so 036=0; the start y location=0;
This seems pretty simple for the first spriteSheet tile, so now we’ll test it out on the 15th tile (the 15th tile would be the 16th on our sheet because we start at 0).
For tile 15, the x calculation would be:
x=int(15%10) * 36.
15 % 10 =5 (5 is the remainder from the division of 15 by 10);
5*36=180;
Our x start location is 180;
For tile 15, t he y calculation would be:
y=int(15/10) 36.
15/10 = 1 (as an integer).
136=36;
That gives us a y start location of 36.
So, the upper left-hand corner of the 15th tile is 180x and 36y. That is where out spriteSheet copy will start and it will use the heliRect and heliPoint values to blit that tile to the canvasBD.
Again, it will look like this:
[cc lang=”javascript” width=”550″]
heliRect.x = int((animationIndex % spritesPerRow))tileWidth;
heliRect.y = int((animationIndex / spritesPerRow))tileHeight;
canvasBD.copyPixels(tileSheet,heliRect, heliPoint);
[/cc]
Slowing down our animation
One last thing we do in this simple second part to our blitting tutorials is create a delay between the display of animation frames for our rotation. We do this so the helicopter does not spin too fast on the screen.
[cc lang=”javascript” width=”550″]
if (animationCount == animationDelay) {
animationIndex++;
animationCount = 0;
if (animationIndex == heliTilesLength){
animationIndex = 0;
}
}else{
animationCount++;
}
[/cc]
Basically this counts 3 frames and then increases the animationIndex by 1.
When the animationIndex is greater than 35 it sets id back to 0;
Where is the rotation code?
Good question. As you can see by analyzing the above code, we didn’t use any of this formula:
x=centerX+Math.cos(angle) * radius; or x=50+Math.cos(0)0; or in other words, 50;
y=centerY+Math.sin(angle) * radius; or y=50+Math.sin(0)0; or in other words 50;
The reason for this is simple. The outcome of each of those formulas is still just the current center point. Also, since we are using the animationIndex value to loop through our spriteSheet, we just need to multiply it by 10 to get the current angle that the heliCopter is facing. We did not need that information in this first part, so I left it out. It will be needed when we rotation with a radius around a center point and especially when we turn the helicopter to face the direction of this radial motion.