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

/*
  Copyright (C) 2014 Apple Inc. All Rights Reserved.
  See LICENSE.txt for this sample’s licensing information
  
  Abstract:
  
        The primary scene class for Adventure
      
*/
 
import SpriteKit
 
// Set this to true to cheat and end up next to the level boss
let cheat = false
 
enum HeroClass {
  case Warrior
  case Archer
}
 
enum WorldLayer: Int {
    case Ground = 0, BelowCharacter, Character, AboveCharacter, Top
}
 
var sBackgroundTiles = [SKSpriteNode]()
var sSharedSmallTree = Tree(sprites: [SKSpriteNode](), usingOffset: 0.0)
var sSharedBigTree = Tree(sprites: [SKSpriteNode](), usingOffset: 0.0)
var sSharedLeafEmitterA = SKEmitterNode()
var sSharedLeafEmitterB = SKEmitterNode()
var sSharedProjectileSparkEmitter = SKEmitterNode()
var sSharedSpawnEmitter = SKEmitterNode()
var sSharedCave = Cave(sprites: [SKSpriteNode](), atPosition: CGPointZero, usingOffset: 0.0)
 
let kWorldTileDivisor = 32
let kWorldSize = 4096
let kWorldTileSize = kWorldSize / kWorldTileDivisor
let kWorldCenter = kWorldSize / 2
let kLevelMapSize = 256
let kLevelMapDivisor = (kWorldSize / kLevelMapSize)
 
let kStartLives = 3
let kWorldLayerCount = 5
let kMinTimeInterval = (1.0 / 60.0)
let kMinHeroToEdgeDistance: CGFloat = 256.0 // minimum distance between hero and edge of camera before moving camera
 
class AdventureScene: SKScene, SKPhysicsContactDelegate {
    // MARK: Properties
    
    // Where all the parts live.
    var world = SKNode()
    
    // Convenience properties for nodes that are updated during each pass. Populated during loading.
    var heroes: [HeroCharacter] = []
    var goblinCaves: [Cave] = []
    var levelBoss: Boss!
    var trees: [Tree] = []
    var particleSystems: [SKEmitterNode] = []
    var parallaxSprites: [ParallaxSprite] = []
    
    // Convenience property containing references to the 5 different layers in the world.
    var layers = [SKNode]()
    
    // Convenience properties for the nodes that make up the HUD.
    var hudAvatar: SKSpriteNode!
    var hudScore: SKLabelNode!
    var hudLifeHearts = [SKSpriteNode]()
    
    var defaultSpawnPoint = CGPointZero
    var defaultPlayer = Player()
    
    // Properties to keep track of details important to scene updates.
    var worldMovedForUpdate = false
    var lastUpdateTimeInterval: NSTimeInterval = 0
    
    // A closure to be called when didMoveToView(_:) completes.
    var finishedMovingToView: Void -> Void = {}
 
    // MARK: SKView Behaviors
 
    override func didMoveToView(view: SKView) {
        let backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
        
        // Complete the loading on a background queue to not take up the main queue's resources.
        dispatch_async(backgroundQueue) {
            self.loadHUD()
 
            self.updateHUDForPlayer(self.defaultPlayer)
            
            self.loadWorld()
            
            self.centerWorldOnPosition(self.defaultSpawnPoint)
 
            dispatch_async(dispatch_get_main_queue(), self.finishedMovingToView)
        }
    }
    
    // MARK: Scene Processing Support
    
    override func update(currentTime: NSTimeInterval) {
        var timeSinceLast = currentTime - lastUpdateTimeInterval
        lastUpdateTimeInterval = currentTime
        
        if timeSinceLast > 1 {
            timeSinceLast = kMinTimeInterval
            worldMovedForUpdate = true
        }
        
        updateWithTimeSinceLastUpdate(timeSinceLast)
        
        if defaultPlayer.hero == nil {
            return
        }
        
        let hero = defaultPlayer.hero!
        
        if hero.dying {
            return
        }
        
        if defaultPlayer.moveForward {
            hero.move(.Forward, withTimeInterval: timeSinceLast)
        }
        else if defaultPlayer.moveBackward {
            hero.move(.Back, withTimeInterval: timeSinceLast)
        }
        
        if defaultPlayer.moveLeft {
            hero.move(.Left, withTimeInterval: timeSinceLast)
        }
        else if defaultPlayer.moveRight {
            hero.move(.Right, withTimeInterval: timeSinceLast)
        }
        
        if defaultPlayer.fireAction {
            hero.performAttackAction()
        }
        
        #if os(iOS)
        if defaultPlayer.targetLocation != CGPointZero {
            if defaultPlayer.fireAction {
                hero.faceTo(defaultPlayer.targetLocation)
            }
            
            if defaultPlayer.moveRequested {
                if defaultPlayer.targetLocation != hero.position {
                    hero.moveTowards(defaultPlayer.targetLocation, withTimeInterval: timeSinceLast)
                }
                else {
                    defaultPlayer.moveRequested = false
                }
            }
        }
        #endif
    }
    
