Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
(ほぼ)標準ライブラリだけでスロットゲームを実装した話
Search
Tomohiro Imaizumi
August 24, 2018
Programming
2.3k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
(ほぼ)標準ライブラリだけでスロットゲームを実装した話
ROPPONGI.swift #5 での登壇資料です。
Tomohiro Imaizumi
August 24, 2018
More Decks by Tomohiro Imaizumi
See All by Tomohiro Imaizumi
VisionFrameworkで実現する - プライバシーに配慮した「顔ぼかし」機能 / Face blurring with Vision Framework
imaizume
0
440
Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fast and stress-free delivery with Feature Flag-based development
imaizume
3
4.9k
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
imaizume
1
930
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
1.3k
スナップショットテスト実戦投入 / Practical Snapshot Testing
imaizume
14
8k
コーディング以外のエンジニアリング / About Engineering Without Coding
imaizume
1
1.9k
Firebase Remote Configの運用で知ったこと・知っておくと良いこと / Things I Learned from Operation of Firebase Remote Config
imaizume
6
4.4k
iOSアプリのテストを書きたいのに書けないあなたへ / How You Should Start to Write Your First Unit Test for iOS
imaizume
6
5.4k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
8.8k
Other Decks in Programming
See All in Programming
graphql-ruby を Spinel で速くしようとした話
mtsmfm
0
140
Next.js: The final boss of Web Performance
guaca
0
380
AI時代のFlutter品質設計 — アプリ品質を守るQuality Layers
omatty198
0
330
パスワード再設定メールのURLについて知っておこう
uyuki234
0
200
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
530
AI駆動開発4年目の現在地(『あなたはFDEになるべきか?』イベント資料)
kumaiu
0
110
最近”作らせてる” ローカルAIアプリの話
akit37
0
530
AI Augmentation Techniques with Liberty
ivargrimstad
0
240
Python Lambdaにおけるテストしやすい設計とAI支援の活用 / Designing Testable Python Lambda Functions with AI
kitano_yuichi
1
270
自作Discord音声botをLambda MicroVMsに移行した話 / Migrating My Discord Voice Bot to Lambda MicroVMs
akihisaikeda
1
240
dotNET_vulnclicker_intro.pdf
deka_morita
0
160
React Native에 현대적인 빌드 도구 더하기: Rollipop 구현기
leegeunhyeok
0
180
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.9k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
510
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.7k
Marketing to machines
jonoalderson
1
6k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
350
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.9k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
31k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.5k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
A designer walks into a library…
pauljervisheath
211
25k
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