간단한 2D게임만들기!
압축해제 후 오픈합니다.
실행하면, 아직 페이지 로딩이 없으니 하얀 공백페이지가 보입니다.
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/
그리고 실제 몬스터를 추가 시키는 소스를 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
원본글 링크 입니다.