Swift: Easy SpriteKit map creation by reading txt files (Tile Map)

Rinaldo Jr
Apple Developer Academy | UFPE
7 min readApr 21, 2023

This is a guide through tutorial, I’ll explain what is a Tile Map, what would you use a Tile Map for? And why SpriteKit? Feel free to skip to the last topic if you only want to see the implementation on code, hope you enjoy!

What is a Tile Map?

A tile map is a technique used in digital games to create levels or environments. It is formed by small images (called “tiles”) that represent objects, terrain, textures, and other elements. These tiles are organized in a grid or mesh, forming a complete map.

Tiles are pre-built pieces that can be reused to quickly create complex environments. Some of the most common tile pieces include earth blocks, walls, doors, and other elements that make up a scenario. It is very popular in digital games (such as Super Mario Bros., The Legend of Zelda: A Link to the Past, and Civilization), as it allows for the quick and easy creation of detailed and complex scenarios. Additionally, the use of tiles does not require great processing power from the computer, since the images are relatively small and easy to load.

One of the main benefits of using tile maps is that they are easily reusable, which simplifies game maintenance and updating. Additionally, the use of tile maps allows for the creation of detailed and complex environments, with a wide variety of elements and objects.

Tile Set and Tile Map from Super Mario Bros.

What would you use a Tile Map for?

Tile maps are often used in platformers, where the environment plays a significant role in the gameplay. For example, in a platformer like Super Mario Bros., the environment includes platforms, obstacles, and enemies, which are all constructed using tiles. By using a tile map, you can create varied and intricate levels that are challenging, engaging, and fun for players to explore.

Tile maps are also used in other types of games, such as role-playing games (RPGs), strategy games, and simulation games. In RPGs, tile maps can be used to create detailed environments, including towns, cities, dungeons, and forests. In strategy games, tile maps can be used to create varied terrain, including mountains, rivers, and valleys. In simulation games, tile maps can be used to create detailed and realistic environments, such as cities or farms.

The use of a tile map can greatly enhance the visual appeal and playability of a game. By using a tile map, you can quickly create intricate environments that are engaging and immersive for players. Additionally, the use of a tile map allows to easily update and maintain the game, as they can quickly swap out tiles or make minor adjustments to the environment as needed.

In summary, a tile map is primarily used for creating levels or environments in digital games. Its versatility makes it a popular choice for game developers across a range of genres, including platformers, RPGs, strategy games, and simulation games. By using a tile map, developers can create detailed and engaging environments that enhance the visual appeal and playability of the game.

