ساخت بازی iOS بدون نیاز به تجربه کدنویسی — از صفر تا صد
در این راهنما با مراحل آمادهسازی رایانه برای ساخت اپلیکیشنهای iOS و ایجاد یک بازی ساده برای این پلتفرم از صفر آشنا میشوید. همه کدهای مورد نیاز بازی نیز ارائه خواهند شد. بدین ترتیب جهت ساخت بازی برای آیفون نیاز به تجربه کدنویسی قبلی ندارید. این راهنما برای سوئیفت 4.0 و XCode 9 نوشته شده است و ممکن است در مورد نسخههای بعدی نیاز به تغییراتی داشته باشد.
پس از به پایان بردن این راهنما، قادر خواهید بود یک اپلیکیشن iOS را که خودتان ساختهاید، روی گوشی یا شبیهساز اجرا کنید. بدین ترتیب با مبانی طراحی یک بازی از صفر آشنا میشوید. شیوه ذخیرهسازی دادهها روی دستگاه پس از بستن اپلیکیشن را میدانید. با روش رندر sprit-ها روی صفحه آشنا میشوید. همچنین درکی ابتدایی از کاربرد موتور بازی SpriteKit به دست میآورید. ضمناً با مراحل ساخت یک بازی Snake و شیوه طراحی بازی بنا به میل خودتان آشنا خواهید شد.
نسخه اندکی متفاوت از این بازی را میتوانید از این آدرس روی اپاستور (+) به صورت کاملاً رایگان و بدون تبلیغات دانلود کنید.
نکته: در سراسر این راهنما از روش کپی و چسباندن کد استفاده شده است که رویهای نادرست محسوب میشود و به طور کلی باید از آن اجتناب کرد، اما در این راهنما برای ساخت سریعتر بازی از این روش به صورت یک استثنا استفاده شده است.
محصول نهایی
در تصویر زیر چیزی که قرار است در انتهای این راهنما ساخته و روی گوشی نصب کنیم را میبینید:
شروع
برای پیگیری این راهنما باید یک اکانت توسعهدهنده اپل ایجاد کرده و Xcode را دانلود کنید. Xcode برنامهای است که برای ساخت اپلیکیشنهای iOS استفاده میشود. متأسفانه Xcode تنها برای سیستمهای Mac ارائه شده است، اگر سیستم ویندوز/ لینوکس دارید، میتوانید به این وبسایت (+) مراجعه کنید و Xcode را راهاندازی کنید.
این گامهای بعدی از طریق ثبت نام در اکانت رایگان توسعهدهنده اپل و نصب Xcode در دسترس شما قرار میگیرد. اگر یک اکانت دولوپر و Xcode را هماینک دارید، میتوانید از این بخش عبور کنید. در غیر این صورت ابتدا به وبسایت developer.apple.com (+) بروید و روی دکمه member center کلیک کنید. سپس با ID اپل خود وارد شوید. به صفحه Apple Developer Agreement بروید و توافقنامه را بپذیرید تا یک اکانت رایگان دولوپر به دست آورید. برای آپلود پروژهها در اپاستور باید مبلغ سالانه 100 دلار پرداخت کنید (ساخت این اکانت البته برای کاربران ایرانی با محدودیتهایی مواجه است).
اکنون یک اکانت دولوپر دارید و باید Xcode را نصب کنید. Xcode را میتوانید از این آدرس (+) دانلود کنید. پس از این که Xcode را اجرا کنید، به منوی + <- Xcode -> Preferences -> Accounts بروید و ID اپل خود را انتخاب کنید. با ID اپل خود که با آن اکانت دولوپر ثبت کردهاید، وارد شوید. اینک میتوانید اپلیکیشنهای خود را در شبیهساز آیفون اجرا کرده یا روی گوشی شخصی خود به اجرای آنها بپردازید.
آغاز پروژه
اکنون که اکانت دولوپر اپل را ثبت و Xcode را نیز نصب کردهاید، میتوانید شروع به توسعه نخستین بازی موبایلی خود بکنید. Xcode را اجرا کرده و روی Create a new Xcode project کلیک کنید:
در ادامه روی قالب Game کلیک کنید.
در این مرحله نام Snake یا هر نام دیگری که دوست دارید را برای بازی وارد کنید. یک نام سازمان انتخاب کنید. برای نمونه اگر یک وبسایت دارید، میتوانید آن را به صورت برعکس (مانند org.faradars) وارد نمایید یا این که از نام خود به عنوان شناسه استفاده کنید. مطمئن شوید که زبان روی Swift تنظیم شده و فناوری گیم روی SpriteKit قرار دارد. در صورتی که 3 کادر بعدی در حالت انتخاب هستند، تیک آنها را بردارید.
روی Actions.sks راست-کلیک کرده و آن را به سطل زباله انتقال دهید. به فایل GameScene.sks بروید و روی متن Hello World کلیک کرده و آن را حذف کنید. به فایل GameScene.swift بروید و همه کدهای از پیش تولیدشده را پاک کنید، به طوری که پروژه مانند تصویر زیر بشود:
با رفتن به منوی File -> New File و کلیک کردن روی Swift File یا با راست-کلیک کردن روی پوشه پروژه (Snake) و انتخاب فایل جدید، یک فایل سوئیفت جدید ایجاد کنید. در صورتی که نوار فیلتر در حال حاضر روی Swift قرار ندارد، آیکون فایل سوئیفت را چنان که در تصویر زیر مشخص شده است، بیابید. نام GameManager را وارد کرده و مطمئن شوید که پروژهتان یعنی Snake زیر هدفها انتخاب شده است. در ادامه روی دکمه Create کلیک کنید تا فایل سوئیفت جدیدی ایجاد شود.
ساخت منوی بازی
پیش از آغاز کدنویسی باید مطمئن شویم که پروژه پس از تغییراتی که در بخش قبلی ایجاد کردیم، همچنان کامپایل میشود. یک دستگاه را از لیست شبیهسازها انتخاب کنید، روی دکمه با عنوان iPhone 6 کلیک کنید. احتمالاً عنوان آن به صورت Generic iOS device باشد. اگر میخواهید اپلیکیشن را روی یک دستگاه فیزیکی تست کنید، آیفون خود را به رایانه متصل کنید و چند لحظه به Xcode فرصت بدهید و سپس روی دستگاهی که ظاهر میشود کلیک نمایید. سپس روی دکمه مثلثی اجرا کلیک کنید. اگر یک دستگاه شبیهساز انتخاب کنید، صفحهای مانند زیر که باز میشود:
اگر روی صفحه عبارت Hello World را میبینید، باید با مراجعه به فایل GameScene.sks و کلیک کردن روی لیبل، آن عنوان را پاک کنید.
در نهایت آماده ساخت بازی خود شدهایم. هنگامی که میخواهید یک بازی بسازید، بهتر است ابتدا لیآوت صفحهها را از قبل طراحی کرده باشید. در این بازی با یک صفحه منوی ساده آغاز میکنیم که عنوان و لوگوی بازی را نمایش میدهد. یک دکمه play صفحه بازی را باز میکند و دو برچسب برای امتیاز کنونی و بهترین امتیاز وجود دارند. زمانی که در بازی میمیرید، صفحه بازی یک گزینه برای بازی مجدد نمایش میدهد.
برای عملیاتی ساختن بازی، ابتدا باید یک منو بسازیم تا بازی از طریق آن آغاز شود. کار خود را با نوشتن کد برای مقداردهی اولیه یک منو از طریق افزودن عنوان بازی آغاز میکنیم، همچنین یک لیبل با عنوان best score و یک دکمه play وجود خواهد داشت. فایل GameScene.swift را باز کرده و همه کد زیر را در آن کپی کنید به طوری که فایل شما با تصویر (1) مطابقت داشته باشد.
1//1
2var gameLogo: SKLabelNode!
3var bestScore: SKLabelNode!
4var playButton: SKShapeNode!
5//2
6initializeMenu()
7//3
8private func initializeMenu() {
9 //Create game title
10 gameLogo = SKLabelNode(fontNamed: "ArialRoundedMTBold")
11 gameLogo.zPosition = 1
12 gameLogo.position = CGPoint(x: 0, y: (frame.size.height / 2) - 200)
13 gameLogo.fontSize = 60
14 gameLogo.text = "SNAKE"
15 gameLogo.fontColor = SKColor.red
16 self.addChild(gameLogo)
17 //Create best score label
18 bestScore = SKLabelNode(fontNamed: "ArialRoundedMTBold")
19 bestScore.zPosition = 1
20 bestScore.position = CGPoint(x: 0, y: gameLogo.position.y - 50)
21 bestScore.fontSize = 40
22 bestScore.text = "Best Score: 0"
23 bestScore.fontColor = SKColor.white
24 self.addChild(bestScore)
25 //Create play button
26 playButton = SKShapeNode()
27 playButton.name = "play_button"
28 playButton.zPosition = 1
29 playButton.position = CGPoint(x: 0, y: (frame.size.height / -2) + 200)
30 playButton.fillColor = SKColor.cyan
31 let topCorner = CGPoint(x: -50, y: 50)
32 let bottomCorner = CGPoint(x: -50, y: -50)
33 let middle = CGPoint(x: 50, y: 0)
34 let path = CGMutablePath()
35 path.addLine(to: topCorner)
36 path.addLines(between: [topCorner, bottomCorner, middle])
37 playButton.path = path
38 self.addChild(playButton)
39}
کد خود را کامپایل کنید و بررسی نمایید که دستگاه، تصویر فوق را نمایش میدهد یا نه. بخشهای مختلف کد را در ادامه توضیح میدهیم. گرچه ممکن است به نظرتان برسد که کد حجم بالایی دارد، اما زمانی که آن را تجزیه کنیم، درکش آسانتر خواهد بود.
- در بخش 1، متغیرهایی برای لوگوها و دکمهها ایجاد میکنیم. علامت (!) پس از نام متغیر به این معنی است که باید متغیرها را مقداردهی کنیم و آنها نمیتوانند خالی یا nil باشند.
- در بخش 2، تابع ()initializeMenu را هنگامی که نمای بازی (Game View) بارگذاری شد، فراخوانی میکنیم. didMove(to: view: SKView) تابعی است که وقتی GameScene بارگذاری شد، فراخوانی میشود.
- در بخش 3، تابع ()intializeMenu را میبینید که برای ایجاد اشیای منو نوشتهایم.
- در بخشهای 4، 5 و 6، اشیایی ایجاد میکنیم و با فراخوانی self.addChild()، اقدام به اضافه کردن GameScene میکنیم.
- در بخش 7، از SKShapeNodes برای این پروژه استفاده میکنیم، چون ساده است. این روش جایگزین برای ایجاد گرافیکها در یک ویرایشگر تصویر است. این خط کد یک مسیر در شکل یک مثلث ایجاد میکند. توجه کنید که اگر بخواهید اپلیکیشن خود را ساخته و منتشر کنید، باید از SKSpriteNodes برای بارگذاری تصویری که ایجاد میکنید، بهره بگیرید، چون ShapeNodes ممکن است در زمانی که در حجم بالایی استفاده میشود، مشکلات عملکردی پدید آورد. دلیل این مسئله آن است که این تصاویر در هر فریم به صورت دینامیک از نو ترسیم میشوند.
- در بخش 8، مسیر مثلثی که برای Sprite به نام playButton ایجاد کردیم را تنظیم کرده و به GameScene اضافه میکنیم.
اجرای بازی
اکنون که یک منوی ساده را تنظیم کردهایم، میتوانیم عملکرد آن را بررسی کنیم. ابتدا به فایل GameManager.swift بروید. همه کدهایی که در آن قرار دارد را با کد زیر عوض کنید. به این ترتیب باید با تصویر (2) زیر مطابقت پیدا کند.
1import SpriteKit
2 class GameManager {
3}
کد زیر را در فایل GameScene.swift کپی کنید، به طوری که با تصویر (3) زیر مطابقت یابد:
1//1
2var game: GameManager!
3//2
4game = GameManager()
5//3
6override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
7 for touch in touches {
8 let location = touch.location(in: self)
9 let touchedNode = self.nodes(at: location)
10 for node in touchedNode {
11 if node.name == "play_button" {
12 startGame()
13 }
14 }
15 }
16}
17//4
18private func startGame() {
19 print("start game")
20}
- در بخش 1 کد فوق، یک شیء GameManager مقداردهی میکنیم. در ادامه در این مورد بیشتر توضیح خواهیم داد. این شیء دادههای امتیاز را نگهداری کرده و حرکت بازیکن را مدیریت میکند.
- در بخش 2، متغیر بازی را روی شیء جدید ()GameManager تنظیم میکنیم.
- در بخش 3، هر بار که کاربر صفحه را لمس میکند، موتور بازی از سوی تابع مربوطه فراخوانی میشود. به خاطر بیاورید که دکمه play که قبلاً ایجاد کردیم، دارای نام play_button بود. با استفاده از این نام میتوانیم بررسی کنیم، آیا کاربر یک SpriteNode با نام SpriteNode را لمس کرده یا نه. زمانی که این اتفاق بیفتد، تابع ()startGame را در بخش چهارم فراخوانی میکنیم.
- در بخش 4، تابع، بازی را آغاز میکند.
با اجرای اپلیکیشن و کلیک روی دکمه مثلثی اجرا، اطمینان حاصل کنید که کد به درستی کار میکند. اگر لمسهای شما به درستی اندازهگیری شوند، در کنسول باید عبارت start game چنان که در تصویر (4) زیر مشخص شده نمایش یابد:
اگر کنسول نمایش پیدا نمیکند، به نوار فوقانی بروید و روی Help کلیک کنید. در نوار جستجو عبارت console را وارد کنید و سپس روی Debug Area > Activate Console کلیک کنید. اکنون یک سیستم منوی عملیاتی و یک دکمه play داریم و این همان جایی است که هیجان اصلی آغاز میشود.
بارگذاری نمای بازی (Game View)
اکنون یک دکمه play داریم که میتواند یک تابع را تحریک کند. برای نمایش نمای بازی ابتدا باید دکمههای منو را پنهان کنیم. خط کد زیر را برای پنهان کردن دکمههای منو به همراه انیمیشن اضافه کنید.
اینک کد شما باید مانند تصویر (5) باشد.
1//start the game
2private func startGame() {
3 print("start game")
4 //1
5 gameLogo.run(SKAction.move(by: CGVector(dx: -50, dy: 600), duration: 0.5)) {
6 self.gameLogo.isHidden = true
7 }
8 //2
9 playButton.run(SKAction.scale(to: 0, duration: 0.3)) {
10 self.playButton.isHidden = true
11 }
12 //3
13 let bottomCorner = CGPoint(x: 0, y: (frame.size.height / -2) + 20)
14 bestScore.run(SKAction.move(to: bottomCorner, duration: 0.4))
15}
- در بخش 1، gameLogo از صفحه خارج شده و سپس آن را پنهان میکنیم. براکتهای پس از SKAction زمانی که این عمل پایان یابد، اجرا میشوند. برای نمونه اگر SKAction به مدت 10 ثانیه اجرا شود، کد درون براکت پس از 10 ثانیه اجرا خواهد شد. به مثال زیر توجه کنید:
1exampleNode.run(SKAction.move(by: CGVector(dx: 0, dy: 0), duration: 10) { 2 print("I am reached after 10 seconds") 3}
- در بخش 2، playButton روی 0 مقیاسبندی میشود. این عمل موجب کوچک شدن دکمه شده و سپس آن را از نما پنهان میسازد.
- در بخش 3، برچسب bestScore به انتهای صفحه جابجا میشود.
اکنون منوی شما در زمان کلیک کردن روی دکمه باید مانند تصویر 6 رفتار کند.
1//1
2var currentScore: SKLabelNode!
3var playerPositions: [(Int, Int)] = []
4var gameBG: SKShapeNode!
5var gameArray: [(node: SKShapeNode, x: Int, y: Int)] = []
6//2
7initializeGameView()
8//3
9private func initializeGameView() {
10 //4
11 currentScore = SKLabelNode(fontNamed: "ArialRoundedMTBold")
12 currentScore.zPosition = 1
13 currentScore.position = CGPoint(x: 0, y: (frame.size.height / -2) + 60)
14 currentScore.fontSize = 40
15 currentScore.isHidden = true
16 currentScore.text = "Score: 0"
17 currentScore.fontColor = SKColor.white
18 self.addChild(currentScore)
19 //5
20 let width = frame.size.width - 200
21 let height = frame.size.height - 300
22 let rect = CGRect(x: -width / 2, y: -height / 2, width: width, height: height)
23 gameBG = SKShapeNode(rect: rect, cornerRadius: 0.02)
24 gameBG.fillColor = SKColor.darkGray
25 gameBG.zPosition = 2
26 gameBG.isHidden = true
27 self.addChild(gameBG)
28 //6
29 createGameBoard(width: width, height: height)
30}
در این مرحله قصد داریم شروع به طراحی بخش واقعی مار در بازی بکنیم. کار خود را با افزودن این خطوط به کد شروع میکنیم به صورتی که با تصویر 7 مطابقت داشته باشد:
- در بخش 1، متغیرهای جدیدی تعریف کردهایم، یک برچسب (Label) ایجاد میکنیم که امتیاز کنونی را نمایش میدهد، یک آرایه از همه موقعیتهایی که مار (بازیکن) هماینک در آنها قرار دارند تعریف میکنیم و پسزمینه برای نمای بازی و یک آرایه برای ردگیری همه سلولهای نمای بازی اعلان کردهایم.
- در بخش 2، تابع ()initializeGameView را فرامیخوانیم.
- در بخش 3، نمای بازی را مقداردهی میکنیم.
- در بخش 4، برچسب امتیاز کنونی را به صفحه اضافه میکنیم، این برچسب تا زمانی که منو را ترک نکنید پنهان میماند.
- در بخش 5، یک ShapeNode ایجاد میکنیم تا ناحیه قابل بازی را نمایش دهیم. این همان جایی است که مار به اطراف حرکت میکند.
- در بخش 6، صفحه بازی را ایجاد میکنیم. این تابع تعداد زیادی سلولهای مربعی را مقداردهی کرده و آنها را به صفحه بازی اضافه میکند.
سپس باید یک آرایه از سلولها ایجاد کنیم که از آن برای رندر مار و نقاط روی صفحه استفاده میکنیم. تابع createGameBoard را بر مبنای کد زیر ایجاد میکنیم، به طوری که با تصویر (7) مطابقت داشته باشد.
1//create a game board, initialize array of cells
2private func createGameBoard(width: Int, height: Int) {
3 let cellWidth: CGFloat = 27.5
4 let numRows = 40
5 let numCols = 20
6 var x = CGFloat(width / -2) + (cellWidth / 2)
7 var y = CGFloat(height / 2) - (cellWidth / 2)
8 //loop through rows and columns, create cells
9 for i in 0...numRows - 1 {
10 for j in 0...numCols - 1 {
11 let cellNode = SKShapeNode(rectOf: CGSize(width: cellWidth, height: cellWidth))
12 cellNode.strokeColor = SKColor.black
13 cellNode.zPosition = 2
14 cellNode.position = CGPoint(x: x, y: y)
15 //add to array of cells -- then add to game board
16 gameArray.append((node: cellNode, x: i, y: j))
17 gameBG.addChild(cellNode)
18 //iterate x
19 x += cellWidth
20 }
21 //reset x, iterate y
22 x = CGFloat(width / -2) + (cellWidth / 2)
23 y -= cellWidth
24 }
25}
اینک کد شما باید با کد فوق مطابقت داشته باشد. در زمان اجرای بازی به ظاهر چیزی تغییر نمییابد. اگر بخواهید صفحه بازی را مانند شبیهساز فوق ببینید، کد زیر را به تابع آغاز بازی اضافه کنید تا با تصویر (9) مطابقت داشته باشد:
1bestScore.run(SKAction.move(to: bottomCorner, duration: 0.4)) {
2 self.gameBG.setScale(0)
3self.currentScore.setScale(0)
4self.gameBG.isHidden = false
5self.currentScore.isHidden = false
6self.gameBG.run(SKAction.scale(to: 1, duration: 0.4))
7self.currentScore.run(SKAction.scale(to: 1, duration: 0.4))
8}
پیش از ادامه باید کمی در مورد متد createGameBoard صحبت کنیم. این متد روی 40 ردیف و 20 ستون میچرخد و برای هر موقعیتِ سطر/ستون یک کادر مربعی یا cellNode ایجاد کرده و به صفحه اضافه میشود. همچنین این cellNode را به آرایه gameArray اضافه میکنیم به طوری که بتوانیم به سادگی یک سطر و ستون را به سلول متناظر ربط دهیم.
ایجاد وهلهای از بازی
اکنون یک دکمه play عملیاتی، یک کادر پر از کادرهای کوچک و تعدادی برچسب داریم.
اما سؤال این است که چطور میتوانیم آن را به یک بازی جالب تبدیل کنیم؟ ابتدا به یک شیء نیاز داریم تا موقعیت یک مار را روی صفحه زمانی که به اطراف حرکت میکند ردگیری کنیم. به این منظور کلاس GameManager.swift را باز کنید و متدهای زیر را ایجاد نمایید. همچنین کلاس شماره 1 را به تابع didMove(to view: SKView) در فایل GameScene.swift اضافه کنید، به ترتیبی که با تصویر 10 مطابقت پیدا کند.
1//1 -- GameScene.swift
2game = GameManager(scene: self)
3//2 -- GameManager.swift
4class GameManager {
5
6 var scene: GameScene!
7 init(scene: GameScene) {
8 self.scene = scene
9 }
10}
با ایجاد این تغییرات اعلام میکنیم که GameManager در زمان وهلهسازی، باید شامل ارجاعی به کلاس GameScene باشد. به این ترتیب کلاس GameManager میتواند با فراخوانی scene.method_name با GameScene ارتباط داشته باشد. برای نمونه متد ()scene.startGame میتواند تابع آغاز بازی را از درون کنترل کلاس GameManager اجرا نماید.
اکنون آماده بارگذاری بازیکن در GameView هستیم. ابتدا قطعه کد زیر را به فایل GameScene.swift در متد ()startGame درون براکتهای { } ()bestScore.run اضافه میکنیم. این متد زمانی که برچسب bestScore اقدام به تکمیل SKAction کند، تابع initGame را فرامیخواند:
1//new code
2self.game.initGame()
اینک به فایل GameManager.swift میرویم و متدهایی که در ادامه آمده را زیر متد init(scene: GameScene) اضافه میکنیم، به طوری که با تصویر 12 تطبیق پیدا کند.
1//1
2func initGame() {
3 //starting player position
4 scene.playerPositions.append((10, 10))
5 scene.playerPositions.append((10, 11))
6 scene.playerPositions.append((10, 12))
7 renderChange()
8}
9//2
10func renderChange() {
11 for (node, x, y) in scene.gameArray {
12 if contains(a: scene.playerPositions, v: (x,y)) {
13 node.fillColor = SKColor.cyan
14 } else {
15 node.fillColor = SKColor.clear
16 }
17 }
18}
19//3
20func contains(a:[(Int, Int)], v:(Int,Int)) -> Bool {
21 let (c1, c2) = v
22 for (v1, v2) in a { if v1 == c1 && v2 == c2 { return true } }
23 return false
24}
- در بخش 1، تابع ()initGame را داریم، این تابع 3 مختصه را به آرایه playerPositions فایل GameScene اضافه میکند.
- در بخش 2، متد ()renderChange را داریم. این متد را هر بار که مار (بازیکن) را حرکت میدهیم، فراخوانی خواهیم کرد. این متد همه مربعهای سیاه را به صورت پاک رندر میکند و همه مربعهایی که بازیکن در آنها قرار دارد به رنگ فیروزهای رندر میشوند.
- در بخش 3، یک تابع ساده داریم که بررسی میکند آیا یک چندتایی در یک آرایه از چندتاییها وجود دارد یا نه. «چندتایی» (tuple) ساختمان دادهای در سوئیفت است که میتواند شامل ترکیبی از چندتاییها به شکل (Int, CGFloat, Int, String) و غیره باشد. این تابع بررسی میکند آیا آرایه playerPositions شامل مختصات واردشده از آرایه سلولهای GameScene است یا نه. این وضعیت لزوماً بهینهترین روش برای انجام کارها نیست، چون باید تکتک سلولهای منفرد را در هر بهروزرسانی بررسی کنیم. اگر میخواهید خودتان را به چالش بکشید، تلاش کنید کد را طوری بهروزرسانی کنید که تنها مربعهایی از آرایه playerPositions تغییر پیدا کنند.
جابجایی بازیکن
ما اینک بازیکن خود را روی صفحه رندر کردهایم و امکان رندر هر تعداد موقعیت را داریم. اگر مختصات بیشتری به آرایه playerPositions اضافه کنید، در این صورت مربعهای بیشتری به رنگ فیروزهای درمیآیند. در طول بازی میخواهیم به طور مداوم مار را در جهتی که بازیکن روی صفحه لمس میکند حرکت دهیم و بدین ترتیب کاربر بتواند جهت حرکت را تغییر دهد. در تصویر زیر شبکهای را میبینید که مختصات سیستم grid را نمایش میدهد و بدین ترتیب میتوانید به سادگی طرز کار سیستم مختصات را در پسزمینه ببینید (شکل 13).
چنان که از روی برچسبهای کوچک میبینید، گوشه چپ-بالا برابر با 0,0 و گوشه راست-پایین برابر با 39,19 است. این بدان معنی است که اگر بخواهیم بازیکن خود را به سمت چپ، راست، بالا و پایین جابجا کنیم، این کار را با بهکارگیری جبر مقدماتی زیر انجام میدهیم (شکل 14).
چنان که میبینید جهتهای چپ/راست با جهتهای صفحه مختصاتی برابرند، چپ، منفی و راست مثبت است. با این حال برای حرکت به سمت بالا در صفحه مختصات باید y را کاهش دهیم و برای حرکت به سمت پایین باید y را افزایش دهیم. دلیل این امر آن است که حلقه for در تابع createGameBoard در مقادیر بالا آغاز به کارکرده و به سمت پایین ادامه مییابد.
اکنون که جهت صفحه را درک کردیم، میتوانیم متدی را که بازیکن را جابجا میکند، پیادهسازی کنیم. اگر فایل GameScene.swift را باز کنید، متوجه یک متد کارآمد به نام update(_ currentTime: TimeInterval) در بخش فوقانی میشوید. در حلقه رندرینگ، تابع update هر ثانیه یک بار فراخوانی میشود. این بدان معنی است که اگر اپلیکیشن شما با نرخ 60 فریم بر ثانیه اجرا شود، تابع در هر ثانیه 60 بار فراخوانی خواهد شد. در صورتی که بازی با نرخ 40 فریم بر ثانیه رندر شود، این تابع در ثانیه 40 بار فراخوانی میشود. درون تابع update خط کد زیر را اضافه کنید تا با شکل (15) مطابقت یابد:
1//1
2game.update(time: currentTime)
پس از این که کد را اضافه کردید، یک خطای قرمز ظاهر میشود. برای حل این مشکل به فایل GameManager.swift بروید و این خطوط را اضافه کنید، به طوری که فایل شما با تصویر (16) مطابقت یابد:
1//1
2var nextTime: Double?
3var timeExtension: Double = 1
4//2
5func update(time: Double) {
6 if nextTime == nil {
7 nextTime = time + timeExtension
8 } else {
9 if time >= nextTime! {
10 nextTime = time + timeExtension
11 print(time)
12 }
13 }
14}
به محض اجرای اپلیکیشن، کنسول در هر ثانیه یک زمان جدید را نمایش میدهد. در بخش بعدی در مورد کارهایی که در این کد اجرا میشوند توضیح دادهایم.
- در بخش 1، دو متغیر جدید مقداردهی میشوند، nextTime بازه nextTime است که یک گزاره را در کنسول پرینت خواهیم کرد نمایش میدهد. timeExtension مدت زمانی که بین هر پرینت طول میکشد را تعیین میکند (1 ثانیه).
- در بخش 2، تابع update به میزان 60 بار در ثانیه فراخوانی میشود. ما تنها میخواهیم موقعیت بازیکن را یکبار در ثانیه فراخوانی کنیم و از این رو بازی چندان سریع نیست. برای اجرای این کار باید بررسی کنیم آیا nextTime تعیین شده است یا نه. چنان که در بخش قبل دیدیم، nextTime یک مقدار optional دارد. علامت (?) پس از Double مشخص برای کامپایلر سوئیفت میکند که میخواهیم nextTime یک مقدار از نوع double باشد و این که «میتواند» به صورت nil تنظیم شود. هنگامی که تابع update فراخوانی میشود، ابتدا بررسی میکنیم آیا nextTime تعیین شده یا نه. اگر تعیین نشده باشد، مقدار آن را روی زمان جاری + timeExtension یعنی 1 ثانیه تنظیم میکنیم. زمانی که زمان جاری از nextTime تجاوز کند، مقدار nextTime را 1 واحد افزایش میدهیم. این تابع اینک به شکل یک تابع بهروزرسانی نامنظم درمیآید (بین 30 تا 60 بار در ثانیه) و تنها یک بار در ثانیه خروجی تولید میکند.
اکنون تابعی داریم که یک بار در ثانیه اجرا میشود. اگر میخواهید سرعت بازی را افزایش دهید، کافی است timeExtension را روی مقداری بالاتر از 0 تنظیم کنید. اگر میخواهید کُندتر شود، مقدار timeExtension را افزایش دهید. توجه کنید که مقدار 1 به معنی 1 ثانیه برای timeExtension است.
اینک میخواهیم بازیکن به اطراف صفحه حرکت کند و کدی را اضافه میکنیم که با تصویر 17 مطابقت دارد. ضمناً خط print(time) را از تابع update که در فایل GameManager.swift ایجاد کردیم، حذف میکنیم. در غیر این صورت کنسول پر از گزارههای time متوالی میشود که صرفاً برای بررسی کد در مرحله دیباگ مفید هستند.
1//1
2var playerDirection: Int = 1
3//2
4updatePlayerPosition()
5//3
6private func updatePlayerPosition() {
7 //4
8 var xChange = -1
9 var yChange = 0
10 //5
11 switch playerDirection {
12 case 1:
13 //left
14 xChange = -1
15 yChange = 0
16 break
17 case 2:
18 //up
19 xChange = 0
20 yChange = -1
21 break
22 case 3:
23 //right
24 xChange = 1
25 yChange = 0
26 break
27 case 4:
28 //down
29 xChange = 0
30 yChange = 1
31 break
32 default:
33 break
34 }
35 //6
36 if scene.playerPositions.count > 0 {
37 var start = scene.playerPositions.count - 1
38 while start > 0 {
39 scene.playerPositions[start] = scene.playerPositions[start - 1]
40 start -= 1
41 }
42 scene.playerPositions[0] = (scene.playerPositions[0].0 + yChange, scene.playerPositions[0].1 + xChange)
43 }
44 //7
45 renderChange()
46}
پس از این که این کد را به بازی اضافه کردید، تصویر آن باید مشابه تصویر 17 باشد. دو نکته در این مرحله قابل توجه است، یکی این که مار به طرز آزاردهندهای کند حرکت میکند و شاید بهتر باشد سرعت را از 1 ثانیه به نیم یا حتی ربع ثانیه افزایش دهیم. دوم این که وقتی مار با دیوارهها برخورد میکند چه باید بکنیم؟ در برخی نسخههای این بازی در چنین مواردی مار وقتی با دیوار برخورد میکند، جهت خود را تغییر میدهد و در برخی دیگر در چنین حالتهایی میمیرد. به نظر میرسد تغییر جهت بهتر است و از این رو از این روش در این بازی استفاده میکنیم. توضیح کد فوق به صورت زیر است:
- در بخش 1، یک متغیر ایجاد میکنیم که برای تعیین جهت کنونی کاربر مفید است. در کد، متغیر روی 1 تنظیم شده. در تصویر متحرک شماره 17، مقدار متغیر را روی جهت 4 تنظیم کردهایم. متغیر را برای دیدن همه جهتها عوض کنید.
- در بخش 2، گزاره print(time) را حذف کرده و به جای آن یک فراخوانی به updatePlayerPosition() قرار دادهایم که در آن به صورت هر ثانیه یک بار update را فراخوانی میکنیم.
- در بخش 3، این متد بازیکن یا مار را در اطراف صفحه حرکت میدهد.
- در بخش 4، متغیرها را طوری تعیین میکنیم که بتوانیم تغییر مقدار x و y روبروی مار را تشخیص دهیم.
- در بخش 5، یک گزاره switch وجود دارد که ورودی playerPosition را میگیرد و متغیرهای x و y را بر اساس این که بازیکن به کدام سمت حرکت کرده، تغییر دهید.
- در بخش 6، بلوک کد موقعیت را به سمت جلوی آرایه تغییر میدهد. ما میخواهیم ابتدای دم در جهت مناسب باشد و سپس همه بلوکهای دم به موقعیت بعدی بروند.
- در بخش 7، تغییرهایی را که روی آرایه موقعیتها اجرا کردیم، رندر میکنیم.
پیچش مار در پیرامون صفحه
ما اینک یک بازیکن متحرک داریم که عالی است. ابتدا باید سرعت بازی را افزایش دهیم، چون مشخص شد که 1 ثانیه بسیار کند است. این درسی است که در فرایند طراحی بازی کسب میشود و متوجه میشویم که به دستکاری ها و تغییرات کوچک زیادی برای خلق حسی عالی در بازی نیاز داریم. در زمان کار روی پروژههای بازی، اغلب زمان روی ایجاد تغییرهای کوچک برای بهبود حس بازی صرف میشود. هنگامی که مکانیک بازی درست شد، میتوان به افزودن جنبههای جالب دیگر مانند ذرات و صداها اندیشید.
متغیر timeExtension را به مقدار 0.15 تغییر داده و پروژه را کامپایل کنید.
1//1 -- GameManager.swift
2var timeExtension: Double = 0.15
اکنون میتوانیم شروع به پیچش مار روی صفحه بکنیم. قطعه کد زیر را به پروژه اضافه کنید تا با تصویر (18) مطابقت یابد. توجه کنید که این کد به تابع ()updatePlayerPosition در فایل GameManager.swift اضافه میشود:
1//1
2if scene.playerPositions.count > 0 {
3 let x = scene.playerPositions[0].1
4 let y = scene.playerPositions[0].0
5 if y > 40 {
6 scene.playerPositions[0].0 = 0
7 } else if y < 0 {
8 scene.playerPositions[0].0 = 40
9 } else if x > 20 {
10 scene.playerPositions[0].1 = 0
11 } else if x < 0 {
12 scene.playerPositions[0].1 = 20
13 }
14}
در زمان کامپایل پروژه، صفحه باید مطابق تصویر فوق باشد، ما از playerDirection 4 در تصویر متحرک فوق استفاده کردهایم. مار میتواند پیرامون همه اضلاع صفحه بپیچد.
در بخش یک، کد نسبتاً سادهای داریم که بررسی میکند آیا موقعیت سر مار از اضلاع فوقانی، تحتانی، چپ و راست تجاوز کرده است و یا نه و سپس بازیکن را به سمت دیگر صفحه هدایت میکند.
کنترل کردن حرکت مار با استفاده از ژستهای سوایپ
بازی ما در حال تکمیل شدن است و اکنون باید متدی برای کنترل کردن جهت حرکت مار طراحی کنیم. برای پیادهسازی این متد از ژستهای Swipe به سم چپ، راست، بالا و پایین استفاده میکنیم. کد زیر را به فایل GameScene.swift اضافه کنید، به صورتی که با تصویر 9 تطبیق یابد.
1//1
2let swipeRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeR))
3swipeRight.direction = .right
4view.addGestureRecognizer(swipeRight)
5let swipeLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeL))
6swipeLeft.direction = .left
7view.addGestureRecognizer(swipeLeft)
8let swipeUp:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeU))
9swipeUp.direction = .up
10view.addGestureRecognizer(swipeUp)
11let swipeDown:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeD))
12swipeDown.direction = .down
13view.addGestureRecognizer(swipeDown)
14//2
15@objc func swipeR() {
16 print("r")
17}
18@objc func swipeL() {
19 print("l")
20}
21@objc func swipeU() {
22 print("u")
23}
24@objc func swipeD() {
25 print("d")
26}
- در بخش 1، ژستهای سوایپ را به تابع didMove(to view: SKView) اضافه میکنیم.
- در بخش 2، تابعهایی ایجاد میکنیم که وقتی کاربر یک سوایپ انجام میدهد، فراخوانی خواهند شد. آن objc@ پیش از تابع یک تابع objective-c ایجاد میکند که برای فراخوانی از طریق selector# در UISwipeGestureRecognizer اصلی ضروری است.
کد را با کامپایل کردن اپلیکیشن و سپس سوایپ کردن به جهتهای مختلف تست میکنیم. اینک کنسول باید حرف متناظر هر ژست سوایپ را پرینت کند. حال که ژستها را سازماندهی کردیم، باید جهت حرکت بازیکن را نیز تغییر داده و گزاره درون تابعهای سوایپ را با کد پرینت کرده و کد را به GameManager.swift اضافه کنیم به طوری که با تصویر 20 تطبیق یابد.
1//1 -- GameScene.swift
2game.swipe(ID: 3)
3game.swipe(ID: 1)
4game.swipe(ID: 2)
5game.swipe(ID: 4)
6//2 -- GameManager.swift
7func swipe(ID: Int) {
8 if !(ID == 2 && playerDirection == 4) && !(ID == 4 && playerDirection == 2) {
9 if !(ID == 1 && playerDirection == 3) && !(ID == 3 && playerDirection == 1) {
10 playerDirection = ID
11 }
12 }
13}
- در بخش 1، زمانی که یک ژست سوایپ تشخیص داده میشود، به اطلاع کلاس gameManager میرسد.
- در بخش 2، اگر سوایپ با جهت کنونی تعارض نداشته باشد، جهت بازیکن روی ورودی سوایپ تنظیم میشود. اگر به سمت پایین در حرکت باشید، نمیتوانید مستقیماً به سمت بالا برگردید و یک تعارض ایجاد میشود. اگر به سمت چپ حرکت میکنید، نمیتوانید بیدرنگ به سمت راست بازگردید. در برخی نسخههای بازی مار، وارد کردن یک حرکت اشتباه مانند این میتواند موجب مرگ شود، اما در این نسخه صرفاً این ورودیهای متعارض را نادیده میگیریم.
افزودن نقاطی به بازی و تغییر امتیاز
اینک یک سیستم منوی عملیاتی داریم که صفحه بازی را باز میکند، همچنین یک آرایه از سلولها و یک آرایه از موقعیتهای بازیکن داریم. ضمناً یک بازیکن وجود دارد که در صفحه به پیرامون حرکت میکند و در لبهها دچار پیچش میشود و کنترلهای سوایپ نیز تشخیص داده میشوند. اکنون باید سازوکار امتیازدهی را به بازی اضافه کنیم تا بر جذابیت آن افزوده شود. این سازوکار نقاط تصادفی تولید میکند که امتیاز را افزایش میدهند و طول دم مار را افزایش میدهد.
گام نخست: یک نقطه تصادفی تولید کرده و آن را روی صفحه رندر میکنیم. کد زیر را به فایل GameScene.swift اضافه کنید تا به صورت تصویر 21 درآید:
1//1
2var scorePos: CGPoint?
اکنون به فایل GameManager.swift میرویم و کد زیر را به آن اضافه میکنیم تا مانند تصویر 22 شود.
1//2
2generateNewPoint()
3//3
4private func generateNewPoint() {
5 let randomX = CGFloat(arc4random_uniform(19))
6 let randomY = CGFloat(arc4random_uniform(39))
7 scene.scorePos = CGPoint(x: randomX, y: randomY)
8}
9//4
10if scene.scorePos != nil {
11 if Int((scene.scorePos?.x)!) == y && Int((scene.scorePos?.y)!) == x {
12 node.fillColor = SKColor.red
13 }
14}
پس از اجرای کد در شبیهساز باید برخی نقاط مربعی قرمز رنگ را ببینید که به صورت تصادفی روی صفحه قرار میگیرند.
- در بخش 1، یک متغیر برای موقعیت امتیاز تصادفی مقداردهی میکنیم. علامت (؟) نشاندهنده این است که این مقدار تا زمانی که مقدار متغیر متعاقباً تعیین شود، nil است.
- در بخش 2، تابع درون تابع ()initGame را فراخوانی میکنیم به طوری که نقطه تصادفی جدیدی تولید خواهد شد.
- در بخش 3، تابع یک موقعیت تصادفی درون کرانهای صفحه (20/40) ایجاد میکند. این آرایهای است که از صفر آغاز میشود و از این رو از 0 تا 19 و از 0 تا 39 میسازیم و آرایهای به ابعاد 20 در 40 تشکیل میدهیم.
- در بخش 4، درون حلقه رندرینگ بررسی میکنیم که آیا موقعیت کنونی گره با موقعیت امتیاز که به صورت تصادفی قرار گرفته است مطابقت دارد یا نه. اگر چنین باشد، رنگ آن را به قرمز عوض میکنیم. میتوانید رنگ آن را برحسب سلیقه خود عوض کنید. متغیری که موقعیت کنونی را ذخیره میکند یک CGPoint است و معنای آن این است که باید point.x و point.y را بررسی کرده و آن را با موقعیتهای x و y گره کنونی مقایسه کنیم. توجه کنید که موقعیتهای x و y در آرایه گرهها به صورت معکوس هستند. این روشی است که برای مقایسه x == y و y == x استفاده میکنیم.
اکنون باید یک متغیر طراحی کنیم که امتیاز بازی کنونی را نگهداری کرده و زمانی که بازیکن با یک نقطه امتیازی برخورد میکند روی آن تکرار کند. هنگامی که بازیکن با یک نقطه برخورد میکند، باید یک نقطه تصادفی ایجاد کرده و طول دم مار را افزایش دهد.
کد زیر را به فایل GameManager.swift اضافه کنید تا با تصویر (23) مطابقت یابد:
1//1
2var currentScore: Int = 0
3//2
4checkForScore()
5//3
6private func checkForScore() {
7 if scene.scorePos != nil {
8 let x = scene.playerPositions[0].0
9 let y = scene.playerPositions[0].1
10 if Int((scene.scorePos?.x)!) == y && Int((scene.scorePos?.y)!) == x {
11 currentScore += 1
12 scene.currentScore.text = "Score: \(currentScore)"
13 generateNewPoint()
14 }
15 }
16}
17//4
18while contains(a: scene.playerPositions, v: (Int(randomX), Int(randomY))) {
19 randomX = CGFloat(arc4random_uniform(19))
20 randomY = CGFloat(arc4random_uniform(39))
21}
- در بخش 1، یک متغیر مقداردهی میکنیم که امتیاز کنونی را در بازی ردگیری میکند.
- در بخش 2، تابع ()checkForScore را درون تابع updae فراخوانی میکنیم. این تابع هر بار که بازیکن حرکتی انجام میدهد فراخوانی خواهد شد.
- در بخش 3، تابع بررسی میکند آیا یک scorePos تعیین شده یا نه. اگر تعیین شده باشد، سر مار را بررسی میکند. اگر مار با چنین نقطهای تماس یابد، امتیاز تکرار میشود، برچسب متنی برای نمایش امتیاز بهروزرسانی میشود و یک نقطه جدید تولید میشود.
- در بخش 4، این کد را به متد ()generateNewPoint اضافه میکنیم تا مطمئن شویم که یک نقطه درون بدنه مار ایجاد نشده است. زمانی که طول مار افزایش مییابد، احتمال این که با این مشکل مواجه شویم افزایش مییابد و از این رو یک بلوک کد میتواند این مشکل را حل کند.
در زمان اجرای کد متوجه خواهید شد که برخورد با یک نقطه امتیازی موجب ایجاد امتیاز جدیدی روی صفحه میشود و روی برچسب امتیاز بازتاب مییابد. اکنون طول مار را افزایش دادهایم و لذا مکانیک روند بازی به پایان خود نزدیک شده است. این فرایند ساده است، کافی است قطعه کد زیر را به تابع ()checkForScore اضافه کنید به طوری که با تصویر (24) مطابقت یابد.
1scene.playerPositions.append(scene.playerPositions.last!)
2scene.playerPositions.append(scene.playerPositions.last!)
3scene.playerPositions.append(scene.playerPositions.last!)
خاتمه بازی
اکنون باید متدی را پیادهسازی کنیم که بازی را خاتمه میبخشد و به سیستم منو بازمیگردد. در بازی مار، بازی زمانی پایان مییابد که بازیکن با دم خود برخورد کند. این وضعیت با پیادهسازی خطوط کد زیر در فایل GameManager.swift امکانپذیر است. مطمئن شوید که کد شما با تصویر (25) مطابقت دارد.
1//1
2checkForDeath()
3//2
4private func checkForDeath() {
5 if scene.playerPositions.count > 0 {
6 var arrayOfPositions = scene.playerPositions
7 let headOfSnake = arrayOfPositions[0]
8 arrayOfPositions.remove(at: 0)
9 if contains(a: arrayOfPositions, v: headOfSnake) {
10 playerDirection = 0
11 }
12 }
13}
14//3
15if playerDirection != 0 {
16 playerDirection = ID
17}
18//4
19case 0:
20 //dead
21 xChange = 0
22 yChange = 0
23 break
- در بخش 1، تابع ()checkForDeath را فراخوانی میکنیم.
- در بخش 2، بررسی میکنیم آیا سر مار با هر کدام از موقعیتهای دم برخورد داشته است یا نه. اگر بازیکن بمیرد، playerDirection را روی 0 تنظیم میکنیم.
- در بخش 3، اگر بازیکن بمیرد، از playerDirection = 0 استفاده میکنیم و سپس امکان وارد کردن ژستهای جدید به عنوان ورودی لغو میشود.
- در بخش 4، یک حالت جدید در گزاره switch در ()updatePlayerPosition اضافه میکنیم، اگر playerDirection روی 0 تعیین شده باشد، موقعیت سر را عوض نمیکنیم. بدین ترتیب میتوانیم موقعیتهای دم را به کندی از نما حذف کنیم.
پس از پیادهسازی این کد، اپلیکیشن باید مانند تصویر 26 فوق عمل کند. هنگامی که مار با خودش تصادم کند، بازی خاتمه مییابد و اکنون باید یک متد برای راهاندازی مجدد بازی و ذخیره امتیاز به عنوان «بالاترین امتیاز» (highscore) طراحی کنیم.
راهاندازی مجدد بازی و ذخیره دادههای highscore
اکنون یک بازی مار ساختهایم که اغلب بخشهای آن کار میکند. مراحل نهایی نیز چندان دور از دسترس نیستند. باید یک متد برای راهاندازی مجدد بازی و بازگشت به منو طراحی کنیم. همچنین در صورتی که دور کنونی بازی بهتر از highscore ذخیرهشده قبلی باشد، باید دادههای highscore را روی دستگاه ذخیره کنیم.
ابتدا یک متد را پیادهسازی میکنیم که وقتی مار انیمیشن پایانی خود را تکمیل میکند، به منوی بازی برمیگردد. کد زیر را به فایل GameManager.swift اضافه کنید تا مانند تصویر 28 زیر شود:
1//1
2finishAnimation()
3//2
4private func finishAnimation() {
5 if playerDirection == 0 && scene.playerPositions.count > 0 {
6 var hasFinished = true
7 let headOfSnake = scene.playerPositions[0]
8 for position in scene.playerPositions {
9 if headOfSnake != position {
10 hasFinished = false
11 }
12 }
13 if hasFinished {
14 print("end game")
15 playerDirection = 4
16 //animation has completed
17 scene.scorePos = nil
18 scene.playerPositions.removeAll()
19 renderChange()
20 //return to menu
21 scene.currentScore.run(SKAction.scale(to: 0, duration: 0.4) {
22 self.scene.currentScore.isHidden = true
23}
24 scene.gameBG.run(SKAction.scale(to: 0, duration: 0.4)) {
25 self.scene.gameBG.isHidden = true
26 self.scene.gameLogo.isHidden = false
27 self.scene.gameLogo.run(SKAction.move(to: CGPoint(x: 0, y: (self.scene.frame.size.height / 2) - 200), duration: 0.5)) {
28 self.scene.playButton.isHidden = false
29 self.scene.playButton.run(SKAction.scale(to: 1, duration: 0.3))
30 self.scene.bestScore.run(SKAction.move(to: CGPoint(x: 0, y: self.scene.gameLogo.position.y - 50), duration: 0.3))
31 }
32 }
33 }
34 }
35}
توضیح کد فوق چنین است:
- در بخش 1، تابع ()finishAnimation را فراخوانی میکنیم.
- در بخش 2، این تابع تکمیل انیمیشن نهایی مار را بررسی میکند. زمانی که همه موقعیتها در آرایه playerPositions با همدیگر برابر شدند؛ طول مار به یک مربع کاهش مییابد. پس از انجام این حالت، playerDirection را روی 4 تنظیم میکنیم و سپس منو را نمایش میدهیم. همچنین برچسب currentScore و شیء gameBG را پنهان میسازیم.
در ادامه متدی اضافه میکنیم که high score را در دستگاه ذخیره میکند تا وقتی که اپلیکیشن بسته شد، دادههای high score را از دست ندهیم. در متد جدید به نام ()finishAnimation خط کد زیر را اضافه میکنیم به طوری که با تصویر 29 زیر مطابقت یابد:
1updateScore()
اکنون فایل AppDelegate.swift را باز کنید و خطوط کد زیر را اضافه کنید، به طوری که با تصویر 30 مطابقت داشته باشد. این قطعه کد از UserDefaults برای ذخیره دادهها در حافظ دستگاه استفاده میکند. اگر قصد دارید پروژهای با حجم بالایی از دادههای ذخیرهشده بسازید، این وضعیت ممکن است موجب بروز شکل میشود، اما در مورد چیزهای سادهای مانند تنظیمات بازی و متغیرها این روش ذخیرهسازی مشکلی پدید نمیآورد.
1let defaults = UserDefaults.standard
2let defaultValue = ["bestScore" : 0]
3defaults.register(defaults: defaultValue)
اکنون به فایل GameManager.swift برگردید و متد زیر را ایجاد کنید، به طوری که کد با تصویر 31 مطابقت یابد. این بلوک کد به سادگی بررسی میکند که آیا امتیاز از بهترین امتیاز قبلی بالاتر است یا نه و سپس بر همین اساس آن را بهروزرسانی میکند.
1//1
2private func updateScore() {
3 if currentScore > UserDefaults.standard.integer(forKey: "bestScore") {
4 UserDefaults.standard.set(currentScore, forKey: "bestScore")
5 }
6 currentScore = 0
7 scene.currentScore.text = "Score: 0"
8 scene.bestScore.text = "Best Score: \(UserDefaults.standard.integer(forKey: "bestScore"))"
9}
فایل GameScene.swift را باز کنید و تابع ()initializeMenu را طوری بهروزرسانی کنید که با تصویر 32 مطابقت داشته باشد. این وضعیت تضمین میکند که بازی بهترین امتیاز بازی را بارگذاری و به جای 0 نمایش میدهد.
1bestScore.text = "Best Score: \(UserDefaults.standard.integer(forKey: "bestScore"))"
پس از افزودن این کد اکنون high score بازیکن هنگامی که اپلیکیشن بسته شود، روی حافظه دستگاه ذخیره میشود.
سخن پایانی
برای حذف اطلاعات توسعهدهنده در انتهای صفحه باید فایل را باز کرده و مقادیر view.showFPS و view.showsNodeCount را روی false تنظیم کنید. بدین ترتیب موفق شدیم یک بازی برای iPhone از صفر طراحی کنیم.
اگر از این پروژه لذت بردهاید، میتوانید اپلیکیشن را از اپاستور (+) دانلود کنید. توجه داشته باشید که آن چه در این مطلب ارائه شد، صرفاً بخش بسیار کوچکی از قابلیتهای موتور بازی SpriteKit است. در صورتی که علاقهمند باشید میتوانید با مطالعه بیشتر با جوانب دیگری از این موتور بازی نیز آشنا شوید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش برنامه نویسی Swift (سوئیفت) برای برنامه نویسی iOS
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- آموزش سوئیفت (Swift) — مجموعه مقالات مجله فرادرس
- انیمیشن رابط کاربری در سوئیفت — راهنمای کاربردی
==