Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

6,384 views

Published on

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

Published in: Technology
  • Be the first to comment

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

  1. 1. SpriteKitでの 横スクロールジャンプ アクションゲーム開発 第65回 Cocoa勉強会関西 2016年1月23日 @studioshin
  2. 2. 自己紹介 フリーでiOS/Macアプリ開発やってます 北村 真二 STUDIO SHIN @studioshinTwitter: Cocoa勉強会関西 代表
  3. 3. こんな本を書いてます。 絶版
  4. 4. Sprite Kitとは? iOS 7から搭載された iOS/OS X向けの2Dゲーム開発の ためのフレームワーク。
  5. 5. WWDC2013で発表!
  6. 6. スプライトによるキャラクターの配置 アクションでキャラクターを動かす シーンによるゲーム画面管理 物理シミュレーション Sprite Kitの基本
  7. 7. Sprite Kitクラスを使ったゲーム構成イメージ
  8. 8. Sprite Kitでの物理シミュレーションイメージ 質量、密度は物理体のサイズから初期値が 自動的に計算される。
  9. 9. 物理体には2つの種類がある ・ボリュームベース物理体 ・ エッジベース物理体 質量と体積を持ち重力や衝突といった他からの力の影響 を受ける。ゲームキャラなどのゲーム内で動き回るオブ ジェクトに使用。 質量を持たず重力や衝突などの影響を受けない。物理シ ミュレーションの境界や空間を表すために使用され
  10. 10. Sprite Kitの座標系 左下原点!
  11. 11. ノードの原点 anchourPoint = {0.5 0.5}
  12. 12. 重力のベクトル(Default) (0, -9.8)
  13. 13. 横スクロールジャンプ アクションゲームを作る Xcodeでゲーム画面をグラフィカルに作成 ジャンプの実装 ゲーム画面のスクロール
  14. 14. デモアプリ http://www.studioshin.com/cocoa_sample/
  15. 15. 今回はSingle View Applicationとして作ります。
  16. 16. class GameScene: SKScene class GameView: SKView class GameViewController: UIViewController 以下の3つのサブクラスを作っておいてテンプレートにする。
  17. 17. File > New > File…ゲーム画面のsksファイルの作成
  18. 18. sksファイル ゲーム画面のサイズを決める これを配置データとして使う
  19. 19. sksファイル 画像をドラッグ&ドロップ
  20. 20. sksファイルでゲーム画面を作成
  21. 21. sksファイルでゲーム画面を作成
  22. 22. スプライトを選択 アトリビュートを設定
  23. 23. zPosition = 2 name = back_wall背景
  24. 24. 物理設定 zPosition = 3 name = ground カテゴリーマスク設定 ボディタイプDynamicのチェックを 外しておく 地面
  25. 25. zPosition = 4 name = floor 物理設定 Dynamicのチェックを 外しておく カテゴリーマスク設定 ボディタイプ 浮床
  26. 26. zPosition = 5 name = player 物理設定 プログラムで行います。 Anchor Point = (0.5, 0.0) プレイヤーキャラ
  27. 27. 画面スクロールの仕組み SKScene
  28. 28. ベースノード:SKNode 画面スクロールの仕組み SKScene シーンにベースノードを配置する。
  29. 29. ベースノード:SKNode SKScene 画面スクロールの仕組み 背景画像のノードキャラノード ゲームの表示物は全てベースノードに配置する。
  30. 30. ベースノード:SKNode SKScene 画面スクロールの仕組み 見えている部分
  31. 31. ベースノード:SKNode SKScene 画面スクロールの仕組み 見えている部分
  32. 32. ベースノード:SKNode SKScene 画面スクロールの仕組み キャラクターが動いたのと同じだけ、 ベースノードを反対に動かしてやる。
  33. 33. ジャンプの仕様 ジャンプ中は「浮床」 を貫通。 ジャンプして「浮床」 に乗れる。 空中で姿勢制御可能
  34. 34. GameViewController.xib UIButton キャラクターをジャンプ させるUI
  35. 35. 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を作る
  36. 36. GameViewController.swift override 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) }
  37. 37. GameViewController.swift @IBOutlet weak var jumpButton: UIButton! @IBAction func jumpButtonAction(sender: AnyObject) { self.gameScene.jumpingAction() } キャラクターをジャンプさせるアクション関数 xibファイルに配置したボタンと繋いでおく。 jumpAction() GameSceneに実装するキャラクターを ジャンプさせる関数
  38. 38. 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
  39. 39. 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 } } }
  40. 40. 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
  41. 41. 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) ・・・・・・・・・・・ } ゲーム画面の作成
  42. 42. 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) }) ・・・・・・・ } ゲーム画面の作成
  43. 43. ゲーム画面の作成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 }
  44. 44. 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() } フリックでキャラクターを動かす
  45. 45. 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) } }
  46. 46. 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) } 移動 ジャンプ 落下
  47. 47. 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) } } }
  48. 48. GameScene.swift 移動 //MARK: - シーンのアップデート時に呼ばれる関数 override func update(currentTime: CFTimeInterval) { ・・・・・ //MARK: 画面をスクロールさせる //シーン上でのプレイヤーの座標をbaseNodeからの位置に変換 let PlayerPt = self.convertPoint(self.playerNode.position, fromNode: self.baseNode) //シーン上でプレイヤー位置を基準にしてbaseNodeの位置を変更する varx = self.baseNode.position.x - PlayerPt.x + self.charXOffset vary = 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 キャラクターが動いたのと同じだけ、 ベースノードを反対に動かしてやる。
  49. 49. 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)) } } } ジャンプ中は「浮床」 を貫通。
  50. 50. 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") } } } ジャンプ -> 落下 ジャンプ 落下
  51. 51. 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() } }

×