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 1

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 simple game shows how to integrate the Algorand network to get more Bonus performing a transaction.
Shows how to create an Algorand Standard Assets (ASA) and Transfer the Asset as a Reward achieve on the game.
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. We will create a transaction and sending it into the network programmatically and Implement a Reward achieve with Algorand Standard Assets (ASA).

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

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.
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 is not to teach how to make a 2D game with SpriteKit, although I am going to explain the main steps that can lead to our main objective.

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
Swift Algorand-Sdk: https://github.com/Jesulonimi21/Swift-Algorand-Sdk.git

Steps

1. Create SwiftUI Project

First let’s start to create your project.
EditorImages/2021/05/05 10:07/1CreatebubbleGame.png
Let’s start with iOS App.
EditorImages/2021/05/05 10:07/2CreateGame.png

2. Add Swift Algorand Sdk Packages

First “l like to thank Jesulonimi21 for the Swift Algorand-Sdk.” Go to the following link and copy the main Branch.
EditorImages/2021/04/20 23:24/3Git.png

Second, add the Swift Algorand-Sdk Package Dependency. Select Branch - main and hit the next button, you’ve successfully added the packages.
EditorImages/2021/05/05 10:10/4packageWhite.png
EditorImages/2021/05/05 10:10/4-1packageWhite.png

3. PureStake API Configuration

PureStake can easily get up and running on the Algorand Network. To know more check this link a walkthrough for using the PureStake API to interact with the Algorand network.

First, need to set the PureStake API address and past your API key on the Token. Please don’t share your API Key.

 `import SwiftUI
import swift_algorand_sdk

struct ContentView: View {
    var ALGOD_API_ADDR="https://testnet-algorand.api.purestake.io/ps2"
    var ALGOD_API_TOKEN=Gs——————————————————“
    var ALGOD_API_PORT=""
` 

Second, need to set the client,

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

Note: Using these code snippets in the app will be shown in Step 11, in the ContentView.

4. SpriteKit Game Overview

Let’s start with an overview of the game and buttons functions. At the top, you can “Create”, “Manager” and “Opt-in” an Algorand Asset. Then you have two buttons to interact width the Game. The “Buy Bonus” button can let you perform a transaction and buy more time for the game. The “Seahorse” button it’s a game Reward, achieved when you collect a specific number of balls. When the “Seahorse” button it’s activated you can transfer your Asset to your account. The “ID” shows the Asset ID when creating an Asset.
Next, the info panels show “Timer” the time left, “Bubbles” the bubbles catch and “Total Bubbles” the number of the balls catch. And the “Play” button to start the game.
EditorImages/2021/05/09 10:11/4_3Overview.png

5. SwiftUI Views

For this project, we have a ContentView, GameScene and the bubbleGame view for the interaction. The ContentView will be a Struct a view protocol, with a body property. This implementation allows swift to render the view on the screen.

The ContentView
This first View is where we can run the full App. Here is where will be adding a SpriteView to present the game and the integration with the Algorand network.
EditorImages/2021/05/05 10:19/5Contentview.png
Figure 5-1 The ContentView
Note: All the code from ContentView Here

The GameScene
This is where all the game logic was developed. Here the game can increase the Bonus Time when a transaction is made. And where the game sends an achieve completed.

EditorImages/2021/05/05 10:20/5Gamescene.png
Figure 5-2 The GameScene
Note: All the code from GameScene Here

The bubbleGame
This is where it’s used the Combine Framework to update all the interactions between the GameScene, Algorand Network and the ContentView.
EditorImages/2021/05/05 10:21/5gbubblegame.png
Figure 5-3 The bubbleGame
Note: All the code from bubbleGame Here

6. Account Configuration

Now you need to create your Wallet and set your mnemonic. Never share your mnemonic secret key. For this tutorial, I use My Algo Wallet as my main wallet for the transaction. You can go to https://wallet.myalgo.com/access and create your account. Use Algorand dispenser to add founds to my wallet.
First your mnemonic1 and mnemonic3 account,

 let mnemonic = "star star star star star star star star star star star star star star star star star star star star star star star star star”

Second your address Key,

Address("KRRRRRRRRRRRRRRRRRRRRRRRRR")

Note: This will be shown in Step 19 in the accounts.swift file.

7. Create GameScene File

The GameScene is where all the game is developed. To start create a new Swift File.
EditorImages/2021/05/05 10:23/7_1gamescene.png
Figure 7-1 Create a new File

EditorImages/2021/05/05 10:23/7_2gamescene.png
Figure 7-2 Choose Swift File

8. GameScene

All of the methods in step are in the GameScene.swift file
Now import the following frameworks,
import Foundation
import Combine
import SwiftUI
import SpriteKit

Declare a GameScene class and add the “didMove(to view: SKView)” method. 
This class have three main components, the “SKView” as the view for a SpriteKit scene, the “SKScene” as the root node where all the properties and methods will be included and the “ObservableObject” where will be passing the state value from the Asset.
Now you can start building your first scene.
EditorImages/2021/05/05 10:27/8gamescene.png
Figure 8-1 Add the GameScene Class

import Foundation
import Combine
import SwiftUI
import SpriteKit

class GameScene: SKScene, ObservableObject {

override func didMove(to view: SKView) {

}
}

Start building the Game and add “@Binding variable”. This variable will be read every time that the value changes on ContentView.

EditorImages/2021/05/05 10:31/8_3gamescene.png
Figure 8-2 Create the Binding variable

class GameScene: SKScene, ObservableObject {

    @Binding var horseAsset: Bool

    init(horseAsset: Binding<Bool>) {
        _horseAsset = horseAsset
        super.init(size: CGSize(width: 400, height: 800))
        self.scaleMode = .fill
      }

      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) is not supported")
      }

Add Image Assets

In the Project Navigator, select the Assets.xcassets (asset catalog) and drag your images from your resources.
Select your images and drag them into the Outline View as in Figure 8-3.
EditorImages/2021/05/05 10:34/8_1gamescene.png
Figure 8-3 Add Assets

Create the Sprite Nodes

Now create your SKSpriteNodes where you can draw your images and animations and SKLabelNodes where you can draw a text label.
These SKNodes provide some properties as .frame, .position and .zPosition.
I will explain how to draw the background to the Scene.
Declare the SKSpriteNode and add the name of the background image added in Asset Catalog, after the GameScene Class.

let background = SKSpriteNode(imageNamed: “bubbleBack")

In the didMove(to:) method, add the following code:

background.position = CGPoint(x: 0, y: 0)
        background.name = "background"
        addChild(background)

Music
The next step is to add Audio to the game scene. For that, we will use SKAudioNode object to load PoolParty.wav file and set autoplayLooped property to true, defining that the sound will immediately play in a loop.

Below the gameScene file add SKAudioNode.

// -------music//
    let musicNode = SKAudioNode(fileNamed: "PoolParty.wav")
    private let playPopSound = SKAction.playSoundFileNamed("pop.mp3",
    waitForCompletion: false)

After the didMove(to:) method add the following code

musicNode.autoplayLooped = true
        musicNode.isPositional = false
        // Add the audio the scene
        addChild(musicNode)

Note: Open the project from GitHub and the drag /gamemusic folder into the Project Navigator to create a new
gamemusic group as in Figure 8-4.

EditorImages/2021/05/09 10:22/musicDrag.png
Figure 8-4 Add Music

GitHub Music folder Here

The Player

This function “createPlayer()” will be called when the view presents the scene. The scene will be populated with the number of bubbles defined in “nrOfBubbles”.

 func createPlayer(){

        for int in 1...nrOfBubbles where int < nrOfBubbles {

            populateStage()
            number += randomCount

        }

    }

    func populateStage() {

        let randomX = CGFloat.random(in: -160...160)
        let randomY = CGFloat.random(in: -370...5)
        let bubbleNumberText:SKLabelNode = SKLabelNode(fontNamed: "Arial")

        bubbleNumberText.fontSize = 40
        bubbleNumberText.verticalAlignmentMode = .center
        bubbleNumberText.fontColor = SKColor.black
        bubbleNumberText.text = String(number)
        bubbleNumberText.zPosition = 3

       let player: SKSpriteNode = SKSpriteNode(imageNamed: "horse")
        player.position = CGPoint(x: randomX, y: randomY)
        player.zPosition = 4
        player.name = String(number)
        player.addChild(bubbleNumberText)
        self.addChild(player)

        playerNameArr.append(player.name!)
        arrplayer.append(player)
//
    } 

Timer

When the play button is press the countdown timer will start “startTheTimer()”.

func startTheTimer() {

        theTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimer), userInfo: nil, repeats: true)
    }

    @objc func countdownTimer(){

        startTime -= 1
        timerLabel.text =  "\(startTime)"

        if startTime == 0 {
            theTimer.invalidate()
            gameOver()
        }
        //
    }

Touch Handlers

For the touch handler, will be using the touchDown(atPoint:) method. This method is called whenever the player touches the screen. If the player touches the screen the position and Node name pass to the custom “touchN” method.

func touchDown(atPoint pos : CGPoint) {


        let touchN = atPoint (pos)

        if touchN.name == "playBtn"{

            createPlayer()

            startTheTimer()
            hideStartButton()
            //
        }

Add Time and Unlock Asset

The functions that will be called to add time and unlock the asset are defined in Figure8-5.
When the player presses the “BuyBonus” button a transaction is made and call the function “addTimeBonus()”. Five seconds are added to the Timer and store in the user’s defaults database. The Timer value persists every time the app launches.

func addTimeBonus() {

        secondsLeft = secondsLeft + 5
        defaults.set( secondsLeft, forKey: "increaseTime")
        self.startTime = self.defaults.integer(forKey: "increaseTime")
        self.timerLabel.text = "\(self.startTime)"
    }

When the player collects the 15 balls the game calls this function and unlock the Asset.

func unlockAsset() {

        theAssetState.add(assetStateChange: false)
        self.horseAsset = false
    }

EditorImages/2021/05/05 10:38/8_2gamescene.png
Figure 8-5 Add Time and Unlock Functions

9. All GameScene Code

All of the methods in step are in the GameScene.swift file

    import Foundation
import Combine
import SwiftUI
import SpriteKit

class GameScene: SKScene, ObservableObject {

    @Binding var horseAsset: Bool

    init(horseAsset: Binding<Bool>) {
        _horseAsset = horseAsset
        super.init(size: CGSize(width: 400, height: 800))
        self.scaleMode = .fill
      }

      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) is not supported")
      }


   var theAssetState:bubbleGame = bubbleGame()


    var assetBubbleIsShowing = false

    let defaults:UserDefaults = UserDefaults.standard


    var timerLabel:SKLabelNode = SKLabelNode(fontNamed: "Arial")
    var bubbleLabel:SKLabelNode = SKLabelNode(fontNamed: "Arial")
    var totalBubbleLabel:SKLabelNode = SKLabelNode(fontNamed: "Arial")
    var ruleLabel:SKLabelNode = SKLabelNode(fontNamed: "Arial")


    var int = 0
    var nrOfBubbles = 5
    var number = 0
    var randomCount = Int.random(in: 1...3) + 1
    private var lastUpdateTime : TimeInterval = 0

    let background = SKSpriteNode(imageNamed: "bubbleBack")
    let timerCount = SKSpriteNode(imageNamed: "timerCount")
    let bubbleCount = SKSpriteNode(imageNamed: "bubbles")
    let totalBubbleCount = SKSpriteNode(imageNamed: "totalbubbles")
    let play: SKSpriteNode = SKSpriteNode(imageNamed: "play")

    var arrplayer :[SKSpriteNode] = [SKSpriteNode]()
    var playerNameArr = [String]()
    var theTimer = Timer()
    var secondsLeft = 15
    var startTime = 0
    var bubblesCatch = 0
    var totalBubblesCatch = 0

    // -------music//
    let musicNode = SKAudioNode(fileNamed: "PoolParty.wav")
    private let playPopSound = SKAction.playSoundFileNamed("pop.mp3",
    waitForCompletion: false)

    override func didMove(to view: SKView) {


        musicNode.autoplayLooped = true
        musicNode.isPositional = false
        // Add the audio the scene
        addChild(musicNode)

        self.lastUpdateTime = 0

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

            startTime = defaults.integer(forKey: "increaseTime")
            print(startTime)

        }else  {

            startTime = secondsLeft
            print(startTime)
        }



        setupStartButton()



        background.position = CGPoint(x: 0, y: 0)
        background.name = "background"
        addChild(background)



        timerCount.position = CGPoint(x: -130, y: 80)
        timerCount.zPosition = 90
        timerCount.name = "timerCount"
        addChild(timerCount)


        timerLabel.text = "\(startTime)"
        timerLabel.fontSize = 24

        timerLabel.fontColor = SKColor.white
        timerLabel.horizontalAlignmentMode = .left
        timerLabel.position = CGPoint(x: -115, y: 80)
        timerLabel.zPosition = 100
        addChild(timerLabel)


        bubbleCount.position = CGPoint(x: 0, y: 80)
        bubbleCount.zPosition = 90
        bubbleCount.name = "timerCount"
        addChild(bubbleCount)


        bubbleLabel.text = "\(bubblesCatch)"
        bubbleLabel.fontSize = 24

        bubbleLabel.fontColor = SKColor.white
        bubbleLabel.horizontalAlignmentMode = .left
        bubbleLabel.position = CGPoint(x: 15, y: 80)
        bubbleLabel.zPosition = 100
        addChild(bubbleLabel)



        totalBubbleCount.position = CGPoint(x: 125, y: 80)
        totalBubbleCount.zPosition = 99
        totalBubbleCount.name = "timerCount"
        addChild(totalBubbleCount)


        totalBubbleLabel.text = "\(totalBubblesCatch)"
        totalBubbleLabel.fontSize = 24

        totalBubbleLabel.fontColor = SKColor.white
        totalBubbleLabel.horizontalAlignmentMode = .left
        totalBubbleLabel.position = CGPoint(x: 140, y: 80)
        totalBubbleLabel.zPosition = 100
        addChild(totalBubbleLabel)
    }
    // MARK: - addTimeBonus

    func addTimeBonus() {

        secondsLeft = secondsLeft + 5
        defaults.set( secondsLeft, forKey: "increaseTime")
        self.startTime = self.defaults.integer(forKey: "increaseTime")
        self.timerLabel.text = "\(self.startTime)"

    }
    // MARK: - UnlockAsset

    func unlockAsset() {

        theAssetState.add(assetStateChange: false)
        self.horseAsset = false
    }

    // MARK: - Animations

    func setupStartButton() {

        ruleLabel.text = "Touch the Seahorse balls in descending order."
        ruleLabel.fontSize = 18
        ruleLabel.fontColor = SKColor.white
        ruleLabel.horizontalAlignmentMode = .center
        ruleLabel.position = CGPoint(x: 0 , y: frame.midY - 200)
        ruleLabel.zPosition = 100
        addChild(ruleLabel)

        play.position = CGPoint(x: frame.midX, y: frame.midY - 100)
        play.zPosition = 2

        self.addChild(play)
        play.name = "playBtn"

    // Add animation
    let scaleUp = SKAction.scale(to: 0.55, duration: 0.65)
    let scaleDown = SKAction.scale(to: 0.50, duration: 0.65)
    let playBounce = SKAction.sequence([scaleDown, scaleUp])
    let bounceRepeat = SKAction.repeatForever(playBounce)
        play.run(bounceRepeat)

    }

    func showStartButton() {
        play.run(SKAction.fadeIn(withDuration: 0.25))
        ruleLabel.run(SKAction.fadeIn(withDuration: 0.25))
    }
    func hideStartButton() {
        play.run(SKAction.fadeOut(withDuration: 0.25))
        ruleLabel.run(SKAction.fadeOut(withDuration: 0.25))
    }

    // MARK: - Game Over

    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)"
        playerNameArr.removeAll()
        for enemy in arrplayer {
            enemy.removeFromParent()

        }

    }

    // MARK: - Timer

    func startTheTimer() {

        theTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimer), userInfo: nil, repeats: true)
    }

    @objc func countdownTimer(){

        startTime -= 1
        timerLabel.text =  "\(startTime)"

        if startTime == 0 {
            theTimer.invalidate()
            gameOver()
        }
        //
    }

    // MARK: - Create Game Characters

    func createPlayer(){

        for int in 1...nrOfBubbles where int < nrOfBubbles {

            populateStage()
            number += randomCount

        }

    }

    func populateStage() {

        let randomX = CGFloat.random(in: -160...160)
        let randomY = CGFloat.random(in: -370...5)
        let bubbleNumberText:SKLabelNode = SKLabelNode(fontNamed: "Arial")

        bubbleNumberText.fontSize = 40
        bubbleNumberText.verticalAlignmentMode = .center
        bubbleNumberText.fontColor = SKColor.black
        bubbleNumberText.text = String(number)
        bubbleNumberText.zPosition = 3

       let player: SKSpriteNode = SKSpriteNode(imageNamed: "horse")
        player.position = CGPoint(x: randomX, y: randomY)
        player.zPosition = 4
        player.name = String(number)
        player.addChild(bubbleNumberText)
        self.addChild(player)

        playerNameArr.append(player.name!)
        arrplayer.append(player)
//
    }
    // MARK: - Play Sounds

    func playPop() {
    let removeFromParent = SKAction.removeFromParent()
    let actionPopGroup = SKAction.group([playPopSound, removeFromParent])
    self.run(actionPopGroup)
    }

    override func update(_ currentTime: TimeInterval) {


    }

    // MARK: - Touch Functions

    func touchDown(atPoint pos : CGPoint) {


        let touchN = atPoint (pos)

        if touchN.name == "playBtn"{

            createPlayer()

            startTheTimer()
            hideStartButton()
            //
        }

        let touchedN = nodes(at: pos)
        for touchedNode in touchedN {

            //

            if touchedNode.name == playerNameArr.last {
                //
                touchedNode.removeFromParent()

                playerNameArr.removeLast()

                bubblesCatch = bubblesCatch + 1

                bubbleLabel.text = "\(bubblesCatch)"

                totalBubblesCatch = bubblesCatch

                totalBubbleLabel.text = "\(totalBubblesCatch)"
                self.playPop()

                if totalBubblesCatch >= 15 {

                    unlockAsset()

                }

                if playerNameArr.isEmpty {
//                    print("is empty.")
                    createPlayer()
                    nrOfBubbles = nrOfBubbles+1
                    number = 0

                }

                return


            }else if touchedNode.name != playerNameArr.last && touchedNode.name != "background" && touchedNode.name != "playBtn" && touchedNode.name != nil{
                print("erro")
                print(touchedNode.name as Any)

                theTimer.invalidate()

                gameOver()
                //
            }

        }

    }

