ساخت بازی Snake در SwiftUI | به زبان ساده
SwiftUI یک کیت ابزار برای طراحی رابط کاربری است که اپل برای سوئیفت، زبان برنامهنویسی پلتفرمهای خود، ارائه کرده است. با استفاده از SwiftUI طراحی اپلیکیشنها به میزان زیادی تسهیل میشود. ما در این راهنما قصد داریم به آموزش روش ساخت بازی Snake در SwiftUI بپردازیم.
Enum و متغیرها
در ابتدا باید جهت سوایپ کردن کاربر را تشخیص بدهیم تا بتوانیم جهت حرکت «مار» (Snake) را تا تغییر بعدی به آن سمت عوض کنیم. بنابراین ابتدا یک enum برای جهتها ایجاد میکنیم:
1enum direction {
2 case up, down, left, right
3}
در این اپلیکیشن از یک تایمر برای کنترل سرعت مار کمک میگیریم. برای این که حرکت مار کُندتر یا تندتر شود، میتوانیم بازههای زمانی تایمر را تغییر دهیم. به جای یک شبکه (Grid) نیز از اندازه مار کمک میگیریم. به این ترتیب میتوانیم موقعیت مار و غذای آن را کنترل کنیم.
بنابراین به متغیرهای زیر در بازی نیاز خواهیم داشت:
1@State var startPos : CGPoint = .zero // the start poisition of our swipe
2@State var isStarted = true // did the user started the swipe?
3@State var gameOver = false // for ending the game when the snake hits the screen borders
4@State var dir = direction.down // the direction the snake is going to take
5@State var posArray = [CGPoint(x: 0, y: 0)] // array of the snake's body positions
6@State var foodPos = CGPoint(x: 0, y: 0) // the position of the food
7let snakeSize : CGFloat = 10 // width and height of the snake
8let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() // to updates the snake position every 0.1 second
عناصر نمای Snake
درون body یک ZStack اضافه میکنیم. درون آن میتوانیم رنگ پسزمینه را تعیین کنیم. همچنین از یک ZStack دیگر برای مستطیل مار و مستطیل غذا استفاده میکنیم. برای ایجاد بدن مار باید روی آرایهای که موقعیت بدنه مار را نگهداری میکند، حلقه تکرار تعریف کنیم. به هر دوی این مستطیلها عرض و ارتفاعی برابر متغیر snakeSize میدهیم. آخرین عنصر درون نما یک نمای متنی است که وقتی نمایش مییابد که متغیر snakeSize فعال شود:
1 var body: some View {
2 ZStack {
3 Color.pink.opacity(0.3)
4 ZStack {
5 ForEach (0..<posArray.count, id: \.self) { index in
6 Rectangle()
7 .frame(width: self.snakeSize, height: self.snakeSize)
8 .position(self.posArray[index])
9 }
10 Rectangle()
11 .fill(Color.red)
12 .frame(width: snakeSize, height: snakeSize)
13 .position(foodPos)
14 }
15
16 if self.gameOver {
17 Text("Game Over")
18 }
19 }
20 }
تابعها
در این بخش یک تابع ایجاد میکنیم که موقعیت مستطیلها را مشخص میسازد. برای موقعیتیابی مار و غذا در یک شبکه ناپیدا، باید از اندازه مار برای یافتن تعداد ردیفها و ستونهایی که درون نما داریم کمک بگیریم:
1let minX = UIScreen.main.bounds.minX
2let maxX = UIScreen.main.bounds.maxX
3let minY = UIScreen.main.bounds.minY
4let maxY = UIScreen.main.bounds.maxY
5
6func changeRectPos() -> CGPoint {
7 let rows = Int(maxX/snakeSize)
8 let cols = Int(maxY/snakeSize)
9
10 let randomX = Int.random(in: 1..<rows) * Int(snakeSize)
11 let randomY = Int.random(in: 1..<cols) * Int(snakeSize)
12
13 return CGPoint(x: randomX, y: randomY)
14 }
در این بخش onAppear. را به یکی از ZStack-هایمان اضافه میکنیم تا موقعیت مار و غذا را با استفاده از تابعی که قبلاً ایجاد کردیم، تعیین کنیم:
1.onAppear() {
2 self.foodPos = self.changeRectPos()
3 self.posArray[0] = self.changeRectPos()
4}
یک تابع ایجاد میکنیم تا بررسی کند مار درون چارچوب صفحه قرار دارد یا خارج شده است. همچنین جهت سوایپ کاربر را تشخیص داده و حرکت مار را به آن جهت عوض میکند:
1 func changeDirection () {
2 if self.posArray[0].x < minX || self.posArray[0].x > maxX && !gameOver{
3 gameOver.toggle()
4 }
5 else if self.posArray[0].y < minY || self.posArray[0].y > maxY && !gameOver {
6 gameOver.toggle()
7 }
8 var prev = posArray[0]
9 if dir == .down {
10 self.posArray[0].y += snakeSize
11 } else if dir == .up {
12 self.posArray[0].y -= snakeSize
13 } else if dir == .left {
14 self.posArray[0].x += snakeSize
15 } else {
16 self.posArray[0].x -= snakeSize
17 }
18
19 for index in 1..<posArray.count {
20 let current = posArray[index]
21 posArray[index] = prev
22 prev = current
23 }
24 }
با استفاده از کد زیر میتوانیم تشخیص دهیم که کاربر به کدام جهت سوایپ کرده است. به این منظور از DragGesture برای دریافت موقعیتهای آغاز و پایان سوایپ استفاده کرده و سپس تفاوت بین مختصات x و y را محاسبه میکنیم تا جهت سوایپ را تشخیص دهیم. تابع زیر را به DragGesture اول اضافه کنید:
1 .gesture(DragGesture()
2 .onChanged { gesture in
3 if self.isStarted {
4 self.startPos = gesture.location
5 self.isStarted.toggle()
6 }
7 }
8 .onEnded { gesture in
9 let xDist = abs(gesture.location.x - self.startPos.x)
10 let yDist = abs(gesture.location.y - self.startPos.y)
11 if self.startPos.y < gesture.location.y && yDist > xDist {
12 self.dir = direction.down
13 }
14 else if self.startPos.y > gesture.location.y && yDist > xDist {
15 self.dir = direction.up
16 }
17 else if self.startPos.x > gesture.location.x && yDist < xDist {
18 self.dir = direction.right
19 }
20 else if self.startPos.x < gesture.location.x && yDist < xDist {
21 self.dir = direction.left
22 }
23 self.isStarted.toggle()
24 }
25 )
اکنون تنها چیزی که باید به ZStack خود اضافه کنیم تا بازی تکمیل شود، یک تابع .onReceieve است که تایمر ما را میگیرید و موقعیتهای مار و غذاها را بهروزرسانی میکند. زمانی که مار به بالای غذا میرسد، یک موقعیت جدید به آرایه موقعیتهای بدن مار اضافه میشود تا طول مار افزایش یابد:
1 .onReceive(timer) { (_) in
2 if !self.gameOver {
3 self.changeDirection()
4 if self.posArray[0] == self.foodPos {
5 self.posArray.append(self.posArray[0])
6 self.foodPos = self.changeRectPos()
7 }
8 }
9 }
10 .edgesIgnoringSafeArea(.all)
به این ترتیب کار ساخت بازی مار در swiftui پایان مییابد. امیدواریم این مطلب مورد توجه شما قرار گرفته باشد.