The Road to Release
I submitted the first beta build on Friday evening, and Apple's review process completed by Sunday night. For the first time, I got to see the game running on devices other than my own. I expected to see some performance issues on lower-end devices, but to my surprise, the game runs smoothly on all of the devices tested so far, all the way back to the iPhone 11 (released in September 2019)!
Apple's TestFlight program allows me to generate public links that I can pass out to people so that they can download and test the app. I can see a little bit of data about each person that installed the app, including the type of device they used, and how many "sessions" they had with the app. I generated a link for my daughter, and told her I'd give her a dollar for each person that installs the app with that link. Several years ago I told her I'd match any donations she got for a school thing, and she ended up raising over $1,000 - I won't make that mistake again, so I told her there would be a cap of $100 this time.
So far, I only owe her $8. It's not the explosive growth I was hoping for, but I'm not sure what I expected. In any case, my family has been absolutely ecstatic for me - this is the farthest I've come with any of my solo endeavors since I've even known my wife!
I algo generated a link specifically for the Apple Arcade application. The confirmation message I received indicates that they won't necessarily even respond to the application unless they are interested. I can track if they actually install the game using the link, but I can't just hold up the release of the game forever. The application asks about a planned release date - my response was March 2024, which is a little more than 2 weeks away. If I don't hear from them by March, then I'll release the game on my own.
Until then, I have some planning to do.
22.1 Marketability
Obviously my audience is currently very slim, so I don't have much data to go off of yet. My daughter managed to invite 8 of her friends, and I've gotten another 8 or so of my own friends and family to give it a shot. Most people have between 1-10 sessions over the last two days, which isn't too bad. One player in particular has recorded over 50 sessions! I'm not exactly sure how Apple is aggregating that metric, but I think it proves that there is a viable audience for this game, even if it's not for everyone.
The big question is: how do I make that audience knowledgeable of the game's existence? I was overly optimistic that Apple would agree to publish the game on Apple Arcade and, in doing so, assist with marketing the game on the Apple Arcade store front. I wonder what other options Apple has for marketing on the App Store.
They apparently have an entire landing page of promotion options. On the page entitled "Getting Featured on the App Store", they specifically request 6-8 weeks of lead time for new apps. That would push my release date well into April, which would be disappointing, but could buy me more time to implement more features or content. The actual form asks some interesting questions regarding the business model and marketing plans. I'm literally trying to determine marketing plans now, which makes that question really hard to answer - does it seem to desperate if I say "this is it"?
Well, that's not entirely accurate. I have reached out to some popular Twitch streamers that focus on World of Warcraft-related content. I figure the healing-inspired gameplay would be a good fit. Unfortunately I haven't heard back from any of them either. No one knows who I am, so why would they even bother checking an email from me?
Monetization
As far as the business model goes, I was really banking on Apple Arcade as the perfect solution. No purchase price, no ads, no in-app purchases - just payout based on the number of people that actually play the game. Some of the most common feedback about the game is about the lack of ads. People are so frequently bombarded with advertisements that they have come to expect it - and it's particularly noticeable when a game doesn't have ads.
I've been surveying people about possible price points for the game, and none of them are really willing to spend any money on it. Some of them might initially answer that they would pay a dollar for it, but when probed a little further, it becomes clear that they would only pay for it because I was the one that made it - if it was some random game they came across on the App Store, they wouldn't bother. I honestly don't blame them! It's a very small game, after all. Even if I added more unlockable content, I still don't think it would be particularly worth a dollar, or even the App Store minimum of $0.29.
As I was trying to fall asleep last night, I had a very unorthodox idea: charge for the ability to compete for a spot on the leaderboard. The thought was sparked by the idea of an arcade cabinet. Arcade games were designed to be hard because every attempt cost the player money - the logical way to maximize profits was to make the players lose, but not too quickly, or else they wouldn't bother trying again. Arcade cabinets had high scores that were localized to that specific machine. Global high scores meant nothing - only a spot on the leaderboard at that specific cabinet at that specific arcade.
That concept is pretty foreign to younger audiences. They either compete within their own circle of friends, or else they are faced with the insurmountable task of ranking against all players in the entire world. Region-specific high scores are a lost art. Even World of Warcraft's player partitioning (in the form of "realms") has largely become irrelevant over the years. I guess that's just a result of the internet's effect on games - everything has been globalized.
Charging for the opportunity to place on a leaderboard would significantly reduce the amount of players on that leaderboard. The players that are serious about making a name for themselves would spend the most money, while casual players could continue to play the game for free. The obvious risk is that no one would care enough about the leaderboard to actually pay money for a chance to be on it. An even bigger risk is that the monetization model has a negative perception in the eyes of the players.
There is one obvious question staring me in the face: should this app just be free?
I decided to submit the Apple Promo form with a declared target release date in mid-April, and a "free" business model. I can change my mind later if I come up with some brilliant idea.
22.2 Minor Updates
There are a few little things I'm going to change with the game.
App Icon Weirdness
The first thing many people notice is that my app icon doesn't look quite right. I designed it in Adobe Illustrator to already have rounded corners with a transparent background. When I tried to use that icon during the initial app verification, an error message indicated to me that transparency was not allowed for app icons, so I changed the background to white. After distributing the app via TestFlight, it became clear to me that the Apple's platform is rounding the corners of the icon for me, and I wasn't supposed to round the corners of my icon at all. The result is that the app icon has a small white trim between my own rounded corners, and the corners that they rounded for me. I just updated the icon to have the same navy blue background with no rounded corners.
Title
I apparently forgot to mention that the name "Aegis" was already taken when I went to upload the app to the App Store, so I gave the game a subtitle - "Aegis: Infinite". I never actually updated the TitleScene
to reflect that change, so I'll go ahead and do that.
New Player Experience
Several people have expressed mild annoyance that they have to choose which hand to use and go through the tutorial every time they play the game. This is now resolved with a simple _isNewPlayer
flag in the SaveManager
.
I myself disliked the tutorial entirely. People tend to skip reading the text entirely, and the game doesn't actually start until they follow the instructions. I got rid of the Star Wars-style scrolling text. The first asteroids will spawn in a lane that the player is not currently in, encouraging movement. Once they finally collide with an asteroid, a new indicator will highlight the shield health bar, hopefully indicating to the player that they should tap it. After they heal the shield, the game will start normally.
"Lives"
The current iteration of the game only ever gives the player one life. I'm going to leave the code that supports multiple lives, but I'm going to remove the life indicator from the game, and move the score indicator over to the right side.
I declared the initial upload as version 0.0.1
, so this release shall be 0.0.2
.
Interestingly, this version of the game only took about 15 minutes to make it through Apple's review process! Here are all the seemingly random changes I've made, and here is the updated app icon.
22.3 The Leaderboard
I was planning on waiting for a response from Apple before deciding on which leaderboard solution to use, but it's definitely the top requested feature among players that actually seem to enjoy the game. The more I think about it, the more I convince myself that a cross-platform leaderboard isn't really a strict requirement - I could use Game Center on Apple devices for now, use Google Play Games if I ever decide to port the game to Android, and explore further options if there is ever a need to port to other platforms.
I've been considering marketing the game on sites like Reddit, focusing more on the WoW-oriented communities. Anecdotally, I've noticed that a large percentage of my WoW-playing friends tend to have Android devices. If I focus my efforts on that target audience, then I have some platform work to do, but maybe I'm getting ahead of myself. My original stated goal was to release the game on a single platform, so I'm going to push forward with that.
Ignoring Android entirely for now, I've been tinkering around with Apple's GameKit framework. It really doesn't seem too complicated, but like every new library, you kind of have to fiddle around with it to familiarize yourself with what it's capable of doing. The core of the framework is predicated on authenticating the player, which then allows you to make requests for the various Game Center features on behalf of that player, specifically for your game.
The library functions often require you to define callbacks for the responses, which makes sense, considering they are asynchronous network operations. I guess that means I'll need to take care of creating some sort of state machine-based abstraction that is relevant to how my game cares to display the information retrieved by the library.
Look and Feel
I got a general idea of how to use the GameKit framework, but before I can really start using it, I need to decide how I want to present the high scores in the game. I created a new LeaderboardScene
and spent the night iterating on the visual design of the leaderboard.
The highly pixelated font makes it really tricky to squeeze the information into the horizontal bounds of the screen. I did a little bit of research into the maximum length of nicknames on Game Center. As far as I can tell, the maximum is currently 15, though it used to be 25. I really hope it's not 25 characters, because my UI design depends on the shorter length!
Just as an extra precaution, I looked up the maximum length for "gamer names" on Google's Play Games platform - it appears to be 16 characters, which I can handle just fine, assuming high scores don't start pushing into the billions!
I generated a handful of random "names" (really just substrings of the alphabet between 3 and 15 characters), and generated some random scores with a max of 1,000,000. Here's what I came up with:
Platform Abstractions
Obviously hard-coding a bunch of random values isn't suitable for the actual leaderboard, and I'll have to retrieve the real data from somewhere. I decided to add a new engine-level platform abstraction named the ScoreProvider
, containing a single getHighScores()
method. I extracted all of the data randomization into a new MacScoreProvider
within Alfredo, since it won't have access to the Game Center or Google Play Games platforms.
Side note: my wife made linguine chicken alfredo tonight and it was delicious.
I wired up the new ScoreProvider
to all the appropriate places in the Engine
and used it to display the randomized data in the LeadboardScene
. So far so good.
Obviously, the IosScoreProvider
was a little more involved. First I had to add an authenticateHandler
to the GKLocalPlayer.localPlayer
from the GameKit framework. The handler provides a viewController
if the player needs to be redirected somewhere for further authentication, and its our responsibility to present it, if provided, using the aptly-named presentViewController:
function. Only after successfully authenticating can we make calls on behalf of the player.
The calls required to get the high scores from our "global" leaderboard include the loadLeaderboardsWithIDs:
function (on the GKLeaderboard
class), and the loadEntriesForPlayerScope:
method (on the GKLeaderboard
instance returned by the first function). As I mentioned before, these functions are oriented around callbacks, so the simplest thing for me to do is introduce my own callback mechanism that gets invoked within the callbacks of those calls.
I was required to create the logical representation of the leaderboard within App Store Connect (the developer portal for Apple's platforms). Each leaderboard is created with an ID - I assigned the ID of beta
for mine, which I'll probably switch out later. This is the ID that I passed into the loadLeaderboardsWithIDs:
.
Before the calls would work correctly, I had to add the Game Center "entitlement" to my app "identifier" in App Store Connect. Unfortunately this only applies to the official "distribution" profile for my app - the "developer" profile is automatically managed by Xcode. I can open Xcode and add the entitlement to the developer profile, but it seems to get un-done for no obvious reason. I dug around a bit, and discovered that I can provide my own entitlements file using the CODE_SIGN_ENTITLEMENTS
Xcode property, which I can set in my CMakeLists.txt
.
scampi/CMakeLists.txt snippet
set_xcode_property(scampi CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/scampi.entitlements" "All")
scampi/scampi.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.game-center</key>
<true/>
</dict>
</plist>
This all seemed to work, but obviously there are no scores recorded on my leaderboard yet, so I just kept receiving an empty response. The obvious next step is to submit scores at the end of each run. I added a new submitScore()
method to the ScoreProvider
interface, which honestly doesn't belong on a "provider" at all, but I'll fix that later. The new method requires callback parameters for success and error responses, just like the getHighScores()
method that I made before. However, the new method requires an additional score
parameter, for obvious reasons. The actual implementation of the method uses the submitScore:
function of the GKLeaderboard
class, and always provides the beta
leaderboard ID.
I called this new method from the GameOverScene
. It works like a charm! There is a slight delay between the time the score is submitted and when the new score shows up on the actual leaderboard, but that's perfectly fine. Just as a nice little touch, I decided to add a flag in the responses to the getHighScores()
method to indicate if the record was set by the current player. If it is, then I highlight the row in the leaderboard with the primary orange color.
Unauthenticated UI
There is a very real chance that the player decides not to log in with Game Center. In that case, I don't want to prevent them from playing the game - I'll just hide any indication of a leaderboard instead.
I added a new isAvailable()
method to the ScoreProvider
, which just returns the player's current authenticated state. I created a new TitleUiSystem
for use in the TitleScene
which sets the visibility of the leaderboard button based on the result of the isAvailable()
method. It also moves the options button around to close the gap left if the leaderboard button is hidden.
I moved the authenticateHandler
out of the ScoreProvider
, into the ScampiViewController
. The actual authentication logic doesn't seem relevant to the ability to retrieve and persist scores.
On iOS, when the user first opens the app, the game automatically tries to log them into Game Center, which is a pretty typical flow. If they are already logged in, the leaderboard button will appear automatically. If they happen to log out of Game Center for any reason, the UI will automatically update to hide the button. Pretty easy!
A Little Cleanup
I renamed the ScoreProvider
to LeaderboardManager
instead. Most of the engine-level systems are already called "managers", even though it doesn't really mean anything. I updated the implementations for Alfredo and Scampi appropriately, and took care to rename all the references to the interface as well.
I added a WebLeaderboardManager
abstraction for Pesto, which returns false
from its isAvailable()
method so that the leaderboard never shows up, and triggers the error callbacks within the other methods.
I did a little more cleanup in the various scenes in order to make the overall look and feel of the app more consistent. I updated the version number in Scampi's CMakeLists.txt
to 0.0.3
, and finally I uploaded a new version of the app to TestFlight. Unfortunately, this version was not reviewed within the short 30-minute window that 0.0.2
was lucky enough to receive - I think it has to do with the fact that it's currently the weekend, while the previous build was uploaded during the week.
Here are all the changes I've made for the leaderboard. It doesn't really include error handling or a clear loading state, but it works!
Do you remember the DESIGN.md
that I made a big fuss about? Let's open it up and see how far we've diverged over the last several months.
HAH!
## Overview
Healing-focused game where players must overcome uniquely challenging encounters using a party of up to 5 "wisps", each with their own attributes and characteristics.
Not exactly, but not too far removed either.
I don't even remember that, but clearly we didn't go in that direction.
The gameplay section is entirely wrong, and I'm not even sure how I veered so far away from the direction I thought I was heading. I guess I'm not really mad, since I find the result to be very fun.
This was entertaining. I'm deleting this dumb file.
Woops
Version 0.0.3
was finally approved, after a couple of days, only to find out that the leaderboard feature wasn't working with the "distribution" build. I had incorrectly assumed that I only needed to manually specific the Game Center entitlement for my "developer" builds, but apparently I need to specify it for all builds.
One of the friends that reported the problem mentioned that he was seeing an "unauthenticated" error after pressing the "options" button - that was very weird, considering the error should only show up in the LeaderboardScene
! Then it dawned on me that the ButtonSystem
isn't actually disabling the Selectable
Renderable
for the "leaderboard" button, and it happens to get rendered to the offscreen framebuffer on top of the "options" Renderable
. The result is that the user presses the options button and gets sent to the LeaderboardScene
instead of the OptionsScene
.
Both of those problems are fixed in the 0.0.4
build. This time I made sure to test the "distribution" build on TestFlight before submitting the update for external use. Any user that is registered on my company's developer account can bypass the review process, but I am currently the only member of my company. In any case, here are the changes I made. Now I wait... again.
More First-Frame Issues
I happened to catch a glimpse of some more issues with the HUD elements during the first frame of the InfiniteRunnerScene
. There's nothing really complicated happening here, I just needed to rearrange the system order. Since I've already submitted 0.0.4
for review, I'll just let this slip in with whatever the next update happens to be.
22.4 Still Waiting
It's been a little over a week since I submitted the application for a spot on Apple Arcade, and exactly a week since I submitted the application for a promo spot on the App Store. I know I should be patient, but I can't help feeling like it's a waste of time. It's entirely possible that neither application will receive any response, and I will have accomplished nothing if I don't continue working on something.
There is obviously a large potential market using Android devices instead of iOS. Maybe we should take a closer look in that direction...