How to Create a Unity iPhone Trailer Video on a Budget

Making a promotional video, trailer, or dev screencast of a game that’s in development can be very tricky. I used a pretty budget-conscious method to create small promo vids for Beast Boxing, and you can too! On the Unity forums, it seems like everyone mentions their screencast setup of choice, but I haven’t seen complete end-to-end instructions yet. Here’s the way that I do it.

Step 1. Set up your Unity editor environment.

Before you start, you need to decide on the screen resolution you want to capture. I’ve heard that some development houses capture at high res then downscale, or even capture at 50% game time so that they can speed it up later and make games look like they perform better! Since there are multiple resolutions now for iDevices, it does make more sense to capture at say, Retina or iPad resolution for videos, but that might require some additional work to make your game resolution-independent, and a heavier-duty machine that can handle playback and recording at a high res too.

Anyway, once you decide on your capture resolution, make sure you click “Maximize on Play” in the Unity Editor. This will help in a couple of ways – first, if you have other editor windows with the scene open, it takes some resource load off of your computer so that it can focus on delivering the fastest framerate. Having to duplicate animations and keep the inspector up to date does slow it down the editor a bit. Second, it also provides a consistent placement for the game scene so that you can have your capture area set up perfectly when you’re ready for it.

If you have a second monitor set up, I’d disconnect it and reboot, and also make sure that background processes like Time Machine aren’t running at the time.

Step 2. Set up your recording / vidcap software.

Everyone’s got their own preference for vidcap software, but I like to use iShowU HD. On my Macbook Pro, other software packages like Camtasia (which I love on PC) or Screenflow just weren’t able to keep a high framerate going, were tricky to get a capture area set up for, or I couldn’t get them to produce output video at my desired resolution. iShowU HD is pretty affordable, too, and has a free demo. In any case, I’d recommend that you try all of the demos that are available to see how they run on your setup, and pick the one that works best for you. These instructions are specific to iShowU HD, but the general idea is the same thing regardless of your software.

First, set your output video size. Since Youtube has a default display height of 360 pixels, I set my capture area to 480 x 360. This provides a 4:3 aspect ratio and a 1:1 mapping with letterboxing on the sides, so it turns out well in the end. Hit the Gear button next to Output video, and select Change Size > Set Size…, and set it to 480 x 360. Hit the Gear again, and select Change Format > Apple Animation. This is a very high quality capture, so you can get a nice vidcap that isn’t blurry and also works with iMovie. Hit the Gear one last time and select Change Frame Rate > 30. 30 fps is what Youtube does, and is all my MBP can handle, so there’s not much point going higher, but feel free to experiment. Make sure that you uncheck “Record sound from input device”, unless you like having your own breathing in your trailers.

iShowU Settings

Over Capture Area, high the Gear, then hit Same as Video Size… this will set up a 1:1 capture mapping and give you the best quality video. Press the Play button in the Unity editor, then go back to iShowU and click Choose over capture area. Drag the rectangle over your video area, and if you want, center it vertically as we’ll see horizontal letterboxing added later in iMovie.

Now, for videos that I’ve capped with iShowU, I used to have problems with the color getting really washed out. The solution I found on their forums is to use the experimental color correction. Go to iShowU’s Preferences, then General, then click “Color correction (experimental).” It did wonders for me, and it might work for you!

Video washed out? Try "Color correction" in iShowU Preferences

Step 3. Record videos.

Fire up your Unity iPhone remote if you need it, then hit the Play button in Unity and press the Record button in iShowU. Record to your hearts’ content, and hit stop when you’re done. Repeat this process as necessary, and stay tuned for my next post if you need tips on what to record!

When you’re done, it’s time to bring your videos into iMovie.

Step 4. Import your videos to iMovie.

I assume you know how to use your own version of iMovie. I just completed the Beast Boxing Trailer with iMovie ’11, so I’ll just speak about the general settings to choose and revisit iMovie specifics another time.

Import the vidcaps into your Event Library, and start working with them in your Project. When you’re satisfied with your trailer, there are many options for sharing. Publishing directly to Youtube with the “Medium” setting resizes and expands your content to 640×480, and ends up adding horizontal letterboxing, and this is what I do. It makes the natural 360px high content look a little better, and users that pick the 480px high HD setting will get an even nicer display.

If you’d rather export directly to Quicktime, choose Share > Export to Quicktime, and pick Options to open the Movie Settings panel. I use H.264 compression, Key Frames Every 24 frames, Data rate of “Restrict to 6000 kbits/sec”, Optimized for CD/DVD-ROM, and a High quality on the Compressor with Encoding: Best Quality. In the Size panel, choose NTSC 720 x 480 4:3, and check Preserve aspect ratio using Letterbox (if required). If you decide to upload this to Youtube, it’ll look pretty much the same as the direct Share to Youtube option, so it’s up to you.

iMovie Quicktime Movie Settings

I had good results with this process, and I hope it’s helpful to you to promote your iPhone games as well!

Beast Boxing – Bush League Character Bios

Bush League. It’s where all Beast Boxers have to prove themselves before moving up to the Pro Leagues, and it’s always a trial by fire. Getting your bearings can be tough, so let’s give you an advantage by scouting your opposition!



Piglas, owner of Piglas’ Gym

Piglas is a washed-up ex-pro boxer who runs a the boxing gym in the slums. He’s got a reputation for being cynical but kind. Rumor has it that he was once the Pro League champ, but suffered devastating injuries when he fought in the Ultra League and hasn’t been the same since. He now coaches aspiring fighters in the slums, but doesn’t have much hope for finding young talent in a place like that…



Polyp, the Nerdy Snake

Polyp is a rising up-and-comer in the Beast Boxing Bush League. He comes from a wealthy family, and can afford the training fees at the most famous boxing school of all, Kamander’s Dojo Express. Unfortunately, that doesn’t translate to much talent. Still, he’s managed to hold his own while his skills improve, and his endless self-promotion on the Internet has earned him lots of fans.



Steve, the Fitness Skeleton

Steve, the Bush League Champion, seems to have kept his thin figure for longer than most beasts can remember. His dedication to fitness is bar none, and it gives him the ability to rise up from near defeat at times to deliver knockout blows. Unfortunately, his flamboyance can be a liability when he faces more focused boxers, as he tends to telegraph his flashier moves far in advance.



Your Pretty Convincing Beast Costume

And then there’s you. Humans in Beasthalla have never really had their place in society. You’ve been considered a weakling your whole life, and had to fight daily in the slums to survive. Your heroes are the amazing Beast Boxing champions that do the same, and your nightly dreams are always crushed when you wake up as a human in a beastly world. That is, until you somehow managed to find this sturdy beast costume. This might be your only chance at a better life… it’s time to see what you’re made of!



Here we go!

Pretty looking thumbnails in iOS 3.2 with CoreGraphics

As Beast Boxing 3D wraps up, I’ve been brushing up my iOS SDK chops by working through the Big Nerd Ranch Guide to iPhone Programming, a great book that takes a fundamentals-based approach to learning how to operate well within Apple’s SDKs. In Chapter 16, there’s a cool challenge where they ask you to take a relatively simple method of converting an image to a thumbnail, and make it prettier. I spent some time researching it and ended up with a method I’m pretty satisfied with that uses CoreGraphics clipping paths and gradients to do the job. As I’m working on iPad apps next, iOS 3.2 is my target, so I take advantage of UIBezierPath, an iOS 3.2 addition, in this sample. Without further ado, here’s the code. Enjoy!


- (void)setThumbnailDataFromImage:(UIImage *)image
{
// Release old data
[thumbnailData release];
[thumbnail release];

// Create an empty image of size 70 x 70
CGRect imageRect = CGRectMake(0, 0, 70, 70);
// Create an offscreen graphics context to draw into
UIGraphicsBeginImageContext(imageRect.size);

CGContextRef c = UIGraphicsGetCurrentContext();
UIBezierPath *bp = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.0];
CGPathRef path = [bp CGPath];
CGContextAddPath(c, path);
CGContextClip(c);
// Render the big image onto the image context
[image drawInRect:imageRect];

// Render a glossy gradient on top!
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorSpace;
size_t num_locations = 2;
CGFloat locations[2] = {
0.0, 1.0
};
CGFloat components[8] = {
1.0, 1.0, 1.0, 0.85, // Start color
1.0, 1.0, 1.0, 0.05 // End color
};
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorSpace, components, locations, num_locations);

CGPoint topCenter = CGPointMake(CGRectGetMidX(imageRect), 0.0);
CGPoint topQuarterBoundary = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height * 0.35);

CGContextDrawLinearGradient(c, glossGradient, topCenter, topQuarterBoundary, 0);

CGGradientRelease(glossGradient);

