Cara Membuat Game SpriteKit Di Swift 3 (Bagian 3)

Diterbitkan: 2022-03-10
Ringkasan cepat Pernahkah Anda bertanya-tanya apa yang diperlukan untuk membuat game SpriteKit? Apakah tombol tampak seperti tugas yang lebih besar dari yang seharusnya? Pernah bertanya-tanya bagaimana cara mempertahankan pengaturan dalam game? Pembuatan game tidak pernah semudah ini di iOS sejak diperkenalkannya SpriteKit. Di bagian ketiga dari seri tiga bagian ini, kita akan menyelesaikan game RainCat dan menyelesaikan pengenalan kita pada SpriteKit. Jika Anda melewatkan pelajaran sebelumnya, Anda dapat mengejar ketinggalan dengan mendapatkan kode di GitHub. Ingat bahwa tutorial ini membutuhkan Xcode 8 dan Swift 3.

Pernahkah Anda bertanya-tanya apa yang diperlukan untuk membuat game SpriteKit? Apakah tombol tampak seperti tugas yang lebih besar dari yang seharusnya? Pernah bertanya-tanya bagaimana cara mempertahankan pengaturan dalam game? Pembuatan game tidak pernah semudah ini di iOS sejak diperkenalkannya SpriteKit. Di bagian ketiga dari seri tiga bagian ini, kita akan menyelesaikan game RainCat dan menyelesaikan pengenalan kita pada SpriteKit.

Jika Anda melewatkan pelajaran sebelumnya, Anda dapat mengejar ketinggalan dengan mendapatkan kode di GitHub. Ingat bahwa tutorial ini membutuhkan Xcode 8 dan Swift 3.

Bacaan Lebih Lanjut di SmashingMag: Tautan

  • Gamification Dan UX: Dimana Pengguna Menang Atau Kalah
  • Desain UX yang Menyenangkan: Membangun Game yang Lebih Baik
  • Menggabungkan Desain UX Dan Psikologi Untuk Mengubah Perilaku Pengguna
Kucing hujan, pelajaran 3
RainCat, pelajaran 3
Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

Ini adalah pelajaran ketiga dalam perjalanan RainCat kami. Dalam pelajaran sebelumnya, kita menjalani hari yang panjang dengan beberapa animasi sederhana, perilaku kucing, efek suara cepat, dan musik latar.

Hari ini kita akan fokus pada hal-hal berikut:

  • head-up display (HUD) untuk penilaian;
  • menu utama — dengan tombol;
  • pilihan untuk mematikan suara;
  • opsi berhenti bermain.

Bahkan Lebih Banyak Aset

Aset untuk pelajaran terakhir tersedia di GitHub. Seret gambar ke dalam Assets.xcassets lagi, seperti yang kita lakukan di pelajaran sebelumnya.

Perhatian!

Kami membutuhkan cara untuk menjaga skor. Untuk melakukan ini, kita dapat membuat head-up display (HUD). Ini akan sangat sederhana; itu akan menjadi SKNode yang berisi skor dan tombol untuk keluar dari permainan. Untuk saat ini, kami hanya akan fokus pada skor. Font yang akan kita gunakan adalah Pixel Digivolve, yang bisa Anda dapatkan di Dafont.com. Seperti halnya menggunakan gambar atau suara yang bukan milik Anda, baca lisensi font sebelum menggunakannya. Yang ini menyatakan bahwa itu gratis untuk penggunaan pribadi, tetapi jika Anda benar-benar menyukai fontnya, Anda dapat menyumbang kepada penulis dari halaman tersebut. Anda tidak selalu dapat membuat semuanya sendiri, jadi memberi kembali kepada mereka yang telah membantu Anda selama ini adalah hal yang baik.

Selanjutnya, kita perlu menambahkan font khusus ke proyek. Proses ini bisa rumit untuk pertama kalinya.

Unduh dan pindahkan font ke folder proyek, di bawah folder "Font". Kita telah melakukan ini beberapa kali dalam pelajaran sebelumnya, jadi kita akan melalui proses ini sedikit lebih cepat. Tambahkan grup bernama Fonts ke proyek, dan tambahkan file Pixel digivolve.otf .

Sekarang sampai pada bagian yang sulit. Jika Anda melewatkan bagian ini, Anda mungkin tidak akan dapat menggunakan font tersebut. Kita perlu menambahkannya ke file Info.plist kita. File ini ada di panel kiri Xcode. Klik dan Anda akan melihat daftar properti (atau plist ). Klik kanan pada daftar, dan klik "Tambah Baris."

Menambahkan baris
Tambahkan baris ke plist .

Ketika baris baru muncul, masukkan berikut ini:

 Fonts provided by application

Kemudian, di bawah Item 0 , kita perlu menambahkan nama font kita. plist akan terlihat seperti berikut:

Piksel digivolve.otf
plist berhasil ditambahkan ke daftar!

Font harus siap digunakan! Kita harus melakukan tes cepat untuk memastikan itu berfungsi sebagaimana dimaksud. Pindah ke GameScene.swift , dan di sceneDidLoad tambahkan kode berikut di bagian atas fungsi:

 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)

Apakah itu bekerja?

Halo Dunia!
Menguji SKLabelNode kami. Oh tidak! Label "Halo Dunia" kembali.

Jika berhasil, maka Anda telah melakukan semuanya dengan benar. Jika tidak, maka ada sesuatu yang salah. Code With Chris memiliki panduan pemecahan masalah yang lebih mendalam, tetapi perhatikan bahwa ini untuk versi Swift yang lebih lama, jadi Anda harus membuat sedikit penyesuaian untuk membawanya ke Swift 3.

Sekarang kita dapat memuat dalam font khusus, kita dapat memulai pada HUD kita. Hapus label “Hello World”, karena kami hanya menggunakannya untuk memastikan font kami dimuat. HUD akan menjadi SKNode , bertindak seperti wadah untuk elemen HUD kami. Ini adalah proses yang sama yang kami ikuti saat membuat simpul latar belakang dalam pelajaran satu.

Buat file HudNode.swift menggunakan metode biasa, dan masukkan kode berikut:

 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)" } }

Sebelum kita melakukan hal lain, buka Constants.swift dan tambahkan baris berikut ke bagian bawah file — kita akan menggunakannya untuk mengambil dan mempertahankan skor tinggi:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Dalam kode, kami memiliki lima variabel yang berhubungan dengan papan skor. Variabel pertama adalah SKLabelNode yang sebenarnya, yang kita gunakan untuk menampilkan label. Berikutnya adalah variabel kami untuk menahan skor saat ini; maka variabel yang memegang skor terbaik. Variabel terakhir adalah boolean yang memberi tahu kita apakah kita sedang menyajikan skor tinggi (kita menggunakan ini untuk menetapkan apakah kita perlu menjalankan SKAction untuk meningkatkan skala papan skor dan mewarnainya menjadi kuning lantai).

Fungsi pertama, setup(size:) , ada hanya untuk mengatur semuanya. Kami menyiapkan SKLabelNode dengan cara yang sama seperti yang kami lakukan sebelumnya. Kelas SKNode tidak memiliki properti ukuran apa pun secara default, jadi kita perlu membuat cara untuk mengatur ukuran untuk memposisikan label scoreNode kita. Kami juga mengambil skor tinggi saat ini dari UserDefaults . Ini adalah cara cepat dan mudah untuk menyimpan potongan kecil data, tetapi tidak aman. Karena kami tidak khawatir tentang keamanan untuk contoh ini, UserDefaults baik-baik saja.

Di addPoint() , kami menambah variabel score saat ini dan memeriksa apakah pengguna mendapatkan skor tinggi. Jika mereka memiliki skor tinggi, maka kami menyimpan skor itu ke UserDefaults dan memeriksa apakah kami saat ini menunjukkan skor terbaik. Jika pengguna telah mencapai skor tinggi, kami dapat menganimasikan ukuran dan warna scoreNode .

Dalam fungsi resetPoints() , kami menetapkan skor saat ini ke 0 . Kami kemudian perlu memeriksa apakah kami menunjukkan skor tinggi, dan mengatur ulang ukuran dan warna ke nilai default jika diperlukan.

Akhirnya, kami memiliki fungsi kecil bernama updateScoreboard . Ini adalah fungsi internal untuk mengatur skor ke teks scoreNode . Ini dipanggil di addPoint() dan resetPoints() .

Menghubungkan HUD

Kita perlu menguji apakah HUD kita bekerja dengan benar. Pindah ke GameScene.swift , dan tambahkan baris berikut di bawah variabel foodNode di bagian atas file:

 private let hudNode = HudNode()

Tambahkan dua baris berikut dalam fungsi sceneDidLoad() , di dekat bagian atas:

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

Kemudian, dalam fungsi spawnCat() , setel ulang poin jika kucing jatuh dari layar. Tambahkan baris berikut setelah menambahkan sprite kucing ke adegan:

 hudNode.resetPoints()

Selanjutnya, pada fungsi handleCatCollision(contact:) , kita perlu mengatur ulang skor saat kucing terkena hujan. Dalam pernyataan switch di akhir fungsi — ketika badan lainnya adalah RainDropCategory — tambahkan baris berikut:

 hudNode.resetPoints()

Terakhir, kita perlu memberi tahu papan skor ketika pengguna telah mendapatkan poin. Di akhir file di handleFoodHit(contact:) , temukan baris berikut hingga di sini:

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

Dan ganti dengan ini:

 hudNode.addPoint()

Voila!

HUD tidak terkunci!
HUD tidak terkunci!

Anda akan melihat HUD beraksi. Berlari dan kumpulkan beberapa makanan. Saat pertama kali mengumpulkan makanan, Anda akan melihat skor menjadi kuning dan bertambah besar. Ketika Anda melihat ini terjadi, biarkan kucing itu tertabrak. Jika skor diatur ulang, maka Anda akan tahu bahwa Anda berada di jalur yang benar!

Skor tinggi!
Skor tertinggi yang pernah ada (… pada saat penulisan)!

Adegan Berikutnya

Itu benar, kita pindah ke adegan lain! Bahkan, ketika selesai, ini akan menjadi layar pertama aplikasi kita. Sebelum Anda melakukan hal lain, buka Constants.swift dan tambahkan baris berikut ke bagian bawah file — kami akan menggunakannya untuk mengambil dan mempertahankan skor tinggi:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Buat adegan baru, letakkan di bawah folder "Scenes", dan beri nama MenuScene.swift . Masukkan kode berikut di file 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) } }

Karena adegan ini relatif sederhana, kami tidak akan membuat kelas khusus. Adegan kami akan terdiri dari dua tombol. Ini bisa menjadi (dan mungkin pantas menjadi) kelas mereka sendiri dari SKSpriteNodes , tetapi karena mereka cukup berbeda, kita tidak perlu membuat kelas baru untuk mereka. Ini adalah tip penting ketika Anda membangun gim Anda sendiri: Anda harus bisa menentukan di mana harus berhenti dan memfaktorkan ulang kode saat keadaan menjadi rumit. Setelah Anda menambahkan lebih dari tiga atau empat tombol ke permainan, mungkin sudah waktunya untuk berhenti dan memfaktorkan ulang kode tombol menu ke dalam kelasnya sendiri.

Kode di atas tidak melakukan sesuatu yang istimewa; itu adalah pengaturan posisi empat sprite. Kami juga mengatur warna latar belakang pemandangan, sehingga seluruh latar belakang adalah nilai yang benar. Alat yang bagus untuk menghasilkan kode warna dari string HEX untuk Xcode adalah Warna UI. Kode di atas juga mengatur tekstur untuk status tombol kita. Tombol untuk memulai permainan memiliki status normal dan status ditekan, sedangkan tombol suara adalah sakelar. Untuk menyederhanakan berbagai hal untuk sakelar, kami akan mengubah nilai alfa tombol suara saat pengguna menekan. Kami juga menarik dan mengatur SKLabelNode skor tinggi.

MenuScene kami terlihat cukup bagus. Sekarang kita perlu menunjukkan adegan saat aplikasi dimuat. Pindah ke GameViewController.swift dan temukan baris berikut:

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

Ganti dengan ini:

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

Perubahan kecil ini akan memuat MenuScene secara default, bukan GameScene .

Adegan baru kami!
Adegan baru kami! Perhatikan 1,0 frame per detik: Tidak ada yang bergerak, jadi tidak perlu memperbarui apa pun.

Status Tombol

Tombol bisa rumit di SpriteKit. Banyak opsi pihak ketiga yang tersedia (saya bahkan membuatnya sendiri), tetapi secara teori Anda hanya perlu mengetahui tiga metode sentuhan:

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

Kami membahas ini secara singkat saat memperbarui payung, tetapi sekarang kami perlu mengetahui hal berikut: tombol mana yang disentuh, apakah pengguna melepaskan ketukannya atau mengklik tombol itu, dan apakah pengguna masih menyentuhnya. Di sinilah variabel selectedButton kami berperan. Saat sentuhan dimulai, kami dapat menangkap tombol yang mulai diklik pengguna dengan variabel itu. Jika mereka menyeret di luar tombol, kami dapat menangani ini dan memberikan tekstur yang sesuai untuk itu. Ketika mereka melepaskan sentuhan, kita kemudian dapat melihat apakah mereka masih menyentuh di dalam tombol. Jika ya, maka kita dapat menerapkan tindakan terkait padanya. Tambahkan baris berikut ke bagian bawah 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") }

Ini adalah penanganan tombol sederhana untuk dua tombol kami. Dalam touchesBegan(_ touches: with events:) , kami memulai dengan memeriksa apakah kami memiliki tombol yang dipilih saat ini. Jika ya, kita perlu mengatur ulang status tombol menjadi tidak ditekan. Kemudian, kita perlu memeriksa apakah ada tombol yang ditekan. Jika salah satu ditekan, itu akan menunjukkan status yang disorot untuk tombol. Kemudian, kami mengatur selectedButton ke tombol untuk digunakan dalam dua metode lainnya.

Dalam touchesMoved(_ touches: with events:) , kami memeriksa tombol mana yang awalnya disentuh. Kemudian, kami memeriksa apakah sentuhan saat ini masih dalam batas selectedButton , dan kami memperbarui status yang disorot dari sana. startButton mengubah tekstur menjadi tekstur status ditekan, di mana status soundButton memiliki nilai alfa sprite yang disetel ke 50%.

Terakhir, di touchesEnded(_ touches: with event:) , kami memeriksa kembali tombol mana yang dipilih, jika ada, dan kemudian apakah sentuhan masih dalam batas tombol. Jika semua kasus terpenuhi, kami memanggil handleStartButtonClick() atau handleSoundButtonClick() untuk tombol yang benar.

Saatnya Beraksi

Sekarang setelah kami memiliki perilaku tombol dasar, kami membutuhkan sebuah peristiwa untuk dipicu ketika mereka diklik. Tombol yang lebih mudah untuk diterapkan adalah startButton . Saat di klik, kita hanya perlu menampilkan GameScene . Perbarui handleStartButtonClick() dalam fungsi MenuScene.swift ke kode berikut:

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

Jika Anda menjalankan aplikasi sekarang dan menekan tombol, permainan akan dimulai!

Sekarang kita perlu mengimplementasikan tombol mute. Kami sudah memiliki pengelola suara, tetapi kami harus dapat mengetahui apakah mematikan atau mematikan. Di Constants.swift , kita perlu menambahkan kunci untuk bertahan saat mematikan aktif. Tambahkan baris berikut:

 let MuteKey = "RAINCAT_MUTED"

Kami akan menggunakan ini untuk menyimpan nilai boolean ke UserDefaults . Sekarang setelah ini diatur, kita dapat pindah ke SoundManager.swift . Di sinilah kita akan memeriksa dan mengatur UserDefaults untuk melihat apakah mematikan atau mematikan. Di bagian atas file, di bawah variabel trackPosition , tambahkan baris berikut:

 private(set) var isMuted = false

Ini adalah variabel yang diperiksa oleh menu utama (dan apa pun yang akan memutar suara) untuk menentukan apakah suara diperbolehkan. Kami menginisialisasinya sebagai false , tetapi sekarang kami perlu memeriksa UserDefaults untuk melihat apa yang diinginkan pengguna. Ganti fungsi init() dengan yang berikut ini:

 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) }

Sekarang kita memiliki nilai default untuk isMuted , kita membutuhkan kemampuan untuk mengubahnya. Tambahkan kode berikut ke bagian bawah 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 }

Metode ini akan mengaktifkan variabel bisu kami, serta memperbarui UserDefaults . Jika nilai baru tidak dimatikan, pemutaran musik akan dimulai; jika nilai baru dimatikan, pemutaran tidak akan dimulai. Jika tidak, kami akan menghentikan pemutaran trek saat ini. Setelah ini, kita perlu mengedit pernyataan if di startPlaying() .

Temukan baris berikut:

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

Dan ganti dengan ini:

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

Sekarang, jika pembisuan dimatikan dan pemutar audio tidak disetel atau pemutar audio saat ini tidak lagi diputar, kami akan memutar trek berikutnya.

Dari sini, kita dapat kembali ke MenuScene.swift untuk menyelesaikan tombol mute kita. Ganti handleSoundbuttonClick() dengan kode berikut:

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

Ini mengaktifkan suara di SoundManager , memeriksa hasilnya dan kemudian mengatur tekstur dengan tepat untuk menunjukkan kepada pengguna apakah suara dibisukan atau tidak. Kami hampir selesai! Kita hanya perlu mengatur tekstur awal tombol saat peluncuran. Di sceneDidLoad() , temukan baris berikut:

 soundButton = SKSpriteNode(texture: soundButtonTexture)

Dan ganti dengan ini:

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

Contoh di atas menggunakan operator ternary untuk mengatur tekstur yang benar.

Sekarang setelah musik terhubung, kita dapat pindah ke CatSprite.swift untuk menonaktifkan kucing mengeong saat mematikan suara aktif. Dalam hitByRain() , kita dapat menambahkan pernyataan if berikut setelah menghapus tindakan berjalan:

 if SoundManager.sharedInstance.isMuted { return }

Pernyataan ini akan mengembalikan apakah pengguna telah membisukan aplikasi. Karena itu, kami akan mengabaikan sepenuhnya efek suara maxRainHits currentRainHits mengeong kami.

Setelah semua itu, sekarang saatnya untuk mencoba tombol mute kami. Jalankan aplikasi dan verifikasi apakah itu diputar dan mematikan suara dengan benar. Matikan suara, tutup aplikasi, dan buka kembali. Pastikan bahwa pengaturan bisu tetap ada. Perhatikan bahwa jika Anda hanya membisukan dan menjalankan kembali aplikasi dari Xcode, Anda mungkin tidak memiliki cukup waktu untuk menyimpan UserDefaults . Mainkan gamenya, dan pastikan kucing tidak pernah mengeong saat Anda dibisukan.

Menguji fungsionalitas tombol.

Keluar dari Game

Sekarang kita memiliki jenis tombol pertama untuk menu utama, kita bisa masuk ke beberapa bisnis rumit dengan menambahkan tombol berhenti ke adegan permainan kita. Beberapa interaksi menarik dapat muncul dengan gaya permainan kami; saat ini, payung akan bergerak ke mana pun pengguna menyentuh atau menggerakkan sentuhan mereka. Jelas, payung yang bergerak ke tombol berhenti ketika pengguna mencoba keluar dari permainan adalah pengalaman pengguna yang sangat buruk, jadi kami akan mencoba untuk menghentikan hal ini terjadi.

Tombol keluar yang kami terapkan akan meniru tombol mulai permainan yang kami tambahkan sebelumnya, dengan sebagian besar prosesnya tetap sama. Perubahannya ada pada cara kita menangani sentuhan. Dapatkan aset quit_button dan quit_button_pressed Anda ke dalam file Assets.xcassets , dan tambahkan kode berikut ke file HudNode.swift :

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

Ini akan menangani referensi quitButton kami, bersama dengan tekstur yang akan kami atur untuk status tombol. Untuk memastikan bahwa kita tidak secara tidak sengaja memperbarui payung saat mencoba berhenti, kita memerlukan variabel yang memberi tahu HUD (dan adegan permainan) bahwa kita sedang berinteraksi dengan tombol keluar dan bukan payung. Tambahkan kode berikut di bawah variabel boolean showingHighScore :

 private(set) var quitButtonPressed = false

Sekali lagi, ini adalah variabel yang hanya dapat diatur oleh HudNode tetapi dapat diperiksa oleh kelas lain. Sekarang variabel kita sudah diatur, kita bisa menambahkan tombol ke HUD. Tambahkan kode berikut ke fungsi 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)

Kode di atas akan mengatur tombol quit dengan tekstur status non-pressed kita. Kami juga mengatur posisi ke sudut kanan atas dan mengatur zPosition ke angka tinggi untuk memaksanya untuk selalu menggambar di atas. Jika Anda menjalankan game sekarang, itu akan muncul di GameScene , tetapi belum dapat diklik.

tombol keluar
Perhatikan tombol berhenti baru di HUD kami.

