All About In-App Purchases Part 4: iTunes Connect

We’re used to going through the approval process for binaries, but in-app purchases are a bit different. Here’s some of what I learned going through this for Flower Garden.

Purchase States

When you first create an in-app purchase in iTunes Connect, it is listed as “Pending Developer Approval”. This means that if you ask for info on that product id with a SKProductsRequest, you’ll get the info for the item only on development builds, not in distributions builds.

Once your in-app purchase item is ready, you need to upload a screenshot of it working in the app, and approve it yourself. At that point it enters the queue to be approved by Apple. Once an item has been approved by Apple (marked as Ready for Sale), it will appear both in development and distribution builds.

This behavior is very convenient because you can add in-app purchases normally for development, and not worry about them showing up for all your existing users until they’re done. Just make sure to keep around a distribution build of your app to test what the end user sees.

In addition to that, you can flag items as available for sale or not. That way you have the added flexibility of temporarily removing an item from sale, or making it available at a later time.

itunesconnect

Approval Process

The first time you submit any in-app purchases, you’ll probably do it along with a binary update (since you need to code in support for displaying and allowing the purchases of items). You’ll have an option to mark the items to be approved along with the binary, so everything will become available on the App Store at the same time. In that case, the approval process is just like for a regular binary update (it seems to be averaging a week lately).

You can also submit in-app purchase items by themselves, without an app binary update. I did this for the Seeds of Winter pack in Flower Garden. Somehow, I was expecting a turnaround time of a day or two. After all, I figured they would look at the provided screenshot and approve it. Unfortunately, that wasn’t the case. In this particular situation, approval took almost a week, and I’m not sure it was because of the normal process, or because I emailed Apple asking what the procedure was to submit a product by itself (the product was approved within an hour of me getting a response from them saying they would look into it).

I was monitoring iTunes Connect pretty obsessively at the time it was approved (because it was right before my two-week break for the holidays) and I saw that the Seeds of Winter pack went from “Waiting for Review” to “In Review” to “Approved for Sale” in a matter of two minutes. That was one quick check! (I’m not complaining though).

What’s even more interesting is that I didn’t know that an item wouldn’t appear on a distribution build unless it was approved by Apple, so I had added an extra layer on the shop catalog file on my server that allowed me to prevent an item from showing up on the Flower Shop until I turned it on myself. And since that check wasn’t turned on, it means that Apple approved the in-app purchase by looking at the provided screenshot, without running it in the app.

This shouldn’t be too surprising. After all, I’m not even sure what the point is for in-app purchase approvals. For content that comes from the server, I can change it at any time, so the only thing they can approve is the name and description provided in iTunes Connect. I suppose the screenshot is fine for that, but I would also expect really speedy approval times. After all, not having to wait for lengthy approval processes is one of the big draws for in-app purchases.

I did learn one important lesson from going through this process. Next time I submit a binary prepared for more in-app purchases, I’m going to create stand-ins for all possible in-app purchases I’m considering and submit them for approval at the same time as the app binary, but marked as not ready for sale. That way they will all be approved at once, and I can take my time creating the final version for those items and making them available instantly whenever I want.

Purchasing Items

The same way that non-Apple approved items only show up in development builds, the only accounts that can purchase any items on development builds are test accounts using the sandbox environment. These are accounts you create on iTunes Connect and allow you to purchase items in your programs without spending real money (otherwise I’d be in trouble considering how many times I ended up purchasing items during the development of the Flower Shop!). Conversely, distribution builds only work with non-test accounts and non-sandbox environment.

One thing that threw me off was that I started getting support requests from people who had trouble logging in with their account to make in-app purchases. They claimed they were being asked about this “sandbox environment”. That threw me in a panic for a second, because I thought that everybody was somehow accessing the sandbox environment and nobody could make any purchases (because they had full iTunes accounts). It turns out, that’s a problem that happens with jailbroken phones with a particular app installed. It’s nicely documented here so I’ve been pointing people in that direction.

Finally, I believe there’s no provision in iTunes Connect to deal with different binary versions, but that’s something that’s very important to handle. If I release a new binary with some new feature built into the app, and an in-app purchase to unlock it, I don’t want people with an earlier version purchasing it and getting mad that it’s not working. Ideally this should be an option in iTunes Connect, but in the meanwhile, I build a version check into the shop catalog itself. That way only apps with that version or higher can even display a particular item. Or I can even take it a step further and display the item but notify the user that they need to update the app before the purchase it.

All About In-App Purchases Part 3: Anti-Piracy Measures

One of the big advantages of in-app purchases for some people is that it makes pirating content more difficult. For me it wasn’t much of an issue because a) Most of the people who enjoy Flower Garden aren’t the ones checking out the app warez sites, and b) I know better than to waste my time trying to prevent piracy [1]. Besides, I always roll my eyes whenever I hear that 90% or some other large percentage of the users are using a pirated copy, so the developer lost all that money. News flash: 99% of those users using a cracked version wouldn’t have bought your product anyway, so you can rest a bit easier at night.

Even so, I decided to take a few extra steps that at least will make things a bit more difficult to crack and keep things more secure. I could have gone way beyond this, but I don’t think that would have been worth my time.

Verify the purchase

fg_pirateRight now, you can download programs that will automatically crack an app by extracting its data and resigning it so it works on any jailbroken phone. There’s no doubt that even if you implement in-app purchases just like I described in earlier posts, crackers will have to analyze the code and do some steps by hand, so that might be enough to slow down the pace of cracked apps. Even so, an experienced cracker won’t take long to crack your in-app purchases unless you take some extra steps. All they have to do is find the function that you call in response to getting the go ahead from the App Store and they’ll gain access to all content.

The first thing you should do is to verify that the purchase happened correctly. To do that, once you get the notification in your app that the payment was complete, you send the receipt to your server, and have your server verify it in turn with the App Store. Since crackers don’t have access to your server (if they do you’re in more trouble than a few cracked apps), that step is secure. If your app was cracked and the user didn’t pay for the purchase, the App Store will fail the verification of the receipt and you don’t have to deliver the content.

How do you check that the receipt is valid? First of all, you need to send the receipt of the purchase to your server. I send it along with some other info, like the user’s UDID, what product was purchased, etc so I can keep general usage statistics. The relevant bits of code are the following:

NSString* receiptString = [self createEncodedString:transaction.transactionReceipt];
//...
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[@"Content-Disposition: form-data; name=\"receipt\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];
//...
[req setHTTPBody:postBody];

The createEncodedString function takes some arbitrary data and creates a base64 encoding. That’s one of the parts that is not particularly well documented in the SDK documentation.

- (NSString*) createEncodedString:(NSData*)data
{
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    const int size = ((data.length + 2)/3)*4;
    uint8_t output[size];

    const uint8_t* input = (const uint8_t*)[data bytes];
    for (int i = 0; i < data.length; i += 3)
    {
        int value = 0;
        for (int j = i; j < (i + 3); j++)
        {
            value <<= 8;
            if (j < data.length)
                value |= (0xFF & input[j]);
        }

        const int index = (i / 3) * 4;
        output[index + 0] =  table[(value >> 18) & 0x3F];
        output[index + 1] =  table[(value >> 12) & 0x3F];
        output[index + 2] = (i + 1) < data.length ? table[(value >> 6)  & 0x3F] : '=';
        output[index + 3] = (i + 2) < data.length ? table[(value >> 0)  & 0x3F] : '=';
    }    

    return  [[NSString alloc] initWithBytes:output length:size encoding:NSASCIIStringEncoding];
}

That just sends the receipt to my server. Once the server receives that request, it needs to verify the data with the App Store. The exact format of the App Store request is, again, not well documented. It turns out it needs to be sent as anonymous data in a POST request. After some trial and error, this is the php code I’m using in my server to verify the receipt:

function isReceiptValid($purchase)
{
	$data = array ('receipt-data' => $purchase->receipt );
	$encodedData = json_encode($data);

	$url = "https://buy.itunes.apple.com/verifyReceipt";

	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_POST, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData);
	$encodedResponse = curl_exec($ch);
	curl_close($ch);

	if ($encodedResponse == false)
	{
		logError($purchase, "Payment could not be verified (no response data).");
		return false;
	}

	$response = json_decode($encodedResponse);
	if ($response->{'status'} != 0)
	{
		logError($purchase, "Payment could not be verified (status != 0).");
		return false;
	}

	$purchase->storeReceipt = $response->{'receipt'};
	return true;
}

