著作権:raywenderlich.com
元のURLはこちら

SwiftとSpriteKitでキャンディクラッシュのようなゲームを作るには:パート1

Xcode 9.3 and Swift 4.1用に更新。SwiftとSpriteKitを使って、モバイルゲームのキャンディクラッシュのようなゲームの作り方を学びましょう。アニメーションとゲームロジックの作り方を学べます。

Version

  • Swift 4, iOS 11, Xcode 9

更新履歴: このSpriteKitチュートリアルはXcode 9.3Swift 4.1用にケビン・コリガンによって更新されました。元となったチュートリアルは マスジス・ホルマンズ によって作成され、 続いてモルテン・ファークログによって更新されました。

SwiftSpriteKitによる「〜を作るには」の3部作の中で、キャンディ・クラッシュ・サーガのようなゲーム(名付けて)クッキークランチアドベンチャーの作り方を学びます。 うん、美味しそう!キャンディよりも聞こえが良いでしょ?

  • (あなたは今ここにいる)3部作の1部では、基礎となる部分を作成します。ゲームプレイ・ビュー、スプライトそれにレベルロードのロジックを作成します。
  • 2部では スワイプの検出、クッキーのスワップ、それにクッキーチェインの検出と削除を作成します
  • 3部ではスワイプ成功後の、クッキーの補充を作成します。そして、スコアポイントの作成、勝ち負けの処理、クッキーのシャッフルといった機能等を加えてゲームを完成させます。

チュートリアルを進めていく中で、あなたはSwiftの素晴らしいテクニックーー例えば、列挙型、ジェネリクス、サブスクリプティング、クロージャー、それにエクステンションの使い方を学んでいくでしょう。更に、ゲームのアーキテクチャとベストプラクティスも学ぶでしょう。

学ぶことが沢山あります、早速飛び込んでみましょう!

注意: このチュートリアルでは貴方はSwiftとSpriteKitがどのように動くか知見があることを前提としています。もしSpriteKitが初めてなら SpriteKit入門を読むか、私たちの書籍2D iOS & tvOS Games by Tutorialsを読んでください。Swiftが初めてならSwift入門を読んでください。

始めよう

クッキークランチではモデル・ビュー・コントローラ又は、ゲーム以外のアプリでご存知かもしれないMVCパターンによく似たアーキテクチャを使用します。

  • モデルはLevelCookieそれに幾つかのクラスから構成されます。モデルは、クッキーの2Dグリッドのようなデータそれに多くのゲームプレイロジックを持ちます。
  • ビューはGameSceneSKSpriteNodeが一方を受け持ち、残りをUIViewが受け持ちます。ビューは画面上にキャラクタを表示する役割と、それらをタッチした時のイベントを発生させる役割を持ちます。特にシーンはクッキーのスプライトを描画し、スワイプの検出をします。
  • ここでのビューコントローラは典型的なMVCアプリと同じ役割を持ちます。モデルとビューの間に座り、一切合切を取り仕切ります。

これらすべてのオブジェクトはお互いに、配列やセットなどを渡すことによってコミュニケーションをし、データを修正します。この役割分担はそれぞれのオブジェクトに自分ができる唯一の仕事を持たせ、お互いを独立させます。このようにして、コードをクリーンかつ編集しやすい状態に保つのです。

このチュートリアルの上部と下部にあるDownload Materialsを使用してスタータープロジェクトをダウンロードしてください。。ファイルを開き、シミュレータで実行してください。これからあなたが作ろうとしているゲームの基礎環境が整ったことを見てください。

  • ゲームのイメージに相応しい背景画像
  • TargetMovesそれにScoreのラベル
  • Shuffleボタン

すべてのクッキーを食べるのは誰でしょうか?

肝心のクッキーはまだ表示されていませんが、この後で画面に表示させていきます。それではスタータープロジェクトを見ていきましょう。

GameScene.swift

GameSceneではサウンドファイルを読み込みます。一度読み込んだサウンドファイルは必要時に、何度でもサウンドを再生することができます。

let swapSound = SKAction.playSoundFileNamed("Chomp.wav", waitForCompletion: false)
let invalidSwapSound = SKAction.playSoundFileNamed("Error.wav", waitForCompletion: false)
let matchSound = SKAction.playSoundFileNamed("Ka-Ching.wav", waitForCompletion: false)
let fallingCookieSound = SKAction.playSoundFileNamed("Scrape.wav", waitForCompletion: false)
let addCookieSound = SKAction.playSoundFileNamed("Drip.wav", waitForCompletion: false)

アセットカタログから背景画像も読み込みます。anchorPointが (0.5, 0.5)に設定されていますので、全てのiPhoneのスクリーンサイズにて背景画像が画面の中央に位置するようになっています。

override init(size: CGSize) {
  super.init(size: size)
    
  anchorPoint = CGPoint(x: 0.5, y: 0.5)
    
  let background = SKSpriteNode(imageNamed: "Background")
  background.size = size
  addChild(background)
}

GameViewController

GameViewControllerに特筆すべき重要な二つのプロパティがあります。プロパティのsceneGameSceneの参照を持ちます。

lazy backgroundMusicは変数の宣言と初期化を同じステートメントで行なっています。これはiOSの共通したパターンです。

lazy var backgroundMusic: AVAudioPlayer? = {
    guard let url = Bundle.main.url(forResource: "Mining by Moonlight", withExtension: "mp3") else {
      return nil
    }
    do {
      let player = try AVAudioPlayer(contentsOf: url)
      player.numberOfLoops = -1
      return player
    } catch {
      return nil
    }
}()

初期化のコードはクロージャの中です。ゲームの背後に流れるMP3ミュージックを読み込み、再生を永久ループします。プロパティはlazyで宣言されているので、クロージャの中のコードはbackgroundMusicが最初にアクセスされるまで実行されません。

GameSceneviewDidLoad()の中で宣言されます。これについては後述します。

IBOutletsIBActionsMain.storyboardのオブジェクトに対応しています。チュートリアルの後半でオブジェクトの接続を実装します

Main Storyboard

シミュレータで見た、Shuffleボタンとラベルを思い出してください。これらはMain.storyboardで作られました。この時点でそれらは動作しません。しかし、完成するまでに実装します。

Main Storyboard

UIKitとSpriteKitが素晴らしい程に協調動作します

TargetMovesそれにScoreラベルは入れ子になったviewとして画面の上部に配置されています。viewについて学び直したいなら、Stack Views tutorialをチェックアウトしてください。

Assets

スタータープロジェクトはオーディオとイメージファイルの束を持ち、クッキークランチのサウンドと外観を美味しそうにします。オーディオファイルはSoundsという名前のフォルダーに入っています。

Sprites.atlas

イメージファイルはグローバルアセットカタログ(Assets.xcassets)か、テクスチャアトラスの中に入ります。Xcodeでは、テクスチャアトラスはフォルダ名の最後に.atlasという名前がついたフォルダになります。この特殊を意味する名前はXCodeに、ビルド時イメージファイルをパックしてテクスチャアトラスに格納するように伝えます。テクスチャアトラスはパフォーマンスを劇的に向上させます。テクスチャアトラスに更に学ぶには、SpriteKit Animations and Texture Atlases tutorialをチェックアウトしてください。

Sprites.atlasの中を見てください。このゲームに相応しい、クロワッサン、カップケーキ、ドーナッツ(美味しそう!)イメージファイルが見つかります。Grid.atlasの中はグリッドイメージがあります(美味しそうではありません)

特筆すべき他のアイテム:

  • LevelData.swiftはSwift 4の新しいAPI Decodableを使い、JSONファイルからデータを作ります。あなたはレベルを作成するのに使うことになります。更なる情報はJSON Parsing screencastを見てください。
  • Array2D.swiftはヘルパーファイルです。二次元配列を作り易くします。
  • Tile.swiftは現在空です。しかし、実装の仕方によってこのファイルでクッキーの代わりに宝石を使うことができます。

スタータープロジェクトのツアーは以上です!

クッキーを追加しましょう

前準備は十分です。クッキーベーキングを始めましょう!次のステップはこうです:

  • Cookieクラスを作ります。
  • Levelクラスを作ります。
  • JSONファイルからレベルを読み込みます
  • クッキーをバックグランドタイルに配置しましょう。何しろ、マムはいつもプレートを使うように教えてくれたはずですよね。

Cookie Class

このゲームのプレイフィールドは9列x9行のグリッドから構成されています。各グリッドのスクェアはクッキーを含むことができます。

0行、0列は最下行左角のグリッドの位置です。SpriteKitの座標システムではpoint (0,0)は画面の最下行左端を表しているため、UIKitと比較すると上下逆さまと考えると意味をなします。

グリッドを実装するために、まずクッキーを表すクラスを作る必要があります。File\New\File…と進んで、iOS\Source\Swift Fileテンプレートを選びNextをクリックしてださい。名前をCookie.swiftにして、Createをクリックしてください。

Cookie.swiftの内容を下記の内容で置き換えてください:

import SpriteKit

// MARK: - CookieType
enum CookieType: Int {
  case unknown = 0, croissant, cupcake, danish, donut, macaroon, sugarCookie 
}

// MARK: - Cookie
class Cookie: CustomStringConvertible, Hashable {
  
  var hashValue: Int {
    return row * 10 + column
  }
  
  static func ==(lhs: Cookie, rhs: Cookie) -> Bool {
    return lhs.column == rhs.column && lhs.row == rhs.row
    
  }
 
  var description: String {
    return "type:\(cookieType) square:(\(column),\(row))"
  }
  
  var column: Int
  var row: Int
  let cookieType: CookieType
  var sprite: SKSpriteNode?
  
  init(column: Int, row: Int, cookieType: CookieType) {
    self.column = column
    self.row = row
    self.cookieType = cookieType
  }
}

二つのプロトコルを使用しますが、後で意味をなしてきます。

  • CustomStringConvertible: Cookieオブジェクトをprintした時に表示するステートメントを読みやすくします
  • Hashable: クッキーは後でSetの中に配置しますが、Setに配置されるオブジェクトはHashableプロトコルを実装する必要があります。これはSwiftの仕様です。

columnとrowプロパティは2Dグリッドの中のCookieの位置を保持します。

spriteプロパティはSKSpriteNodeの後にクエスチョンマークが付いているので、オプショナルです。なぜならクッキーオブジェクトは常にスプライトを持つとは限らないからです。

cookieTypeはクッキーのタイプを表し、列挙型であるCookieTypeの値を使用します。その値は単純に1〜6の値をとりますが、enumを使用するのは、数値で覚えるよりもクッキーの名前のほうが覚えやすいからです。

意図的にUnknown (value 0)を使用することはないでしょう。後で使用することになります、この値は特別な意味を持っています。

それぞれのクッキータイプはスプライトイメージに対応しています:

Swiftでは、列挙型は数値の代わりに名前で連想させるだけに役立つのではありません。関数と算術プロパティを列挙型に宣言することができます。次のコードをCookieTypeに追加してください:

var spriteName: String {
  let spriteNames = [
    "Croissant",
    "Cupcake",
    "Danish",
    "Donut",
    "Macaroon",
    "SugarCookie"]

  return spriteNames[rawValue - 1]
}

var highlightedSpriteName: String {
  return spriteName + "-Highlighted"
}

spriteNameプロパティはテクスチャアトラスのなかの対応するスプライトイメージのファイル名を返します。加えて通常のクッキースプライトの他に、プレイヤーがクッキーをタップした時に表示されるハイライトバージョンのスプライトイメージ名も存在します。

spriteNamehighlightedSpriteNameプロパティは単純にstring配列からクッキースプライトの名前を検索するだけです。rawValueはenumの現在値をint型に直した値となります。クッキーのタイプ、クロワッサン(croissant)は値が1から始まっていることを思い出してください。しかし、配列は0から始まりますので、値を取得するには(rawValue - 1)とする必要があります。

クッキーをゲームの中に登場させる度に、クッキーのタイプをランダムに選ぶ必要があります。ですので、次の関数を列挙型に持たせるとよいでしょう。

static func random() -> CookieType {
  return CookieType(rawValue: Int(arc4random_uniform(6)) + 1)!
}

この関数はarc4random_uniform(_:)を呼び出し、0〜5のランダムな数値を生成します。そのあと値が1〜6になるように1を加算します。Swiftは変数の型にとても厳しい言語なので、arc4random_uniform(_:)で返されるUInt32の値は、(ここでは)必ずIntにキャストしなければなりません。こうすることによって、適切なCookieTypeの値にすることができます。

ここであなたは「どうしてCookieSKSpriteNodeのサブクラスにしないのだろう」と思うかもしれません。結局のところ、クッキーは画面に表示させるものに変わりません。MVCパターンに詳しいのならば、Cookieをクッキーのデータを表す単純なモデルであると考えてください。クッキーの外見は分離されたオブジェクトであり、スプライトプロパティに格納されています。

このデータモデルとビューの分離というコンセプトは、このチュートリアルを通じて一貫して使用するコンセプトとなります。MVCパターンはゲームよりも一般的なアプリでお馴染みですが、見てきた通りコードの綺麗さと柔軟性を維持します。

The Level Class

levelsの構築に取り掛かりましょう。File\New\File…と辿り、iOS\Source\Swift Fileテンプレートを選択しNextをクリック。Level.swiftと名付けて、Createをクリックしてください。

Level.swiftの内容を次の内容で置き換えてください:

import Foundation

let numColumns = 9
let numRows = 9 

class Level {
  private var cookies = Array2D<Cookie>(columns: numColumns, rows: numRows)
}

レベルの次元を表す2つの定数を宣言します。numColumnsnumRowsです。これによりコードのいたるところで数値の9をコードしなくて済みます。

プロパティcookiesは2次元の配列であり、合計で81個(9列x9行)のCookieオブジェクトを保持します。

cookies配列はprivateの為、Levelは他のclassに対して、レベルグリッドにある特定の位置のクッキーオブジェクトを取得する方法を提供する必要があります。

Levelの最下行に次のコードを追加してください:

func cookie(atColumn column: Int, row: Int) -> Cookie? {
  precondition(column >= 0 && column < numColumns)
  precondition(row >= 0 && row < numRows)
  return cookies[column, row]
}

cookie(atColumn: 3, row: 6)のように使用して、列3、行6にあるクッキーをLevelに求めることができます。ゲームのバックグランド処理のなかで、Array2Dに対してクッキーを求める処理が行われてクッキーオブジェクトが戻り値として戻ります。戻り値はCookie?とオプショナルで定義されています。なぜなら、すべてのグリッドのマスがクッキーを保持しているわけではないからです。

preconditionを使用して、特定位置の列と行の番号が0-8以内に収まっているか検証していることに注目してください。

これでようやく、cookies配列にクッキーを充填するタイミングとなりました。後でJSONファイルからレベルの設定を読み込む方法を学びますが、今の所自力で配列にクッキーを埋めるようにして、画面に表示させます。

次の2つのメソッドをLevelの最後に追加してください:

func shuffle() -> Set<Cookie> {
  return createInitialCookies()
}

private func createInitialCookies() -> Set<Cookie> {
  var set: Set<Cookie> = []

  // 1
  for row in 0..<numRows {
    for column in 0..<numColumns {

      // 2
      let cookieType = CookieType.random()

      // 3
      let cookie = Cookie(column: column, row: row, cookieType: cookieType)
      cookies[column, row] = cookie

      // 4
      set.insert(cookie)
    }
  }
  return set
}

どちらのメソッドもSet<Cookie>を返します。Setはコレクションです。配列のようなものですが、格納される各要素は一度だけ格納されて同じ要素は格納されず、ユニークです。また特定の順序で並びません。

shuffle()はレベルをランダムなクッキーで充填します。今のところcreateInitialCookies()を呼び出すだけですが、実際に動作します。どのような動作かを一つ一つ見ていきましょう:

  1. メソッドは2D配列の列と行の分だけループします。この処理はチュートリアルの中で多く見かけるでしょう。列0、行0は2Dグリッドの最下行左角であることを思い出してください。
  2. チュートリアルの初期で追加したメソッドを使用してランダムでクッキーのタイプを選びます。
  3. 次に、新しいCookieを作り、2D配列に追加します。
  4. 最後に、新しいCookieSetに追加します。shuffle()はこのクッキーのセットを呼び出し元に返り値とします。

コードを設計するときに最も難しいことの一つは、違うオブジェクトがどのようにお互いコミュニケーションするのかを決めることです。このゲームでは、しばしば、オブジェクトのコレクションを渡すことによってこの問題を解決します。通常コレクションはSetArrayになります。

ここでは、新しいLevelを作ってからshuffle()を呼び出してクッキーを補充します。Levelは「これが新しいクッキーで埋め尽くしたsetだよ」と返します。そのsetを受け取ってから、例えば、クッキーが持つことになるスプライトを新しく作ることができます。実際、次にあなたが行うことはまさにそのことです。

アプリをビルドしてください。何もコンパイルエラーがないことを確認してください。

GameScene.swiftを開きクラスに次のプロパティを追加してください:

var level: Level!

let tileWidth: CGFloat = 32.0
let tileHeight: CGFloat = 36.0

let gameLayer = SKNode()
let cookiesLayer = SKNode()

sceneはカーレントレベルの参照を保持するpublicなプロパティを持ちます。

2Dグリッドの各マスは32 x 36 pointsの大きさですので、この値をtileWidthtileHeightの定数にセットします。これらの定数は、クッキー・スプライトの位置の計算をし易くするために定義します。

SpriteKit nodeのきちんと組織化された階層を保つために、GameSceneは幾つかのレイヤーを使います。ベースとなるレイヤーはgameLayerです。このレイヤーは他の全てのレイヤーのコンテナの役割を持ち、画面の中央に位置します。あなたはクッキー・スプライトをcookiesLayerに追加することになります。このレイヤーはgameLayerのチルドレイヤーです。

次のコードをinit(size:)に追加して、新しいレイヤーを追加してください。このコードはbackground nodeを生成しているコードの後に置いてください。

addChild(gameLayer)

let layerPosition = CGPoint(
    x: -tileWidth * CGFloat(numColumns) / 2,
    y: -tileHeight * CGFloat(numRows) / 2)

cookiesLayer.position = layerPosition
gameLayer.addChild(cookiesLayer)

このコードは2つの空のSKNodeを画面にレイヤーとして追加しています。レイヤーとは他のnodeを追加できる透明な生地のようなものだと考えてください。

ここにくるまでに、sceneのanchorPointを(0.5, 0.5)?に設定したのを思い出してください。この設定によりsceneにchildren nodeを追加し、初期位置を(0, 0)にすると、自動的に、sceneの中央に自動配置されることになります。

しかしながら、列0、行0は2Dグリッドの最下行左角であるので、スプライトの位置をcookiesLayerの最下行左角からの相対値にすると都合が良いでしょう。この為にcookiesLayerをグリッドの高さと幅の半分だけ左下に移動させた理由なのです。

スプライトをsceneに追加するのはaddSprites(for:)の役割です。init(size:)の次に追加してください:

func addSprites(for cookies: Set<Cookie>) {
  for cookie in cookies {
    let sprite = SKSpriteNode(imageNamed: cookie.cookieType.spriteName)
    sprite.size = CGSize(width: tileWidth, height: tileHeight)
    sprite.position = pointFor(column: cookie.column, row: cookie.row)
    cookiesLayer.addChild(sprite)
    cookie.sprite = sprite
  }
}

private func pointFor(column: Int, row: Int) -> CGPoint {
  return CGPoint(
    x: CGFloat(column) * tileWidth + tileWidth / 2,
    y: CGFloat(row) * tileHeight + tileHeight / 2)
}

addSprites(for:)はcookiesのsetをイテレートし、クッキーのタイプに対応したSKSpriteNodeのインスタンスをクッキーレイヤーに追加していきます。クッキースプライトの位置を決める為にヘルパーメソッドのpointFor(column:, row:)を使います。これは列と行の値をCGPointの値に変換しcookiesLayerでの座標を決めます。このポイントの値はクッキーのSKSpriteNodeの中心となる座標になります。

GameViewController.swiftを開き、クラスに新しいプロパティを追加してください:

var level: Level!

次に、2つの新しいメソッドを追加してください:

func beginGame() {
  shuffle()
}

func shuffle() {
  let newCookies = level.shuffle()
  scene.addSprites(for: newCookies)
}

beginGame()メソッドはshuffle()メソッドを呼び出してゲームを開始します。新しいCookieオブジェクトが入ったSetを戻り値とするLevelshuffle()を呼び出すのはここになります。これらのクッキーオブジェクトは単なるモデルデータであることを思い出してください。まだスプライトを持っていません。クッキーを画面に表示する為に、これらのクッキーにスプライトを追加するようGameSceneに処理を持たせます。

まだ足りないことがあります。それはLevelのインスタンスを作成することです。次のコードをviewDidLoad()の中に追加してください。ちょうどsceneが現れる直前にです:

level = Level()
scene.level = level

Levelのインスタンスを作った後、sceneのlevelプロパティに代入しモデルとビューを結合します。

viewDidLoad()の最後でbeginGame()を確実に呼び出すようにしてください。ついにゲームが動き出します。

beginGame()

ビルドして実行してください。ついにクッキーが画面に現れます:

finally, some cookies!

JSON FilesからLevelの読み出し

キャンディクラッシュサーガの全てのレベルが単純な四角形のグリッドを持っているわけではありません。これからJsonファイルからレベル設定を読み出すサポートメソッドを追加することになります。5つの設定があり、全て9x9グリッドであることは同じですが、いくつかのグリッドはブランクのままとなります。

スタータープロジェクトのLevelsフォルダーの中を見てください。いくつかのファイルが見えるはずです。

Level JSON

Level_1.jsonをクリックして内容を見てください。3つの要素を含むディクショナリ型のデータ構造が見えるはずです。要素はそれぞれ、タイル、ターゲットスコア、それにムーブズです。

タイル配列は9つの配列が含まれます。1つの配列がレベルの行を表します。もしタイルが1の場合クッキーがある事を意味し、0の場合は空であること意味します。

Level.swiftを開き、新しいプロパティとメソッドを追加してください:

private var tiles = Array2D<Tile>(columns: numColumns, rows: numRows)

func tileAt(column: Int, row: Int) -> Tile? {
  precondition(column >= 0 && column < numColumns)
  precondition(row >= 0 && row < numRows)
  return tiles[column, row]
}

変数tilesはlevelの構造を表します。これはcookies配列にとても似ていますが、Array2DTileオブジェクトで生成している点が異なります。

cookies配列はlevelの中のCookieオブジェクトを保持しますが、tilesは単純にlevelのどのグリッドが空でどのグリッドがcookieを含んでいるかを表します:

tiles[a, b]がnilならば、グリッドのどこであれ空を意味しクッキーを保持できない事を意味します。

levelを表すインスタンス変数は用意されました。データを埋めるためのコードを追加しましょう。

Level.swiftinit(filename:)というイニシャライザを追加してください:

init(filename: String) {
  // 1
  guard let levelData = LevelData.loadFrom(file: filename) else { return }
  // 2
  let tilesArray = levelData.tiles
  // 3
  for (row, rowArray) in tilesArray.enumerated() {
    // 4
    let tileRow = numRows - row - 1
    // 5
    for (column, value) in rowArray.enumerated() {
      if value == 1 {
        tiles[column, tileRow] = Tile()
      }
    }
  }
}

このイニシャライザが何をしているのか一つ一つ見ていきましょう

  1. 指定されたJSONファイルからlevelデータを読み込みます。このファンクションはnilを返すかもしれない事に注意してください。オプショナルです。guardを使用してオプショナルに対応しています。
  2. tiles配列を生成します。
  3. 組み込み関数のenumerated()メソッドを使用して、tiles配列の行数分だけ反復します。このメソッドは現在の行数を返すので便利です。
  4. SpriteKit(0, 0)は画面の最下行を表しますので、ここでは行の並び順を逆に考えます。JSONファイルから読み込んだ最初の行は2Dグリッドの最後の行に相当します。
  5. 現在の行からカラムを取り出します。1が見つかる度にTileオブジェクトを生成し、tiles配列に格納していきます。

tiles配列を別な箇所で使用する必要があります。createInitialCookies()メソッドの内側の二重になっているfor-loopsなかで、さらにCookieオブジェクトを生成しているコードのあたりに、次のif-cluaseを追加してください:

// This line is new
if tiles[column, row] != nil {

  let cookieType = ...
  ...
  set.insert(cookie)
}

こうしてCookieオブジェクトはtileのある場所にだけ生成される事になります。

残っている仕事がまだ一つあります。GameViewController.swiftviewDidLoad()の中で、levelオブジェクトを生成してる行を次に置き換えてください:

level = Level(filename: "Level_1")

ビルドして実行してください。次の画像のように、四角形ではないlevelの形となるはずです:

level 1 rendered on screen

Who are you calling a square?

Tilesを可視化する

クッキーのスプライトを背景からちょこっと目立たせる為に、少し暗いtileスプライトを各クッキーの背後に描画します。これらのタイルスプライトは独自のレイヤであるtilesLayerに配置します。画像ファイルはスタータープロジェクトに含まれています。Grid.atlasの中にあります。

GameScene.swiftの中で、3つのプロパティを追加してください:

let tilesLayer = SKNode() 
let cropLayer = SKCropNode() 
let maskLayer = SKNode()

ここでは3つのレイヤーを生成します。tilesLayercropLayerそれにmaskLayerです。SKCropNodeは特殊なノードで、マスクが画像を持つ場合のみ子ノードを描画します。これによりtileのある場所にのみクッキーを描画し、決して背景の上には描画しません。

init(size:)の中で、layerPositionを生成する下記のコードを追加してください:

tilesLayer.position = layerPosition
maskLayer.position = layerPosition
cropLayer.maskNode = maskLayer
gameLayer.addChild(tilesLayer)
gameLayer.addChild(cropLayer)

tilesがcropLayer(クッキーを含んでいる)の背後となるように正しい順番で子ノードを追加するように気をつけてください。SpriteKitはzPositionが同じ場合追加された順で描画していきます。

この行を:

gameLayer.addChild(cookiesLayer)

この行で置き換えてください:

cropLayer.addChild(cookiesLayer)

こうして、cookiesLayerを直接gameLayerに追加する代わりに新しいcropLayerに追加します。

GameScene.swiftに次のメソッドも追加してください:

func addTiles() {
  // 1
  for row in 0..<numRows {
    for column in 0..<numColumns {
      if level.tileAt(column: column, row: row) != nil {
        let tileNode = SKSpriteNode(imageNamed: "MaskTile")
        tileNode.size = CGSize(width: tileWidth, height: tileHeight)
        tileNode.position = pointFor(column: column, row: row)
        maskLayer.addChild(tileNode)
      }
    }
  }

  // 2
  for row in 0...numRows {
    for column in 0...numColumns {
      let topLeft     = (column > 0) && (row < numRows)
        && level.tileAt(column: column - 1, row: row) != nil
      let bottomLeft  = (column > 0) && (row > 0)
        && level.tileAt(column: column - 1, row: row - 1) != nil
      let topRight    = (column < numColumns) && (row < numRows)
        && level.tileAt(column: column, row: row) != nil
      let bottomRight = (column < numColumns) && (row > 0)
        && level.tileAt(column: column, row: row - 1) != nil

      var value = topLeft.hashValue
      value = value | topRight.hashValue << 1
      value = value | bottomLeft.hashValue << 2
      value = value | bottomRight.hashValue << 3

      // Values 0 (no tiles), 6 and 9 (two opposite tiles) are not drawn.
      if value != 0 && value != 6 && value != 9 {
        let name = String(format: "Tile_%ld", value)
        let tileNode = SKSpriteNode(imageNamed: name)
        tileNode.size = CGSize(width: tileWidth, height: tileHeight)
        var point = pointFor(column: column, row: row)
        point.x -= tileWidth / 2
        point.y -= tileHeight / 2
        tileNode.position = point
        tilesLayer.addChild(tileNode)
      }
    }
  }
}

この処理が何をしているかを説明します。

  1. タイルがあるところならばどこでも、MaskTileスプライトをmaskLayerへ追加します。これはSKCropNodeのマスクとして機能します。
  2. これはlevelのタイルの境界となる部分のパターンを描画します。

    4つの角が丸い4角形を4分割した形を想像してください。4つのboolean型変数---topLeftbottomLefttopRighttopLeftは、背景にどの形が必要なのかを表します。例えば4辺がすべて囲まれているタイルは、つなぎ目がない背景のように描画すれば十分でしょう。しかし、グリッドの角の部分である最下行右角は上と左がつなぎ目のない次のようなタイルを必要とするでしょう:

  3. このコードはどのタイトルが必要なのかを判断し、正しいタイルを選択します。

最後にGameViewController.swiftを開いてください。そして次のコードをviewDidLoad()のsceneの直後に追加してください:

scene.addTiles()

ビルドして実行してくださいクッキーが美しく表示されます!

Looks good enough to eat!

さあ、次はどこ?

チュートリアルパート1の完成したプロジェクトファイルをトップとボトムにあるDownload Materialsからダウンロードできます。

ゲームは上手く形作られているが、完成までにまだ道のりがある。パート1ではクッキーを表示させる部分を作った。

パート2では、スワイプの検知、クッキーのスワッピング、クッキーチェインの検知を扱います。新しい内容であなたをおもてなしします;]

休息を取っている間、フォーラムであなたの意見を聞かせてください。

著作権:画像はGame Art Guppyからフリーゲームアートを使用しています。

ソースコードの一部はGabriel NicaのSwift版ゲームからヒントを得ています。

Contributors

Comments