Sekarang tombol telah diposisikan, kita harus dapat berinteraksi dengannya. Saat ini, satu-satunya tempat kita berinteraksi di GameScene adalah saat kita berinteraksi dengan umbrellaSprite . Dalam contoh kita, HUD akan diprioritaskan di atas payung, sehingga pengguna tidak perlu memindahkan payung untuk keluar. Kita dapat membuat fungsi yang sama di HudNode.swift untuk meniru fungsi sentuh di GameScene.swift . Tambahkan kode berikut ke 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 }

Kode di atas sangat mirip dengan kode yang kita buat untuk MenuScene . Perbedaannya adalah hanya ada satu tombol untuk dilacak, jadi kami dapat menangani semuanya dalam metode sentuh ini. Juga, karena kita akan mengetahui lokasi sentuhan di GameScene , kita bisa memeriksa apakah tombol kita berisi titik sentuh.

Pindah ke GameScene.swift , dan ganti metodetouchBegan(_touch touchesBegan(_ touches with event:) touchesMoved(_ touches: with event:) dengan kode berikut:

 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) } }

Di sini, setiap metode menangani semuanya dengan cara yang hampir sama. Kami memberi tahu HUD bahwa pengguna telah berinteraksi dengan adegan. Kemudian, kami memeriksa apakah tombol berhenti saat ini menangkap sentuhan. Jika tidak, maka kita pindahkan payungnya. Kami juga telah menambahkan fungsi touchesEnded(_ touches: with event:) untuk menangani akhir klik untuk tombol keluar, tetapi kami masih tidak menggunakannya untuk umbrellaSprite .

Mengklik tombol keluar tidak akan memindahkan payung, tetapi mengklik di tempat lain akan memindahkannya.

Sekarang setelah kita memiliki tombol, kita perlu cara agar tombol itu memengaruhi GameScene . Tambahkan baris berikut ke bagian atas HudeNode.swift :

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

Ini adalah penutupan umum yang tidak memiliki input dan output. Kami akan mengatur ini dengan kode di file GameScene.swift dan memanggilnya ketika kami mengklik tombol di HudNode.swift . Kemudian, kita dapat mengganti TODO dalam kode yang kita buat sebelumnya di fungsi touchEndedAtPoint(point:) dengan ini:

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

Sekarang, jika kita mengatur penutupan quitButtonAction , itu akan dipanggil dari titik ini.

Untuk mengatur penutupan quitButtonAction , kita perlu pindah ke GameScene.swift . Di sceneDidLoad() , kita dapat mengganti pengaturan HUD kita dengan kode berikut:

 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)

Jalankan aplikasi, tekan play, lalu tekan quit. Jika Anda kembali ke menu utama, maka tombol keluar Anda berfungsi sebagaimana mestinya. Dalam penutupan yang kami buat, kami menginisialisasi transisi ke MenuScene . Dan kami mengatur penutupan ini ke simpul HUD untuk dijalankan ketika tombol berhenti diklik. Baris penting lainnya di sini adalah ketika kita menyetel quitButtonAction ke nil . Alasan untuk ini adalah bahwa siklus retensi sedang terjadi. Adegan memegang referensi ke HUD di mana HUD memegang referensi ke adegan. Karena ada referensi ke kedua objek, keduanya tidak akan dibuang ketika tiba saatnya untuk pengumpulan sampah. Dalam hal ini, setiap kali kita masuk dan keluar dari GameScene , instance lain darinya akan dibuat dan tidak pernah dirilis. Ini buruk untuk kinerja, dan aplikasi pada akhirnya akan kehabisan memori. Ada beberapa cara untuk menghindari ini, tetapi dalam kasus kami, kami hanya dapat menghapus referensi ke GameScene dari HUD, dan adegan dan HUD akan dihentikan setelah kami kembali ke MenuScene . Krakendev memiliki penjelasan lebih dalam tentang tipe referensi dan cara menghindari siklus ini.

Sekarang, pindah ke GameViewController.swift , dan hapus atau komentari tiga baris kode berikut:

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

Dengan keluarnya data debug, gim ini terlihat sangat bagus! Selamat: Saat ini kami sedang dalam versi beta! Lihat kode terakhir mulai hari ini di GitHub.

Pikiran Akhir

