神刀安全网

SpriteKit Game From Scratch, Swift 2.0, part 3

Swift 2.0, Xcode 7.2

Part 3 of the SpriteKit Game tutorial

Introduction

Before I go on with this tutorial I just want to say a few words. The tutorial is getting quite long and for a few good reasons – I am trying not to leave a lot out and I am writing in in a raw format, meaning I am not refining it, rather making it in a way someone might create a game. Basically instead of showing you a huge block of code and explain it, I prefer going back and forth, adding small pieces of code, which is pretty much how coding a game works. There are many ways of accomplishing almost everything, some could be better for specific situations and sometimes there is not much difference.I am trying to give you as much options as possible and open your way for any changes you might want to do on your own.

We will continue along with our SpriteKit tutorial from where we left off inPart2.

What we have at the moment is :

  • creating characters ( hero and enemy )
  • creating a background
  • created some projectiles
  • move characters
  • some protocols for shooting and targetting

To be honest we might end up not using the protocols much, as it will just be a waste of time for a simple game like that, but I’ll figure this out at a later time.

The Plan

What I have in mind to accomplish in this Part 3 of the tutorial is :

  • projectiles do damage
  • characters die and re-spawn
  • projectile clean up

Of course we might add things here and there.

A few words about collision detection . If you are not familiar with it – this is basically how in  game or an app you will detect if two objects collide. There are different ways of doing and in fact Apple have their own built in method that you could use. However  I’ve found out that since Swift 2.0 it’s been kinda buggy, especially when used in more complex games, where you could have a lot of object colliding at the same time. So rather than using the built in method we’ll create our own, but I will show you Apple’s built in method as well, just so you get familiar with it.

So let’s go over the idea of collision and object clean up, which is pretty standard for any game.

Apple’s built in contact delegate is pretty neat ( if it was working all the time SpriteKit Game From Scratch, Swift 2.0, part 3

) – they have built in functions like didBeginContact, which ( once your scene conforms to the contact delegate ) will be called 60 times per second ( or as many times as FPS you have ) . It will check if any physicsBodies contact and then depending on the bitMasks( more about that later ) you can define different actions. If you set up your own contact and collision detection you have to run a function( again as many times as FPS you have ) that will iterate over all objects of some sort, calculate distances and then if the distance is less than some threshold – do something.

Whenever you want to iterate over some objects you would usually put those in an array or in a separate layer.

So let’s say you want to check if any of the bullets is hitting the Hero – we’ll iterate ( enumerate ) through all the children in a layer, calculate the distances of each child ( bullet ) to the Hero and then do something if any of them has a distance, let’s say less some value. It’s a bit more work and it’s a bit more heavy on resources, but you have a lot more fine control over it.

Contacts and Collisions, Apple’s way

So first thing first – you need to make sure that the objects you want to collide have a

physicsBody.

If you go back to the hero.swift and enemy.swift files you will see that we did define a physicsBody for each:

Swift

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0
self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

This is all gonna help us with the collision detection.

Now if you want to use the Apple’s built in method it’s recommended to use something called BitMasks.

Each physicsBody has 3 BitMasks: categoryBitMask , collisionBitMask , contactTestBitMask .

categoryBitMask will define your object, so other objects can refer to it, collisionBitMask will help with physics – for example if you want objects to bounce off of other objects, push them away and so on, contactTestBitMask will help you in executing your own actions upon collisions between object – basically it will be called but it will not invoke the physics.

In other words objects objects that interact with each other, based only on their contactTestBitMask will not collide, bounce or push each other, but a method will be called, in which you could put your own action. I know it sounds a bit complicated and crazy if you’ve never used BitMasks, but no worries, it’s way less complicated than it sounds.

We’ll do some preparation, so we could later use the Apple’s collision methods with the BitMasks.

Inside our global.swift we’ll add a new struct to contain a few masks. Masks need to be UInt32 ( 32 bit numbers ):

Swift

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }
struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

Then we need to go back and add the masks to each of the classes. For our Hero it will be:

Swift

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy
                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

What that means is that the Hero’s category is ‘hero’ and we want the hero to interact with the Enemy’s projectiles – both collisions and contacts( we might not need collision actually… )

Add this for the Enemy:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero
// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

