The Pixelwave Manual

From Pixelwave

Documentation > Manual

Welcome! Congratulations on taking this step towards learning about Pixelwave. I hope that you find this manual helpful and enjoy using the framework.

If by some act of (Google) magic you got to this page not knowing a thing about Pixelwave, I should mention this:

Pixelwave is a framework for building 2D games and other interactive applications for iOS. It wraps low-level, hardware-accelerated code in a clean and lean object-oriented interface. Bam!

Pixelwave was designed from the start to be a native iOS framework—that means you'll be interacting with it using Objective-C (and some C, but only if you'd like).

You should know that this manual assumes no prior knowledge of any language other than Objective-C, and even though you’ll be able to brag about how your apps are so “hardware-accelerated”, you don’t have to know the first thing about OpenGL. Sweet!

An important thing to note is that if you’ve ever used ActionScript 3.0 (AS3) you’ll probably notice a striking similarity to the naming conventions in Pixelwave—that was our goal! But if you’ve never seen or heard of it, don’t panic; you’ll feel at home quickly with the elegant and powerful interface.

Note: This manual is a work in progress.


Contents

1 Fundamentals

1.1 Design Goals

Let’s take a minute to discuss the fundamental driving forces behind Pixelwave—the design goals. I put them into a list because, let’s admit it, nobody likes to read too much.

A clean interface
Pixelwave's interface is written in 100% Objective-C. The class names and functionality are derived from AS3 but tweaked to fit into Objective-C standards. We decided to base the class structure around AS3 because we find it to be very clean and intuitive. As mentioned earlier, while previous AS3 experience will make it easy to jump in almost immediately, it's certainly not required. If you're new to all this you should be good to go after reading through this manual.
Performance
While the framework’s external interface is designed to be object-oriented and easy to use, all the under-the-hood, core parts of Pixelwave are written in old school C because, as you know, it’s really fast.
One of the neatest things about coding for iOS is the ability to mix C with Objective-C. In practice this translates to being able to use fast C code in key areas, while keeping object oriented (OO) class structure intact. So advanced users can momentarily drop down to a lower-level language for some fierce optimization without breaking the beautiful OO code structure.
On top of the inherent performance gains from having a C core (as opposed to a dragon heartstring or phoenix feather core), Pixelwave does a lot of invisible optimizations under the hood, such as automatically batching draw calls and ignoring redundant GL synchronization (read slow) calls. If you have no idea what that means, don’t worry—that’s the whole point.
Extendability
You probably already realize that Pixelwave is open source software, which means anyone can freely browse around and modify whatever they deem necessary. But for most users, going through and trying to analyze loads of 3rd party code isn’t exactly the idea of a fun Friday night. So just for you weekend-lovers, we included various interfaces that make it simple to extend the functionality of the engine right from your own project.
Documentation
The truth is, not everyone is as familiar with Pixelwave as we are. We believe that good documentation is the way to change that. As we speak, our crack team of technical writers is sitting in a room lined with whiteboards twelve stories underground, drinking sunkist and writing first-rate documentation. In the meantime though, I'm all you've got. :)

So those are our goals in a nutshell. You can be sure we’ll discuss each of these in extended detail in the upcoming sections, but first let’s talk briefly about PixelKit.

1.2 PixelKit

Most frameworks seem to combine basic engine functionality and industry-specific utilities into the same code-base (a game physics engine, for example, would be bundled with a rendering engine). While this makes some users' lives easier, it usually winds up cluttering the framework, making it less accessible to new users. It also makes the core engine less usable in other industries.

So, we decided to do something slightly different. To take advantage of the convenience that comes with bundling everything into one package, while avoiding the clutter involved, we created PixelKit.

PixelKit is a collection of utility classes and frameworks for game developers that extend the functionality of Pixelwave. Its features include:

  • A physics engine (Box2D)

These are currently in the works:

  • A particle engine
  • A tweening engine
  • Debugging utilities
  • Other useful utilities.

PixelKit is designed to make the lives of game developers easier when using Pixelwave. It’s worth to note though that Pixelwave can be used just fine without PixelKit. You can think of Pixelwave as the core tool that can be used to build any type of interactive application, and PixelKit as as extension to the core that makes it easier to build games.

When creating a Pixelwave + PixelKit project there's no need to worry about which class comes from which project. The two behave like one bundle of classes. If you'd like to only use the bare minimum though, you can create a plain Pixelwave project which doesn't include PixelKit.

This setup allows for finer control without giving up convenience. It also lets us to keep the two frameworks separated, which makes them easier to understand (and explain about in this manual). We'll first learn about Pixelwave and then go on to talk about PixelKit and the conveniences it provides.

1.3 Conventions

Before getting into specifics let’s go over the conventions we’ve been using to build Pixelwave. Knowing these before you start reading and writing code will most likely help you recognize certain patterns and interpolate about the meaning of new findings.

1.3.1 Prefixes

If we learned anything since we started developing for the iPhone, it’s that any good Objective-C library needs a solid prefix. Since the language doesn’t support namespaces or packages, the name of every class, C function, and struct of a framework should be prefixed with two or three letters unique to that framework. For example, NS is the prefix of the foundation Cocoa classes (NSString, NSData, NSArray, etc.). All Pixelwave related constructs are prefixed with the letters PX. So when coding with Pixelwave we’d use the class names PXSprite, PXTexture, and PXStage. When referring to these classes in text though, we’ll just call them Sprite, Texture, and Stage respectively.

Yes, we could have chosen PW as the prefix instead, but everyone knows that anything with the letter X in it is ten times more awesome. Plus, Pixelwave is one word not two, duh…

1.3.2 Internal members

You may encounter a method or variable in Pixelwave that begin with the underscore '_' character--pretend they don't exist. The '_' prefix indicates an internal class member. Don't access any instance variable or function of a class that's prefixed with the '_' character as it may not do exactly what you think. It is also subject to change without notice in future revisions.

1.3.3 AS3 API

Even though we didn’t make a big deal about the similarities between Pixelwave and AS3 in the introduction (you know how those non-flash programmers can get), you should know that they are more than skin-deep. Being the fanatics that we are, we went out of our way to make sure that Pixelwave follows the functionality exposed by AS3 to a high degree—but only where it makes sense. Some things on mobile are inherently different from the desktop so changes had to be made. In general though, you can expect the framework’s major classes to follow suite with important AS3 classes.

As a rule of thumb, if a Pixelwave class or function has the same name as an AS3 class or function, you can expect it to behave the same way. If the names differ, you’re probably looking at code that was created to accommodate for hardware-level optimizations or mobile-only features.

1.4 Subsytems

Let me tell you a secret: Pixelwave isn’t just one system. It’s the sum of several subsystems that are combined to create a seamless whole. Each subsystem handles a different set of tasks but together they provide a complete and simple solution for real-time apps on any iOS device. The following sections will detail each system and its related classes, but for now here is a list of the subsystems within Pixelwave and a brief description of each:

1.4.1 The Graphics System

The graphics system takes care of everything involved in drawing things on the screen, from talking to the GPU to loading images from the hard-drive. It lets you easily load many file types with a flexible loader class. It also provides hierarchical manipulation of what’s on screen through the display list. The rendering engine is built around 2D graphics, though it can be extended to support simple 3D effects with relative ease.

Along-side the awesome texture rendering engine, the following are also supported:

Text Rendering
Pixelwave includes a custom text rendering engine that's optimized for GPU accelerated rendering. Currently it supports custom TrueType, OpenType, and many other popular font types, along with the fonts available in iOS. User-created font sheets are also supported.
Vector Shapes
Pixelwave allows for hardware-accelerated vector shape drawing. This feature is currently in the lab and not available yet.

1.4.2 The Audio System

Pixelwave's advanced audio system is built on top of OpenAL and other native iOS frameworks to provide super simple loading and playback of major audio file types. It allows for volume and pitch control and even supports 3D playback. The audio system makes positional audio really easy to use with any type of game (yes even 2D games). It even takes into account the Doppler effect!

1.4.3 Event Dispatching

Pixelwave includes a custom event dispatching system that allows the user to register custom method callbacks to important system events. For example, a user pressing a button or a sound completing playback. The event dispatcher is so cool that you may want to use it in your own projects as well.

Note to AS3 programmers: The event dispatching model is identical to the one found in AS3.

2 Graphics

Pixelwave houses a powerful graphics engine designed around 2D rendering and OpenGL ES. Luckily it happens to be wrapped in a set of simple and intuitive classes and functions. Now that we got that self-serving disclaimer out of the way, let’s start learning about drawing things on the screen!

The discussion of the graphic system begins with a brief description of the display list (which you may know otherwise as a scene graph). The display list organizes the different visual elements on the screen, and is essentially the backbone of the rendering system.

After learning about the display list we’ll talk quite a bit about display objects. These are the individual visual elements that are displayed on the screen, each one containing information about its position, rotation, scale, etc.

2.1 The Display List

The display list is a tree structure that houses display objects (leaf nodes) and display object containers (parent nodes). Here is a quick visualization of what a simple display list may look like and its rendered result on the screen:

As you can see the display list has a single ‘root’ display object container, which can hold multiple child display objects. A display object container is essentially a sub-tree that can hold other nested containers in a recursive fashion. As is common with tree structures, a display object node can't have more than one parent (ie. it can never exist within more than one container at once).

2.2 Important Classes

Let's start talking about some actual classes by discussing the main players in Pixelwave's graphics system. These are the base display classes on which everything else is constructed:

2.2.1 The DisplayObject

A display object is an individual visual element (such an image, a button, an animated character, etc.). The base class for all display objects is the DisplayObject. A quick introspection of the DisplayObject class will reveal cool-sounding properties such as x, y, rotation, scale, and alpha. These give us a hint at the basic transformations each display object can undergo.

Subclasses of DisplayObject exist to provide functionality for things like textures, buttons, and text fields. Each of these subclasses exposes additional properties and functionality related to its nature.

Examples of display objects

2.2.2 The DisplayObjectContainer

While display objects represent every element that gets rendered to the screen, they'll never be seen unless they're in a container. That's where the DisplayObjectContainer comes into play. A DisplayObjectContainer is a DisplayObject that can contain other child DisplayObjects within it. These children can be buttons, textures, text fields, etc. but they can also be other containers as well. Trippy, dude.

Being a subclass of DisplayObject, the DisplayObjectContainer can also have its own transformations (rotation, scale, etc), which will propagate down to its children.

An example of a container in Pixelwave could be a message box. The children of the message box container would be its buttons, text, and icons. The buttons may be containers as well, each holding onto their own icons and text.

If we were to actually build this message box it may look something like this:

Image of message box

The resulting display list may look something like this:

Image of display list of message box

So to wrap up, DisplayObjectContainer objects together with DisplayObject objects are the building blocks of the Pixelwave display list.

2.2.3 The root display object

Now that we have an idea of what display lists are about, let's discuss what it takes to actually draw them to the screen. In order to keep track of what should be drawn to the screen and what shouldn't, Pixelwave has a special display list we call the main display list. To put it simply, any display object within that list gets drawn to the screen. While display objects can live in other display lists, they will simply be ignored by the renderer.

To keep track of this display list, Pixelwave holds on to a special variable called root. As the name suggest, root acts as the trunk of the main display list. It gets created automatically when the engine starts up, but populating it is up to you. So to have a display object rendered to the screen simply add it to the root container or any of root's children.

It's worth noting that the root of the main display list can be set to any display object you'd like. It's usually best though to keep the default root that Pixelwave creates.

2.2.4 The Stage

Pixelwave has a very special class called Stage, which is used for reading and setting certain global properties involved with the screen and the device on which Pixelwave is running.

The main uses of the Stage class include:

  • Reading the size of the stage
  • Setting the orientation of the stage
  • Setting various optimization toggles (described in Optimizations below)
  • Setting the frame-rate of the simulation

A common instance of Stage is created automatically by the engine, and should never be instantiated by the user. To access the common stage object, reference the stage property of any display object that's currently on the main display list.

2.3 Coordinate system

When it comes to representing the position and size of elements on the screen, Pixelwave uses a variant of the basic Cartesian coordinate system.

As we all know from our early school years, a Cartesian coordinate system is essentially a grid. There is an origin point, denoted as (0,0), from which the x-axis sprawls horizontally and the y-axis sprawls vertically.

The classic Cartesian coordinate system

Pixelwave uses a slightly different version of the Cartesian coordinate system in which the y-axis is inverted, with positive values falling beneath the origin instead of above it. Also, the origin (0,0) of the Stage is located at the top left corner of the screen. If you're familiar with other graphic or design applications you probably already know that this is a pretty popular convention.


Pixelwave's coordinate system


Each unit in Pixelwave's coordinate system is equivalent to a single pixel on the screen. Well, at least usually. In some cases a single unit may represent more than a single pixel, such as when working with the iPhone's retina display. The specifics of working with the retina display are discussed in detail in the section Cocoa Touch specifics.

To keep things simple we'll call these units pixels for the rest of the manual.

2.4 Transformations

Now that we know how Pixelwave's coordinate system works, let's see how we can plot display objects on the screen. The position of a DisplayObject within Pixelwave's coordinate system is represented by its x and y properties. x represents the object's horizontal distance from its container's origin, while y represents the vertical distance.

Let's use an example to demonstrate this visually. We'll start with a simple display object (a red box) who's origin is right in the center. Our box, like any DisplayObject starts out at position (0,0). Here's what it looks like:

Since we haven't placed our box in a container, it's position doesn't really have any meaning yet. Let's add the box to the stage:

As you can see, the origin of our display object is now placed at point (0,0) within its container (the stage).

A note about origin:
stage is the only display object in the main display list that doesn't have a parent, since it represents the top-most node of the display list: the screen. The origin of stage is always the top-left corner of the screen.

Now lets see what happens if we change the x and y properties of our red box. Let's try setting the position to (250, 150).

Now the origin of our display object is offset 250 pixels to the right and 150 pixels down. Makes sense, right?

2.4.1 Nested transformations

Even though it may seem like it in our example above, a DisplayObject's x and y properties don't always represent the final position on the screen at which it'll be rendered. Instead they represent its position relative to its parent container.

In the basic case where a display object lives directly on the stage (like our example above), its rendered position is the same as its x and y properties. But, since display objects can be placed in containers, which can in turn be placed in other containers, the position at which an object will finally be rendered on the screen depends on the transformations of all of its containers (or ancestors) combined.

To demonstrate what happens when display objects are nested within containers, let's place our red box in a container and place that container on the stage.

First, we'll place a container on the stage, at position (40, 40):

Now let's take our red box and put it back in the main display list. This time though, we'll add it to the container instead of directly to the stage. We'll leave the box's x and y properties exactly as they were before (250, 150). It would look something like this:

Something changed a bit this time around. Even though the red box is still offset by (250, 150) pixels from the origin of its container, the position on the screen at which its drawn has changed. This is because the the offset of the container (40, 40), propagated down to it.

The final position of the red box when rendered to the screen is now equal to the original offset (250, 150) plus its container's offset (40, 40), which comes out to (290, 190). This final screen-space position can't be controlled directly by the user, but is rather a combination of the transformations of all the display objects on the display list.

This ability to define a display object's transformation relative to its parent instead of using absolute screen coordinates is actually extremely useful. It makes it simple to divide a bunch of display objects into groups and sub-groups. For example, you may create a container which holds all of the elements of a window, such as the menu bar at the top and the status bar at the bottom. Because the screen position of objects depends on the transformation of their container, moving all the different parts of the window means simply changing the position of the one window container.

2.4.2 Rotation

The angle of a display object can be set through the DisplayObject.rotation property. You should know the following things about angles in Pixelwave:

  • Angles are measured in degrees.
  • Increasing angle values rotate an object in a clock-wise direction.
  • Display objects are rotated around their origin.

It's pretty straight forward, but let's just use an image to avoid writing a thousand more words:

Two display object with different values for rotation

2.4.3 Scale

A display object's size can be changed by setting its scale. Scale in PIxelwave is measured in percent, and usually lies between 0% and 100%. The scale of a display object can be greater than 100%, which stretches it, or less than 0%, which flips it.

scaleX and scaleY

To change a display object's scale we interact with the scaleX and scaleY properties. As the names suggest, one will scale the object along the x-axis and the other along the y-axis. Here's a quick visual:

scale

Having control over the scale of each individual axis can be very useful at times, which is why the separation exists here. A lot of times though we need to scale our objects proportionally, which means the scaleX and scaleY properties should be set to the same value. To make that task easier we included a derivative property within DisplayObject named scale. Setting the scale property simply sets the scaleX and scaleY properties to the same value.

So writing:

box.scaleX = 0.5;
box.scaleY = 0.5;

Is exactly the same as:

box.scale = 0.5;

width and height

The scale properties allow us to set how much bigger or smaller a display object should be from its original size. Sometimes though we need to set exactly what the width and height of the object should be. To do that we can use the width and height properties of the DisplayObject. These properties allow us to both read the current width and height of a display object (taking its scale and rotation into account), and set the width and height (again, taking rotation into account).

It's worth noting that width and height are derivative properties. Setting them will actually change the scaleX and scaleY values of the display object.

Let's use a simple example to demonstrate. Say we wanted our red box to be exactly 150px by 132 px. Of course, we could do it the "long" way by finding the original size of the box and then figuring out what the scaleX and scaleY values should be, but there's really no need. Here's what we would do:

box.width = 150;
box.height = 132;

And the output would now look something like the box on the right:

Now let's take it a step further. Let's say we wanted our box to be rotated 30° but we still wanted its total width and height to stay the same as above. Simple:

box.rotation = 30;
box.width = 150;
box.height = 132;

Output (right):

I hope the previous example makes this final note a bit clearer: the width and height properties represent the size of the axis-aligned bounding box around the display object.

Performance Tip
Setting the scale of a display object is the preferred, and most efficient way to change its size. Use the width and height properties selectively as there's an inherent overhead associated with the calculations involved.


2.4.4 Color

Like rotation and scale, the color of a display object can also be transformed. When we say color we actually mean the red, green, blue, and alpha channels of the display object. Pixelwave allows you to assign different multipliers to each of these values to tweak the way a display object is rendered to the screen.

alpha

The DisplayObject.alpha property lets us set how translucent a display object should be. Much like scale, transparency is measured in percent, with valid values ranging from 0% to 100%. An alpha value of 0% means that a display object will be completely invisible, while a value of %100 means it will be completely visible. Any values in between will cause the display object to blend with the graphics beneath it. Values outside the valid range will be clamped.

Several display objects with alpha values ranging from 100% to 10%

ColorTransform

While the alpha property allows us to directly set the alpha channel multiplier of a display object, there aren't such properties in place for tweaking the red, green, and blue channels.

You're probably wondering why. Well, to tell the truth, the alpha property is really just a short-cut. It lets us quickly change the display object's ColorTransform without going through too many hoops. In order to avoid clutter, only the alpha channel got this special consideration because its so commonly used. To tweak the other channels we'd need to do things the "long way". But don't worry, it's not too long.

Some things are easier coded than explained, so let's write some code to change the color transformation of a display object named box.

// Grab a copy of the box's ColorTransform object
PXColorTransform *colorTransform = box.transform.colorTransform;

// Set some values
colorTransform.redMultiplier = 0.5;
colorTransform.greenMultiplier = 0.1;
colorTransform.blueMultiplier = 1.0;
colorTransform.alphaMultiplier = 1.0;

// Update the box's color transform
box.transform.colorTransform = colorTransform;

This piece of code will make our box's red channel have 50% strength, the green channel have 10% stength, the blue channel will stay the same, and the alpha channel will be at 100% (as you can see, the alpha channel multiplier can be set by tweaking the colorTransform or by setting the alpha property).

After undergoing the above color transformation, our good ol' red box would look something like this:

How fancy!


2.5 Display Objects

Alrighty, now that we understand how the graphics system works, let's talk about the actual types of display objects in Pixelwave and the visual elements they support (no more mysterious-red-box-place-holder).

The DisplayObject class is considered abstract, meaning it provides a base to build upon instead of actual functionality. As such, DisplayObject should never be instantiated directly. Instead, concrete classes that extend DisplayObject exist for different purposes.

These concrete objects should be instantiated by the user and added to the display list. Without any further ado, they are:

Texture
The Texture class has the specific purpose of drawing images (otherwise known as textures). A texture display object represents a portion of an image that can be positioned, rotated, scaled, etc.
We'll go through the specifics of textures later in the section titled Working with textures.
TextField
As the name implies, this display object is used for drawing text. Text fields work with different fonts to customize the appearance of their text.
Much more on these in the upcoming section titled Working with text.
SimpleButton
This one's more a of a composite display object. It allows the user to choose a different DisplayObject for it's up, down, and hit states. It then takes responsibility for displaying the appropriate visual at the right time and dispatching events when a user presses or releases it.
For more information about the SimpleButton class take a look at the API reference entry.
Shape
This display object is responsible for drawing vector shapes. Paths and fills are created and supplied to the shape's graphics object.
As of the time of this writing, vector drawing is not yet fully supported in Pixelwave.
Sprite
This section is currently under construction and will be updated soon.

3 Working With Textures

Textures are probably the most commonly used elements in interactive applications. You may know them otherwise as images or bitmaps, but we simply decided to call them textures (If you're wondering why, it goes back to OpenGL where images are traditionally called textures). There are several intrpretations of the word 'Texture', but as defined in the graphics world (as opposed to the fabrics world for example) a texture is as a grid of pixels.

3.1 The TextureData

Pixelwave makes a separation between the raw pixels of a texture in memory and the display object that's used to actually draw them to the screen.

A TextureData object represents the raw pixels of an image in memory. A Texture object represents an instance of that image on the display list.

For example, in order to render multiple instances of the same image, we wouldn't create multiple copies of it in memory. Instead, we'd make multiple Texture display objects which reference the one TextureData object.

A TextureData object holds on to an image that's been loaded into memory. It represents the actual pixel data in GPU memory. The amount of memory that a TextureData object takes up depends on its dimensions (the amount of pixels) and its pixel format (bits per pixel).

When an image is no longer in use, the TextureData object can simply be released to unload it from GPU memory.

3.2 Loading textures

So now we know how textures are represented in memory. But, how do the textures get there? Well, by loading them from the hard-drive of course.

Pixelwave can load many different image formats, all with a simple class named TextureLoader. Here's a quick code example for loading an image:

// Load the image 'sun.png' synchronously:
// In order to load files asynchronously simply run the
// following line in a background thread instead
PXTextureLoader *loader = [[PXTextureLoader alloc] initWithContentsOfFile:@"sun.png"];

// At this point the image has been loaded into CPU memory.
// Create an instance of the texture in GPU memory:
PXTextureData *textureData = [loader newTextureData];

// We no longer need the raw loaded data.
[loader release];
loader = nil;

Texture loaders follow the same design principals as other loaders in Pixelwave (such as sound and font loaders). The basic life-cycle of a texture loader is as follows:

  1. A TextureLoader object is instantiated and passed a path argument.
  2. The file at path is loaded synchronously. If the file was loaded and parsed successfully it's contents are now stored in the TextureLoader object. If there was an error, the loader's init method returns a nil reference.
  3. Now the [PXTextureLoader newTextureData] method can be used to create an instance of the loaded data in video card memory. The returned TextureData object represents that data.
  4. It is now safe to release the loader object. If you'd like to keep a copy of the texture in CPU memory you can keep the loader around. In most cases though loaders are disposed right after spawning a TextureData object.

Ok, so it looks like all it took was 3 lines of real code to load an image. We think we can do better. In some cases where fine control over the loading process is required you'd need to follow the same process as above, but when you just need to load an image quickly you can just do this:

PXTextureData *textureData = [PXTextureData textureDataWithContentsOfFile:@"sun.png"];

That was easier. If you inspect the above method's implementation you'll see that internally it goes through the same process involved with creating a texture loader, but its useful when you want to avoid the extra lines of code.

As a general rule of thumb Pixelwave will always try to simplify your life with these kind of helper methods. If you need fine control you can always do it the "long" way, but for all the other cases the redundant work was wrapped in utility methods.

Supported image formats

For your convenience, here's a list of all the image formats supported by Pixelwave:

  • tiff / tif
  • jpg / jpeg
  • gif
  • bmp / BMPf
  • ico
  • cur
  • xmb
  • png
  • pvr
  • pvrtc

3.3 Displaying textures

Ok, now that we know how to load images, ehm , textures, let's see how we can display them on the screen. By now we realize that anything that gets drawn to the screen needs some subclass of DisplayObject to actually draw it and represent it on the display list. To draw texture data objects onto the screen we'd use the Texture class.

The Texture class inherits all of the functionality of the DisplayObject class, but adds the ability to interact with TextureData objects. So, in order to draw a texture in memory onto the screen we'd write the following:

// Load 'sun.png'
PXTextureData *sunData = [PXTextureData textureDataWithContentsOfFile:@"sun.png"];

// Step 1:
PXTexture *sunObject = [[PXTexture alloc] initWithTextureData:sunData];

// Step 2: This optional. We just want to show the entire image on screen
// so we won't modify the clipRect.

// Step 3:
[container addChild:sunObject];
  1. Create a Texture instance and set its textureData property to reference some TextureData object.
  2. Define the clipRect of the Texture. A clip rectangle is the area of the TextureData that you'd like to draw. By default the area of the entire TextureData is used.
  3. Add the Texture object to the display list.

Of course we've included some helper methods to quickly create a Texture object. I'll leave it to you to find them all. Happy browsing.

An important thing to note (again) is that a TextureData object should never exist more than once for the same image. If you need to draw the same TextureData object in more than one location, simply make a bunch of Texture objects that reference that TextureData object.

So if, for example, you wanted to draw 25 instances of the same tree scattered on the screen you'd:

  1. Create a single TextureData object containing the contents of the tree texture ('tree.png' for example).
  2. Create 25 Texture objects, each with a different position, but all of them should reference the same TextureData object.
A note to AS3 programmers:
The Texture/TextureData objects are an example of classes that don't exist in AS3 but were created to accommodate for the specifics of GPU optimization. The functionality of these classes is very similar to that of the Bitmap/BitmapData classes in AS3. It was decided to rename these classes to avoid confusion as they do differ a bit in some areas.

3.4 Rendering to texture

TextureData objects usually hold image data that was loaded from an image file. But they don't really have to. For eaxmple, you can draw a display list directly into a TextureData object instead of drawing it to the screen.

To do so, simply create a new TextureData object and call it's drawDisplayObject: method, passing the root of the display list you'd like to draw into it.

Here's a quick code sample that draws the main display list (what's on the screen) onto an empty TextureData object (essentially taking a 'screen shot').

// Create a new texture data 512x512 pixels
PXTextureData *screenshot = [[PXTextureData alloc] initWithWidth:512 andHeight:512];

// Draw everything that's on the stage into that texture
[screenshot drawDisplayObject:self.stage];

And that's really all it takes. For more information take a look at the API reference entry for drawDisplayObject:.

4 Texture Atlases

A sample video game texture atlas

If you've worked with games before you've probably seen a texture atlas. You may have heard it described as a sprite sheet or a tile sheet, but it all really boils down to the same thing. We decided to use the term Texture Atlas for the simple reason that it's the term Wikipedia uses.

4.1 The basics

The concept behind texture atlases is very simple. Instead of loading several images individually we combine all of them into a large texture.

Then, using some sort of meta data we define the region of each sub-image within the atlas so that we can extract and display each one individually at run-time.

Why texture atlases?

Now you may be wondering: "Why should I pack all of my images into a single texture? Wouldn't it save resources to load in each image individually?". Well, perhaps against intuition, the answer is not really.

The reason why lies in the way the graphics hardware handles images:

  • All textures must be sized in powers of two
Most GPU's require that every texture they render have a width and height that's exactly a power of two (powers of two are numbers like 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024). In practice this means that any image you load to Pixelwave is automatically expanded to have 'legal' width and height values.
But what happens to all those extra pixels left over after the image's dimensions are expanded? They just sit there being empty and sad, taking up our precious memory. Let's illustrate with an image:

The original image has a size of 174 x 130. When the time comes to load it to the GPU (so it can be displayed on the screen), the GPU says wait a minute, this image's dimensions aren't legal!. In order to avoid making the GPU angrier than it usually is, Pixelwave automatically converts the image size to the closest power-of-two, 256 x 256, as soon as it's loaded. All those extra pixels added to the image to make the GPU happy will never actually be used for anything productive, and are in essence a total waste of memory.
On the other hand, if you were to load an image who's dimensions are exactly powers-of-two, Pixelwave wouldn't need to do any extra steps and pass it right to the GPU.
All this black magic Pixelwave performs to appease the GPU doesn't really change anything from the user's point of view. The TextureData object will report the texture having the original , unexpanded size.

It's all happening behind the scenes because it has to be done, and so you don't have to bother with it.
Now that you know about all these pixels being wasted without your knowledge, aren't you kinda angry? I know I was. But it won't help to worry too much... It helps if you think of the GPU as a government agency: it'll do things in its odd ways, and there's nothing you can do about it. If you want its help you'll have to find workarounds to adapt.
In this case, one possible workaround is just making all your image assets in power-of-two dimensions. Another, more flexible option is using a texture atlas. The texture atlas lets us to use a texture with power-of-two dimensions and fill its pixels with as many images as we can, therefore wasting as little memory as possible.
  • Context switching is expensive
OpenGL, the C library that lets Pixelwave communicate with the GPU, does this thing called context switching. To avoid getting too technical, let's just say that this operation requires a relatively long amount of time to perform.
In OpenGL, every time a different texture is to be rendered, at least one context switch must occur. That means that if we draw the content of 3 different TextureData objects every frame, we'll need to context-switch at least 3 times per frame. Pretty simple logic.
The great thing about texture atlases is that they allow us to render multiple images every frame without having to switch between textures. Texture atlases allow us to maximize the performance of the GPU.
  • The GPU is great at rendering just parts of a texture
At this point you may be wondering: Isn't it taxing on the GPU to hold on to a large texture and extract pixels out of different parts of it to render?. Actually, no. The GPU is pretty optimized to do just that. Not to say that rendering an entire texture is slow or anything, just that only rendering a small part of a texture doesn't have any more overhead.

4.2 Pixelwave's texture atlas

Alrighty, now that we all know about the great benefits of texture atlases, let's talk about how they work in Pixelwave.

When designing the PXTextureAtlas class we had the following goals in mind:

  • It should be easy to use (and have a simple interface)
  • It should be able to handle common use-cases (like specifying rotation offset, or extra padding for each sub-image).
  • It should be able to load common texture atlas file formats
  • It should be simple to extend in the case of a special user requirement is needed.

The entire texture atlas system boils down to just two public classes: TextureAtlas and AtlasFrame. You can probably guess what each one does, but let's go through them in case you just tuned in.

4.2.1 The AtlasFrame class

The AtlasFrame class represents a single frame, or sub-image, within a texture atlas. While the frames that make up an atlas are usually created automatically (such as when loading it from file), there's nothing stopping you from adding your own frames to the atlas or removing some at any time.