    override func didSimulatePhysics() {
        if let defaultHero = defaultPlayer.hero {
            let heroPosition = defaultHero.position
            var worldPos = world.position
            
            let yCoordinate = worldPos.y + heroPosition.y
            if yCoordinate < kMinHeroToEdgeDistance {
                worldPos.y = worldPos.y - yCoordinate + kMinHeroToEdgeDistance
                worldMovedForUpdate = true
            } else if yCoordinate > (frame.size.height - kMinHeroToEdgeDistance) {
                worldPos.y = worldPos.y + (frame.size.height - yCoordinate) - kMinHeroToEdgeDistance
                worldMovedForUpdate = true
            }
            
            let xCoordinate = worldPos.x + heroPosition.x
            if xCoordinate < kMinHeroToEdgeDistance {
                worldPos.x = worldPos.x - xCoordinate + kMinHeroToEdgeDistance
                worldMovedForUpdate = true
            } else if xCoordinate > (frame.size.width - kMinHeroToEdgeDistance) {
                worldPos.x = worldPos.x + (frame.size.width - xCoordinate) - kMinHeroToEdgeDistance
                worldMovedForUpdate = true
            }
            
            world.position = worldPos
            
            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
        if contact.bodyA.categoryBitMask & rawProjectileType == rawProjectileType || contact.bodyB.categoryBitMask & rawProjectileType == rawProjectileType {
            if let projectile = (contact.bodyA.categoryBitMask & rawProjectileType) == rawProjectileType ? contact.bodyA.node : contact.bodyB.node {
                projectile.runAction(SKAction.removeFromParent())
                
                let emitter = sSharedProjectileSparkEmitter.copy() as SKEmitterNode
                addNode(emitter, atWorldLayer: .AboveCharacter)
                emitter.position = projectile.position
                
                runOneShotEmitter(emitter, withDuration: 0.15)
            }
        }
    }
    
    // MARK: Game Start
 
    func startLevel(charClass: HeroClass) {
        defaultPlayer.charClass = charClass
        addHeroForPlayer(defaultPlayer)
        
        if cheat {
            var bossPosition = levelBoss!.position
            bossPosition.x += 128
            bossPosition.y += 512
            defaultPlayer.hero!.position = bossPosition
        }
    }
 
    // MARK: Character Support
 
    func canSee(point: CGPoint, from vantagePoint: CGPoint) -> Bool {
        let rayStart = vantagePoint
        let rayEnd = point
        
        var canSee = true
        physicsWorld.enumerateBodiesAlongRayStart(rayStart, end: rayEnd) { body, point, normal, stop in
            if body.categoryBitMask & ColliderType.Wall.rawValue == ColliderType.Wall.rawValue || body.node?.name == "wall" {
                canSee = false
                stop.memory = true
            }
        }
        
        return canSee
    }
    
    // MARK: Hero Interactions
 
    func addHeroForPlayer(player: Player) -> HeroCharacter {
        if let hero = player.hero {
            if !hero.dying {
                hero.removeFromParent()
            }
        }
        
        let spawnPos = defaultSpawnPoint
        
        var hero: HeroCharacter
        switch player.charClass! {
        case .Warrior:
            hero = Warrior(atPosition: spawnPos, withPlayer: player)
            
        case .Archer:
            hero = Archer(atPosition: spawnPos, withPlayer: player)
        }
        
        let emitter = sSharedSpawnEmitter.copy() as SKEmitterNode
        emitter.position = spawnPos
        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, obj) in enumerate(heroes) {
            if obj === 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 loadHUD() {
        let hudScene: SKScene = SKScene.nodeWithName("AdventureHUD")
        let hud = hudScene.children[0].copy() as SKNode
        hud.position = CGPoint(x: 0, y: self.frame.size.height)
        addChild(hud)
        hudAvatar = hud.childNodeWithName("hudAvatar") as SKSpriteNode
        hudScore = hud.childNodeWithName("hudScore") as SKLabelNode
        hud.enumerateChildNodesWithName("hudHeart") { node, stop in
            self.hudLifeHearts.append(node as SKSpriteNode)
        }
    }
    
    func updateHUDForPlayer(player: Player) {
        hudScore.text = "SCORE: \(player.score)"
    }
    
    func updateHUDAfterHeroDeathForPlayer(player: Player) {
        // Fade out the relevant heart - one-based livesLeft has already been decremented.
        let heartNumber = player.livesLeft
        
        let heart = hudLifeHearts[heartNumber]
        heart.runAction(SKAction.fadeAlphaTo(0.0, duration: 3.0))
    }
    
    func addToScore(amount: Int, afterEnemyKillWithProjectile projectile: SKNode) {
        if let player = projectile.userData?[kPlayer] 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 = SKScene.nodeWithName("AdventureWorld")
        let templateWorld = scene.children[0].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) -> Void in
            let layer = SKNode()
            layer.name = node.name
            self.world.addChild(layer)
            self.layers.append(layer)
        }
    }
    
