Tutorials
No Results

Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial

Intermediate · 1 hour

Developing 2D Games Using SpriteKit and SwiftUI - Part 2

This tutorial teaches you the first steps in how to interact with Algorand Standard Assets (ASA) and perform transactions with the Algorand network in a 2D Game with SpriteKit and SwiftUI using Xcode.
This Part 2 of the game demonstrates how to freeze an asset when the player makes a wrong choice. This is made by freezing a particular account and after that, the asset can no longer be sent anymore to that account.
The objective of this tutorial is how to implement an iOS Game Application using the SpriteKit framework, SwiftUI, Swift-Algorand-Sdk and the PureStake API. This Part 2 we will Freeze an asset on the game.

Requirements

Xcode 12 Developer Tools Download Here
SpriteKit Learn Here
Swift Algorand-Sdk Download Here
PureStake Account Create
TestNet Account See Tutorial
My Algo Algorand Wallet Create
Bubble Game Part1 Download Here

Background

In this Bubble game, the player’s goal is to catch balls in descending order. The game ends if the player doesn’t follow the right sequence or the timer reaches zero.
The main challenge is to catch 15 balls and after that, a reward will be available for the player. The game Freezes the asset when the player touches other fish different from the Seahorse. To Un-freeze, the asset the player needs to clean the trash from the ocean.
This game was developed with Algorand interaction in mind. The player as two possibilities can buy more time sending a transaction to the network or get a reward as a fungible asset.
The objective of this Part 2 of the game is to incorporate the Asset Freeze from Algorand Standard Asset (ASA). To be able to test this function other elements will be added to the game creating more challenges and difficulty.

The PureStake API Service https://developer.purestake.io/
About the PureStake API: https://www.purestake.com/technology/algorand-api/
Code Samples: https://developer.purestake.io/code-samples
API Examples: https://github.com/PureStake/api-examples

Steps

1. Game Overview

If you have not downloaded the starter solution from Part 1 please do so, Part 2 builds on Part 1. Code from Part1 Download Here.
To be able for testing this functionality in this Part 2 of the tutorial was added more elements to the game.
The objective stays the same, the player needs to reach the 15 balls to unlock the Asset. But now if he touches the new fish that doesn’t belong to the sequence, the asset gets freeze and can’t be sent to or from that account.
To “Un-freeze” the asset the player needs to collect the trash from the ocean so they can get the asset again.

EditorImages/2021/04/22 11:37/assetFreeze2x.png

EditorImages/2021/04/22 11:37/trash2x.png

2. Game Scene

Game Scene, all the parameters that need to freeze and unfreeze will be developed in this SKView.
The default configuration values for PureStake´s API are set in this class.

   var ALGOD_API_ADDR="https://testnet-algorand.api.purestake.io/ps2"
        var ALGOD_API_TOKEN="GGs——————————————————"
        var ALGOD_API_PORT=""

EditorImages/2021/05/10 22:44/2gamescene.png
Figure 2-1 Replace value for ALGOD_API_TOKEN

Asset
Copy all the parameters from the “ContentView.swift“ and add in the “gameScene.swift”.

 @State var teste:String = "Hello"
        @State var resultadoLda:Float = 0
        @State var assetTotal:Int64 = 10000
        @State var assetDecimals:Int64 = 0
        @State var assetUnitName = "HR"
        @State var assetName = "Hero"
        @State var url = "https://www.3dlifestudio.com"
        @State var defaultFrozen = false

EditorImages/2021/05/10 22:45/2_0gamescene.jpg
Figure 2-2 Add Asset

New characters and other key players have been added to the game. Later you can check on the full project available on Github.
In the GameScene new sprite nodes “SKSpriteNode” like the following code add.

 let barrel: SKSpriteNode = SKSpriteNode(imageNamed: "barrel")
    let vase: SKSpriteNode = SKSpriteNode(imageNamed: "vase")
    let vase2: SKSpriteNode = SKSpriteNode(imageNamed: "vase2")
    let meat: SKSpriteNode = SKSpriteNode(imageNamed: "meat")
    let playerTang: SKSpriteNode = SKSpriteNode(imageNamed: "tang")
    var freezeMessage: SKSpriteNode = SKSpriteNode(imageNamed: "assetFreeze")
    let unFreezeMessage: SKSpriteNode = SKSpriteNode(imageNamed: "assetUnFreeze")
    let trashMessage: SKSpriteNode = SKSpriteNode(imageNamed: "trash")

EditorImages/2021/05/10 22:47/2_1gamescene.png

Figure 2-3 New elements added

Tang fish
EditorImages/2021/04/22 11:41/tang3x.png

Create Sprite Nodes after the GameScene Class.

let playerTang: SKSpriteNode = SKSpriteNode(imageNamed: “tang")

Create a function createTangFish() and add.

EditorImages/2021/05/10 22:49/2_4gamescene.png

Figure 2-4 Function Tang fish

Tang fish code

 func createTangFish() {

        let randomX = CGFloat.random(in: -100...100)
        let randomY = CGFloat.random(in: -300...5)

        let mainTangNumber:SKLabelNode = SKLabelNode(fontNamed: "Arial")

        mainTangNumber.fontSize = 28
        mainTangNumber.verticalAlignmentMode = .center
        mainTangNumber.fontColor = SKColor.black
        mainTangNumber.text = String(number)
        mainTangNumber.zPosition = 3

        playerTang.position = CGPoint(x: randomX, y: randomY)
        playerTang.zPosition = 4
        playerTang.name = "tang"

        playerTang.addChild(mainTangNumber)

        self.addChild(playerTang)


    }

Ocean Trash

Create Sprite Nodes after the GameScene Class.

    let barrel: SKSpriteNode = SKSpriteNode(imageNamed: "barrel")
    let vase: SKSpriteNode = SKSpriteNode(imageNamed: "vase")
    let vase2: SKSpriteNode = SKSpriteNode(imageNamed: "vase2")
    let meat: SKSpriteNode = SKSpriteNode(imageNamed: “meat")

Create a function createTrash() and add.
EditorImages/2021/05/10 22:49/2_3gamescene.png

Figure 2-5 Function Trash

Ocean Trash code

 func createTrash() {

        barrel.position = CGPoint(x: -150, y: -350)
        barrel.zPosition = 4
        barrel.name = "barrel"
        self.addChild(barrel)

        vase.position = CGPoint(x: -50, y: -350)
        vase.zPosition = 4
        vase.name = "vase"
        self.addChild(vase)

        vase2.position = CGPoint(x: 50, y: -350)
        vase2.zPosition = 4
        vase2.name = "vase2"
        self.addChild(vase2)

        meat.position = CGPoint(x: 150, y: -350)
        meat.zPosition = 4
        meat.name = "meat"
        self.addChild(meat)

    }

Animations

When a Freeze occurs the function “showFreezeMessage()” will show an info-panel on the screen alerting the player that the asset was Freeze.
Create a function showFreezeMessage() and add.

 func showFreezeMessage() {


        freezeMessage.position = CGPoint(x: frame.midX, y: frame.midY - 800)

        freezeMessage.zPosition = 8

        self.addChild(freezeMessage)
        freezeMessage.name = "freezeMessage"


        let actionFreezeMoveUp = SKAction.move(to: CGPoint(x: frame.midX, y: frame.midY - 100), duration: 0.7)
        // 2
        let actionWait = SKAction.wait(forDuration: 2.6)

        let actionFreezeMovedown = SKAction.move(to: CGPoint(x: frame.midX, y: frame.midY - 800), duration: 0.7)
        // 3
        let actionRemoveFreezeMovedown = SKAction.removeFromParent()
        // 4
        let sequence = SKAction.sequence([actionFreezeMoveUp, actionWait, actionFreezeMovedown, actionRemoveFreezeMovedown])
        // 5
        freezeMessage.run(sequence)



        trashMessage.position = CGPoint(x: frame.midX, y: frame.midY - 800)

        trashMessage.zPosition = 8

        self.addChild(trashMessage)
        trashMessage.name = "trashMessage"


        let actiontrashMoveUp = SKAction.move(to: CGPoint(x: frame.midX, y: frame.midY - 200), duration: 1.0)
        // 2
        let actiontrashWait = SKAction.wait(forDuration: 2.0)

        let actiontrashMovedown = SKAction.move(to: CGPoint(x: frame.midX, y: frame.midY - 800), duration: 1.0)
        // 3
        let actionRemovetrash = SKAction.removeFromParent()
        // 4
        let sequenceTrash = SKAction.sequence([actiontrashMoveUp, actiontrashWait, actiontrashMovedown, actionRemovetrash])
        // 5
        trashMessage.run(sequenceTrash)


    }

EditorImages/2021/05/10 22:55/2_5gamescene.png
Figure 2-6 Asset Freeze

When an Un-Freeze occurs the function “showUnFreezeMessage()” will show an info-panel on the screen alerting the player that the asset was Un-Freeze and the player can now get the asset again.
Create a function showUnFreezeMessage() and add.

func showUnFreezeMessage() {


        unFreezeMessage.position = CGPoint(x: frame.midX, y: frame.midY - 800)

        unFreezeMessage.zPosition = 8

        self.addChild(unFreezeMessage)
        unFreezeMessage.name = "unFreezeMessage"


        let actionUnFreezeMoveUp = SKAction.move(to: CGPoint(x: frame.midX, y: frame.midY - 100), duration: 0.7)
        // 2
        let actionWaitUnFreeze = SKAction.wait(forDuration: 2.0)

        let actionUnFreezeMovedown = SKAction.move(to: CGPoint(x: frame.midX, y: frame.midY - 800), duration: 0.7)
        // 3
        let actionRemoveUnFreezeMovedown = SKAction.removeFromParent()
        // 4
        let sequenceUnFreeze = SKAction.sequence([actionUnFreezeMoveUp, actionWaitUnFreeze, actionUnFreezeMovedown, actionRemoveUnFreezeMovedown])
        // 5
        unFreezeMessage.run(sequenceUnFreeze)


    }

EditorImages/2021/05/10 22:56/2_6gamescene.png
Figure 2-7 Asset Un-Freeze

Sounds
The next step is to add the new sounds for the new characters.
Add this Sprite Nodes after the GameScene Class.

private let playErrorSound = SKAction.playSoundFileNamed("Error.wav",
    waitForCompletion: false)
    private let playTrashSound = SKAction.playSoundFileNamed("trash.wav",
    waitForCompletion: false)

EditorImages/2021/05/10 23:02/2_8gamescene.png

Figure 2-8 Create Sounds

Create the following functions.

func playError() {
    let removeFromParent = SKAction.removeFromParent()
    let actionErrorGroup = SKAction.group([playErrorSound, removeFromParent])
    self.run(actionErrorGroup)
    }

    func playTrash() {
    let removeFromParent = SKAction.removeFromParent()
    let actionTrashGroup = SKAction.group([playTrashSound, removeFromParent])
    self.run(actionTrashGroup)
    }

EditorImages/2021/05/10 23:01/2_7gamescene.png

Figure 2-9 Add Sounds

3. Create the Freeze

Now you need to create the function that freezes the asset when called. The initial state value is set to “true”.

var freezeAsaState = true

The freeze function will freeze the account define as in the following code.
EditorImages/2021/05/10 23:08/3freeze.png
Figure 3-1 Mnemonic1 and Mnemonic3

   func freezeASA(){

        let algodClient=AlgodClient(host: ALGOD_API_ADDR, port: ALGOD_API_PORT, token: ALGOD_API_TOKEN)
        algodClient.set(key: "X-API-KeY")

        do {

            let account1 = try Account(defaults.string(forKey: "mnemonic1") ?? "")

            let senderAddress1 = account1.getAddress()

            let account3 = try Account( defaults.string(forKey: "mnemonic3") ?? "")

            let senderAddress3 = account3.getAddress()

            algodClient.transactionParams().execute(){paramResponse in
                if(!(paramResponse.isSuccessful)){
                    print(paramResponse.errorDescription!);
                    print("passou")
                    return;
                }

                let tx = Transaction.assetFreezeTransactionBuilder()

                    .setSender(senderAddress1)
                    .freezeTarget(freezeTarget:senderAddress3)
                    .freezeState(freezeState: self.freezeAsaState)
                    .assetIndex(assetIndex: Int64((self.defaults.integer(forKey: "AssetId"))))
                    .suggestedParams(params:paramResponse.data!).build();



                let signedTrans=account1.signTransaction(tx: tx)
                //
                let encodedTx:[Int8]=CustomEncoder.encodeToMsgPack(signedTrans)
                algodClient.rawTransaction().rawtxn(rawtaxn: encodedTx).execute(){
                    response in
                    if(response.isSuccessful){
                        print("freeze ok!")

                        if  self.freezeAsaState == true {

                            self.showFreezeMessage()

                        } else if  self.freezeAsaState == false {

                            self.showUnFreezeMessage()
                            self.freezeAsaState = true
                        }


                    }else{
                        print("freeze Fail!")
                        //
                    }

                }

            }//paramresponse

        } catch {
            //handle error
            print(error)
        }
        print("algo buy!")

    }

4. Load Asset ID

To run and get the account freeze we need the AssetId created on the game start.
The AssetId was stored and persists across our App as an UserDefaults value.

let defaults:UserDefaults = UserDefaults.standard

Note: This will be shown in Step 9 in the ContentView.
In ContentView when the asset was created, I add a new function to store the AssetID value.

defaults.set( AssetModel.assetIndexName, forKey: “AssetId")

In GameScene the value can now be read as:

.assetIndex(assetIndex: Int64((self.defaults.integer(forKey: "AssetId")))) 

EditorImages/2021/05/10 23:08/4loadAsset.png
Figure 4-1 Asset ID

5. Trigger the Freeze

Trigger the freeze will be call when the “TangFish” is touched,

 let touchTang = atPoint (pos)

        if touchTang.name == "tang"{


            touchTang.removeFromParent()
            freezeASA()
//
        }

6. Freeze the Asset

After the function called “freezeASA()” we can get the game message “freeze” and we can check the account state.

EditorImages/2021/04/22 11:47/frezeanim.png

If the player press the Horse reward button get the following result.

EditorImages/2021/04/22 11:48/freeze.png

7. Un-Freeze the Asset

To Un-freeze the asset the player must collect all the ocean trash. The game checks if the trash has been collect and tries to call the “UnfreezeAsa” function. The function first changes the freeze state value and call the freeze function to unfroze the transaction.

 func UnfreezeASA(){

        if  unfreezeNumber == 4 {

            freezeAsaState = false
            freezeASA()
            unfreezeNumber = 0

            showUnFreezeMessage()
        }


    }

EditorImages/2021/04/22 11:50/unfreezeanim.png

8. Game Over

Game Over, reset all the values from the game. The “Buy Bonus” time was defined as a persist value because the player can make a transaction and get more time to play.

func gameOver(){



        if defaults.object(forKey: "increaseTime") != nil {

            startTime = defaults.integer(forKey: "increaseTime")
            self.timerLabel.text = "\(self.startTime)"
            print(startTime)

        }else  {

            startTime = secondsLeft
            print(startTime)
        }

        showStartButton()
        nrOfBubbles = 5
        number = 0
        bubblesCatch = 0
        bubbleLabel.text = "\(bubblesCatch)"
        timerLabel.text =  "\(startTime)"
        groups.removeAll()
        for enemy in arrplayer {
            enemy.removeFromParent()

        }

        tangIsShowing = false
         barrel.removeFromParent()
         vase.removeFromParent()
         vase2.removeFromParent()
         meat.removeFromParent()
         playerTang.removeFromParent()

    }

9. ContentView

All of the methods in step are in the ContentView.swift file.
The asset ID value when created now persists on the game. Open the “ContentView.swift” file and the following code on the function waitForTransaction().

 func waitForTransaction(txId:String) {

       let algodClient=AlgodClient(host: ALGOD_API_ADDR, port: ALGOD_API_PORT, token: ALGOD_API_TOKEN)
           algodClient.set(key: "X-API-KeY")


       var confirmedRound: Int64?=0
       var assetIndex:Int64?=0
       algodClient.pendingTransactionInformation(txId:txId).execute(){
           pendingTransactionResponse in
               if(pendingTransactionResponse.isSuccessful){
                   confirmedRound=pendingTransactionResponse.data!.confirmedRound
                   assetIndex=pendingTransactionResponse.data!.assetIndex
                   print(assetIndex as Any)
                   print("pendingTransactionResponse")
                   AssetModel.add(assetID: assetIndex)
                   assetIndex1 = assetIndex
                   if(confirmedRound != nil && confirmedRound! > 0){

                       print("confirmedRound")

                    defaults.set( assetIndex1, forKey: "AssetId")

                    print(self.defaults.integer(forKey: "AssetId"))

                    storeAssetID()

                   }else{
                       self.waitForTransaction(txId: txId)

                       print("fwait")
                   }
               }else{
                   print(pendingTransactionResponse.errorDescription!)

                   confirmedRound=12000;
               }
   }
}

EditorImages/2021/05/10 23:30/content1.png
Figure 9-1 Set defaults
Create a function storeAssetID() and add.

    func storeAssetID() {

        defaults.set( AssetModel.assetIndexName, forKey: "AssetId")

    }

EditorImages/2021/05/10 23:30/content2.png

Figure 9-2 Store defaults

10. Execute and Check

The freeze and Un-freeze messages

The game shows the freeze and Un-freeze messages.
The state value define the message called. For testing proposes only when the transaction succeeds the game sprites message are showed.

         if(response.isSuccessful){
                        print("freeze ok!")

                        if  self.freezeAsaState == true {

                            self.showFreezeMessage()

                        } else if  self.freezeAsaState == false {

                            self.showUnFreezeMessage()
                        }



                        //
                    }else{
                        print("freeze Fail!")
                        //
                    }

                }

EditorImages/2021/05/10 23:10/failtransaction.png

11. Github Project

Go to the following link and try the full project.
https://github.com/3dlifee/bubbleSwiftuiPart2.git

May 20, 2021