100 Days of Swift – Day 91


Today I am not in a hurry … I am the hurrying! I have so little time it is causing my head to turn so forgive me if I will skip the report from yesterday’s feeling and try to dive straight into the challenges…

Review for Project 26

Here is what we learnt in the Marble Maze project:

  1. We can run a completion closure when an SKAction finishes. This lets us perform some other work when an action completes. It is worth mentioning here also the other option because the explanation is really important: “Once we create an instance of CMMotionManager we can start reading accelerometer data straight away. FALSE: accelerometer data is only available after we have called startAccelerometerUpdates() on our motion manager.
  2. A physics body’s collisionBitMask determines which other objects it bounces off. By default this is set to “everything”. I try to understand this by reading the property name without the “BitMask” part so, “collision” = … well, you say it, what it will collide with and react. The other part of the question was asking whether categoryBitMask would have been the one, which instead determines what type of thing this physics body is. I will need much more time to get comfortable with this as I feel my synapses and neural cells digging some fresh space in my brainfor this!
  3. The linearDamping property of a physics body lets us control how much friction applies to it. This should be a number between 0 and 1.
  4. Calling fatalError() will always crash our code. This is useful to cater for cases that must never happen. I want to mention again also the other option because, in the video lesson, we were not given a proper explanation about the bitwise OR operator |: the question was “The | and + operators do the same thing” and the explanation was “they are quite different operators: | combines numbers, whereas + adds them. For example, 4 | 3 is 7, but 4 | 4 is 4, because you can only add each number to the mix once”. I think this will be important for us in the coming challenges.
  5. Enum raw values are underlying values that sit behind each case. These can be strings, integers, or something else. We have never met enum raw values other than integers but I’m sure we will, sooner or later. The advantage of integers is that the Swift compiler automatically provides other cases if we write just one of them!
  6. Looping over a string gives us each letter one at a time. Be careful, though: strings don’t let us read individual letters using an integer position.
  7. The components(separatedBy:) method of strings sends back an array of strings. Some strings might be empty depending on what our source data was.
  8. Physics bodies with isDynamic set to false won’t get moved by gravity. Non-dynamic physics bodies are actually faster for SpriteKit to simulate.
  9. Pi radians is equal to 180 degrees. UIKit and SpriteKit both work in radians rather than degrees. (unfortunately…)
  10. All iOS devices have accelerometers. Even the very first iPhone had an accelerometer built in.
  11. #if targetEnvironment(simulator) is a compiler directive that lets us compile code only for the simulator. Any code inside this directive won’t be compiled for real devices – it’s as if it doesn’t exist.
  12. Bitmasks can be combined using |. This operator is called “bitwise or” and it combines two numbers together.

That’s it! Let’s move on to the challenges!

Challenges for project 26

Challenge 1: rewrite the loadLevel() method so that it’s made up of multiple smaller methods. This will make your code easier to read and easier to maintain, or at least it should if you do a good job!

I tried a few different approaches in order to compact the code as much as possible but there were a few things not collaborating too much or that would have not saved me enough lines of code (write a 5-line method to save 3 lines in total is not really a nice idea of refactoring…).

Finally I tried the simplest approach to see if that would have been enough: I created four different methods called, for example loadWall(at position: CGPoint), and moved the creation code inside.

I then saw that circular nodes have many lines in common but some of them are separated by others so I gave up on that side for now.

Coming back at it I created another method called createNode(called nodeName: String, at position: CGPoint) -> SKSpriteNode. Inside I put the node’s initialisation, the node’s naming and the node’s positioning before returning the node. The method is 7 lines long including braces and saves about 12 so we are at a +5 win. Maybe it’s worth it, after all!

Challenge 2: when the player finally makes it to the finish marker, nothing happens. What should happen? Well, that’s down to you now. You could easily design several new levels and have them progress through.

The basic logic here is that we need to be able to support multiple levels in our code based on multiple text files so I created a new property called currentLevel and set it equal to 1. Then, in loadLevel() I modified both guard statements to have \(currentLevel) instead of 1 in the string. Finally, in the playerCollided(with:) method, in the else if node.name == "finish" block, I called this:

currentLevel += 1

For testing purposes I made it so that the player is always created at the same starting point. By now I am having tremendous difficulties to reach the end of level 1 as it is and when I load level 2 it loads something that doesn’t correspond at all with my text file, which sincerely I do not understand.

For future testing purposes I added an acceleration Double property set to 25.0 (half of the previous value) and modified the corresponding entry in the update method. This should allow for an easier gameplay at the beginning.

One of the many, too many things I have to understand is that when you want to load a new level of nodes… the other nodes must go away otherwise they will just overlap and produce crazy results. But to do so we need to have something to remove. You see, when we created the first level we never thought we would have had another one so we need to go back a few steps.

I also profited from making a separate method of the createBackground code, so that any line saved is a line gained (and I did the same for the createScoreLabel one.

For this we need an array to store all component nodes we added to the scene in the level loading process so that they will be easily removable when the level ends. It will be an empty array of SKSpriteNodes by now. Next we need to call levelNodes.append(node) at the end of each of the refactoring methods we had introduced in challenge 1.

Next, when we hit the “finish” node, we need to remove each node from the parent and there is a functional method called .forEach that allows to call a closure on every element of the array. So, calling levelNodes.forEach { $0.removeFromParent() } would just do the trick. Thanks to Rob Baldwin (once more) for suggesting this.

To avoid needless crashes we also create a maxLevel integer variable so that if we hit the finish cup when the current level is equal to the max level, we just go back to level 1. We could also just present a “Game Over” message so that the game ends. It’s a choice.

… well … what can I say … I now tried to build and run the app again and everything is absolutely messed up… the loaded level is not level 1 nor any other … I have no idea what is going on…

I thought it could be an issue with the createNode method but removing it and bringing back the previous code didn’t help. The level loaded is a strange mix which is quite not right…

… ok … something seems not to be right at all with the level-nodes array and the appending part or something like that… Otherwise it would not be possible that everything was fine before and now it is not. … insert few minutes here… found the mistake… too embarrassed to say what it was… let me just say that an if-else statement has not break command word inside it…

Now I will try to get to the end of the level and see if the loading works. Still, every time I build this game I get a purple warning from Core Motion. I wish we were taught how to handle these warnings … but I guess I am getting nervous only because I have so little time today. Learning how to code under times constraints is very unproductive…

… fine, it all works. Phew… Let’s move on to the last one.

Challenge 3: add a new block type, such as a teleport that moves the player from one teleport point to the other. Add a new letter type in loadLevel(), add another collision type to our enum, then see what you can do.

Let’s proceed in order: let’s add a teleport case to the CollisionTypes enum equal to 32, double of the last one, so that they can add together without overlapping.

Inside the loadLevel method I added two extra else if so that the letter “d” would mean departure and “a” would mean arrival, calling respectively the method loadTeleport(at:isDeparture:) with true in the first case and false in the second case.

Down below I wrote the loadTeleport method which is very similar to all the other ones with just a check for it being a departure point or an arrival one and changing the name of the node accordingly. I also added a “bubbly” feeling so that the teleport shrinks down a bit and expands back every half a second and repeats this indefinitely.

At the bottom of the playerCollided(with:) method we create two extra if else cases (I know, a switch would have been more appropriate but I will possibly get there in another run) for “departure” and “arrival”. To avoid crazy stuff we should consider adding a Boolean that checks for the player being in a teleporting state or not otherwise once the player arrives to the new teleport it would just be bounced back. That could be solved with a var isTeleporting = false at the top of the class.

By now I assume there would be only two teleports in the game, no more. So, in the else if block I checked that the teleporting Boolean would be false then, if there would be a node called “arrival” anywhere in the scene, I would set the Boolean to true, temporarily deactivate the player’s physics body’s dynamic to avoid any interaction for a few instants during teleportation, then performe a bunch of actions (move to the center of the teleport, scale down, teleport, scale up, restore dynamic (with a closure)) in a sequence, then switch the names of then nodes so that travel back would be possible without writing another method (which may still be the best way in the long run but …) and then setting the teleporting Boolean back to false after 2 seconds.

I tried it out and everything works. Of course, if you get to the arrival teleport first, it will not teleport you, which from one point of view is undesired, from another it could be a role-play element that will ask the player to look for the good portal. I could improve this with changing the colours of the portals, something like green and red but, apart from really not having time today, I think the scope of this challenge is accomplished.

I admit it, after almost 3 hours of trying and bashing my head I checked Rob’s proposed solution for inspiration and then implemented my own version in the end. I feel that if I had spent about 3 to 4 more hours on this I could have gotten there, which is already great if I think of it but … as today I really do not have that time, I could not. Also, SpriteKit games are not really my forte and as much as I like games, I wish I could learn how to create more advances apps instead of games. So, all the while learning a lot with them, I do not want to kill myself with them.

So, that’s it for today, I need to get back to work!

You can find the finished code for this project here.

Please don’t forget to drop a hello and a thank you to Paul for all his great work (you can find him on Twitter) and be sure to visit the 100 Days Of Swift initiative page. We are learning so much thanks to him and he deserves to know of our gratitude.

He has about 20 great books on Swift, all of which you can check about here.

The 100 Days of Swift initiative is based on the Hacking with Swift book, which you should definitely check out.

If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: