Wow, time flies when you’re busy. As seen on the OML site, we’ve announced we’re working on our next game. With that out of the bag, I’ll probably be blogging about the progress of it, obstacles I run into, and things that are helping us along with the development that might prove useful to other iOS devs. With that, the first major issue we’ve come across is trying to develop 2D assets for a game that is planned to run in 3 different resolutions.
The first step to working around this problem is having your art assets in vector form. Adam uses Illustrator and Flash to do pretty much all of our assets. This is great because we don’t lose any fidelity in assets when resizing them. The alternative for those that are doing raster-only would be to to develop assets at the highest res and shrink them.
When working with a ton of assets, a major pain point came up when we wanted to export a set of images for the different devices (iphone, iphone retina, ipad). The process was very manual:
- Create an action to resize our asset to the needed size. Save it.
- re-use the action on each individual asset to get the 3 different sizes.
- rinse-repeat anytime an asset has changed
The above process worked for us more or less for Tilt to Live because the assets were less numerous and not much changed. But it doesn’t scale. Any programmer will see that this should be automated. But then the problem is audience, as the user of this script/automation will most likely be the artist….who happens to be on windows. So what to do?
Are You Using Smart Objects?
During GDC we met up with the awesome Retro Dreamer guys, Gavin Bowman and Craig Sharpe, and had a discussion on asset generation while riding back to the airport. The idea of Adobe CS’s new-ish “smart object" feature came up in conversation, and was something we looked into recently. Another thing that I discovered was the whole world of scripting in Photoshop. Not actions recordings, but full on javascript/VB/AppleScript support. So I took those two ideas and ran with it, and we came up with a pretty basic asset pipeline for getting our assets ready to be used in the game.
Using smart objects has some pretty cool benefits, which you can read about here. The biggest benefit to us was that the assets weren’t rasterized until saved as PNGs. So whether we blew our assets up or shrank them, it made no difference in quality. This tiny aspect helped a bit later in our script. The other neat benefit of smart objects is being able to link them to allow you to update all your instances with a click. We don’t use this feature ourselves, but it’s something definitely worth knowing about. John Nack has a brief post about this feature if you want to know more (as well as a mention of an obscure PS feature called ‘PS variables’, which seems it could be rather useful). So how do we use smart objects? Adam does his magic in illustrator, then pastes an illustration into a psd file in photoshop as a smart object. This becomes a ‘master’ file of sorts.
Yummy Scripting in Photoshop
Now onto the scripting part. We could have used actions, but it didn’t really allow us much flexibility in where assets go, what they are named, all in 1 pass instead of having an action for each resolution output. I decided to use javascript, mainly because it works both on Mac and Windows, and I was a bit more comfortable using it. The script is rather straightforward in that it prompts for a directory selection, creates a ‘pngs’ subfolder in it, and then scans it for psd files. Any files it finds, it’ll then take the original psd with the smart object, save it out as ‘pngs/whatever_iPad.png’, shrink it by 60% and save as ‘pngs/whatever.png’, and finally blow it up by 200% and save it as ‘pngs/whatever@2x.png’. Nothing too mind blowing here. You can find the script in full at the bottom of this post if you wish to try it yourself.
So why not use something like ImageMagick since it’s a simple re-size? A few reasons:
- It excels as a command line tool for the most part. command line sucks on windows, and if your artist isn’t comfortable using one, it’ll just be another pain point.
- Scripts that are native to photoshop show up under File->Scripts. Click it, and it runs. No prior setup. No ‘cd’ing into a current directory or typing in parameters, or trying to code up a custom UI. Very quick to knock together scripts.
- The biggest reason is the power these scripts grant you. You have access to a lot of internal data about the current file you’re working with in a very manageable way. We’ve brainstormed some ways to help automate animation exports from any hand-drawn animations Adam has done by organizing the layers into groups and having the script run through the groups to get the necessary frames out (again, in all 3 resolutions).
So now with the click of a button and a folder selection I can generate the different asset sizes I need for the game. Still a bit tedious if you have to do it for each folder, yes? It’s trivial to add recursive folder searching, and is something I actually did. Now I can simply click our root project folder and regenerate all of the game’s asset in one go, without human error.
Just to show how simple it is to do resizing here is a snippet of the ‘meat’ of the script. ‘saveImage’ is just a 5 line function that outputs the image to a PNG:
var fileList = GetFiles(docFolder,myPSDFilter);
for(var i = 0; i < fileList.length; ++i) {
var file = fileList[i];
var docRef = open(file);
var docName = docRef.name;
var oldWidth = docRef.width;
var oldHeight = docRef.height;
var fileNameNoExt = docName.substr(0, docName.lastIndexOf('.')) || docName;
// save out ipad 1x size
saveImage(pngFolder, fileNameNoExt+"_iPad.png");
// save out 60% size for iphone
var iphoneW = Math.ceil(oldWidth _ .6);
var iphoneH = Math.ceil(oldHeight _ .6);
docRef.resizeImage(iphoneW,iphoneH,72);
saveImage(pngFolder,fileNameNoExt +".png");
// save out double that size
var retinaW = iphoneW _ 2;
var retinaH = iphoneH _ 2;
docRef.resizeImage(retinaW,retinaH,72);
saveImage(pngFolder,fileNameNoExt+"@2x.png");
docRef.close(SaveOptions.DONOTSAVECHANGES);
}
Now I had made a point earlier about how using smart objects gives us more flexibility in the script, and the above script shows why. It was more straightforward to go from ipad to iphone (an arbitrary percentage) and then double whatever the iphone size is for retina, instead of calculating each separately. You wouldn’t be able to do that with rasterized images. Another nuance in there is the use of Math.ceil(). When shrinking by a fraction you can end up with non-whole number dimensions, and photoshop truncates the number. This can lead to weird retina resolutions that don’t have even-numbered dimensions (one pixel off).
No Vector Graphics? No Problem!
Now even those people that are using raster graphics aren’t completely left out in the woods either if they want to shrink then grow their assets. For our animation script I had to deal with raster-only graphics but I wanted to keep the same “shrink to iphone then double" procedure. The problem would be that going from iphone to retina would cause blurry graphics. Not so, if you look into using historyStates in your scripts! Here’s the same basic procedure as above except used in raster-only graphics:
var docRef = open(file);
var docName = docRef.name;
var oldWidth = docRef.width;
var oldHeight = docRef.height;
var origState = docRef.activeHistoryState;
// save out ipad 1x size
var ipadW = Math.ceil(oldWidth _ .5);
var ipadH = Math.ceil(oldHeight _ .5);
docRef.resizeImage(ipadW,ipadH,72);
saveImage(ipadFolder, docName);
// save out 60% size
var iphoneW = Math.ceil(ipadW _ .6);
var iphoneH = Math.ceil(ipadH _ .6);
docRef.resizeImage(iphoneW,iphoneH,72);
saveImage(iphoneFolder,docName);
docRef.activeHistoryState = origState;
// save out double that size
var retinaW = iphoneW _ 2;
var retinaH = iphoneH _ 2;
docRef.resizeImage(retinaW,retinaH,72);
saveImage(retinaFolder,docName);
docRef.close(SaveOptions.DONOTSAVECHANGES);
Notice in the last resizing batch I set the docRef’s activeHistory state to the original State. This is effectively automating an ‘undo’. I reset the graphic to be the super high-res ipad2X size and then shrink down from there again. No blurry graphics! Hurray!
I’m planning on looking into scripting possibilities in flash as well, as automating our exporting for that would be a huge help there too. But for the time being those are exported as a png sequence, and then another photoshop script goes in and organizes the 3 different sizes in 3 different sub-folders. Nifty, eh?
The last step in this pipeline is creating texture atlases, and is something I’ll be automating as well in a couple weeks time possibly using Texture Packer’s command line interface.
Here is a download link to the script in full in case you want to try it out, change it, tailor it to your needs, go wild: