この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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