This post is meant as a general showcase of the challenges and problems I’ve faced while creating a convention game in about 2 months (give or take, not considering time spent with the writing, game design, art etc…), what I learned from all of this, and how I achieved it, which is also going to serve as a follow-up to a post I made a while ago where I showcased a small Command Pattern implementation I made for Godot.
I’ve wanted to make this for a while now but, unfortunately, because of an NDA, I was unable to show off anything about it at all, but now that the convention is over, I’m free to do this.
I will break the post into several parts to make it easier to follow and so you can jump to any of the topics that interest you the most without having to go through a bunch of filler first.
The main parts are as follows:
• Preparations / pre-production (aka why I picked the C# version of Godot)
• Development process
• Dealing with possible cheaters
• Fighting with Apple and Google
• Outcome: Player / attendee feedback
• All the lessons in a few sentences
I created a very short and somewhat simple showcase showing off the game in motion, at least it’s first Temple, with all items already unlocked. Normally the attendees had to go around the convention ground and collect them as they found them using the QR codes, but for easier showcasing, I simply unlocked all of them for the video.
Preparations
A little bit of backstory. I’ve joined this convention last year to help with their game as well. The whole idea is that the convention is not only for people to have fun and party, but we have an entire story and multiple characters that we develop over the years with each iteration of the convention. And the past 2 years, a small video game was made to help move the story forward as well. The first year it was a simple Visual Novel, where attendees had to solve a crime mystery, and find out who stole a specific item in the story. Last year, the convention was split into two groups of attendees. Each attendee could pick a side to fight on, and during the con itself, we had a small web minigame where they could fight monsters in a very undertale-inspired battle, and with each battle, they would earn some points for their team. The outcome of the game was announced during the closing ceremony, which also influenced the theme we went for this year, which was “Echoes of the ancients”. Think Indiana Jones, old temples and treasure etc… We got quite a lot of feedback from last year, saying that the game was a little bit too competitive, it was too much effort, you had to play a lot to properly contribute to your team and such, and it split the convention into two groups (which was kind of the point, but it still wasn’t ideal for what is supposed to be a highly social and cooperative event). Based on all of that, this year we decided to do a much less competitive and more accessible game. No more split groups, and this time, instead of a web game, we went with a proper, fully fledged mobile game for both iOS and Android. The genre is a point and click puzzle game. Without going into too much detail, the game has 3 temples, each temple opened one by one as the convention progressed. On the first day after the opening ceremony, we opened the “Earth” temple, which contained two tutorial puzzles, 3 side puzzles and one main puzzle. The next day, we opened the “Water” temple, which contained 3 side puzzles and one main puzzle, and of course, on the 3rd and last day, we opened the “Fire” temple, with 3 side puzzles and one main puzzle. The goal of the game was quite simple, complete at least 2 of the 3 side puzzles to access the main puzzle, then complete the main puzzle to “finish” that specific temple. Once you finished all 3 temples, the outro would play, and that attendee has finished the game and progressed the story in a canonical way, something we showed off with an animation during the closing ceremony. Some puzzles required items to be collected around the venue, each had its own QR code which the attendees had to scan. Once they did, the server would tell the game which items the attendee currently has.
Since this is considered a large convention (1,700 attendees this year and growing), we have quite a large team that handles things, and all the volunteers are sorted into different departments, such as Stage tech, Security, Events etc… and of course, the one I’m in, the Story department. Within this department, I’m a sub-department lead for the Game developers. There’s quite a bit of overlap though, and cross-department work is a norm, for example, with the game’s website, frontend and backend, I had help from guys who are in Stage tech as well as Events.
Now, onto the reason why I picked the C# version of the engine, while it’s clearly still marked as “Experimental” on both iOS and Android. The truth is, it’s experience. Both me and most of my team have A LOT more experience using C# than GDScript, even the game’s backend was written using C# (aspnet core). Last year for the little web game I had to use GDScript since it was a web game, but this year, since I knew, we would want to make a mobile game, I had the opportunity to stick with what I know best, and that’s what I did. But on top of that, I was curious about how well the C# version works on mobile platforms, so I wanted to give it a go, knowing full well I might run into plenty of issues along the way. Surprisingly, that wasn’t the case at all.
Development process
Now onto the process itself, and this is going to be a heavy follow-up on that old post I made, but with a lot more, proper examples. Since I made that little system, I’ve been adding to it and polishing it more and more, I needed it to be as stable and as reliable as possible, considering I only had roughly 2 months to make the game from start to finish, so I couldn’t afford using a fragile or unfinished system. My requirements were the following:
A system that can “do stuff” when the user taps or otherwise activates a specific area on the screen
…that can share data between its different actions
…that is very easy to serialize and save
…that is also very simple for designers to use as they see fit
…that also allows for quick, rapid changes during development.
My original command pattern implementation was almost good… except it had one flaw. It couldn’t really share any data between its own actions, at least not easily. So, my goal was quite simple, I had to add some way to pass data from one action / command to the next, and that’s where the Blackboard came into play! To understand the basic structure of the command pattern implementation in the first place, please check out the old post I created. Once you get a general idea, come back here and follow along.
I had to modify my base TriggerAction class to make room for the Blackboard, so I did:
[GlobalClass]
public abstract partial class TriggerAction : Resource
{
public abstract Task Execute(ActionContext context);
}
This allowed me to have a class which contained a few important things that I could pass along from command to command. ActionContext looked like this:
public class ActionContext
{
public Node Caller { get; set; }
public Variant PreviousResult { get; set; }
public Dictionary<string, Variant> Blackboard { get; set; } = new();
public bool IsCancelled { get; set; } = false;
public CancellationToken CancellationToken { get; set; }
}
And to make my life even easier, I’ve created an Extension for the Node class:
public static class NodeExtensions
{
public static async Task ExecuteAllActions(this object caller, Array<TriggerAction> actions,
CancellationToken cancellationToken = default)
{
if (actions == null)
{
return;
}
ActionContext sharedContext = new()
{
Caller = caller as Node,
CancellationToken = cancellationToken
};
foreach (TriggerAction action in actions)
{
if (action is not null)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
try
{
await action.Execute(sharedContext);
}
catch (OperationCanceledException)
{
break;
}
if (sharedContext.IsCancelled || cancellationToken.IsCancellationRequested)
{
break;
}
}
}
}
}
This basically allowed me to do the following thing from ANYWHERE in my code:
await this.ExecuteAllActions(_breakActions);
I can implement this system into any node I wish by simply creating a local variable or property that’s an array of TriggerAction and simply call the ExecuteAllActions function that is now available in any code that inherits Node. (Which is quite a lot in Godot)
And thanks to the blackboard pattern, it’s now trivial to share data from one command to the next, a great example of this is any node that checks some kind of outcome, for example, the CanFinalIdolAppear command. This commands only job is to check if the player can grab the final idol and trigger the ending sequence of the game. It looks like this:
[GlobalClass]
public partial class CanFinalIdolAppear : TriggerAction
{
[Export] public string ContextName { get; set; } = "FinalIdolAppear";
public override Task Execute(ActionContext context)
{
bool canAccess = GameFlags.GetFlag(FlagName.EFinishedColorRiddle)
&& GameFlags.GetFlag(FlagName.WFinishedMadDashToFinish)
&& GameFlags.GetFlag(FlagName.FFinishedOrgans);
context.Blackboard.Add(ContextName, canAccess);
return Task.CompletedTask;
}
}
Once this command is executed, the next command will have access to the variable called “FinalIdolAppear” on the blackboard and can do something with that information. Most of the time though, I simply used the outcome of these blackboard variables in a BranchAction I created:
Since the system still uses Resources, the Godot UI makes it super easy to edit things, re-arrange them, change the properties and have them update immediately, which is great for making rapid changes and seeing how it all affects the game overall.
I made a small video demonstrating how all of this looks inside the game through a single interaction.
Dealing with possible cheaters
This topic is very split for a lot of people, and I can completely understand why, but I’d still like to go over it since it was a huge part of our development process. Cheating.
Our main worry was quite simple, the target audience for our convention is… for the most part, IT people. Not necessarily all, but quite a few of them are, and they are REALLY good at what they do. So, we couldn’t really apply the whole “most people won’t even be able to figure this out” rule, because, yes, these guys can absolutely figure it out. So, we decided to have two lines of defence. First one was simply Apple. On iOS, the game with C# was using NativeAoT, which means the code got compiled into Assembly. While this is not a 100% foolproof at all, it is most certainly a lot harder to read and deal with than pure decompiled C# code. This was good enough for iOS, but then there’s Android… more about that later, but on Android it’s quite a lot easier to just get the APK file and kind of just… look at it in any way one would like. This is obviously not ideal, and if we just simply left the APK file as is, people would VERY easily figure out all the API endpoints from the code and complete the puzzles programmatically and win the game. So, in Androids case, we decided to go with the Encrypted PCK file route… with a twist. Obviously, we know about the many automated tools that can extract the encryption key from the compiled executable for Godot games, since Godot is open source and all, and that key is always in the same place. So, we did a little sneaky. We created a custom fork of Godot that had all the same elements, and it kept the same encryption key in the exact same place as the normal engine… except that key wasn’t used to decrypt the files. That key was hidden somewhere completely different. We didn’t just want to remove the key outright, since that would immediately tell most users “Oh okay they moved the key”, instead, we kept a decoy / red herring key where it was expected to be, but then not actually use it. That way most automated tools will be tripped up, or if their only job is to give the user back the encryption key, they will do that, but it’ll be useless. We felt this would be enough for the Android version, since the convention itself was only 3 days long anyway.
And of course, both versions had our final line of defence: The backend itself. We REQUIRED a ticket from all players, you couldn’t access the game unless you had an active, paid ticket, the backend wouldn’t even give you a token if you didn’t have that. And near the end, when we created a database dump, we looked over the data we got, filtered out people who already tested the game to make it fair, and we looked at the timestamps for when the requests for item collections and puzzle completions happened. If someone completed all the puzzles in a span of 10 seconds, we could be quite confident that they cheated, but luckily nothing like this happened, and it looks like everyone played the game legit.
Fighting with Apple and Google
Ah yes, this part was quite… interesting. I never had to deal with publishing an app in either app stores, but now it was time to do so, since I oversaw this entire process. We had a nonprofit organization for the convention, so Apple was quite simple to deal with the first time, they simply gave us a developer account for free, under one condition: We cannot have microtransactions, ads, or anything that would generate us income at all in our apps / games. This is fair, and we weren’t planning to include anything like that anyway.
During the review process of Apple, we got rejected 3 times before finally getting accepted on the 4th. The very first time was because of our login flow. Our login flow looked like this: The game generates a code challenge, then opens our website to a login page. Then we ask the user to log in with their main convention account, and once they do, we store some basic info about them, only as much as the game needs to function, and if the game pokes an API with a specific code it generated, and that user exists, we give the game a token it can use to make requests to the server. So, from a user’s perspective: Tap on “Open browser” button, their browser opens, they log in, then we ask them to go back to the game, at which point the game will recognize the focus coming back to the game, and make a request to the server, and it gets a token. Apple said this is bad user experience (I don’t agree with it, since a LOT of things still use this style of login), so I had my team create a native iOS plugin that uses the built-in webkit browser to authenticate the user inside the game itself. The only drawback of this approach was the fact that the documentation for creating a native iOS plugin is severely lacking for Godot as far as we could tell. But we got it working in the end.
…Google on the other hand, was HORRIBLE to deal with. They have a policy that will simply deactivate your developer account if you are inactive for X amount of time, and once your account is deactivated, there’s no way to get it back. When we first reached out to Google about this, they simply told us to just create a new account. Well, this is something we couldn’t do, since our main Google account is an organization account, and it’s registered under our legal name. We cannot register a second google account legally with the same organization name. We reached out to support almost 3 months before the convention, and we never, ever got an answer back. They kept telling us they will escalate the situation, but it never got anywhere. So, in the end, we simply had to distribute a pure APK file for Android users and create a small help page on how to install said APK on different devices.
Outcome: Player / attendee feedback
In the end, we got quite a bit of feedback, most of it positive, with only a few complains about one specific puzzle that required attendees to listen to a melody around the venue and play that on an instrument inside the game itself. This was not at all accessible, and we will try to avoid such puzzles in the future. Other than that, given out how much effort the entire team put into it, and how the convention’s focus wasn’t really to play games, those who did seemed to love it all. We only had a few technical problems during the convention, namely people with older phones being unable to play the game (this is something I couldn’t really do much about, since Godot itself has no support for such old hardware and software), and one person had some login issues, something we were able to solve rather easily.
All the lessons in a few sentences
I’ll summarize a few things I learned overall and what I would’ve done differently in the future.
- Do NOT wait with the app store processes. Seriously. Even if you want to release your game in a year from now, start the process. Start making your developer account and start getting approved all around. Trust me, it is SLOW. VERY SLOW. Get started as soon as you can.
- Your choice of programming language really doesn’t matter. Both GDScript and C# work really well, and you can argue that “But GDScript has better support”, but in the end, if you know how your game will work, and you know how to make your systems in a nice, elegant way, it genuinely does not matter what language you go with. It won’t slow you down or speed you up. Use what you know best.
- While this is not a massive successful game, it was played by a bunch of people, using wildly different hardware, on platforms that are known for being very picky about the type of games it’ll let you run. We found that, despite the “Experimental” warning given to the C# version of iOS and Android exports, it seems to stand up well and works wonderfully! Props to the Godot team who’s keeping it all alive.
- Don’t overengineer. While I was, and still am, very proud of the command pattern implementation I used, quite a few parts of the game, that I knew would never have to be changed later down the line, were done in a “lazier” way to save time. Do not be afraid to be lazy. A huge example for us was the credits sequence. I knew there would be no player interaction during that, I knew it’s just a scrolling parallax background with names flying by, so the code behind it is very much a mess, but it works, and it was very easy and quick to make.
- With the previous point in mind, if you know exactly how some of your systems should work, and you have your requirements down to a point where you are confident it won’t change too much, spend extra time making your systems interact with each other well and work in a more generic way. I’m not saying you should stick to all the coding patterns ever made but do try to write readable and maintainable code. It’ll help you later down the line a lot, especially towards the end where your testers will point out small things that will bother players. You should be able to fix those with little to no extra code.
- Test. Test test test. Luckily, I already had a lot of things in mind with testing being involved from the start, but it truly helped us IMMENSELY! As far as we can tell, the game never crashed a single time during the entire convention, and the very few problems we did come across were all related to the server, not the game itself. And the server is a lot easier to fix vs. the game, then trying to get the patch approved and pushed through Apple and Google.
Hopefully this post will give a confidence boost to someone out there, it really doesn’t take a lot to make a fun game that will put a smile on peoples faces! And if you have a team behind you for all the different parts, it’ll go even easier / faster / better!
As a final PS, if you’d like to know more about any part of this process in more detail, I’m happy to answer things in the comments!
