Swift/приключение/приключение Shared/Scene/AdventureScene.swift

/*
  Copyright (C) 2015 Apple Inc. All Rights Reserved.
  See LICENSE.txt for this sample’s licensing information
  
  Abstract:
  The primary scene class for Adventure.
*/
 
import SpriteKit
import GameController
 
class AdventureScene: SKScene, SKPhysicsContactDelegate {
    // MARK: Types
    
    enum WorldLayer: Int {
        case Ground = 0, BelowCharacter, Character, AboveCharacter, Top
    }
    
    struct Constants {
        static let worldTileDivisor = 32
        static let worldSize = 4096
        static let worldTileSize = worldSize / worldTileDivisor
        static let worldCenter = worldSize / 2
        
        static let minimumUpdateInterval = 1.0 / 60.0
        
        // The minimum distance between the hero and a currently visible edge before moving camera.
        static let minimumDistanceFromHeroToVisibleEdge: CGFloat = 256.0
        
        // Node names for each of the HUD nodes.
        static let hudNodeName = "AdventureHUD"
        static let hudAvatarName = "hudAvatar"
        static let hudScoreName = "hudScore"
        static let hudHeartName = "hudHeart"
        
        static let hudWidth = 300
        
        static let backgroundQueue = dispatch_queue_create("com.example.apple-samplecode.Adventure.backgroundQueue", DISPATCH_QUEUE_SERIAL)
    }
    
    // MARK: Properties
    
    /*
        Parent node containing the entire scene. This allows the node to be moved relative to the
        viewport, allowing the characters to move about in a scene larger than the viewport.
    */
    var world = SKNode()
    
    // Properties to track nodes that are updated during each pass of the game loop. Populated during loading.
    var heroes = [HeroCharacter]()
    var goblinCaves = [Cave]()
    var trees = [Tree]()
    var particleSystems = [SKEmitterNode]()
    var parallaxSprites = [ParallaxSprite]()
    // The boss will always exist. Its value is deferred until its position is known when the world is loaded.
    var levelBoss: Boss!
    
    // Static during game processing. Populated during loading.
    var backgroundTiles = [SKSpriteNode]()
    
    // Templates populated during initialization and used during environment population.
    var leafEmitterATemplate: SKEmitterNode
    var leafEmitterBTemplate: SKEmitterNode
    var spawnEmitterTemplate: SKEmitterNode
    var projectileSparkEmitterTemplate: SKEmitterNode
    
    // Property containing references to the 5 different layers in the world.
    var layers = [SKNode]()
    
    // Properties for the nodes that make up the HUD.
    var hudAvatar: SKSpriteNode!
    var hudScore: SKLabelNode!
    var hudLifeHearts = [SKSpriteNode]()
 
    var defaultSpawnPoint = CGPointZero
    var defaultPlayer = Player()
    
    /*
        A maximum of 4 players are supported. The array is initialized with `nil` in each position. The default
        player will be populated during initialization at index 0. Subsequent players will be added as needed 
        during game controller connection up to a maximum of 4.
    */
    var players: [Player!] = [nil, nil, nil, nil]
 
    // Properties to keep track of details important to scene updates.
    var worldMovedForUpdate = false
    var lastUpdateTimeInterval: NSTimeInterval = 0
 
    // Set to `true` to cheat and start the level next to the boss.
    var shouldCheat = false
    
    // A closure to be called when `didMoveToView(_:)` completes.
    var finishedMovingToView: Void -> Void = {}
    
    // MARK: Initializers
    
    override init(size: CGSize) {
        leafEmitterATemplate = SKEmitterNode(fileNamed: "Leaves_01")
        leafEmitterBTemplate = SKEmitterNode(fileNamed: "Leaves_02")
        projectileSparkEmitterTemplate = SKEmitterNode(fileNamed: "ProjectileSplat")
        spawnEmitterTemplate = SKEmitterNode(fileNamed: "Spawn")
        
        super.init(size: size)
 
        players[0] = defaultPlayer
    }
 
    required init?(coder aDecoder: NSCoder) {
        fatalError("AdventureScene does not support NSCoding. Use loadSceneAssetsWithCompletionHandler(_:) instead.")
    }
    
    // MARK: SKView Behaviors
 
    override func didMoveToView(view: SKView) {
        // Complete the loading on a background queue to not take up the main queue's resources.
        dispatch_async(Constants.backgroundQueue) {
            self.loadWorld()
            
            self.centerWorldOnPosition(self.defaultSpawnPoint)
 
            dispatch_async(dispatch_get_main_queue(), self.finishedMovingToView)
        }
    }
 
    override func didChangeSize(oldSize: CGSize) {
        enumerateChildNodesWithName(Constants.hudNodeName) { hud, stop in
            hud.position = CGPoint(x: hud.position.x, y: self.frame.size.height)
        }
    }
 
    // MARK: Scene Processing Support
 
    override func update(currentTime: NSTimeInterval) {
        if paused {
            lastUpdateTimeInterval = currentTime
            
            return
        }
 
        var timeSinceLast = currentTime - lastUpdateTimeInterval
        lastUpdateTimeInterval = currentTime
 
        if timeSinceLast > 1 {
            timeSinceLast = Constants.minimumUpdateInterval
            worldMovedForUpdate = true
        }
 
        updateWithTimeSinceLastUpdate(timeSinceLast)
 
        #if os(iOS)
        if defaultPlayer.hero == nil {
            return
        }
 
        let hero = defaultPlayer.hero!
 
        if hero.isDying {
            return
        }
 
        if defaultPlayer.targetLocation != CGPointZero {
            if defaultPlayer.fireAction {
                hero.faceToPosition(defaultPlayer.targetLocation)
            }
 
            if defaultPlayer.moveRequested {
                if defaultPlayer.targetLocation != hero.position {
                    hero.moveTowardsPosition(defaultPlayer.targetLocation, withTimeInterval: timeSinceLast)
                }
                else {
                    defaultPlayer.moveRequested = false
                }
            }
        }
        #endif
 
        for player in self.players {
            // If there is no player in this slot, move on.
            if player == nil {
                continue
            }
 
            // If the player has no assigned hero or their hero is isDying, move on.
            if player.hero == nil || player.hero!.isDying {
                continue
            }
 
            let hero = player.hero!
 
            /*
                Player movement input will be provided via `heroMoveDirection` or individual movements via
                `moveForward` or `moveBackward` and rotation via `moveLeft` and `moveRight`. `heroMoveDirection` 
                is populated when input is received from a controller. The individual movement details are 
                received when using keyboard input.
            */
            if let heroMoveDirection = player.heroMoveDirection {
                var moveFacing: CGPoint?
                if let heroFaceLocation = player.heroFaceLocation {
                    moveFacing = heroFaceLocation
                }
                
                let magnitude = hypotf(Float(heroMoveDirection.dx), Float(heroMoveDirection.dy))
                if  magnitude > 0.0 || moveFacing != nil {
                    hero.moveInDirection(heroMoveDirection, withTimeInterval: timeSinceLast, facing: moveFacing)
                }
            }
            else {
                if let heroFaceLocation = player.heroFaceLocation {
                    hero.faceToPosition(heroFaceLocation)
                }
                
                if player.moveForward {
                    hero.moveInMoveDirection(.Forward, withTimeInterval: timeSinceLast)
                }
                else if player.moveBackward {
                    hero.moveInMoveDirection(.Back, withTimeInterval: timeSinceLast)
                }
 
                if player.moveLeft {
                    hero.moveInMoveDirection(.Left, withTimeInterval: timeSinceLast)
                }
                else if player.moveRight {
                    hero.moveInMoveDirection(.Right, withTimeInterval: timeSinceLast)
                }
            }
 
            if player.fireAction {
                hero.performAttackAction()
            }
        }
    }
 
    override func didSimulatePhysics() {
        if paused {
            return
        }
        
        for player in players {
            if let defaultHero = player?.hero {
                let heroPosition = defaultHero.position
                var worldPosition = world.position
                
                let yCoordinate = worldPosition.y + heroPosition.y
                if yCoordinate < Constants.minimumDistanceFromHeroToVisibleEdge {
                    worldPosition.y = worldPosition.y - yCoordinate + Constants.minimumDistanceFromHeroToVisibleEdge
                    worldMovedForUpdate = true
                } else if yCoordinate > (frame.size.height - Constants.minimumDistanceFromHeroToVisibleEdge) {
                    worldPosition.y = worldPosition.y + (frame.size.height - yCoordinate) - Constants.minimumDistanceFromHeroToVisibleEdge
                    worldMovedForUpdate = true
                }
                
                
                let xCoordinate = worldPosition.x + heroPosition.x
                if xCoordinate < Constants.minimumDistanceFromHeroToVisibleEdge {
                    worldPosition.x = worldPosition.x - xCoordinate + Constants.minimumDistanceFromHeroToVisibleEdge
                    worldMovedForUpdate = true
                } else if xCoordinate > (frame.size.width - Constants.minimumDistanceFromHeroToVisibleEdge) {
                    worldPosition.x = worldPosition.x + (frame.size.width - xCoordinate) - Constants.minimumDistanceFromHeroToVisibleEdge
                    worldMovedForUpdate = true
                }
                
                world.position = worldPosition
                
                updateAfterSimulatingPhysics()
                
                worldMovedForUpdate = false
            }
        }
    }
 