If things check out, the server returns a valid code, and you can go ahead and make the content available to the user. Otherwise, it means the user is trying to pass up a fake purchase as valid and you can deal with it any way you want.

Get content from the server

Of course, if the content is already embedded in the app itself, the cracker can bypass both the App Store purchase and the receipt verification through your server, and just access the content directly. If you want a better chance of not being cracked, have your server deliver the content. That way, if the receipt doesn’t check out, you never transfer the content.

In the case of Flower Garden, all seed packets are downloaded from the server, but the other purchases (extra pots, extra fertilizer) are just changing a few internal variables, so they would be a lot easier to crack.

One of the easiest ways that crackers can get around this verification is by buying the content once and saving the receipt that comes with it. Then, cracked versions can pass that receipt to your server, they’ll be validated as correctly, and they’ll be able to download the content. So, as an added precaution, you might want to keep track how many times a certain purchase is re-downloaded, especially from different UDIDs. I can’t imagine a legitimate reason for a user to download the same purchase more than 20-30 times from different devices, so denying the purchase at that point in the server seems justified.

Save those receipts

The last step I to make cracking more difficult was to save the receipt itself to disk along with the content that was downloaded. Then, periodically, the game can pick one of those receipts, and verify with the server that it’s still valid. If it’s ever reported as an invalid receipt, the game deletes the content for that purchase. This is not as draconian as it sounds, because even if it’s ever triggered for a legitimate user (and I have no idea how that could be other than data corruption), they can always re-purchase it again for free (and they would want to do that to fix their data corruption anyway).

To be able to delete things easily and cleanly, I keep each downloaded in-app purchase on a separate directory. So it’s very easy to delete the whole directory without having to access files everywhere. The hardest thing is making sure the game deals with the missing data and that nothing is referencing it. I’ll touch on that on a future part of this article.

Keeping purchases in separate directories also makes it easier to reset all purchases. That’s something I didn’t originally include in Flower Garden, but I should have. Sometimes, data can get corrupted or maybe a bug occurs because of some combination of purchases (hopefully not, but you never know!). Allowing the user to reset the purchases and re-download them allows them to fix those problems without having to reinstall the app and lose their progress.

[1] I know from first hand experience that one dedicated teenager with all the time in his hands can spend inordinate amounts of time bypassing the most elaborate anti-piracy schemes. The harder it is, the more of a challenge it becomes, and the more fun it is. To this day, I still remember the thrill of finally bypassing the copy protection of 1943 on the Amstrad CPC after two days of non-stop hacking, which included several loops XORing all the available memory (including the currently executing code), and messages from the developers taunting me!

All About In-App Purchases Part 2: Selling The Goods

The last entry about in-app purchases left off with the products correctly displayed on the store and ready for purchase. Let’s push that buy button!

Purchasing a product

iap_1The store is fully populated, the items are displayed and, if all goes well, the user presses the buy button. What now?

At this point I check that in-app purchases are enabled and if they aren’t, I put up a message box. Apple says you can also do this earlier, but I figured I’d rather show users what’s available and entice them to buy it. By the time they get that message, they’ll be much more likely to enable them and continue the purchase than if they never saw the store in the first place.

Actually, before I even do that check, I see if the item is currently offered for free. If it is, the code takes an alternative path that doesn’t involve the App Store because apparently you can’t set an in-app purchase to free. Really! Why not? It’s yet something else we need to implement ourselves. Please, Apple, fix that. In the case of free purchases (like the Bonus Seeds included in the full version of Flower Garden) I contact my server directly and download everything from there. If you’re thinking that a hacker can just set the free flag to true and have access to all your content, it’s not that easy, because the server checks that the product is really marked as free when it’s contacted, and if it isn’t, it declines the transaction. So they have to work a bit harder than that to crack things (more on that next time).

But if the product is not marked as free and in-app purchases are enabled, then you can add the payment to the StoreKit payment queue which is, of course, another asynchronous call. So now we’re back to having to make a decision of what we want the user to do after they attempt to purchase something. Does someone really want to move away from the screen before they complete the purchase? I don’t, so I set another blocking activity screen explaining what’s going on with an activity indicator.

if ([SKPaymentQueue canMakePayments])
{
    SKPayment* payment = [SKPayment paymentWithProductIdentifier:m_product.m_id];
    SKPaymentQueue* queue = [SKPaymentQueue defaultQueue];
    [queue addPayment:payment];
    [m_progressViewController setText:@"Accessing store" completed:NO];
    [m_progressViewController display:YES animated:YES];
}

To be able to test the actual purchase, you need to create a test account in iTunes Connect. It’s slightly annoying that you need to explicitly log out from the Settings | Store screen and you can’t do that from the app itself, but it’s not nearly as much of a big deal as not being able to test purchases from the simulator at all. That’s a big fail as far as I’m concerned, and I can’t imagine a single reason why that was done that way (other than lack of time/manpower on Apple’s side).

Completing the purchase

You think we might be done by now, but not. Far from it. We’re just getting warmed up.

Now that the payment has entered the queue, you’re going to get a callback notification every time its status changes. That means you’ll get one right away because it was just added to the queue, another time when the payment is approved by the App Store, or when it fails, or if it’s detected that it was already purchased and this a re-purchase.

iap_2While that’s happening, the StoreKit code will bring up those ugly message boxes asking the user if they’re really sure they want to purchase the item for that price, and possibly ask them for their login info. It’s funny that the first time I implemented this part, I thought that it was up to me to put those message boxes up, so they were showing up twice. You don’t have to do anything for that, whether you want to or not. What’s worse, is that each time one of those message boxes comes up, your app gets a message putting it on hold, which usually means the sound is temporarily muted. Looks very unprofessional and I’m surprised Apple forces that on us while they leave everything else so open ended and requiring so much work.

Once the purchase is marked as complete, you can obtain the content for that purchase and make it available in your app. After that step is complete, you can remove the payment from the queue and the transaction is considered complete. In the case of Flower Garden, most purchases involve accessing my server and downloading some data from there for new seeds. Some purchases don’t require any downloading though, and all they do is twiddle a few bits internally.

It’s important to understand the payment queue well. First of all, it’s persistent, so once a purchase payment has been added to it, it will be there next time the application starts if it was never removed. It was designed that way to make sure an application can deliver on the product that was purchased even if the app is stopped, crashes, or is interrupted in any way. So initially, a payment is added to the queue meaning that the user has initiated the purchase transaction. At that point, the transaction can fail for multiple reasons: User cancels payment during one of the prompts, wrong user ID, the servers are down, the product was removed from the store, etc. Assuming the purchase is successful, the user account will be charged the cost of the product and the payment will be marked as successful. It’s at this time that you have to deliver the goods. Only once you’ve successfully done that you can go ahead and remove the payment from the queue. Otherwise, next time that the app starts, your code should detect a payment there and continue processing it. You really never want to get in a situation that the user pays for something and it’s never delivered to them.

I’m happy with how the StoreKit API will keep track of past purchases, and if a user tries to buy the same item again, they’ll be given the option to redownload it for free. It’s a great relief not to have to keep track of that ourselves. It will also cut down on customer support emails a huge amount because users can always redownload something if they had to uninstall their app, or somehow, in spite of all our precautions, they bought something but it was never updated in their app. In that case a quick re-download should fix it (just make sure your code is pretty stateless and can deal with re-downloading the same content multiple times).

It gets a bit trickier though. It turns out the payment queue can have more than one payment in it. Why is that? First of all, because the buy process wasn’t blocking the way Apple designed it, a hyperactive user could initiate multiple purchases at once. The other, more realistic situation is that the user initiates a purchase, it’s verified with the App Store, but before you can complete the download, your application is shut down. The Store Kit queue will be restored next time the application starts, but at that point the user could go to the store again and start another purchase.

