使用GKComponent制作可重复使用的盾牌

时间:2017-09-28 14:58:13

标签: ios game-physics gameplay-kit

我正在尝试制作一个简单的游戏:屏幕底部的太空飞船射击小行星从屏幕顶部“掉落”。

我正在学习ECS和GameplayKit,并一直试图将盾牌变成一个组件。我非常依赖Apple的DemoBots示例应用程序,并从示例代码中解除了PhysicsComponentColliderTypeContactNotifiableType

盾牌需要渲染与之相关的资产(一个用于完全盾牌,一个用于半盾牌),一个与船舶不同的物理体,因为它的半径明显大于船舶,并跟踪它的状态。为此,我写道:

final class ShieldComponent: GKComponent {
    enum ShieldLevel: Int {
        case full = 0, half, none
    }

    var currentShieldLevel: ShieldLevel = .full {
        didSet {
            switch currentShieldLevel {
            case .full:
                node.isHidden = false
                node.texture = SKTexture(image: #imageLiteral(resourceName: "shield"))
            case .half:
                node.isHidden = false
                node.texture = SKTexture(image: #imageLiteral(resourceName: "damagedShield"))
            case .none:
                node.isHidden = true
            }
        }
    }

    let node: SKSpriteNode

    override init() {
        node = SKSpriteNode(imageNamed: "shield")
        super.init()

        node.physicsBody = {
            let physicsBody = SKPhysicsBody(circleOfRadius: node.frame.size.width / 2)
            physicsBody.pinned = true
            physicsBody.allowsRotation = false
            physicsBody.affectedByGravity = false

            ColliderType.definedCollisions[.shield] = [
                .obstacle,
                .powerUp
            ]

            physicsBody.categoryBitMask = ColliderType.shield.rawValue
            physicsBody.contactTestBitMask = ColliderType.obstacle.rawValue
            physicsBody.collisionBitMask = ColliderType.obstacle.rawValue
            return physicsBody
        }()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func loseShields() {
        if let newShieldLevel = ShieldLevel(rawValue: self.currentShieldLevel.rawValue + 1) {
            self.currentShieldLevel = newShieldLevel
        }
    }

    func restoreShields() {
        self.currentShieldLevel = .full
    }
}

在我的Ship初始化程序中,我这样做:

    let shieldComponent = ShieldComponent()
    renderComponent.node.addChild(shieldComponent.node)

如果我可以重用RenderComponent,并且我已经使用了我的船和小行星PhysicsComponent子类,那么来自DemoBots的GKEntity会很棒,但组件不能有组件。我已将ShieldComponent设为ContactNotifiableType,但因为屏蔽节点实际上并不属于船舶实体。

我知道我明显错了,我不知道如何纠正这个问题。我希望得到一个如何制作屏蔽组件的例子。

1 个答案:

答案 0 :(得分:1)

您必须了解组件只能处理一种行为。所以git摆脱了init()函数中的物理代码,而是构建了一个类似于DemoBots中的物理组件。

根据自己的喜好调整渲染组件。使用DemoBots代码的问题在于它并不完全适合。所以我们来调整一下

class RenderComponent: GKComponent {
// MARK: Properties

// The `RenderComponent` vends a node allowing an entity to be rendered in a scene.
@objc let node = SKNode()

var sprite = SKSpriteNode

// init
init(imageNamed name: String) {
    self.sprite = SKSpriteNode(imageNamed: name)
}
// MARK: GKComponent

override func didAddToEntity() {
    node.entity = entity
}

override func willRemoveFromEntity() {
    node.entity = nil
}

}

final class ShieldComponent: GKComponent {


    var node : SKSpriteNode
    //add reference to ship entity
    weak var ship: Ship?


    enum ShieldLevel: Int {
        case full = 0, half, none
    }

    var currentShieldLevel: ShieldLevel = .full {
        didSet {
            switch currentShieldLevel {
            case .full:
                node.isHidden = false
                node.texture = SKTexture(image: #imageLiteral(resourceName: "shield"))
            case .half:
                node.isHidden = false
                node.texture = SKTexture(image: #imageLiteral(resourceName: "damagedShield"))
            case .none:
                node.isHidden = true
            }
        }
    }

    // Grab the visual component from the entity. Unwrap it with a Guard. If the Entity doesnt have the component you get an error.
    var visualComponentRef : RenderComponent {
        guard let renderComponent = ship?.component(ofType: RenderComponent.self) else {
            fatalError("entity must have a render component")
        }
    }

    override init(shipEntity ship: Ship) {
    let visualComponent = RenderComponent(imageNamed: "imageName")
    node = visualComponent.sprite
        self.ship = ship
    super.init()

       // get rid of this. Use a Physics Component for this, Kep your components to one behaviour only. Make them as dumb as possible.
//    node.physicsBody = {
//        let physicsBody = SKPhysicsBody(circleOfRadius: node.frame.size.width / 2)
//        physicsBody.pinned = true
//        physicsBody.allowsRotation = false
//        physicsBody.affectedByGravity = false
//
//        ColliderType.definedCollisions[.shield] = [
//            .obstacle,
//            .powerUp
//        ]
//
//        physicsBody.categoryBitMask = ColliderType.shield.rawValue
//        physicsBody.contactTestBitMask = ColliderType.obstacle.rawValue
//        physicsBody.collisionBitMask = ColliderType.obstacle.rawValue
//        return physicsBody
//    }()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func loseShields() {
    if let newShieldLevel = ShieldLevel(rawValue: self.currentShieldLevel.rawValue + 1) {
        self.currentShieldLevel = newShieldLevel
    }
}

func restoreShields() {
    self.currentShieldLevel = .full
    }

};

确保查看我如何更改组件与实体的交互。您可以直接为Ship实体创建引用对象。或者您可以查看天气与否ShieldComponententity entity?属性。 (小心。它是可选的,所以打开它。

获得实体引用后,您可以搜索其他组件并检索使用component(ofType:_)属性。 例如ship?.component(ofType: RenderComponent.self)

除此之外,我认为你有一个不错的盾牌组件