    func updateWithTimeSinceLastUpdate(timeSinceLast: NSTimeInterval) {
        for hero in heroes {
            hero.updateWithTimeSinceLastUpdate(timeSinceLast)
        }
 
        levelBoss?.updateWithTimeSinceLastUpdate(timeSinceLast)
 
        for cave in goblinCaves {
            cave.updateWithTimeSinceLastUpdate(timeSinceLast)
        }
    }
 
    func updateAfterSimulatingPhysics() {
        var position = defaultPlayer.hero!.position
 
        for tree in trees {
            if tree.position.distanceToPoint(position) < 1024 {
                tree.updateAlphaWithScene(self)
            }
        }
 
        if !worldMovedForUpdate {
            return
        }
 
        for emitter in particleSystems {
            var emitterIsVisible = (emitter.position.distanceToPoint(position) < 1024)
            if !emitterIsVisible && !emitter.paused {
                emitter.paused = true
            } else if emitterIsVisible && emitter.paused {
                emitter.paused = false
            }
        }
 
        for sprite in parallaxSprites {
            if sprite.position.distanceToPoint(position) < 1024 {
                sprite.updateOffset()
            }
        }
    }
 
    // MARK: SKPhysicsContactDelegate
 
    func didBeginContact(contact: SKPhysicsContact) {
        if let character = contact.bodyA.node as? Character {
            character.collidedWith(contact.bodyB)
        }
 
        if let character = contact.bodyB.node as? Character {
            character.collidedWith(contact.bodyA)
        }
 
        let rawProjectileType = ColliderType.Projectile.rawValue
        let bodyAIsProjectile = contact.bodyA.categoryBitMask & rawProjectileType == rawProjectileType
        let bodyBIsProjectile = contact.bodyB.categoryBitMask & rawProjectileType == rawProjectileType
        
        if bodyAIsProjectile || bodyBIsProjectile {
            // Conditionally set `bodyA` as the projectile; if it isn't then `bodyB` is the projectile.
            if let projectile = bodyAIsProjectile ? contact.bodyA.node : contact.bodyB.node {
                projectile.runAction(SKAction.removeFromParent())
                
                let emitter = projectileSparkEmitterTemplate.copy() as! SKEmitterNode
                addNode(emitter, atWorldLayer: .AboveCharacter)
                emitter.position = projectile.position
 
                runOneShotEmitter(emitter, withDuration: 0.15)
            }
        }
    }
 
    // MARK: Game Start
 
    func startLevel(heroType: Player.HeroType) {
        defaultPlayer.heroType = heroType
        addHeroForPlayer(defaultPlayer)
        
        if shouldCheat {
            var bossPosition = levelBoss.position
            bossPosition.x += 128
            bossPosition.y += 512
            defaultPlayer.hero!.position = bossPosition
        }
 
        // Setup the HUD for the default player.
        loadHUDForPlayer(defaultPlayer, atIndex: 0)
 
        configureGameControllers()
    }
 
    // MARK: Character Support
 
    func canSee(point: CGPoint, from vantagePoint: CGPoint) -> Bool {
        let rayStart = vantagePoint
        let rayEnd = point
 
        var wallFound = false
        physicsWorld.enumerateBodiesAlongRayStart(rayStart, end: rayEnd) { body, point, normal, stop in
            if body.categoryBitMask & ColliderType.Wall.rawValue == ColliderType.Wall.rawValue {
                wallFound = true
                stop.memory = true
            }
        }
 
        return !wallFound
    }
 
    // MARK: Hero Interactions
 
    func addHeroForPlayer(player: Player) -> HeroCharacter {
        if let hero = player.hero {
            if !hero.isDying {
                hero.removeFromParent()
            }
        }
 
        var spawnPosition = defaultSpawnPoint
 
        for aHero in heroes {
            if !aHero.isDying {
                spawnPosition = aHero.position
            }
        }
 
        var hero: HeroCharacter
        switch player.heroType! {
            case .Warrior:
                hero = Warrior(atPosition: spawnPosition, withPlayer: player)
 
            case .Archer:
                hero = Archer(atPosition: spawnPosition, withPlayer: player)
        }
 
        let emitter = spawnEmitterTemplate.copy() as! SKEmitterNode
        emitter.position = spawnPosition
        addNode(emitter, atWorldLayer: .AboveCharacter)
        runOneShotEmitter(emitter, withDuration: 0.15)
 
        hero.fadeIn(2.0)
        hero.addToScene(self)
        heroes.append(hero)
 
        player.hero = hero
 
        return hero
    }
 
    func heroWasKilled(hero: HeroCharacter) {
        for cave in goblinCaves {
            cave.stopGoblinsFromTargettingHero(hero)
        }
 
        let player = hero.player
 
        // Remove this hero from our list of heroes
        for (idx, aHero) in enumerate(heroes) {
            if aHero === hero {
                heroes.removeAtIndex(idx)
                break
            }
        }
 
        #if os(iOS)
        // Disable touch movement, otherwise new hero will try to move to previously-touched location.
        player.moveRequested = false
        #endif
 
        --player.livesLeft
 
        if player.livesLeft < 0 {
            // In a real game, you'd want to end the game when there are no lives left.
            return
        }
 
        updateHUDAfterHeroDeathForPlayer(hero.player)
 
        let hero = addHeroForPlayer(player)
 
        centerWorldOnCharacter(hero)
    }
 
    // MARK: HUD and Scores
 
    func loadHUDForPlayer(player: Player, atIndex index: Int) {
        let hudScene: SKScene = SKScene(fileNamed: Constants.hudNodeName)
        let hud = hudScene.children.first!.copy() as! SKNode
        hud.name = Constants.hudNodeName
        hud.position = CGPoint(x: CGFloat(0 + Constants.hudWidth * index), y: frame.size.height)
        addChild(hud)
        player.hudAvatar = hud.childNodeWithName(Constants.hudAvatarName) as! SKSpriteNode
        player.hudScore = hud.childNodeWithName(Constants.hudScoreName) as! SKLabelNode
        hud.enumerateChildNodesWithName(Constants.hudHeartName) { node, stop in
            player.hudLifeHearts.append(node as! SKSpriteNode)
        }
 
        updateHUDForPlayer(player)
    }
 
    func updateHUDForPlayer(player: Player) {
        player.hudScore.text = String.localizedStringWithFormat(NSLocalizedString("SCORE: %d", comment: ""), player.score)
    }
 
    func updateHUDAfterHeroDeathForPlayer(player: Player) {
        // Fade out the relevant heart - one-based livesLeft has already been decremented.
        let heartNumber = player.livesLeft
 
        let heart = player.hudLifeHearts[heartNumber]
        heart.runAction(SKAction.fadeAlphaTo(0.0, duration: 3.0))
    }
 
    func addToScore(amount: Int, afterEnemyKillWithProjectile projectile: SKNode) {
        if let player = projectile.userData?[Player.Keys.projectileUserDataPlayer] as? Player {
            player.score += amount
            updateHUDForPlayer(player)
        }
    }
 
    // MARK: World Construction
 
    func loadWorld() {
        physicsWorld.gravity = CGVector(dx: 0, dy: 0)
        physicsWorld.contactDelegate = self
        
        let scene = SKScene(fileNamed: "AdventureWorld")
        let templateWorld = scene.children.first!.copy() as! SKNode
 
        world.name = "world"
        addChild(world)
 
        populateLayersFromWorld(templateWorld)
        populateBackgroundTiles()
        populateWallsFromWorld(templateWorld)
        populateCharactersFromWorld(templateWorld)
        populateTreesFromWorld(templateWorld)
    }
 
    func populateLayersFromWorld(fromWorld: SKNode) {
        fromWorld.enumerateChildNodesWithName("layer*") { node, stop in
            let layer = SKNode()
            layer.name = node.name
            self.world.addChild(layer)
            self.layers.append(layer)
        }
    }
 
    func populateBackgroundTiles() {
        for tileNode in backgroundTiles {
            addNode(tileNode, atWorldLayer: .Ground)
        }
    }
 
    func populateWallsFromWorld(fromWorld: SKNode) {
        let ground = fromWorld.childNodeWithName("layerGround")!
        ground.enumerateChildNodesWithName("wall") { node, stop in
            // Unwrap the physics body to configure it.
            let wallNode = node.copy() as! SKNode
            wallNode.physicsBody!.dynamic = false
            wallNode.physicsBody!.categoryBitMask = ColliderType.Wall.rawValue
            self.addNode(wallNode, atWorldLayer: .Ground)
        }
    }
 
    func populateCharactersFromWorld(fromWorld: SKNode) {
        defaultSpawnPoint = fromWorld.childNodeWithName("//defaultSpawnPoint")!.position
        levelBoss = Boss(atPosition: fromWorld.childNodeWithName("//boss")!.position)
        levelBoss.addToScene(self)
        
        fromWorld.enumerateChildNodesWithName("//cave") { node, stop in
            let cave = Cave.Shared.template.copy() as! Cave
            cave.position = node.position
            cave.zRotation = node.zRotation
            cave.timeUntilNextGenerate = 5.0 + 5.0 * unitRandom()
            
            for _ in 0..<Cave.Constants.goblinCapacity {
                var goblin = Goblin(atPosition: node.position)
                goblin.cave = cave
                cave.inactiveGoblins.append(goblin)
            }
 
            // Make it aware!
            cave.intelligence = SpawnArtificialIntelligence(character: cave)
 
            self.goblinCaves.append(cave)
            self.parallaxSprites.append(cave)
            cave.addToScene(self)
        }
    }
 
    func populateTreesFromWorld(fromWorld: SKNode) {
        fromWorld.enumerateChildNodesWithName("//*Tree") { node, stop in
            var tree: Tree
            switch node.name {
            case let name where name == "smallTree":
                tree = Tree.Shared.smallTemplate.copy() as! Tree
            case let name where name == "bigTree":
                tree = Tree.Shared.largeTemplate.copy() as! Tree
                
                var emitter: SKEmitterNode
                if arc4random_uniform(2) == 1 {
                    emitter = self.leafEmitterATemplate.copy() as! SKEmitterNode
                } else {
                    emitter = self.leafEmitterBTemplate.copy() as! SKEmitterNode
                }
 
                emitter.position = node.position
                emitter.paused = true
                self.addNode(emitter, atWorldLayer: .AboveCharacter)
                self.particleSystems.append(emitter)
            default:
                return
            }
 
            tree.position = node.position
            tree.zRotation = unitRandom()
            self.addNode(tree, atWorldLayer: .Top)
            self.parallaxSprites.append(tree)
            self.trees.append(tree)
        }
    }
 
    func addNode(node: SKNode, atWorldLayer layer: WorldLayer) {
        let layerNode = layers[layer.rawValue]
 
        layerNode.addChild(node)
    }
 
    // MARK: Asset Pre-loading
    
    class func loadSceneAssetsWithCompletionHandler(completionHandler: AdventureScene -> Void) {
        dispatch_async(Constants.backgroundQueue) {
            Tree.loadSharedAssets()
            Warrior.loadSharedAssets()
            Archer.loadSharedAssets()
            Cave.loadSharedAssets()
            Goblin.loadSharedAssets()
            Boss.loadSharedAssets()
            
            let loadedScene = AdventureScene(size: CGSize(width: 1024, height: 768))
            loadedScene.loadBackgroundTiles()
            
            dispatch_async(dispatch_get_main_queue()) { completionHandler(loadedScene) }
        }
    }
 
    func loadBackgroundTiles() {
        var tileAtlas = SKTextureAtlas(named: "Tiles")
 
        for y in 0..<Constants.worldTileDivisor {
            for x in 0..<Constants.worldTileDivisor {
                let tileNumber = (y * Constants.worldTileDivisor) + x
                
                let tileNode = SKSpriteNode(texture: tileAtlas.textureNamed("tile\(tileNumber).png"))
 
                let xPosition = CGFloat((x * Constants.worldTileSize) - Constants.worldCenter + Constants.worldTileSize / 2)
                let yPosition = CGFloat((Constants.worldSize - (y * Constants.worldTileSize)) - Constants.worldCenter  - Constants.worldTileSize / 2)
 
                let position = CGPoint(x: xPosition, y: yPosition)
 
                tileNode.position = position
                tileNode.zPosition = -1.0
                tileNode.blendMode = .Replace
                backgroundTiles.append(tileNode)
            }
        }
    }
 