Tile Map used for a RPG game (https://www.drivethrurpg.com/)

Why use it in SpriteKit?

One of the main advantages of using tile maps in SpriteKit is that they allows you to create complex environments quickly and efficiently. Since SpriteKit games are typically 2D, tile maps are ideal for building the levels and environments that the player will navigate through. With a tile map, you can piece together pre-built images to form the game environment, without having to create each piece from scratch. This can save a significant amount of time and effort, allowing developers to focus on other aspects of game development and in addition, tile maps can greatly improve the performance of a SpriteKit game. By using your pre-built images, the engine can render the environment more efficiently, without the need to load individual images for each element of the environment. This can result in faster load times and smoother gameplay, which can greatly enhance the player’s experience.

Another advantage of using tile maps in SpriteKit is that they allow for easy updates and maintenance. If you needs make changes to your game environment, such as adding new elements or adjusting the layout, you can simply modify the tile map, rather than having to make changes to all of the individual images or code. This can simplify the development process and reduce the risk of introducing bugs or errors.

Overall, the use of tile maps in SpriteKit can greatly benefit game development by simplifying the process of creating complex environments, improving game performance, and simplifying updates and maintenance. If you’re developing a 2D game in SpriteKit, using a tile map is definitely worth considering.

Map made following a Hacking with Swift Tutorial

Implementation

For last but not least lets implement a algorithm that reads the txt archive and creates the map in SpriteKit.

1. Create the txt archive

Create a file in the xcode and change the extension to .txt

2. Reference and read the file in the game scene

if let levelPath = Bundle.main.path(forResource: "archiveName", ofType: "txt") {
if let levelString = try? String(contentsOfFile: levelPath) {
let lines = levelString.components(separatedBy: "\n")
for (row, line) in lines.reversed().enumerated() {
for (column, letter) in line.enumerated() {

}
}
}
}

This way you can identify the exactly what we are reading in the text letter by letter.

Hopefully you can see were its going, doing like this now we only need to set a size and position were the sprite will be and identify which node will be placed for each letter that is read.

3. How to know the correct size and position?

It is very easy to know the correct size for the node. The first thing you may consider is the size of the scene and second how many letters or nodes you want to show for each column as well as the rows.

Once you has these values you do some simple math to calculate the correct size and position of the node:

The node size will have the following values:

Node Height = Scene height / Quantity of Rows
Node Width = Scene width / Quantity of Columns

The node position will have the following values:

X = (Node Width * Current Column) + Node Width / 2
Y = (Node Height * Current Rows) + Node Height / 2

Let’s say in our example that the Scene has the size of Height = 1280 and Width = 1920 and our level has 8 Rows and 12 Columns, that means that our node will have the size of Height = 160 (1280/8) and Width = 160 (1920/12), lets build it code ways!

let sceneSize = CGSize(width: 1920, height: 1280)
let numberOfRows = 8.0
let numberOfColumns = 12.0
let nodeHeight = sceneSize.height / numberOfRows
let nodeWidth = sceneSize.width / numberOfColumns

if let levelPath = Bundle.main.path(forResource: "archiveName", ofType: "txt") {
if let levelString = try? String(contentsOfFile: levelPath) {
let lines = levelString.components(separatedBy: "\n")
for (row, line) in lines.reversed().enumerated() {
for (column, letter) in line.enumerated() {

let position = CGPoint(x: (nodeWidth * Double(column)) + nodeWidth/2, y: (nodeHeight * Double(row)) + nodeWidth/2)

}
}
}
}

4. Adding node

To add the node you'll need to understand how the build works, it starts from the bottom left up, reading letter by letter (even the empty spaces) so be careful because the nodes higher on the list will be positioned in front of the nodes created before.

Let's say we want to create a platform game and the size of the Scene will be Height = 640 and Width = 960 and our level has 4 Rows and 6 Columns, we want blocks on the bottom and a platform on the middle of the scene.

Our Level.txt is:

The code is:

let sceneSize = CGSize(width: 960, height: 640)
let numberOfRows = 4.0
let numberOfColumns = 6.0
let nodeHeight = sceneSize.height / numberOfRows
let nodeWidth = sceneSize.width / numberOfColumns

if let levelPath = Bundle.main.path(forResource: "archiveName", ofType: "txt") {
if let levelString = try? String(contentsOfFile: levelPath) {
let lines = levelString.components(separatedBy: "\n")
for (row, line) in lines.reversed().enumerated() {
for (column, letter) in line.enumerated() {

let position = CGPoint(x: (nodeWidth * Double(column)) + nodeWidth/2, y: (nodeHeight * Double(row)) + nodeWidth/2)

if letter == "x" {
let node = SKSpriteNode(imageNamed: "block")
node.size = CGSize(width: blockSize, height: blockSize)
node.position = position
addChild(node)
}
}
}
}
}

The map will be:

Note that this form of generation is very flexible, adaptable and has immense potential since you can expand it in many ways, creating new ises, nodes, increasing the map, you can do whatever your imagination grants you, the sky is the limit!

Final considerations:

as soon as I watched the hacking with swift video I was stunned by the possibilities of this application, so much so that I created an entire game around this creation of maps, it is really worth trying.
Follow me on Instagram: https://www.instagram.com/rinaldo_sbj/
and good studies!

--

--