An AtlasFrame is pretty self contained. It holds all the information one would need to extract a sub image from a texture atlas and display it. Namely:

  • The TextureData within which the sub-image exists.
  • A rectangle representing the sub-image's position and area within the texture atlas.
  • The anchor point the sub-image should have (this is optional). This is useful when creating sprite animations where all the frames of an animations should have a common anchor point.
  • The amount of padding that should be added to the image when extracted from the atlas. Most atlas creation tools have the option to automatically trim any white-space around an image when storing into the texture file in order to conserve space. The padding property lets the Texture display object know that some of the white-space has been trimmed so it can re-apply it when rendering the image.

To display the contents of an AtlasFrame you could extract all of this data manually (by accessing each of the frame's properties), but there's an easier way. Calling the [AtlasFrame setToTexture:] method with a Texture display object will automatically update it to display the frame's contents.

But it gets even easier. For most common use-cases you won't need to deal directly with individual frame objects. Operations like populating a Texture display object with a frame's contents can be done directly from the main TextureAtlas class. Read on for more details and some code examples.

4.2.2 The TextureAtlas class

As its name suggests, the TextureAtlas class is really the meat of it all. In essence though, it's actually pretty light-weight. The TextureAtlas class is just a collection of AtlasFrame objects, each associated with a unique string used to reference it.

The TextureAtlas class provides the basic functionality to add/remove/fetch frame object. It also has useful utility methods to make your life easier:

Methods to create a texture atlas with the contents of a file:

initWithContentsOfFile:modifier:
initWithData:modifier:

Methods to add and remove frames:

addFrame:(PXAtlasFrame *)frame withName:(NSString *)name
removeFrame:(NSString *)name

Methods to extract atlas frames directly into Texture objects:

textureForFrame:
setFrame:toTexture:

Methods to grab useful information:

PXTextureAtlas.textureDatas
PXTextureAtlas.allNames
PXTextureAtlas.allFrames

4.2.3 Using texture atlases

We spent a good amount of time talking about texture atlases, how they work, and how they're implemented in Pixelwave. All of that is important for you to know so you can make the most efficient use of resources. But you'll be glad to know that actually using texture atlases on a daily basis is very easy and often requires no more than one or two lines of code.

So enough talking, let's write some code. Here's how you'd most often load a texture atlas from file:

PXTextureAtlas *atlas = [PXTextureAtlas textureAtlasWithContentsOfFile:@"myAtlas.json" modifier:nil];

And here's how you'd display an image from the atlas on the screen:

PXTexture *texture = [atlas textureForFrame:@"image1.png"];
[self addChild:texture];

Easy, right? To learn more about the capabilities of the TextureAtlas class check out the sample project included with Pixelwave and the API reference.

4.2.4 Creating texture atlases

There are two main ways to create a texture atlas:

The long way

You can create texture atlases manually via code. Just create an empty TextureAtlas:

PXTextureAtlas *atlas = [PXTextureAtlas textureAtlas];

Next you'd probably want to load your texture atlas image:

PXTextureData *atlasTextureData = [PXTextureData textureDataWithContentsOfFile:@"atlasImage.png"];

Now you can create an AtlasFrame object for each sub-image and add it to your atlas:

PXAtlasFrame *frame1 = [PXAtlasFrame atlasFrameWithClipRect: [PXClipRect clipRectWithX:100 y:150 width:56 height:37]
													textureData: atlasTextureData];
	
[atlas addFrame:frame1 withName:@"frame1"];

As you can see, it's not very difficult to do it this way, but it'll probably take a while. As you probably already guessed, there's an easier way.

The easy way
Texture atlases don't need to be created manually these days

If you've ever tried to create a texture atlas by hand I probably don't have to tell you how tedious it can be on both the graphical and programmatic side. Thankfully there are a few applications out there that will automatically create a texture atlas for you. You just need to tell them which images you want to include and they'll take care of the rest, composing the final atlas image and creating a meta data file describing all the frames within (usually in XML or JSON format).

One of the best things about automatic tools is that they handle changes very well. If you need to tweak the spacing between images or add another image to the atlas, they will do it faster than you can blink, and probably pack everything more tightly than a human ever could, saving you precious pixel space.

So if I didn't make it pretty obvious already, we strongly encourage this way of creating texture atlases.

To learn more about texture atlas creation tools, checkout the Third party tools section.

4.2.5 Supported file formats

Pixelwave currently supports the following texture atlas formats:

4.3 Further reading

Like a lot of things in computer science, creating texture atlases is a bit of an art form. There are some quirks and gotchas you should watch out for. You can read more about those in the "Texture atlas best practices" article.

To see texture atlases in use you can checkout the SimpleButtonsAtlas sample project included with Pixelwave.

5 Working With Text

Pixelwave contains two important classes that deal with drawing text: Font and TextField. The Font class represents raw font data (glyphs), while the TextField class is the display object used to draw lines of text to the screen.

5.1 The TextField

The TextField display object is pretty straight forward. You just need to tell it the string you want it to draw and it'll take care of the rest.

You can also tell it which font to use and tweak the size, color, and alignment of the text. Like any other display object it can also be positioned, scaled, rotated, etc.

Here's a quick example:

PXTextField *txt = [PXTextField new];

txt.font = @"Arial";
txt.text = @"Hello world!";

[self addChild:txt];

[txt release];

And here's what the output would look like:


The example above is really pretty simple. As you can see we didn't specify any limitations on what kind of text we'll be using and so Pixelwave had to make some assumptions for us. In order to accomodate for whatever string of text we provide, Pixelwave will use on-the-fly font rasterizing, which as you'll read very soon, isn't the most efficient way to render text. For a simple example like ours this inefficiency is just fine, but it may have negative effects on larger scale projects.

As you'll read in the next section, in order to maximize the efficiency of our text we could specify some limits such the specific characters we're going to use. We would do that be creating a custom font and registering it with Pixelwave.


5.2 Fonts

Working with text on desktop environments is usually a breeze. Due to the abundance of CPU power and memory, redundant glyphs (or characters) can be kept in memory and text can be rasterized on the fly (rasterizing means converting the vector shapes of a font into pixel data). On mobile environments on the other hand, we usually need to keep a closer eye on resource usage (at least for now until they catch up with desktops). While this is true for every aspect of the application, working with text may require some careful considerations.

Take for instance the case of rendering a constantly changing string (such as the current time) to the screen. If we were to simply rasterize our string every time it changed our CPU would be heavily taxed. The CPU power needed for rasterization could instead be put to better use by our particle simulation or physics engine for example.

To alleviate constant rasterization we can instead rasterize all of the characters of a font once and save that pixel data in memory. Then, when we need to draw glyphs to the screen we'd simply reference our cached pixels. While this solution would be much less taxing on the CPU it could potentially take up a lot of memory depending on the number of characters in a font.

The ultimate solution is then to find a happy medium between CPU and memory usage. It's funny how often this dilemma pops up in computer science problems.

To avoid making inappropriate optimizations, Pixelwave leaves making these decisions to you. For example, if you were working on an application in which text isn't a major component (a 2d shooter for example) you would want to avoid real-time rasterization to leave more room for things like collision detection and visual effects. If instead you were working on a text-heavy application that doesn't require expensive rendering or simulations (a trivia game for example) you'd probably be fine with rasterizing text on the fly.

Now that we're aware of the inherent optimization issues involved with text rendering, let's go through the options Pixelwave provides. There are currently two:

5.2.1 Real-time rasterization

This is the most simple and flexible method. Using it doesn't require doing any setup--it's the default rendering method. Just specify the name of the font you want to use and bam, it's there. Keep in mind that if the specified font isn't available on the system you're using the default one will be used instead.

The biggest issue with this rendering approach is real-time performance. Because it rasterizes text on the fly it can have a negative impact on larger projects. This method does lend itself well to use in experimental code or while debugging, but we don't suggest actually using it in production. That is, unless having super-flexible text capabilities is a requirement of your application.

Here is a quick code sample that uses real-time text rasterization to render a string to the screen.

PXTextField *txt = [PXTextField new];
txt.font = "Arial";
txt.text = @"OMG, I'm on teh interwebs";

[self addChild:txt];

5.2.2 Texture Fonts

Texture fonts are our happy medium. They limit flexibility while maximizing performance. A texture font asks you to specify which characters of a specific font you need to use in order to better allocate memory and minimize CPU usage.

If you think about it, most applications don't use more than numeral characters (0,1,2..), letters (A-Z), and basic punctuation characters in their text. Sometimes even less. So why waste memory on all of the unused characters? Pixelwave takes the approach of letting you pick which characters you want to use instead of which characters you want to leave out.

Once a set of characters is chosen, they are rasterized, rendered, and packed very tightly into a TextureData. We call the process of writing the rastertized data to a texture baking. Once the font's glyph's are baked they can be used at a later time without the need for rasterization. ...and this is why we call them texture fonts.

Here's what the code would look like to write the same string to the screen, but using a texture font:


// The only difference from the previous example is
// that is time we create and register a texture font
// by specifying its name and which characters we
// want to be baked onto the texture (all letters
// and the ',' and '!' characters).

PXTextureFontOptions *options;
	
options = [PXTextureFontOptions textureFontOptionsWithSize:12
												 characterSets:PXFontCharacterSet_AllLetters
											 specialCharacters:@",!"];
	
[PXFont registerFontWithSystemFont:@"Arial"
							  name:@"Arial_baked"
						   options:options];

PXTextField *txt = [PXTextField new];
txt.font = "Arial_baked";
txt.text = @"OMG, I'm on teh interwebs";

[self addChild:txt];

5.3 Texture fonts

A font sheet generated by rasterizing all the letters and numbers of the "Zpfino" font

Texture fonts are very general. They may hold any arbitrary set of characters, or glyphs, within a TextureData. In reality they are just a specific type of texture atlas. These font-texture-atlases, or font-sheets, can be generated in one of several ways:

  • By rasterizing a font that's part of the OS
  • By loading in a custom font file and rasterizing it
  • By loading in a pre-rendered 'font-sheet'

So after all of this explanation it's time to get into some examples. Let's discuss the different options and how they work:

5.3.1 Generating a TextureFont using system fonts

This is the simplest way to create a font-sheet. Since the OS already comes packed with several fonts, we can just grab one at run-time and turn it into a texture. The up-side is that we don't have to ship our app with any special font files (which may bloat the file-size considerably). The down-side is that our selection is pretty limited.

Here's the code to generate texture font which will hold all the numeral characters (0 - 9) in the 'Arial' font and the characters '-' and ','.

PXTextureFontOptions *options;
options = [PXTextureFontOptions textureFontOptionsWithSize:20
											 characterSets:PXFontCharacterSet_Numerals
										 specialCharacters:@"-,"];
	
PXFont *newFont = [PXFont fontWithSystemFont:@"Arial" options:options];

For your viewing pleasure, here's what the resulting font-sheet will look like:


As you can see, Pixelwave packed the characters together tightly to fit into the smallest texture possible. This 64x64 font sheet takes up only 4kb of GPU memory.

5.3.2 Registering fonts

Now that we've created our texture font it's probably safe to predict that we'll be using it within a text field. To do that, we need to register our new font with Pixelwave and give it a unique name.

This is how we'd register our font with the name "arial_numerals_20" (for lack of a more creative one):

[PXFont registerFont:myFont withName:@"arial_numerals_20"];

// Once a font is registered we can release our retain on it
[myFont release];

That's it. Easy, right?

5.3.3 Using registered fonts

Now that we have a registered font we can use it with a text field:

PXTextField *txt = [[PXTextField alloc] initWithFont:@"arial_numerals_20"];
txt.text = @"212-555-5555";

[self addChild:txt];

[txt release];

For your viewing pleasure (again), here's the end result:

5.3.4 Generating a TextureFont using external fonts

If the system's limited collection of fonts doesn't contain the one you need there's no reason to panic. Pixelwave has the ability to load many different types of fonts and turn them into font sheets. To handle this task Pixelwave uses the FreeType library which supports several popular font types including TrueType (ttf) and OpenType (otf).

The process for creating a texture font from file is actually almost identical to the process for creating one using a system font. The only difference is that this time we'll need to load the font from the hard-drive first.

Let's use a simple example. The font BadaBoom doesn't come pre-installed on many systems (none that I know of at least), so it's safe to say we'll need to load it from file if we'd like to use it. BadaBoom also happens to be a TrueType font so Pixelwave should be able to load it (what a coincidence). On OSX it looks like this:

To use this font we'd need to first include the BadaBoom font file into our project. Let's hypothetically call this file "BadaBoom.ttf". Now let's write the code that will load and register it:

// This time we'll use a utility method that'll load
// and register our font in one shot instead of doing
// it the "long way" as we did in the previous example

PXTextureFontOptions *options;
options = [PXTextureFontOptions textureFontOptionsWithSize:20
											 characterSets:PXFontCharacterSet_LowerCase | PXFontCharacterSet_Numerals
										 specialCharacters:nil];
	
[PXFont registerFontWithContentsOfFile:@"BadaBoom.ttf"
								  name:@"BadaBoom"
							   options:options];

You'll notice the entire process is exactly the same as before, only this time we had to specify the file name instead of the system font name. The class that does the magic behind the scenes here is PXFontLoader.

Here's the font sheet Pixelwave would produce for our font (magnified for effect):

Now that we loaded and registered the font we can use the registered name ("BadaBoom") with our text field exactly as we did with the system font before:

textField.font = @"BadaBoom";
textField.text = @"omg this is so badaboom"; // silly

And here's what that would look like:

5.3.5 Loading custom font sheets

This section is currently under construction and will be updated soon.

6 Audio

Pixelwave abstracts playing audio from several different subsystems (OpenAL, AVFoundation) into one simple class: Sound. The Sound class represents audio data in memory. When a sound is no longer needed, freeing it's memory means simply releasing the Sound object associated with it.

A sound may be made of compressed or uncompressed data and may deal with different audio systems internally, but that shouldn't bother you one bit. You only need to know how to deal with the Sound class.

6.1 Loading Sounds

Loading sounds can sometimes be a complicated process. It usually requires parsing different codecs and dealing with different libraries, but you know what... it doesn't have to be! Loading sound with Pixelwave is really simple. It can be as simple as:

PXSound *beepSound = [PXSound soundWithContentsOfFile:@"beep.wav"];

That's it. Now we have the sound "beep.wav" loaded in memory and ready to be played.

SoundLoader

While in most cases using the utility function [PXSound soundWithContentsOfFile:] used above will suffice, you should know that all sound loading uses the SoundLoader class. The SoundLoader works just like the TextureLoader and FontLoader classes:

  • It loads sound files from the hard-drive synchronously and parses them
  • It spawns Sound instances that contain unique copies of the loaded data

If you need more control, or the ability to keep a sound in main memory without loading it into sound card memory, you can use the SoundLoader class. Actually, the code sample above uses the SoundLoader class under the hood.

Here's a quick code sample for loading a sound with the SoundLoader class:

PXSoundLoader *soundLoader = [PXSoundLoader soundLoaderWithContentsOfFile:@"beep.wav"];
PXSound *beepSound = [soundLoader newSound];

As you can see, the SoundLoader is created once and can be used to spawn off new instances of the Sound class with the [PXSoundLoader newSound] method.

A sound loader, for lack of a better metaphor, is like a pair of cheap airline headphones. You usually use it once and dispose of it. Here's a code sample that loads a sound using the SoundLoader class directly:

// Create a variable to hold our loaded sound.
PXSound *beep = nil;

// 1. Load "beep.wav" synchronously.
PXSoundLoader *soundLoader = [[PXSoundLoader alloc] initWithContentsOfFile:@"beep.wav"]];

if (soundLoader)
{
    // 2. If the sound was loaded successfully, create a Sound object.
    beep = [soundLoader newSound];
    
    // 3. Unload the SoundLoader from memory, as 'beep' now contains
    // all the data we need to play a sound.
    [soundLoader release];
    soundLoader = nil;
}
else
{
    // If the sound wasn't loaded sucesfuly, let the user know.
    NSLog(@"Error loading 'beep.wav'");
}

The lifecycle of a SoundLoader isn't normally very long:

  1. When instantiated, the SoundLoader is passed a path string which is used for synchronously loading the sound. Once the raw sound data is loaded it is contained in the SoundLoader object.
  2. The [PXSoundLoader newSound] method must then be called to create an instance of the new sound.
  3. The new sound is completely independent of the SoundLoader so in most cases there's no reason to keep the SoundLoader around anymore and it can be deallocated.

6.2 Playing Sounds

Once you've loaded a sound you can play it with the [Sound play] method:

PXSound *sound = ...;
[sound play];

This will cause the sound to start playing immediately.

If we take a quick look at the play method, we'll see that it returns a SoundChannel object. This object represents the newly created sound stream, and can be used to control it (pause, change volume, change pitch, etc). If you call the play method 5 times, 5 different SoundChannel objects will be created, each representing a unique sound stream.

