SpriteKit: The Runner Chronicles

Part 2: Accessing individual block instances

Okay, hi again! I had to cut the blog post. It was getting a little too long for me. I was about to go all out but decided against it because this part is long.

What I wanted

Alright. Second bug. The second bug came about when I wanted to check if the player has successfully passed a green block. Or, you can think of it the other way around since the block is the one moving towards the player.

So how do we do this?

The basic requirement for me is that the block needs to be at least half way through the player for the score to increment by 1. That’s just a personal want.

We can check the block’s coordinate when it passes by the player. So the thing that comes straight to mind is to check it in the update method.

How game mechanisms are usually set up

As in every game development framework, the methods include event handling, updating and rendering. All encapsulated in the run method which is a while loop.

It literally goes:

  • user clicks on a location on screen (any mouse clicks or keyboard input is an event)
  • update to that location so player will go there
  • render everything (player starts to move and starts being displayed as moving)

In the update method it literally says

override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}

This is the basic update method that comes with SpriteKit. So, we know it’s the right method.

Turning thoughts into code

The first thing that came to mind was I needed to have an instance of the block so I can access its position.

This was what I had in mind.

if block.position.x < (self.player.position.x + (self.player.size.width / 2)) {
// block passed through half of the player
}

Pretty simple.

One thing I’d like to point out. When working with coordinate systems, it’s important to train your brain to switch between values and positions.

Those two do not necessarily mean the same things.

Example:

(self.player.position.x + self.player.size.width / 2)

is not

self.player.size.width / 2

It may look obvious but when dealing with a coordinate system, you want the former, not the latter.

Alright, let’s get back.

So the code I had in mind works, only if I have access to the block instance from the update method. The problem is the blocks are locally declared in a spawnBlocks() method in the same source file.

func spawnBlock() {
let block = SKShapeNode.init(rectOf: CGSize.init(width: 100, height: 100))
let n = arc4random_uniform(UInt32(2))
var spawnHeight: CGFloat = 0
if n == 0 {
spawnHeight = 100 / 2
} else if n == 1 {
spawnHeight = self.size.height - (100 / 2)
}
block.position = CGPoint.init(x: self.size.width, y: spawnHeight)
block.fillColor = UIColor.green
block.zPosition = 3

self.addChild(block)

block.physicsBody = SKPhysicsBody.init(rectangleOf: block.frame.size)
block.physicsBody?.categoryBitMask = Collision().Block
block.physicsBody?.contactTestBitMask = Collision().Player
let action = SKAction.move(to: CGPoint.init(x: -100, y: block.position.y), duration: 3)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([action, remove])
block.run(sequence)
}

Solutions I tried

I tried to incorporate the check into the script. See that last sequence variable in the snippet?

It does the action and then it removes the block. But I tried to insert the check in between. Did not turn out so well.

let check = SKAction.run {
if block.position.x < (self.player.position.x + (self.player.size.width / 2)) {
// block passed through half of the player
}
}

I then added this into the sequence. Did not work.

It works

There was one other way but I’m not entirely sure it’d work because it could potentially cause a major performance bottleneck. In layman’s terms, it means it will use up lots of resources and thus, battery life.

So I decided to search Stackoverflow and one user suggested it. I’ve been avoiding it but I’m probably out of options at that moment, so I decided to test my theory out.

Here’s what I had in mind

Every time I create a block, I added it to the scene and I also added it to an array of SKShapeNode . Since that array was declared globally, well, in the file, but definitely not locally, I have access to every block in that array.

So it’s all good.

So how do I get the one that’s going to be checked?

The block that is going to be checked is the first one that I inserted into the array. Following me? 👍🏼

And when that block is done being checked, we remove it from the scene, we remove it from the array and the second block becomes the first.

In a way, we are queueing the blocks. This is the updated sequence.

let action = SKAction.move(to: CGPoint.init(x: -100, y: block.position.y), duration: 3)
let remove = SKAction.removeFromParent()
let removeFromArray = SKAction.run {
self.shapeArr.remove(at: self.shapeArr.startIndex)
}
let sequence = SKAction.sequence([action, remove, removeFromArray])

Alright. I shall break this post into a third part. You can read Part 3 here!