search instagram arrow-down

Archives

Fish Game: Part 3

If you are interested in more iOS tutorials, please visit my portfolio site at https://suzhoupanda.github.io/. Some of my games and apps haven’t been updated for a while and may be a little buggy, but they are free, so feel free to download and enjoy.

Let’s continue to define a BaseScene class from which other scenes will inherit. This will make it easier to configure derived scenes, since the basic implementation details for the functionality common across all scenes will be hidden, allowing us to focus the aspects of the derived scenes that unique to those scenes.


import Foundation
import SpriteKit
import GameplayKit


class BaseScene: SKScene{
    
    lazy var agentSystem: GKComponentSystem = {
        
       let agentSystem = GKComponentSystem(componentClass: FishAgent.self)
        return agentSystem
        
    }()
    
    let trackingAgent = GKAgent2D()
    var player: Player!


    
    /** The StopGoal and SeekGoal are fundamental to every scene, since they handle the mechanics of player movement, and are therefore defined as stored properties on the BaseScene**/
    
    lazy var stopGoal: GKGoal = {
        
        let goal = GKGoal(toReachTargetSpeed: 0.00)
        
        return goal
    }()
    
    lazy var seekGoal: GKGoal = {
        
        let goal =  GKGoal(toSeekAgent: self.trackingAgent)
        
        return goal
        
    }()
    
    lazy var seekingSpeedGoal: GKGoal = {
        
        let goal = GKGoal(toReachTargetSpeed: 500.0)
        
        return goal
        
    }()
    
    var _seeking: Bool = false
    
    var seeking: Bool{
        
        set(isSeeking){
            
            _seeking = isSeeking
            
            if(isSeeking){
                self.player.agent.behavior?.setWeight(0.00, for: self.stopGoal)
                self.player.agent.behavior?.setWeight(1.00, for: self.seekGoal)
                self.player.agent.behavior?.setWeight(1.00, for: self.seekingSpeedGoal)
            } else {
                self.player.agent.behavior?.setWeight(1.00, for: self.stopGoal)
                self.player.agent.behavior?.setWeight(0.00, for: self.seekGoal)
                self.player.agent.behavior?.setWeight(0.00, for: self.seekingSpeedGoal)

            }
        }
        
        get{
            return _seeking
        }
    }
    
    
   
    
    var lastUpdateTime = 0.00
    
    
    let backgroundNode: SKNode!
    let worldNode: SKNode!
    let overlayNode: SKNode!
    let graphNode: SKNode!
    
    /** The filename for the SKS file providing the background graphics, obstacle, path, and other information for the scene **/
    
    var sceneName: String{
        return "background1"
    }
    
    var bgMusicFilename: String{
        return "Polka Train.mp3"
    }
    
    
    override init(size: CGSize) {
        
        overlayNode = SKNode()
        worldNode = SKNode()
        backgroundNode = SKNode()
        graphNode = SKNode()
        
        super.init(size: size)
        self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    }
    
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
  
    

    
}


In our didMove(to view:) function, we call several private helper functions which act together to set up the basic functionality necessary for all scenes. These helper methods are not defined yet but are shown here to provide an overview of how the scene is setup:

  
    override func didMove(to view: SKView) {
        super.didMove(to: view)
        
       
        configureNodeLayers()
        configurePhysicsWorld()
        configureBackgroundSceneryAndPlaceholderNodes()
        configureBackgroundMusic()
        configurePlayer()
        configureCamera()
        
 
    }
    

Let’s define the private helper function configureNodeLayers(), which basically adds the different node layers to scene’s root node:


    private func configureNodeLayers(){
        addChild(overlayNode)
        addChild(worldNode)
        addChild(backgroundNode)
        addChild(graphNode)
        
    }

Now let’s define the private helper method configurePhysicsWorld() , which basically set the physicWorld’s contactDelegate property to the current scene:

    
    private func configurePhysicsWorld(){
        self.physicsWorld.contactDelegate = self

    }

Now let’s define the configureBackgroundSceneryAndPlaceholderNodes() function, which loads the background scene we created in the first tutorial and transfers it to the backgroundNode:

    
    private func configureBackgroundSceneryAndPlaceholderNodes(){
        if let scene = SKScene(fileNamed: self.sceneName){
            
            if let background = scene.childNode(withName: "Root"){
                
                background.move(toParent: backgroundNode)
                
                
                
            }
            
            
            
            
        }
        
    }

Now let’s implement the configureBackgroundMusic() function, which adds an SKAudioNode that will be responsible for playing our background music:

    
    private func configureBackgroundMusic(){
        let backgroundSound = SKAudioNode(fileNamed: "Polka Train.mp3")
        self.addChild(backgroundSound)
  
    }

Now let’s implement the configurePlayer() function, which instantiates a Player and configures its agent behavior with the stored properties for the different goals we defined earlier (i.e. seekGoal, stopGoal, and seekingSpeedGoal):

    
    private func configurePlayer(){
        self.player = Player(baseScene: self, fishType: .BlueFish, position: CGPoint.zero, zRotation: 0.00, radius: 50.0)
        
        
        self.player.agent.behavior = GKBehavior(goals: [self.seekGoal,self.stopGoal,self.seekingSpeedGoal])
        
        self.player.configureAgent(withMaxSpeed: 150, andWithMaxAccelerationOf: 100)
        
        self.agentSystem.addComponent(self.player.agent)
        
    }
  

Finally, we need to implement the private helper function for configuring the scene’s camera, which involves instantiating a SKCameraNode, setting it equal to the scene’s camera property, and then adding it to the worldNode:

    private func configureCamera(){
        let cam = SKCameraNode()
        self.camera = cam
        worldNode.addChild(self.camera!)
        
        
        
    }
    

Now we have to implement the touchHandling functions for the scene such that the scene’s seeking property is toggled on or off depending on whether the user is touching the screen or not, respectively. When the user touches the screen, the seeking property is toggled on, which configures the player’s behavior such that its self.seekGoal and self.seekSpeedGoal are given a weight of 1.0, while its self.StopGoal is given a weight of 0.0, causing the player fish to move in the direction of the user’s touch point. In the touchesBegan function, the scene’s tracking agent’s position is set to the position of the user’s touch. Since the player’s agent has a seekGoal which is configured such that the agent will try to move in the direction of the tracking agent, any time the seeking property is toggled on, the the player fish will move in that direction accordingly. This is also because, in the Fish class, the sprite node used to render the fish sprite is updated such that it’s orientation and position are in sync with the fish’s agent. The FishAgentDelegate protocol methods are implemented in the Fish class, thereby ensuring that this synchronization between the agent and the sprite node happens automatically.

 
    override func touchesEnded(_ touches: Set, with event: UIEvent?) {
       self.seeking = false
    }
    
    override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
        self.seeking = false
    }
    
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        self.seeking = true
        
        let touch = touches.first! as UITouch
        let touchPosition = touch.location(in: worldNode)
        
        self.trackingAgent.position = vector_float2(x: Float(touchPosition.x), y: Float(touchPosition.y))
        
        
        
        
    }
    
    override func touchesMoved(_ touches: Set, with event: UIEvent?) {
        self.seeking = true
        
    }

Next, in our scene’s update function, we call the update method on the agentSystem stored reference:

 override func update(_ currentTime: TimeInterval) {
        super.update(currentTime)
        
        if(lastUpdateTime == 0){
            lastUpdateTime = currentTime
        }
        
        let delta = currentTime - lastUpdateTime
        
        self.agentSystem.update(deltaTime: delta)
        
        lastUpdateTime = currentTime
    }
    
   

In our didSimulatePhysics() function, we make sure that the scene’s camera is synchronized with the position of the player node, so that the camera is always focused on the player as it moves throughout the scene:

 override func didSimulatePhysics() {
        super.didSimulatePhysics()
        
        if self.camera != nil{
            camera!.position = player.node.position
        }
        
    }

Finally, we will define an extension for the BaseScene where we will implement the SKPhysicsContactDelegate methods. For now, we will define stubs for many of the contact handler functions that are called when different kinds of game objects collide with each other. In our didBegin(_ contact) function, we use a switch statement to determine which contact handler function is called based on the categoryBitMasks associated with the contact object passed into the didBegin(_ contact) callback method:


extension BaseScene: SKPhysicsContactDelegate{
    
    func didBegin(_ contact: SKPhysicsContact) {
        let contactA = contact.bodyA
        let contactB = contact.bodyB
        
        
        
        
        switch (contactA.categoryBitMask,contactB.categoryBitMask) {
        case (let x, let y) where x == ColliderType.RedFish.categoryMask ||  y == ColliderType.RedFish.categoryMask:
            break
        case (let x, let y) where x == ColliderType.BlueFish.categoryMask || y == ColliderType.BlueFish.categoryMask:
            break
        case (let x, let y) where x == ColliderType.OrangeFish.categoryMask ||  y == ColliderType.OrangeFish.categoryMask:
            break
        case (let x, let y) where x == ColliderType.PinkFish.categoryMask ||  y == ColliderType.PinkFish.categoryMask:
            break
        case (let x, let y) where x == ColliderType.BlowFish.categoryMask ||  y == ColliderType.BlowFish.categoryMask:
            break
        case (let x, let y) where x == ColliderType.Eel.categoryMask ||  y == ColliderType.Eel.categoryMask:
            break
        case (let x, let y) where x == ColliderType.Barrier.categoryMask ||  y == ColliderType.Barrier.categoryMask:
            break
        case (let x, let y) where x == ColliderType.Player.categoryMask || y == ColliderType.Player.categoryMask:
            break
        default:
            break
        }
        
        
    }
    
    func didEnd(_ contact: SKPhysicsContact) {
        
    }
    
    
    func handlePlayerContacts(contact: SKPhysicsContact){
        
        
    }
    
    func handleRedFishContacts(contact: SKPhysicsContact){
        
        
    }
    
    func handleBlueFishContacts(contact: SKPhysicsContact){
        
        
    }
    
    func handleOrangeFishContacts(contact: SKPhysicsContact){
        
        
    }
    
    func handlePinkFishContacts(contact: SKPhysicsContact){
        
        
    }


    func handleEelContacts(contact: SKPhysicsContact){
        
        
    }
    
    func handleBlowFishContacts(contact: SKPhysicsContact){
        
        
    }
    
    func handleBarrierContacts(contact: SKPhysicsContact){
        
        
    }





    func handleCollectibleContacts(contact: SKPhysicsContact){
        
        
    }
}

Excellent job, to continue to the next section, click here.
Otherwise, to go back to previous tutorial for more review, click here, or to go back to first page to start over, click here.

Advertisements
Leave a Reply
Your email address will not be published. Required fields are marked *

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: