How to Make a Waiting Game Like Farmville with SpriteKit and Swift
In this SpriteKit tutorial, you’ll learn how to make your very own waiting game — just like Farmville — with SpriteKit and Swift. By Kevin Colligan.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Make a Waiting Game Like Farmville with SpriteKit and Swift
30 mins
- Getting Started
- Implementing the State Machine
- Cleaning up the Interface
- Switching States
- Updating States Over Time
- Stocking Your Items
- Selling Items
- Introducing Customers
- Sending User Notifications
- Local vs. Remote Notifications
- Asking for User Permission
- Scheduling Notifications
- Resetting the App Badge
- Where to Go From Here?
Switching States
Add the following method to StockItem.swift:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
switch state {
case .empty:
let bought = gameDelegate.updateMoney(by: -stockingPrice * maxAmount)
if bought {
switchTo(state: .stocking)
} else {
let playSound = SKAction.playSoundFileNamed("hit.wav", waitForCompletion: true)
run(playSound)
let rotateLeft = SKAction.rotate(byAngle: 0.2, duration: 0.1)
let rotateRight = rotateLeft.reversed()
let shakeAction = SKAction.sequence([rotateLeft, rotateRight])
let repeatAction = SKAction.repeat(shakeAction, count: 3)
priceTag.run(repeatAction)
}
case .stocked:
switchTo(state: .selling)
default:
break
}
}
This method operates on the two states that allow user interaction: empty
and stocked
.
When empty
, you first attempt to update the player’s money through gameDelegate
. If the player has enough money to make the purchase, you then call switchTo(state:)
to change the item’s state to stocking
. If the player is short on funds, you let the player know by playing a sound effect and shaking the price tag.
To handle stocked
, you simply call switchTo(state:)
with the selling
state. There are no additional conditions that need to be met for this transition, and this puts the item in a state where it will update over time.
Updating States Over Time
To update an item over time, you’ll first need to know the last time the state changed to calculate how much time has passed and how far along the stocking or selling process should be.
Add the following property to StockItem.swift, right after the state
property:
private var lastStateSwitchTime: CFAbsoluteTime
You’ll use CFAbsoluteTime
to refer to a specific point in time. Even if the player restarts the game you still need to know exactly when that event happened in order to update stock properly.
Add the following line to init(stockItemData:stockItemConfiguration:gameDelegate:)
just before super.init()
, to load the time of the last state change:
lastStateSwitchTime = stockItemData["lastStateSwitchTime"] as AnyObject? as! CFAbsoluteTime
And add the following line to data(), right before the return
statement:
data["lastStateSwitchTime"] = lastStateSwitchTime
This line adds an entry for the last state-switch time to the data
dictionary stored in gamedata.plist.
Now you need to make sure that lastStateSwitchTime
is assigned the proper value while the game is running.
Add the following line of code to the beginning of switchTo(state:)
:
if self.state != state {
lastStateSwitchTime = CFAbsoluteTimeGetCurrent()
}
This ensures that you’ve actually changed states. If so, then update lastStateSwitchTime
to the current time. You can always get the current time using the ever-helpful CFAbsoluteTimeGetCurrent().
Stocking Your Items
You can use the absolute time of the last state-switch to show some progress indicators to your player. Start by updating the countdown that shows the player how long they need to wait for a purchased item to complete stocking.
Add the following method to StockItem.swift:
func updateStockingTimerText() {
let stockingTimeTotal = CFTimeInterval(Float(maxAmount) * stockingSpeed)
let currentTime = CFAbsoluteTimeGetCurrent()
let timePassed = currentTime - lastStateSwitchTime
let stockingTimeLeft = stockingTimeTotal - timePassed
stockingTimer.text = String(format: "%.0f", stockingTimeLeft)
}
In this method, you set the text of the stockingTimer
to the time remaining until stocking is complete. To get this value, you first calculate the amount of time it takes to fully stock the item. You do so by multiplying stockingSpeed
and the maximal amount of the stock item and then cast it to CFTimeInterval
. Next, you store the current time in a temporary variable to calculate how much time has passed since the last state change.
The time to restock the item is now simply the total time minus the time that has passed to this point:
Finally, you set the text to the remaining time, so the user can see when the item will be fully stocked. Since you only want to display whole seconds to your player you use the format specifier %.0f
, which tells Swift to display a float variable with zero digits after the decimal.
Add the following method to StockItem.swift to update the display of the item during stocking and selling:
func update() {
let currentTimeAbsolute = CFAbsoluteTimeGetCurrent()
let timePassed = currentTimeAbsolute - lastStateSwitchTime
switch state {
case .stocking:
updateStockingTimerText()
amount = min(Int(Float(timePassed) / stockingSpeed), maxAmount)
if amount == maxAmount {
switchTo(state: .stocked)
}
default:
break
}
}
First, calculate how much time has passed since the last state-switch. If the item’s current state is stocking
, you call the helper method updateStockingTimerText()
.
Next, you update the item amount which is simply the time elapsed divided by the stocking speed. Of course, the player can never stock more items than maxAmount
, so you use min
to limit the amount to maxAmount
. Finally, you check whether the new amount is equal to maxAmount
. If so, then change the state to stocked
.
The only thing left to do is call update()
for every stock item.
Add the following method override in GameScene.swift at the bottom of GameScene
class as follows:
override func update(_ currentTime: TimeInterval) {
for stockItem in stockItems {
stockItem.update()
}
}
Build and run your project. Tap on a stock item and you’ll see the timer count down to zero. Then the item switches to the stocked
state. That coin in front of the cookies indicates that they are ready to be sold.
Selling Items
As soon as an item is fully stocked, players can start selling it. Add the following code to update()
in StockItem.swift, inside the case
statement right before the default
case:
case .selling:
let previousAmount = amount
amount = maxAmount - min(maxAmount, Int(Float(timePassed) / sellingSpeed))
let amountSold = previousAmount - amount
if amountSold >= 1 {
let _ = gameDelegate.updateMoney(by: sellingPrice * amountSold)
progressBar.setProgress(percentage: Float(amount) / Float(maxAmount))
if amount <= 0 {
switchTo(state: .empty)
}
}
First, you store the current amount of the item in previousAmount
. You then calculate the new amount by subtracting the quotient of timePassed
and sellingSpeed
from maxAmount
. Again, you need to limit the number of items that can be sold to maxAmount
. Now, the number of items sold is simply the difference between the previous amount and the new amount.
In order to limit the number of calls to progressBar
and gameDelegate
, you check whether at least one item has been sold since the last call to update
. If so, notify gameDelegate
about the change in the player's funds, then set the progress bar to the value of the amount sold divided by the maximum amount available. This change in money will always be positive, so you can ignore the result of updateMoney(by:) here.
Finally, you check whether the stock item sold out by comparing the amount remaining to 0. When the item is sold out, set the state back to empty
. Your state machine is now complete.
Build, run and buy some cookies! Click on the coin to start selling. You'll see your cookies sell over time, fattening your wallet: