Monday, 2 August 2010

Tutorial 1-2 : The Beat Box

Tutorial 1 : The Beat Box
A multipart tutorial teaching sprites, views, sounds, clicking and more!
Part One, Part Two, Part Three, More to come.

For our multiple sprites, we need an array to hold them. Within .h we'll make an array to do that.

UIImage Arrays are a lot like regular images, so we can declare them in exactly the same way, in the .h file.

@interface BeatBoxViewController : UIViewController {
UIImage* img_DotsFull;
UIImage* img_Dots[5];

@property (nonatomic, retain) UIImage* img_DotsFull;
@property (nonatomic, retain) UIImage* img_Dots;

As you can see, we've simply replicated the DotsFull lines, changing only the first part to an Array. Save the .h, and head to the .m.

Inside the .m, we again need to synthesize our variable, except this time we can't actually use Synthesize. Thing of Synthesize as being like the Basic keyword "Global". We can Global a single variable, but an array is different. We need to "Dim" the array, instead.
So, for arrays, we use "Dynamic" instead.
@dynamic img_Dots;

And down in the dealloc function, we need to clean it up afterwards.
This line is nearly the same, except with a star at the start, to point to the array, rather than the variable.
[*img_Dots dealloc];


So, that's your variable ready..
Let's head back to .h again.

We're going to create a drawing function.
Actually, we're going to create a set of functions that together give us a really useful function.
We take a sprite, take a blank sheet, draw onto the sheet, and then make use of the sheet.
This is handy in all kinds of locations, but we need to be sure we don't go overboard with it. We CAN do it, but we can't do a lot of it, or else the slower iPhings will start to suffer.
In quick tests I've found that the iPod Touch can generally do about 5 or 6 of theses per-frame before it starts to struggle. Of course, if you're doing this during a loading screen, you can get away with drawing all manner of wonderful things, and reusing these large drawn sheets as background maps.. That's very handy indeed!
Our first use, though, will be to take our original spritesheet, and draw pieces of it onto smaller 16x16 pixel sprites, which will represent the cut out chunks of our sprites.

ok, first up, we need to declare a couple of small variables. So, In-Bracket, we want the following..
CGContextRef LockedBuffer;
int BufferHeight;
These represent two things. First, a CG item which is our current drawing buffer. This will only be used within our upcoming drawing function, but represents the image that will eventually be spat out. The second is the height of said buffer.

Under-Bracket, we want to declare the function's we're going to be using.
void LockBuffer(UIImage* ImageLock);
void DrawToBuffer(UIImage* ImageDraw,int x,int y);
UIImage* UnlockBuffer();

The first will be used to Lock the buffer, or rather, to take an image and turn it into a drawing buffer. The drawing buffer is that CGContextRef from earlier. CGContextRef's cannot be seen onscreen, they're background memory things.. The second draws a sprite onto the buffer at the location provided, whilst the third spits our buffer back out again, turning it back into a usable UIImage.

Now we need to hop on over to our .m and code those exact functions.

Up in the declaration section, we need to redefine our couple of variables.
CGContextRef ctx;
int BufferHeight;

and then further down the script (anywhere, really) you can plonk down the functions.
// Buffer Phings
void LockBuffer(UIImage* ImageLock)
CGImageRef oldimage = ImageLock.CGImage;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
ctx = CGBitmapContextCreate(NULL,
CGImageGetWidth(oldimage), CGImageGetHeight(oldimage),
8, CGImageGetWidth(oldimage) * 4,
colorspace, kCGImageAlphaPremultipliedLast);
CGContextSetBlendMode(ctx, kCGBlendModeCopy);
CGRect r = CGRectMake(0,0,CGImageGetWidth(oldimage), CGImageGetHeight(oldimage));
CGContextDrawImage(ctx, r, oldimage);

void DrawToBuffer(UIImage* ImageDraw,int x,int y)
CGImageRef useimage = ImageDraw.CGImage;
CGContextSetBlendMode(ctx, kCGBlendModeCopy);
// add new image
CGRect r = CGRectMake(x,y-BufferHeight,CGImageGetWidth(useimage),CGImageGetHeight(useimage));
CGContextDrawImage(ctx, r, useimage);

UIImage* UnlockBuffer()
// create resulting image
CGImageRef thisimage;
thisimage = CGBitmapContextCreateImage(ctx);
UIImage* newImage = [[UIImage alloc] initWithCGImage:thisimage];
return newImage;
That's a little messy!
It does what I said earlier! At the moment, you needn't worry about all the horrible nastyness, and should just assume that it does it's job. If you want to scan through it, it kinda makes sense. Just not entirely!

It does have a few quirks that I've yet to bother tweaking, mostly involving co-ordinates being slight odd, but we needn't worry about those just yet.
I'll leave it up to you to play about with the functions at a later date.

Something you might like to note, is the "CGContextSetBlendMode" bit, which you can change to do all kinds of neat tricks, like additives, and subtractions, and lovely swooshy things like that.
Obviously, you couldn't keep up the framerates if you tried to do Geometry Wars like that, but.. It's nice to know it's there, once we've started to really play with stuff.

OK, our functions are ready for use.

Next we need a new sprite! We can't cut our image into "no-space", and instead need a blank sprite to use as a target. Since I can't figure out how to generate a blank sprite in-code, let's cheat, and load in a blank png image.
Create a simple 16x16 pixel image in your favourite art package, save it to "blankx16.png" and drag it into your project, copying it over, the same as you did for the first one.

Create the variable like we always do,
.h In-bracket
UIImage* img_Blank;
.h Under-bracket
@property (nonatomic, retain) UIImage* img_Blank;

.m Declare
@synthesize img_Blank;
.m Dealloc

Now, head into the viewDidLoad.
int x=0; // Start from the left
int y=0; // Start from the top.
for (int n=1;n<4;n++)
img_Blank=[UIImage imageNamed:@"blankx16.png"];
// Load blank target image
// Lock buffer
// Draw onto buffer
// Release buffer into variable
[img_Blank dealloc];
// Free original target
x=x-16; if (x==-32) {x=0;y=y+16;}
// Move position for the next part.

We still can't see anything, yet, but it's all happening in memory, and we're about ready to start playing with our engine.
If you're finding it hard to keep up, here's everything so far.

No comments:

Post a Comment