본문 바로가기
IT/swift

iOS 스프라이트킷 강좌 번역 - SpriteKit Tutorial for Beginners

by 가능성1g 2022. 9. 9.
반응형

간단한 2D게임만들기!

 

NinjaAttackStarter.zip
0.67MB

압축해제 후 오픈합니다.

 

실행하면, 아직 페이지 로딩이 없으니 하얀 공백페이지가 보입니다.

GameViewController.swift 파일의 viewDidLoad 함수의 하단에 아래의 소스를 추가 합니다.

 

let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)

GameScene.swift 파일에 아래 소스를 추가합니다.

  //player 라는 스프라이트 노드 추가
  let player = SKSpriteNode(imageNamed: "player")
    
  override func didMove(to view: SKView) {
    // 하얀색 백그라운드
    backgroundColor = SKColor.white
    // player 위치 설정
    player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
    // player 스프라이트 노드를 씬에 추가
    addChild(player)
  }

실행하면 추가된 플레이어가 보입니다.

이제 오른쪽에서 왼쪽으로 지나가는 몬스터를 생성하겠습니다.

 

GameScene.swift 하단에 아래 소스를 추가합니다.

  func random() -> CGFloat {
    return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
  }

  func random(min: CGFloat, max: CGFloat) -> CGFloat {
    return random() * (max - min) + min
  }

  func addMonster() {
    
    // 몬스터 스프라이트 생성
    let monster = SKSpriteNode(imageNamed: "monster")
    
    // 몬스처 위치(Y)를 랜덤하게 설정
    let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
    
    // 오른쪽 끝에 몬스터 위치 설정
    // Y위치는 위에서 생성한 랜던함 위치
    monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
    
    // 씬에 몬스터 추가
    addChild(monster)
    
    // 몬스터의 이동속도도 랜덤하게 설정
    let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
    
    // 액션 추가(왼쪽으로 이동)
    let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY),
                                   duration: TimeInterval(actualDuration))
    let actionMoveDone = SKAction.removeFromParent()
    monster.run(SKAction.sequence([actionMove, actionMoveDone]))
  }

소스중 arc4random() 에 대한 설명입니다.

https://soooprmx.com/arc4random/

 

난수생성을 위한 조금 더 나은 선택 - arc4random · Wireframe

전통적인 C 함수로 srandom, rand를 이용해서 난수를 생성하는 방법이 있는데, 보다 진보된(?)형태의 함수로 arc4random 함수가 있다. 이 역시 표준 C 라이브러리(libc)에 탑재되어 있다. (GCC 버전 요구사

soooprmx.com

그리고 실제 몬스터를 추가 시키는 소스를 didMove 함수 하단에 추가합니다.

    run(SKAction.repeatForever(
          SKAction.sequence([
            SKAction.run(addMonster),
            SKAction.wait(forDuration: 1.0)
            ])
        ))

몬스터들이 계속 추가되며 지나다닙니다.

이제 화면을 터치했을때 나가는 수리검에 대해서 구현한다.

구현전에 필요한 함수를 위해 4칙 연산자 오버로딩을 쓴다.

GameScene.swift 파일의 위쪽에 아래 소스를 추가한다.

func +(left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}

func -(left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x - right.x, y: left.y - right.y)
}

func *(point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x * scalar, y: point.y * scalar)
}

func /(point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x / scalar, y: point.y / scalar)
}

#if !(arch(x86_64) || arch(arm64))
  func sqrt(a: CGFloat) -> CGFloat {
    return CGFloat(sqrtf(Float(a)))
  }
#endif

extension CGPoint {
  func length() -> CGFloat {
    return sqrt(x*x + y*y)
  }
  
  func normalized() -> CGPoint {
    return self / length()
  }
}

삼각함수를 위한 CGPoint, CGFloat 의 계산을 위해 오버로딩 함수와 길이 구하기, 노멀라이즈드 함수를 추가!

이제 터치할때 발생하는 함수를 오버라이딩 해서 구현을 완료한다.

GameScene 클래스 내부의 하단에 소스를 추가 한다.

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    // 터치정보 가져오기
    guard let touch = touches.first else {
      return
    }
    let touchLocation = touch.location(in: self)
    
    // 발사체==수리검 의 시작위치를 플레이어 위치로 설정한다.
    let projectile = SKSpriteNode(imageNamed: "projectile")
    projectile.position = player.position
    
    // 터치한 위치와 발사체의 거리를 계산
    let offset = touchLocation - projectile.position
    
    // 발사체가 플레이어 뒤면 나가지 않음
    if offset.x < 0 { return }
    
    // 발사체 씬에 추가
    addChild(projectile)
    
    // 어디로 나가는지 방향 계산
    let direction = offset.normalized()
    
    // 화면밖까지 나가는게 이어지도록 멀리 쏘게한다.
    let shootAmount = direction * 1000
    
    // 현재위치에서 도착지까지 더함
    let realDest = shootAmount + projectile.position
    
    // 액션 생성
    let actionMove = SKAction.move(to: realDest, duration: 2.0)
    let actionMoveDone = SKAction.removeFromParent()
    projectile.run(SKAction.sequence([actionMove, actionMoveDone]))
  }