While a Sound object holds the actual sound data, a SoundChannel object represents a currently playing stream of that sound, or a channel. The SoundChannel class allows you to do the following operations to a playing a sound:

  • Stop
  • Pause / play
  • Change volume/pitch
  • Change positional audio properties
Remember that the Sound object represents the sound's data in memory while SoundChannel objects are streams of a sound that are currently playing, and don't use any additional memory.

6.2.1 Finer control

Now you may be thinking "Ok, the play method is pretty cool, but what if I want a bit more control over how the sound is played? What if it should start at a specific volume, play a specific amount of times, or start at a specified time?" The answer lies in the complete version of the play function:

- (PXSoundChannel *)playWithStartTime:(unsigned)startTime
                            loopCount:(int)loops
                       soundTransform:(PXSoundTransform *)soundTransform;

The startTime and loops parameters seem pretty self-explanatory, but let's go into more detail about the soundTransform parameter.

6.2.2 The SoundTransform

The SoundTransform class is actually pretty straight forward. It describes how a SoundChannel should be transformed when playing. For standard sounds these transformations include volume and pitch.

Setting the transformation of a sound channel can be done when playing the sound (when the sound channel is created), or at any point during its lifetime. To set the transform as soon as a sound begins playing, pass a SoundTransform object as the transform parameter of the playWithStartTime: method (now that's a mouthful).

Here's a code example to make your brain hurt less:

PXSoundTransform *transform= [PXSoundTransform new];

transform.volume = 0.5f;

// Play the sound at 50% volume and make it loop twice
[sound playWithStartTime:0
               loopCount:2
          soundTransform:transform];

You can also just pass nil for soundTransform to use the default transformation (volume:1.0, pitch:1.0).

To play the sound in 3D just pass an instance of SoundTransform3D instead. More on that in a bit.

6.3 Global control

Sometimes you need fine control over every individual sound. But alot of times you don't--that's where the SoundMixer class comes in handy. It lets you interact with the sound system on a global level. You can use the static methods of the SoundMixer class to:

  • Set the global sound transform (the master volume/pitch for all sounds).
  • Set the speed of sound (applicable when using the Doppler effect).
  • Interact with the sound listener (applicable only to 3D sounds).
  • Set the 3D distance model (applicable only to 3D sounds).

To read more about the SoundMixer class and the functionality it exposes, check out the API reference entry.

6.4 3D Positioning

Playing a sound in 3D is quite simple. The only thing that needs to change is the type of transform object used with the SoundChannel. Just use a SoundTransform3D instead of the default SoundTransform. Here's an example:

// Create a sound object
PXSound *mySound = ...

// Set up the sound's transformation
PXSoundTransform3D *transform3D = [PXSoundTransform3D soundTransform3DWithVolume:1.0 andPitch:1.0];

// Set the sound's position
transform3D.x = 100.0f;
transform3D.y = 50.0f;
transform3D.z = -60.0f;

// Play the sound with a 3D transformation
[mySound playWithStartTime:0
                 loopCount:0
            soundTransform:transform3D];

Remember that we always set the transformation of the SoundChannel, not the transformation of the Sound object. A Sound object is just a collection of bytes in memory while a SoundChannel is a currently playing stream of sound. Each SoundChannel has a unique transform that can be changed at any point, even while the sound is playing.

Here's how we'd change the position of the sound while it's playing:

PXSoundChannel *channel = [sound play];

PXSoundTransform3D *transform3D = (PXSoundTransform3D *)channel.soundTransform;

transform3D.x = 150.0f;
transform3D.y = 0.0f;
transform3D.z = 0.0f;

channel.soundTransform = transform3D;

6.4.1 The Sound Listener

When dealing with objects in space, whether sounds, polygons, or angry ogres, there is always a point from which things are perceived by the viewer. In the visual word we call this view-point the camera. When working with sounds, the view-point from which everything is heard is called the listener.

In Pixelwave this listener is represented by the SoundListener class. There is a single, shared SoundListener object that the engine uses as the reference point from which to calculate the appropriate volume, pan, and pitch of every 3D sound. You can think of the listener as a set of ears, or microphones, from which all 3D sounds are heard.

As you may expect, the position of the sound listener has absolutely no effect on non-3D (ambient) sounds.

Pixelwave's shared SoundListener object lives in the SoundMixer class. You can grab a reference to it like so:

PXSoundListener *sharedSoundListener = [PXSoundMixer soundListener];

You could then set its position like so:

sharedSoundListener.x = 50.0f;
sharedSoundListener.y = -60.0f;
sharedSoundListener.z = -4.0f;

To learn more about the SoundListener class and the properties it exposes, check out the API entry for it.

6.4.2 3D sounds in a 2D environment

[IMAGE OF 3D sound vs a 2D sound]

When it comes to virtual sound positioning, Pixelwave supports both 3D and 2D sounds. While 3D sounds are very popular in three dimensional applications, they don't always make sense within a two dimensional environment. If your app's visuals are strictly 2D there's usually no good reason to keep the extra dimension when it comes to sounds.

Although 2D sounds do make sense within a 2D environment, it seems that they aren't being used too much in games these days--most games get away with playing ambient (1D) sounds. We're not sure why this is, since 2D sounds are really cool. Perhaps it's because there isn't a really simple way to harness 3D sounds into a 2D environment? I'm not sure. Either way, we tried to make it as simple as possible without limiting those applications that need the 3rd dimension.

The main questions that pop up when using trying to limit 3D sounds to two dimensions are:

  • What coordinate system should I use (Should my sounds live in the x-y plane, the x-z plane, or some other arbitrary plane?)
  • How should I rotate the listener relative to the sounds? (Setting the rotation of the listener in 3D requires some knowledge of linear algebra)

So to deal with these dilemmas we came up with a user-enforced convention for dealing with 2D sounds. This convention is simply a way to set up your sound objects and sound listener. While you are of course welcome to use your own conventions, we tried to make ours as simple and intuitive as possible. To follow this convention with your 2D sounds, just do the following:

  • Only specify the x and y components of the position for your sounds and listener. you can just ignore the z component and leave it at 0.
  • Don't set the orientation of the SoundListener the 'hard-core' way (by setting the up and forward vectors). Instead use the utility function [soundListener setRotation:using2DPerspective:].

This is the quickest way to use 2D sounds in Pixelwave. It allows you to keep using only the x and y properties, just like you're used to with display objects. It also lets you set the rotation of the listener by specifying a single rotation value and a type of perspective. This perspective can be side-view or top-down, depending on your specific application. To read more about this check out the API reference.

6.4.3 Requirements for playing 3D sounds

Due to the limitations of OpenAL (the library responsible for 3D audio), not all sounds can be played with a 3D transformation. In order for a sound file to be playable in 3D the following requirements must be met:

  • The sound must be of an uncompressed format (ie. wav, aiff, but not mp3)
  • The sound must be mono (have only a single channel)

If either of these aren't true for a sound, any 3D transformation of it will simply be ignored and the sound will continue to play ambiently.

If you'd like to play a stereo sound in 3D you'd need to convert it to mono first. If you are too lazy to convert the sound to mono yourself, it can be converted by Pixelwave automatically at load-time. To learn about converting a sound to mono at load-time, check out the SoundLoader class.

Although converting a sound to mono with the SoundLoader class is very easy and convenient, we advise against using it in a non-debugging build of your app. To avoid the overhead involved with run-time conversion and the extra hard-drive space needed to store a stereo sound (just about double), we suggest converting the sound to mono using an external sound utility and then importing it into your project.

The section Supported formats below contains a full list of all supported sound formats and their supported transformations

6.5 Supported formats

All supported sound formats and their supported transformations:

File Type Basic playback Pitch shifting 3D positioning
wav (mono only)
caf (mono only)
aif (mono only)
aiff (mono only)
mp3 (compressed)
mp4a (compressed)
acc (compressed)
alac (compressed)

Basic playback - play/pause/stop/rewind functionality

7 Event Dispatching

Pixelwave uses a custom event dispatching system to notify the user of different events that occur in the system. These events include touches (screen interaction), button presses, sound playback progress, etc. Custom events can be dispatched by the user as well.

A note to AS3 Developers:
The event dispatching system in Pixelwave is identical to the one used in AS3. The only exception is that strong-referenced listeners aren't supported due to the memory-management (in)capabilities of Objective-C.

7.1 Event Listeners

In order to dispatch events an object must extend the EventDispatcher class. The EventDispatcher lets the user register a user-made function with a specific type of event. It also allows the user to create and dispatch custom events.

To interact with the event disptacher you would first register an event listener for a specific type of event. An event listener is simply a user-designed function designated to respond to a specific type of events.

All display objects are capable of dispatching events because they extend from the EventDispatcher class.

As a quick example, when working with a SimpleButton you may register an event of type "tap" to the function [onButtonPress:]. Let's dive right into some code to see how an event listener can be applied to a button press:

// Define the event listener function
- (void)onButtonPress:(PXTouchEvent *)event
{
    // Do something related to the button press
}
// Create a button
PXSimpleButton *button = [PXSimpleButton new];

// Register the 'onButtonPress:' method with the "tap" event
[button addEventListenerOfType:PXTouchEvent_Tap
                      listener:PXListener(onButtonPress:)];

There are 3 special things to pay attention to in the above example:

  • Listener functions take an Event object as a parameter
The first parameter of every function registered as an event listener must be of type Event or a sub-class of the Event class. If an event listener function takes a first parameter of a different type you can expect some odd run-time errors to pop up.
The event object passed to the event listener contains information related to that event. In the case of a button press, the event contains information such as the position of the touch that initiated the button press.
  • Event types are strings
The type of the registered event in the above example is PXTouchEvent_Tap. PXTouchEvent_Tap is just a string constant defined by the TouchEvent class to describe a 'tap' event. To learn about all of the event types associated with the TouchEvent class, take a look at the API Reference.
  • Event listener functions must be wrapped in an EventListener object when registered.
Objective-C doesn't provide an easy way to pass around specific function references. It requires passing the name (selector) of the function and the object in which it resides. To make bundling these two components simpler, Pixelwave uses the EventListener class. An EventListener object is instantiated with a selector and a target. The EventListener object is then be used along with the [addEventListenerOfType:] method to register the function to a given event type.
A quick look into the addEventListener method will show us that the second parameter, listener, must be of type EventListener. If that's the case then how come the above example gets away with using PXListener?.
PXListener() is a convenience macro that generates an EventListener object with the given method signature as the selector and and the current self variable as the target.
To illustrate better, the following line:
 [PXEventListener eventListenerWithTarget:self
                              selector:@selector(onButtonPress:)];
Is identical to:
 PXListener(onButtonPress:)
You can choose to create event listeners either way. We suggest the shorter way.

7.2 Removing event listeners

This is an important note about event listeners

All event listeners in Pixelwave are weakly referenced, which means that an event listener can potentially point to a function that no longer exist.

To understand why this could cause problems, let's take a look at this simple scenario:

  1. You create a button and add it to the stage. Let's call this button btnClose
  2. You then create a pop-up box and add it to the screen as well. This pop-up box object has a method called [closeMe] which removes the screen from the stage and deallocates it.
  3. To make the button close the box when pressed, you decide to register the pop-up box's [closeMe] method as the event listener for btnClose's tap event.
  4. Still following? Good. Now that all the code is set up you run the app and see a pop-up box along with the 'close' button on the screen. You press the 'close' button and the box goes away. Good. You press the button again and boom, the program crashes without any good explanation.

Ok, so what happened?

The first time the 'close' button is pressed, it dispatches the tap event which in turn invokes the [PopUpBox closeMe] method. This closes the pop-up box and deallocates it from memory. Because the [closeMe] method was a part of the PopUpBox object, the function no longer exists either.

The second time the button is pressed, the 'tap' event gets dispatched once more, only this time it tries to invoke a function on an object that's been deallocated (oops)! As we know, accessing these zombie objects, like dealing with real zombies, can't end well (unless you are Chuck Norris of course, in which case you should click here).

Unfortunately Objective-C doesn't provide a bullet-proof way to avoid these errors, so you must be very careful to remove an event listener when it's no longer needed.

To avoid this pesky problem follow these guidelines:

  • Remove all event listeners that belong to an object that is about to be deallocated. In other words, for every addEventListener call within a class there should be a matching removeEventListener call somewhere, usually in the dealloc method.
  • Try to only use methods within the scope of self as event listeners. In other words, don't use a method of an object other than self as an event listener (like we foolishly did in the above example). If you use PXListener() to create event listeners this will be enforced automatically.

To fix the above example we'd need remove the event listener after closing the popup box. Event listeners are removed with the [EventDispatcher removeEventListenerOfType:...] method.

7.3 Event Objects

This section is currently under construction and will be updated soon.

7.4 EnterFrame events

This section is currently under construction and will be updated soon.

7.5 Touch Events

This section is currently under construction and will be updated soon.

7.6 The Event Flow

This section is currently under construction and will be updated soon.

More information over at http://livedocs.adobe.com/flex/3/html/help.html?content=events_08.html

8 Extending Pixelwave

This section is currently under construction and will be updated soon.

9 Useful Utilities

Pixelwave includes several classes that were designed to make your life easier with day-to-day operations. You certainly don't have to use most of them to get your work done, but they'll probably save you some time and effort.

The Math class
This class provides several static functions for generating random numbers. Although it's common for the Math class to implement mathematical operations such as sin, cos, sqrt, etc. Pixelwave avoids wrapping those as it can introduce unnecessary overhead. Instead it's suggested that you use the C methods provided by the native run-time.
The LinkedList class
Provides an efficient implementation of a linked list data structure. Linked lists are most useful when dealing with ordered collections of items which need to be added / removed into and from arbitrary locations in the list.
The main reason for implementing our own version of a linked list was that there is no native implementation of a linked list in the Cocoa framework. Pixelwave requires dynamic lists, which can be expensive to maintain with the NSArray class, and NSArray turns out to be pretty slow. In fact, Pixelwave's linked list can be up to 10x faster at certain operations.
The ObjectPool class
Pooling objects can be a great way to speed up your program at the cost of a little more memory usage. Pixelwave's object pool is very configurable and can handle any type of class.
The geom classes
Pixelwave uses the PXPoint, PXRectangle, and PXVector3D, and PXMatrix classes internally to represents geometrical objects, but they can be instantiated and used by anyone at any time.

10 Cocoa Touch Specifics

This section is currently under construction and will be updated soon.

11 Optimizations

This section is currently under construction and will be updated soon.

12 PixelKit

    This page was last modified on 4 October 2011, at 21:55.