In any case, you really need to deal with that situation, otherwise you risk losing purchases and angering users (and you really don’t want to rely on users re-downloading something for free if you can avoid it). This is trickier than it sounds when you’re dealing with downloads from your own server because it might involve multiple downloads, multiple files, etc. So in my case, whenever I get an update of status in the SKPaymentQueue, I add it to my own queue. Then I can process that queue sequentially, one purchase at the time.

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction* transaction in transactions)
        [m_transactionQueue addObject:transaction];

    [self processNextTransaction];
}

- (void)processNextTransaction
{
    if (m_processingTransaction)
        return;

    while (m_transactionQueue.count > 0)
    {
        SKPaymentTransaction* transaction = [m_transactionQueue objectAtIndex:0];
        [m_transactionQueue removeObjectAtIndex:0];

        switch (transaction.transactionState)
        {
        case SKPaymentTransactionStatePurchased:
            m_processingTransaction = true;
            [self completeTransaction:transaction];
            return;
        case SKPaymentTransactionStateFailed:
            m_processingTransaction = true;
            [self failedTransaction:transaction];
            return;
        case SKPaymentTransactionStateRestored:
            m_processingTransaction = true;
            [self restoreTransaction:transaction];
            return;
        default: break;
        }
    }
}

Whenever a transaction is complete, you can flag it as finished and it will be completely removed from the payment queue: [[SKPaymentQueue defaultQueue] finishTransaction:info.m_transaction];

A whole  step I skipped for now is verifying the purchase with your server. I’ll cover that in the next part of this post when I talk about anti-piracy measures.

All About In-App Purchases Part 1: Displaying Store Items

Ever since Apple announced OS 3.0 with in-app purchases, I knew I had implement it in Flower Garden. The concept of in-app purchases fits very well with the idea of a flower shop where virtual gardeners can purchase extra items for their gardens. It was just a matter of justifying the time necessary to implement it. I knew from my previous experience with downloadable content in games that only a small amount of the people who originally purchased the game would be interested in buying additional content. Flower Garden has been extremely well received, both by the media and the players, but it never got high-up enough on the charts to be a big seller, so in-app purchases were doomed from the start to be very limited.

The situation improved as soon as Apple announced the availability of in-app purchases in free apps. Flower Garden Free had been out for a few weeks at that point, so that effectively doubled the potential audience.

The final consideration that convinced me to plunge ahead was the realization that while only a small percentage of users would buy extra content, a fraction of those would make multiple purchases, and some people would probably buy every available item. So maybe it would be worthwhile after all.

fg_flowershop2Having said that, I gave myself one week to completely implement in-app purchases in Flower Garden. That included everything, from implementing the actual purchasing through StoreKit, to server code, and, of course, creating the additional content itself.

For those of you familiar with Flower Garden, the new content I created specifically for in-app purchases was a flower shop offering:

  • An extra garden screen with 12 pots
  • Liquid plant fertilizer that speeds up plant growth. This is a consumable item with a fixed number of doses and can be repurchased after it’s used up.

Additionally, the flower shop in the free version of Flower Garden had these items that would bring it functionality up to par with the full version of Flower Garden:

  • Extra pots in the main garden
  • Extra common seeds
  • Set of bonus seeds

My initial estimate of one week turned out to be very optimistic, and it ended up taking closer to two and a half weeks. Most of it was spent creating the new content and integrating it smoothly in the game.

In this post and the next few ones, I’ll share my experiences with in-app purchases. From implementation details, tips and tricks, how it might help with piracy, mistakes I made along the way, and even how many sales in-app purchases generated.

Displaying Store Items

Apple did a good job documenting the overview of how to implement in-app purchases. Some of the details are missing, but they’re small enough that I was able to fill in the blanks pretty easily and get things working in a couple of days.

However, I really think the process should have been implemented very differently in Apple’s part. The sequence of actions that needs to be done feels overly complicated. It may be great if someone is trying to do a very customized and integrated store with all sorts of crazy features, but really, in 99% of the cases, developers just want to sell something and be done with it. Forcing us to go through all those steps seems overkill. A simplified higher-level set of helper functions would be a welcome addition to the SDK. For suggestions on how to go about it, look at the SDK for game consoles. They got that part right (or at least much more right than the iPhone SDK).

Getting the catalog

