[SpriteKit] 고양이 퍼즐 게임 기능추가 chapter.02

Emily·2025년 6월 15일
0

GridPopGame

목록 보기
7/8

고양이 게임에 배경음악과 효과음을 넣어볼 것이다. iOS 프로젝트에 오디오 기능을 구현하기 위한 프레임워크로 AVFoundation을 제일 먼저 떠올리기 쉽지만, SpriteKit만으로도 간단한 오디오 재생 구현이 가능하다. 바로 오디오 노드(SKAudioNode)를 통해서다. SpriteKit을 접해보거나 내 고양이 퍼즐 게임 시리즈를 읽어본 사람이라면 노드가 게임의 구성요소 단위 정도라는 건 알 것이다. 오디오 asset 또한 노드로써 추가할 수 있다.

출처 - https://developer.apple.com/documentation/spritekit/skaudionode/

SKAudioNode는 씬에 오디오를 추가할 수 있게 하는 객체다. 이 객체는 AVFoundation을 통해 자동으로 소리를 재생하며, 3D 공간 오디오 효과를 추가적으로 적용할 수도 있다. 표시 중인 씬 객체는 AVAudio3DMixing 프로토콜에 정의된 파라미터를 기반으로 씬에 있는 노드들과 오디오를 믹싱할 수 있다. 씬의 audioEngine 프로퍼티는 볼륨과 재생을 제어할 수 있다.

기본적으로 오디오 노드는 (isPositional 프로퍼티를 true로 할 경우)위치 기반이다. 씬에 listener가 설정된 상태에서 오디오 노드를 추가할 경우 SpriteKit은 두 노드의 상대적인 위치를 기반으로 스테레오 밸런스와 볼륨을 자동으로 조정한다.

오디오 노드에 action을 실행함으로써 스테레오 밸런스와 볼륨을 설정할 수 있다.

(뒤에 자세한 내용 생략 - 이 이상의 섬세한 동작이 필요할 때 다시 찾아볼 것)

중요한 결론은 SKAudioNode를 사용하면 내장된 AVFoundation의 일부 기능을 통해 씬에서 오디오를 처리할 수 있는 것이다.

오디오 노드를 활용하기로 결정했으니, 오디오 소스를 준비하자.

배경음악 출처 - https://freetouse.com/music/moavii/foreign
효과음 출처 - https://pixabay.com/ko/users/slodkabonanza-43033281/?utm_source=link-attribution&utm_medium=referral&utm_campaign=music&utm_content=197846

저작권 걱정 없는 무료 음악 사이트에서 고양이 퍼즐에 어울리는 경쾌한 배경음악과 퍼즐 pop 효과음을 골라 Xcode 프로젝트에 넣었다.

01) 배경음악 넣기

  1. 씬에 오디오 노드를 추가해주었다.
class GameScene: SKScene {
	// ... //
    
    private let backgroundMusic: SKAudioNode = {
    	// 파일 이름으로 오디오 노드 생성
        let node = SKAudioNode(fileNamed: Assets.backgroundMusic.rawValue)
        
        node.isPositional = false	// 위치 기반 사운드 필요 없음
        node.autoplayLooped = true	// 자동으로 재생 시작 + 반복 재생
        
        return node
    }()
    
    // ... //
    
    private func addAudioNodes() {
        addChild(backgroundMusic)
    }
    
    override func didMove(to view: SKView) {
    	// ... //
        
        addBackgroundMusic()
    }
}

autoplayLoopedtrue기 때문에, addChild를 해주는 순간 오디오는 재생을 시작한다.

  1. musicButton의 토글에 따라 음악도 꺼졌다가 켜지도록 구현했다. 커스텀 버튼 노드 클래스에서 콜백할 클로저를 정의해주고 touchesEnded에서 isMusicOn 값 전달과 함께 호출해주었다. 그 다음 씬에서 isMusicOn 값에 따라 음악의 재생과 일시정지가 토글되도록 구현했다.

고양이 노드를 아래로 움직일 때 코드를 보면,

let downAction = SKAction.move(to: positionItem(for: item), duration: 0.3)
item.run(downAction)

SKNode.run(SKAction) 형태로 노드의 액션을 제어하는 것을 볼 수 있다. 오디오 노드도 마찬가지로 run 메소드를 통해 재생 액션을 추가하였다.

class MusicButton: SKSpriteNode {
	private var isMusicOn: Bool = true {
        didSet {
            toggleImage()
        }
    }
    
    // 클로저에 isMusicOn 값을 전달하기 위해 Bool -> Void 타입으로 지정
    var buttonAction: ((Bool) -> Void)?
    
    // ... //
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        setScale(1.0)
        
        isMusicOn.toggle()
        buttonAction?(isMusicOn)	// buttonAction 클로저에 토글 값 전달
    }
}
class GameScene: SKScene {
	// ... //
    
    private func addMusicButton() {
        // ... //
        
        musicButton.buttonAction = { [weak self] isMusicOn in
            isMusicOn
            ? self?.backgroundMusic.run(SKAction.play())
            : self?.backgroundMusic.run(SKAction.pause())
        }
        
        addChild(musicButton)
    }
}
  • 참고로, 오디오 노드 클래스에는 play/pause와 같은 제어 메소드가 없다. 위에 언급했듯이 autoplayLoopedtrue일 때 addChild를 하는 순간 재생이 시작되고 removeFromParent를 하면 멈추기 때문에 이걸 활용할 수도 있지만 이는 정지로 동작하며, 일시정지를 하고 싶다면 SKAction.pause()를 사용해야 한다.
  • 재생을 제어하는 방법은 이외에도 여러가지가 있지만, 이번 프로젝트에서는 섬세한 오디오 설정까진 필요없기 때문에 가장 간단한 방법인 SKAction을 사용했다. (참고 키워드만 언급하자면 AVFAudio, AVAudioPlayerNode, AVAudioPlayer ... )

02) 효과음 넣기

배경음악은 일시정지가 필요하지만 효과음의 경우 고양이를 누른 순간 팝! 소리를 한번 재생하면 된다. autoplayLoopedfalse인 노드를 추가해주고 play() 액션만 추가하면 끝이다.

class GameScene: SKScene {
	private let popSound: SKAudioNode = {
        let node = SKAudioNode(fileNamed: Assets.popSound.rawValue)
        
        node.isPositional = false
        node.autoplayLooped = false		// 자동재생, 반복 off
        
        return node
    }()

	// ... //
    
    private func addAudioNodes() {
        addChild(backgroundMusic)
        addChild(popSound)
    }
    
    // ... //
    
    func removeMatches() {
    	guard matchedItems.count > 1 else { return }
        
        // ... //
        
        popSound.run(SKAction.play())	// 2개 이상의 고양이들이 pop될 때 sound 재생
    }
}

사실 효과음과 같은 단일 재생의 경우 노드 추가 없이 코드 한줄로도 구현이 가능하다.

func removeMatches() {       
    // ... //
    let popSoundAction = SKAction.playSoundFileNamed("popSound.mp3", waitForCompletion: false)
    run(popSoundAction)
}

써놓고 보니 두줄인데 하여튼 기분 상 한줄 수준인 코드다. 정말 간편하지 않은가? 이걸 보고 SKAction의 공식문서에 들어가서 어떤 액션들이 있는지 구경하면 정말 흥미로울 거 같단 생각이 들었다.

03) 볼륨 조절하기

원래 더 공부하기 귀찮아서 이번에는 볼륨을 다루고 싶지 않았는데, 막상 오디오 노드를 추가하고 나니 배경음악이 효과음에 비해 커서 조절하고 싶어졌다. 결국 어쩔 수 없이 좀더 찾아보게 되었다.

우선 맨 위에서 정리한 SKAudioNode의 공식문서 내용 중에

씬의 audioEngine 프로퍼티는 볼륨과 재생을 제어할 수 있다.

는 내용이 있다. 그리고 그에 해당하는 코드를 사용해보았다.

scene?.audioEngine.mainMixerNode.outputVolume = 0.7

이 코드로 씬이 포함하고 있는 오디오 전체의 볼륨을 조절할 수 있었다. (+ 참고로, AVAudioEngine타입인 오디오 엔진은 SKScene의 프로퍼티라고 해도 AVFAudio 프레임워크를 import 해야만 접근할 수 있다.)

다만 노드 개별적으로 조절하고 싶다면 다른 방법을 써야 한다. AVAudioPlayerNode를 활용하는 방법이다. (이게 유일한 방법은 아니다. 제일 접근이 쉽고 편한 방법 하나만 소개하는 것이다)

SKAudioNode에는 avAudioNode 프로퍼티가 있는데, 얘는 AVAudioNode 타입이다. 그런데 AVAudioNode 클래스의 속성으로 음원을 제어할 수 있는 건 아니고, AVAudioPlayerNode로 타입 캐스팅 해줘야 한다. (왜 이런지는 모르겠다. 애초에 그냥 AVAudioPlayerNode 타입의 프로퍼티를 SKAudioNode에 넣어줄 수는 없었을까? 깊게 공부하다보면 분명히 합리적인 이유가 있겠지만 아직 거기까지 파볼 건 아니다) 그렇게 생성된 플레이어 노드의 프로퍼티인 volume을 통해 노드의 볼륨을 제어할 수 있다.

if let playerNode = backgroundMusic.avAudioNode as? AVAudioPlayerNode {
	playerNode.volume = 0.7
}

배경음악 노드의 플레이어 노드를 통해 볼륨을 70%로 줄여주었다. 그러고나니 효과음과 균형이 잘 맞았다.

그리고 한가지 더 실험을 해봤는데, scene.audioEngine으로 조절한 볼륨이 playerNode로 조절한 볼륨보다 우선순위가 높다. 오디오 엔진이라는 이름만 들어도 더 상위에서 제어할 거 같은 느낌이 들어 자연스럽게 추론이 되는 부분이긴 하다.

04) 번외

나는 코드에 대한 조언을 ChatGPT 보다는 Claude를 통해 구하는 편인데, 얘네가 잘못 알려줄 때마다 어이가 없다.

  1. 오디오 노드의 볼륨을 개별적으로 관리할 수 있냐는 질문에 당당하게 SKAudioNode.volume 프로퍼티에 접근하라는 조언을 해줘서 어이가 없었다.
  1. 저 코드를 복붙하면 당연히 컴파일 에러를 만나게 된다.
  1. 없다고 알려주니까 사과를 하더니 다시 여러가지 방법을 소개해줬는데, 여기에도 엉터리 내용이 포함되어 있었다 - 심지어 '키포인트'에다가 AVAudioPlayerNodevolume 프로퍼티가 없단다. (있음. 내 어이는 여전히 없음)
  1. 또 사과 받음

로드야 너가 날 가르쳐줘야지 내가 알려주는 게 맞냐.. 이래서 한번의 질문과 답으로 AI가 주는 정보를 그대로 받아들이면 안된다. 교차검증 필수.

다만 이렇게 옥신각신 하는 과정에서도 유익한 정보를 주워먹기도 한다.

노드마다 볼륨을 다르게 설정할 수 있냐는 첫번째 질문에 대한 여러가지 제안 중 하나로 나온 부분인데, SKActionchangeVolume 메소드를 통해 볼륨을 서서히 줄이거나 높이는 효과를 넣을 수 있다는 것을 알았다. 저 코드는 실험 결과 엉터리가 아니라 아주 잘 작동한다. 나중에 음원에 다양한 효과를 넣고 싶을 때 떠올려서 유용하게 쓸 수 있을 거 같다.


여기까지 오디오 효과를 넣으면서 배운 내용들을 정리해봤다. 기기로 시뮬레이팅한 화면 녹화 영상에서 소리를 들을 수 있다.

profile
iOS Junior Developer

0개의 댓글