Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
(ほぼ)標準ライブラリだけでスロットゲームを実装した話
Search
Tomohiro Imaizumi
August 24, 2018
Programming
1
2k
(ほぼ)標準ライブラリだけでスロットゲームを実装した話
ROPPONGI.swift #5 での登壇資料です。
Tomohiro Imaizumi
August 24, 2018
Tweet
Share
More Decks by Tomohiro Imaizumi
See All by Tomohiro Imaizumi
Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fast and stress-free delivery with Feature Flag-based development
imaizume
2
3.6k
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
imaizume
1
770
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
1.2k
スナップショットテスト実戦投入 / Practical Snapshot Testing
imaizume
13
7k
コーディング以外のエンジニアリング / About Engineering Without Coding
imaizume
1
1.7k
Firebase Remote Configの運用で知ったこと・知っておくと良いこと / Things I Learned from Operation of Firebase Remote Config
imaizume
6
3.9k
iOSアプリのテストを書きたいのに書けないあなたへ / How You Should Start to Write Your First Unit Test for iOS
imaizume
6
5k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
8k
シングルトンではじめる状態管理と依存注入 / A way to control state using singleton pattern
imaizume
0
4.8k
Other Decks in Programming
See All in Programming
これが俺の”自分戦略” プロセスを楽しんでいこう! - Developers CAREER Boost 2024
niftycorp
PRO
0
190
命名をリントする
chiroruxx
1
420
Zoneless Testing
rainerhahnekamp
0
120
Spatial Rendering for Apple Vision Pro
warrenm
0
110
今年一番支援させていただいたのは認証系サービスでした
satoshi256kbyte
1
260
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
400
Keeping it Ruby: Why Your Product Needs a Ruby SDK - RubyWorld 2024
envek
0
190
Haze - Real time background blurring
chrisbanes
1
510
rails stats で紐解く ANDPAD のイマを支える技術たち
andpad
1
290
Jakarta EE meets AI
ivargrimstad
0
260
わたしの星のままで一番星になる ~ 出産を機にSIerからEC事業会社に転職した話 ~
kimura_m_29
0
180
たのしいparse.y
ydah
3
120
Featured
See All Featured
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Being A Developer After 40
akosma
87
590k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.3k
Visualization
eitanlees
146
15k
What's in a price? How to price your products and services
michaelherold
243
12k
Git: the NoSQL Database
bkeepers
PRO
427
64k
The Power of CSS Pseudo Elements
geoffreycrofte
73
5.4k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
The Cult of Friendly URLs
andyhume
78
6.1k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
111
49k
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
Transcript
(΄΅)ඪ४ϥΠϒϥϦ͚ͩͰ εϩοτήʔϜΛ࣮ͨ͠ ROPPONGI.swift #5 @imaizume
今泉 智博 @imaizume 2017年株式会社ミクシィ新卒入社 株式会社 所属 iOS利用経験0から iOS版開発担当に (ほぼ)毎日健康COMP生活はもうすぐ1周年
1. 背景 2. スロットの仕様 3. スロット実装の解説とDEMO 4. 実装過程での課題と解決方法 5. 将来的な話
ຊͷINDEX
夏はマッチングアプリが盛り上がる大切な時期 Poiboyも夏にキャンペーンを実施 今年の企画: スロットゲームで豪華賞品プレゼント എܠ 実は昨年やるはずがビジネス的事情により1年寝かされていました 詳細は↓の登壇&インタビューを御覧ください https://mixil.mixi.co.jp/people/2103
(1)Symbol: 絵柄1つのこと (2)Reel: 絵柄のパターンの1セット (3)ReelSet: 複数リールのセット (imaizumeオリジナル用語) (4)Drum: スロットの中の1レーン (右の図では3ドラム)
ຊ…ͷલʹεϩοτήʔϜͷ༻ޠղઆ ギャンブルに詳しくない方のために このあとの説明で必要になるので (1) (2) (3) スロットの例 (4)
✓ ゲームの開始はユーザータイミング ✓ 当たり判定はゲーム開始時に決定済み ✓ 3つのドラムは独立して回転 ✓ 回転数不明=無限スクロール ✓ 適度な回転スピードと停止時の加速度
✓ 目標となる絵柄を各ドラムの中心で停止 ✓ 賞品ポップアップを全ドラムの停止後に表示 ✓ Auto Layout対応必須 ՆΩϟϯϖʔϯͷεϩοτήʔϜͷ༷ あなたならどうやってスロットを実装しますか❓ (※࣌SwiftΛॻ͖࢝Ίͯ4 ϲ݄
1: SpriteKitで絵柄単体を動かす ❌ 速度を変えたときに絵柄間の間隔が変わる (渋滞の車列みたいになる) 2: UICollectionView / UITableView 絵柄の余白調整や中心での停止が難しい
cellのreuseによる表示のバグも懸念 3. UIScrollView ⭕ 一番自由度が高く良さそう 中心で止められそう ͕ࣗߟ࣮͑ͨͷީิ (※લड़ͷొஃࢿྉͰUICollectionViewΛͬͨͱ͋ΔͷޡΓͰ͢ )
1.絵柄に対応するUIViewを作成 2.絵柄を連ねてReelを作成 3.複数のReel群 = ReelSetを縦につなげる 4.ReelSetをUIScrollViewに入れDrumにする Ͳ͏࣮ͬͯݱ͔ͨ͠(View) Symbol Reel ReelSet
Drum1 可視領域
࣮ࡍͷView֊
1.遠くの座標にcontentOffsetを設定 2.下に向かって自動スクロールで下端到達 3.上部の同絵柄にジャンプ 4.1~3を繰り返す Ͳ͏࣮ͬͯݱ͔ͨ͠(ಈ͖) setContentOffset (y: 5000) ~~~ 2.下端に到達
3.同じ絵柄にジャンプ drumScrollView. setContentOffset (500) すぐにゴールを 遠くに戻す y=5000 y=0 1.offsetを遠くに
ίϯϙʔωϯτͷਤ DrumScrollView: UIScrollView enum Symbol: Int symbol: [Symbol] numberOfReel: Int
symbolViews:[UIView] drum: DrumScrollView ✕ drumSet: [DrumScrollView] numberOfDrum: Int ✕
DrumScrollView(ύϥϝʔλઃఆ) var drumParam : DrumParam = DrumParam() { didSet {
// MARK: View設定 self.subviews.forEach({ $0.removeFromSuperview() }) self.contentSize = CGSize( width: self.frame.width, height: (CGFloat)(self.drumParam.numberOfSymbol) * self.symbolHeight) for (index, view) in self.drumParam.totalSetOfSymbolImageView.enumerated() { view.frame = CGRect( x: 0, y: (CGFloat)(index) * self.symbolHeight, width: self.frame.width, height: self.symbolHeight ) self.addSubview(view) } self.setNeedsLayout() self.layoutIfNeeded() // MARK: ゲーム設定 self.remainingScrollCount = self.drumParam.numberToScroll // 開始時のOffsetをランダムにずらず let startIndex: UInt32 = arc4random_uniform(UInt32(self.drumParam.numberOfReel)) self.contentOffset.y = ((CGFloat)(startIndex) + 0.5) * self.symbolHeight // 停止位置座標の決定 var targetY: CGFloat = self.symbolHeight * ((CGFloat)(self.drumParam.targetIndex) + 0.5) - self.frame.height / 2 // 頭のリールは切れてしまうので前半のリールは後半の後ろに持っていく if self.drumParam.targetIndex < self.drumParam.numberOfReel / 2 { targetY += (CGFloat)(self.drumParam.numberOfReel) * reelHeight } self.stopTargetPoint = targetY } } 縦にSymbolをつなげる contentViewの領域確保 ちょっとした気遣い パラメータ用Structを定義
DumScrollView(͖͍͠ઃఆ) /// 残り回転数 fileprivate var remainingScrollCount: Int = 0 ///
回転を減速するべきか fileprivate var shouldSlowDownScroll: Bool { let isNearTheTarget = abs(self.contentOffset.y - self.stopTargetPoint) < self.scrollTargetDistanceForSlowSpeed let isLastScroll = self.remainingScrollCount == 0 return isNearTheTarget && isLastScroll } /// 回転を停止するべきか fileprivate var shouldFinishScroll: Bool { let isOnTheTarget = abs(self.contentOffset.y - self.stopTargetPoint) < self.bufferForTargetPosition let isLastScroll = self.remainingScrollCount == 0 return isOnTheTarget && isLastScroll } /// 回転位置を戻すべきか fileprivate var shouldRewindScroll: Bool { return self.contentOffset.y <= self.reelHeight } fileprivate var stopTargetPoint: CGFloat = 0 /// 停止位置ちょうどに止めることはできないため許容される停止位置までの誤差 fileprivate let bufferForTargetPosition: CGFloat = 10.0 /// 通常回転時のスクロール目標位置 fileprivate let scrollTargetDistanceForNormalSpeed : CGFloat = 5000 /// 減速時のスクロール目標位置 fileprivate let scrollTargetDistanceForSlowSpeed : CGFloat = 100 停止までの回転数(5とか) 停止/減速までのバッファを作るのがポイント POINT: 停止位置にはバッファ(ズレの許容度)を与える 回転速度が早すぎるとバッファを通過してしまう ズレは小さくしたいので直前で減速させる
DrumScrollView(ಈ࡞ઃఆ) func updateViews() { if self.remainingScrollCount < 0 { return
} if self.shouldFinishScroll { // ఀࢭ࣌ self.setContentOffset(CGPoint(x: 0, y: self.stopTargetPoint), animated: true) self.gameFinishCallback() } else if self.shouldSlowDownScroll { // ݮ࣌ let scrollTargetPoint = -(100 + self.contentOffset.y) self.setContentOffset(CGPoint(x: 0, y: scrollTargetPoint), animated: !self.shouldRewindScroll) return } else if self.shouldRewindScroll { // ্ʹୡͨ࣌͠ let scrollTargetPoint = (self.contentSize.height / (CGFloat)(self.drumParam.numberOfReelSet) * 2) + self.contentOffset.y self.decreaseScrollCount() self.setContentOffset(CGPoint(x: 0, y: scrollTargetPoint), animated: !self.shouldRewindScroll) } else { // ௨ৗͷճస࣌ let scrollTargetPoint = -(5000 + self.contentOffset.y) self.setContentOffset(CGPoint(x: 0, y: scrollTargetPoint), animated: !self.shouldRewindScroll) } } 停止後に呼ぶコール バック後で解説
❇とりあえず時間をもらった (いろいろ試すしかない) チームの理解も得られていろいろとトライさせてくれた ໘ͨ͠՝ͱղܾํ๏ 1. ࣾ֎Θͣલྫ͕ͳ͍ 力技(ひたすらdebugポイントとprintを埋め込み) タイミングよくrevealを教えてもらったのはラッキー SymbolやDrumに処理を移譲してカプセル化 2.
ಈ͖ͷσόοά͕େม Offsetや回転スピードの関係を確認するため 値を計測してグラフ化したりもしました ➡
໘ͨ͠՝ͱղܾํ๏ UIScollViewはスクロールスピードを指定できない 現在のOffset + αにsetContentOffsetで定速スクロール 3. ఀࢭݮ࣌ͷՃௐ offset: 100 drumScrollView.
setContentOffset (100 + 200) offset: 100 drumScrollView. setContentOffset (100 + 1000) offset: 120 drumScrollView. setContentOffset (120 + 200) offset: 200 drumScrollView. setContentOffset (200 + 1000)
ここだけPromiseKitを使いました 各Drumの停止時コールバックでfulfillをcall 3つのDrumの停止後に当選ポップアップを表示 (当選結果自体はViewController側で保持) let games = self.drumScrollViews.map { drum
in return Promise<Void> { (fullfil, _) in drum.gameFinishCallback = { fullfil(()) } } } // MEMO: 当選ポップアップの表示 when(resolved: games).always { self.showWinPopup() } ໘ͨ͠՝ͱղܾํ๏ 4. ಠཱͨ͠ճసͷऴྃͷͪ߹Θͤ
কདྷతͳ ϥΠϒϥϦԽ͍ͨ͠ ‣ 今回はゲーム開始前に結果が決まっていた ‣ 本来は動的に停止位置や速度調整をする必要あり ‣ Viewの制約で上端や下端では止められない ‣ 横スクロールにすれば回転寿司みたいなUIも作れそう?
PromiseKitͷษڧձΛͦͷ͏ͪΓ·͢ ‣ 今回の実装でPromiseKitをうまく扱えるようになった ‣ コールバック直書きのコードもいい感じに書き換えた ‣ 現在勉強会企画中 (SupporterZ CoLabを予定)
ࢀߟࢿྉ 新規サービスのアプリ開発で経験したリアルな出来事 https://speakerdeck.com/imaizume/xin-gui-sabisufalseapurikai-fa-dejing- yan-sitariarunachu-lai-shi 失敗こそ学びの力に!貪欲にチャレンジし続ける ~新卒1年 目 成長の軌跡~#5|ミクシル https://mixil.mixi.co.jp/people/2103 mxcl/PromiseKit:
Promises for Swift & ObjC https://github.com/mxcl/PromiseKit