The first step towards implementing in-app purchases, is to get a list of all the products you want to sell in your store. It’s a good idea to keep this list off your app and on a web server instead, that way you can add, remove, or edit any products without going through a full app update. That in itself makes in-app purchases very attractive, doesn’t it? You can have a look at the master Flower Garden shop catalog.

Getting product info

Step two is to go through each of those products, and get the official product information from the App Store. That involves creating a set with the product ids you want to query, and sending a request.

The code in Flower Garden to query for product ids looks like this:

NSArray* products = m_appData->m_shopCatalog.m_products;
NSMutableSet* productIds = [[NSMutableSet alloc] initWithCapacity:32];
for (int i=0; i<[products count]; ++i)
{
    ShopProduct* product = [products objectAtIndex:i];
    if (!product.m_alreadyIncluded)
        [productIds addObject:product.m_id];
}

SKProductsRequest* request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIds];
request.delegate = self;
#ifdef WORK_WITHOUT_APP_STORE
[self productsRequest:request didReceiveResponse:nil];
#else
[request start];
#endif
[productIds release];

There are a couple interesting things in that code. First of all, notice that before we ask for the product info, we check if the product is already included in the app. That’s because some items, such as the extra pots or the common seeds, already come as part of Flower Garden Full. But at the same time, I wanted to show them in the store and mark them as purchased. So I need to have a parallel path for those items that doesn’t go through the App Store. More on that in a future post.

The other interesting bit is the #ifdef. This one really sucks, so get ready: The simulator can’t make any StoreKit calls. It fails with a nice message box explaining why, but it fails nonetheless. To put it in highly technical terms: That blows! Seriously, that means you’re stuck developing on the device. Turnaround times are about 20-30 seconds, and debugging on the device directly is no fun at all. I’m convinced that alone was part of the reason it took me longer than my initial estimate. So that #ifdef was my attempt to try to get as much done on the simulator as possible. At least I was able to display the products on the flower shop. The actual buying had to be done on the device though. No way around it as far as I know. Why, oh, why, doesn’t Apple let us add a test account on the simulator? Maybe a feature for SDK 4.0?

Displaying the products

fg_flowershop3

Some time after sending the request for info on the products, and assuming we have a valid internet connection and that the App Store is in a good mood (to their credit, Apple’s servers have been very reliable), we’ll get a response. The response will include more information for each of the products we requested, including localized name, description, and price. This is all information taken from the in-app purchases you created in iTunes Connect. And yes, that means that you need to keep multiple sets of data in sync: Your shop catalog and the in-app purchases in iTunes Connect.

Notice that because everything we’ve done so far has been asynchronous (and asynchronous is the name of the game for a lot of the remaining steps as well) it introduces a lot of complexity. What do we do during that time? Do we let the user wander around to other parts of the program? Do we block and make the UI modal? I went with the latter approach for simplicity both coding and in the UI for the user.

At this point you can display them in your shop. In my case I ended up using a very similar interface to the App Store on the iPhone. I figured users are already familiar with that kind of interface, so might as well build on top of that. So that meant the flower shop is a table showing all available products. Clicking on a cell brings up a view with details and a description of each product.

To make my life easier, the only elements that are hardwired on the product page are the icon, the title, and the price/purchase button. The actual description and screenshots is just an embedded web view and part of the product description in the catalog has the correct URL for each project. You can even access some of them directly here.

So far all this has done for us is let us populate a list of products that are available to be sold. Next entry will cover the details of the actual transaction and a few things to watch out for.

Two Day iPhone OpenGL Class Coming to The Bay Area

carsAfter the success of the OpenGL class in Denver, we’re bringing the iPhone OpenGL class to the Bay Area. It will be held November 19th and 20th in Cupertino, right next to Apple’s headquarters at Infinite Loop.

The class is aimed at iPhone developers without previous OpenGL or 3D graphics experience. As part of the class, we’ll create both 2D and 3D OpenGL applications, and we’ll cover a broad range of topics, start with the basics of setting up OpenGL and rendering triangles on screen, to multitexturing and point sprites in the last day. This is definitely a hands-on class, so you’ll need to bring your laptop and be ready to do some coding.

Check out the Mobile Orchard page for discounts and registration details. Feel free to contact me if you have any questions.

Hope to see you there!