【问题标题】:Good Practices for Swift SpriteKit Class StructureSwift SpriteKit 类结构的良好实践
【发布时间】:2019-12-27 17:22:06
【问题描述】:

我正在使用 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)

【问题讨论】:

    标签: ios swift class model sprite-kit


    【解决方案1】:

    有趣的问题。我建议您采用 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 并想要执行其操作时,只需调用传递所有参数的操作方法即可。

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

    【讨论】:

    • – 谢谢!这对结构非常详细和有用。关于上面列出的这个实现,我有一些后续问题。想法?
    • @SharpSharpLes 你的问题与你没有完全按照我的建议有关;) Player 应该是一个类
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-08
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    相关资源
    最近更新 更多