You may be thinking, “wait, VVVVVV is already on iOS”. Yes, it is. But I can explain after the link.
|VVVVVV (C++ version) for iOS and tvOS for Apple TV
I’ve been working on some more iOS stuff and game ports since DOOM 3, including a “white whale” thing I just haven’t been able to do yet, along with a revamp of this site to more easily show off the stuff I’ve done over the years, but between that one project being difficult and a while bunch of Real Life™ stuff, I haven’t completed anything yet.
Then on Friday, January 10, 2010, Terry Cavanagh released the source code to VVVVVV for its 10th anniversary and I just got this wild hair to try something and see how quickly I could do it, if I could.
One of the things that makes this different is that not only has VVVVVV already been ported to iOS before, but it’s still there today, available for purchase, working just fine. No 32-bitpocalypse here. And the source code for it is on the repo he released.
So then what the heck is this?
VVVVVV, the game, was released in 2010 for Windows and the Mac. Cavanagh wrote it using Adobe Flash, I’m guessing because it’s what he was experienced in using at the time (Flash is also a bit more visual than your standard source code IDE so that may have been part of the appeal as well). The game was designed to resemble old games for the Commodore 64 with low resolution graphics and MIDI/SID music and sound. You control your character who has the ability to move left and right, but jumping causes them to flip upside down and then cling to the ceiling, where movement can commence but with the gravity physics reversed. The game is essentially a puzzler game with every screen being some sort of puzzle in this vein (though some areas have puzzles and ceilings spanning multiple screens). The tendency of 2D games on early computer platforms to be single-screen based and not side scrollers is used to strategic benefit here. Additionally, some of the puzzles are quite difficult, another feature in common with older 2D games.
VVVVVV was successful, award-winning, and remained notable throughout the last ten years, both because of the fortunate timing of being released at the beginning the indie game renaissance triggered by the success of Minecraft and also because its Commodore 64-style graphics still stand out compared to many other pixel art efforts.
Writing the game in Flash, however, limited its ability to be released on other platforms – notably Linux, which I don’t think ever had support for standalone Flash executables. It definitely became a liability when it came to game consoles. So in 2011 the game was ported to C++. I don’t know the exact arrangement but my guess is he hired a C++ developer to translate the game’s code to C++ and later a different developer handled updates and maintenance. This leads to a number of quirks in the code since a major refactoring was out of the scope, something he discusses on the blog post announcing the source release.
In 2014, however, the game was ported to iOS and Android, however for whatever reason it used the Flash source code version. It uses something called Adobe AIR which allowed developers to use various Adobe technologies to create standalone releases (it may be what he used for the original releases on Windows and Mac) and iOS and Android were output targets. So given the circumstances this might just have been the easiest way to do it. Can’t really argue with the results.
It’s cool that he released the source for both versions, and especially interesting that you can compare the two if you’re so inclined and see how A got translated to B, but if you’re like me and you’d rather open up the iOS version and play with it, you’re mostly out of luck. Adobe has deprecated Flash and they’re in the process of sunsetting Adobe AIR, and that’s before you consider the fact that you’d need a license for Flash or whatever they’re calling it now.
But I saw that the C++ version is based off of SDL and I got this wild hair to see how hard it would be to get the C++ version running on iOS. I had it almost completely working by the end of the day and I had the tvOS version working a few days later. Quite frankly I probably could have had both versions working by the end of the next day but that Real Life™ stuff kept happening, including dodging some tornadoes that never happened.
I’m increasingly appreciative of SDL in general. By accident or design I had avoided using it until the Quake II port and I wasn’t sure what the point was seeing as how it didn’t handle OpenGL versus OpenGL ES changes, which was the big stumbling point on that one, but when DOOM 3 took under two months I started to get it, and appreciate the extent to which a lot of the work it was handling had been handled by the source ports I was using prior to that point. In the time since I’ve done the SDL thing to the Quake III: Arena and Return to Castle Wolfenstein ports as well.
All of this is to say that the reason I thought it might not be too difficult to do stemmed from the fact that it likely had no OpenGL code to worry with, and I recalled how the Quake II port’s software renderer came across much easier than the hardware accelerated version.
The desktop version code went so far as to come with a CMake file so I was able to generate an Xcode project for the Mac version and had it running with little hassle. VVVVVV is interesting in that the levels are hardcoded in the source code. He would use a level editor he made and had it export the levels as source code so some of the files have these nightmarish string/integer arrays but it works. However while this means the data for the game doesn’t contain the levels, it does contain the graphics and sound. Consequently you have to have this data file either from the original game or from the “Make and Play” version which is free on his website. The version created by a CMake version of the game (be it Makefile or Xcode) comes up with a regular executable file, one that’s looking for the data.zip file to be parallel to it in the same directory.
From this I went and made an Xcode project from scratch for macOS that would essentially do the same thing but not be beholden to quite the same practices as the CMake generated version and also have the output be an .app file with the data.zip file built in and its own icon. This is probably how one would distribute this game for the Mac today, and it also worked as a stepping stone to the iOS version. For the sake of reference I put it on GitHub here but with numerous caveats pointing out that people wanting to mess with the official source code should look at the official repo.
From there I took the latest version of the source release of SDL (2.0.10 as of this writing) and started with the example Xcode-iOS project. I’ve been using this tutorial from someone called Lazy Foo to figure out the right way to do SDL and iOS. I used it back when I did Quake II and when I did DOOM 3, I just took Quake II‘s project and gutted the C engine code and renamed all instances of “quake2” to “doom3”, but I didn’t want to do that here.
Something I’ve been doing with all the id Tech ports which increasingly became a liability was this insistence to have a CocoaTouch interface prior to the game’s engine launching. Originally the idea was that since the official Wolfenstein 3-D and DOOM ports did this, I wanted to follow suit. In the case of Quake III: Arena it came in handy because I was able to do that server browser as well. But when I started using SDL it became apparent – SDL thinks it has total control.
Way back at the dawn of the C programming language it was decided that there’s this reserved function named “main()” and it’s where everything starts. When you dig into an example iOS project in Xcode you don’t find a “main()” function but something that’s become apparent to me is that whether or not you see it, it’s still there – your AppDelegate file has these methods that get called when things happen like the OS starts the app or is finished loading or your app is going into the background but there’s still a “main()” function under the covers pulling the strings. That’s what SDL is trying to take over, and the considerably easier way to live is basically to just let it.
So since I had no real urge to make the CocoaTouch menus thing happen again and was instead thinking of speed of development, I just skipped that part entirely. So now it just launches into the game. Really the only reason it works in the id Tech games is because most of them have extensive command line options that let you launch into the game directly with a level, difficulty setting, etc. I doubt those exist with VVVVVV but I haven’t checked.
The Xcode project CMake came up with had three additional directories, physfs, lodepng and tinyxml, as separate static libraries. I actually had it working on the Mac Xcode project with these just all globbed in but I decided to part them out into separate static libraries anyway. I did the same thing on the iOS version and eventually remembered that I had to make two versions of these – one for tvOS and another for iOS.
The project also required SDL_Mixer. Both SDL and SDL_Mixer are on the Mac as built in libraries but I’m actually not sure if it’s because they come with macOS or if it’s because I’ve installed them some time in the six years I’ve been using this Mac but in any event I just did a link to the static libraries on the Mac and it worked there.
For iOS, I needed to include them as projects/source like I did with the Quake II and DOOM 3 ports, but the SDL distributions did something I didn’t like previously. They come with these subfolders, one of which is “Xcode” and the other is “Xcode-iOS”. The Xcode-iOS subfolder has subfolders of its own, including the template project and another “SDL” subfolder which has the Xcode project for the library, and it references the source code two folders up.
I preferred the SDL Xcode project be in the same or a parent directory of the SDL source code, and also ditch anything unnecessary like Visual Studio project files or Android includes. And I did the same thing here except it requires you to go in and mess with all the relative path names so it can see the files.
I did this and I did something similar with SDL_Mixer and I had it working but it required some additional manual tweaking since some of the subfolders I thought weren’t necessary actually were.
I hit build and run and then I was greeted by an iOS simulator and the C64-style loading screen the game starts with
Once it got to the menu I couldn’t do anything in the Simulator since it didn’t have any on-screen controls but I pushed it to a device (after clearing up some build issue with real hardware I don’t even remember now) and the MFi controls worked perfectly out of the box.
Except there wasn’t any music. I looked up a bunch of stuff and it seems as if the issue boiled down to SDL_Mixer being configured incorrectly so the ogg files weren’t loading.
Basically it boiled down to more missing files I thought weren’t missing but then it hit me – why am I doing this crap with the SDL projects to begin with? When you include the SDL projects they have the source code files in subfolders in the project, though they’re not real folders, just virtual ones, since the actual code isn’t in subfolders. But it hit me that this doesn’t really matter, the code that needs to be there is there and, while it does mean my repo will contain files I don’t need, when later versions of SDL come out I can just drop them in as replacements and not have to worry about what files are what.
Once I gutted my SDL and SDL_Mixer attempts and just dropped in the official distributions the music came back. I need to do this to the rest of my ports now. I’ve seen the light.
I’m trying to minimize changes to the game engine code so for the on-screen controls I followed a pattern I did with the Quake 3 iOS port recently where I add methods to one of the header files to add some methods to add the on-screen controls, then I have an Objective-C file implement those methods to call the Swift methods to add the controls as UIKit objects. I could probably have eliminated one of those layers if I just wrote the UIKit stuff in Objective-C to begin with but oh well.
But while this worked in the Quake 3 port it wasn’t working here. In the end though it was the weirdest thing – I renamed the file from sys_ios.m to sys_ios.mm and everything worked. A .m file is an Objective-C file (the m is for “iMplementation” and I’m not kidding) but an .mm file is considered an “Objective C++” file, and as far as I can tell, Objective C++ isn’t a different language but rather just a compiler variant so it can get along with C++ code.
In my id Tech ports I had been doing a thing where the on-screen controls triggered keyboard controls and actions. For some reason my first instinct was to try the same thing here but I hit a wall because the way this game is organized, I’d have to manipulate an instance of a “Game” object but my efforts to try and pass that into my custom code for the on screen controls failed. I looked up why and it was also the weirdest thing but not an easy fix this time – Swift code can talk to C code via the bridging header just fine but as of right now it can’t talk to C++ code and the “Game.c” file was using some C++ specific include libraries so it was no dice.
I recently did some more work to the Quake 3 iOS port where I was able to get the on-screen menus working and I needed to basically look up the way to pump SDL input events so that the touches would register as mouse clicks. It finally hit me – this SDL input event pumping is the right way to do things. So once I got that working I could play the game with the on-screen controls.
Initially the on-screen controls were cramped into the same 4:3 area as the game and that means you’d be touching the screen you need to see even though there’s plenty of room on the sides. I thought this was an SDL quirk since the game area is 4:3 but it turns out the real issue was because the SDL example project was old enough that iOS thought it needed the 3.5″ or 4″ screen. Replacing the splash screen with a launch screen storyboard told it to use the whole screen, it still only used a centered 4:3 area for the game screen, and I could put the controls where they should be, to the sides of the screen.
The way the controls work, pressing up or down is the same thing as jumping and the on-screen controls were damn near impossible to use because any drifting up or down would send you flying. I wound up disabling up and down which I thought would be an issue on the menu screen but I lucked out because left and right let you maneuver there, too.
There’s still an issue with on-screen controls. Sometimes they stick. This is especially pronounced in the DOOM 3 port – it’s like some sort of graphics stuff takes over before the on-screen joystick is allowed to recenter. I’m not sure what the best way is to fix this but until I have an epiphany in this area, MFi controls remain the best way to handle controlling the game.
The tvOS port wasn’t quite as quick as I had thought it would be and I honestly don’t remember what all I did to get it working but it was just a few hours so it wasn’t too hard. I do remember I had accidentally started that target out as an Objective-C target but that meant it was missing some setting I needed to change for Swift and there’s literally no way to get these settings into the target without just deleting it and starting over so I did. But now it works, and unlike the iOS version, this version didn’t exist before.
As of this writing there’s still some quirks to hammer out, and while I gave the iOS version an icon (same one from the desktop version) and a splash screen, I haven’t given the tvOS version the same love.
But yeah, I was able to get this working relatively quickly because it was a 2D game that used SDL. If I owned a decent Android device I might be able to get it going there too, or at least it would be a good starting point to learn how since it has fewer difficulties and no OpenGL hassles.
And as I write this it occurs to me I should see what happens when I click the checkmark to make a Mac port from the iOS port using Catalyst.
Anyway have fun, if anyone wants to contact me I can be reached at firstname.lastname@example.org.