Blog

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

by on March 19, 2019

For this series, we are developing an iOS game called “TapMe”. As TapMe is a multiplayer game, it provides registration for the new users and login for the existing ones. In this article, we are going to demonstrate how to handle user registration and login, as well as how to store a player’s information in your Backendless database.

The source code for the game is available in the author’s personal Github repo: https://github.com/olgadanylova/TapMe.git

You can read Part 1 of this series here.
Develop an iPhone Game App

First, we need to insert the existing APP_ID and API_KEY values into the AppDelegate.m class; otherwise, when the first-time user tries to sign up or log in, he or she will receive the “KEY before running the app. Closed” error and the app will close.

AlertViewController.swift

func showErrorAlertWithExit(_ target: UIViewController) {
    let alert = UIAlertController(title: "❗️Error", message: "Make sure to configure the app with your APP ID and API KEY before running the app. \nApplication will be closed", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { action in
        exit(0)
    }))
    target.view.endEditing(true)
    target.present(alert, animated: true)
}

Login configuration error

New Player Registration

The registration screen appears after pressing the “Sign up” button on the main screen. The user can set his/her profile picture, name, email address, and password.
Sign in screen
After pressing the “Sign up” button, registration is complete and the user will be automatically signed in to start a game.

During the registration process, a new player based on the BackendlessUser is created. The profile picture is uploaded using the File Service API to the Backendless server and is stored in the tapMeProfileImages folder.

We will use the snippets of code below to complete the new player registration process:

Player.swift

@objcMembers
class Player: NSObject {
    var objectId: String?
    var profileImageUrl: String?
    var name: String?
    var user: BackendlessUser?
    var maxScore: NSInteger = 0
}

The next section of code describes:

  1. Uploading an image to the server,
  2. New BackendlessUser registration (if he/she doesn’t exist),
  3. Creation of the new player based on the BackendlessUser from step 2 with a 1:1 relationship.

RegisterViewController.swift

let data = UIImagePNGRepresentation(cropToBounds(image: profileImageView.image!, width: 256, height: 256) )
let filePathName = String(format: "/tapMeProfileImages/%@.png", emailField.text!)
let emailFieldText = self.emailField.text as NSString?
let passwordFieldText = self.passwordField.text as NSString?
// 1)
Backendless.sharedInstance().file.uploadFile(filePathName, content: data, overwriteIfExist: true, response: { profileImage in
    let queryBuilder = DataQueryBuilder()!
    queryBuilder.setWhereClause((String(format: "email = '%@'", emailFieldText!)))
// 2)
Backendless.sharedInstance().data.of(BackendlessUser.ofClass()).find(queryBuilder, response: { registeredUsers in
        if registeredUsers?.first != nil {
// 3) if BackendlessUser already exists
            self.createNewPlayer(registeredUsers?.first as? BackendlessUser, profileImage, color)
        }
        else {
// 2-3) if BackendlessUser doesn't exist
            let user = BackendlessUser()
            user.email = emailFieldText
            user.password =  passwordFieldText!
            Backendless.sharedInstance().userService.register(user, response: { registeredUser in
                self.createNewPlayer(registeredUser, profileImage, color)
            }, error: { fault in
                AlertViewController.sharedInstance.showErrorAlert(fault!, self)
                self.returnToSignUp(color!)
            })
        }
    }, error: {  fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    })
}, error: { fault in
    AlertViewController.sharedInstance.showErrorAlert(fault!, self)
    self.returnToSignUp(color!)
})
func createNewPlayer(_ registeredUser: BackendlessUser?, _ profileImage: BackendlessFile?, _ color: UIColor?) {
    let newPlayer = Player()
    newPlayer.profileImageUrl = profileImage?.fileURL
    newPlayer.maxScore = 0
    newPlayer.name = self.nameField.text
    Backendless.sharedInstance().data.of(Player.ofClass()).save(newPlayer, response: { player in
        let userId: String = registeredUser!.objectId! as String
        Backendless.sharedInstance().data.of(Player.ofClass()).setRelation("user:Users:1", parentObjectId: (player as! Player).objectId, childObjects: [userId], response: { relationSet in
            DispatchQueue.main.async {
                self.profileImageView.image = UIImage(named: "profileImage.png")
                self.activityIndicator.stopAnimating()
                self.activityIndicator.isHidden = true
                self.performSegue(withIdentifier: "unwindToLoginVC", sender: nil)
                self.nameField.text = ""
                self.emailField.text = ""
               self.passwordField.text = ""
                self.navigationController?.navigationBar.isUserInteractionEnabled = true
                self.navigationController?.navigationBar.tintColor = color
            }
        }, error: { fault in
            AlertViewController.sharedInstance.showErrorAlert(fault!, self)
            self.returnToSignUp(color!)
        })
    }, error: { fault in
        AlertViewController.sharedInstance.showErrorAlert(fault!, self)
        self.returnToSignUp(color!)
    })
}