10. bubbleGame File

All of the methods in step are in the bubbleGame.swift file
Repeat the process as in step 7 and create a new swift File “bubbleGame.swift”.
Import the Combine framework and start creating your bubbleGame class:

class bubbleGame: ObservableObject {
}

Note: bubbleGame was implemented as an ObservableObject.

Now with ObservableObject protocol, all variables were defined with the property, publisher. Whenever the value of the variables changes, they publish themselves and update all values on ContentView”

EditorImages/2021/05/05 10:52/10bubbleggame.png

Figure 10-1 Create bubbleGame class

All code from the swift File “bubbleGame.swift

import Foundation
import Combine


class bubbleGame: ObservableObject {



    //    -----------Unlock SeaHorse -------------//

    @Published var assetState:Bool = true

    var assetBubbleState:Bool{

        Bool(self.assetState)

    }


    func add(assetStateChange:Bool){

        self.assetState = assetStateChange

        print("BubbleClass \( assetState)")

        return
    }


    //    -----------Unlock SeaHorse Info-------------//

    @Published var UnlockHorseAsset:String = "Try to catch 15 Bubbles and win the Asset."

    var horseStringValue:String{
        String(UnlockHorseAsset)
    }


    func add(horseValue:String = ""){
        UnlockHorseAsset = horseValue

        print("UnlockHorseAsset \( UnlockHorseAsset)")
        return
    }