잘나간다!

 

이제 발사체==수리검과 몬스터가 충돌했을때를 체크하고 두 물체를 제거하는것을 구현합니다.

충돌체의 구분을 위해 카타고리 구조체를 선언합니다. GameScene 클래스 위에 추가합니다.

struct PhysicsCategory {
  static let none      : UInt32 = 0
  static let all       : UInt32 = UInt32.max
  static let monster   : UInt32 = 0b1       
  static let projectile: UInt32 = 0b10      
}

클래스 가장 하단에, 물리체크 관련 델리게이트 프로토콜을 추가합니다.

extension GameScene: SKPhysicsContactDelegate {

}

didMove 함수 하단에 추가합니다.  중력은 없고 자신의 충돌체크를 하는거를 설정합니다.

physicsWorld.gravity = .zero
physicsWorld.contactDelegate = self

addMonster 함수에서 monster 스프라이트를 생성하는것 밑에 다음을 추가합니다.

몬스터 스프라이트의 물리속성을 추가하는 내용입니다.

   
    monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size) // 물리몸체 정의
    monster.physicsBody?.isDynamic = true // 물리엔진이 제어하지 않고 액션으로 움직임
    monster.physicsBody?.categoryBitMask = PhysicsCategory.monster // 몬스터 카타고리
    monster.physicsBody?.contactTestBitMask = PhysicsCategory.projectile // 어떤카타고리랑 반응하는가 == 발사체
    monster.physicsBody?.collisionBitMask = PhysicsCategory.none // 발사체와 몬스터의 바운스 관련은 없음

touchesEnded함수에 있는 발사체 생성되는곳 아래에도 물리속성추가 소스를 씁니다.

    projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
    projectile.physicsBody?.isDynamic = true
    projectile.physicsBody?.categoryBitMask = PhysicsCategory.projectile
    projectile.physicsBody?.contactTestBitMask = PhysicsCategory.monster
    projectile.physicsBody?.collisionBitMask = PhysicsCategory.none
    projectile.physicsBody?.usesPreciseCollisionDetection = true

두개의 물체가 충돌할때 발사체와 몬스터를 제거하는 함수를 미리 추가합니다.

  func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
    print("Hit")
    projectile.removeFromParent()
    monster.removeFromParent()
  }

마지막으로 두개의 물체충돌을 시작하는 시작점 함수 및 제거 함수 호출해주는 didBegin 함수를 추가해 줍니다.

  func didBegin(_ contact: SKPhysicsContact) {
    // 두개의 물체가 충돌했을때 할당
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
      firstBody = contact.bodyA
      secondBody = contact.bodyB
    } else {
      firstBody = contact.bodyB
      secondBody = contact.bodyA
    }
   
    // 두 물체를 정의하고 함수호출!
    if ((firstBody.categoryBitMask & PhysicsCategory.monster != 0) &&
        (secondBody.categoryBitMask & PhysicsCategory.projectile != 0)) {
      if let monster = firstBody.node as? SKSpriteNode,
        let projectile = secondBody.node as? SKSpriteNode {
        projectileDidCollideWithMonster(projectile: projectile, monster: monster)
      }
    }
  }

발사체에 사라지는 몬스터를 볼수 있습니다!

 

심심하니 배경음악과 발사체 효과음을 집어넣어 봅시다.

didMove 함수 하단에 추가합니다.

let backgroundMusic = SKAudioNode(fileNamed: "background-music-aac.caf")
backgroundMusic.autoplayLooped = true
addChild(backgroundMusic)

touchesEnd 함수의 guard 조건 밑에 추가합니다.

run(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))

사람 목소리 인듯? 한 뾱뾱 소리가 수리검이 나갈때마다 나옵니다.

 

 

https://www.raywenderlich.com/71-spritekit-tutorial-for-beginners

 

SpriteKit Tutorial for Beginners

In this SpriteKit tutorial, you will learn how to create a simple 2D game using SpriteKit, Apple’s 2D game framework, while writing in Swift 4!

www.raywenderlich.com

원본글 링크 입니다.

반응형