51
SpriteKit での 横スクロールジャンプ アクションゲーム開発 65Cocoa勉強会関西 2016123@studioshin

Sprite kitでの横スクロールジャンプ アクションゲーム開発

Embed Size (px)

Citation preview

Page 1: Sprite kitでの横スクロールジャンプ アクションゲーム開発

SpriteKitでの 横スクロールジャンプ アクションゲーム開発

第65回 Cocoa勉強会関西

2016年1月23日

@studioshin

Page 2: Sprite kitでの横スクロールジャンプ アクションゲーム開発

自己紹介

フリーでiOS/Macアプリ開発やってます

北村 真二STUDIO SHIN

@studioshinTwitter:

Cocoa勉強会関西 代表

Page 3: Sprite kitでの横スクロールジャンプ アクションゲーム開発

こんな本を書いてます。

絶版

Page 4: Sprite kitでの横スクロールジャンプ アクションゲーム開発

Sprite Kitとは?

iOS 7から搭載されたiOS/OS X向けの2Dゲーム開発のためのフレームワーク。

Page 5: Sprite kitでの横スクロールジャンプ アクションゲーム開発

WWDC2013で発表!

Page 6: Sprite kitでの横スクロールジャンプ アクションゲーム開発

スプライトによるキャラクターの配置

アクションでキャラクターを動かす

シーンによるゲーム画面管理

物理シミュレーション

Sprite Kitの基本

Page 7: Sprite kitでの横スクロールジャンプ アクションゲーム開発

Sprite Kitクラスを使ったゲーム構成イメージ

Page 8: Sprite kitでの横スクロールジャンプ アクションゲーム開発

Sprite Kitでの物理シミュレーションイメージ

質量、密度は物理体のサイズから初期値が自動的に計算される。

Page 9: Sprite kitでの横スクロールジャンプ アクションゲーム開発

物理体には2つの種類がある

・ボリュームベース物理体

・ エッジベース物理体

質量と体積を持ち重力や衝突といった他からの力の影響を受ける。ゲームキャラなどのゲーム内で動き回るオブジェクトに使用。

質量を持たず重力や衝突などの影響を受けない。物理シミュレーションの境界や空間を表すために使用され

Page 10: Sprite kitでの横スクロールジャンプ アクションゲーム開発

Sprite Kitの座標系

左下原点!

Page 11: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ノードの原点

anchourPoint = {0.5 0.5}

Page 12: Sprite kitでの横スクロールジャンプ アクションゲーム開発

重力のベクトル(Default)

(0, -9.8)

Page 13: Sprite kitでの横スクロールジャンプ アクションゲーム開発

横スクロールジャンプ アクションゲームを作るXcodeでゲーム画面をグラフィカルに作成

ジャンプの実装

ゲーム画面のスクロール

Page 14: Sprite kitでの横スクロールジャンプ アクションゲーム開発

デモアプリhttp://www.studioshin.com/cocoa_sample/

Page 15: Sprite kitでの横スクロールジャンプ アクションゲーム開発

今回はSingle View Applicationとして作ります。

Page 16: Sprite kitでの横スクロールジャンプ アクションゲーム開発

class GameScene: SKSceneclass GameView: SKViewclass GameViewController: UIViewController

以下の3つのサブクラスを作っておいてテンプレートにする。

Page 17: Sprite kitでの横スクロールジャンプ アクションゲーム開発

File > New > File…ゲーム画面のsksファイルの作成

Page 18: Sprite kitでの横スクロールジャンプ アクションゲーム開発

sksファイル

ゲーム画面のサイズを決める

これを配置データとして使う

Page 19: Sprite kitでの横スクロールジャンプ アクションゲーム開発

sksファイル

画像をドラッグ&ドロップ

Page 20: Sprite kitでの横スクロールジャンプ アクションゲーム開発

sksファイルでゲーム画面を作成

Page 21: Sprite kitでの横スクロールジャンプ アクションゲーム開発

sksファイルでゲーム画面を作成

Page 22: Sprite kitでの横スクロールジャンプ アクションゲーム開発

スプライトを選択

アトリビュートを設定

Page 23: Sprite kitでの横スクロールジャンプ アクションゲーム開発

zPosition = 2name = “back_wall”背景

Page 24: Sprite kitでの横スクロールジャンプ アクションゲーム開発

物理設定

zPosition = 3name = “ground”

カテゴリーマスク設定

ボディタイプDynamicのチェックを外しておく

地面

Page 25: Sprite kitでの横スクロールジャンプ アクションゲーム開発

zPosition = 4name = “floor”

物理設定Dynamicのチェックを外しておく

カテゴリーマスク設定

ボディタイプ

浮床

Page 26: Sprite kitでの横スクロールジャンプ アクションゲーム開発

zPosition = 5name = “player”

物理設定プログラムで行います。

Anchor Point = (0.5, 0.0)

プレイヤーキャラ

Page 27: Sprite kitでの横スクロールジャンプ アクションゲーム開発

画面スクロールの仕組みSKScene

Page 28: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ベースノード:SKNode

画面スクロールの仕組みSKScene シーンにベースノードを配置する。

Page 29: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ベースノード:SKNode

SKScene

画面スクロールの仕組み

背景画像のノードキャラノード

ゲームの表示物は全てベースノードに配置する。

Page 30: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ベースノード:SKNode

SKScene

画面スクロールの仕組み

見えている部分

Page 31: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ベースノード:SKNode

SKScene

画面スクロールの仕組み

見えている部分

Page 32: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ベースノード:SKNode

SKScene

画面スクロールの仕組み

キャラクターが動いたのと同じだけ、ベースノードを反対に動かしてやる。

Page 33: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ジャンプの仕様

ジャンプ中は「浮床」を貫通。

ジャンプして「浮床」に乗れる。

空中で姿勢制御可能

Page 34: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameViewController.xib

UIButton キャラクターをジャンプ

させるUI

Page 35: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameViewController.swift

var gameView: GameView! var gameScene: GameScene! class func gameViewController() -> GameViewController {

let gameView = GameViewController(nibName: "GameViewController", bundle: nil) let frame = UIScreen.mainScreen().bounds gameView.view.frame = CGRectMake(0, 0, frame.size.width, frame.size.height) return gameView

}

xibファイルからGameViewControllerを作る

