[iOS 10] メッセージ拡張 MSSessionで「まるばつゲーム」を作ってみた
1 はじめに
iOS 10から、拡張機能(Extension)を作成することで、メッセージアプリで、ステッカー(Lineのスタンプみたいな感じ)、メディアファイル、及び、対話型のメッセージなどを送信することができるようになりました。
新しいメッセージは、セッション情報を持っており、相互にデータをやり取りすることで簡単なゲームなどを作成することも可能になりました。
今回は、このセッション機能を利用した、まるばつゲームを作成してみました。
相互に、「まる」、「ばつ」を付けて、メッセージを送り合うと、ゲームが勝ち負け判定を行います。
なお、メッセージ拡張の基本的な作成方法などは下記をご参照下さい。
[iOS 10] メッセージ拡張の作成方法
[iOS 10] メッセージ拡張 MSStickerBrowserViewControllerを使用したビューの作成
[iOS 10] メッセージ拡張 MSMessageTemplateLayoutによるテキストや画像(映像)の配置
[iOS 10] メッセージ拡張 ノンプログラミングによるスタンプ作成
[iOS 10] メッセージ拡張 ノンプログラミングによるアニメーションスタンプ作成
2 プロジェクト作成と画面設計
(1) プロジェクト作成
File > New > Project > iMessage Application で新規にプロジェクトを作成します。プロジェクト名は「MaruBatuApp」としました。
(2) フルスクリーンとコンパクト
コンパクトモードで表示するビューは、ゲームを開始するボタンだけを置き、フルスクリーンモードで表示するビューには、中央にボタンを9個スタックで並べました。
CompactモードとFullScreenモードは、それぞれ専用に作った2つのビューの表示・非表示を切り替えることで実装しました。
// MARK: - Private private func presentViewController(style: MSMessagesAppPresentationStyle) { compactView.isHidden = style == .expanded fullScreenView.isHidden = style == .compact }
上記のメソッドは、表示モードが切り替わるタイミングで呼ばれています。
// MARK: - Conversation Handling override func willBecomeActive(with conversation: MSConversation) { presentViewController(style: presentationStyle) } override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) { presentViewController(style: presentationStyle) }
3 データモデル
ゲームのデータは、「まる」・「ばつ」・「空白」の3種類のステータスを持つ、9個の配列で表現されています。
enum Kind { case Maru case Batu case None case Draw } class MaruBatu { var kinds: [Kind] = [.None, .None, .None, .None, .None, .None, .None, .None, .None] ・・・省略・・・
勝敗は、縦横斜めに揃っているかどうかで判定され、空白がなくなった時点で勝敗なし(Draw)となります。
func winner() -> Kind { if kinds[0] == kinds[1] && kinds[0] == kinds[2] { // 0,1,2 return kinds[0] } if kinds[0] == kinds[4] && kinds[0] == kinds[8] { // 0,4,8 return kinds[0] } if kinds[0] == kinds[3] && kinds[0] == kinds[6] { // 0,4,6 return kinds[0] } if kinds[1] == kinds[4] && kinds[1] == kinds[7] { // 1,4,7 return kinds[1] } if kinds[2] == kinds[4] && kinds[2] == kinds[6] { // 2,4,6 return kinds[2] } if kinds[2] == kinds[5] && kinds[2] == kinds[8] { // 2,5,8 return kinds[2] } if kinds[3] == kinds[4] && kinds[3] == kinds[5] { // 3,4,5 return kinds[3] } if kinds[6] == kinds[7] && kinds[6] == kinds[8] { // 6,7,8 return kinds[6] } if count() == 9 { return .Draw } return .None }
4 データの受け渡し
メッセージのやり取りの中でデータのやり取りには、MSMessageのurlプロパティを使用しています。
オブジェクトからは、対戦中の現在の9個のステータスを文字列化して取り出し、MSMessageにセットする際にURL型に変化します。
// 現在の対戦状況を文字列で返す func data() -> String { var result = "" for var kind in kinds { switch kind { case .None: result = result + "0" case .Maru: result = result + "1" case .Batu: result = result + "2" default: break } } return result }
メッセージ受信者は、選択したメッセージのurlプロパティが有効であれば、その文字列(対戦中の状態)を元に、オブジェクトを初期化し、ゲームを継続します。
if let message = conversation.selectedMessage { if let url = message.url { // 選択中のMSMessageが有効な場合、urlからデータを取得してMaruBatuを初期化する maruBatu = MaruBatu(data: url.absoluteString) } }
5 MSMessageTemplateLayout
メッセージは、MSMessageTemplateLayoutで表示しています。 MSMessageTemplateLayoutには、画像を設定するプロパティが有りますが、そこに、現在の対戦状況の画像を貼っています。
対戦状況を画像にしているコードは以下のとおりです。
// 現在の対戦状況を画像で返す func renderSticker() -> UIImage? { let baseSize = 100 // ひとマスのサイズは、縦横100 if let backImage = UIImage(named: "back.png") { if let maruImage = UIImage(named: "maru.png") { if let batuImage = UIImage(named: "batu.png") { UIGraphicsBeginImageContext(CGSize(width:baseSize * 3, height:baseSize * 3)) if winner() != .None { if let contextRef = UIGraphicsGetCurrentContext() { contextRef.setFillColor(winnerColor().cgColor) contextRef.fill(CGRect(x: 0, y: 0, width: baseSize * 3, height: baseSize * 3)); } } backImage.draw(in: CGRect(x: 0, y: 0, width: baseSize * 3, height: baseSize * 3)) for i in 0 ..< 9 { let x:Int = i % 3 let y:Int = i / 3 if kinds[i] == .Maru { maruImage.draw(in: CGRect(x: x * baseSize, y: y * baseSize, width: baseSize, height: baseSize)) } else if kinds[i] == .Batu { batuImage.draw(in: CGRect(x: x * baseSize, y: y * baseSize, width: baseSize, height: baseSize)) } } let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } } } return UIImage() }
また、その画像をMSMessageに設定しているコードは以下のとおりです。
var message = MSMessage(session: MSSession()) // MSMessageを生成 let layout = MSMessageTemplateLayout() // MSMessageTemplateLayoutを生成 layout.image = maruBatu.renderSticker() // imageプロパティに対戦中の画像を設定 message.layout = layout // MSMessageのlayoutプロパティに、上記layoutを設定 self.activeConversation?.insert(message, completionHandler: nil)
6 最後に
完成度は、かなり低いですが、一応、メッセージのセッション機能でゲーム(らしきもの)が、作成出来ることが確認できました。 すいません、説明不足な部分は、コードをご参照下さい。
[GitHub] https://github.com/furuya02/MaruBatuApp
7 参考リンク
iMessage + Apps
API Reference Messages Framework
iMessage Apps and Stickers, Part 1
iMessage Apps and Stickers, Part 2
SampleCode Ice Cream Builder: A simple Messages app extension