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.5k
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
imaizume
1
750
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
1.1k
スナップショットテスト実戦投入 / 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
4.9k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
8k
シングルトンではじめる状態管理と依存注入 / A way to control state using singleton pattern
imaizume
0
4.7k
Other Decks in Programming
See All in Programming
リアーキテクチャxDDD 1年間の取り組みと進化
hsawaji
1
220
Micro Frontends Unmasked Opportunities, Challenges, Alternatives
manfredsteyer
PRO
0
110
Kaigi on Rails 2024 〜運営の裏側〜
krpk1900
1
240
Enabling DevOps and Team Topologies Through Architecture: Architecting for Fast Flow
cer
PRO
0
340
Generative AI Use Cases JP (略称:GenU)奮闘記
hideg
1
300
とにかくAWS GameDay!AWSは世界の共通言語! / Anyway, AWS GameDay! AWS is the world's lingua franca!
seike460
PRO
1
900
Contemporary Test Cases
maaretp
0
140
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
14
2.2k
見せてあげますよ、「本物のLaravel批判」ってやつを。
77web
7
7.8k
イベント駆動で成長して委員会
happymana
1
340
Webの技術スタックで マルチプラットフォームアプリ開発を可能にするElixirDesktopの紹介
thehaigo
2
1k
Jakarta EE meets AI
ivargrimstad
0
150
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
67
10k
Product Roadmaps are Hard
iamctodd
PRO
49
11k
GitHub's CSS Performance
jonrohan
1030
460k
Java REST API Framework Comparison - PWX 2021
mraible
PRO
28
8.2k
A Tale of Four Properties
chriscoyier
156
23k
Producing Creativity
orderedlist
PRO
341
39k
[RailsConf 2023] Rails as a piece of cake
palkan
52
4.9k
Scaling GitHub
holman
458
140k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
4
370
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.3k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
6.8k
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