Now that we have our Encounter "node" defined, we are ready to create a class EncounterSeries that will represent a series of encounters. We will define several variables for this class, including firstEncounter, which will correspond to the root node in the linked list of node, planeViewController, which is the name of the main view controller for game play, hasTerminatedEncounterSeries, a Boolean value that will be used stop the execution of the encounter series, and currentEncounter, of optional Encounter type and for which we define a property observer that will call the executeEncounter() function if the current encounter is not nil.

Now that we have our variables defined, we have to define an initializer, which will take the view controller for game play as well as an encounter corresponding to the firstEncounter.

    class EncounterSeries{
    
    var firstEncounter: Encounter
    var planeViewController: PlaneViewController
    
    var hasTerminatedEncounterSeries: Bool = false
    
    var currentEncounter: Encounter?{
        didSet{
            
            print("Current encounter has been set, executing nextencounter.....")

            if(currentEncounter != nil){
                executeEncounter()
            }
        }
    }

    init(planeViewController: PlaneViewController, firstEncounter: Encounter) {
        self.firstEncounter = firstEncounter
        self.planeViewController = planeViewController
        
   
        
    }

  }
  

We will also define a private helper function addGameObjects() whose main purpose will be to add the enemies to the SceneKit scene. Our main view controller, the planeViewController, has variables defined for manager classes (not shown here), such as spikeBallManager, spaceCraftManager, etc.), whose main purpose is to add, remove, update, and manage the enemies in the SceneKit scene and which we have not shown here in order to avoid getting bogged down in the implementation details of the game. Suffice it to say, each time an encounter is "executed," it will add enemy objects to the SceneKit scene depending on whether or not a given encounter has a nubmer specified for a given enemy.

One other thing to note is that the addGameObject() function will not execute if the game is not in a playing state or if the SceneKit scene, or the worldNode in the SceneKit scene, are presently in the paused state. This prevents enemies from constantly being spawned while the game is in a paused state.

     
    private func addGameObjects(){
        
        
        if(GameHelper.sharedInstance.state != .Playing || self.planeViewController.scnScene.isPaused || self.planeViewController.worldNode.isPaused){
            return
        }
        
        if let numberOfSpikeBalls = self.currentEncounter!.numberOfSpikeBalls{
            planeViewController.spikeBallManager.addRandomSpikeBalls(number: numberOfSpikeBalls)
        }
        
        if let numberOfSpaceCraft = self.currentEncounter!.numberOfSpaceCraft{
            planeViewController.spaceCraftManager.addRandomSpaceCraft(number: numberOfSpaceCraft)
        }
        
        if let numberOfLetters = self.currentEncounter!.numberOfLetters{
            
            planeViewController.letterRingManager.addRandomizedMovingRing(with: numberOfLetters, fromWord: planeViewController.currentWord)
        }
        
        if let numberOfFireballs = self.currentEncounter!.numberOfFireballs{
            planeViewController.fireballManager.addRandomFireballs(number: numberOfFireballs)
        }
        
        if let numberOfAlienHeads = self.currentEncounter!.numberOfAlienHeads{
            planeViewController.alienHeadManager.addRandomAlienHeads(number: numberOfAlienHeads)
        }
        
    }



  

Now, we define an executeEncounter() function, which will will be the key function of this class. After performing a check for game state (i.e. if the game is in a paused state or the hasTerminatedEncounterSeries boolean flag is set to true), the no further encounters are executed. The hasTerminatedEncounterSeries boolean flag was added here as workaround to a bug that occurred during development and provided an additional means of stopping the execution of the encounter series, since it is accessible from the main game view controller, but the implementation details of this are not provided here in order to keep focused on the main point of the tutorial.

The executeEncounter() function is wrapped in the DispatchQueue.global().asyncAfter(deadline:execute:) function, where waitTime for the currentEncounter is added to the current clock time (.now() + waitTime) so that the spawning of the enemies is managed asynchronously by the global dispatch queue. In the operation block passed into this function, we call our addGameObjects() function, after which we retrieve the reference to the next encounter from the current encounter and set that to the value of the first encounter, which in turn triggers the property observer defined for the current encounter, which in turn recursively calls the executeEncounter() function.

In addition, in order for our linked list to work properly, we define a start() function, which basically will set the current encounter to the first encounter and thereby set in motion, so to speak, the entire series of encounters since each time the current encounter is set, the property server defined for the current encounter will call executeEncounter(), which in turn sets the value of the currentEncounter to the nextEncounter defined for the current encounter, unless the next encounter is nil, in which case the encounter series ends, in which case we can send a notification to the plane view controller to end the game or, since we have direct access to the planeViewController, set a boolean flag (i.e. planeViewController.encounterIsFinished) to true, so that when this boolean flag is checked in the next run of the game update loop, the game can come to a graceful conclusion.

  
    
    func start(){
        print("Starting the encounter series....setting the first encounter...")
        self.currentEncounter = self.firstEncounter
    }
    
  
    
    func executeEncounter(){
    
        if(GameHelper.sharedInstance.state != .Playing || hasTerminatedEncounterSeries){
            return
        }
        
        if(self.currentEncounter == nil){
            return
        }
        
        let waitTime = self.currentEncounter!.waitTime
        
        print("Preparing to execute next encounter....")
        
        DispatchQueue.global().asyncAfter(deadline: .now() + waitTime, execute: {
            
            if(GameHelper.sharedInstance.state != .Playing || self.hasTerminatedEncounterSeries){
                return
            }
            
            print("Adding game objects for encounter....")

            self.addGameObjects()
            
            
            print("Getting next encounter....")

            if let nextEncounter = self.currentEncounter!.getNextEncounter(){
                
                print("Setting next encounter....")

                self.currentEncounter = nextEncounter
        
            } else {
                print("No more encounters in series...")
                self.planeViewController.encounterIsFinished = true
            }
            
        })
    }

    
    
   

    

One last note on the hasTerminatedEncounterSeries boolean flag mention earlier with respect to the executeEncounter() function. An alternative to setting this flag from the main game view controller would be to define a function that can be called upon receiving a notification that can be sent from the game view controller or any other class or game object that might be responsible for ending the game. An example of such a function is shown below:


  @objc func terminateEncounterSeries(){
        print("Notification received by EncounterSeries....requesting restart")
        self.hasTerminatedEncounterSeries = true
    }

		
	

Now that we have our linked list defined, we are ready to go one step further, and create a generator class that can use customizable parameters for creating EncounterSeries that we can customize for different game levels based on the difficulty level or the specific enemies that should appear for that level. Stay tuned!

To continue, please click here

If you feel confused or are having trouble following, you can go back to the previous page or back to the iOS tutorials table of contents table of contents.