    //    -----------Asset ID -------------//

    @Published var assetIndexName:Int64?=0

    var assetIDValue:String{
        String(assetIndexName ?? 0)

    }


    func add(assetID:Int64?=0){
        assetIndexName = assetID
        print(assetIndexName as Any)
        return
    }






}

11. ContentView

The ContentView is where the player can do a Transaction and test how an Asset is implemented.
For test proposes the player will be able to create an Asset, Manager, Opt-in and Transfer.
The Transfer button it’s only available when the player unlocks the Asset.

Replace the values for ALGOD_API_TOKEN.

EditorImages/2021/05/09 10:26/11_1Accountconfig.png
Figure 11-1 Replace value for ALGOD_API_TOKEN

Add the “@ObservedObject”, we need to declare a dependency on a reference type that conforms to the ObservableObject protocol.

 @ObservedObject var AssetModel:bubbleGame
 @ObservedObject var gameScene: GameScene
 @ObservedObject var assetSate = bubbleGame()

Note: Pass the values down in “ContentView_Previews” or you can get some errors.

ContentView(AssetModel: bubbleGame(), gameScene: GameScene(horseAsset: .constant(true)))

EditorImages/2021/05/09 10:27/11_2Contentview.png
Figure 11-2 Add @ObservedObject variables

Add the “SKScene”, and pass the Asset state value from the GameScene.

//   ------- SKScene --------------//

    var scene: SKScene {
        let scene = GameScene(horseAsset: $assetSate.assetState)
        scene.size = CGSize(width: 400, height: 800)
        scene.scaleMode = .aspectFit
        scene.anchorPoint =  CGPoint(x: 0.5, y: 0.5)
        return scene
    }

EditorImages/2021/05/09 10:27/11_3Contentview.png
Figure 11-3 Add SKScene

Now we need to create the UI (User Interface). Using a combination of code and drag-from-object-library we will create the Staks, Buttons and the Text on the canvas.

Add the SpriteView to present the GameScene.
EditorImages/2021/05/09 10:28/11_4Contentview.png
Figure 11-4 Add SpriteView

The Add Button
EditorImages/2021/05/09 10:29/11_5Contentview.png
Figure 11-5 Add Address Button

Note: This will be shown in Step 19 in the accounts.

Add the Buttons,
EditorImages/2021/05/09 10:30/11_6Contentview.png
Figure 11-6 Add Buttons

Remember: Very import pass these Observable Objects and Binding values on the “ContentView_Previews” struct to avoid get some errors.
The code can be found here
EditorImages/2021/05/09 15:29/11_7Contentview.png
Figure 11-7 Add Observable Objects and Binding

