2015.
Gamers worldwide hold their breath. Bandai Namco’s patent on minigames in loading screens expires after 20 years of tyranny.
…
…
crickets.
I’m as disappointed as anyone. 10 years on, and loading screen minigames are all of nowhere. Is it the SSDs? 5G? Pure lack of whimsy on the part of game developers?
I’m taking a stand.
Like all devs, at one point or another, I’ve entertained the idea of going into game development. In my IT GCSE, our final-year coursework was to make a game from Scratch, that is, with a drag-and-drop logic system designed for little kids. As a result, everyone got moderated down a grade, because we were meant to program it in Adobe Flash.
I was actually the only one who cinched an A*. But I deserved it.
This project consumed me for 9 months. I made an elaborate space shooter with 5 side-scrolling levels, multiple playable ships, an 8-bit heavy-metal soundtrack I generated using midi files, plus co-op, 1-v-1 deathmatch, and boss rush modes.

Like most low-agency 15-year-olds, I didn’t think too hard about the next logical step of my programming prowess. I promptly dropped all my knowledge when I started my science & maths A-levels, so I could work towards my future career as “probably a doctor”.
Since landing in software engineering mostly by chance, I never seriously entertained going into game development. I like money and seeing my family too much.
I got the SpriteKit book in 2017, a book which is still entirely up-to-date (its last release was 2015). I set it aside after my colleague pointed out that I still couldn’t remember to set the Delegates and DataSources on my table views.
I digress*.
*What’s frickin’ new?
I’m re-litigating my past triumphs and blunders for a reason.
Game engines.
Everyone sort of knows what these are. But, uh, maybe not specifically. They essentially divide the responsibilities of game development between the cracked goblins that write shaders in assembly vs the gameplay designers that shuffle the subsequent triangles into playable levels.
Today I’m going to show you…
What is a game engine?
The moving parts of a game engine
How to build your own simple game engine in SwiftUI
How to use this to make 2D games
The future of app loading screens
SwiftUI was a surprisingly perfect fit. My engine is entirely open source, plus my sample app, where I demo some minigames in a real loading screen. The games might just feel familiar if you owned an iPod Touch back in 2011.
This email can be too long for many email clients, so please read on my website for the best experience.
What is a Game Engine?
A game engine is a set of libraries that implements the common functionality that most games will need. It handles things like:
2D and 3D graphics rendering to draw stuff on-screen
Physics and collision detection to move objects realistically
Audio to play sound effects and music
User input with keyboards, mice, controllers, or fingers
Networking to support online multiplayer
AI for enemy behaviour and pathfinding
Consider the iOS SDK: you don’t need to think about the window server, capacitive touch hardware, runloop events, or hit-testing every time you use the gesture recogniser APIs.
The way a ray of light bounces off a 3D texture doesn’t change much between games. Keyboard and controller inputs are pretty standardised. Physical laws are often the same, barring a few tuning parameters like gravity.
The wheel (or, perhaps “the engine” in this instance) has been invented, we are just shaping the chassis and slapping on some sick decals. We can focus on what makes your game unique.
This engine is what I built for you today. Let’s look at how the moving parts are implemented using SwiftUI.
Anatomy of the SwiftUI Game Engine
If you reckon me a charlatan for redacting my code blocks, here’s the full monty on GitHub: go ahead and install my package today:
https://github.com/jacobsapps/GameEngine
Windowing
Every game needs a window in which to display.
Since iOS 15, SwiftUI ships with Canvas, a simple 2D graphics engine which allows us to freely draw onto the screen. It’s exactly what we need. This naturally locks our engine into 2D, but I suspect I’ll lose some of you if I dive straight into 3D shader math*.
*Also, I’d have to learn it.
Canvas has a graphics context in which we can pretty much draw anything we like. It also gives us a CGSize, allowing us to scale everything accurately.
Canvas { graphicsContext, size in
// Draw game here
}The Game Loop
The game loop is the most fundamental concept in game programming.
It’s the central source for everything that happens. Since this is ostensibly an iOS blog, it’s a bit like the iOS run loop:
Actually, yeah, it’s exactly like the run loop. Another classic Jacob Perfect Analogy™.
While your game runs, there’s a while(true) loop that ticks at the frame-rate. It receives events (user input), updates the logic in the game, then renders everything to screen for a frame.
A game is this while loop.
Input.
Logic.
Rendering.
Repeat.
If you didn’t think SwiftUI was already a great fit, we can wrap our Canvas in a TimelineView that ticks every 1/60 of a second, and lock in a buttery 60fps for our game loop.
These 7 lines of logic achieve the 4 most important functions of a game engine:
Set up windowing with Canvas.
Set up the game loop using TimelineView locked to 60fps.
Pass game ticks into the game engine so all objects update.
Pass the graphics context into the game engine so we can render everything.
Logical Building Blocks
Games are very object-oriented.
Literally.
While this is anathema if you’re a protocol-oriented composition Stan, it makes sense for games to be crufty.
Everything in a game is a game object.
Inheriting from the GameObject superclass, to be exact.
This is really critical because everything in the game has the same basic properties.
Inheritance is genuinely the way to go here. In a game, you really do want every object to work the same way by default. Re-implementing the same properties and functions across interrelated game objects is a waste of time, since objects form a natural inheritance hierarchy.
Everything in your game subclasses from this basic functionality: size, position, and layer (so we know what to render if there’s overlap).
Rendering
Everything visible will be drawn on-screen. The render() function takes the GraphicsContext created in the Canvas to draw the GameObject directly onto the Canvas using SwiftUI drawing APIs. You can subclass with a more complex Path, a gradient, or even a sprite from your asset catalog.
For anything complex, use context.resolve() to cache views into GraphicsContext.ResolvedImage to avoid re-rendering static content over and over every frame.
Logic
All game objects have logical properties which are modified every game tick inside update(deltaTime). This is a no-op on the superclass, but every subclass may have properties that change with time.
The ticks of the game loop are the core of everything. Every single object in your game follows the ticks, updates their logical properties, and re-renders their current state on-screen.
isActive is a flag for cleanup. When an enemy scrolls off-screen, we stop rendering it, then deinit. This is vital for performance as you get more objects in a level. It was critical in 1985 for NES games, and it’s still important now.
User Input
Interactivity is the definitional differentiating factor for media to be considered a game. Engines have to handle user input.
On a mobile app, input can be as varied as taps, drags, accelerometer data, shakes, camera data, or bluetooth data streams. It’s a bit 2011, but you can even implement software joysticks.
We can start by handling user touch input gestures on our main GameView:
DragGesture(minimumDistance: 0) is used here instead of a tap gesture because this allows us to continuously stream the user’s finger position each game tick. This takes a complex range of potential inputs and condenses them into a single input, at the given location, each game tick. These gestures may be propagated down towards game objects.