    func populateBackgroundTiles() {
        for tileNode in sBackgroundTiles {
            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 = sSharedCave.copy() as Cave
            cave.position = node.position
            cave.zRotation = node.zRotation
            cave.timeUntilNextGenerate = 5.0 + 5.0 * unitRandom()
            
            for _ in 0..<caveCapacity {
                var goblin = Goblin(atPosition: node.position)
                goblin.cave = cave
                cave.inactiveGoblins.append(goblin)
            }
            
            // Make it aware!
            cave.intelligence = SpawnArtificialIntelligence(character: cave, target: nil)
            
            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 = sSharedSmallTree.copy() as Tree
            case let name where name == "bigTree":
                tree = sSharedBigTree.copy() as Tree
                
                var emitter: SKEmitterNode
                if arc4random_uniform(2) == 1 {
                    emitter = sSharedLeafEmitterA.copy() as SKEmitterNode
                } else {
                    emitter = sSharedLeafEmitterB.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: Void -> Void) {
        let queue = dispatch_get_main_queue()
 
        let backgroundQueue = dispatch_get_global_queue(CLong(DISPATCH_QUEUE_PRIORITY_HIGH), 0)
        dispatch_async(backgroundQueue) {
            AdventureScene.loadBackgroundTiles()
            
            Goblin.loadSharedAssets()
            Warrior.loadSharedAssets()
            Archer.loadSharedAssets()
            Cave.loadSharedAssets()
            HeroCharacter.loadSharedHeroAssets()
            Boss.loadSharedAssets()
            
            sSharedLeafEmitterA = .nodeWithName("Leaves_01")
            sSharedLeafEmitterB = .nodeWithName("Leaves_02")
            sSharedProjectileSparkEmitter = .nodeWithName("ProjectileSplat")
            sSharedSpawnEmitter = .nodeWithName("Spawn")
            
            // Load Trees
            let atlas = SKTextureAtlas(named: "Environment")
            var sprites = [
                SKSpriteNode(texture: atlas.textureNamed("small_tree_base.png")),
                SKSpriteNode(texture: atlas.textureNamed("small_tree_middle.png")),
                SKSpriteNode(texture: atlas.textureNamed("small_tree_top.png"))
            ]
            sSharedSmallTree = Tree(sprites: sprites, usingOffset: 25.0)
            sSharedSmallTree.name = "smallTree"
            
            sprites = [
                SKSpriteNode(texture: atlas.textureNamed("big_tree_base.png")),
                SKSpriteNode(texture: atlas.textureNamed("big_tree_middle.png")),
                SKSpriteNode(texture: atlas.textureNamed("big_tree_top.png"))
            ]
            sSharedBigTree = Tree(sprites: sprites, usingOffset: 150.0)
            sSharedBigTree.name = "bigTree"
            sSharedBigTree.fadeAlpha = true
            
            
            sprites = [
                sSharedCaveBase.copy() as SKSpriteNode,
                sSharedCaveTop.copy() as SKSpriteNode
            ]
            sSharedCave = Cave(sprites: sprites, atPosition: CGPointZero, usingOffset: 50.0)
            sSharedCave.movementSpeed = 0.0
            sSharedCave.name = "goblinCave"
            
            dispatch_async(queue, completionHandler)
        }
    }
 
    class func loadBackgroundTiles() {
        var tileAtlas = SKTextureAtlas(named: "Tiles")
 
        for y in 0..<kWorldTileDivisor {
            for x in 0..<kWorldTileDivisor {
                let tileNumber = (y * kWorldTileDivisor) + x
                
                let tileNode = SKSpriteNode(texture: tileAtlas.textureNamed("tile\(tileNumber).png"))
 
                let xPosition = CGFloat((x * kWorldTileSize) - kWorldCenter + kWorldTileSize / 2)
                let yPosition = CGFloat((kWorldSize - (y * kWorldTileSize)) - kWorldCenter  - kWorldTileSize / 2)
 
                let position = CGPoint(x: xPosition, y: yPosition)
 
                tileNode.position = position
                tileNode.zPosition = -1.0
                tileNode.blendMode = .Replace
                sBackgroundTiles.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)
    }
}