All the UI declare code.

   var body: some View {

        ZStack(alignment: .center) {

            SpriteView(scene: scene)

                .ignoresSafeArea()


            VStack(alignment: .leading){

                HStack(){

                    Button(action: {

                        accountIsShowing = true

                    }) {
                        Image(systemName:"plus.circle.fill")
                            .font(.system(size: 50.0))
                            .foregroundColor((Color("seta")))

                    }
                    .padding(.trailing)

                    .sheet(isPresented: $accountIsShowing, onDismiss: {}, content: {
                        AlgoBubbleGame.accounts(accountIsShowing: $accountIsShowing)

                      })

                    Button(action: {

                        self.createAssetClicked()

                    }) {
                        Image("create")

                    }
                    .padding(.trailing)

                    Button(action: {

                        self.changeAsaManager()

                    }) {
                        Image("manager")

                    }
                    .padding(.trailing)

                    Button(action: {

                        self.optInToAsa()

                    }) {
                        Image("optin")

                    }
                    .padding(.trailing)

                }
                //
                .padding(.top, 30.0)
                HStack(){
                    Button(action: {

                        activeGoSheet = .bonus
                        //
                        requestBonus()
                        //

                    }) {
                        Image("bonus")

                    }

                    Text("ID: \(AssetModel.assetIDValue)")
                        .fontWeight(.heavy)
                        .padding(.vertical, 10.0)
                        .font(.custom("AvenirNext-Medium", size: 22))
                        .foregroundColor(Color.black)


                }


                Button(action: {

                    activeGoSheet = .assetSeaHorse
                    self.transferAsa()


                }) {
                    Image("seahorse")

                }
                .opacity(Bool(self.assetSate.assetState) ? 0.5 : 1)
                //                .opacity(0.5)
                .disabled(Bool(self.assetSate.assetState))
                //                .disabled(buttonDisabled)

            }

            .offset(y:-300)

            .sheet(isPresented:  $assetRewardState) {

                if self.activeGoSheet == .assetSeaHorse {
                    VStack(){
                        if istransaction == true {


                            Image(transactionSuccedImage)
                            Text(String("Score: \(self.assetSate.UnlockHorseAsset)"))

                            Button("Dismiss",
                                   action: {  self.assetRewardState.toggle() })

                        } else  if istransaction == false {


                            Image(transactionFailImage)
                            Text(String("Score: \(self.assetSate.UnlockHorseAsset)"))

                            Button("Dismiss",
                                   action: {  self.assetRewardState.toggle() })

                        }
                    }
                }

                else if self.activeGoSheet == .bonus {

                    VStack(){
                        if transactionIsShowing == true {


                            Image(bonusSuccedImage)
                            Text(String("Score: \(self.assetSate.UnlockHorseAsset)"))

                            Button("Dismiss",
                                   action: {  self.assetRewardState.toggle() })

                        } else  if transactionIsShowing == false {


                            Image(bonusFailImage)
                            Text(String("Score: \(self.assetSate.UnlockHorseAsset)"))

                            Button("Dismiss",
                                   action: {  self.assetRewardState.toggle() })

                        }
                    }
                }

            }


            Text("Try to catch 15 Bubbles and win the Asset.")

                .fontWeight(.bold)
                .font(.custom("AvenirNext-Medium", size: 18))
                .foregroundColor(Color.black)
                .offset(y:-150)
        }


    }

12. Transaction

When the player presses the “Buy Bonus” button +1 Algo will automatically be transferred and the Algorand transaction will then be created. If The transaction succeeds “gameScene.addTimeBonus()”, 5 seconds will be added to the Timer.

EditorImages/2021/05/09 10:33/12Transaction.png
Figure 12-1 Mnemonic and receiver address.

   //    ----transaction --------

    func  requestBonus(){

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

        do {

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

            let senderAddress = account.getAddress()
            let receiverAddress = try! Address(defaults.string(forKey: "receiveAddress") ?? "")


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


                let tx =  try? Transaction.paymentTransactionBuilder().setSender(senderAddress)
                    .amount(1000000)
                    .receiver(receiverAddress)
                    .note("Swift Algo sdk is cool".bytes)
                    .suggestedParams(params: paramResponse.data!)
                    .build()


                let signedTransaction=account.signTransaction(tx: tx!)

                let encodedTrans:[Int8]=CustomEncoder.encodeToMsgPack(signedTransaction)

                algodClient.rawTransaction().rawtxn(rawtaxn: encodedTrans).execute(){
                    response in
                    if(response.isSuccessful){
                        print(response.data!.txId)
                        print("Sucesso")



                        transactionIsShowing = true

                        if transactionIsShowing == true {
                            DispatchQueue.main.async {

                                let result = "Transaction Ok!"
                                assetSate.add(horseValue:String(result))

                                gameScene.addTimeBonus()
                                self.assetRewardState = true

                            }
                        }

                        //
                    }else{
                        print(response.errorDescription!)
                        print("Failed")

                        transactionIsShowing = false

                        if transactionIsShowing == false {
                            DispatchQueue.main.async {

                                let result = "Transaction fail!"
                                assetSate.add(horseValue:String(result))


                                self.assetRewardState = true

                            }
                        }




                    }

                }
            }

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

13. Create

Create an Algorand Standard Asset

Create the asset.
The parameters for the ASA are the following:

    @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/05 11:05/13_1Assetvalues.png

Figure 13-1 ASA values.
EditorImages/2021/05/09 10:41/13_2Createasa.png

Figure 13-2 Mnemonic and receiver address.

The create button will call this function and create the Asset.

  func createAssetClicked() {


        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()



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

                }



           let tx = Transaction.assetCreateTransactionBuilder()
               .setSender(senderAddress1)

               .setAssetTotal(assetTotal: assetTotal)
               .setAssetDecimals(assetDecimals: assetDecimals)
               .assetUnitName(assetUnitName: assetUnitName)
               .assetName(assetName: assetName)
               .url(url: url)
               .manager(manager: defaults.string(forKey: "receiveAddress") ?? "")
               .reserve(reserve: defaults.string(forKey: "receiveAddress") ?? "")
               .freeze(freeze:  defaults.string(forKey: "receiveAddress") ?? "")
               .defaultFrozen(defaultFrozen:defaultFrozen)
               .clawback(clawback:  defaults.string(forKey: "receiveAddress") ?? "")
               .suggestedParams(params: paramResponse.data!).build()


        let signedTransaction=account1.signTransaction(tx: tx)
        let encodedTrans:[Int8]=CustomEncoder.encodeToMsgPack(signedTransaction)
        _=Data(CustomEncoder.convertToUInt8Array(input: encodedTrans))

        algodClient.rawTransaction().rawtxn(rawtaxn: encodedTrans).execute(){
            response in
            if(response.isSuccessful){

                print(signedTransaction)
                print(encodedTrans)
                //

                print(response.data!.txId)
                print("Pass")
                self.waitForTransaction(txId:response.data!.txId)

            }else{
                print(response.errorDescription!)
                print("Failed")
            }
        }


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

Wait for Transaction. Here we get the AssetID and hold the value as an Observable Object.

AssetModel.add(assetID: assetIndex)

//-----
   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")


                   }else{
                       self.waitForTransaction(txId: txId)

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

                   confirmedRound=12000;
               }
   }
}

14. Manager

The Manager button will call this function and change the Asset manager role. This only to demonstrate the power of ASA - Algorand Standard Asset. New parameters can be added like new account and new Address.
EditorImages/2021/05/09 10:37/14manager.png

Figure 14-1 Mnemonic and receiver address.

func  changeAsaManager(){

       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()

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

           }

           let tx = Transaction.assetConfigureTransactionBuilder()
               .reserve(reserve: defaults.string(forKey: "receiveAddress") ?? "")
               .freeze(freeze:  defaults.string(forKey: "receiveAddress") ?? "")
               .clawback(clawback:  defaults.string(forKey: "receiveAddress") ?? "")
               .assetIndex(assetIndex: Int64(AssetModel.assetIDValue)!)
               .setSender(senderAddress1)
               .manager(manager: defaults.string(forKey: "receiveAddress") ?? "")
               .suggestedParams(params: paramResponse.data!)
                     .build();


           let signedTransaction=account1.signTransaction(tx: tx)


           let encodedTrans:[Int8]=CustomEncoder.encodeToMsgPack(signedTransaction)
           _=Data(CustomEncoder.convertToUInt8Array(input: encodedTrans))
           algodClient.rawTransaction().rawtxn(rawtaxn: encodedTrans).execute(){
              response in
               if(response.isSuccessful){
//                    functionToCall(response.data!.txId)
                   print("manager sim")
               }else{
//                    functionToCall(response.errorDescription)
                   print("manager nao")
               }

           }


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

15. Opt-In

The Opt-In button will call this function and check if you can receive an Asset. We need to check Opt-In button before you can get your ASA game reward.

EditorImages/2021/05/09 10:37/15optin.png
Figure 15-1 Mnemonic.

 func optInToAsa(){


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

        do {


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

            let senderAddress3 = account3.getAddress()

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


                let tx = Transaction.assetAcceptTransactionBuilder()

                    .setSender(senderAddress3)

                    //                    .acceptingAccount(acceptingAccount:senderAddress3)
                    .assetIndex(assetIndex: Int64(AssetModel.assetIDValue))
                    .suggestedParams(params: paramResponse.data!)
                    .build();




                //                    var txMessagePack:[Int8]=CustomEncoder.encodeToMsgPack(tx)
                let signedTrans=account3.signTransaction(tx: tx)
                let encodedTx:[Int8]=CustomEncoder.encodeToMsgPack(signedTrans)
                algodClient.rawTransaction().rawtxn(rawtaxn: encodedTx).execute(){
                    response in
                    if(response.isSuccessful){

                        print("optInToAsa yes")
                        //                            functionToCall(response.data!.txId)
                    }else{

                        print("optInToAsa off")
                        //                            functionToCall(response.errorDescription!)
                    }


                }
                //


            }//paramresponse

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


    }