See if you can finish the BitMasks on your own, adding them to the 2 projectiles.

Click to show/hide Solution

One thing we’ll do extra is we’ll set the collision mask on both the Hero and the Enemy’s projectile to bitMasks.noContact , as we don’t want the projectiles bouncing off of the Hero ( at least for now )

In order to be able to detect Contact and Collisions we need to have our scene conform to Apple’s PhysicsContactDelegate , so add the protocol back next to the class declaration like this:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate {
class GameScene: SKScene, SKPhysicsContactDelegate { 

Then right in the didMoveToView method we’ll declare these two:

Swift

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

0

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

1

, first one is to say that we our Scene will be receiving all the events from the delegate and the second line is to define the gravity – for now none.

We are pretty much ready to start using the contact delegate. We need to call the didBeginContact function, which is predefined by Apple.

Please note how much pseudo code I put in my projects – it’s imperative for troubleshooting and I usually remove it completely only after everything has been tested out. A lot of times I even leave the print statements in, I just comment them out.

Swift

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

2

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

3

Run the project and you should see the output in the console showing each of the contacts.

Now the enemy bullets should be going through the Hero.

Now let’s see what we are going to do about removing them, once they hit.

You could just remove them from the didBeginContact, but it might cause problems and it’s not recommended.

Instead we are going to declare an array of SKNodes, add them to it once hit and then clear that array every frame.

So, we’ll declare the array right after the class:

Swift

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

4

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

5

Then we need to add the projectiles to the array in case they hit the Hero.See if you can figure this out on your own.( if not just expand the solution below )

Show Solution

Next we need to create the cleaning up, as we don’t want the bullets that hit something to continue through the target, we want them to disappear.We’ll add a new function to our scene:

Swift

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

6

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

7

Please note that we call the ‘ removeChildrenInArray ‘ on the objectsLayer, because the projectiles are children of it. If you were adding the projectiles to a different Node or to the scene itself you would need to change that.

Then we just need to call it. You should already have the update function in your scene ( if not you can always add it ), so just call the cleaning() in it like this :

Swift

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

8

self.physicsBody = SKPhysicsBody(texture: texture, size: size)         self.physicsBody?.dynamic = false         self.physicsBody?.affectedByGravity = false            // ( physical body stuff )         self.physicsBody?.mass = 1.0 

9

Please be careful of what you put in the update method as it’s been called every frame.

So if you run your game now you should have this:

SpriteKit Game From Scratch, Swift 2.0, part 3

One more thing that we need to do is clean up all the projectiles that go off screen, otherwise your app will keep consuming more and more memory, the FPS will drop and finally the app will crash. While the game is running you’ll notice that the number of the NODES displayed in the bottom right corner never goes beyond 18-20, however that counter only shows what’s visible on the screen. If you want to see all nodes in the scene you can put this line in your update method:

Swift

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

0

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

1

You’ll see that the number of the nodes keeps increasing and that’s not good.Let’s add a way of cleaning those up in our cleaning method:

Swift

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

2

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

3

Basically we go over each item in the objectsLayer that is named ‘projectile’ ( we named them back in our projectile.swift ) and then checking if any of them is off the screen – if so add them to the objectsToRemove array.

Run the project again and you’ll see that the number of the objects now stays down.

Good job! That’s a major performance improvement.

Now let’s have the bullets do some damage to our Hero.

We already have everything we need, we just need to add this line in our didBeginContact method, right after ‘print(“Hero got hit by a projectile !!!”)’ :

Swift

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

4

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

5

We also need to add something to our die() method, located back in our character.swift:

Swift

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

6

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

7

It is a best practice to not actually remove a node from within itself, rather adding it to an array as we did with the projectiles, however I think it will be safe for killing our Hero and also I wanted you to see this way as well.

Run your project and you should see that the Hero disappears after the 10-th hit.

So far so good.

Lets add some labels to display the Health.

Add this right after the scene class declaration, before the didMoveToView :

Swift

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

8

struct bitMasks {          // Bit Masks    static let hero: UInt32 = 0x1 << 0    static let enemy: UInt32 = 0x1 << 1    static let projectileHero: UInt32 = 0x1 << 2    static let projectileEnemy: UInt32 = 0x1 << 3    static let noContact: UInt32 = 0x1 << 4 }

9

Now we’ll create a few SKColors for later use, just add them to your global.swift:

Swift

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

0

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

1

Next we want to set up the labels in a separate function, so we can call it again, whenever we want to reset the game.Create this new function at the bottom of our scene:

Swift

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

2

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

3

Now we need to update the Hero’s HP after each hit, add this line right after the ‘ spawnedHero.takeDamage(1) ‘ in the didBeginContact :

Swift

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

4

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

5

Run your project and you should see the labels and the Hero label updating properly.

We also need to add a way to restart the scene and re-spawn our Hero after it dies.

You can create a button, using a SKNode or SKSpriteNode , for now I am just gonna use a SKLabelNode , so let’s add another one to the scene:

Swift

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

6

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

7

Then add it’s set up in the setupLabels method:

Swift

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

8

struct bitMasks {          // Bit Masks   static let hero: UInt32 = 0x1 << 0   static let enemy: UInt32 = 0x1 << 1   static let projectileHero: UInt32 = 0x1 << 2   static let projectileEnemy: UInt32 = 0x1 << 3   static let noContact: UInt32 = 0x1 << 4 } 

9

And you can see that I am not adding it to the objectsLayer just yet, as we won’t need that visible for now.

We’ll add this line in the didBeginContact , right after we change the text for our Hero, when our Hero is dead:

Swift

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

0

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

1

That will show the label, once our Hero dies.Now we have to add our restart method:

Swift

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

2

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

3

Now we need to tie up the Label to the action. In SpriteKit there is no native way of creating buttons, however you can detect if there is a tap/touch on any object, so we’ll use that.

Find the built in touchesBegan method ( if missing just create it ) and change it to this:

Swift

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

4

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

5

Start your project and after your Hero dies you should be able to restart it, tapping on the RESTART label.

SpriteKit Game From Scratch, Swift 2.0, part 3

Now we need a way for our Hero to shoot back.

Currently we have him shoot one bullet upon spawning.

However we need to be able to repeat that action somehow.

I want to create a way for the user to shoot, but let’s say every 2 seconds only, so we need to create a button, that after use get’s disabled for 2 seconds.

First let’s create a new SKLabel in our scene:

Swift

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

6

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

7

Then add this to our setupLabels() method, so we can set the new label up:

Swift

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

8

// COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy

9

Can you link the new Label to the shooting action of our Hero ?Try and figure it out, if not – check the solution below.

Show/Hide Solution

Try running your project and you should be able to shoot, using the new Button/Label.

However, there are a few problems. You can shoot too quickly, bullets are bouncing off of the Enemy and they are not passing through. Let’s deal with the first problem.

There are multiple ways of solving this problem, but here is how we are going to do this – we’ll create a Boolean flag called ‘ shootingAllowed ‘ and then set it to false after each shooting action, along with that we’ll start a delayed action, that will set it to true after 2 seconds.

Inside the the touchesBegan we’ll slightly change our latest method as well.

Let’s add the Boolean first, at the top of the scene:

Swift

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

0

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

1

Add this line

Swift

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

2

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

3

to our restartGame() method, just to make sure you can shoot when you restart the game.

We’ll need to add the delayed function, I have this ready, so you can just add this to the scene:

Swift

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

4

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

5

You’ll see how to use that function in a second.

Go back to the touchesBegan method and change our shooting action to this:

Swift

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

6

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

7

Basically we are saying – when the SHOOT label is tapped AND if shootingAllowed is true -> then shoot, forbid the shooting and re-allow it in 2 secs.

We are also changing the color of the button, so it’s visually obvious if you can shoot or not.

Make sure to change the collision mask of Hero’s bullet:

Swift

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

8

                                                        // COLLISION STUFF         self.physicsBody?.categoryBitMask = bitMasks.hero          self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy         self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy 

9

, so the bullets can pass through the Enemy freely.Ok, so far so good, we got to making our bullets hurt the enemy.

Contacts , a custom way

So, let’s see about this, we can make it from the point view of the bullets, the Enemy or the scene.

For our little project it doesn’t really matter, but I would like to make it from the point of the scene.

I found that we need to change the Hero’s shooting method a bit to look like this:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

0

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

1

We are pretty much just mirroring the way the Enemy bullets are set up.

However it’s going to be hard to filter for the Hero bullets from the enemy Bullets, since they are both named “ projectile “.

Let’s rename them, add an extra line after the super.init() in each subclass, for each projectile, enemy:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

2

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

3

and hero:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

4

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

5

However we already used the name “ projectile ” in our cleanup method.

We’ll split our method, as we need to now search for 2 differently named Nodes:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

6

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

7

We’ll use the second enumerate action in our Enemy, but we’ll just wrap it inside an action, so we can repeat it over and over again.We’ll add one more function to make our lives easier, just add it before the class declaration of our scene, right after the import:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

8

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero

9

This will take two CGPoints and calculate the distance between them.

We placed it outside the class, so we can use it from within different classes and for our little project that will work like a charm.

Here is our method for checking if the enemy got hit:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

0

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

1

If you have never used actions it might look a bit overwhelming, but it’s quite simple.

If you examine how the Actions are nested you’ll figure it out pretty easily. The checkAction is what’s interesting, as we do all the calculations there. We’ll check our objectsLayer for any Nodes, named projectileHero, then calculate the distance to the Hero. I decided that for me the threshold will be 100, but you can adjust that. If the distance is < than that – we are having the Enemy take 4 damage and changing the labels appropriately.

Now just call the method at the bottom of the didMoveToView :

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

2

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

3

I also noticed that we were setting the Health labels statically, so the Enemy Health was showing to be starting at 10, even though the enemy had 16hp. Let’s change that in our setupLabels methods, change the lines to be like that:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

4

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

5

and

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

6

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

7

One more thing – we need to disable the SHOOT button, once our Hero is dead.

Generally is good to have a Boolean flag for GameOver , so we’ll just create one right before the class declaration of the scene, as we need to be able to access it from the scene and from the character class:

Swift

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

8

// COLLISION STUFF                  self.physicsBody?.categoryBitMask = bitMasks.enemy  // ship         self.physicsBody?.collisionBitMask = bitMasks.projectileHero         self.physicsBody?.contactTestBitMask = bitMasks.projectileHero 

9

Then set it to true in the die() method in the character.swift:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate {

0

class GameScene: SKScene, SKPhysicsContactDelegate {

1

and then set it back to false in the restartGame() method:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate {

2

class GameScene: SKScene, SKPhysicsContactDelegate {

3

Then in our shooting action in touchesBegan , let’s check if the game is over as well:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate {

4

class GameScene: SKScene, SKPhysicsContactDelegate {

5

An easier way would’ve been to just remove the SHOOT label like this ( you can still do that as well ), just add the removing line to the top of our checkAction ( as this one is repeating every .5 seconds ):

Swift

class GameScene: SKScene, SKPhysicsContactDelegate {

6

class GameScene: SKScene, SKPhysicsContactDelegate {

7

There is even a better way of doing this. We are already setting the gameOver = true in our die() method, regardless of which character dies, so we could just re-work our whole method to check if the game is over:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate {

8

class GameScene: SKScene, SKPhysicsContactDelegate {

9

I am also changing the shooting delay for the Enemy, to make it a bit more interesting and give myself a chance at winning SpriteKit Game From Scratch, Swift 2.0, part 3 – feel free to play around with this:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate { 

0

class GameScene: SKScene, SKPhysicsContactDelegate { 

1

Last thing we are going to do is make a little change to our touchesBegan method, we need to make sure that you can restart the game only if the game is over, so we’ll change this line:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate { 

2

class GameScene: SKScene, SKPhysicsContactDelegate { 

3

to:

Swift

class GameScene: SKScene, SKPhysicsContactDelegate { 

4

class GameScene: SKScene, SKPhysicsContactDelegate { 

5

This will conclude part 3 of this tutorial.

If you need to review, please go back topart 1 orpart 2.

Here is how your final project should look like:

SpriteKit Game From Scratch, Swift 2.0, part 3

Here you can find all the files on GitHub ( created a new branch for part 3 ):

SpriteKit Game from Scratch, part 3, GitHub

Any questions or comments – please comment below. Cheers !

原文  http://helpmecodeswift.com/sprite-kit/spritekit-game-from-scratch-swift-2-0-part-3

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » SpriteKit Game From Scratch, Swift 2.0, part 3

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