Page 36: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameViewController.swiftoverride func viewDidLoad() { super.viewDidLoad() //=================== //Game View作成 //=================== let frame = UIScreen.mainScreen().bounds self.gameView = GameView(frame: CGRectMake(0,0,frame.size.width,frame.size.height)) self.gameView.allowsTransparency = true self.gameView.ignoresSiblingOrder = true self.view.addSubview(self.gameView) self.view.sendSubviewToBack(self.gameView) //=================== // Game Scene作成 //=================== self.gameScene = GameScene(size: CGSizeMake(frame.size.width,frame.size.height)) self.gameScene.scaleMode = .AspectFill //シーンをビューと同じサイズに調整する self.gameScene.size = CGSizeMake(frame.size.width, frame.size.height) // ゲームシーンを表示 self.gameView.presentScene(self.gameScene) }

Page 37: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameViewController.swift

@IBOutlet weak var jumpButton: UIButton! @IBAction func jumpButtonAction(sender: AnyObject) { self.gameScene.jumpingAction() }

キャラクターをジャンプさせるアクション関数xibファイルに配置したボタンと繋いでおく。

jumpAction() GameSceneに実装するキャラクターを

ジャンプさせる関数

Page 38: Sprite kitでの横スクロールジャンプ アクションゲーム開発

import UIKit import SpriteKit

class GameView: SKView { override init(frame: CGRect) { super.init(frame: frame) }

required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }

GameView.swift

Page 39: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift//移動方向 enum Direction: Int { case Right = 0 //右 case Left = 1 //左 }

enum NodeName: String { case frame_ground = "frame_ground" //地面あたり case frame_floor = "frame_floor" //浮床あたり case player = "player" //プレイヤー case backGround = "backGround" //背景 case ground = "ground" //地面 case floor = "floor" //浮床 //接触カテゴリビットマスク func category()->UInt32{ switch self { case .frame_ground: //地面あたり return 0x00000001 << 0 case .frame_floor: //浮床あたり return 0x00000001 << 1 case .player: //プレイヤー return 0x00000001 << 2 default: return 0x00000000 } } }

Page 40: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift プロパティ //画面 let baseNode = SKNode() //ゲームベースノード let backScrNode = SKNode()   //背景ノード

var allScreenSize = CGSizeMake(0, 0) //全画面サイズ let oneScreenSize = CGSizeMake(375, 667) //1画面サイズ // プレイヤーキャラ var playerNode: SKSpriteNode! var playerDirection: Direction = .Right //移動方向 var physicsRadius: CGFloat = 14.0 //物理半径 var playerAcceleration: CGFloat = 50.0 //移動加速値 var playerMaxVelocity: CGFloat = 200.0 //MAX移動値 var jumpForce: CGFloat = 16.0 //ジャンプ力 var charXOffset: CGFloat = 0 //X位置のオフセット var charYOffset: CGFloat = 0 //Y位置のオフセット var moving: Bool = false //移動中フラグ var jumping: Bool = false //ジャンプ中フラグ var falling: Bool = false //落下中 var tapPoint: CGPoint = CGPointZero var screenSpeed: CGFloat = 12.0 var screenSpeedScale: CGFloat = 1.0

Page 41: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift //MARK: シーンが表示されたときに呼ばれる関数 override func didMoveToView(view: SKView) { self.backgroundColor = SKColor.clearColor() //接触デリゲート self.physicsWorld.contactDelegate = self //MARK: 背景 //地面、キャラのスクロール self.addChild(self.baseNode) //背景のスクロール self.addChild(self.backScrNode) let wCount = 4 //横の画面数 self.allScreenSize = CGSizeMake(self.oneScreenSize.width * CGFloat(wCount), self.size.height)

・・・・・・・・・・・ }

ゲーム画面の作成

Page 42: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift let wCount = 4 //横の画面数 self.allScreenSize = CGSizeMake(self.oneScreenSize.width * CGFloat(wCount), self.size.height) //シーンファイルを読み込み if let scene = SKScene(fileNamed: "GameScene.sks") { //=================== //背景 //=================== scene.enumerateChildNodesWithName("back_wall", usingBlock: { (node, stop) -> Void in let back_wall = node as! SKSpriteNode back_wall.name = NodeName.backGround.rawValue //シーンから削除して再配置 back_wall.removeFromParent() self.midScrNode.addChild(back_wall) }) //=================== //地面 //=================== scene.enumerateChildNodesWithName("ground", usingBlock: { (node, stop) -> Void in let ground = node as! SKSpriteNode ground.name = NodeName.ground.rawValue //シーンから削除して再配置 ground.removeFromParent() self.baseNode.addChild(ground) }) //=================== //浮床 //=================== scene.enumerateChildNodesWithName("floor", usingBlock: { (node, stop) -> Void in let floor = node as! SKSpriteNode floor.name = NodeName.floor.rawValue //シーンから削除して再配置 floor.removeFromParent() self.baseNode.addChild(floor) })

・・・・・・・ }

ゲーム画面の作成

Page 43: Sprite kitでの横スクロールジャンプ アクションゲーム開発

ゲーム画面の作成GameScene.swift・・・・・・・

//=================== //MARK: プレイヤー //=================== self.playerDirection = .Right self.charXOffset = self.oneScreenSize.width * 0.5 self.charYOffset = self.oneScreenSize.height * 0.5 scene.enumerateChildNodesWithName("player", usingBlock: { (node, stop) -> Void in let player = node as! SKSpriteNode self.playerNode = player //シーンから削除して再配置 player.removeFromParent() self.baseNode.addChild(self.playerNode) //物理設定 self.playerNode.physicsBody = SKPhysicsBody(circleOfRadius: self.physicsRadius,center: CGPointMake(0, self.physicsRadius)) self.playerNode.physicsBody!.friction = 1.0 //摩擦 self.playerNode.physicsBody!.allowsRotation = false //回転禁止 self.playerNode.physicsBody!.restitution = 0.0 //跳ね返り値 self.playerNode.physicsBody!.categoryBitMask = NodeName.player.category() self.playerNode.physicsBody!.collisionBitMask = NodeName.frame_ground.category()|NodeName.frame_floor.category() self.playerNode.physicsBody!.contactTestBitMask = 0 self.playerNode.physicsBody!.usesPreciseCollisionDetection = true }) //=================== //壁あたり //=================== let wallFrameNode = SKNode() self.baseNode.addChild(wallFrameNode) //読み込んだシーンのサイズから外周のあたりを作成する wallFrameNode.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRectMake(0, 0, scene.size.width, scene.size.height)) wallFrameNode.physicsBody!.categoryBitMask = NodeName.frame_ground.category() wallFrameNode.physicsBody!.usesPreciseCollisionDetection = true }

Page 44: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift 移動 //MARK: - タッチ処理 //タッチダウンされたときに呼ばれる関数 override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { var location: CGPoint! for touch in touches { location = touch.locationInNode(self) } self.tapPoint = location self.playerNode.physicsBody!.linearDamping = 0.0 } //タッチ移動 override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { var location: CGPoint! for touch in touches { location = touch.locationInNode(self) } //移動角度 let radian = (atan2(location.y-self.tapPoint.y, location.x-self.tapPoint.x)) let angle = radian * 180 / CGFloat(M_PI) if angle > -90 && angle < 90 { if self.moving == false || self.playerDirection != .Right { self.moveToRight() //右 } } else { if self.moving == false || self.playerDirection != .Left{ self.moveToLeft() //左 } } } //タッチアップされたときに呼ばれる関数 override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { self.moveStop() }

フリックでキャラクターを動かす

Page 45: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift 移動 //MARK: - 移動 func moveToRight() { self.moving = true //移動中フラグON self.playerDirection = .Right if self.jumping == false && self.falling == false { let names = ["right2", "right1","right3","right1"] self.startTextureAnimation(self.playerNode, names: names) } else { self.playerNode.texture = SKTexture(imageNamed: "right_jump1") } } func moveToLeft() { self.moving = true //移動中フラグON self.playerDirection = .Left if self.jumping == false && self.falling == false { let names = ["left2", "left1","left3","left1"] self.startTextureAnimation(self.playerNode, names: names) } else { self.playerNode.texture = SKTexture(imageNamed: "left_jump1") } } //MARK: - 停止 func moveStop() { self.moving = false //移動中フラグOFF if self.jumping == false && self.falling == false { var name: String! if self.playerDirection == .Right { name = "right1" } else { name = "left1" } self.stopTextureAnimation(self.playerNode, name: name) self.playerNode.physicsBody!.velocity = CGVectorMake(0, 0) } }

Page 46: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift テクスチャーアニメーション //開始 func startTextureAnimation(node: SKSpriteNode, names: [String]) { node.removeActionForKey("textureAnimation") var ary: [SKTexture] = [] for name in names { ary.append(SKTexture(imageNamed: name)) } let action = SKAction.animateWithTextures(ary, timePerFrame: 0.1, resize: true, restore: false) node.runAction(SKAction.repeatActionForever(action), withKey: "textureAnimation") } //停止 func stopTextureAnimation(node: SKSpriteNode, name: String) { node.removeActionForKey("textureAnimation") node.texture = SKTexture(imageNamed: name) }

移動 ジャンプ 落下

Page 47: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift 移動 //MARK: - シーンのアップデート時に呼ばれる関数 override func update(currentTime: CFTimeInterval) { //MARK: プレイヤー移動 if self.moving == true { var dx: CGFloat = 0 var dy: CGFloat = 0 if self.playerDirection == .Right { dx = self.playerAcceleration dy = 0.0 } else if self.playerDirection == .Left { dx = -(self.playerAcceleration) dy = 0.0 } self.playerNode.physicsBody!.applyForce(CGVectorMake(dx, dy)) //速度制限 if self.jumping == false && self.falling == false { //地面上の移動 if self.playerNode.physicsBody!.velocity.dx > self.playerMaxVelocity { self.playerNode.physicsBody!.velocity.dx = self.playerMaxVelocity } else if self.playerNode.physicsBody!.velocity.dx < -(self.playerMaxVelocity) { self.playerNode.physicsBody!.velocity.dx = -(self.playerMaxVelocity) } } else { //空中での姿勢制御 if self.playerNode.physicsBody!.velocity.dx > self.playerMaxVelocity / 2 { self.playerNode.physicsBody!.velocity.dx = self.playerMaxVelocity / 2 } else if self.playerNode.physicsBody!.velocity.dx < -(self.playerMaxVelocity / 2) { self.playerNode.physicsBody!.velocity.dx = -(self.playerMaxVelocity / 2) } }

}

Page 48: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift 移動 //MARK: - シーンのアップデート時に呼ばれる関数 override func update(currentTime: CFTimeInterval) { ・・・・・

//MARK: 画面をスクロールさせる //シーン上でのプレイヤーの座標をbaseNodeからの位置に変換 let PlayerPt = self.convertPoint(self.playerNode.position, fromNode: self.baseNode) //シーン上でプレイヤー位置を基準にしてbaseNodeの位置を変更する var x = self.baseNode.position.x - PlayerPt.x + self.charXOffset var y = self.baseNode.position.y - PlayerPt.y + self.charYOffset //スクロール制限 if x <= -(self.allScreenSize.width - self.size.width) { x = -(self.allScreenSize.width - self.size.width) } if x > 0 { x = 0 } if y <= -(self.allScreenSize.height - self.size.height) { y = -(self.allScreenSize.height - self.size.height) } if y > 0 { y = 0 } self.baseNode.position = CGPointMake(x, y) self.backScrNode.position = CGPointMake(x/4, y)

ベースノード:SKNode

SKScene

キャラクターが動いたのと同じだけ、ベースノードを反対に動かしてやる。

Page 49: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift ジャンプ //MARK: - ジャンプ func jumpingAction() { if self.jumping == false && self.falling == false { self.moving = false self.jumping = true //地面に接触判定 self.playerNode.physicsBody!.collisionBitMask = NodeName.frame_ground.category() self.playerNode.physicsBody!.contactTestBitMask = 0 if self.playerDirection == .Left { self.stopTextureAnimation(self.playerNode, name: "left_jump1") self.playerNode.physicsBody!.applyImpulse(CGVectorMake(0.0, self.jumpForce)) } else { self.stopTextureAnimation(self.playerNode, name: "right_jump1") self.playerNode.physicsBody!.applyImpulse(CGVectorMake(0.0, self.jumpForce)) } } }

ジャンプ中は「浮床」を貫通。

Page 50: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift //MARK: - シーンのアップデート時に呼ばれる関数 override func update(currentTime: CFTimeInterval) {

・・・・・・・・・・・・・

//MARK: 落下 if self.playerNode.physicsBody?.velocity.dy < -9.8 && self.falling == false {

self.jumping = false self.falling = true

self.playerNode.physicsBody!.collisionBitMask = NodeName.frame_ground.category()|NodeName.frame_floor.category() self.playerNode.physicsBody!.contactTestBitMask = NodeName.frame_floor.category()|NodeName.frame_ground.category() if self.playerDirection == .Left { self.stopTextureAnimation(self.playerNode, name: "left_falling1") } else { self.stopTextureAnimation(self.playerNode, name: "right_falling1") } }

}

ジャンプ -> 落下

ジャンプ 落下

Page 51: Sprite kitでの横スクロールジャンプ アクションゲーム開発

GameScene.swift 着地判定//MARK: - 接触判定

func didBeginContact(contact: SKPhysicsContact) { //着地 //当たり判定戻す self.playerNode.physicsBody!.collisionBitMask = NodeName.frame_ground.category()|NodeName.frame_floor.category() self.playerNode.physicsBody!.contactTestBitMask = 0 self.jumping = false self.falling = false if self.moving { //移動中 if self.playerDirection == .Right { self.moveToRight() } else if self.playerDirection == .Left { self.moveToLeft() } } else { //停止 self.moveStop() }

}