    // MARK: Camera Convenience
    
    func centerWorldOnPosition(position: CGPoint) {
        world.position = CGPoint(x: -position.x + CGRectGetMidX(frame),
            y: -position.y + CGRectGetMidY(frame))
        worldMovedForUpdate = true
    }
 
    func centerWorldOnCharacter(character: Character) {
        centerWorldOnPosition(character.position)
    }
 
    // MARK: Game Controllers
    
    func configureGameControllers() {
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: "gameControllerDidConnect:", name: GCControllerDidConnectNotification, object: nil)
        notificationCenter.addObserver(self, selector: "gameControllerDidDisconnect:", name: GCControllerDidDisconnectNotification, object: nil)
 
        configureConnectedGameControllers()
 
        GCController.startWirelessControllerDiscoveryWithCompletionHandler(nil)
    }
 
    func configureConnectedGameControllers() {
        let gameControllers = GCController.controllers() as! [GCController]
        
        for controller in gameControllers {
            let playerIndex = controller.playerIndex
            if playerIndex == GCControllerPlayerIndexUnset {
                continue
            }
 
            assignPresetController(controller, toIndex: playerIndex)
        }
 
        for controller in gameControllers {
            let playerIndex = controller.playerIndex
            if playerIndex != GCControllerPlayerIndexUnset {
                continue
            }
 
            assignUnknownController(controller)
        }
    }
 
    func gameControllerDidConnect(notification: NSNotification) {
        let controller = notification.object as! GCController
        let playerIndex = controller.playerIndex
        if playerIndex == GCControllerPlayerIndexUnset {
            assignUnknownController(controller)
        }
        else {
            assignPresetController(controller, toIndex: playerIndex)
        }
    }
 
    func gameControllerDidDisconnect(notification: NSNotification) {
        let controller = notification.object as! GCController
        for player in players {
            if let player = player {
                if player.controller == controller {
                    player.controller = nil
                }
            }
        }
    }
 
    func assignUnknownController(controller: GCController) {
        // Specifically declare `player` as mutable so that we can reassign it while processing.
        for (index, var player) in enumerate(players) {
            if player == nil {
                player = Player()
                players[index] = player
            }
 
            if player.controller != nil {
                continue
            }
 
            controller.playerIndex = index
            configureController(controller, forPlayer: player)
            return
        }
    }
 
    func assignPresetController(controller: GCController, toIndex index: Int) {
        var player = players[index]
        if player == nil {
            player = Player()
            players[index] = player
        }
 
        if player?.controller != nil && player?.controller != controller {
            assignUnknownController(controller)
            return
        }
 
        configureController(controller, forPlayer: player)
    }
 
    func configureController(controller: GCController, forPlayer player: Player) {
        player.controller = controller
 
        let directionPadMoveHandler: GCControllerDirectionPadValueChangedHandler = { dpad, x, y in
            let length = hypotf(x, y)
            if length > 0.0 {
                let inverseLength = 1 / length
                player.heroMoveDirection = CGVector(dx: CGFloat(x * inverseLength), dy: CGFloat(y * inverseLength))
            }
            else {
                player.heroMoveDirection = CGVector(dx: 0, dy: 0)
            }
        }
 
        let rightThumbstickHandler: GCControllerDirectionPadValueChangedHandler = { dpad, x, y in
            let length = hypotf(x, y)
            if length <= 0.5 {
                player.heroFaceLocation = nil
                return
            }
 
            let point = CGPoint(x: CGFloat(x), y: CGFloat(y))
            player.heroFaceLocation = point
        }
 
        controller.extendedGamepad?.leftThumbstick.valueChangedHandler = directionPadMoveHandler
        controller.extendedGamepad?.rightThumbstick.valueChangedHandler = rightThumbstickHandler
        controller.gamepad?.dpad.valueChangedHandler = directionPadMoveHandler
 
        let fireButtonHandler: GCControllerButtonValueChangedHandler = { button, value, pressed in
            player.fireAction = pressed
        }
 
        controller.gamepad?.buttonA.valueChangedHandler = fireButtonHandler
        controller.gamepad?.buttonB.valueChangedHandler = fireButtonHandler
        controller.extendedGamepad?.rightTrigger.valueChangedHandler = fireButtonHandler
 
        if player !== defaultPlayer && player.hero == nil {
            loadHUDForPlayer(player, atIndex: player.controller!.playerIndex)
            addHeroForPlayer(player)
        }
    }
}