// Render a shadow gradient down low
CGGradientRef shadowGradient;

CGFloat shadowLocations[2] = {
0.0, 1.0
};
CGFloat shadowComponents[8] = {
0.0, 0.0, 0.0, 0.65, // Start color
0.0, 0.0, 0.0, 0.00 // End color
};
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
shadowGradient = CGGradientCreateWithColorComponents(rgbColorSpace, shadowComponents, shadowLocations, num_locations);

CGPoint bottomCenter = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height);
CGPoint bottomQuarterBoundary = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height * 0.85);

CGContextDrawLinearGradient(c, shadowGradient, bottomCenter, bottomQuarterBoundary, 0);

CGGradientRelease(shadowGradient);
CGColorSpaceRelease(rgbColorSpace);

// Make a new one from the image context
thumbnail = UIGraphicsGetImageFromCurrentImageContext();

// Retain the new one
[thumbnail retain];

// Clean up image context resources
UIGraphicsEndImageContext();

// Make a new data object from the image
thumbnailData = UIImageJPEGRepresentation(thumbnail, 0.5);

// You may get malloc warnings on the simulator from this line,
// it is a bug in the simulator

// Retain it
[thumbnailData retain];

}

Beast Boxing has a story!?!

Yep, it’s true! Even though Beast Boxing 3D is chock-full of arcade-reminiscent touch action, we’ve got a story mode in the game that lets you play through three daunting boxing leagues of opponents! You start out as a poor boxer from the slums who spent their last coins to enter the Amateur Leagues, so you’ve got nothing to lose as you try to make your way to champion status.

It’s still a work in progress, but you can take a look at an example dialogue screen of the character Treimann talking some trash:

So yeah, you’re gonna get trash talked by a tree.

In the Kingdom of Beasthalla, the strongest monsters fight in grand boxing leagues to prove their strength. This is called….

BEAST BOXING!

Kamander! The Champ of the Pro Leagues

The character designs we’re working on are probably the best part of Beast Boxing! This is Kamander, a cyclops snake with a bad attitude and a strong Internet fan base in the fantasy land of Beasthalla. Here’s a quick quote from my story document to feast on:

Kamander rose through the ranks of beast boxers, defeating all comers in the Pro League even though suffers from no depth perception. His strong defense and devastating hook combos have very few weaknesses, and his fan club is rabid and devoted.

Kamander was a really fun character to set up. He’s the second time in the game you encounter a snake-style character, and he’s set up to be very quick and powerful, requiring a good base of upgrades to take him on successfully. Since the last time I intro’ed a character, some people wanted to see more gameplay, I’m happy to show off this new video highlighting a full, if lopsided, round fighting Kamander.

This promo shows off some of the polish we’ve been adding into the game over the last month and a half since the last video. The most fun addition was definitely adding in a powerup that activates when you land 5 consecutive hits – you’re on fire! We’ve been psyched about the new NBA Jam trailers coming out and it reminded us that our game feels a lot like the NBA Jam of iPhone boxing games!

We certainly know and admire about all the ultra-realistic, simulation-oriented fighting games that have come out, but we’re more interested in providing a quick-paced, super fun and awesome-looking fighting game instead. Hope you enjoy the video, and sign up for our mailing list on http://www.beastboxing.com to get notified when it’s ready!

Beast Boxing 3D, Coming Soon!

Announcing the new promo site at http://www.beastboxing.com with a few goodies!

We’re in production mode on the prototype formerly known as Monster Boxing, and it’s shaping up to be a really fun game! As we get through more and more polish, we’ll be posting all sorts of stuff to the new promo site, and talking about our development process.

The first character up for this treatment is Piglas Porkopoulous. In Beast Boxing, he’s your first opponent and also doubles as your training coach once you beat him. If you visit the promo site, you can check out a short gameplay teaser with him, sign up for updates about the game, and see a couple new screenshots of the game in its work in progress state.

As you can see in the video, the game offers easy to learn and straightforward boxing controls with quick jabs, heavy hooks and uppercuts, as well as dodging left and right across the stage and blocking, all implemented with the iPhone’s multitouch and accelerometer controls. We’re seeing solid 30+ fps on the iPhone 3GS right now, and expect it to get better as we get closer to launch and do more optimization work. There’s also a unique mechanic of picking up coins that the crowd throws at you when you’re fighting with gusto. Pick as many of them up as you can, and spend them later on training and special powers!

