用 Swift 语言和 SpriteKit 创建有人工智能的井字游戏

本文讲的是用 Swift 语言和 SpriteKit 创建有人工智能的井字游戏,


我对(自我)学习有着很强的热情并且非常着迷。最近,我提出了一个利用制作游戏的理论应用到应用程序开发中来提高用户体验的假说。很多人提出“游戏化”这类的流行词,通过应用程序与用户之间的交互,以及让用户主动参与的方式去取悦用户,达到解决应用程序的痛点的难题。无论你的应用程序到底提供了什么内容。我们今天不会讨论这个(我甚至都不会提起增加游戏感行为的倡导者们是否玩游戏这样的问题。) 取而代之,我们会使用 SpriteKitGameplayKit 和 Swift 来建立一个游戏。

抑制下你的期望

在你野心勃勃(准备)创建一个高居榜单的应用程序之前,我要告诉你这不是我们今天的目标。我们准备只看冰山一角,创建一个简单的井字游戏 Tic Tac Toe。在我们着手工作后,我会增加一个由计算机控制的AI(人工智能) 对手(供)允许你对战。

第一部分 - 原理

Apple 公司在召开 WWDC2013 期间发布了 SpriteKit,这给开发者一个比玩转自己(创建的)框架更快建立游戏应用程序的可选方案。由于游戏应用程序这个类别在 Apple 的生态系统中占据了大部分的下载量,这就一点儿都不奇怪 Apple 公司致力于游戏社区的发展并且从让程序开发者们更加简单的创建新的 iOS,macOS,watchOS 和 tvOS 游戏获得巨大的利益。

SpriteKit 也通常被引用为 sprites,是一个处理渲染,图形动画和图片的框架。作为一个程序开发者,你决定了改变什么,SpriteKit 就去处理显示这些变化的工作。你能在这里读到更多有关SpriteKit的内容。我也强烈推荐你去阅读 SpriteKit编程指导 获取更多框架提供的其他特性,例如处理声音的播放和 Sprite 物理模型。

SpriteKit 处理你游戏的运行循环并提供多个地方让开发者在每一帧更新游戏内容。下图展示了每一帧从开始更新到最终渲染发生了什么。本质上来说,在每一帧你有很多机会来调整你的游戏。

GameplayKit 是另外一个能使用的框架。 GameplayKit 是在去年的 WWDC 被引进的,它提供了很多制作游戏使用的通用方法的实现,例如创建随机数,创建人工智能对手,或者障碍物的寻路系统。他们是非常有用的工具,能做很多繁重的工作并且让游戏应用程序的开发者把精力放在怎么制作更有趣的游戏。我强烈推荐你阅读GameplayKit编程指导 去学习怎样利用这些框架中的技巧。回到我们这个简单的游戏,我们将只包含框架中的一小部分内容让我们的计算机对手更加“聪明”。

启动 Xcode

启动 XCode 并且通过为 iOS 设计的模板创建一个游戏项目。 命名游戏为 TicTacToe 并且确认编程语言设定为 Swift。在项目的创建过程中, XCode 创建了 SKScene 文件,它展示了游戏的初始视图,连同一个视图控制器文件用于初始化你的游戏场景并且处理在应用程序启动的时候展现在屏幕上。如果你现在启动应用程序,你会看到Hello World标签,它让你所有的东西都立即可以使用了。另外,如果你点击了视图,一个宇宙飞船会增加在你点击的位置。我们已经不再需要那个标签和宇宙飞船了,让我们移除那部分代码。切换到 GameScene.swift 文件,移除 didMoveToView 和 touchesBegan 方法中的代码。

让我们来花点时间并强调一些场景编辑器的特性。视图的中心是显示了场景,黄色的轮廓围绕着我们的 tic tac toe 游戏棋盘展现了我们的视口,它让我们的游戏可见。我们能改变游戏的视口或者增加摄像机,让我们实时得看见更多游戏的内容。在platformer 中,我们也可以创建一个很大的很多敌人点散列在场景终的背景图片。我们也可以使用一个摄像机节点横跨整个场景随着时间去显示更多新的背景部分。然而,对于这个游戏,我们的视图将会时静止在棋盘附近。

场景的底部是节点编辑器。我们可以使用这个编辑器为节点增加功能或者在场景中更容易的选择他们。我们用增加一个节点来表示游戏棋盘,标签和每一格游戏棋盘的占位节点。最后,每一个在场景中的节点都有一个名字,它用于在代码中引用回去。

为了考虑写这篇文章的时间我已经将整个游戏项目提交到了 Github,所以你可以追随并且研究我略过的这个区域。

回到代码

让我们切换回到 GameViewController.swift 去看看怎样建立我们的场景和让我们的游戏干点事情。在 viewDidLoad 方法中我们配置并且装载我们的场景。我们也增加了调试语句所以我们能追踪代码和每秒多少帧。在一个动作游戏中,我们对监控每秒钟多少个节点同时出现在屏幕上连同我们是否可以保持理想上的60fps的帧率感兴趣。

    override func viewDidLoad() {
      super.viewDidLoad()

      if let scene = GameScene(fileNamed:"GameScene") {
        // Configure the view.
        let skView = self.view as! SKView
        skView.showsFPS = true
        skView.showsNodeCount = true

        /* Sprite Kit applies additional optimizations to improve rendering performance */
        skView.ignoresSiblingOrder = true

        /* Set the scale mode to scale to fit the window */
        scene.scaleMode = .AspectFill

        skView.presentScene(scene)
      }
    }

再看 GameScene.swift 文件,我们需要检查以下三个方法: didMoveToViewtochesBegan 和 update。当场景既要显示在我们的视图控制器的视图内时,didMoveToView 方法被调用。我们的 GameScene 视图最炫的是我们有好几种选择访问视图里的节点。在我们的方法里,我们通过移除游戏棋盘单元格背景的颜色初始化场景。我们也干了点其他事情,但是我们将在之后的文章里面讨论这些。

    override func didMoveToView(view: SKView) {
      /* Setup your scene here */
      self.enumerateChildNodesWithName("//grid*") { (node, stop) in
        if let node = node as? SKSpriteNode{
          node.color = UIColor.clearColor()
        }
      }

      let top_left: BoardCell  = BoardCell(value: .None, node: "//*top_left")
      let top_middle: BoardCell = BoardCell(value: .None, node: "//*top_middle")
      let top_right: BoardCell = BoardCell(value: .None, node: "//*top_right")
      let middle_left: BoardCell = BoardCell(value: .None, node: "//*middle_left")
      let center: BoardCell = BoardCell(value: .None, node: "//*center")
      let middle_right: BoardCell = BoardCell(value: .None, node: "//*middle_right")
      let bottom_left: BoardCell = BoardCell(value: .None, node: "//*bottom_left")
      let bottom_middle: BoardCell = BoardCell(value: .None, node: "//*bottom_middle")
      let bottom_right: BoardCell = BoardCell(value: .None, node: "//*bottom_right")

      let board = [top_left, top_middle, top_right, middle_left, center, middle_right, bottom_left, bottom_middle, bottom_right]

      gameBoard = Board(gameboard: board)
  }

下一个我们讨论的方法是 touchesBegan。这个方法处理用户选择移动和重置游戏的触摸事件。对场景上的每一个触摸事件,我们决定他们在场景上的位置和哪一个节点被选中。就我们的情况来说,我们要么放置玩家的棋子在一个单元格里,要么重置游戏。我们也更新内部的游戏棋盘状态。

  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches {
      let location = touch.locationInNode(self)
      let selectedNode = self.nodeAtPoint(location)
      var node: SKSpriteNode

      if let name = selectedNode.name {
        if name == "Reset" || name == "reset_label"{
          self.stateMachine.enterState(StartGameState.self)
          return
        }
      }

      if gameBoard.isPlayerOne(){
        let cross = SKSpriteNode(imageNamed: "X_symbol")
        cross.size = CGSize(width: 75, height: 75)
        cross.zRotation = CGFloat(M_PI / 4.0)
        node = cross
      }
      else{
        let circle = SKSpriteNode(imageNamed: "O_symbol")
        circle.size = CGSize(width: 75, height: 75)
        node = circle
      }

      for i in 0...8{
        guard let cellNode: SKSpriteNode = self.childNodeWithName(gameBoard.getElementAtBoardLocation(i).node) as? SKSpriteNode else{
            return
        }
        if selectedNode.name == cellNode.name{
          cellNode.addChild(node)
          gameBoard.addPlayerValueAtBoardLocation(i, value: gameBoard.isPlayerOne() ? .X : .O)
          gameBoard.togglePlayer()
        }
      }
    }
  }

最后需要被重写的方法是 update。这个方法在游戏中的每一帧都被调用并且是触发我们游戏逻辑处理的地方。我们使用GameplayKit 的状态机(StateMachine)处理我们的游戏逻辑。

  override func update(currentTime: CFTimeInterval) {
    /* Called before each frame is rendered */
    self.stateMachine.updateWithDeltaTime(currentTime)
  }

第二部分 - 游戏逻辑

到目前为止,我们已经涉及掌握了建立(游戏如何)显示的内容,我们将开始涉及足够你能自己开发一个游戏的游戏逻辑。

状态机(StateMachines)

大部分游戏的逻辑仅仅是请求当前游戏设置的状态。随着状态的改变,所以逻辑也需要改变。这也就归结为(怎么)对待状态机。我们将使用 GameplyKit 提供的一种状态机的实现在我们的游戏里。让我们看看我实现的 GameStateMachine.swift 去控制游戏中的状态。我为我们的游戏创建了三个状态: StartGameState, ActiveGameState 和 EndGameState,他们都从自GKState 继承来。为了让我们的状态机工作,我们必须为每一个(状态)提供有效的下一个状态连同一个更新(状态)的方法,我们的状态机将同每一个帧更新的同时调用这个方法。在每一个更新时,我们的状态机会为活动的状态调用updateWithDeltaTime 方法。

StartGameState是我们如何开始游戏(的状态)。在这个状态行下我们重置游戏棋盘并且在之后转换到 ActiveGameState。我们重写 isValidNextState 函数确保只有 ActiveGameState 是下一个有效的状态。所以,当我们在 StartGameState(状态下), 我们只能去那里并且避免进入到其他状态。状态机也有一个 didEnterWithPreviousState 的函数被调用当正在执行的状态被激活。它也提供给你这个状态来自哪里。就我们的情况来说,我们调用 resetGame 函数去建立我们的游戏。

    class StartGameState: GKState{
      var scene: GameScene?
      var winningLabel: SKNode!
      var resetNode: SKNode!
      var boardNode: SKNode!

      init(scene: GameScene){
        self.scene = scene
        super.init()
      }

      override func isValidNextState(stateClass: AnyClass) -> Bool {
        return stateClass == ActiveGameState.self
      }

      override func updateWithDeltaTime(seconds: NSTimeInterval) {
        resetGame()
        self.stateMachine?.enterState(ActiveGameState.self)
      }

      func resetGame(){
        let top_left: BoardCell  = BoardCell(value: .None, node: "//*top_left")
        let top_middle: BoardCell = BoardCell(value: .None, node: "//*top_middle")
        let top_right: BoardCell = BoardCell(value: .None, node: "//*top_right")
        let middle_left: BoardCell = BoardCell(value: .None, node: "//*middle_left")
        let center: BoardCell = BoardCell(value: .None, node: "//*center")
        let middle_right: BoardCell = BoardCell(value: .None, node: "//*middle_right")
        let bottom_left: BoardCell = BoardCell(value: .None, node: "//*bottom_left")
        let bottom_middle: BoardCell = BoardCell(value: .None, node: "//*bottom_middle")
        let bottom_right: BoardCell = BoardCell(value: .None, node: "//*bottom_right")

        boardNode = self.scene?.childNodeWithName("//Grid") as? SKSpriteNode

        winningLabel = self.scene?.childNodeWithName("winningLabel")
        winningLabel.hidden = true

        resetNode = self.scene?.childNodeWithName("Reset")
        resetNode.hidden = true

        let board = [top_left, top_middle, top_right, middle_left, center, middle_right, bottom_left, bottom_middle, bottom_right]

        self.scene?.gameBoard = Board(gameboard: board)

        self.scene?.enumerateChildNodesWithName("//grid*") { (node, stop) in
          if let node = node as? SKSpriteNode{
            node.removeAllChildren()
          }
        }
      }
    }