Ini adalah pelajaran terakhir dari tutorial tiga bagian, dan jika Anda berhasil sejauh ini, Anda baru saja melakukan banyak pekerjaan pada permainan Anda. Dalam tutorial ini, Anda beralih dari adegan yang sama sekali tidak ada apa-apanya, ke game yang sudah selesai. Selamat! Dalam pelajaran satu, kami menambahkan sprite lantai, rintik hujan, latar belakang, dan payung. Kami juga bermain-main dengan fisika dan memastikan tetesan air hujan kami tidak menumpuk. Kami memulai dengan deteksi tabrakan dan bekerja pada pemusnahan node sehingga kami tidak akan kehabisan memori. Kami juga menambahkan beberapa interaksi pengguna dengan membiarkan payung bergerak ke arah tempat pengguna menyentuh layar.

Dalam pelajaran dua, kami menambahkan kucing dan makanan, bersama dengan metode pemijahan khusus untuk masing-masing kucing. Kami memperbarui deteksi tabrakan kami untuk memungkinkan sprite kucing dan makanan. Kami juga mengerjakan pergerakan kucing. Kucing itu mendapatkan suatu tujuan: Makanlah setiap makanan yang tersedia. Kami menambahkan animasi sederhana untuk kucing dan menambahkan interaksi khusus antara kucing dan hujan. Terakhir, kami menambahkan efek suara dan musik untuk membuatnya terasa seperti permainan yang lengkap.

Dalam pelajaran terakhir ini, kami membuat tampilan awal untuk menahan label skor kami, serta tombol berhenti kami. Kami menangani tindakan di seluruh node dan memungkinkan pengguna untuk berhenti dengan panggilan balik dari node HUD. Kami juga menambahkan adegan lain yang dapat digunakan dan dibuka kembali oleh pengguna setelah mengklik tombol keluar. Kami menangani proses untuk memulai permainan dan untuk mengontrol suara dalam permainan.

Ke mana harus pergi dari sini?

Kami menghabiskan banyak waktu untuk sampai sejauh ini, tetapi masih banyak pekerjaan yang bisa dilakukan untuk game ini. RainCat masih melanjutkan pengembangan, dan tersedia di App Store. Di bawah ini adalah daftar keinginan dan kebutuhan yang perlu ditambahkan. Beberapa item telah ditambahkan, sementara yang lain masih tertunda:

  • Tambahkan ikon dan layar splash.
  • Selesaikan menu utama (disederhanakan untuk tutorial).
  • Perbaiki bug, termasuk tetesan hujan nakal dan banyak makanan yang bertelur.
  • Refactor dan optimalkan kode.
  • Ubah palet warna permainan berdasarkan skor.
  • Perbarui kesulitan berdasarkan skor.
  • Animasikan kucing saat makanan berada tepat di atasnya.
  • Mengintegrasikan Game Center.
  • Berikan kredit (termasuk kredit yang tepat untuk trek musik).

Pantau terus GitHub karena perubahan ini akan dilakukan di masa mendatang. Jika Anda memiliki pertanyaan tentang kode ini, silakan hubungi kami di [email protected] dan kami dapat mendiskusikannya. Jika topik tertentu cukup mendapat perhatian, mungkin kita bisa menulis artikel lain yang membahas topik tersebut.

Terima kasih!

Saya ingin berterima kasih kepada semua orang yang membantu dalam proses pembuatan game dan mengembangkan artikel yang menyertainya.

  • Cathryn Rowe Untuk seni awal, desain dan pengeditan, dan untuk menerbitkan artikel di Garasi kami.
  • Morgan Wheaton Untuk desain menu akhir dan palet warna (yang akan terlihat mengagumkan setelah saya benar-benar menerapkan fitur ini — pantau terus).
  • Nikki Clark Untuk tajuk dan pemisah yang mengagumkan dalam artikel dan untuk bantuan dalam mengedit artikel.
  • Laura Levisay Untuk semua GIF yang luar biasa di artikel dan untuk mengirimi saya GIF kucing lucu untuk dukungan moral.
  • Tom Hudson Untuk bantuannya dalam menyunting artikel dan tanpa siapa seri ini tidak akan dibuat sama sekali.
  • Lani DeGuire Untuk bantuan dalam mengedit artikel, yang merupakan banyak pekerjaan.
  • Jeff Moon Untuk bantuan mengedit pelajaran tiga dan ping-pong. Banyak pingpong.
  • Tom Nelson Untuk membantu memastikan bahwa tutorial berfungsi sebagaimana mestinya.

Serius, butuh banyak orang untuk menyiapkan semuanya untuk artikel ini dan merilisnya ke toko.

Terima kasih juga untuk semua orang yang membaca kalimat ini.