귀여운 고양이 그리드를 만들었으니 이제는 고양이를 누르면 주변의 같은 고양이들과 함께 사라지도록 구현해볼 것이다.
SKScene
이 SKNode
의 하위 클래스인 SKEffectNode
의 하위 클래스라는 것을 day.01에서 언급했었다. 즉, SKNode > SKEffectNode > SKScene
의 상속 관계가 성립된다.
여기서 끝이 아니다. SKNode
는 SKView
의 하위 클래스고, SKView
는 UIView
를 상속 받는다. 그리고, UIView
의 상위 클래스는 UIResponder
다.
UIResponder > UIView > SKView > SKNode > SKEffectNode > SKScene
이런 상속 구조가 되는 것이다. 이걸 언급하는 이유는 UIResponder
의 메소드 중 하나인 touchesBegan
을 GameScene
에서 활용하기 때문이다.
https://developer.apple.com/documentation/uikit/uiresponder/touchesbegan(_:with:)
touchesBegan
은 위에 쓰여있듯이 하나 이상의 터치 이벤트가 발생했을 경우 호출되는 메소드다. 사용자가 고양이를 눌렀을 때의 동작을 이 메소드 내에서 호출하면 된다.
class GameScene: SKScene {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 터치된 지점 가져오기 : UITouch
// 고양이 게임에서는 터치 영역에 하나의 노드만 있기 때문에 첫번째 값 추출
guard let touch = touches.first else { return }
// 터치된 지점 좌표 가져오기 : CGPoint
let location = touch.location(in: self)
// ... //
}
}
UITouch
클래스의 location(in: )
메소드를 통해 터치된 지점의 CGPoint
를 반환 받아 이 값을 활용할 것이다.
SKNode
클래스의 nodes(at: )
메소드는 CGPoint
타입의 매개변수를 받아 해당 좌표에 위치한 노드들([SKNode]
)을 반환한다.
func findItem(point: CGPoint) -> Item? {
// point에 있는 노드(SKNode)를 Item으로 캐스팅하여 반환
let item = nodes(at: point).compactMap { $0 as? Item }
return item.first
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
// 터치된 지점에 해당하는 Item 추출
guard let tappedItem = findItem(point: location) else { return }
// ... tappedItem이랑 똑같은 고양이 찾기 ... //
}
눌린 고양이 주변 똑같은 고양이들을 찾는 과정을 생각해보자.
재귀 호출을 통해 반복하며, 이 과정에서 이미 검사한 고양이라면 다시 검사하지 않도록 해야 한다.
var matchedItems = Set<Item>() // 똑같은 고양이로 확인 완료된 item 모아넣을 값
func findMatch(currentItem: Item) {
// 확인 완료 집합에 현재 고양이 삽입
matchedItems.insert(currentItem)
// 검사 대상 고양이 담을 값
var checkItems = [Item?]()
// 좌표로 상하좌우 고양이 찾아서 checkItems에 추가
let position = currentItem.position
checkItems.append(findItem(point: CGPoint(x: position.x, y: position.y - itemSize)))
checkItems.append(findItem(point: CGPoint(x: position.x, y: position.y + itemSize)))
checkItems.append(findItem(point: CGPoint(x: position.x - itemSize, y: position.y)))
checkItems.append(findItem(point: CGPoint(x: position.x + itemSize, y: position.y)))
// checkItems에서 nil이 아닌 값들만 반복
for case let check? in checkItems {
// 확인 완료된 고양이는 패스
if matchedItems.contains(check) {
continue
}
// 똑같은 고양이 찾으면 그 고양이 인접 고양이 검사 시작(재귀 호출)
if check.name == currentItem.name {
findMatch(currentItem: check)
}
}
}
SKNode
의 removeFromParent()
메소드를 활용한다.
func removeMatches() {
// 검사 결과 고양이 노드가 1개라면 없애지 않는다
guard matchedItems.count > 1 else { return }
for item in matchedItems {
item.removeFromParent()
}
}
touchesBegan
메소드에서 정의한 함수들을 호출해준다.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
guard let tappedItem = findItem(point: location) else { return }
// 기존 검사 내용 삭제
matchedItems.removeAll()
// 똑같은 고양이 찾기
findMatch(currentItem: tappedItem)
// 찾은 고양이 노드들을 씬에서 제거
removeMatches()
}
다음에는 없어진 고양이 수만큼 새로운 노드들을 생성하고, 위에서부터 아래로 빈자리를 채우는 것을 구현할 것이다.
한 세 수 배워갑니다