One way to fetch a random row from a CoreData entity

I spent a little time looking around for a way to get a random row out of a CoreData entity, and I didn’t find too much, so I’ll post about it here. In my entity (let’s call it “Items”), there are 198 objects stored. I have a non-alphabetical Decimal “siteOrder” attribute that helps me sort by a canonical order dependent on its original source. Because I have this decimal attribute already, I can take advantage of it by using arc4random() (don’t forget to #import stdlib.h for it) to just pick out an appropriate random index, and then set that in an NSPredicate like this:


NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Assumes that you know the number of objects per entity, and that your order starts at zero.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"siteOrder = %d", arc4random() % kNumItems];
[request setPredicate:predicate];
[request setFetchLimit:1];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];

Easy!

UIScrollView and Memory Allocations

I ran into a curious problem while using nib-based UIView hierarchies as part of a horizontally scrolling UIScrollView. I followed the Apple PageControl example of progressively loading the next and last page’s viewcontrollers and adding them as subviews, and I also took the step of progressively unloading the viewcontrollers (see below) outside those bounds. Still, every time I was paging back and forth, my real memory allocations would increase by a few MB for every page!

This became a problem pretty quickly, as you can imagine. I ended up finding a solution when I saw this post about the reverse issue.

It turns out that if you assign a viewcontroller’s view as a subview of a UIScrollView, you must both send a removeFromSuperview message to the subview, and also replace the viewcontroller’s spot in the NSArray container in order to get it released.


- (void)unloadPage:(int)page {
if (page < 0) return; if (page >= kNumItems) return;
if ((NSNull *)[viewControllers objectAtIndex:page] != [NSNull null]) {
ItemViewController *controllerToDelete =
[viewControllers objectAtIndex:page];
// Release the view from the scrollview view
[controllerToDelete.view removeFromSuperview];
// release the viewcontroller from the collection
[viewControllers replaceObjectAtIndex:page
withObject:[NSNull null]];
}
}

This ended up making my memory allocations steady, but it didn’t totally get rid of the hitching that was present. I believe that because my UIViews held in the UIScrollView load images from the Documents folder, I/O was getting triggered every time a pagination boundary was crossed, which in the PageControl example is unfortunately right in the middle of the transition. However, a smarter way to do the loading is to make most loads occur within the scrollViewDidEndDecelerating: event handler, like so:

- (void) loadSurroundingPagesForPage:(int)page {

[self unloadPage:page + kSurroundingPagesToLoad + 1];
[self unloadPage:page - kSurroundingPagesToLoad - 1];

for (int i=1; i < kSurroundingPagesToLoad; i++) { [self loadPage:page + i]; [self loadPage:page - i]; } [self loadPage:page]; lastPageLoaded = page; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { pageControlUsed = NO; [self loadSurroundingPagesForPage:pageControl.currentPage]; }

That way, in normal use, a user flipping slowly through the scrolled items won't notice the hitch because the subview will be still while it happens! Unfortunately, this means that a user who is continuously scrolling might scroll completely past the loaded content, leading to a "blank" - so, if you're willing to take a hitch, you can force a load in those conditions.


- (void)scrollViewDidScroll:(UIScrollView *)sender {
if (pageControlUsed) {
return;
}

CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth/2) / pageWidth) + 1;
pageControl.currentPage = page;
// Change of tactics - do lazy load in between pages IFF the new page requested is near our bounds
if (abs(lastPageLoaded - page) >= kSurroundingPagesToLoad-1) {
NSLog(@"Reached bounds without decelerating - must do a lazy load");
[self loadSurroundingPagesForPage:pageControl.currentPage];
}

}

This last bit takes the hitch in the middle of the page transition if we run out of our buffer zone of UIScrollView subviews. A couple possible optimizations come to mind – well, first, you could up the value of the kSurroundingPagesToLoad constant. Also, you could take account of the direction of the scrolling, and load more pages in the forward scroll direction than in the reverse. It would sort of be like a dual-clutch transmission at that point, only unable to cope when shifting unexpectedly or too many times in a row.

Anyway, this approach seems to work well for my project, and it uses the fetchedresultcontroller as well to cache and lazily load the coredata objects which populate the child views, so all of the scrollview stuff only takes up 4-5 MB of data while running. Pretty nice if you ask me!

I hope that helps anyone who was in the same boat as me. As always, use any of the code you find here at your own risk! Good luck! :)