ActiveGameState 是当我们正在玩我们的游戏时候的状态。我们再次重写 isValidNextState 这个函数并且这次我们想你只能转换到 EndGameState 状态。我们也重写了 didEnterWithPreviousState 函数,但是这次我们只是在 update 方法被调用的时候更新一个我们的实例属性让我们的游戏如期望的执行。最后,我们重写 updateWithDeltaTime 函数去决定是否有一个胜利者,游戏是否被绘制,或者当前玩家游戏回合被改变。此外当玩家二的回合时,我们调用 AI 的程序去决定对玩家最好的策略并执行这个策略。

    class ActiveGameState: GKState{
      var scene: GameScene?
      var waitingOnPlayer: Bool

      init(scene: GameScene){
        self.scene = scene
        waitingOnPlayer = false
        super.init()
      }

      override func isValidNextState(stateClass: AnyClass) -> Bool {
        return stateClass == EndGameState.self
      }

      override func didEnterWithPreviousState(previousState: GKState?) {
        waitingOnPlayer = false
      }

      override func updateWithDeltaTime(seconds: NSTimeInterval) {
        assert(scene != nil, "Scene must not be nil")
        assert(scene?.gameBoard != nil, "Gameboard must not be nil")

        if !waitingOnPlayer{
          waitingOnPlayer = true
          updateGameState()
        }
      }

      func updateGameState(){
        assert(scene != nil, "Scene must not be nil")
        assert(scene?.gameBoard != nil, "Gameboard must not be nil")

        let (state, winner) = self.scene!.gameBoard!.determineIfWinner()
        if state == .Winner{
          let winningLabel = self.scene?.childNodeWithName("winningLabel")
          winningLabel?.hidden = true
          let winningPlayer = self.scene!.gameBoard!.isPlayerOne(winner!) ? "1" : "2"
          if let winningLabel = winningLabel as? SKLabelNode,
            let player1_score = self.scene?.childNodeWithName("//player1_score") as? SKLabelNode,
            let player2_score = self.scene?.childNodeWithName("//player2_score") as? SKLabelNode{
            winningLabel.text = "Player \(winningPlayer) wins!"
            winningLabel.hidden = false

            if winningPlayer == "1"{
              player1_score.text = "\(Int(player1_score.text!)! + 1)"
            }
            else{
              player2_score.text = "\(Int(player2_score.text!)! + 1)"
            }

            self.stateMachine?.enterState(EndGameState.self)
            waitingOnPlayer = false
          }
        }
        else if state == .Draw{
          let winningLabel = self.scene?.childNodeWithName("winningLabel")
          winningLabel?.hidden = true

          if let winningLabel = winningLabel as? SKLabelNode{
            winningLabel.text = "It's a draw"
            winningLabel.hidden = false
          }
          self.stateMachine?.enterState(EndGameState.self)
          waitingOnPlayer = false
        }

        else if self.scene!.gameBoard!.isPlayerTwoTurn(){
          //AI moves
          self.scene?.userInteractionEnabled = false

          assert(scene != nil, "Scene must not be nil")
          assert(scene?.gameBoard != nil, "Gameboard must not be nil")

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
            self.scene!.ai.gameModel = self.scene!.gameBoard!
            let move = self.scene!.ai.bestMoveForActivePlayer() as? Move

            assert(move != nil, "AI should be able to find a move")

            let strategistTime = CFAbsoluteTimeGetCurrent()
            let delta = CFAbsoluteTimeGetCurrent() - strategistTime
            let  aiTimeCeiling: NSTimeInterval = 1.0

            let delay = min(aiTimeCeiling - delta, aiTimeCeiling)
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay) * Int64(NSEC_PER_SEC)), dispatch_get_main_queue()) {

              guard let cellNode: SKSpriteNode = self.scene?.childNodeWithName(self.scene!.gameBoard!.getElementAtBoardLocation(move!.cell).node) as? SKSpriteNode else{
                return
              }
              let circle = SKSpriteNode(imageNamed: "O_symbol")
              circle.size = CGSize(width: 75, height: 75)
              cellNode.addChild(circle)
              self.scene!.gameBoard!.addPlayerValueAtBoardLocation(move!.cell, value: .O)
              self.scene!.gameBoard!.togglePlayer()
              self.waitingOnPlayer = false
              self.scene?.userInteractionEnabled = true
            }
          }
        }
        else{
          self.waitingOnPlayer = false
          self.scene?.userInteractionEnabled = true
        }
      }
    }

