Blog

How to Develop a Multi-User Game for iOS (Part 3)

by on April 18, 2019

Develop an iPhone Game App

This is the final article in a three-part series on building a multi-user iOS game app. In part 2 of this series, we demonstrated the process of player registration, login, and storing in Backendless Database. Now, let’s take a look at counting the score for every player, creating a leaderboard, and how all of the game installations are notified when this information changes.

The source code for the game is available in the author’s personal Github repoYou can read Part 1 of this series here and Part 2 here.

After the player is successfully logged in, the main screen with the “Tap Me” button appears.

Tap Me score tracker diagram

The player’s goal in the game is to tap the button as many times as possible. The player who taps most compared to other players receives a notification with congratulations. The message with the player’s email is sent to the game channel on the backend. If the email in the message is equal to the logged-in player’s email, that player gets a notification. All of this happens instantly in real-time using our real-time messaging system.

Tap Me new high score alert

Other players get an update of the World Record value. This happens using real-time handlers – there is an EventListener for the Player table which tracks the changes in the Person objects. As soon as one of the players sets a new record, information is updated immediately on the device screen.

func fillScores(_ player: Player) {
    self.yourMaxScore.text = String(format: "⭐️ Your max score: %i", self.player.maxScore)
    let queryBuilder = DataQueryBuilder()!
    queryBuilder.setProperties(["Max(maxScore) as maxScore"])
    Backendless.sharedInstance().data.of(Player.ofClass()).find(queryBuilder, response: { result in
        let bestPlayer = (result?.first as! Player)
        self.worldRecordScore = bestPlayer.maxScore
        self.gameMaxScore.text = String(format: "???? World record: %i", self.worldRecordScore)
    }, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
}
    
func addDataEventListeners() {
    Backendless.sharedInstance().data.of(Player.ofClass()).rt.addUpdateListener({ updatedPlayer in
        self.fillScores(updatedPlayer as! Player)
}, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
}
 
func removeDataEventListeners() {
    Backendless.sharedInstance().data.of(Player.ofClass()).rt.removeAllListeners()
}
    
func addMessageListeners() {
    channel = Backendless.sharedInstance().messaging.subscribe("TapMeChannel")
    channel?.addMessageListenerString(String(format: "bestPlayerEmail = '%@''", (player.user?.email)!), response: { message in
        AlertViewController.sharedInstance.showCongratulationsAlert(message!, self)
    }, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
}
    
func removeMessageListeners() {
    channel?.removeAllListeners()
}

Let’s take a look at the fillScores method.

yourMaxScore is a label that represents the maximum number of points scored by the current player. This value changes if the current player sets a new game record. worldRecordScore is a label that shows the maximum points scored in the game overall. The DataQueryBuilder is used to get this data; the player who tapped the highest score is selected among others and this score is shown on the worldRecordScore label.

We stop all subscriptions by calling the removeDataEventListeners and removeMessageListeners the viewWillDisappear method. The viewWillDisappear method is then called.

Each player also has access to the leaderboard where he/she can check all players and their points by clicking the “Players” button. The getPlayers method is used to get the list of the players ordered by maxScore, descending.

func getPlayers() {
    let dataStore = Backendless.sharedInstance().data.of(Player.ofClass())
    let queryBuilder = DataQueryBuilder()!
    queryBuilder.setSortBy(["maxScore DESC", "name"])
    dataStore?.find(queryBuilder, response: { updatedPlayers in
        self.players = updatedPlayers as? [Player]
        self.tableView.reloadData()
    }, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
}

The leaderboard also updates in real-time. If you’re looking through the leaderboard and someone is setting the new record or improves his/her score right at this moment, it will immediately be displayed on the device screen. The addListeners method is used to track the current player’s changes or new players appearing.

Tap Me game leaderboard

Similarly to the game screen, all subscriptions are stopped by calling the viewWillDisappear method.

func addListeners() {
    let eventHandler = Backendless.sharedInstance().data.of(Player.ofClass()).rt
    eventHandler?.addCreateListener({ createdPlayer in
        self.getPlayers()
    }, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
    eventHandler?.addUpdateListener({ updatedPlayer in
        self.getPlayers()
    }, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
}
    
func removeListeners() {
    Backendless.sharedInstance().data.of(Player.ofClass()).rt.removeAllListeners()
}

You can see an example of this behavior in this video. User3, who has zero scores at the beginning, taps 11 times on their first play and moves higher in the players rating. The user then taps 20 times and sets a new world record.

That’s all for this series of “How to develop a multi-user game for iOS” tutorials. Hope you’ve enjoyed it! Happy coding!