首页 > 解决方案 > Swift SpriteKit 类结构的良好实践

问题描述

我正在使用 Swift 开发 iOS 纸牌游戏,并且在使用 SpriteKit 和 iOS 设计游戏框架时有一个关于良好实践的问题。我的基本结构如下:

class Card: SpriteKitNode {
    cardValue: Int

    func action() {}
}

struct Player {
    playerName = "Joe" 
    playerPile = [Card]()
    playerStack = [Card]()
}

struct Game {
    // Create players and deals out the cards to each player pile. 
}

每个玩家都有几堆卡片,它们都聚集在游戏结构中。我游戏中的大部分牌都是独立的牌。因此,如果玩家 1 打出一张牌,它对其他玩家没有影响。但是,我的游戏中的一些牌具有旨在影响其他玩家牌组的动作,具体取决于所玩的牌。

我的问题是,卡片“动作”听起来应该在卡片类中定义。卡本身的一个特点就是它有这个能力。但是,当我考虑如何实现这一点时,我不确定它会如何影响游戏级别以访问其他玩家堆。那么,当“卡片”不知道有多少玩家、它属于哪个玩家以及如何访问另一个玩家的牌堆时,尝试定义具有上游影响的动作时,最佳实践是什么?

我想实现的动作示例:玩家 1 可以将任何玩家堆栈中的顶牌移动到任何其他玩家堆栈中的顶牌。因此,玩家 1 可以根据棋盘上玩家的数量将最上面的牌从玩家 2 移到 1、2 到 3 或任何其他组合。我以为我可以通过向动作函数 action(moveFrom: Player1, moveTo: Player3) 传递大量参数来做到这一点,但我想我会来这里寻找最佳实践。

还有一些其他的动作可能会根据打出的牌有不同的输入。这些应该是单独的功能,还是都内置在一个“卡片行动”功能中?

// Possibly how this function might look. 
func action(moveFrom: Player, moveTo: Player) {
    let cardMoved = moveFrom.playerPile[0]
    moveTo.playerPile.append(cardMoved)
}

编辑 - 跟进问题

更改为 POP 后,我还有一些问题困扰着我如何实施。

  1. 我的动作函数不允许我改变玩家已选择的牌堆。错误 =“不能在不可变值上使用变异成员:'fromPlayer' 是一个 'let' 常量”。这是否意味着每次调用每个玩家时我都必须为每个玩家销毁、创建和归还新桩,而不仅仅是修改现有的?这似乎效率很低有没有更好的方法来做到这一点?
  2. 尝试调用我的操作函数时出现另一个错误。我已经检查过我的卡确实是“切片卡”,但我收到错误消息“'卡'类型的值没有成员'动作'”。
protocol ActionCard {
    func action(fromPlayer: Player, toPlayer: Player)
}

class Card {

}

class SliceCard: Card, ActionCard {

    func action(fromPlayer: Player, toPlayer: Player) {
        let cardTaken = fromPlayer.stack.removeLast()
        toPlayer.stack.append(cardTaken)
    }

}

struct Player {
    var stack = [Card]()

    func playCard(card: Card, fromPlayer: Player, toPlayer: Player) {
        if card is SliceCard {
            card.action(fromPlayer: fromPlayer, toPlayer: toPlayer)
        }
    }
}

let player1 = Player()
let player2 = Player()
let cardSelected = SliceCard()

player1.playCard(card: cardSelected, fromPlayer: player1, toPlayer: player2)

标签: iosswiftclassmodelsprite-kit

解决方案


有趣的问题。我建议您采用 POP(面向协议的编程)方法。

播放器类型

首先,我建议使用 Player 类型的类,因为您希望将相同的实例传递给其他方法/操作,并希望这些方法能够改变原始实例。

您仍然可以使用 struct + inout 参数,但使用类感觉更正确。

class Player {

    let name: String
    var pile: [Card] = []
    var stack: [Card] = []

    init(name: String) {
        self.name = name
    }

}

ActionError 枚举

只需创建一个枚举并为动作可能引发的每个可能的错误添加一个案例

enum ActionError: Error {
    case playerHasNoCards
    // add more errors here
}

BaseCard 类

你把任何常见的所有东西都放在这里Card

class BaseCard: SKSpriteNode {

    let cardValue: Int

    init(cardValue: Int) {
        self.cardValue = cardValue
        let texture = SKTexture(imageNamed: "card_image")
        super.init(texture: texture, color: .clear, size: texture.size())
    }

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

}

卡协议

在这里,您要求为了符合Card类型必须

  1. 是继承自 BaseCard 的类
  2. 并且必须有一个action(...)方法

这是代码

protocol Card: BaseCard {

    func action(currentPlayer: Player, destinatonPlayer: Player, allPlayers: [Player]) throws

}

请注意,action 方法应该接收您要执行的任何操作所需的所有参数。

你的第一张卡片

最后你可以实现你的第一张卡

class CardToStoleACardFromAnotherPlayer: BaseCard, Card {

    func action(currentPlayer: Player, destinatonPlayer: Player, allPlayers: [Player]) throws {
        guard destinatonPlayer.pile.isEmpty == false else { throw ActionError.playerHasNoCards }
        let card = destinatonPlayer.pile.removeFirst()
        currentPlayer.pile.append(card)
    }

}

创建任意数量的类,您将在每个类中写入不同的逻辑。

例子

class CardToStoleAllCardsFromAllPlayers: BaseCard, Card {

    func action(currentPlayer: Player, destinatonPlayer: Player, allPlayers: [Player]) throws {
        // ...
    }

}

class CardToGiftACardToAnotherPlayer: BaseCard, Card {

    func action(currentPlayer: Player, destinatonPlayer: Player, allPlayers: [Player]) throws {
        // ...
    }

}

注意事项

现在,当您选择一个Card并且想要执行它的操作时,只需调用传递所有参数的操作方法。

根据包含在该变量中的实例类型 ( CardToStoleACardFromAnotherPlayer, CardToStoleAllCardsFromAllPlayers, CardToGiftACardToAnotherPlayer, ...),将执行不同的逻辑。


推荐阅读