EndGameState 是我们状态机的最后一个状态。isValidNextState 函数只允许这个状态被转换到StartGameStatedidEnterWithPreviousState 函数只显示重置按钮所以玩家可以点击并且重置游戏。

    class EndGameState: GKState{
      var scene: GameScene?

      init(scene: GameScene){
        self.scene = scene
        super.init()
      }

      override func isValidNextState(stateClass: AnyClass) -> Bool {
        return stateClass == StartGameState.self
      }

      override func didEnterWithPreviousState(previousState: GKState?) {
        updateGameState()
      }

      func updateGameState(){
        let resetNode = self.scene?.childNodeWithName("Reset")
        resetNode?.hidden = false
      }
    }

极大极小值策略(MinMax Strategist)

在很多棋盘类游戏中,胜利基于策略,并且每一步都需要计算。在 GameplayKit 中,一个(战略家) Strategist 是一个人工智能,可作为一个对手或者确定一步或者多步的策略为玩家给予提示。 GKMinmaxStrategist 类提供一个实现极大极小值策略的方式。极大极小值策略通过在游戏里建立一个对所有剩余可能性策略选择的决策树。我们可以通过配置预测多少步让 AI 变得更强或更弱。

让我们关注下最后的文件 AIStrategy.swift 去瞧一瞧怎么实现必须的类和协议来完成我们游戏中AI的工作。最终我们需要创建一个 GKMinmaxStrategist 类的实例并且实现以下几个协议: GKGameModelGKGameModelUpdate 和 GKGameModelPlayer

我们需要模型去展示玩家,他们的步棋策略和展示棋盘。我们实现了 GKGameModelPlayer 协议,所以我们的AI(人工智能)可以辨别我们不同的玩家。还有一个必须要实现的属性是 playerId。下一个我们需要解决的是创建一个策略对象,该对象实现了 GKGameModelUpdate 协议。该协议需要我们提供一个数值属性(可被量化的属性),该属性能被决策树用于评估每一次的策略。我们所做的是把这个数值增加到我们的类中然后让极大极小值策略者来解决余下的问题。

最后,我们创建游戏棋盘的类并且实现 GKGameModel 协议。这个协议很大一部分的作用是模拟一个玩家可能使用的策略(步骤走法)。通过实现 gameModleUpdatesForPlayer 函数,我们汇总了某一个玩家所有可能使用的走法。每一个我们存储的走法都是 Move 类的实例,该函数实现了 GKGameModelUpdate 的协议。我也实现了一些其他的协议方法,我会把这些留给读者去研究: unapplyGameModelUpdate, isWinForPlayer, isLossForPlayer, setGameModel, activePlayerscoreForPlayer

汇总

切换回 GameScene.swift 文件并且检查 didMoveToView 这个功能函数。我们需要连接AI(人工智能)和创建我们的状态机。关于连接AI(人工智能),我们初始化一个 GKMinmaxStrategist 的实例并且设置 maxLookAheadDepth 来控制AI 对手在游戏种到底有多厉害。我们也会设定 GKARC4RandomSource 的随机源属性,所以我们的 AI(人工智能)将会在出现很多个“最好”的走法的时候随机的选择一个。关于我们的状态机,我们创建 Start , Active 和 End 状态的实例并且将他们传给我们的状态机,让他开始运作。最后,我们告诉状态机进入 StartGameState

     override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        self.enumerateChildNodesWithName("//grid*") { (node, stop) in
          if let node = node as? SKSpriteNode{
            node.color = UIColor.clearColor()
          }
        }

        let top_left: BoardCell  = BoardCell(value: .None, node: "//*top_left")
        let top_middle: BoardCell = BoardCell(value: .None, node: "//*top_middle")
        let top_right: BoardCell = BoardCell(value: .None, node: "//*top_right")
        let middle_left: BoardCell = BoardCell(value: .None, node: "//*middle_left")
        let center: BoardCell = BoardCell(value: .None, node: "//*center")
        let middle_right: BoardCell = BoardCell(value: .None, node: "//*middle_right")
        let bottom_left: BoardCell = BoardCell(value: .None, node: "//*bottom_left")
        let bottom_middle: BoardCell = BoardCell(value: .None, node: "//*bottom_middle")
        let bottom_right: BoardCell = BoardCell(value: .None, node: "//*bottom_right")

        let board = [top_left, top_middle, top_right, middle_left, center, middle_right, bottom_left, bottom_middle, bottom_right]

        gameBoard = Board(gameboard: board)

        ai = GKMinmaxStrategist()
        ai.maxLookAheadDepth = 9
        ai.randomSource = GKARC4RandomSource()

        let beginGameState = StartGameState(scene: self)
        let activeGameState = ActiveGameState(scene: self)
        let endGameState = EndGameState(scene: self)

        stateMachine = GKStateMachine(states: [beginGameState, activeGameState, endGameState])
        stateMachine.enterState(StartGameState.self)

    }

如果你运行了 iOS 模拟器,我们可以开始玩游戏并且测试我们的 AI(人工智能)!

总结

我们已经创建了一个快速的游戏并且学习了一些关于 SpriteKit 和 GameplayKit 的内容。我支持你们去改变这个游戏并且尝试些什么让自己更舒服。如果你需要什么建议,你也许可以开始先从实现玩家交替(游戏)开始。另一个想法是可实现提供玩家一个在屏幕上调整 AI(人工智能)的等级或者彻底关闭(的功能)。

还有一点要注意的是,我也写了一篇关于为什么创建原生应用程序可能是最好的努力学习移动手机应用程序开发的方式。你可以阅读它并且可以提出自己的观点。






原文发布时间为:2016年07月20日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-10-16 02:27:04

用 Swift 语言和 SpriteKit 创建有人工智能的井字游戏的相关文章

Javascript编程语言和DOM接口系列教程(1)

Hello,今天开始彬Go将以系列教程的方式为大家讲解Javascript编程语言和DOM接口,本篇教程为该系列的第一部分. 虽然现在一些js框架诸如jQuery.Prototype和MooTools能提高我们的前端开发效率而且很好的解决了浏览器兼容性问题,但我们仍要打好javascript技术基础.Javascript DOM 控制系列教程将告诉你你需要了解的javascript和文档对象模型(DOM)的本质. 引言 JavaScript JavsScript是可以在各种不同环境下使用的动态的

C语言和Fortran语言的差异

本文详细介绍C语言和Fortran语言的差异 1. C++语言和Fortran语言的发展背景 在程序设计语言的发展过程中,FORTRAN 语言被认为是科学计算的专用语言.后来推出的FORTRAN90 和FORTRAN 95 版本也不例外,它们虽然可以完全实现C++语言同样的功能,然而其软件开发环境和软件的集成性等方面都远不如C++ 语言.近年来,随着计算机软硬件技术的发展,数据结构.数据库管理技术.可视化与计算机图形学.用户接口系统集成以及人工智能等领域的成果被逐渐应用到结构分析软件中,结构分析

如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 2)

本文讲的是如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 2), 如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 2) 你是否想过如何来开发一款 SpriteKit[1] 游戏?实现碰撞检测会是个令人生畏的任务吗?你想知道如何正确的处理音效和背景音乐吗?随着 SpriteKit 的发布,在 iOS 上的游戏开发已经变得空前简单了.在本系列三部中的第二部分中,我们将继续探索 SpriteKit 的基础知识. 如果你错过了 之前的课程[2],你

如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 1)

本文讲的是如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 1), 你有没有想过要如何开始创作一款基于 SpriteKit 的游戏?开发一款基于真实物理规则的游戏是不是让你望而生畏?随着 SpriteKit[1] 的出现,在 iOS 上开发游戏已经变得空前的简单了. 本系列将分为三个部分,带你探索 SpriteKit 的基础知识.我们会接触到物理引擎( SKPhysics ).碰撞.纹理管理.互动.音效.音乐.按钮以及场景( SKScene ) .这些看上去艰深晦涩的东

如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 3)

本文讲的是如何在 Swift 3 中用 SpriteKit 框架编写游戏 (Part 3), 你有没有想过要如何开始创作一款基于 SpriteKit 的游戏?按钮的开发是一个很庞大的任务吗?想过如何制作游戏的设置部分吗?随着 SpriteKit 的出现,在 iOS 上开发游戏已经变得空前的简单了.在本系列的第三部分,我们将完成 RainCat 游戏的开发以及对 SpriteKit 框架的介绍. 如果你错过了上一课,你可以通过获取 Github 上的代码来赶上进度.请记住,本教程需要使用 Xcod

swift 如何使用数组创建按钮

问题描述 swift 如何使用数组创建按钮 数组里面是通过json解析的数据模型类,类里面有个字符串类型的属性,如何挨个取出里面的字符串设置成按钮的文字?我使用for in但是无法进入循环 解决方案 Swift之数组使用Swift数组的简单使用Swift 数组 解决方案二: 先把json转成NSDictionary 然后再循环

买水果问题,用c语言和c++回答

问题描述 买水果问题,用c语言和c++回答 今天zz突然想吃水果了,然后他到超市去买水果,现超市有n个不同的水果,zz要买m个水果 (m<=n);那么zz有多少种买水果的方式?? 解决方案 这个应该是排列组合的问题,和彩票22选5类似,可以参考这个 http://wenku.baidu.com/link?url=L91yYWksToQXFnfyKL22o83XzvR2l6CBrNDVdhw1Gkjz1gAtcE6uC6ih14FzMRew5LeZ3jNVp0uXhJtSzir9QFxeUPABc

请问java语言和vfp语言项目的结合怎么做?

问题描述 请问java语言和vfp语言项目的结合怎么做? 请问java语言和vfp语言项目的结合怎么做?请问java语言转vfp语言的数据困难么?一般耗时多少? 解决方案 www.vfp2java.net 将vfp转换为java

r语言-R语言和MySQL还有Hadoop的教学课程哪里找

问题描述 R语言和MySQL还有Hadoop的教学课程哪里找 R语言和MySQL还有Hadoop的教学课程哪里找 还请高手指点 解决方案 test 1,没人回答 解决方案二: R语言,翻pdf学吧, MySQL,是关系型数据库,可以找其他的关系型数据学习,比如oracle,通用,注意oracle多了一点内容 Hadoop,这个没办法,问谷歌和度娘 解决方案三: R和mysql这两个可以找本PDF来看,hadoop其实这东西也是有书的