DemoBots/Entities/FlyingBot.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
A floating `TaskBot` with a radius blast attack. This `GKEntity` subclass allows for convenient construction of an entity with appropriate `GKComponent` instances. |
*/ |
import SpriteKit |
import GameplayKit |
class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType { |
// MARK: Static Properties |
/// The size to use for the `FlyingBot`s animation textures. |
static var textureSize = CGSize(width: 144.0, height: 144.0) |
/// The size to use for the `FlyingBot`'s shadow texture. |
static var shadowSize = CGSize(width: 60.0, height: 27.0) |
/// The actual texture to use for the `FlyingBot`'s shadow. |
static var shadowTexture: SKTexture = { |
let shadowAtlas = SKTextureAtlas(named: "Shadows") |
return shadowAtlas.textureNamed("FlyingBotShadow") |
}() |
/// The offset of the `FlyingBot`'s shadow from its center position. |
static var shadowOffset = CGPoint(x: 0.0, y: -58.0) |
/// The animations to use when a `FlyingBot` is in its "good" state. |
static var goodAnimations: [AnimationState: [CompassDirection: Animation]]? |
/// The animations to use when a `FlyingBot` is in its "bad" state. |
static var badAnimations: [AnimationState: [CompassDirection: Animation]]? |
// MARK: TaskBot Properties |
override var goodAnimations: [AnimationState: [CompassDirection: Animation]] { |
return FlyingBot.goodAnimations! |
} |
override var badAnimations: [AnimationState: [CompassDirection: Animation]] { |
return FlyingBot.badAnimations! |
} |
// MARK: Initialization |
required init(isGood: Bool, goodPathPoints: [CGPoint], badPathPoints: [CGPoint]) { |
super.init(isGood: isGood, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints) |
// Determine initial animations and charge based on the initial state of the bot. |
let initialAnimations: [AnimationState: [CompassDirection: Animation]] |
let initialCharge: Double |
if isGood { |
guard let goodAnimations = FlyingBot.goodAnimations else { |
fatalError("Attempt to access FlyingBot.goodAnimations before they have been loaded.") |
} |
initialAnimations = goodAnimations |
initialCharge = 0.0 |
} |
else { |
guard let badAnimations = FlyingBot.badAnimations else { |
fatalError("Attempt to access FlyingBot.badAnimations before they have been loaded.") |
} |
initialAnimations = badAnimations |
initialCharge = GameplayConfiguration.FlyingBot.maximumCharge |
} |
// Create components that define how the entity looks and behaves. |
let renderComponent = RenderComponent() |
addComponent(renderComponent) |
let orientationComponent = OrientationComponent() |
addComponent(orientationComponent) |
let shadowComponent = ShadowComponent(texture: FlyingBot.shadowTexture, size: FlyingBot.shadowSize, offset: FlyingBot.shadowOffset) |
addComponent(shadowComponent) |
let animationComponent = AnimationComponent(textureSize: FlyingBot.textureSize, animations: initialAnimations) |
addComponent(animationComponent) |
let intelligenceComponent = IntelligenceComponent(states: [ |
TaskBotAgentControlledState(entity: self), |
FlyingBotPreAttackState(entity: self), |
FlyingBotBlastState(entity: self), |
TaskBotZappedState(entity: self) |
]) |
addComponent(intelligenceComponent) |
let physicsBody = SKPhysicsBody(circleOfRadius: GameplayConfiguration.TaskBot.physicsBodyRadius, center: GameplayConfiguration.TaskBot.physicsBodyOffset) |
let physicsComponent = PhysicsComponent(physicsBody: physicsBody, colliderType: .TaskBot) |
addComponent(physicsComponent) |
let chargeComponent = ChargeComponent(charge: initialCharge, maximumCharge: GameplayConfiguration.FlyingBot.maximumCharge) |
chargeComponent.delegate = self |
addComponent(chargeComponent) |
// Connect the `PhysicsComponent` and the `RenderComponent`. |
renderComponent.node.physicsBody = physicsComponent.physicsBody |
// Connect the `RenderComponent` and `ShadowComponent` to the `AnimationComponent`. |
renderComponent.node.addChild(animationComponent.node) |
animationComponent.shadowNode = shadowComponent.node |
// Specify the offset for beam targeting. |
beamTargetOffset = GameplayConfiguration.FlyingBot.beamTargetOffset |
} |
required init?(coder aDecoder: NSCoder) { |
fatalError("init(coder:) has not been implemented") |
} |
// MARK: ContactableType |
override func contactWithEntityDidBegin(_ entity: GKEntity) { |
super.contactWithEntityDidBegin(entity) |
guard !isGood else { return } |
var shouldStartAttack = false |
if let otherTaskBot = entity as? TaskBot, otherTaskBot.isGood { |
// Contact with good task bot will trigger an attack. |
shouldStartAttack = true |
} |
else if let playerBot = entity as? PlayerBot, !playerBot.isPoweredDown { |
// Contact with an active `PlayerBot` will trigger an attack. |
shouldStartAttack = true |
} |
if let stateMachine = component(ofType: IntelligenceComponent.self)?.stateMachine, shouldStartAttack { |
stateMachine.enter(FlyingBotPreAttackState.self) |
} |
} |
// MARK: ChargeComponentDelegate |
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) { |
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return } |
intelligenceComponent.stateMachine.enter(TaskBotZappedState.self) |
isGood = !chargeComponent.hasCharge |
} |
// MARK: ResourceLoadableType |
static var resourcesNeedLoading: Bool { |
return goodAnimations == nil || badAnimations == nil |
} |
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) { |
// Load `TaskBot`s shared assets. |
super.loadSharedAssets() |
let flyingBotAtlasNames = [ |
"FlyingBotGoodWalk", |
"FlyingBotGoodAttack", |
"FlyingBotBadWalk", |
"FlyingBotBadAttack", |
"FlyingBotZapped" |
] |
/* |
Preload all of the texture atlases for `FlyingBot`. This improves |
the overall loading speed of the animation cycles for this character. |
*/ |
SKTextureAtlas.preloadTextureAtlasesNamed(flyingBotAtlasNames) { error, flyingBotAtlases in |
if let error = error { |
fatalError("One or more texture atlases could not be found: \(error)") |
} |
/* |
This closure sets up all of the `FlyingBot` animations |
after the `FlyingBot` texture atlases have finished preloading. |
*/ |
goodAnimations = [:] |
goodAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[0], withImageIdentifier: "FlyingBotGoodWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale") |
goodAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[1], withImageIdentifier: "FlyingBotGoodAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") |
badAnimations = [:] |
badAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[2], withImageIdentifier: "FlyingBotBadWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale") |
badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[3], withImageIdentifier: "FlyingBotBadAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") |
badAnimations![.zapped] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[4], withImageIdentifier: "FlyingBotZapped", forAnimationState: .zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") |
// Invoke the passed `completionHandler` to indicate that loading has completed. |
completionHandler() |
} |
} |
static func purgeResources() { |
goodAnimations = nil |
badAnimations = nil |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13