I have reached the end of my jigsaw puzzle game journey!
Documenting this process in as much detail as I did has been revealing. There were holes in my knowledge: things that I knew how to do without a full understanding of how they actually worked. Trying to explain every line of code forced me to reach for that deeper understanding.
Despite this, some of my decisions were still questionable and I would do certain things differently now. On the other hand, I was pleased at how other aspects came out and there are ways that I have implemented things in this that I definitely want to pull into my adventure game engine.
Using the Entity Component System approach was overkill for such a straightforward game but understanding how all the pieces fit together (sorry) was enlightening.
Thinking about games in terms of components and properties and systems cemented the idea that they are just discrete frames made up of huge amounts of state. Change enough properties of enough components 60 times a second and one can create massive, believable worlds full of life and adventure (or simulate a jigsaw puzzle).
While the SpriteKit actions and physics classes are amazing and were helpful when I was just starting out, they hide the complexity at the expense of clarity.
Breaking things down into components with a series of properties gives a clearer insight into what’s happening on any given frame. At any moment I could pause the game and step back through to see how any individual property had been changed, who changed it, and why.
SpriteKit provides hooks into different parts of the update cycle. For every frame, the
update(:) method is called in the
SKScene instance, then actions are run and the
didEvaluateActions() method is called, then physics are simulated and
didSimulatePhysics() is called, the constraints are evaluated and
didApplyConstraints() is called, and finally
didFinishUpdate() is called for any final changes.
These can be considered as five discrete systems which have a defined execution order. However, the order cannot be changed. With ECS, I am able to go beyond this and develop my own systems and choose in which order those systems are run.
Also, from a maintenance point of view, having systems live in separate files makes managing games easier and prevents the
SKScene subclasses from becoming too bloated.
Interesting new game dynamics become possible with little work using ECS. For an easy mode where some pieces get fixed in place when the game begins, I could set the position component to the target position, the rotation component to 0, and remove the interaction component and a piece begins the game, fixed and immovable, in the correct place:
Fighting the Frameworks
Having said that, using ECS is not appropriate all the time. While I recognised at the time that it was ridiculous to create my own scale component, I should have used an
SKAction to handle the scaling effect as all I did was re-implement the
Implementing it with components did make it clear as to what was happening when during each frame. I was able to control when during the update cycle the scaling effect would be applied but this level of control was unnecessary for a game like this.
The other effect of using a scale component was that its existence controlled which entities could scale and which couldn’t. Instead of the sender having knowledge about the receiver’s capabilities, the sender (the game scene) could indiscriminately apply the scale to every entity and it was up to the receiver to react to it if it could (in this case, only if it had a scale component).
A good compromise here could be to keep the scale component as a way of giving the entities control over their abilities and attributes, but then use SpriteKit actions behind the scenes to implement the actual transform.
Additionally, I found that shoehorning certain things (especially lower-level things like input data) into an ECS-based system doesn’t always make sense and I fell into some rabbit holes of trying to adhere too closely to ECS principles.
In the world of iOS and macOS where everything is built on top of frameworks with very particular styles and conventions, it often pays to be flexible and use a hybrid approach.
Which leads us to…
I don’t think I got the balance of abstraction and dependence with the interaction system.
I’m not happy about how the game scene is making decisions about how an entity should respond to a particular input. For this game, it’s fine and it works—pieces can only be moved or rotated—but for more complicated games I might want more options.
For example, in a space sim, some entities (for example, HUD elements) might respond to a tap by opening up a modal window whereas for other entities I might want lasers to fire or the hyperspace to activate.
In this case, I don’t want the
SKScene subclass deciding whether or not a tap means “open modal” or “fire laser”.
The way that SpriteKit is set up encourages you to use
SKScene subclasses to manage input data, but I think the most the scene should be doing is report to an entity “this is a pan/mouse drag” or “this is a click/tap”. How an entity then responds to that input should be defined by its components.
What this actually looks like in practice is something I’m still figuring out.
I was pleasantly surprised at how easy it was to get the game running on macOS as well as iOS and how cleanly I was able to implement it. The platform-specific work consisted of using two extensions on the
GameScene class, as well as an individual view controller for each platform.
There was one extension included in the target for each platform that handled the custom input data for that platform (one for mouse on Mac and one for touch on iOS). Although I don’t think I’ve nailed the balance quite yet, the interaction implementation I did end up choosing made it easy for both macOS and iOS versions of the game to respond identically to different input methods.
The view controllers were required by each platform to present the SpriteKit scene anyway. They were only slightly adapted from the boilerplate one gets when starting a new SpriteKit project on each platform.
This was basically all I needed to do to get support for both Macs and iPhones and iPads. The cross-compatibility of SpriteKit is amazing.
Getting a deep understanding of scale modes and scene sizes helped me figure out a compromise that made it possible to use the same Photoshop document for iPads and Macs. From there I was able to implement out some additional optimisations to have even smaller assets for iPhones.
I’m happy at this balance between optimisation and ease of development and this method is perfect for individual scenes in adventure games. Provided all the action takes place in the visible 4:3 area, then it doesn’t matter if the iPhones get some extra background.
Improving the Render System
I limited myself by having the render system be part of the sprite component rather than using a more generic setup. The application of position, scaling, and rotation applied only to
SKSpriteNodes. If I wanted to use the same system for text nodes (for example) I would have to copy the
update(deltaTime:) method of the sprite component and paste it into the text component. Any changes would then have to be made in both classes. Not ideal.
A better solution would be to have a separate render component with a generic
SKNode property. The render system would then be an extension of this component. Position, rotation, and scaling would be applied to this
I could then create an additional sprite component (or text component, or particle component, or whatever) that would have its own property that was specific to this type of node (e.g. an
SKSpriteNode property in a
SpriteComponent class). This specific node would then be added as a child to the generic node of the render component (in fact, this is how Apple manages it in their DemoBots application).
This would make it so the position, scaling, and rotation code could be applied (indirectly) to any possible node types.
I now have a working jigsaw puzzle game engine! It supports both macOS and iOS, and works on all of the iPad and iPhone screen sizes. It has a companion Photoshop script that makes creating new puzzles from photos or illustrations easier.
It almost feels like a real game!
Sadly, it’s not even close.
The reality is that turning it into an actual game that could be on the App Store would require a lot of additional work. Off the top of my head, here’s a list of things that are still missing:
- Being able to manually select different puzzles
- Some indication of the difficulty of different puzzles
- Saving and resuming puzzles
- Some kind of timer or other scoring system
- Sound effects and music
- A menu system and settings pages (even if this is only for volume controls)
- A monetisation strategy (e.g. In App Purchases for more puzzles)
- The in-game UI for the monetisation strategy (e.g. icons for IAPs, additional view controllers, etc.)
- An icon, screenshots, descriptions, and other marketing assets
It was originally intended to be a mini-game as part of a larger adventure game and I think I’d like to integrate it into my AdventureKit engine rather than do all the work required to turn it into its own game.
Instead, all of the source code as well as the assets for the two basic puzzles used throughout this series is available on GitHub. I’m also well aware that I still have a lot to learn, so I welcome any feedback or questions!