App Prices and In-App Purchases

For several months we’ve been trying to decide on how to sell Letter Quest, what to charge for it, etc. There are tons of articles out there singing praises about in-app purchases, others doing the same for not using in-app purchases, some that say your game has to be free, etc. It’s gotten to the point that it’s tough to decide on what would work best for any particular game. A small sample of some of the articles we’ve been reading: The Value of In-App Purchases Freemium is Not Shareware 2.0 Punch Quest’s Monetization Problem For Letter Quest, we’re leaning towards making the game free, and having some in-app purchases. Given that we’re a fairly new studio, and that Letter Quest is our first game, we feel that charging any money for it up front just won’t get very many people on iOS willing to take a chance on it. We’ve done our research, seen sales figures from other indies, talked to several other developers, etc. and are thinking that this may be the best route for us to take. The trick to going with in-app purchases is that it means we need to do some redesigning for the eventual PC/Mac release, since we definitely won’t have in-app purchases in that version of the game. We’d rather just charge an up-front cost and balance the game accordingly. Here are the options we’re considering for iOS: Purchasable in-game currency, and a currency-doubler Only allow up to a certain level to be playable, then be presented with an in-app purchase prompt to unlock all additional content forever for a set cost (ie: a paywall) Make the entire game free with the exception of some alternate costumes/customization features Purchasable in-game currency, and a currency-doubler – and if the player purchases anything (currency doubler, some currency, etc.) they unlock all additional game areas and modes for free, even in future updates. In this option, only area 1 would be accessible without purchasing anything – to be fair, that’s a large chunk of content, around 3-8 hours depending on player skill level Make the game a paid app ($1-3?) and remove all in-app purchases, or possibly leave in the purchasable currency for those that want it (but this often upsets players since they already paid for the game) We’re definitely open to suggestions, feel free to add a comment below. This is a really tricky thing to get right, and it seems like the entire games industry is still struggling to figure out what works best. At the end of the day, all we really want is for people to be able to enjoy our...

read more

Saving Starling Screenshots

Today’s post is (other than a nice example of alliteration) just a quick couple of code snippets to help with saving out screenshots when using Starling, instead of having to print-screen, paste into a paint program, crop to the actual game screen content, etc. As it turns out, the author of Starling, Daniel, did most of the required work already – he’s included a method drawToBitmapData in the Starling Stage class. So that easily gets us the BitmapData for the current screen, but we also need a way to save this out, preferably as a png or jpeg to save on disk space. As a convenience, I chose to name the image files using the current date and time. For handling PNG and JPG encoding, I used a couple of classes from the excellent as3corelib. Here’s how simple it is: public function savePng(showDebugInfo:Boolean = false):void { var starlingStatsWereShowing:Boolean; var versionTextWasShowing:Boolean; if (!showDebugInfo) // don't want debug info in screenshots, so toggle it off { starlingStatsWereShowing = Starling.current.showStats; Starling.current.showStats = false; versionTextWasShowing = Globals.Game.showVersionText; Globals.Game.showVersionText = false; } var png:ByteArray = PNGEncoder.encode(Starling.current.stage.drawToBitmapData()); if (!showDebugInfo) // restore visibility of debug info { Starling.current.showStats = starlingStatsWereShowing; Globals.Game.showVersionText = versionTextWasShowing; } saveImageFile(png, "png"); } private var mJpgEncoder:JPGEncoder; public function saveJpg(showDebugInfo:Boolean = false):void { if (!mJpgEncoder) { mJpgEncoder = new JPGEncoder(90); } var starlingStatsWereShowing:Boolean; var versionTextWasShowing:Boolean; if (!showDebugInfo) // don't want debug info in screenshots, so toggle it off { starlingStatsWereShowing = Starling.current.showStats; Starling.current.showStats = false; versionTextWasShowing = Globals.Game.showVersionText; Globals.Game.showVersionText = false; } var jpg:ByteArray = mJpgEncoder.encode(Starling.current.stage.drawToBitmapData()); if (!showDebugInfo) // restore visibility of debug info { Starling.current.showStats = starlingStatsWereShowing; Globals.Game.showVersionText = versionTextWasShowing; } saveImageFile(jpg, "jpg"); } // saves an image file to either the "\png" or "\jpg" subdirectory, using the current date and time as YYYYMMDD_HHMMSS as the filename. private function saveImageFile(image:ByteArray, imageExt:String):void { var fileName:String = Constants.SCREENSHOT_DIRECTORY + imageExt + "\\" + Utils.getIsoFormattedDateImageFilename() + "." + imageExt; trace("Saving " + fileName + "......."); var file:File = new File(); file.nativePath = fileName; var fileStream:FileStream = new FileStream(); fileStream.open(file, FileMode.WRITE); fileStream.writeBytes(image); fileStream.close(); } As you can see, there’s a parameter for savePng and saveJpg called showDebugInfo – if that is false, the Starling stats and my own custom on-screen game version TextFields are disabled for the screenshot, then toggled back to their previous visibility state after the screenshot is taken. I bound savePng and saveJpg with no debug info to F1 and F2, and savePng and saveJpg with debug info to F5 and F6. Now I can take screenshots at any time by simply pressing a key. A cool thing to consider doing is taking a screenshot automatically whenever the game is closed during development, since this will usually take a shot of whatever feature was being tested – I got the idea from this interview with the author of “The Dungeoning”. Here’s a couple of sample screens captured using the above code: Update 2014/07/14: The nice folks over at DiaDraw have created a slick ANE that allows you to record video from your Starling and Away3D apps – be sure to take a look if that’s something that is of use to...

read more

The Value of a Demo

As we’re getting close to releasing our first game, Letter Quest, I figure it’s a good time to talk about the value of a demo and getting feedback from complete strangers. About a month ago we decided that we should make a demo version of Letter Quest that people could try in a web browser. Because we built the game using Flash and Starling, it was trivial to get a browser version working. A few things needed to be tweaked to make more sense with a mouse (instead of the usual touch controls), but that didn’t take long. I ended up adding a flag to the game called DEMO_BUILD, and if it was set to true, a whole bunch of things in the game are automatically changed and configured for doing demo builds. This includes the following changes: limiting the game to just the first 7 stages in the first area, removing the area/mode select screen, removing the ability to purchase cash items (we’re still deciding on how to monetize the game), etc. Now whenever we want to do a demo build, it’s as easy as setting that flag and uploading the build to our web server. Before releasing a public demo, we had six excellent testers. One of them has played the game for probably close to 100 hours at this point, and she’s still not tired of the game, which is a great sign! The trouble with having a limited set of testers, though, is that after a while they get to be way too good at the game. I’m also very good at the game now too because I’ve played it so much during development. So of course the first handful of people that played the public demo came back with feedback of “nice game but way too hard”, “why is the first enemy so hard?”, “Y so difficult?”, etc. I played the game again and still don’t find it hard. I did some reading about game balancing and found that making your game too difficult/making it feel “just right” for yourself is a common problem that developers face when making games. So I adjusted the difficulty of the game to the point where my testers and I found it to be quite easy, and lacking any real challenge. It’s still fun, but not difficult for us. Then I updated the demo and got more people to play it. Everyone suddenly thought the difficulty was very good, with comments like “challenging but fair”, “very well-balanced”, “cool game and nice difficulty progression”, etc. Lesson learned: make your game a little too easy for yourself, and it’s likely just about right for new players. I’ve also been really impressed with the feedback I’ve gotten from players on a couple of forums. A few people commented in the threads I posted, but many people ended up private messaging or emailing me with feedback, which I really appreciated. I’ve been using this feedback to quickly release new demos with fixes/changes, and players have been noticing – I’ve received several emails from happy players that are impressed that their feedback was useful/listened to, and that have said they’d love an email notifying them when the game is released on iOS. Seems like a pretty good way to start slowly building a great fan-base. Here’s the forums I’ve posted the demo on so far – I try to post on one new forum with each new revision of the demo, to avoid getting the same feedback from multiple sources: Starling forum Tig-source forum I’ve been using the excellent Flox analytics to track...

read more

ActionScript Error Handler

When developing a game, it’s super helpful to have as much info as possible when it comes to errors. I wanted error handling that would let me know that a player had encountered an error, what the error was, provide a stack trace if possible, and to let the user know that an error had occurred. Using this system I’ve managed to find and fix many bugs, and it’s made error reporting so much easier. Before this I had to rely on testers telling me what they had done, where the error occurred, etc. Here’s how I managed to get everything working: First off, I handle all logging with a couple of simple utility functions that can be called from anywhere: public static function logInfo(text:String):void { writeToLog(text); } public static function logWarning(text:String):void { writeToLog("@@@ WARNING @@@ " + text); } public static function logError(text:String):void { writeToLog("*** ERROR *** " + text); throw new Error(text); } private static function writeToLog(text:String):void { text = getIsoFormattedDate(new Date()) + " - " + text; trace(text); DebugManager.getInstance().addDebugText(text); } In my main class, I listen for all uncaught errors: loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandlerCB); The error handler: private function uncaughtErrorHandlerCB(event:UncaughtErrorEvent):void { if (event.error is Error) { var error:Error = event.error as Error; Utils.logInfo("uncaughtErrorHandlerCB - caught an Error, ID: [" + error.errorID + "], Name: [" + error.name + "], Message: [" + error.message + "]"); if (Constants.LOG_ANALYTICS) { Flox.logError(error, "GAME LOG:\n" + DebugManager.getInstance().debugLogMessages); } } else if (event.error is ErrorEvent) { var errorEvent:ErrorEvent = event.error as ErrorEvent; Utils.logInfo("uncaughtErrorHandlerCB - caught an ErrorEvent, ID: [" + errorEvent.errorID + "], Target: [" + errorEvent.target + "]"); } else { // a non-Error, non-ErrorEvent type was thrown and uncaught Utils.logInfo("uncaughtErrorHandlerCB - caught something that wasn't an Error or an ErrorEvent - what is this!?!?"); } DebugManager.getInstance().show(this.stage); } The DebugManager.getInstance().show(this.stage) has two ways to show debug info – if the Constants.BETA flag is true, then we show a minimalcomps window with the stack trace and last 50 log messages. If Constants.BETA is false, then this is a proper release build and we show a custom in-game error window instead, with a player-friendly message mentioning that the game has encountered an error, we’re sorry for the inconvenience, etc. In all cases the error is logged to Flox so we can analyze it and attempt to fix the bug.   Here’s the entire DebugManager class: package framework { // caches up to MAX_MESSAGES of the most recent debug messages public class DebugManager { private static var s_Instance:DebugManager; private static const MAX_MESSAGES:int = 50; // num messages to keep, oldest ones are discarded private var mDebugWindow:Window; private var mDebugTextArea:TextArea; private var mMessageBuffer:Array; private var mNextMessageIndex:int = 0; // treat message buffer array as a circular/ring buffer for efficiency public function DebugManager() { if (s_Instance) { throw new Error("DebugManager is a singleton, access it with DebugManager.getInstance()"); } } public static function getInstance():DebugManager { if (s_Instance == null) { s_Instance = new DebugManager; } return s_Instance; } public function initialize():void { mMessageBuffer = []; } public function addDebugText(text:String):void { mMessageBuffer[mNextMessageIndex] = text; mNextMessageIndex++; if (mNextMessageIndex >= MAX_MESSAGES) { mNextMessageIndex = 0; } } public function show(stage:Stage):void { if (Constants.BETA) // if running a beta version, and encounter an error, we show a minimalcomps window with a stack trace { if (!mDebugWindow) { // NOTE: because we're using minimalcomps and not Starling for this, need to manually scale sizes for different devices/pixel densities // create the debug window mDebugWindow = new Window(stage, 0, 0, "[Game Name Goes Here]" + Constants.GAME_VERSION); mDebugWindow.hasMinimizeButton = true; mDebugWindow.width = Constants.STAGE_WIDTH * Starling.contentScaleFactor - 20; mDebugWindow.height = Constants.STAGE_HEIGHT * Starling.contentScaleFactor - 20; mDebugWindow.alpha...

read more

Player’s Perception of Value

An interesting point was raised by a couple of our players – sometimes when they defeated a monster or hit a bag of gems at the end of a stage, they would only be rewarded with one single gem. They said that this didn’t feel very rewarding. In Letter Quest, there are five different colors of gems, each worth a different amount: As an example, imagine a monster dropping 20 gems when defeated – only a single red gem would drop. Even though that gem is worth 20, players were perceiving this as a smaller reward than seeing a bunch of green and blue gems. There are a couple of things we could change to fix this. The first option is to make each successive higher-valued gem physically larger. However this still wouldn’t solve the issue because if they only received one red gem, there wouldn’t be any green or blue gems on-screen to compare it to. The second option is the one we ended up going with, and it’s a very simple fix. Before we drop any gems for the player, we look at the number of gems. If it happens to be greater than 1, and an even multiple of one of our gem values (5, 20, 50, or 100), we simply create one single green gem, and then create the rest of our gems as usual. This gives the appearance of receiving much more gems, and players were much happier and felt like the rewards were more valuable. Example: instead of dropping 1 red gem for a 20-gem reward, we would instead drop 1 green gem, then run the rest of our algorithm, which would create 3 blue gems (each worth 5 gems), then 4 green gems (each worth 1). So the player would see a total of 8 gems on-screen (ooooh shiny!), instead of just 1 red one....

read more