[cocos2d-x] 2048 게임 만들기 feat. SpriteBuilder and Swift
- Study/Cocos2d-x 3.x
- 2016. 6. 5. 16:24
퍼즐게임중에 2048이란 게임이 있는데 이 게임을 한번 따라서 만들어 보겠습니다.
추가적인 이미지나 Resource를 사용하지 않고 아주 노멀하게 ~ 기본적인 소스들만 사용해서 만들 예정입니다.
일단 2048이라는 게임을 모르시는 분들도 있기때문에 아래 링크를...
몇 번 플레이해보시면 아시겠지만 2의 배수로 계속 더해 2048을 만드는 것이 목적인 게임입니다. 한 때 모바일로 출시되 많은 사랑을 받은 게임인데요, 사실 2048보다는 Three라는 게임이 원조입니다. 그 뒤에 2048이 나왔는데 어찌 마케팅을 2048이 더 잘 해서 2048이 더 유명해졌네요.
암튼 Three보다는 2048이 게임 로직이 더 쉽기 때문에 2048을 따라해보려고 합니다.
개발 환경은 SpriteBuilder와 Swift 언어를 사용하겠습니다.
일단 SpriteBuilder에서 신규 프로젝트를 만듭니다.
개발 언어가 obj-C가 아니라 Swift입니다!!!
디폴트 프로젝트가 만들어지고 나면 정상적으로 실행되는지 x-code로 publishing 한 번 해봅시다.
X-code에서 실행시키는 방법은 간단합니다. SpriteBuilder로 만든 프로젝터 경로로 가서 .xcodeproj 파일을 실행하면 됩니다.
처음 프로그램을 실행시키면 swift언어를 최신버전으로 convert할 것인지 물어봅니다. 뭐 항상 Yes를 누르긴 하는데 항상 바꿀게 없다고 나옵니다. X-code에서 가상머신을 iphone6로 설정 후 실행하면 아래와 같이 실행될 겁니다.
SpriteBuilder의 기본 화면입니다. 자 이제 이 화면을 2048 화면으로 바꿔볼까요?
먼저 2048은 세로화면에서 진행되기 때문에 화면을 세로로 고정해 줄 필요가 있습니다. SpriteBuilder에서 File -> Proejct setting에서 Global 옵션에 있는 Orientation을 Portrait으로 바꿔줍니다. 그리고 MainScene.ccb에 있는 기본문구 SpriteBuilder라고 써있는 CCLabelTTF를 지워줍니다.
게임화면은 아주 간단합니다. 현재 점수를 나타내는 Score와 최고점수를 나타내는 HighScore, 그리고 실제 타일들이 움직이는 Grid만 있으면 됩니다.
글씨들은 Label TTF를 사용해서 아~~무 위치나 적당히 하면 됩니다. Grid는 anchorpoint를 0.5, 0.5를 주고 화면 중앙에 위치, 300x300크기의 Color Node로 만들었습니다. 이미지셋을 안쓰기 때문에 색을 주기 위해 ColorNode를 씁시다!!
X-code에서 사용하기 위해 Grid는 커스텀클래스로 만들고, Label은 변수 이름을 지정합시다. (점수, 최고점수 레이블은 필요 없고, 숫자 부분만 각각 scoreLabel, highscoreLabel로 지정!)
이제 실제로 숫자가 적혀나올 Tile을 만듭니다.
크기는 기본적으로 70x70을 주었습니다. Grid가 300x300이기 때문에 가로 세로 4개씩 들어가려면 4*70 = 280 이고 여백도 조금 필요해서 70이 적당합니다. 기본 노드의 크기를 70x70으로 만들고 그 아래 컬러노드를 추가합니다.
컬러노드는 대충 색을 아무거나 지정하고 상위 노드와 같은 크기, 같은위치에 위치하게 설정합니다. 타일에 표기할 Label도 넣습니다.
자 이제 부모노드인 CCNode는 Tile의 커스텀클래스를 만들고, Color노드는 backgroundNode, Label은 valueNode라고 이름을 지정해줍니다.
이 부분을 잘 하셔야지 publish했을 때 Xcode에서 실행이 잘됩니당. 헷갈리시면 안됩니다!!
이젠 Xcode가서 클래스와 변수를 선언하고 실행해 봅시다.
1 2 3 4 5 6 7 8 9 10 | import Foundation class MainScene: CCNode { weak var scoreLabel: CCLabelTTF! weak var highscoreLabel: CCLabelTTF! weak var grid: Grid! } | cs |
메인의 코드입니다.
1 2 3 4 5 | import Foundation class Grid: CCNodeColor { } | cs |
Grid는 커스텀클래스이기 때문에 swift파일을 생성해주셔야됩니다. CCNode가 아니라 CCNodeColor인점 확인해주세요~
1 2 3 4 5 6 7 8 | import Foundation class Tile: CCNode { weak var backgroundNode: CCNodeColor! weak var valueLabel: CCLabelTTF! } | cs |
Tile 역시 swift파일을 생성해주시고 backgroundNode와 valueNode를 불러옵니다.
선언을 다 하고 프로그램을 실행하면 아래와 같은 화면이 뜹니다.
잘 실행이 된다면 선언은 잘된겁니다. 만약 실행이 안되면 이름이 틀렸거나 커스텀클래스를 선언하지 않았거나 하는 문제가 있다는 것 이므로 다시 한번 잘 살펴봅시다.
가장 먼저 할 것은 그리드에 배경을 깔아주는 것입니다. 4x4의 그림자 타일을 깔아서 타일의 위치가 어디어디인지 틀을 잡아주는 역할을 합니다. 또한 타일의 크기나 여백의 크기, 그리드 사이즈 같은 기본 변수들을 선언해줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | import Foundation class Grid: CCNodeColor { let gridSize = 4 // 그리드 사이즈 var columnWidth: CGFloat = 0 // 타일의 가로 크기 var columnHeight: CGFloat = 0 // 타일의 세로 크기 var tileMarginVertical: CGFloat = 0 // 타일 세로 여백 크기 var tileMarginHorizontal: CGFloat = 0 // 타일 가로 여백 크기 // 클래스가 실행될 때 자동으로 실행됨 func didLoadFromCCB(){ setupBackground() } // 그리드에 배경을 깔아줌 func setupBackground() { // 타일 클래스로 Tile 노드를 읽어옴 let tile = CCBReader.load("Tile") as! Tile // 타일의 가로 세로 크기 설정 columnWidth = tile.contentSize.width columnHeight = tile.contentSize.height // 여백 크기를 어떻게 할지 계산 tileMarginHorizontal = (contentSize.width - (CGFloat(gridSize) * columnWidth)) / CGFloat(gridSize + 1) tileMarginVertical = (contentSize.height - (CGFloat(gridSize) * columnHeight)) / CGFloat(gridSize + 1) var x = tileMarginHorizontal var y = tileMarginVertical // 배경으로 쓸 타일을 4x4그리드에 한장씩 배치 for _ in 0..<gridSize{ x = tileMarginHorizontal for _ in 0..<gridSize{ let backgroundTile = CCNodeColor(color: CCColor.grayColor()) backgroundTile.contentSize = CGSize(width: columnWidth, height: columnHeight) backgroundTile.position = CGPoint(x: x, y: y) addChild(backgroundTile) x += columnWidth + tileMarginHorizontal } y += columnHeight + tileMarginVertical } } } | cs |
자세한 설명은 주석을 참고하세요~! 실행하면 아래와 같습니다.
기본적인 틀이 완성되었습니다. 이제 실제로 움직이는 타일을 생성하는 소스를 추가해보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | // // Grid.swift // cholol2048 // // Created by Cho on 2016. 6. 5.. // Copyright © 2016년 Apportable. All rights reserved. // import Foundation class Grid: CCNodeColor { let gridSize = 4 // 그리드 사이즈 var columnWidth: CGFloat = 0 // 타일의 가로 크기 var columnHeight: CGFloat = 0 // 타일의 세로 크기 var tileMarginVertical: CGFloat = 0 // 타일 세로 여백 크기 var tileMarginHorizontal: CGFloat = 0 // 타일 가로 여백 크기 // 시작 타일수 let startTiles = 2 // 그리드 배열 관리 var gridArray = [[Tile?]]() var noTile: Tile? = nil // 클래스가 실행될 때 자동으로 실행됨 func didLoadFromCCB(){ setupBackground() // 그리드에 타일들을 초기화 for _ in 0..<gridSize { var column = [Tile?]() for _ in 0..<gridSize { column.append(noTile) } gridArray.append(column) } // 시작타일 생성 spawnStartTiles() } // 그리드에 배경을 깔아줌 func setupBackground() { // 타일 클래스로 Tile 노드를 읽어옴 let tile = CCBReader.load("Tile") as! Tile // 타일의 가로 세로 크기 설정 columnWidth = tile.contentSize.width columnHeight = tile.contentSize.height // 여백 크기를 어떻게 할지 계산 tileMarginHorizontal = (contentSize.width - (CGFloat(gridSize) * columnWidth)) / CGFloat(gridSize + 1) tileMarginVertical = (contentSize.height - (CGFloat(gridSize) * columnHeight)) / CGFloat(gridSize + 1) var x = tileMarginHorizontal var y = tileMarginVertical // 배경으로 쓸 타일을 4x4그리드에 한장씩 배치 for _ in 0..<gridSize{ x = tileMarginHorizontal for _ in 0..<gridSize{ let backgroundTile = CCNodeColor(color: CCColor.grayColor()) backgroundTile.contentSize = CGSize(width: columnWidth, height: columnHeight) backgroundTile.position = CGPoint(x: x, y: y) addChild(backgroundTile) x += columnWidth + tileMarginHorizontal } y += columnHeight + tileMarginVertical } } // 배열 인덱스를 이용해 tile 위치를 반환하는 함수 func positionForColumn(column: Int, row: Int) -> CGPoint { let x = tileMarginHorizontal + CGFloat(column) * (tileMarginHorizontal + columnWidth) let y = tileMarginVertical + CGFloat(row) * (tileMarginVertical + columnHeight) return CGPoint(x: x, y: y) } // 특정 위치에 tile 생성 func addTileAtColumn(column: Int, row: Int) { let tile = CCBReader.load("Tile") as! Tile gridArray[column][row] = tile tile.scale = 0 addChild(tile) tile.anchorPoint = ccp(0,0) // 없어도 되는데 SpriteBuilder에서 anchorPoint를 잘못지정해서 넣음.. ㅎ tile.position = positionForColumn(column, row: row) let delay = CCActionDelay(duration: 0.3) let scaleUp = CCActionScaleTo(duration: 0.2, scale: 1) let sequence = CCActionSequence(array: [delay, scaleUp]) tile.runAction(sequence) } // 랜덤한 위치에 Tile 생성 func spawnRandomTile() { var spawned = false while !spawned { let randomRow = Int(CCRANDOM_0_1() * Float(gridSize)) let randomColumn = Int(CCRANDOM_0_1() * Float(gridSize)) let positionFree = gridArray[randomColumn][randomRow] == noTile if positionFree { addTileAtColumn(randomColumn, row: randomRow) spawned = true } } } // 시작할 때 생성할 타일 수만큼 타일 생성 func spawnStartTiles(){ for _ in 0..<startTiles { spawnRandomTile() } } } | cs |
역시 소스 설명은 주석으로... 실행하면 아래와 같습니다.
타일이 생성되었긴 한데 숫자가 0이네요. Tile.swift에서 숫자를 넣어봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // // Tile.swift // cholol2048 // // Created by Cho on 2016. 6. 5.. // Copyright © 2016년 Apportable. All rights reserved. // import Foundation class Tile: CCNode { weak var backgroundNode: CCNodeColor! weak var valueLabel: CCLabelTTF! // SpriteBuilder에서 valueNode로 했었는데 valueLabel로 수정하세요~! // value 초기값 0으로 지정. var value: Int = 0 { // 값이 변경될 때마다 자동으로 실행 didSet{ valueLabel.string = "\(value)" } } func didLoadFromCCB() { // 타일이 생성될 때마다 2 or 4 값으로 설정 value = Int(CCRANDOM_MINUS1_1() + 2) * 2 } } | cs |
위와같이 바꿔주고 실행하면 다음과 같습니다. (오류가 나시는 분은 valueLabel 변경 주석을 참고하세요.)
자 이제 이 다음에 타일을 움직이는 것과 같은 숫자일 경우 합치는것, 점수 카운트 등이 남았는데..
따로 설명할 수가 없어서 갑작스럽게 풀코드로 가겠습니다.
그 전에 일단 게임오버 화면부터 만들어보겠습니다.
뭐 대충 위와같이 GameEnd라는 이름의 ccb를 만듭니다 (노드로 만듭니다.)
루트의 크기를 300x200으로 해주시고 컬러노드 넣어서 색 넣어주시고 레이블과 버튼을 위치시킵니다. 위치는 뭐 그렇게 중요하지 않습니다. 중요한건 버튼에 콜백메소드 설정과 Label 이름 설정, 커스텀 클래스 이름 설정입니다.
빼먹지 말고 하나하나 지정해줍시다.
자 이제 필요한 화면은 다 만들었고, 아래 코드까지 전부 입력하면 완성입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | // // Grid.swift // cholol2048 // // Created by Cho on 2016. 6. 5.. // Copyright © 2016년 Apportable. All rights reserved. // import Foundation class Grid: CCNodeColor { // 게임 점수 var score: Int = 0 { didSet { var mainScene = parent as! MainScene mainScene.scoreLabel.string = "\(score)" } } // 승리 점수 let winTile = 2048 let gridSize = 4 // 그리드 사이즈 var columnWidth: CGFloat = 0 // 타일의 가로 크기 var columnHeight: CGFloat = 0 // 타일의 세로 크기 var tileMarginVertical: CGFloat = 0 // 타일 세로 여백 크기 var tileMarginHorizontal: CGFloat = 0 // 타일 가로 여백 크기 // 시작 타일수 let startTiles = 2 // 그리드 배열 관리 var gridArray = [[Tile?]]() var noTile: Tile? = nil // 클래스가 실행될 때 자동으로 실행됨 func didLoadFromCCB(){ //초기화 setupBackground() setupGestures() // 그리드에 타일들을 초기화 for _ in 0..<gridSize { var column = [Tile?]() for _ in 0..<gridSize { column.append(noTile) } gridArray.append(column) } // 시작타일 생성 spawnStartTiles() } // 그리드에 배경을 깔아줌 func setupBackground() { // 타일 클래스로 Tile 노드를 읽어옴 let tile = CCBReader.load("Tile") as! Tile // 타일의 가로 세로 크기 설정 columnWidth = tile.contentSize.width columnHeight = tile.contentSize.height // 여백 크기를 어떻게 할지 계산 tileMarginHorizontal = (contentSize.width - (CGFloat(gridSize) * columnWidth)) / CGFloat(gridSize + 1) tileMarginVertical = (contentSize.height - (CGFloat(gridSize) * columnHeight)) / CGFloat(gridSize + 1) var x = tileMarginHorizontal var y = tileMarginVertical // 배경으로 쓸 타일을 4x4그리드에 한장씩 배치 for _ in 0..<gridSize{ x = tileMarginHorizontal for _ in 0..<gridSize{ let backgroundTile = CCNodeColor(color: CCColor.grayColor()) backgroundTile.contentSize = CGSize(width: columnWidth, height: columnHeight) backgroundTile.position = CGPoint(x: x, y: y) addChild(backgroundTile) x += columnWidth + tileMarginHorizontal } y += columnHeight + tileMarginVertical } } // 배열 인덱스를 이용해 tile 위치를 반환하는 함수 func positionForColumn(column: Int, row: Int) -> CGPoint { let x = tileMarginHorizontal + CGFloat(column) * (tileMarginHorizontal + columnWidth) let y = tileMarginVertical + CGFloat(row) * (tileMarginVertical + columnHeight) return CGPoint(x: x, y: y) } // 특정 위치에 tile 생성 func addTileAtColumn(column: Int, row: Int) { let tile = CCBReader.load("Tile") as! Tile gridArray[column][row] = tile tile.scale = 0 addChild(tile) tile.anchorPoint = ccp(0,0) // 없어도 되는데 SpriteBuilder에서 anchorPoint를 잘못지정해서 넣음.. ㅎ tile.position = positionForColumn(column, row: row) let delay = CCActionDelay(duration: 0.3) let scaleUp = CCActionScaleTo(duration: 0.2, scale: 1) let sequence = CCActionSequence(array: [delay, scaleUp]) tile.runAction(sequence) } // 랜덤한 위치에 Tile 생성 func spawnRandomTile() { var spawned = false while !spawned { let randomRow = Int(CCRANDOM_0_1() * Float(gridSize)) let randomColumn = Int(CCRANDOM_0_1() * Float(gridSize)) let positionFree = gridArray[randomColumn][randomRow] == noTile if positionFree { addTileAtColumn(randomColumn, row: randomRow) spawned = true } } } // 시작할 때 생성할 타일 수만큼 타일 생성 func spawnStartTiles(){ for _ in 0..<startTiles { spawnRandomTile() } } // 제스처 정의 func setupGestures() { let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(Grid.swipeLeft)) swipeLeft.direction = .Left CCDirector.sharedDirector().view.addGestureRecognizer(swipeLeft) let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(Grid.swipeRight)) swipeRight.direction = .Right CCDirector.sharedDirector().view.addGestureRecognizer(swipeRight) let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(Grid.swipeUp)) swipeUp.direction = .Up CCDirector.sharedDirector().view.addGestureRecognizer(swipeUp) let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(Grid.swipeDown)) swipeDown.direction = .Down CCDirector.sharedDirector().view.addGestureRecognizer(swipeDown) } // 제스처 왼쪽 func swipeLeft(){ print("왼쪽") move(CGPoint(x: -1, y: 0)) } // 제스처 오른쪽 func swipeRight(){ print("오른쪽") move(CGPoint(x: 1, y: 0)) } // 제스처 위 func swipeUp(){ print("위") move(CGPoint(x: 0, y: 1)) } // 제스처 아래 func swipeDown(){ print("아래") move(CGPoint(x: 0, y: -1)) } // 제스처를 취할 경우 타일이 움직이는 함수 func move(direction: CGPoint){ var currentX = 0 var currentY = 0 // 타일이 실제로 움직였는지 판단. 못움직이는 방향으로 움직였을 때 라운드가 진행 안되도록 하기 위해.. var movedTilesThisRound = false /* 제스처의 방향을 판단하여 그리드 전체 타일을 검사할 때 시작점을 정의 게임로직의 가장 핵심이라고 할 수 있다. 만약 이동방향이 오른쪽이라면 제일 오른쪽 아래 타일부터 세로로 확인. 이동방향이 왼쪽이라면 왼쪽아래 타일부터 세로로 확인. 이동방향이 아래면 왼쪽아래 타일부터 가로로 확인. 이동방향이 위면 왼쪽 위 타일부터 가로로 확인. */ while indexValid(currentX, y: currentY) { let newX = currentX + Int(direction.x) let newY = currentY + Int(direction.y) if indexValid(newX, y: newY) { currentX = newX currentY = newY } else { break; } } var initialY = currentY var xChange = Int(-direction.x) var yChange = Int(-direction.y) if xChange == 0 { xChange = 1 } if yChange == 0 { yChange = 1 } // 실제로 타일을 이동시키는 부분 while indexValid(currentX, y: currentY) { while indexValid(currentX, y: currentY) { if var tile = gridArray[currentX][currentY] { var newX = currentX var newY = currentY while indexValidAndUnoccupied(newX + Int(direction.x), y: newY + Int(direction.y)){ newX += Int(direction.x) newY += Int(direction.y) } var performMove = false if indexValid(newX + Int(direction.x), y: newY + Int(direction.y)){ var otherTileX = newX + Int(direction.x) var otherTileY = newY + Int(direction.y) if let otherTile = gridArray[otherTileX][otherTileY]{ // 이동하는 타일에 이동선상에 있는 타일이 같은 값이고, 이번 라운드에 합처진 타일이 아닌지 체크 if tile.value == otherTile.value && !otherTile.mergedThisRound{ mergeTilesAtindex(currentX, y: currentY, withTileAtIndex: otherTileX, y: otherTileY) movedTilesThisRound = true } else { performMove = true } } } else { performMove = true } if performMove{ if newX != currentX || newY != currentY { moveTile(tile, fromX: currentX, fromY: currentY, toX: newX, toY: newY) movedTilesThisRound = true } } } currentY += yChange } currentX += xChange currentY = initialY } // 이번 라운드에 타일이 움직였으면 다음 라운드 진행 if movedTilesThisRound { nextRound() } } // 인덱스가 그리드 안에 있는지 체크. (0, 0) ~ (3, 3) 범위 내에 있는지 체크 func indexValid(x: Int, y: Int) -> Bool { var indexValid = true indexValid = (x >= 0 ) && (y >= 0 ) if indexValid { indexValid = x < Int(gridArray.count) if indexValid { indexValid = y < Int(gridArray[x].count) } } return indexValid } // 적합한 인덱스 이면서 해당 인덱스에 타일이 있는지 체크 func indexValidAndUnoccupied(x: Int, y: Int) -> Bool { let indexValid = self.indexValid(x, y: y) if !indexValid { return false } // unoccupied? return gridArray[x][y] == noTile } // 타일 이동 애니메이션 및 배열값 설정 func moveTile(tile: Tile, fromX: Int, fromY: Int, toX: Int, toY: Int){ gridArray[toX][toY] = gridArray[fromX][fromY] gridArray[fromX][fromY] = noTile let newPosition = positionForColumn(toX, row: toY) let moveTo = CCActionMoveTo(duration: 0.2, position: newPosition) tile.runAction(moveTo) } // 타일이 합처질경우 애니메이션 및 값 설정 func mergeTilesAtindex(x: Int, y: Int, withTileAtIndex otherX: Int, y otherY: Int){ let mergedTile = gridArray[x][y]! let otherTile = gridArray[otherX][otherY]! score += mergedTile.value + otherTile.value gridArray[x][y] = noTile var otherTilePostion = positionForColumn(otherX, row: otherY) let moveTo = CCActionMoveTo(duration: 0.2, position: otherTilePostion) let remove = CCActionRemove() let mergeTile = CCActionCallBlock(block: { () -> Void in otherTile.value *= 2 }) var checkWin = CCActionCallBlock(block: { () -> Void in if otherTile.value == self.winTile {self.win()} }) let sequence = CCActionSequence(array: [moveTo, mergeTile, checkWin, remove]) mergedTile.runAction(sequence) otherTile.mergedThisRound = true } // 다음라운드 메소드 func nextRound() { // 랜덤으로 하나의 타일 생성 spawnRandomTile() // 모든 타일 합처젔는지 체크 변수 초기화 for column in gridArray { for tile in column { tile?.mergedThisRound = false } } // 게임오버 여부 체크. if !movePossible(){ lose() } } // 현재 상태에서 이동이 더 가능한지 체크. 전체 타일에 대해 이웃한 같은값을 가진 타일이 있는지 체크 func movePossible() -> Bool { for i in 0..<gridSize { for j in 0..<gridSize { if let tile = gridArray[i][j]{ let topNeighbor = tileForIndex(i, y: j+1) let bottomNeightbor = tileForIndex(i, y: j-1) let leftNeighbor = tileForIndex(i-1, y: j) let rightNeighbor = tileForIndex(i+1, y: j) let neighbors = [topNeighbor, bottomNeightbor, leftNeighbor, rightNeighbor] for neigbor in neighbors { if let neighborTile = neigbor{ if neighborTile.value == tile.value{ return true } } } } else { return true } } } return false } // 인덱스에 타일이 있는지 여부 func tileForIndex(x: Int, y: Int) -> Tile? { return indexValid(x, y: y) ? gridArray[x][y] : noTile } // 게임오버 func lose(){ endGameWithMessage("You Lose") } // 게임승리 func win(){ endGameWithMessage("You Win!") } // 게임오버 창 띄우기. func endGameWithMessage(message: String) { let defaults = NSUserDefaults.standardUserDefaults() let highscore = defaults.integerForKey("highscore") if score > highscore { defaults.setInteger(score, forKey: "highscore") defaults.synchronize() } let gameEndPopover = CCBReader.load("GameEnd") as! GameEnd gameEndPopover.positionType = CCPositionType(xUnit: .Normalized, yUnit: .Normalized, corner: .BottomLeft) gameEndPopover.position = ccp( 0.5, 0.5 ) gameEndPopover.zOrder = Int.max gameEndPopover.setMessage(message, score: score) addChild(gameEndPopover) } } | cs |
거의 메인이라고 할 수 있는 Grid입니다. 생각보다 타일 움직이는 로직이 복잡합니다. 주석을 잘 읽으시고 코드를 한번씩 따라가 보셔야지 이해 할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import Foundation class MainScene: CCNode { weak var scoreLabel: CCLabelTTF! weak var highscoreLabel: CCLabelTTF! weak var grid: Grid! // 최고점수를 체크하기위한 메소드. 유저 공통 저장공간에 저장한다. func updateHighscore() { let newHighscore = NSUserDefaults.standardUserDefaults().integerForKey("highscore") highscoreLabel.string = "\(newHighscore)" } func didLoadFromCCB() { NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "highscore", options: [], context: nil) // 시작시 최고점수를 공통변수에서 꺼내온다. updateHighscore() } // 최고점수 값이 없데이트 되면 변경해준다. override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if keyPath == "highscore" { updateHighscore() } } } | cs |
메인.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // // GameEnd.swift // cholol2048 // // Created by Cho on 2016. 6. 5.. // Copyright © 2016년 Apportable. All rights reserved. // import Foundation class GameEnd: CCNode{ weak var messageLabel: CCLabelTTF! weak var scoreLabel: CCLabelTTF! // 재시작 콜백 메소드 func restart(){ let mainScene = CCBReader.loadAsScene("MainScene") CCDirector.sharedDirector().presentScene(mainScene) } // 팝업에 메시지 띄우기 func setMessage(message: String, score: Int){ messageLabel.string = message scoreLabel.string = "\(score)" } } | cs |
게임 오버.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // // Tile.swift // cholol2048 // // Created by Cho on 2016. 6. 5.. // Copyright © 2016년 Apportable. All rights reserved. // import Foundation class Tile: CCNode { weak var backgroundNode: CCNodeColor! weak var valueLabel: CCLabelTTF! // SpriteBuilder에서 valueNode로 했었는데 valueLabel로 수정하세요~! // 이번 라운드에 합처진 타일인지 체크 var mergedThisRound = false // value 초기값 0으로 지정. var value: Int = 0 { // 값이 변경될 때마다 자동으로 실행 didSet{ valueLabel.string = "\(value)" } } func didLoadFromCCB() { // 타일이 생성될 때마다 2 or 4 값으로 설정 value = Int(CCRANDOM_MINUS1_1() + 2) * 2 } } | cs |
타일.
총 4개의 소스가 있습니다.
완성된 화면은 아래와 같습니다.
퍼즐게임이다 보니 아무래도 로직이 복잡합니다.
병합되는지 체크하는 부분이나 타일이 이동되는 부분은 천천히 시간을 갖고 보시길 바랍니다.~
'Study > Cocos2d-x 3.x' 카테고리의 다른 글
[cocos2d-x] SpriteBuilder와 Swift이용한 게임 만들기 (preeved penguins 프로젝트) (0) | 2016.06.01 |
---|---|
[cocos2d-x] 간단한 게임 만들기 (4) (0) | 2016.04.23 |
[cocos2d-x] 간단한 게임 만들기 (3) (0) | 2016.04.16 |
[cocos2d-x] 간단한 게임 만들기 (2) (0) | 2016.04.10 |
[cocos2d-x] 간단한 게임 만들기 (1) (0) | 2016.04.10 |