I was recently playing The Black Mirror and one of the earlier challenges is a jigsaw puzzle where you have to reconstruct a torn up photograph.
The player clicks on a piece to activate it, then they can move it around using the mouse or right-click on it to rotate it to one of four preset rotations. They click again to release it. When a piece has the correct rotation and is near the correct position, it snaps into place, which works as visual feedback that it has been placed correctly.
It was an enjoyable break from the exploring and it got me thinking about how I might add it as an optional mini-game type in my adventure game engine.
The game is also simple enough that I can use it as an opportunity to take an in-depth look at every aspect of its development. I’m hoping that I’ll come to a deeper understanding of the entire process of developing games.
There are still many things that I know how to do and yet I still feel that I don’t fully understand how or why they work.
The rough outline for this series will be:
- Planning (this post)
- Setting up Photoshop for different iOS device sizes and the Mac and understanding document sizes and scaling modes in SpriteKit
- Scripting Photoshop to create the puzzle pieces
- Setting up Xcode with multiple targets (iOS and Mac) and creating the model
- Creating the entities and components
- Developing the player interaction models for Mac and iOS
- Implementing the systems and win conditions
- Wrapping up and final thoughts
These are the initial notes I made (with improved spelling and grammar) about my version after playing The Black Mirror version:
The object of this mini-game is to solve arbitrary jigsaw puzzles by dragging and rotating pieces into the correct position.
The pieces can be any shape or size as the correct placement will be based on the centre point of their frame.
Pieces can overlap.
Like the Black Mirror puzzle, there will be an adjustable tolerance for both distance and rotation and, when a piece is within both these tolerances, it will snap into its final correct position.
The correct rotation will always be 0º.
The piece can only snap once the player stops interacting with it (i.e. it cannot snap while it’s still under their finger).
Unlike the Black Mirror puzzle, the rotation is entirely free and is not limited to the four compass positions as this would feel clunky on a touch device.
The player wins when all of the pieces are in their final position.
In order to make this mini-game reusable, the puzzles will be modelled from a JSON file that is an array of dictionaries. Each dictionary will have the name of the sprite and the final, correct position of that sprite in the puzzle.
The main game scene will load the JSON file, create entities to represent the pieces, and randomly place them in the scene.
While the Entity Component System approach is overkill for a game like this—there’s only one type of entity so I don’t gain any of the benefits of this modular design approach—the components and systems involved are straightforward enough that I can write about them in a lot more depth (unlike, say, character movement and pathfinding).
Each puzzle piece will be an entity, and these entities will have at least the following components:
- Position component
- Rotation component
- Interaction component
- Sprite component
In addition, there will at least be the following systems:
- Render system (to update the position and rotation of the sprite)
- Interaction system (to handle touch/mouse events)
- Position system (to manage the snapping functionality)
After looping through each system, the main game loop will go through each entity and check the position and rotation component. If every one of these entities is in the correct position, then we have met the win condition.
This is by far the clearest plan I’ve had for developing a game, and it feels good to be able to look at something and have a clear idea of how I might go about building it myself.
Let’s see how long that clarity lasts!
Get all of my latest posts direct to your inbox! Pop in your email address here: