Swift 3でSpriteKitゲームを構築する方法(パート3)

公開: 2022-03-10
簡単な要約↬SpriteKitゲームを作成するのに何が必要か疑問に思ったことはありませんか? ボタンは本来よりも大きなタスクのように見えますか? ゲームで設定を保持する方法を考えたことはありますか? SpriteKitの導入以来、iOSでのゲーム作成はかつてないほど容易になりました。 この3部構成のシリーズの第3部では、RainCatゲームを終了し、SpriteKitの紹介を完了します。 前のレッスンを見逃した場合は、GitHubでコードを取得することで追いつくことができます。 このチュートリアルにはXcode8とSwift3が必要であることに注意してください。

SpriteKitゲームを作成するのに何が必要か疑問に思ったことはありますか? ボタンは本来よりも大きなタスクのように見えますか? ゲームで設定を保持する方法を考えたことはありますか? SpriteKitの導入以来、iOSでのゲーム作成はかつてないほど容易になりました。 この3部構成のシリーズの第3部では、RainCatゲームを終了し、SpriteKitの紹介を完了します。

前のレッスンを見逃した場合は、GitHubでコードを取得することで追いつくことができます。 このチュートリアルにはXcode8とSwift3が必要であることに注意してください。

SmashingMagの詳細:リンク

  • ゲーミフィケーションとUX:ユーザーが勝つか負けるか
  • 遊び心のあるUXデザイン:より良いゲームの構築
  • UXデザインと心理学を組み合わせてユーザーの行動を変える
レインキャット、レッスン3
RainCat、レッスン3
ジャンプした後もっと! 以下を読み続けてください↓

これは、RainCatの旅のレッスン3です。 前のレッスンでは、いくつかの簡単なアニメーション、猫の行動、クイックサウンドエフェクト、バックグラウンドミュージックを見て長い一日を過ごしました。

今日は、以下に焦点を当てます。

  • スコアリング用のヘッドアップディスプレイ(HUD)。
  • メインメニュー—ボタン付き。
  • サウンドをミュートするためのオプション。
  • ゲーム終了オプション。

さらに多くの資産

最後のレッスンのアセットはGitHubで入手できます。 前のレッスンで行ったように、画像をAssets.xcassetsに再度ドラッグします。

注意喚起!

スコアを維持する方法が必要です。 これを行うために、ヘッドアップディスプレイ(HUD)を作成できます。 これは非常に簡単です。 スコアとゲームを終了するためのボタンを含むSKNodeになります。 今のところ、スコアに焦点を当てます。 使用するフォントは、Dafont.comで入手できるPixelDigivolveです。 自分のものではない画像や音声を使用する場合と同様に、使用する前にフォントのライセンスをお読みください。 これは個人的な使用は無料であると述べていますが、フォントが本当に気に入った場合は、ページから作者に寄付することができます。 いつも自分ですべてを作ることはできないので、途中であなたを助けてくれた人たちに恩返しをするのはいいことです。

次に、カスタムフォントをプロジェクトに追加する必要があります。 このプロセスは、初めてトリッキーになる可能性があります。

フォントをダウンロードして、「Fonts」フォルダの下のプロジェクトフォルダに移動します。 これは前のレッスンで数回行ったので、このプロセスをもう少し早く実行します。 Fontsという名前のグループをプロジェクトに追加し、 Pixel digivolve.otfファイルを追加します。

ここで注意が必要な部分があります。 この部分を見逃すと、おそらくフォントを使用できなくなります。 Info.plistファイルに追加する必要があります。 このファイルはXcodeの左側のペインにあります。 それをクリックすると、プロパティリスト(またはplist )が表示されます。 リストを右クリックし、「行の追加」をクリックします。

行を追加する
plistに行を追加します。

新しい行が表示されたら、次のように入力します。

 Fonts provided by application

次に、 Item 0の下に、フォントの名前を追加する必要があります。 plistは次のようになります。

ピクセルdigivolve.otf
フォントがplistに正常に追加されました!

フォントを使用する準備ができているはずです。 意図したとおりに機能することを確認するために、簡単なテストを行う必要があります。 GameScene.swift sceneDidLoad関数の上部に次のコードを追加します。

 let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)

それは機能しますか?

こんにちは世界!
SKLabelNodeをテストします。 大野! 「HelloWorld」レーベルが帰ってきました。

それが機能する場合は、すべてが正しく行われています。 そうでない場合は、何かが間違っています。 Code With Chrisには、より詳細なトラブルシューティングガイドがありますが、これは古いバージョンのSwift用であるため、Swift3に移行するには微調整を行う必要があることに注意してください。

カスタムフォントをロードできるようになったので、HUDから始めることができます。 「HelloWorld」ラベルは、フォントが読み込まれることを確認するためにのみ使用したため、削除してください。 HUDはSKNodeになり、HUD要素のコンテナのように機能します。 これは、レッスン1でバックグラウンドノードを作成するときに実行したプロセスと同じです。

通常の方法を使用してHudNode.swiftファイルを作成し、次のコードを入力します。

 import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }

他の作業を行う前に、 Constants.swiftを開き、ファイルの最後に次の行を追加します。これを使用して、ハイスコアを取得して保持します。

 let ScoreKey = "RAINCAT_HIGHSCORE"

コードには、スコアボードに関連する5つの変数があります。 最初の変数は実際のSKLabelNodeであり、これを使用してラベルを表示します。 次は、現在のスコアを保持するための変数です。 次に、最高のスコアを保持する変数。 最後の変数は、現在ハイスコアを表示しているかどうかを示すブール値です(これを使用して、スコアボードのスケールを大きくし、床の黄色に色付けするためにSKActionを実行する必要があるかどうかを確認します)。

最初の関数setup(size:)は、すべてを設定するためだけのものです。 以前と同じ方法でSKLabelNodeを設定しました。 SKNodeクラスにはデフォルトでサイズプロパティがないため、 scoreNodeラベルを配置するためのサイズを設定する方法を作成する必要があります。 UserDefaultsから現在のハイスコアも取得しています。 これは、データの小さなチャンクをすばやく簡単に保存する方法ですが、安全ではありません。 この例ではセキュリティについて心配していないため、 UserDefaultsはまったく問題ありません。

addPoint()では、現在のscore変数をインクリメントし、ユーザーが高スコアを取得したかどうかを確認しています。 スコアが高い場合は、そのスコアをUserDefaultsに保存し、現在最高のスコアを表示しているかどうかを確認します。 ユーザーが高いスコアを達成した場合、 scoreNodeのサイズと色をアニメーション化できます。

resetPoints()関数では、現在のスコアを0に設定します。 次に、ハイスコアを表示しているかどうかを確認し、必要に応じてサイズと色をデフォルト値にリセットする必要があります。

最後に、 updateScoreboardという名前の小さな関数があります。 これは、スコアをscoreNodeのテキストに設定するための内部関数です。 これは、 addPoint()resetPoints() )の両方で呼び出されます。

HUDを接続する

HUDが正しく機能しているかどうかをテストする必要があります。 GameScene.swiftに移動し、ファイルの上部にあるfoodNode変数の下に次の行を追加します。

 private let hudNode = HudNode()

上部近くのsceneDidLoad()関数に次の2行を追加します。

 hudNode.setup(size: size) addChild(hudNode)

次に、 spawnCat()関数で、猫が画面から落ちた場合に備えてポイントをリセットします。 猫のスプライトをシーンに追加した後、次の行を追加します。

 hudNode.resetPoints()

次に、 handleCatCollision(contact:)関数で、猫が雨に見舞われたときにスコアを再度リセットする必要があります。 関数の最後にあるswitchステートメント(もう一方の本体がRainDropCategoryの場合)に、次の行を追加します。

 hudNode.resetPoints()

最後に、ユーザーがポイントを獲得したときにスコアボードに通知する必要があります。 handleFoodHit(contact:)のファイルの最後で、ここまでの次の行を見つけます。

 //TODO increment points print("fed cat")

そして、それらをこれに置き換えます:

 hudNode.addPoint()

出来上がり!

HUDのロックが解除されました!
HUDのロックが解除されました!

HUDの動作が表示されます。 走り回って食べ物を集めましょう。 初めて食べ物を集めると、スコアが黄色に変わり、スケールが大きくなるのがわかります。 あなたがこれが起こるのを見るとき、猫を殴らせてください。 スコアがリセットされると、正しい方向に進んでいることがわかります。

高得点!
史上最高のスコア(…執筆時点)!

次のシーン

そうです、別のシーンに移ります! 実際、完了すると、これがアプリの最初の画面になります。 他の作業を行う前に、 Constants.swiftを開き、ファイルの最後に次の行を追加します。これを使用して、ハイスコアを取得して保持します。

 let ScoreKey = "RAINCAT_HIGHSCORE"

新しいシーンを作成し、「Scenes」フォルダーの下に配置して、 MenuScene.swiftという名前を付けます。 MenuScene.swiftファイルに次のコードを入力します。

 import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }

このシーンは比較的単純なので、特別なクラスは作成しません。 私たちのシーンは2つのボタンで構成されます。 これらは、独自のSKSpriteNodesのクラスである可能性があります(おそらくそれに値する)が、十分に異なるため、それらの新しいクラスを作成する必要はありません。 これは、独自のゲームを構築する際の重要なヒントです。複雑になったときにコードを停止してリファクタリングする場所を決定できる必要があります。 ゲームに3つまたは4つ以上のボタンを追加したら、メニューボタンのコードを停止して独自のクラスにリファクタリングするときが来るかもしれません。

上記のコードは特別なことは何もしていません。 4つのスプライトの位置を設定しています。 また、背景全体が正しい値になるように、シーンの背景色も設定しています。 XcodeのHEX文字列からカラーコードを生成するための優れたツールはUIColorです。 上記のコードは、ボタンの状態のテクスチャも設定しています。 ゲームを開始するためのボタンは通常の状態と押された状態ですが、サウンドボタンはトグルです。 トグルの操作を簡単にするために、ユーザーが押すとサウンドボタンのアルファ値が変更されます。 また、ハイスコアのSKLabelNodeをプルして設定しています。

MenuSceneはかなり見栄えがします。 次に、アプリが読み込まれたときにシーンを表示する必要があります。 GameViewController.swiftに移動し、次の行を見つけます。

 let sceneNode = GameScene(size: view.frame.size)

これに置き換えます:

 let sceneNode = MenuScene(size: view.frame.size)

この小さな変更により、 GameScene MenuSceneデフォルトでロードされます。

私たちの新しいシーン!
私たちの新しいシーン! 1秒あたり1.0フレームに注意してください。何も動いていないため、何も更新する必要はありません。

ボタンの状態

SpriteKitではボタンが扱いにくい場合があります。 サードパーティのオプションはたくさんありますが(私も自分で作成しました)、理論的には3つのタッチ方法を知っていれば十分です。

  • touchesBegan(_ touches: with event:)
  • touchesMoved(_ touches: with event:)
  • touchesEnded(_ touches: with event:)

アンブレラを更新するときにこれについて簡単に説明しましたが、次に、どのボタンがタッチされたか、ユーザーがタップを離したかそのボタンをクリックしたか、ユーザーがまだボタンに触れているかどうかを知る必要があります。 ここで、 selectedButton変数が機能します。 タッチが始まると、ユーザーがその変数でクリックし始めたボタンをキャプチャできます。 ボタンの外側にドラッグすると、これを処理して適切なテクスチャを与えることができます。 タッチを離すと、ボタンの内側にまだタッチしているかどうかを確認できます。 そうである場合は、関連するアクションをそれに適用できます。 MenuScene.swiftの下部に次の行を追加します。

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }

これは、2つのボタンの簡単なボタン処理です。 touchesBegan(_ touches: with events:)では、現在選択されているボタンがあるかどうかを確認することから始めます。 その場合、ボタンの状態を押されていない状態にリセットする必要があります。 次に、ボタンが押されているかどうかを確認する必要があります。 1つを押すと、ボタンの強調表示された状態が表示されます。 次に、 selectedButtonを他の2つの方法で使用するボタンに設定します。

touchesMoved(_ touches: with events:)では、どのボタンが最初にタッチされたかを確認します。 次に、現在のタッチがまだselectedButtonの範囲内にあるかどうかを確認し、そこから強調表示された状態を更新します。 startButtonの強調表示された状態は、テクスチャを押された状態のテクスチャに変更します。ここで、 soundButtonの強調表示された状態では、スプライトのアルファ値が50%に設定されています。

最後に、 touchesEnded(_ touches: with event:)で、選択されているボタンがある場合はそれを再度確認し、タッチがまだボタンの範囲内にあるかどうかを確認します。 すべてのケースが満たされた場合、正しいボタンに対してhandleStartButtonClick handleStartButtonClick()またはhandleSoundButtonClick() )を呼び出します。

行動の時

基本的なボタンの動作が下がったので、クリックされたときにトリガーするイベントが必要です。 実装が簡単なボタンはstartButtonです。 クリックすると、 GameSceneを表示するだけで済みます。 MenuScene.swift関数のhandleStartButtonClick()を次のコードに更新します。

 func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }

今すぐアプリを実行してボタンを押すと、ゲームが始まります!

次に、ミュートトグルを実装する必要があります。 すでにサウンドマネージャーがありますが、ミューティングがオンかオフかを判断できる必要があります。 Constants.swiftで、ミューティングがオンのときに持続するキーを追加する必要があります。 次の行を追加します。

 let MuteKey = "RAINCAT_MUTED"

これを使用して、ブール値をUserDefaultsに保存します。 これが設定されたので、 SoundManager.swiftに移動できます。 ここで、 UserDefaultsをチェックして設定し、ミューティングがオンかオフかを確認します。 ファイルの先頭のtrackPosition変数の下に、次の行を追加します。

 private(set) var isMuted = false

これは、メインメニュー(およびサウンドを再生するその他のもの)がサウンドが許可されているかどうかを判断するためにチェックする変数です。 falseとして初期化しますが、 UserDefaultsをチェックして、ユーザーが何を望んでいるかを確認する必要があります。 init()関数を次のように置き換えます。

 private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }

isMutedのデフォルト値ができたので、それを変更する機能が必要です。 次のコードをSoundManager.swiftの下部に追加します。

 func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }

このメソッドは、ミュートされた変数を切り替え、 UserDefaultsを更新します。 新しい値がミュートされていない場合、音楽の再生が開始されます。 新しい値がミュートされている場合、再生は開始されません。 それ以外の場合は、現在のトラックの再生を停止します。 この後、 startPlaying()ifステートメントを編集する必要があります。

次の行を見つけます。

 if audioPlayer == nil || audioPlayer?.isPlaying == false {

そしてそれをこれに置き換えてください:

 if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {

ここで、ミューティングがオフで、オーディオプレーヤーが設定されていないか、現在のオーディオプレーヤーが再生されていない場合、次のトラックを再生します。

ここから、 MenuScene.swiftに戻って、ミュートボタンを終了できます。 handleSoundbuttonClick()を次のコードに置き換えます。

 func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }

これにより、 SoundManagerのサウンドが切り替わり、結果が確認され、テクスチャが適切に設定されて、サウンドがミュートされているかどうかがユーザーに表示されます。 ほぼ完了です! 起動時にボタンの初期テクスチャを設定するだけです。 sceneDidLoad()で、次の行を見つけます。

 soundButton = SKSpriteNode(texture: soundButtonTexture)

そしてそれをこれに置き換えてください:

 soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)

上記の例では、三項演算子を使用して正しいテクスチャを設定しています。

音楽が接続されたので、 CatSprite.swiftに移動して、ミュートがオンのときに猫の鳴き声を無効にすることができます。 hitByRain()で、歩行アクションを削除した後、次のifステートメントを追加できます。

 if SoundManager.sharedInstance.isMuted { return }

このステートメントは、ユーザーがアプリをミュートしたかどうかを返します。 このため、 currentRainHitsmaxRainHits 、および鳴き声の効果音は完全に無視されます。

それがすべて終わったら、今度はミュートボタンを試してみましょう。 アプリを実行し、サウンドが適切に再生およびミュートされているかどうかを確認します。 サウンドをミュートし、アプリを閉じてから再度開きます。 ミュート設定が持続することを確認してください。 Xcodeからアプリをミュートして再実行しただけでは、 UserDefaultsを保存するのに十分な時間が与えられていない可能性があることに注意してください。 ゲームをプレイし、ミュートされているときに猫がニャーと鳴かないようにします。

ボタンの機能をテストします。

ゲームを終了する

メインメニューの最初のタイプのボタンができたので、ゲームシーンに終了ボタンを追加することで、トリッキーなビジネスに取り掛かることができます。 いくつかの興味深い相互作用は、私たちのゲームスタイルを思い付くことができます。 現在、傘はユーザーが触れたり動かしたりする場所に移動します。 明らかに、ユーザーがゲームを終了しようとしているときに傘が終了ボタンに移動するのは、ユーザーエクスペリエンスがかなり悪いため、これが発生しないようにします。

実装している終了ボタンは、前に追加したゲーム開始ボタンを模倣し、プロセスの多くは同じままです。 変更は、タッチの処理方法にあります。 quit_buttonおよびquit_button_pressedアセットをAssets.xcassetsファイルに取得し、次のコードをHudNode.swiftファイルに追加します。

 private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")

これにより、ボタンの状態に設定するテクスチャとともに、 quitButton参照が処理されます。 終了しようとしているときにアンブレラを誤って更新しないようにするには、アンブレラではなく終了ボタンを操作していることをHUD(およびゲームシーン)に通知する変数が必要です。 showingHighScoreブール変数の下に次のコードを追加します。

 private(set) var quitButtonPressed = false

繰り返しますが、これはHudNodeのみが設定できる変数ですが、他のクラスはチェックできます。 変数が設定されたので、ボタンをHUDに追加できます。 次のコードをsetup(size:)関数に追加します。

 quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)

上記のコードは、押されていない状態のテクスチャで終了ボタンを設定します。 また、位置を右上隅に設定し、 zPositionを高い数値に設定して、常に上に描画するようにします。 今すぐゲームを実行すると、 GameSceneに表示されますが、まだクリックできません。

終了ボタン
HUDの新しい終了ボタンに注意してください。

ボタンが配置されたので、ボタンを操作できるようにする必要があります。 現在、 GameSceneでインタラクションを行う唯一の場所は、 umbrellaSpriteとインタラクションしているときです。 この例では、HUDがアンブレラよりも優先されるため、ユーザーはアンブレラを移動して終了する必要がありません。 HudNode.swift GameScene.swiftタッチ機能を模倣できます。 次のコードをHudNode.swiftに追加します。

 func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }

上記のコードは、 MenuScene用に作成したコードとよく似ています。 違いは、追跡するボタンが1つしかないため、これらのタッチメソッド内ですべてを処理できることです。 また、 GameSceneでタッチの位置がわかるので、ボタンにタッチポイントが含まれているかどうかを確認できます。

GameScene.swiftに移動し、 touchesBegan(_ touches with event:)およびtouchesMoved(_ touches: with event:)メソッドを次のコードに置き換えます。

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }

ここでは、各メソッドがほぼ同じ方法ですべてを処理します。 ユーザーがシーンを操作したことをHUDに伝えています。 次に、終了ボタンが現在タッチをキャプチャしているかどうかを確認します。 そうでない場合は、傘を動かします。 また、 touchesEnded(_ touches: with event:)関数を追加して、終了ボタンのクリックの終了を処理しましたが、これはまだumbrellaSpriteスプライトには使用していません。

終了ボタンをクリックしても傘は移動しませんが、他の場所をクリックすると移動します。

ボタンができたので、 GameSceneに影響を与える方法が必要です。 HudeNode.swiftの先頭に次の行を追加します。

 var quitButtonAction : (() -> ())?

これは、入力も出力もない一般的なクロージャです。 これをGameScene.swiftファイルのコードで設定し、 HudNode.swiftのボタンをクリックしたときに呼び出します。 次に、 touchEndedAtPoint(point:)関数で以前に作成したコードのTODOを次のように置き換えることができます。

 if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }

ここで、 quitButtonActionクロージャを設定すると、この時点から呼び出されます。

quitButtonActionクロージャーを設定するには、 GameScene.swiftに移動する必要があります。 sceneDidLoad()で、HUDセットアップを次のコードに置き換えることができます。

 hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)

アプリを実行し、再生を押してから、終了を押します。 メインメニューに戻った場合は、終了ボタンが意図したとおりに機能しています。 作成したクロージャーで、 MenuSceneへの遷移を初期化しました。 そして、このクロージャをHUDノードに設定して、終了ボタンがクリックされたときに実行されるようにします。 ここでのもう1つの重要な行は、 quitButtonActionnilに設定する場合です。 これは、保持サイクルが発生しているためです。 シーンはHUDへの参照を保持しており、HUDはシーンへの参照を保持しています。 両方のオブジェクトへの参照があるため、ガベージコレクションのときにどちらも破棄されません。 この場合、 GameSceneに出入りするたびに、別のインスタンスが作成され、リリースされることはありません。 これはパフォーマンスに悪影響を及ぼし、アプリは最終的にメモリを使い果たします。 これを回避する方法はいくつかありますが、この場合、HUDからGameSceneへの参照を削除するだけで、 MenuSceneに戻るとシーンとHUDが終了します。 Krakendevには、参照型とこれらのサイクルを回避する方法についてのより深い説明があります。

次に、 GameViewController.swiftに移動し、次の3行のコードを削除またはコメントアウトします。

 view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true

デバッグデータが邪魔にならないので、ゲームは本当に見栄えがします! おめでとうございます:現在ベータ版です! GitHubで今日の最終的なコードを確認してください。

最終的な考え

これは3部構成のチュートリアルの最後のレッスンであり、これまでに達成した場合は、ゲームで多くの作業を行っただけです。 このチュートリアルでは、まったく何も含まれていないシーンから、完成したゲームに移行しました。 おめでとうございます! レッスン1では、床、雨滴、背景、傘のスプライトを追加しました。 また、物理学をいじって、雨滴がたまらないようにしました。 衝突検出から始めて、メモリが不足しないようにノードのカリングに取り組みました。 また、ユーザーが画面上でタッチした場所に傘を移動できるようにすることで、ユーザーとの対話を追加しました。

レッスン2では、猫と餌を追加し、それぞれにカスタムのスポーン方法を追加しました。 猫と食べ物のスプライトを許可するように衝突検出を更新しました。 猫の動きにも取り組みました。 猫は目的を達成しました:利用可能な食べ物を少しずつ食べる。 猫用のシンプルなアニメーションを追加し、猫と雨の間のカスタムインタラクションを追加しました。 最後に、効果音と音楽を追加して、完全なゲームのように感じさせました。

この最後のレッスンでは、スコアラベルと終了ボタンを保持するためのヘッドアップディスプレイを作成しました。 ノード間でアクションを処理し、ユーザーがHUDノードからのコールバックで終了できるようにしました。 また、ユーザーが起動して、終了ボタンをクリックした後に戻ることができる別のシーンを追加しました。 ゲームの開始とゲーム内のサウンドの制御のプロセスを処理しました。

ここからどこへ行くか

これまでに多くの時間を費やしましたが、このゲームに取り掛かることができる作業はまだたくさんあります。 RainCatはまだ開発を続けており、AppStoreで入手できます。 以下は、追加する必要があるものと必要なもののリストです。 一部のアイテムは追加されましたが、他のアイテムはまだ保留中です。

  • アイコンとスプラッシュ画面を追加します。
  • メインメニューを完成させます(チュートリアルのために簡略化されています)。
  • 不正な雨滴や複数の食料の産卵などのバグを修正します。
  • コードをリファクタリングして最適化します。
  • スコアに基づいてゲームのカラーパレットを変更します。
  • スコアに基づいて難易度を更新します。
  • 食べ物が真上にあるときに猫をアニメートします。
  • ゲームセンターを統合します。
  • クレジットを付与します(音楽トラックの適切なクレジットを含む)。

これらの変更は将来行われるため、GitHubを追跡してください。 コードについてご不明な点がございましたら、hello @ thirteen23.comまでお気軽にお問い合わせください。ご相談させていただきます。 特定のトピックが十分に注目されている場合は、そのトピックについて説明している別の記事を書くことができます。

ありがとう!

ゲームの作成とそれに伴う記事の作成に協力してくれたすべての人に感謝したいと思います。

  • Cathryn Rowe初期のアート、デザイン、編集、およびガレージでの記事の公開。
  • Morgan Wheaton最終的なメニューデザインとカラーパレット(これらの機能を実際に実装すると、見栄えが良くなります。しばらくお待ちください)。
  • Nikki Clark記事のすばらしいヘッダーと仕切り、および記事の編集のヘルプ。
  • Laura Levisay記事にあるすべての素晴らしいGIFと、道徳的なサポートのためにかわいい猫のGIFを送ってくれました。
  • トム・ハドソン記事の編集を手伝ってくれて、誰がいなければこのシリーズはまったく作られなかったでしょう。
  • LaniDeGuire記事の編集を手伝ってくれました。これは大変な作業でした。
  • ジェフムーンレッスン3とピンポンの編集を手伝ってくれました。 たくさんのピンポン。
  • TomNelsonチュートリアルが正常に機能することを確認するのを手伝ってくれました。

真剣に、この記事のすべてを準備し、それを店にリリースするのにたくさんの人がかかりました。

この文章を読んでくださった皆様、ありがとうございました。