16. Transfer

The Transfer button will call this function and transfer the asset to a specified account.
EditorImages/2021/05/09 10:38/16transfer.png
Figure 16-1 Mnemonic.

 func transferAsa(){


        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.assetTransferTransactionBuilder()

                    .setSender(senderAddress1)
                    .assetReceiver(assetReceiver:senderAddress3)
                    .assetAmount(assetAmount: 1)
                    .assetIndex(assetIndex: Int64(AssetModel.assetIDValue)!)

                    .suggestedParams(params:paramResponse.data!).build();

                print(AssetModel.assetIDValue)
                //
                let signedTrans=account1.signTransaction(tx: tx)

                let encodedTx:[Int8]=CustomEncoder.encodeToMsgPack(signedTrans)
                algodClient.rawTransaction().rawtxn(rawtaxn: encodedTx).execute(){
                    response in
                    if(response.isSuccessful){
                        print("Transaction ok!")
                        istransaction = true

                        if istransaction == true {
                            DispatchQueue.main.async {

                                let result = "Transaction Ok!"
                                assetSate.add(horseValue:String(result))


                                self.assetRewardState = true

                            }
                        }

                        //                            functionToCall(response.data!.txId)
                    }else  {
                        //                            functionToCall(response.errorDescription!)
                        print("Transaction no!")
                        //
                        //
                        istransaction = false

                        if istransaction == false {
                            DispatchQueue.main.async {

                                let result = "Transaction Fail!"
                                assetSate.add(horseValue:String(result))


                                self.assetRewardState = true

                            }
                        }



                    }

                }



            }//paramresponse

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

    }

17. Check ASA Transfer

When a transaction between an asset from one account to another will succeed the game will show a notification.

if(response.isSuccessful){
                        print("Transaction ok!")
                        istransaction = true

                        if istransaction == true {
                            DispatchQueue.main.async {

                                let result = "Transaction Ok!"
                                assetSate.add(horseValue:String(result))


                                self.assetRewardState = true

                            }
                        } 

EditorImages/2021/04/20 23:51/assetOK.png

18. defaultsModel

Now you need to set your mnemonic. Never share your mnemonic secret key.

All of the methods in step are in the defaultsModel.swift file.

Repeat the process as in step 7 and create a new swift File “defaultsModel.swift”.
Import the Combine framework and start creating your defaultsModel class:

class defaultsModel: ObservableObject {
}

Note: defaultsModel was implemented as an ObservableObject.

Now with ObservableObject protocol, all variables were defined with the property, publisher. Whenever the value of the variables changes, they publish themselves and update all values on ContentView”.
EditorImages/2021/05/09 10:44/18Defaults.png
Figure 18-1 Create defaultsModel class

All code from the swift File “defaultsModel.swift”.

import Foundation
class defaultsModel: ObservableObject {


    @Published var mnemonic1: String = UserDefaults.standard.string(forKey: "mnemonic1") ?? "" {

        didSet {

            UserDefaults.standard.set(self.mnemonic1, forKey: "mnemonic1")
        }

         }


    @Published var mnemonic3: String = UserDefaults.standard.string(forKey: "mnemonic3") ?? "" {

           didSet {

               UserDefaults.standard.set(self.mnemonic3, forKey: "mnemonic3")
           }

            }


    @Published var receiveAddress: String = UserDefaults.standard.string(forKey: "receiveAddress") ?? "" {

           didSet {

               UserDefaults.standard.set(self.receiveAddress, forKey: "receiveAddress")
           }

            }



}

19. Accounts

Create a new swiftUI File “accounts.swift”.
EditorImages/2021/05/09 10:47/19_0Accounts.png
Figure 19-1 Create new file

EditorImages/2021/05/09 10:47/19_1Accounts.png
Figure 19-2 Select SwiftUI file

All code from the swift File “accounts.swift”.

Note: Copy all the code from accounts.swift here, run the App and follow the next steps.

EditorImages/2021/05/09 10:50/18_1Accounts.png
Figure 19-3 Press the plus Button
Note: Now you need to set your mnemonic. Never share your mnemonic secret key.
EditorImages/2021/05/09 10:50/18_2Accounts.png
Figure 19-4 Paste the mnemonic and Address key

EditorImages/2021/05/09 10:50/18_3Accounts.png
Figure 19-5 Save the values

Test the game and have some fun.

20. Github Project

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

One the next tutorial I will include other ASA functions like freeze an Asset.
Thanks.

May 20, 2021