How to Make a Game Like Monster Island Tutorial

Learn how to make a game like Monster Island. By Brian Broom.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Adding the Explosion Animation

To display the explosion image, you’ll need a SKSpriteNode. You may be wondering where the best place is to position the explosion. There are several answers, but one of the simplest is to add it as a child of the beaker.

Add the following to the end of newProjectile():

let cloud = SKSpriteNode(imageNamed: "regularExplosion00")
cloud.name = "cloud"
cloud.setScale(0)
cloud.zPosition = 1
beaker.addChild(cloud)

This creates a new sprite and adds it as a child node of the beaker, meaning the two will move and rotate together. You set the scale to zero to hide the sprite, since the explosion happens later. Setting the zPosition ensures the cloud appears on top of the beaker instead of underneath.

To see the explosion, add the following to tossBeaker(strength:) replacing the explosion added later comment:

if let cloud = beaker.childNode(withName: "cloud") {
  
  // 1
  let fuse = SKAction.wait(forDuration: 4.0)
  let expandCloud = SKAction.scale(to: 3.5, duration: 0.25)
  let contractCloud = SKAction.scale(to: 0, duration: 0.25)
  
  // 2
  let removeBeaker = SKAction.run() {
    beaker.removeFromParent()
  }
  let boom = SKAction.sequence([fuse, expandCloud, contractCloud, removeBeaker])
  
  // 3
  let respawnBeakerDelay = SKAction.wait(forDuration: 1.0)
  let respawnBeaker = SKAction.run() {
    self.newProjectile()
  }
  let reload = SKAction.sequence([respawnBeakerDelay, respawnBeaker])
  
  // 4
  cloud.run(boom) {
    self.run(reload)
  }
}

Taking this step-by-step:

  1. The first three actions wait 4 seconds before making the cloud grow and shrink in the form of an explosion. Feel free to experiment with these values for different effects.
  2. Normally, this would be a simple removeFromParent action, but these actions run on the cloud node. You need to remove the beaker node instead, so you define a runBlock action. Then you combine the actions in a sequence.
  3. These actions give you another beaker to toss at the cats.
  4. Finally, you run the sequence of actions from Step 2 on the cloud node. While you could have the reload actions in the main sequence action, the completion block will come in handy later.

Build and run to see how dangerous zombie goo can be.

Down with the ferocious felines!

Make a game like Monster Island tutorial. boom

Down with the ferocious felines!

Animate the Explosion

You’ve got an explosion now, but it’s pretty basic. To really knock those kitties off their paws, animate the explosion during the expansion and contraction step. The starter project includes images regularExplosion00.png through regularExplosion08.png, and here you’ll add an action that cycles through them.

First, add a property to the top of GameScene.swift:

var explosionTextures = [SKTexture]()

Now add the following to the end of didMove(to:):

for i in 0...8 {
  explosionTextures.append(SKTexture(imageNamed: "regularExplosion0\(i)"))
}

This builds the array of SKTextures for the animation.

Next, find section 2 of the tossBeaker(strength:) method and replace the line let boom = SKAction.sequence... with the following:

let animate = SKAction.animate(with: explosionTextures, timePerFrame: 0.056)

let expandContractCloud = SKAction.sequence([expandCloud, contractCloud])
let animateCloud = SKAction.group([animate, expandContractCloud])

let boom = SKAction.sequence([fuse, animateCloud, removeBeaker])

You calculate the value for timePerFrame by dividing the total duration for the expand and contract animation (0.5 s) by the number of frames (9).

Build and run to see your upgraded explosion.

Your dog would approve. :]

The explosion animation now uses multiple frames.

Your dog would approve. :]

Setting the Explosion Range

When the beaker explodes, you’ll want to know which cat sprites are within range of the explosion. One way to do this is to create a separate SKPhysicsBody for the explosion and use the contact system to be notified when the explosion touches a cat. However, since each SKSpriteNode can only have one physics body attached to it, you’ll create an invisible node for the explosion radius and add it to the beaker as a child.

Add the following to the end of newProjectile():

let explosionRadius = SKSpriteNode(color: UIColor.clear, size: CGSize(width: 200, height: 200))
explosionRadius.name = "explosionRadius"

let explosionRadiusBody = SKPhysicsBody(circleOfRadius: 200)
explosionRadiusBody.mass = 0.01
explosionRadiusBody.pinned = true
explosionRadiusBody.categoryBitMask = PhysicsType.explosionRadius
explosionRadiusBody.collisionBitMask = PhysicsType.none
explosionRadiusBody.contactTestBitMask = PhysicsType.cat

explosionRadius.physicsBody = explosionRadiusBody
beaker.addChild(explosionRadius)

After you create the explosion physics body, you set its mass property to 0.01 to minimize the amount of mass the explosionRadiusBody will add to the beakerBody.

Notice how explosionRadiusBody has none for its collisionBitMask. You don’t want this extra “bubble” around the beaker to collide with anything, because then it would look like the beaker bounced before it hit the object. You do want the contactTestBitMask set to cat, so that the system will recognize those sprites overlapping as a contact.

Modify the if let cloud statement in tossBeaker(strength:) to look like:

if let cloud = beaker.childNode(withName: "cloud"),
   let explosionRadius = beaker.childNode(withName: "explosionRadius") {

Next, add the following to section 2 in tossBeaker(strength:), just after the let animate... line:

let greenColor = SKColor(red: 57.0/255.0, green: 250.0/255.0, blue: 146.0/255.0, alpha: 1.0)
let turnGreen = SKAction.colorize(with: greenColor, colorBlendFactor: 0.7, duration: 0.3)

let zombifyContactedCat = SKAction.run() {
  if let physicsBody = explosionRadius.physicsBody {
    for contactedBody in physicsBody.allContactedBodies() {
      if (physicsBody.contactTestBitMask & contactedBody.categoryBitMask) != 0  ||
        (contactedBody.contactTestBitMask & physicsBody.categoryBitMask) != 0  {
        contactedBody.node?.run(turnGreen)
        contactedBody.categoryBitMask = PhysicsType.zombieCat
      }
    }
  }
}

This action finds all the physics bodies in contact with the explosionRadius body and uses the colorize(with:colorBlendFactor:duration:) action to turn the new zombie cat a putrid green color.

The if statement with contactTestBitMask and categoryBitMask is there because the allContactedBodies() method returns all the SKPhysicsBody objects touching the given body, instead of only the ones that match the contactTestBitMask. This statement filters out the extra physics bodies.

Change the expandContractCloud action to this:

let expandContractCloud = SKAction.sequence([expandCloud, zombifyContactedCat, contractCloud])

Update the collisionBitMask for the beaker in newProjectile() to:

beakerBody.collisionBitMask = PhysicsType.wall | PhysicsType.cat | PhysicsType.zombieCat

Without this change, the beaker would pass through the zombie cats—which would make sense if the zombie goo turned the cats into ghosts, but sadly, it does not.

Build and run. The beaker should explode next to the cat on the right, turning him into a zombie cat. Finally!

Meow – Brains!

Make a game like Monster Island tutorial. One of the cats has been turned into a zombie.

Meow – Brains!

Making Sprites React

Now that the cats know what’s coming, it would be great to show how afraid they are of your beaker of goo. Since you already have a SKPhysicsBody object to tell when the beaker is close to a cat, you can have the contact system notify you when this body touches a cat node. The system does this by calling delegate methods in your class. You’ll set that up next.

First, since you want to change the image texture for the cat sprites when contact starts, then revert it when it ends, it makes sense to store these textures in a property for easy access. Add these properties to the property section at the top of GameScene.swift:

let sleepyTexture = SKTexture(imageNamed: "cat_sleepy")
let scaredTexture = SKTexture(imageNamed: "cat_awake")

To keep your code nice and organized, add a class extension at the bottom of GameScene.swift to conform to the SKPhysicsContactDelegate protocol:

// MARK: - SKPhysicsContactDelegate
extension GameScene: SKPhysicsContactDelegate {
  
  func didBegin(_ contact: SKPhysicsContact) {
    if (contact.bodyA.categoryBitMask == PhysicsType.cat) {
      if let catNode = contact.bodyA.node as? SKSpriteNode {
        catNode.texture = scaredTexture
      }
    }
    
    if (contact.bodyB.categoryBitMask == PhysicsType.cat) {
      if let catNode = contact.bodyB.node as? SKSpriteNode {
        catNode.texture = scaredTexture
      }
    }
  }
  
  func didEnd(_ contact: SKPhysicsContact) {
    if (contact.bodyA.categoryBitMask == PhysicsType.cat) {
      if let catNode = contact.bodyA.node as? SKSpriteNode {
        catNode.texture = sleepyTexture
      }
    }
    
    if (contact.bodyB.categoryBitMask == PhysicsType.cat) {
      if let catNode = contact.bodyB.node as? SKSpriteNode {
        catNode.texture = sleepyTexture
      }
    }
  }
}

Both methods are almost identical, except that the first changes the texture to the afraid cat, and the second sets it back to the sleepy cat. When this method is called, it is passed a SKPhysicsContact object, which contains the two bodies that generated the contact as bodyA and bodyB. One quirk of this system: there is no guarantee which body will be A and which will be B, so it’s helpful to test both. If the categoryBitMask of that body matches the value for cat, you can reassign the texture of that body.

Next, add the following line to the end of didMove(to:):

physicsWorld.contactDelegate = self

This assigns the GameScene class as the delegate for the physics contact system. The system will call didBegin(_:) and didEnd(_:) on the delegate each time the appropriate physics bodies touch or stop touching. The values of categoryBitMask and contactTestBitMask determine which bodies trigger these method calls.

You’ll need to switch from the afraid cat texture back to sleepy cat if the cat turns into a zombie (zombies aren’t afraid anymore, obviously). Update the zombifyContactedCat action in tossBeaker(strength:) to this:

let zombifyContactedCat = SKAction.run() {
  if let physicsBody = explosionRadius.physicsBody {
    for contactedBody in physicsBody.allContactedBodies() {
      if (physicsBody.contactTestBitMask & contactedBody.categoryBitMask) != 0  ||
        (contactedBody.contactTestBitMask & physicsBody.categoryBitMask) != 0  {
        if let catNode = contactedBody.node as? SKSpriteNode {
          catNode.texture = self.sleepyTexture // set texture to sleepy cat
        }
        contactedBody.node?.run(turnGreen)
        contactedBody.categoryBitMask = PhysicsType.zombieCat
      }
    }
  }
}

It’s a simple addition, but having the enemies in a game react to their surroundings, especially their impending doom, can add a lot of fun and anticipation.

Build and run to see the effect.

Oh noes

Cat is afraid because the exploding beaker is near.

Oh noes

Brian Broom

Contributors

Brian Broom

Author

Over 300 content creators. Join our team.