AlertViewController.swift

func showErrorAlert(_ fault: Fault, _ target: UIViewController) {
    let alert = UIAlertController(title: "❗️Error", message: fault.detail, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil))
    target.view.endEditing(true)
    target.present(alert, animated: true)
}

Some errors could appear while registering a new player. If you encounter any of these errors, you can look up solutions here: https://backendless.com/docs/ios/doc.html#user-registration

Existing Player Login

The player should enter his/her login and password to sign in.
Sign up screen
Some errors could appear while signing in. You can see more detail about those errors here: https://backendless.com/docs/ios/users_login.html#errors

Our “TapMe” app is designed to remember the logged-in player and login will occur automatically after the app restart. If the player signs in on another device, the first device will be logged out and show the login screen when the app restarts.

LoginViewController.swift

private var timer: Timer?
override func viewDidLoad() {
    super.viewDidLoad()
    guard Backendless.sharedInstance().userService.currentUser == nil else {
        if Backendless.sharedInstance().userService.isValidUserToken() {
            timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(startGame), userInfo: nil, repeats: false)
        }
        else {
            Backendless.sharedInstance().userService.logout({
            }, error: { fault in
                if fault?.faultCode == "404" {
                   AlertViewController.sharedInstance.showErrorAlertWithExit(self)
                }
            })
        }
        return
    }
}
@IBAction func startGame() {
    self.performSegue(withIdentifier: "segueToTapMe", sender: nil)
}

These are the steps of the login method:

  1. Check if the player exists,
  2. If the player exists, he/she is logged in and the game starts,
  3. If the player doesn’t exist, a message asking them to register appears.

 

func login(_ userEmail: String, _ userPassword: String) {
    let queryBuilder = DataQueryBuilder()!
    queryBuilder.setWhereClause(String(format: "user.email = '%@'", userEmail))
// 1
    Backendless.sharedInstance().data.of(Player.ofClass()).find(queryBuilder, response: { foundPlayers in
// 2
        if foundPlayers?.first != nil {
            Backendless.sharedInstance().userService.setStayLoggedIn(true)
            Backendless.sharedInstance().userService.login(userEmail, password: userPassword, response: { loggedInUser in
                self.startGame()
            }, error: { fault in
                if fault?.faultCode == "3087" {
                    AlertViewController.sharedInstance.showErrorAlert(Fault(message: fault?.message, detail: "Please confirm your email address so you can login"), self)
                }
                else {
                    AlertViewController.sharedInstance.showErrorAlert(fault!, self)
                }
            })
        }
        else {
            AlertViewController.sharedInstance.showRegisterPlayerAlert(self)
        }
    }, error: { fault in
        if fault?.faultCode == "404" {
            AlertViewController.sharedInstance.showErrorAlertWithExit(self)
        }
        else {
            AlertViewController.sharedInstance.showErrorAlert(fault!, self)
        }
    })
}

AlertViewController.swift

func showRegisterPlayerAlert(_ target: UIViewController) {
    let alert = UIAlertController(title: "???? Create a new player", message: "Please sign up as a new player", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
    target.view.endEditing(true)
    target.present(alert, animated: true)
}

That’s all for now. Next time, I’ll show you how to calculate and display the players on a leaderboard.

Happy building!

Leave a Reply