배경 이미지를 적용했다면 이제 귀여운 고양이 얼굴 이미지를 퍼즐로 넣어줄 차례다. 레이아웃을 동적
으로 넣는 방법을 공부하면서 작업했다.
고양이 얼굴 이미지가 그리드 형태로 여러개 들어가기 때문에, 커스텀 노드 클래스를 만들어 재활용하는 것이 좋아보인다. 배경 이미지 노드와 마찬가지로 SKSpriteNode
로 만든다.
class Item: SKSpriteNode {
var column: Int // 열
var row: Int // 행
// 생성자를 통해 고양이 이미지 이름과 행, 열을 주입한다.
init(imageNamed: String, column: Int, row: Int) {
self.column = column
self.row = row
// SKTexture를 통해 이미지를 렌더링한다.
super.init(texture: SKTexture(imageNamed: imageNamed), color: .clear, size: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
코드를 보면 노드 생성 시 행, 열, 이미지 이름을 주입하도록 되어있다. size
는 화면 크기에 따라 다르게 지정할 것이기 때문에 zero
로 기본값을 해두었다.
day.01에서 GameScene
클래스를 만들고 그 안에 배경 이미지 노드를 생성해주었는데, 고양이 그리드도 그렇게 할 것이다. scene
의 사이즈가 곧 화면 사이즈기 때문에 그 값을 활용한다.
내가 보면서 따라했다는 유튜브 영상에서는 itemSize
를 정적인 값을 가진 저장 프로퍼티로 선언했지만, 나는 동적인 값을 위해 계산 프로퍼티를 활용했다.
class GameScene: SKScene {
// 8x8개의 고양이 노드를 만들 것
private let itemsPerColumn: Int = 8
private let itemsPerRow: Int = 8
// 고양이 노드 크기 계산 프로퍼티
private var itemSize: CGFloat {
// 가로, 세로 화면 크기의 1/8 구하기
let horizontalGridSize = size.width / CGFloat(itemsPerRow)
let verticalGridSize = size.height / CGFloat(itemsPerColumn)
// 둘 중 작은 값의 95%로 축소 >> 너무 꽉차면 안예쁘기 때문에 적당한 공백을 주기 위함
return min(horizontalGridSize, verticalGridSize) * 0.95
}
// ... //
}
UIKit
을 공부할 때 레이아웃의 기본요소 2가지를 강조했던 게 생각난다. 바로 크기와 위치
다. 크기는 위에서 지정해주었으니, 이제 위치를 어떻게 잡을지 생각해야 한다.
day.01
에서 언급했듯이 노드의 기본 앵커포인트가 스스로의 중심점이기 때문이다.class GameScene: SKScene {
// ... //
// X축 시작점 (화면의 시작점인 leading과 첫 노드 간 간격)
private var gridStartX: CGFloat {
// 그리드 영역 전체의 너비 : 노드 크기 * 노드 개수
let gridWidth = itemSize * CGFloat(itemsPerRow)
// 화면 너비에서 그리드 영역을 뺀 부분
let spareWidth = size.width - gridWidth
// 화면 전체에서 그리드 영역을 빼준 뒤 반을 나누고 노드의 절반 크기만큼 더해준다
return (spareWidth / 2) + (itemSize / 2)
}
// Y축 시작점 (화면의 시작점인 bottom과 첫 노드 간 간격)
private var gridStartY: CGFloat {
let gridHeight = itemSize * CGFloat(itemsPerColumn)
let spareHeight = size.height - gridHeight
return (spareHeight / 2) + (itemSize / 2)
}
// ... //
}
CGPoint가 (0, 0)일 때 노드 위치
CGPoint가 (gridStartX, gridStartY)일 때 노드 위치
CGPoint
를 반환하는 메소드를 정의한다. (ex. 0행 0열이면 당연히 시작점과 일치하는 자리에 위치할 것이고, 1행 1열이면 시작점으로부터 노드 1개 크기만큼 x, y축을 이동하여 위치할 것이다)class GameScene: SKScene {
// ... //
private func positionItem(for item: Item) -> CGPoint {
// 시작점 + 코드 크기 * 행 or 열
let x: CGFloat = gridStartX + (itemSize * CGFloat(item.column))
let y: CGFloat = gridStartY + (itemSize * CGFloat(item.row))
return CGPoint(x: x, y: y)
}
}
class GameScene: SKScene {
// ... //
// 노드 생성 메소드
private func createItem(column: Int, row: Int) -> Item {
let itemImages = ["calico", "cheese", "gray", "mackerel", "vanila"]
// 0 ~ 4 사이의 랜덤 index로 1개의 이미지 이름 추출
let itemImage = itemImages[GKRandomSource.sharedRandom().nextInt(upperBound: itemImages.count)]
// 추출된 이미지 + 매개변수로 주입 받은 행, 열의 노드 생성
let item = Item(imageNamed: itemImage, column: column, row: row)
// position 및 size 지정해준 뒤 scene에 추가
item.position = positionItem(for: item)
item.size = CGSize(width: itemSize, height: itemSize)
addChild(item)
return item
}
// 열 x 행 노드(Item) 배열
private var columns: [[Item]] = []
// 반복문으로 노드 생성 메소드 호출
private func createGrid() {
for x in 0..<itemsPerRow {
var column = [Item]()
for y in 0..<itemsPerColumn {
let item = createItem(column: x, row: y)
column.append(item)
}
columns.append(column)
}
}
override func didMove(to view: SKView) {
// ... //
createGrid()
}
}
기기에 따라 귀여운 고양이 그리드에 오토레이아웃이 잘 적용된 모습