Swift 3에서 SpriteKit 게임을 빌드하는 방법(3부)
게시 됨: 2022-03-10SpriteKit 게임을 만드는 데 무엇이 필요한지 생각해 본 적이 있습니까? 버튼이 생각보다 큰 작업처럼 보입니까? 게임에서 설정을 유지하는 방법이 궁금하신가요? SpriteKit이 도입된 이후 iOS에서 게임 제작이 그 어느 때보다 쉬워졌습니다. 이 3부작 시리즈의 3부에서는 RainCat 게임을 끝내고 SpriteKit에 대한 소개를 완료합니다.
이전 강의를 놓쳤다면 GitHub에서 코드를 받아 따라잡을 수 있습니다. 이 자습서에는 Xcode 8 및 Swift 3이 필요합니다.
SmashingMag에 대한 추가 정보: 링크
- 게임화와 UX: 사용자의 승패
- 재미있는 UX 디자인: 더 나은 게임 만들기
- UX 디자인과 심리학을 결합하여 사용자 행동 변화

이것은 RainCat 여정의 세 번째 교훈입니다. 이전 수업에서 우리는 간단한 애니메이션, 고양이 행동, 빠른 음향 효과 및 배경 음악을 통해 긴 하루를 보냈습니다.
오늘 우리는 다음에 초점을 맞출 것입니다:
- 득점을 위한 헤드업 디스플레이(HUD);
- 메인 메뉴 - 버튼 포함;
- 음소거 옵션;
- 게임 종료 옵션.
더 많은 자산
마지막 수업의 자산은 GitHub에서 사용할 수 있습니다. 이전 단원에서 했던 것처럼 이미지를 Assets.xcassets
로 다시 드래그합니다.
머리 위로!
점수를 유지하는 방법이 필요합니다. 이를 위해 헤드업 디스플레이(HUD)를 만들 수 있습니다. 이것은 매우 간단합니다. 점수와 게임 종료 버튼이 포함된 SKNode
가 됩니다. 지금은 점수에만 집중하겠습니다. 우리가 사용할 글꼴은 Dafont.com에서 얻을 수 있는 Pixel Digivolve입니다. 자신의 것이 아닌 이미지나 사운드를 사용할 때와 마찬가지로 글꼴을 사용하기 전에 해당 글꼴의 라이선스를 읽으십시오. 이 글은 개인적인 사용은 무료라고 명시되어 있지만, 정말 마음에 드는 글꼴이 있다면 페이지에서 작가에게 기부할 수 있습니다. 항상 모든 것을 스스로 만들 수는 없으므로 도움을 준 사람들에게 보답하는 것이 좋습니다.
다음으로 프로젝트에 사용자 정의 글꼴을 추가해야 합니다. 이 과정은 처음에는 까다로울 수 있습니다.
글꼴을 다운로드하여 "Fonts" 폴더 아래의 프로젝트 폴더로 이동합니다. 이전 단원에서 이 작업을 몇 번 수행했으므로 이 프로세스를 조금 더 빠르게 진행합니다. 프로젝트에 Fonts
라는 그룹을 추가하고 Pixel digivolve.otf
파일을 추가합니다.
이제 까다로운 부분이 나옵니다. 이 부분을 놓치면 아마 글꼴을 사용하지 못할 것입니다. Info.plist
파일에 추가해야 합니다. 이 파일은 Xcode의 왼쪽 창에 있습니다. 클릭하면 속성 목록(또는 plist
)이 표시됩니다. 목록을 마우스 오른쪽 버튼으로 클릭하고 "행 추가"를 클릭합니다.

plist
에 행을 추가합니다.새 행이 나타나면 다음을 입력하십시오.
Fonts provided by application
그런 다음 Item 0
아래에 글꼴 이름을 추가해야 합니다. plist
는 다음과 같아야 합니다.

plist
에 성공적으로 추가되었습니다! 글꼴을 사용할 준비가 되어 있어야 합니다! 의도한 대로 작동하는지 확인하기 위해 빠른 테스트를 수행해야 합니다. GameScene.swift
로 이동하고 sceneDidLoad
에서 함수 상단에 다음 코드를 추가합니다.
let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)
작동합니까?

SKLabelNode
테스트. 안 돼! "Hello World" 레이블이 돌아왔습니다.작동하면 모든 작업을 올바르게 수행한 것입니다. 그렇지 않다면 뭔가 잘못된 것입니다. Code With Chris에는 더 심층적인 문제 해결 가이드가 있지만 이전 버전의 Swift를 위한 것이므로 Swift 3으로 가져오려면 약간의 조정을 해야 합니다.
이제 사용자 정의 글꼴을 로드할 수 있으므로 HUD에서 시작할 수 있습니다. "Hello World" 레이블을 삭제합니다. 글꼴 로드를 확인하기 위해서만 사용했기 때문입니다. HUD는 SKNode
가 될 것이며 HUD 요소의 컨테이너 역할을 합니다. 이것은 1과에서 배경 노드를 생성할 때 따랐던 것과 동일한 프로세스입니다.
일반적인 방법을 사용하여 HudNode.swift
파일을 만들고 다음 코드를 입력합니다.
import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }
다른 작업을 수행하기 전에 Constants.swift
를 열고 파일 맨 아래에 다음 줄을 추가합니다. 이 줄을 사용하여 최고 점수를 검색하고 유지합니다.
let ScoreKey = "RAINCAT_HIGHSCORE"
코드에는 스코어보드와 관련된 5개의 변수가 있습니다. 첫 번째 변수는 레이블을 표시하는 데 사용하는 실제 SKLabelNode
입니다. 다음은 현재 점수를 유지하기 위한 변수입니다. 그런 다음 최고 점수를 보유하는 변수입니다. 마지막 변수는 현재 최고 점수를 표시하고 있는지 여부를 알려주는 부울입니다(이를 사용하여 스코어보드의 규모를 늘리고 바닥의 노란색으로 색칠하기 위해 SKAction
을 실행해야 하는지 여부를 설정합니다).
첫 번째 함수인 setup(size:)
은 모든 것을 설정하기 위한 것입니다. 이전과 같은 방식으로 SKLabelNode
를 설정했습니다. SKNode
클래스에는 기본적으로 크기 속성이 scoreNode
레이블을 배치할 크기를 설정하는 방법을 만들어야 합니다. 또한 UserDefaults
에서 현재 최고 점수를 가져옵니다. 이것은 작은 데이터 청크를 저장하는 빠르고 쉬운 방법이지만 안전하지 않습니다. 이 예제에서는 보안에 대해 걱정하지 않기 때문에 UserDefaults
는 완벽합니다.
addPoint()
에서 현재 score
변수를 증가시키고 사용자가 높은 점수를 얻었는지 확인합니다. 점수가 높으면 해당 점수를 UserDefaults
에 저장하고 현재 최고 점수를 표시하고 있는지 확인합니다. 사용자가 높은 점수를 얻은 경우 scoreNode
의 크기와 색상을 애니메이션할 수 있습니다.
resetPoints()
함수에서 현재 점수를 0
으로 설정합니다. 그런 다음 높은 점수를 표시하고 있는지 확인하고 필요한 경우 크기와 색상을 기본값으로 재설정해야 합니다.
마지막으로 updateScoreboard
라는 작은 함수가 있습니다. scoreNode
의 텍스트에 점수를 설정하는 내부 함수입니다. 이것은 addPoint()
및 resetPoints()
) 모두에서 호출됩니다.
HUD 연결하기
HUD가 제대로 작동하는지 테스트해야 합니다. GameScene.swift
로 이동하고 파일 상단의 foodNode
변수 아래에 다음 줄을 추가합니다.
private let hudNode = HudNode()
sceneDidLoad()
함수의 상단 근처에 다음 두 줄을 추가합니다.
hudNode.setup(size: size) addChild(hudNode)
그런 다음, spawnCat()
함수에서 고양이가 화면에서 떨어진 경우 포인트를 재설정합니다. 장면에 고양이 스프라이트를 추가한 후 다음 줄을 추가합니다.
hudNode.resetPoints()
다음으로, handleCatCollision(contact:)
함수에서 고양이가 비를 맞았을 때 점수를 다시 재설정해야 합니다. 다른 바디가 RainDropCategory
인 경우 함수 끝에 있는 switch
문에서 다음 줄을 추가합니다.
hudNode.resetPoints()
마지막으로 사용자가 포인트를 얻었을 때 스코어보드에 알려야 합니다. handleFoodHit(contact:)
의 파일 끝에서 여기까지 다음 행을 찾으십시오.
//TODO increment points print("fed cat")
그리고 다음과 같이 교체하십시오.
hudNode.addPoint()
짜잔!

HUD가 작동하는 것을 확인해야 합니다. 이리저리 뛰어다니며 음식을 모으세요. 처음으로 음식을 수집할 때 점수가 노란색으로 바뀌고 규모가 커지는 것을 볼 수 있습니다. 이런 일이 발생하면 고양이를 치게하십시오. 점수가 재설정되면 올바른 길을 가고 있다는 것을 알게 될 것입니다!

다음 장면
맞습니다, 우리는 다른 장면으로 이동합니다! 실제로 완료되면 이것이 우리 앱의 첫 번째 화면이 됩니다. 다른 작업을 수행하기 전에 Constants.swift
를 열고 파일 맨 아래에 다음 줄을 추가합니다. 이 줄을 사용하여 최고 점수를 검색하고 유지합니다.
let ScoreKey = "RAINCAT_HIGHSCORE"
새 장면을 만들고 "Scenes" 폴더 아래에 놓고 MenuScene.swift
라고 합니다. MenuScene.swift
파일에 다음 코드를 입력하세요:
import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }
이 장면은 비교적 간단하기 때문에 특별한 클래스를 만들지 않습니다. 우리 장면은 두 개의 버튼으로 구성됩니다. 이것들은 SKSpriteNodes
의 자체 클래스일 수 있고(그럴 자격이 있을 수 있음) 충분히 다르기 때문에 새 클래스를 만들 필요가 없습니다. 이것은 자신의 게임을 빌드할 때 중요한 팁입니다. 일이 복잡해지면 중지하고 코드를 리팩터링할 위치를 결정할 수 있어야 합니다. 게임에 3~4개 이상의 버튼을 추가했다면 메뉴 버튼의 코드를 중지하고 자체 클래스로 리팩토링해야 할 때입니다.
위의 코드는 특별한 작업을 수행하지 않습니다. 4개의 스프라이트 위치를 설정하고 있습니다. 또한 전체 배경이 올바른 값이 되도록 장면의 배경색을 설정하고 있습니다. Xcode용 HEX 문자열에서 색상 코드를 생성하는 좋은 도구는 UI 색상입니다. 위의 코드는 버튼 상태에 대한 텍스처도 설정하고 있습니다. 게임을 시작하는 버튼은 일반 상태와 눌린 상태가 있지만 사운드 버튼은 토글입니다. 토글을 단순화하기 위해 사용자가 누를 때 사운드 버튼의 알파 값을 변경합니다. 또한 고득점 SKLabelNode
를 가져와서 설정하고 있습니다.
우리의 MenuScene
은 꽤 좋아 보입니다. 이제 앱이 로드될 때 장면을 표시해야 합니다. GameViewController.swift
로 이동하여 다음 라인을 찾으세요.
let sceneNode = GameScene(size: view.frame.size)
다음으로 교체하십시오.
let sceneNode = MenuScene(size: view.frame.size)
이 작은 변경은 기본적으로 GameScene
대신 MenuScene
을 로드합니다.

버튼 상태
버튼은 SpriteKit에서 까다로울 수 있습니다. 많은 타사 옵션을 사용할 수 있지만(직접 만들었습니다) 이론상 다음 세 가지 터치 방법만 알면 됩니다.
-
touchesBegan(_ touches: with event:)
-
touchesMoved(_ touches: with event:)
-
touchesEnded(_ touches: with event:)
우산을 업데이트할 때 이 내용을 간략하게 다루었지만 이제는 어떤 버튼이 터치되었는지, 사용자가 탭을 놓았는지 또는 해당 버튼을 클릭했는지, 사용자가 여전히 터치하고 있는지 여부를 알아야 합니다. 여기에서 selectedButton
변수가 작동합니다. 터치가 시작되면 사용자가 해당 변수로 클릭하기 시작한 버튼을 캡처할 수 있습니다. 버튼 외부로 드래그하면 이를 처리하고 적절한 텍스처를 줄 수 있습니다. 터치에서 손을 떼면 여전히 버튼 내부를 터치하고 있는지 확인할 수 있습니다. 그렇다면 관련 작업을 적용할 수 있습니다. MenuScene.swift
하단에 다음 라인을 추가하세요.
override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }
이것은 두 개의 버튼에 대한 간단한 버튼 처리입니다. touchesBegan(_ touches: with events:)
에서 현재 선택된 버튼이 있는지 확인하는 것으로 시작합니다. 그렇게 하면 버튼의 상태를 누르지 않은 상태로 재설정해야 합니다. 그런 다음 어떤 버튼이 눌렸는지 확인해야 합니다. 하나를 누르면 버튼의 강조 표시된 상태가 표시됩니다. 그런 다음 selectedButton
을 다른 두 메서드에서 사용할 버튼으로 설정합니다.

touchesMoved(_ touches: with events:)
에서 어떤 버튼이 원래 터치되었는지 확인합니다. 그런 다음 현재 터치가 여전히 selectedButton
범위 내에 있는지 확인하고 여기에서 강조 표시된 상태를 업데이트합니다. startButton
의 강조 표시된 상태는 텍스처를 눌린 상태의 텍스처로 변경합니다. 여기서 soundButton
의 강조 표시된 상태는 스프라이트의 알파 값이 50%로 설정됩니다.
마지막으로, touchesEnded(_ touches: with event:)
에서 어떤 버튼이 선택되었는지 다시 확인하고(있는 경우) 터치가 여전히 버튼 범위 내에 있는지 여부를 확인합니다. 모든 경우가 충족되면 올바른 버튼에 대해 handleStartButtonClick handleStartButtonClick()
또는 handleSoundButtonClick()
을 호출합니다.
행동을 위한 시간
이제 기본 버튼 동작이 끝났으므로 클릭할 때 트리거할 이벤트가 필요합니다. 구현하기 쉬운 버튼은 startButton
입니다. 클릭 시 GameScene
만 표시하면 됩니다. MenuScene.swift
함수의 handleStartButtonClick()
을 다음 코드로 업데이트합니다.
func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }
지금 앱을 실행하고 버튼을 누르면 게임이 시작됩니다!
이제 음소거 토글을 구현해야 합니다. 이미 사운드 관리자가 있지만 음소거가 켜져 있는지 꺼져 있는지 여부를 알 수 있어야 합니다. Constants.swift
에서 음소거가 켜져 있을 때 지속할 키를 추가해야 합니다. 다음 줄을 추가합니다.
let MuteKey = "RAINCAT_MUTED"
이것을 사용하여 부울 값을 UserDefaults
에 저장합니다. 이제 설정이 SoundManager.swift
로 이동할 수 있습니다. 여기에서 음소거가 켜져 있는지 여부를 확인하기 위해 UserDefaults
를 확인하고 설정합니다. 파일 상단의 trackPosition
변수 아래에 다음 줄을 추가합니다.
private(set) var isMuted = false
이것은 메인 메뉴(그리고 사운드를 재생하는 모든 것)가 사운드 허용 여부를 결정하기 위해 확인하는 변수입니다. false
로 초기화하지만 이제 사용자가 원하는 것을 보기 위해 UserDefaults
를 확인해야 합니다. init()
함수를 다음으로 바꿉니다.
private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }
이제 isMuted
에 대한 기본값이 있으므로 이를 변경할 수 있는 기능이 필요합니다. SoundManager.swift
하단에 다음 코드를 추가하세요.
func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }
이 메서드는 음소거된 변수를 토글하고 UserDefaults
를 업데이트합니다. 새 값이 음소거되지 않으면 음악 재생이 시작됩니다. 새 값이 음소거되면 재생이 시작되지 않습니다. 그렇지 않으면 현재 트랙의 재생이 중지됩니다. 그런 다음 startPlaying()
에서 if
문을 편집해야 합니다.
다음 줄을 찾습니다.
if audioPlayer == nil || audioPlayer?.isPlaying == false {
그리고 이것을 다음과 같이 바꾸십시오.
if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {
이제 음소거가 꺼져 있고 오디오 플레이어가 설정되어 있지 않거나 현재 오디오 플레이어가 더 이상 재생되지 않는 경우 다음 트랙을 재생합니다.
여기에서 MenuScene.swift
로 돌아가서 음소거 버튼을 완료할 수 있습니다. handleSoundbuttonClick()
을 다음 코드로 바꿉니다.
func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }
이렇게 하면 SoundManager
에서 사운드가 토글되고 결과를 확인한 다음 사운드가 음소거되었는지 여부를 사용자에게 표시하도록 텍스처를 적절하게 설정합니다. 거의 완료되었습니다! 실행 시 버튼의 초기 텍스처만 설정하면 됩니다. sceneDidLoad()
에서 다음 줄을 찾습니다.
soundButton = SKSpriteNode(texture: soundButtonTexture)
그리고 이것을 다음과 같이 바꾸십시오.
soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)
위의 예는 삼항 연산자를 사용하여 올바른 텍스처를 설정합니다.
이제 음악이 연결 CatSprite.swift
로 이동하여 음소거가 켜져 있을 때 고양이 야옹 소리를 비활성화할 수 있습니다. hitByRain()
에서 걷기 동작을 제거한 후 다음 if
문을 추가할 수 있습니다.
if SoundManager.sharedInstance.isMuted { return }
이 문은 사용자가 앱을 음소거했는지 여부를 반환합니다. 이 때문에 currentRainHits
, maxRainHits
및 야옹 소리 효과를 완전히 무시합니다.
그 모든 것이 끝나면 이제 음소거 버튼을 사용해 볼 시간입니다. 앱을 실행하고 제대로 재생되고 있는지 확인하고 소리를 음소거합니다. 소리를 음소거하고 앱을 닫았다가 다시 엽니다. 음소거 설정이 지속되는지 확인하십시오. Xcode에서 앱을 음소거하고 다시 실행하면 UserDefaults
를 저장할 시간이 충분하지 않을 수 있습니다. 게임을 하고 고양이가 음소거 상태일 때 절대 야옹하지 않도록 하세요.
게임 종료
이제 주 메뉴에 대한 첫 번째 유형의 버튼이 있으므로 게임 장면에 종료 버튼을 추가하여 까다로운 작업을 수행할 수 있습니다. 몇 가지 흥미로운 상호작용이 우리의 게임 스타일에 영향을 미칠 수 있습니다. 현재 우산은 사용자가 터치하거나 터치하는 곳으로 이동합니다. 분명히, 사용자가 게임을 종료하려고 할 때 우산이 종료 버튼으로 이동하는 것은 매우 좋지 않은 사용자 경험이므로 이러한 일이 발생하지 않도록 시도할 것입니다.
우리가 구현하고 있는 종료 버튼은 이전에 추가한 게임 시작 버튼을 모방하고 대부분의 프로세스가 동일하게 유지됩니다. 터치를 처리하는 방식이 변경됩니다. quit_button
및 quit_button_pressed
자산을 Assets.xcassets
파일로 가져오고 다음 코드를 HudNode.swift
파일에 추가합니다.
private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")
이것은 버튼 상태에 대해 설정할 텍스처와 함께 quitButton
참조를 처리합니다. 종료를 시도하는 동안 우발적으로 우산을 업데이트하지 않도록 하려면 우산이 아닌 종료 버튼과 상호 작용하고 있음을 HUD(및 게임 장면)에 알려주는 변수가 필요합니다. showingHighScore
부울 변수 아래에 다음 코드를 추가합니다.
private(set) var quitButtonPressed = false
다시 말하지만 이것은 HudNode
만 설정할 수 있지만 다른 클래스는 확인할 수 있는 변수입니다. 이제 변수가 설정되었으므로 버튼을 HUD에 추가할 수 있습니다. setup(size:)
함수에 다음 코드를 추가합니다.
quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)
위의 코드는 누르지 않은 상태의 텍스처로 종료 버튼을 설정합니다. 또한 위치를 오른쪽 상단 모서리로 설정하고 zPosition
을 높은 숫자로 설정하여 항상 맨 위에 그리도록 합니다. 지금 게임을 실행하면 GameScene
에 표시되지만 아직 클릭할 수는 없습니다.

이제 버튼의 위치가 지정되었으므로 버튼과 상호 작용할 수 있어야 합니다. 현재 GameScene
에서 상호 작용하는 유일한 장소는 umbrellaSprite
스프라이트와 상호 작용할 때입니다. 이 예에서 HUD는 우산보다 우선 순위가 높기 때문에 사용자가 나가기 위해 우산을 치우지 않아도 됩니다. GameScene.swift
에서 터치 기능을 모방하기 위해 HudNode.swift
에서 동일한 기능을 생성할 수 있습니다. HudNode.swift
에 다음 코드를 추가하세요.
func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }
위의 코드는 MenuScene
용으로 만든 코드와 매우 유사합니다. 차이점은 추적할 버튼이 하나뿐이므로 이러한 터치 메서드 내에서 모든 것을 처리할 수 있다는 것입니다. 또한 GameScene
에서 터치 위치를 알 수 있기 때문에 버튼에 터치 포인트가 있는지 여부만 확인할 수 있습니다.
GameScene.swift
로 이동하고touchBegan touchesBegan(_ touches with event:)
및 touchesMoved(_ touches: with event:)
메서드를 다음 코드로 바꿉니다.
override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }
여기에서 각 방법은 거의 동일한 방식으로 모든 것을 처리합니다. 사용자가 장면과 상호 작용했음을 HUD에 알려줍니다. 그런 다음 종료 버튼이 현재 터치를 캡처하고 있는지 확인합니다. 그렇지 않은 경우 우산을 이동합니다. 또한 종료 버튼에 대한 클릭 종료를 처리하기 위해 touchesEnded(_ touches: with event:)
함수를 추가했지만 아직까지는 umbrellaSprite
에 사용하지 않습니다.
이제 버튼이 GameScene
에 영향을 줄 방법이 필요합니다. HudeNode.swift
상단에 다음 라인을 추가하세요.
var quitButtonAction : (() -> ())?
이것은 입력과 출력이 없는 일반 클로저입니다. GameScene.swift
파일의 코드로 이것을 설정하고 HudNode.swift
의 버튼을 클릭할 때 호출합니다. 그런 다음 이전에 touchEndedAtPoint(point:)
함수에서 만든 코드의 TODO
를 다음과 같이 바꿀 수 있습니다.
if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }
이제 quitButtonAction
클로저를 설정하면 이 지점에서 호출됩니다.
quitButtonAction
클로저를 설정하려면 GameScene.swift
로 이동해야 합니다. sceneDidLoad()
에서 HUD 설정을 다음 코드로 바꿀 수 있습니다.
hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)
앱을 실행하고 재생을 누른 다음 종료를 누릅니다. 기본 메뉴로 돌아가면 종료 버튼이 의도한 대로 작동하는 것입니다. 우리가 만든 클로저에서 MenuScene
으로의 전환을 초기화했습니다. 그리고 종료 버튼을 클릭했을 때 실행되도록 이 클로저를 HUD
노드로 설정했습니다. 여기서 또 다른 중요한 줄은 quitButtonAction
을 nil
로 설정할 때입니다. 그 이유는 보유 주기가 발생하기 때문입니다. 장면은 HUD가 장면에 대한 참조를 보유하고 있는 HUD에 대한 참조를 보유하고 있습니다. 두 개체에 대한 참조가 있기 때문에 가비지 수집 시간이 되면 둘 다 삭제되지 않습니다. 이 경우 GameScene
에 들어가고 나갈 때마다 다른 인스턴스가 생성되고 절대 해제되지 않습니다. 이것은 성능에 좋지 않으며 결국 앱의 메모리가 부족해집니다. 이를 피하는 방법은 여러 가지가 있지만, 우리의 경우 HUD에서 GameScene
에 대한 참조를 제거할 수 있으며 MenuScene
으로 돌아가면 장면과 HUD가 종료됩니다. Krakendev는 참조 유형과 이러한 주기를 피하는 방법에 대해 자세히 설명합니다.
이제 GameViewController.swift
로 이동하고 다음 세 줄의 코드를 제거하거나 주석 처리합니다.
view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true
디버깅 데이터를 제거하면 게임이 정말 좋아 보입니다! 축하합니다: 현재 베타 단계입니다! 오늘부터 GitHub에서 최종 코드를 확인하세요.
마지막 생각들
이것은 세 부분으로 구성된 튜토리얼의 마지막 레슨이며 여기까지 했다면 게임에서 많은 작업을 수행한 것입니다. 이 튜토리얼에서는 완전히 아무것도 없는 장면에서 완성된 게임으로 이동했습니다. 축하 해요! 레슨 1에서는 바닥, 빗방울, 배경 및 우산 스프라이트를 추가했습니다. 우리는 또한 물리학을 가지고 놀았고 빗방울이 쌓이지 않도록 했습니다. 우리는 충돌 감지로 시작하여 메모리가 부족하지 않도록 노드 컬링 작업을 했습니다. 또한 사용자가 화면에서 터치하는 방향으로 우산을 이동할 수 있도록 하여 사용자 상호 작용을 추가했습니다.
두 번째 강의에서는 고양이와 음식을 추가하고 각각에 대한 사용자 지정 산란 방법을 추가했습니다. 고양이와 음식 스프라이트를 허용하도록 충돌 감지를 업데이트했습니다. 고양이의 움직임도 작업했습니다. 고양이는 목적을 얻었습니다. 가능한 모든 음식을 먹습니다. 고양이를 위한 간단한 애니메이션을 추가하고 고양이와 비 사이의 사용자 지정 상호 작용을 추가했습니다. 마지막으로 사운드 효과와 음악을 추가하여 완전한 게임처럼 느껴지도록 했습니다.
이 마지막 레슨에서 우리는 점수 레이블과 종료 버튼을 유지하는 헤드업 디스플레이를 만들었습니다. 노드 전체에서 작업을 처리하고 사용자가 HUD 노드에서 콜백으로 종료할 수 있도록 했습니다. 또한 사용자가 시작하고 종료 버튼을 클릭한 후 돌아갈 수 있는 또 다른 장면을 추가했습니다. 게임 시작 및 게임 내 사운드 제어 프로세스를 처리했습니다.
여기에서 갈 곳
우리는 여기까지 오기 위해 많은 시간을 투자했지만 이 게임에 들어갈 수 있는 작업은 여전히 많습니다. RainCat은 계속 개발 중이며 App Store에서 사용할 수 있습니다. 아래는 원하는 목록과 추가해야 할 사항입니다. 일부 항목이 추가되었지만 다른 항목은 아직 보류 중입니다.
- 아이콘과 시작 화면을 추가합니다.
- 기본 메뉴를 완성합니다(튜토리얼을 위해 단순화됨).
- 불량 빗방울 및 여러 음식 생성을 포함한 버그를 수정합니다.
- 코드를 리팩토링하고 최적화합니다.
- 점수에 따라 게임의 색상 팔레트를 변경합니다.
- 점수에 따라 난이도를 업데이트합니다.
- 음식이 바로 위에 있을 때 고양이를 움직이세요.
- 게임 센터를 통합합니다.
- 크레딧을 제공합니다(음악 트랙에 대한 적절한 크레딧 포함).
이러한 변경 사항은 향후에 이루어지므로 GitHub에서 추적하세요. 코드에 대해 질문이 있는 경우 [email protected]으로 연락해 주시면 논의할 수 있습니다. 특정 주제가 충분한 관심을 받으면 해당 주제에 대해 다른 기사를 작성할 수 있습니다.
감사 해요!
게임을 만들고 관련 기사를 개발하는 과정에 도움을 주신 모든 분들께 감사드립니다.
- Cathryn Rowe 초기 아트, 디자인 및 편집, 그리고 Garage의 기사 게시.
- Morgan Wheaton 최종 메뉴 디자인 및 색상 팔레트(이 기능을 실제로 구현하면 멋지게 보일 것입니다. 계속 지켜봐 주시기 바랍니다).
- Nikki Clark 기사의 멋진 머리글과 구분선과 기사 편집에 대한 도움.
- Laura Levisay 기사에 있는 모든 멋진 GIF와 도덕적 지원을 위해 귀여운 고양이 GIF를 보내주셔서 감사합니다.
- Tom Hudson 기사 편집에 도움을 주시고 누구 없이는 이 시리즈가 만들어지지 않았을 것입니다.
- Lani DeGuire 많은 작업이 필요한 기사 편집에 도움을 주셔서 감사합니다.
- Jeff Moon 3과 및 탁구 편집에 도움이 필요합니다. 많은 탁구.
- Tom Nelson 튜토리얼이 제대로 작동하는지 확인하는 데 도움을 주셔서 감사합니다.
진지하게, 이 기사를 위한 모든 것을 준비하고 상점에 출시하는 데 많은 사람들이 필요했습니다.
이 문장을 읽어주신 모든 분들께도 감사드립니다.