ゲームを作ってみた
2014.4.19
Cocoa勉強会 関西
大森智史
@oogon / satoshi.oomori
あんた、誰?
• と、いうわけで自己紹介。
• 大森智史といいます。
• 某印刷会社勤務。
• Cocoa勉強会関西は第0回からいます。
ゲーム
• ガッポガッポでウハウハらしい。
• 当然モテモテらしい。
• 作るしかない!
…でも作ったことない
• どうやって作るんだろうか
• Cocos-2Dとかあるけど
• Sprite Kitでしょ
参考資料
• Sprite Kit iPhone 2Dゲームプログラミング
• http://www.amazon.co.jp/dp/479804055X/
参考資料
• Sprite Kitもろもろ
• http://spritekit.jp
さて今日は
• 試行錯誤で作ってみましたので間違い・  
もっといい方法があるかもしれません。
はてなポイント
• スライドにこのマークが付いているところは
よくわかってないところです。
• 随時ツッコミをお願いします。
• 某社サービスとは無関係です。
?
どんなのを作るか
• シューティングゲーム
• 単純明快なもの
どんなのを作るか
• 宇宙船
• 空中戦
• 地上攻撃
• きれいなグラフィック
どんなのを作るか
• 中ボス登場
• 大ボス登場
• アンドアジェネシス!
参考資料
• ゼビウス
• http://ja.wikipedia.org/wiki/ゼビウス
• ゼビウス軍事パレード
• http://www22.tok2.com/home/takaakit/xevi/
parade.html
まずはサンプル
• Appleのサンプルを動かしてみる。
• とりあえずSKSpriteNode=キャラクタのよう
なのでそれを動かしてみれば良いみたいだ。
2014.2.15
• プロジェクト始動!
いろいろさわってみる
• サンプルをもとにスプライト画像をさしかえ
たり、アクションを変更したりしてみる。
重大な問題
• 絵が描けない。
• 描く道具がない。
• 困った。
Nintendo 3DS
• 「うごめも」で描いてみる。
• パラパラ漫画が描ける。
• 着色はPixelMetorで(Photoshopより安い)
?
参考資料
• Nintendo3DS
• http://www.nintendo.co.jp/3ds/
参考資料
• Nintendo3DS
• http://www.nintendo.co.jp/3ds/
参考資料
• うごくメモ帳3D
• http://www.nintendo.co.jp/3ds/eshop/jkzj/
index.html
とりあえず描いてみた
• うごメモからGIF出力
• 着色はPixelMetorで
• PNGファイルにする
次は敵キャラ
• 敵キャラも作る。
• まずは簡単な動きのキャラクタから。
• でも、鉄板くるくる回したい!
アトラス
• アトラス(SKTextureAtlas)っていうのでパ
ラパラ漫画にするといいようだ。
• パラパラ漫画なら「うごめも」!
?
アトラス
!
• テクスチャだけ
SKSpriteNode *obj = [SKSpriteNode spriteNodeWithImageNamed:@“Spaceship"];
• テクスチャアトラス
SKTextureAtlas *pla...
ちょっとしたTips
• 連番画像を使うとき@“%03d”とすると
001,002,003…とできます。
for (int i=0; i < numImages; i++) {
NSString *textureName =
[NSString...
アトラス
• テクスチャアトラスはRetina用と非Retina用
がいるんだそうだ。Retina用は”@2x”を追加
• ビルド設定
SPRITEKIT_TEXTURE_ATLAS_OUTPUT = YES
にしておかないといけない
• とり...
参考文献
• テクスチャアトラス
• http://www.raywenderlich.com/ja/51748/
sprite-kit-チュートリアル-アニメーションと
テクスチャ
衝突
• ぶつかったらやっぱり爆発するよね
• エミッターからパーティクルが飛んでいく
• パーティクルクラスっていうのはないので注
意が必要
エミッター
//エミッターの作成
NSString *path = [[NSBundle mainBundle]
          pathForResource:@"FireParticle" ofType:@"sks"];
static ...
パーティクル
• エミッターから飛んで行くもの
• パーティクルクラスはないよ!
• エミッタークラスを探せ!
?
とりあえず作って
動かしてみた
整理
• シーンにごにょごにょ書いていたので、見苦
しい。
• オブジェクト志向っぽく書こう
• SKSpriteNodeを使っていたけれど、キャラ
クタごとにサブクラス作成。自分のことは自
分でするように。 ?
• クラス分け
自機クラス
敵キャラクラス
シーンクラス
シーンクラス
!
!
!
!
!
!
!
!
!
自機の動き
敵キャラの動
き
タッチ処理等
タッチ処理等
枠の外
• キャラクタが枠の外に出たら消さないと。
• -(void)didSimulatePhysics ?
枠の外
• いや、ここはやはり時間が取れる     
-(void)update:(CFTimeInterval)currentTime 
を使うべきかと
• このメソッドが呼ばれた時、前回呼ばれた時
間との間隔を測り、一定時間が経っていたら
...
枠の外
• 「後日談」
• (今回は0.3秒間隔で)としていたが…
• →やはり0.015秒間隔で(1/60=0.0166..)
敵キャラの出現
• ランダムだと、パターン攻略本出せないよね。
• 一定の法則で敵キャラを出すには? ?
敵キャラ
• タイマー?
• さっき、一定時間ごとにチェックするように
したんだから、それを使えばいいやん
?
敵キャラ
• 出現時間をリストにして、時間が来たらキャ
ラクタを出すようにした。
• NSDictionaryをNSArrayで
• 将来的には別ファイルにして「面」構成に
敵キャラ
• 出現時間をリストにして、
NSArray *anArray =
@[@{@"name":@"teppan",@"time":@1.0f,@"posX":@100.0,@"pos
Y":@600.0},
@{@“name":@"te...
敵キャラ
• リストに基づいてキャラクタを作成するようにした。
//0.3秒間隔で調査
if ((beforeTime + 0.3f) < currentTime){
[enemyArray enumerateObjectsUsingBlock...
敵キャラ
• 毎回オブジェクトを作っているけど使い回し
したほうがいいのか?
?
敵キャラ
• 「後日談」
• できれば面の最初にオブジェクトを作っておく
ほうが良い。
• まあでも毎回作っても大丈夫だけどアトラスの
作成には時間がかかるので、最低限アトラスだ
けは先に作っておいて使いまわしする。ほうが
よい
?
• 敵キャラが規則
的に出現するよ
うになった
サウンド
• 音がないと寂しいよね。
• 効果音とBGM
重大な問題
• 作曲できない。
• 楽器も演奏できない。
• 困った…。
ガレージバンドで
• なんとかやってみる。
• 売るときはプロに頼めばいいか。
効果音
• NodeにActionとしてつけるとよいらしい。
?
EmitterNodeにAction
SKAction *soundPlay;
!
soundPlay = [SKAction playSoundFileNamed:@"bomb.m4a" waitForCompletion:NO];
!
//...
BGM
• AVFoundationを使うらしい
?
BGM
@property (nonatomic) AVAudioPlayer *
backgroundMusicPlayer;
!
//BGM
NSError *error;
NSURL * backgroundMusicURL = [[NS...
背景
• 背景も真っ暗だと寂しい。宇宙みたい。
• 地面の絵がほしいよね。
• ナスカの地上絵とか
どうするんだろう
• 小さなノードをブロック上に置く
• 大きな一枚絵のノードを作る
• どちらにしても、はみでる部分を継ぎ足し継
ぎ足し
• 今回は大きなノードを作ることに
?
?
• 背景を動かしてみた
速度は大丈夫か
• Air 11インチのシミュレータでは30fpsに落ち
るが、iPhone5s実機では60fpsを保ったまま
• 大丈夫みたい。 ?
背景画像を変えてみる
• ノードを作り直す
• ノードはそのままで、テクスチャ画像を変え
る
• →下のほうがコストが低そう ?
背景画像を変えてみる
• 「後日談」
• テクスチャ画像を変える方法で作ったが、微妙に「カ
クッ」と動く
• →結局最初に面の背景を全部作っておくことにしま
した。
• 途中で作り直すことにした。最初から常にパフォー
マンスを意識して制作しない...
背景もパラパラしたい
• atlasをたくさん作って切り替える ?
• パタパタと2種類の
背景を切り替えな
がら4枚の背景をつ
なげて動かしてみ
た。
砲弾
• 自機から砲弾が飛ぶようにしたい。
• またサブクラスを作って動作を定義
?
• 砲弾を発射してみた
当たったら終了
• 自機が爆発したら終了
• 別のシーンに切り替えたい。
• presentScene:transition:でよいようだ。
?
• 自機が破壊された
ら終了画面に行く
遅延移行
• 爆発後少し余韻を残すため、2秒後に
presentScene:を呼び出しています。
//シーンの遅延移行
-(void)delayPresentScene:(NSDictionary *)obj;
{
SKView * skVie...
参考文献
• Sprite Kitリファレンス
• シーン遷移等
• http://cocoaapi.hatenablog.com/entry/
SpriteKit/index
整理
• キャラクターが増えてきたので、共通部分を
抜き出し、キャラクタのベースクラスとなる
抽象クラスを作成
• 定期的なアップデート、衝突時の処理、範囲
外に出た場合など共通の処理を記載
• ベースクラス作成
キャラクタクラス
!
!
自機クラス
敵キャラクラス
自機クラス
敵キャラクラス
くるくる動く敵キャラ
• まっすぐ向かってくるだけではつまらないの
で、一定の法則で動くキャラを作る
• 動きはベジェパスで作ることができる。
• 速さはspeedで設定可能
パスで動きをつけるとは?
• パスは計算
式で曲線を
表すことが
出来ます。
→位置を計
算できる
• http://www.slideshare.net/oogon/cocoa201303pdf
?
• パスで敵キャラの
動きを作ってみた
でもね…
• パス作るのってなんだかわけがわからない。
• イラストレーターとかでSVG書き出し?
• ごもっとも!
モーションパスエディタ
• こんなのを作ってみました。
• 作っている最中ですが、デモ...
参考文献
• パスで動きを作る
• http://stackoverflow.com/questions/
19215223/spritenode-not-following-cgpath
砲弾が当たるように
• シーンで衝突が起こると、呼ばれるメソッド
//衝突検知
-(void)didBeginContact:(SKPhysicsContact *)contact
{
}
• このメソッドで衝突したオブジェクトがわか
るので衝...
砲弾が当たるように
• この処理も大きくなりがちなので、各オブジェ
クトに移していきたい。
• 当たり判定とか調
整してみてゲームっ
ぽく動く状態に
再スタート
• ゲームオーバーになったあと、もう一度ゲー
ムをするための処理を行う
• 一旦別のシーンに移ったあともう一度プレイ
シーンに戻るには?
• 別のシーンに移るときに、プレイシーンを保
持しておいて、プレイシーンに戻ればいい。
?
?
再スタート
• 移行先シーンにプロパティを設定して
@property (retain) SKScene* beforeScene;
!
• プレイシーンから移行するときに保持してお
く ?
ノードをボタンに
• 再スタートボタンはどうする?
SKLabelNode?
• スプライトノードを作ってボタンにしてもい
いか。
?
?
タッチしたノードは?
• タッチした部分にあるノードは何?か調べて
SKNode *nodeAtPoint = [self nodeAtPoint:[touch
locationInNode:self]];
• あとはノードの名前で処理を変える...
• 再スタートボタン
をつけてみた
参考資料
• SKLabelNodeをボタンにする
• http://spritekit.jp/tutorial/example/
参考資料
• チュートリアル
• http://www.raywenderlich.com/42699/
spritekit-tutorial-for-beginners
ゲームのやり直し
• 前のシーンの続きになっているので、ゲーム
を最初からやり直すには、ゲーム設定を戻す
ことが必要。
• いままで、シーンの初期化メソッドに書いて
いたのを-(void)startGameというメソッドに
まとめることに。
• ゲームのやり直し
スコア
• そうだ、スコアを残そう
• とりあえずラベルで。余裕ができたらスプラ
イトノードで数字を表示しよう!
• スコアの表示
実機で
• このあたりから実機で確認することに
• 音付きで
オープニング
• いきなりゲームが始まるのもおかしいので、
オープニングシーンを作ってみる。
• スコアシーンと同じ
• オープニング画面
自動運転
• テスト面倒。
• デモ画面では自機が自動的に操作されている。
• 自機を自動操縦させよう。
自動運転
• 敵キャラの発生と同じように時間が来たら指
定の操作をすればいいんではないか?
• 自動操縦画面
自動運転
• 物理シミュレーションは毎回同じ結果になる
とは限らないようだ。
• →と、当初考えていたけれど、初期化にかか
る時間の違いで少しのズレが発生していた。
初期化が終わってから開始時間を0にするこ
とで解消。
?
地上敵キャラ
• 今度は地上の敵キャラを作る
• また同じようにキャラクタクラスを作って…
地上敵キャラ
• ん?自機が地上敵キャラに隠れる時があるぞ。
そうか、あとから描画するほうが上へ上へと
くるのか
• 奥行きの順をコントロール
するにはどうするのだ?
レイヤー
• zPositionというプロパティを使うみたいだ。
数字が大きくなるほど手前に来る。
• デフォルトは1のようなので、地上物は3、空
中物は5、スコアは7ぐらいにしておこう。
?
• 上下関係を直してみた。
参考資料
• シーンを組み立てる
• http://spritekit.jp/tutorial/scene/
自由移動
• 今まで水平方向にしか動かなかったけれど前
後方向もうごかせるようにした。
• 自由に、とはいっても動くことのできる範囲
を限定するためのコードを追加
• 自由に動き回れる
ようになった
整理
• 当たり判定を、シーンでゴニョゴニョしてい
るので、オブジェクト指向的に処理したい。
• シーンで衝突時にSKPhysicsContactをそれぞ
れのオブジェクトに渡す。それぞれのオブジェ
クトで処理を行う。シーンでは判断しない。
整理
• ベースクラスに衝突時のメソッドを作る。
• サブクラスでそれぞれの処理をオーバーライ
ドする。
• キャラクタの独立性を高めて、キャラクタが
増えた時の対応をしやすくする
整理
• シーンでの-didBiginContact{}の処理がSKPhysicsContactオブ
ジェクトを渡すだけになった。
• ここで、ベースクラスOOOChatacterNodeを作った効果が出
てくる。
-(void)didBegi...
シーンクラス
!
-(void)didBeginContact:
(SKPhysicsContact *)contact
{
!
!
}
• 衝突時の処理
自機クラス
-(void)contact:
(SKPhysicsCon
tact *)c...
敵砲弾
• 敵キャラも砲弾を
敵砲弾
• 敵も砲弾を撃つ
いっしょに動く
• 地上攻撃のマーカーを作りたい。
• 自機と一緒に動くノードSKPhysicsJointFixed
で自機と接続
• ハマった所:シーンにすでに接続するものが
ないと例外発生
地上照準を表示
• 自機と一緒に動
くマーカーを表
示
もろもろ調整
• プレイしながら細かいところを修正する
画面が暗くならないよう
• デモプレイしているとタッチしないのでスリー
プする。
• スリープしないようにするにはデリゲートで
application.idleTimerDisabled = YES;として
やればよい。(ゲームが終了・バックグ...
参考資料
• iOSデバイスをスリープしないようにする
• http://program.station.ez-net.jp/special/
handbook/objective-c/uiapplication/
idletimer.asp
終了画面から
タイトル画面へ
• 再スタートボタンしかなかったので終了して
からタイトル画面に戻るボタンを付けた。
• 一定の時間で強制的にタイトルに戻るように
したほうがいいかもしれない。 ?
パフォーマンス向上
• 概ね60fpsで動いているが、背景画像の切替
時にもたつきが発生している。
• 画像差し替え時の読み込みに時間がかかって
いる?
• 事前にバックグラウンドで読み込んでおく?
?
パフォーマンス向上
• SKTextureAtlasの作成時に時間がかかっているようなので
事前に画像を読み込み、非同期読み込み。
-(void)preloadAtlas:(int)atlasNumber
{
dispatch_async(di...
パフォーマンス向上
• 回転する鉄板もサイズは大きくないが15枚つ
かっているので、atlas読み込み時に時間がか
かっている模様。
• 同じキャラクタでアトラスを使いまわしでき
るよう、キャッシュする。
?
パフォーマンス向上
• アトラスをキャッシュすることオブジェクト
の独立性が薄くなる懸念がある。
• メモリが許せば面に登場するキャラクタオブ
ジェクトを最初に全部作っておくこともいい
かも?
• →出来る限り最初に作成しておく!
?
パフォーマンス改善後
• アトラスをキャッ
シュすることで
もたつき感解消
地上攻撃
• 地上のターゲットを攻撃するときの処理を加
えた ?
地上ターゲットを攻撃
• 地上のターゲッ
トを攻撃する処
理を追加
敵キャラAI
• 敵キャラに、状況に応じて行動させる。
• 敵キャラと自機の距離が一定以下になったら
砲弾を発射
• SKActionで、位置チェック、敵キャラごとに
違う処理の実装可能 ?
敵キャラAI
• 敵キャラと自機の距離が200pt以下になった場合砲弾を発射する。
SKAction *attackAction = [SKAction customActionWithDuration:2.0 actionBlock:^(SK...
面構成
• ボスキャラを倒したら面クリアというのが定
番
• 速度重視のために最初にリソースを読み込ん
でおく事が必要なため、このパターンが生ま
れた。
?
面構成
• ユーザーの体調面を考慮して、休憩を挟むこ
とが必要かと。
• ボスキャラを倒す、他はある地点まで到達す
ることで面クリア。 ?
面クリア
• ボスキャラを倒
したら面クリア
ポーズ対応
• シナリオ実行は経過時間で実行しているので
バックグラウンドに入った時などには対応が
必要。(開始時刻ー現在時刻=経過時間)
• 停止時間を計測して開始時刻に足してやる。
?
残機設定
• 自機が1回衝突になったらGame Overになっ
ていたが、酷すぎるので最初は3機からス
タート。
• Score sceneを改良して、残機が0の場合と0
以上の場合で分けてみる
残機設定
• ゲームスタートの設定も、新規ゲームと残機
スタートで、クリアする内容を分ける
キャラクタの動作
• キャラクタの動作はキャラクタで固定のパスだったけれ
ど、シナリオファイルにmotionとしてパスを持たせるこ
とにした。
• ベジェパスの内、点の移動、直線描画、曲線描画を辞書
で持たせて、ベジェパスを作成する。コードから...
キャラクタの動作
キャラクタの動作
NSArray *maruMotionArray =
@[@{@"command":@"move",@"x":@0.0,@"y":@0.0},
@{@"command":@"curve",@"x":@150.0,@"y":@-...
パワーアップアイテム
• アイテムを取ると自機の機能が強化される。
• 敵キャラを応用して、衝突時に自機が消滅し
ないようにして、パワーアップフラグを立て
ればOK
パワーアップアイテム
• パワーアップアイテ
ムを取ると砲弾が0.5
間隔で発射されるよ
うになる
音声合成
• パワーアップアイテムが出現したら音声合成
でお知らせしてみた。
• これだけではさみしいので、スタート時に日
本語を話すようにしてみた。
AVSpeechUtterance
参考資料
• iOSアプリ開発メモメモ
• http://iosmemo.ou-net.com/?p=314
実機デモ
今後の課題
• iPad,Mac対応。アスペクト比が違うので、何
か対応がいるのかな。
• 広告。ライトゲームだと無料+広告モデルが
主体だろうと思うので、使いこなせるように
今後の課題
• Game center対応。Game centerが変わるよ
うな があるので、変わってからにしようか
なと。
• 課金。面構成のゲームだと無料+追加面とい
うシステムも取れるのかなあと。
ゲームを作ってみて
• いくつか感じたことを述べたいと思います。
ゲームを作ってみて
• パフォーマンスを常に意識して作る。カクカク
する動きはダメなので、そのような動きになら
ないように常に気を使うこと。
• 速度落ちそうなことはやらない。
• 実機重要。Sprite KitはGPUの効果で高速化がな
され...
ゲームを作ってみて
• フレームワークを使えばわりと簡単にできる。
技術的には敷居は高くない。
ゲームを作ってみて
• Sprite Kitの後に、Scene Kitという3Dゲーム
フレームワークが控えているので(今はMac
のみ)応用できそう。
ゲームを作ってみて
• クラスの独立重要
• キャラクタをクラスで分割したり、シナリオ
ファイルを作ったり、分業できるシステムで
作っておくと、一旦ゲームシステムを作れば、
あとは人海戦術で対応出来る。
ゲームを作ってみて
• Apple TVとかきっとゲームできる用な感じで
出てくるんじゃないかと思う。
ゲームを作ってみて
• だいたい作り方はわかったので、設定変えて
オリジナルゲーム作ってみたいなあと。
ゲームを作ってみて
• ガッポガッポは「アイデア」と「運」次第
ありがとうございました
• デモ用のiPod touchを持ってきていますの
で、休憩時・懇親会などに試してみて意見を
聞かせてください。
Upcoming SlideShare
Loading in...5
×

Cocoa勉強会20140419ゲームをつくってみる

2,952

Published on

1 Comment
1 Like
Statistics
Notes
  • 動画はこちら。(途中で音が出ます)
    http://youtu.be/1x4srcrf1Kc
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
2,952
On Slideshare
0
From Embeds
0
Number of Embeds
10
Actions
Shares
0
Downloads
9
Comments
1
Likes
1
Embeds 0
No embeds

No notes for slide

Cocoa勉強会20140419ゲームをつくってみる

  1. 1. ゲームを作ってみた 2014.4.19 Cocoa勉強会 関西 大森智史 @oogon / satoshi.oomori
  2. 2. あんた、誰? • と、いうわけで自己紹介。
  3. 3. • 大森智史といいます。 • 某印刷会社勤務。 • Cocoa勉強会関西は第0回からいます。
  4. 4. ゲーム • ガッポガッポでウハウハらしい。 • 当然モテモテらしい。 • 作るしかない!
  5. 5. …でも作ったことない • どうやって作るんだろうか • Cocos-2Dとかあるけど • Sprite Kitでしょ
  6. 6. 参考資料 • Sprite Kit iPhone 2Dゲームプログラミング • http://www.amazon.co.jp/dp/479804055X/
  7. 7. 参考資料 • Sprite Kitもろもろ • http://spritekit.jp
  8. 8. さて今日は • 試行錯誤で作ってみましたので間違い・   もっといい方法があるかもしれません。
  9. 9. はてなポイント • スライドにこのマークが付いているところは よくわかってないところです。 • 随時ツッコミをお願いします。 • 某社サービスとは無関係です。 ?
  10. 10. どんなのを作るか • シューティングゲーム • 単純明快なもの
  11. 11. どんなのを作るか • 宇宙船 • 空中戦 • 地上攻撃 • きれいなグラフィック
  12. 12. どんなのを作るか • 中ボス登場 • 大ボス登場 • アンドアジェネシス!
  13. 13. 参考資料 • ゼビウス • http://ja.wikipedia.org/wiki/ゼビウス • ゼビウス軍事パレード • http://www22.tok2.com/home/takaakit/xevi/ parade.html
  14. 14. まずはサンプル • Appleのサンプルを動かしてみる。 • とりあえずSKSpriteNode=キャラクタのよう なのでそれを動かしてみれば良いみたいだ。
  15. 15. 2014.2.15 • プロジェクト始動!
  16. 16. いろいろさわってみる • サンプルをもとにスプライト画像をさしかえ たり、アクションを変更したりしてみる。
  17. 17. 重大な問題 • 絵が描けない。 • 描く道具がない。 • 困った。
  18. 18. Nintendo 3DS • 「うごめも」で描いてみる。 • パラパラ漫画が描ける。 • 着色はPixelMetorで(Photoshopより安い) ?
  19. 19. 参考資料 • Nintendo3DS • http://www.nintendo.co.jp/3ds/
  20. 20. 参考資料 • Nintendo3DS • http://www.nintendo.co.jp/3ds/
  21. 21. 参考資料 • うごくメモ帳3D • http://www.nintendo.co.jp/3ds/eshop/jkzj/ index.html
  22. 22. とりあえず描いてみた • うごメモからGIF出力 • 着色はPixelMetorで • PNGファイルにする
  23. 23. 次は敵キャラ • 敵キャラも作る。 • まずは簡単な動きのキャラクタから。 • でも、鉄板くるくる回したい!
  24. 24. アトラス • アトラス(SKTextureAtlas)っていうのでパ ラパラ漫画にするといいようだ。 • パラパラ漫画なら「うごめも」! ?
  25. 25. アトラス ! • テクスチャだけ SKSpriteNode *obj = [SKSpriteNode spriteNodeWithImageNamed:@“Spaceship"]; • テクスチャアトラス SKTextureAtlas *plateAnimatedAtlas = [SKTextureAtlas atlasNamed:@"spaceship"]; NSUInteger numImages = plateAnimatedAtlas.textureNames.count; for (int i=0; i < numImages; i++) { NSString *textureName = [NSString stringWithFormat:@"%03d", i]; SKTexture *temp = [plateAnimatedAtlas textureNamed:textureName]; [plateFrames addObject:temp]; }
  26. 26. ちょっとしたTips • 連番画像を使うとき@“%03d”とすると 001,002,003…とできます。 for (int i=0; i < numImages; i++) { NSString *textureName = [NSString stringWithFormat:@"%03d", i]; SKTexture *temp = [plateAnimatedAtlas textureNamed:textureName]; [plateFrames addObject:temp]; }
  27. 27. アトラス • テクスチャアトラスはRetina用と非Retina用 がいるんだそうだ。Retina用は”@2x”を追加 • ビルド設定 SPRITEKIT_TEXTURE_ATLAS_OUTPUT = YES にしておかないといけない • とりあえずRetina用だけで作っとく ?
  28. 28. 参考文献 • テクスチャアトラス • http://www.raywenderlich.com/ja/51748/ sprite-kit-チュートリアル-アニメーションと テクスチャ
  29. 29. 衝突 • ぶつかったらやっぱり爆発するよね • エミッターからパーティクルが飛んでいく • パーティクルクラスっていうのはないので注 意が必要
  30. 30. エミッター //エミッターの作成 NSString *path = [[NSBundle mainBundle]           pathForResource:@"FireParticle" ofType:@"sks"]; static SKEmitterNode* spark2 = nil; spark2 = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; spark2.position = contact.contactPoint; spark2.numParticlesToEmit = contact.collisionImpulse; //アクション SKAction *fadeOut = [SKAction fadeOutWithDuration:1.0f]; SKAction *remove = [SKAction removeFromParent]; SKAction *sequence = [SKAction sequence:@[fadeOut, remove ]];   [spark2 runAction:sequence];   [spark2 runAction:soundPlay]; !   [self addChild:spark2];
  31. 31. パーティクル • エミッターから飛んで行くもの • パーティクルクラスはないよ! • エミッタークラスを探せ! ?
  32. 32. とりあえず作って 動かしてみた
  33. 33. 整理 • シーンにごにょごにょ書いていたので、見苦 しい。 • オブジェクト志向っぽく書こう • SKSpriteNodeを使っていたけれど、キャラ クタごとにサブクラス作成。自分のことは自 分でするように。 ?
  34. 34. • クラス分け 自機クラス 敵キャラクラス シーンクラス シーンクラス ! ! ! ! ! ! ! ! ! 自機の動き 敵キャラの動 き タッチ処理等 タッチ処理等
  35. 35. 枠の外 • キャラクタが枠の外に出たら消さないと。 • -(void)didSimulatePhysics ?
  36. 36. 枠の外 • いや、ここはやはり時間が取れる      -(void)update:(CFTimeInterval)currentTime  を使うべきかと • このメソッドが呼ばれた時、前回呼ばれた時 間との間隔を測り、一定時間が経っていたら チェックすることに(今回は0.3秒間隔で) ?
  37. 37. 枠の外 • 「後日談」 • (今回は0.3秒間隔で)としていたが… • →やはり0.015秒間隔で(1/60=0.0166..)
  38. 38. 敵キャラの出現 • ランダムだと、パターン攻略本出せないよね。 • 一定の法則で敵キャラを出すには? ?
  39. 39. 敵キャラ • タイマー? • さっき、一定時間ごとにチェックするように したんだから、それを使えばいいやん ?
  40. 40. 敵キャラ • 出現時間をリストにして、時間が来たらキャ ラクタを出すようにした。 • NSDictionaryをNSArrayで • 将来的には別ファイルにして「面」構成に
  41. 41. 敵キャラ • 出現時間をリストにして、 NSArray *anArray = @[@{@"name":@"teppan",@"time":@1.0f,@"posX":@100.0,@"pos Y":@600.0}, @{@“name":@"teppan",@"time":@3.0f,@"posX":@200.0,@"posY" :@600.0}, ……
  42. 42. 敵キャラ • リストに基づいてキャラクタを作成するようにした。 //0.3秒間隔で調査 if ((beforeTime + 0.3f) < currentTime){ [enemyArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([[obj objectForKey:@"time"] floatValue] < (currentTime - durationTime) ) { if ([[obj objectForKey:@"name" ] isEqualToString:@"teppan"]) { … } … … } … } … }
  43. 43. 敵キャラ • 毎回オブジェクトを作っているけど使い回し したほうがいいのか? ?
  44. 44. 敵キャラ • 「後日談」 • できれば面の最初にオブジェクトを作っておく ほうが良い。 • まあでも毎回作っても大丈夫だけどアトラスの 作成には時間がかかるので、最低限アトラスだ けは先に作っておいて使いまわしする。ほうが よい ?
  45. 45. • 敵キャラが規則 的に出現するよ うになった
  46. 46. サウンド • 音がないと寂しいよね。 • 効果音とBGM
  47. 47. 重大な問題 • 作曲できない。 • 楽器も演奏できない。 • 困った…。
  48. 48. ガレージバンドで • なんとかやってみる。 • 売るときはプロに頼めばいいか。
  49. 49. 効果音 • NodeにActionとしてつけるとよいらしい。 ?
  50. 50. EmitterNodeにAction SKAction *soundPlay; ! soundPlay = [SKAction playSoundFileNamed:@"bomb.m4a" waitForCompletion:NO]; ! //エミッターの作成 NSString *path = [[NSBundle mainBundle]           pathForResource:@"FireParticle" ofType:@"sks"]; static SKEmitterNode* spark2 = nil; spark2 = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; spark2.position = contact.contactPoint; spark2.numParticlesToEmit = contact.collisionImpulse; //アニメーション SKAction *fadeOut = [SKAction fadeOutWithDuration:1.0f]; SKAction *remove = [SKAction removeFromParent]; SKAction *sequence = [SKAction sequence:@[fadeOut, remove ]];   [spark2 runAction:sequence];   [spark2 runAction:soundPlay]; !   [self addChild:spark2]; ココ ココ
  51. 51. BGM • AVFoundationを使うらしい ?
  52. 52. BGM @property (nonatomic) AVAudioPlayer * backgroundMusicPlayer; ! //BGM NSError *error; NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"BGM" withExtension:@"m4a"]; self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error]; ! self.backgroundMusicPlayer.numberOfLoops = -1; [self.backgroundMusicPlayer prepareToPlay]; [self.backgroundMusicPlayer play];
  53. 53. 背景 • 背景も真っ暗だと寂しい。宇宙みたい。 • 地面の絵がほしいよね。 • ナスカの地上絵とか
  54. 54. どうするんだろう • 小さなノードをブロック上に置く • 大きな一枚絵のノードを作る • どちらにしても、はみでる部分を継ぎ足し継 ぎ足し • 今回は大きなノードを作ることに ? ?
  55. 55. • 背景を動かしてみた
  56. 56. 速度は大丈夫か • Air 11インチのシミュレータでは30fpsに落ち るが、iPhone5s実機では60fpsを保ったまま • 大丈夫みたい。 ?
  57. 57. 背景画像を変えてみる • ノードを作り直す • ノードはそのままで、テクスチャ画像を変え る • →下のほうがコストが低そう ?
  58. 58. 背景画像を変えてみる • 「後日談」 • テクスチャ画像を変える方法で作ったが、微妙に「カ クッ」と動く • →結局最初に面の背景を全部作っておくことにしま した。 • 途中で作り直すことにした。最初から常にパフォー マンスを意識して制作しないといけません。 ?
  59. 59. 背景もパラパラしたい • atlasをたくさん作って切り替える ?
  60. 60. • パタパタと2種類の 背景を切り替えな がら4枚の背景をつ なげて動かしてみ た。
  61. 61. 砲弾 • 自機から砲弾が飛ぶようにしたい。 • またサブクラスを作って動作を定義 ?
  62. 62. • 砲弾を発射してみた
  63. 63. 当たったら終了 • 自機が爆発したら終了 • 別のシーンに切り替えたい。 • presentScene:transition:でよいようだ。 ?
  64. 64. • 自機が破壊された ら終了画面に行く
  65. 65. 遅延移行 • 爆発後少し余韻を残すため、2秒後に presentScene:を呼び出しています。 //シーンの遅延移行 -(void)delayPresentScene:(NSDictionary *)obj; { SKView * skView = (SKView *)self.view; [skView presentScene:[obj objectForKey:@"scene"] transition:[obj objectForKey:@"transition"]]; } ! ! [self performSelector:@selector(delayPresentScene:) withObject:dic afterDelay:2];
  66. 66. 参考文献 • Sprite Kitリファレンス • シーン遷移等 • http://cocoaapi.hatenablog.com/entry/ SpriteKit/index
  67. 67. 整理 • キャラクターが増えてきたので、共通部分を 抜き出し、キャラクタのベースクラスとなる 抽象クラスを作成 • 定期的なアップデート、衝突時の処理、範囲 外に出た場合など共通の処理を記載
  68. 68. • ベースクラス作成 キャラクタクラス ! ! 自機クラス 敵キャラクラス 自機クラス 敵キャラクラス
  69. 69. くるくる動く敵キャラ • まっすぐ向かってくるだけではつまらないの で、一定の法則で動くキャラを作る • 動きはベジェパスで作ることができる。 • 速さはspeedで設定可能
  70. 70. パスで動きをつけるとは? • パスは計算 式で曲線を 表すことが 出来ます。 →位置を計 算できる • http://www.slideshare.net/oogon/cocoa201303pdf ?
  71. 71. • パスで敵キャラの 動きを作ってみた
  72. 72. でもね… • パス作るのってなんだかわけがわからない。 • イラストレーターとかでSVG書き出し? • ごもっとも!
  73. 73. モーションパスエディタ • こんなのを作ってみました。 • 作っている最中ですが、デモ...
  74. 74. 参考文献 • パスで動きを作る • http://stackoverflow.com/questions/ 19215223/spritenode-not-following-cgpath
  75. 75. 砲弾が当たるように • シーンで衝突が起こると、呼ばれるメソッド //衝突検知 -(void)didBeginContact:(SKPhysicsContact *)contact { } • このメソッドで衝突したオブジェクトがわか るので衝突処理
  76. 76. 砲弾が当たるように • この処理も大きくなりがちなので、各オブジェ クトに移していきたい。
  77. 77. • 当たり判定とか調 整してみてゲームっ ぽく動く状態に
  78. 78. 再スタート • ゲームオーバーになったあと、もう一度ゲー ムをするための処理を行う • 一旦別のシーンに移ったあともう一度プレイ シーンに戻るには? • 別のシーンに移るときに、プレイシーンを保 持しておいて、プレイシーンに戻ればいい。 ? ?
  79. 79. 再スタート • 移行先シーンにプロパティを設定して @property (retain) SKScene* beforeScene; ! • プレイシーンから移行するときに保持してお く ?
  80. 80. ノードをボタンに • 再スタートボタンはどうする? SKLabelNode? • スプライトノードを作ってボタンにしてもい いか。 ? ?
  81. 81. タッチしたノードは? • タッチした部分にあるノードは何?か調べて SKNode *nodeAtPoint = [self nodeAtPoint:[touch locationInNode:self]]; • あとはノードの名前で処理を変える if ([nodeAtPoint.name isEqualToString: @"restart"]) { OOOLabelButtonNode *startButton = (OOOLabelButtonNode *)nodeAtPoint; startButton.highlighted = YES; }
  82. 82. • 再スタートボタン をつけてみた
  83. 83. 参考資料 • SKLabelNodeをボタンにする • http://spritekit.jp/tutorial/example/
  84. 84. 参考資料 • チュートリアル • http://www.raywenderlich.com/42699/ spritekit-tutorial-for-beginners
  85. 85. ゲームのやり直し • 前のシーンの続きになっているので、ゲーム を最初からやり直すには、ゲーム設定を戻す ことが必要。 • いままで、シーンの初期化メソッドに書いて いたのを-(void)startGameというメソッドに まとめることに。
  86. 86. • ゲームのやり直し
  87. 87. スコア • そうだ、スコアを残そう • とりあえずラベルで。余裕ができたらスプラ イトノードで数字を表示しよう!
  88. 88. • スコアの表示
  89. 89. 実機で • このあたりから実機で確認することに
  90. 90. • 音付きで
  91. 91. オープニング • いきなりゲームが始まるのもおかしいので、 オープニングシーンを作ってみる。 • スコアシーンと同じ
  92. 92. • オープニング画面
  93. 93. 自動運転 • テスト面倒。 • デモ画面では自機が自動的に操作されている。 • 自機を自動操縦させよう。
  94. 94. 自動運転 • 敵キャラの発生と同じように時間が来たら指 定の操作をすればいいんではないか?
  95. 95. • 自動操縦画面
  96. 96. 自動運転 • 物理シミュレーションは毎回同じ結果になる とは限らないようだ。 • →と、当初考えていたけれど、初期化にかか る時間の違いで少しのズレが発生していた。 初期化が終わってから開始時間を0にするこ とで解消。 ?
  97. 97. 地上敵キャラ • 今度は地上の敵キャラを作る • また同じようにキャラクタクラスを作って…
  98. 98. 地上敵キャラ • ん?自機が地上敵キャラに隠れる時があるぞ。 そうか、あとから描画するほうが上へ上へと くるのか • 奥行きの順をコントロール するにはどうするのだ?
  99. 99. レイヤー • zPositionというプロパティを使うみたいだ。 数字が大きくなるほど手前に来る。 • デフォルトは1のようなので、地上物は3、空 中物は5、スコアは7ぐらいにしておこう。 ?
  100. 100. • 上下関係を直してみた。
  101. 101. 参考資料 • シーンを組み立てる • http://spritekit.jp/tutorial/scene/
  102. 102. 自由移動 • 今まで水平方向にしか動かなかったけれど前 後方向もうごかせるようにした。 • 自由に、とはいっても動くことのできる範囲 を限定するためのコードを追加
  103. 103. • 自由に動き回れる ようになった
  104. 104. 整理 • 当たり判定を、シーンでゴニョゴニョしてい るので、オブジェクト指向的に処理したい。 • シーンで衝突時にSKPhysicsContactをそれぞ れのオブジェクトに渡す。それぞれのオブジェ クトで処理を行う。シーンでは判断しない。
  105. 105. 整理 • ベースクラスに衝突時のメソッドを作る。 • サブクラスでそれぞれの処理をオーバーライ ドする。 • キャラクタの独立性を高めて、キャラクタが 増えた時の対応をしやすくする
  106. 106. 整理 • シーンでの-didBiginContact{}の処理がSKPhysicsContactオブ ジェクトを渡すだけになった。 • ここで、ベースクラスOOOChatacterNodeを作った効果が出 てくる。 -(void)didBeginContact:(SKPhysicsContact *)contact { //それぞれのオブジェクトに衝突情報を渡す [(OOOCharacterNode *)contact.bodyA.node contact:contact]; [(OOOCharacterNode *)contact.bodyB.node contact:contact]; }
  107. 107. シーンクラス ! -(void)didBeginContact: (SKPhysicsContact *)contact { ! ! } • 衝突時の処理 自機クラス -(void)contact: (SKPhysicsCon tact *)contact{} 敵キャラクラス -(void)contact: (SKPhysicsCon tact *)contact{} シーンクラス ! -(void)didBeginContact: (SKPhysicsContact *)contact { ! ! ! ! ! ! } 自機の処理 敵キャラの処理 contactオブジェクト contactオブジェク contactオブジェクト
  108. 108. 敵砲弾 • 敵キャラも砲弾を
  109. 109. 敵砲弾 • 敵も砲弾を撃つ
  110. 110. いっしょに動く • 地上攻撃のマーカーを作りたい。 • 自機と一緒に動くノードSKPhysicsJointFixed で自機と接続 • ハマった所:シーンにすでに接続するものが ないと例外発生
  111. 111. 地上照準を表示 • 自機と一緒に動 くマーカーを表 示
  112. 112. もろもろ調整 • プレイしながら細かいところを修正する
  113. 113. 画面が暗くならないよう • デモプレイしているとタッチしないのでスリー プする。 • スリープしないようにするにはデリゲートで application.idleTimerDisabled = YES;として やればよい。(ゲームが終了・バックグラウ ンドになる時はNOにしておく) ?
  114. 114. 参考資料 • iOSデバイスをスリープしないようにする • http://program.station.ez-net.jp/special/ handbook/objective-c/uiapplication/ idletimer.asp
  115. 115. 終了画面から タイトル画面へ • 再スタートボタンしかなかったので終了して からタイトル画面に戻るボタンを付けた。 • 一定の時間で強制的にタイトルに戻るように したほうがいいかもしれない。 ?
  116. 116. パフォーマンス向上 • 概ね60fpsで動いているが、背景画像の切替 時にもたつきが発生している。 • 画像差し替え時の読み込みに時間がかかって いる? • 事前にバックグラウンドで読み込んでおく? ?
  117. 117. パフォーマンス向上 • SKTextureAtlasの作成時に時間がかかっているようなので 事前に画像を読み込み、非同期読み込み。 -(void)preloadAtlas:(int)atlasNumber { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGR OUND, 0), ^{ // Background operations preloadTextureAtlas = [SKTextureAtlas atlasNamed:[NSString stringWithFormat:@"ground%03d", groundNum]]; //dispatch_async(dispatch_get_main_queue(), ^{ // Main Thread //}); }); } ?
  118. 118. パフォーマンス向上 • 回転する鉄板もサイズは大きくないが15枚つ かっているので、atlas読み込み時に時間がか かっている模様。 • 同じキャラクタでアトラスを使いまわしでき るよう、キャッシュする。 ?
  119. 119. パフォーマンス向上 • アトラスをキャッシュすることオブジェクト の独立性が薄くなる懸念がある。 • メモリが許せば面に登場するキャラクタオブ ジェクトを最初に全部作っておくこともいい かも? • →出来る限り最初に作成しておく! ?
  120. 120. パフォーマンス改善後 • アトラスをキャッ シュすることで もたつき感解消
  121. 121. 地上攻撃 • 地上のターゲットを攻撃するときの処理を加 えた ?
  122. 122. 地上ターゲットを攻撃 • 地上のターゲッ トを攻撃する処 理を追加
  123. 123. 敵キャラAI • 敵キャラに、状況に応じて行動させる。 • 敵キャラと自機の距離が一定以下になったら 砲弾を発射 • SKActionで、位置チェック、敵キャラごとに 違う処理の実装可能 ?
  124. 124. 敵キャラAI • 敵キャラと自機の距離が200pt以下になった場合砲弾を発射する。 SKAction *attackAction = [SKAction customActionWithDuration:2.0 actionBlock:^(SKNode *node, CGFloat elapsedTime){ NSArray *nodes = [self.parent children]; [nodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { SKNode *node = (SKNode *)obj; if ([node.name isEqualToString: @"spaceship"]){ float deltaX = node.position.x - self.position.x; float deltaY = node.position.y - self.position.y; float delta = sqrtf(pow(deltaX,2.0) + pow(deltaY,2.0)); //NSLog(@"%.2f",delta); if (delta > 200.0) { NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@150.0,@"posX", [SKTransition fadeWithDuration:1],@"transition", nil]; if (!fireing){ [self performSelector:@selector(fireEnemyShell:) withObject:dic afterDelay: 0.0f]; fireing = YES; } } } }]; }]; [obj runAction:attackAction];
  125. 125. 面構成 • ボスキャラを倒したら面クリアというのが定 番 • 速度重視のために最初にリソースを読み込ん でおく事が必要なため、このパターンが生ま れた。 ?
  126. 126. 面構成 • ユーザーの体調面を考慮して、休憩を挟むこ とが必要かと。 • ボスキャラを倒す、他はある地点まで到達す ることで面クリア。 ?
  127. 127. 面クリア • ボスキャラを倒 したら面クリア
  128. 128. ポーズ対応 • シナリオ実行は経過時間で実行しているので バックグラウンドに入った時などには対応が 必要。(開始時刻ー現在時刻=経過時間) • 停止時間を計測して開始時刻に足してやる。 ?
  129. 129. 残機設定 • 自機が1回衝突になったらGame Overになっ ていたが、酷すぎるので最初は3機からス タート。 • Score sceneを改良して、残機が0の場合と0 以上の場合で分けてみる
  130. 130. 残機設定 • ゲームスタートの設定も、新規ゲームと残機 スタートで、クリアする内容を分ける
  131. 131. キャラクタの動作 • キャラクタの動作はキャラクタで固定のパスだったけれ ど、シナリオファイルにmotionとしてパスを持たせるこ とにした。 • ベジェパスの内、点の移動、直線描画、曲線描画を辞書 で持たせて、ベジェパスを作成する。コードから切り離 すことで、モーションエディタで動作の設定が可能 • 動的なベジェパスの作成は、AIでの動作にも応用できる
  132. 132. キャラクタの動作
  133. 133. キャラクタの動作 NSArray *maruMotionArray = @[@{@"command":@"move",@"x":@0.0,@"y":@0.0}, @{@"command":@"curve",@"x":@150.0,@"y":@-100.5,@"cp1x":@60.0 ,@"cp1y":@-110.5,@"cp2x":@40.0,@"cp2y":@-90.5}, @{@"command":@"curve",@"x":@-50.0,@"y":@-340.0,@"cp1x":@-100 .0,@"cp1y":@-100.0,@"cp2x":@-110.0,@"cp2y":@-350.0}, @{@"command":@"curve",@"x":@500.0,@"y":@-140.5,@"cp1x":@400. 0,@"cp1y":@-240.0,@"cp2x":@600.0,@"cp2y":@-40.0} ]; ! 上記パスをシナリオ配列に組み込む 20秒後に出現するキャラクタに動きを設定 @{@"name":@"maru",@"time":@20.0f,@"posX":@100.0,@"posY":@600 .0,@"motion":maruMotionArray},
  134. 134. パワーアップアイテム • アイテムを取ると自機の機能が強化される。 • 敵キャラを応用して、衝突時に自機が消滅し ないようにして、パワーアップフラグを立て ればOK
  135. 135. パワーアップアイテム • パワーアップアイテ ムを取ると砲弾が0.5 間隔で発射されるよ うになる
  136. 136. 音声合成 • パワーアップアイテムが出現したら音声合成 でお知らせしてみた。 • これだけではさみしいので、スタート時に日 本語を話すようにしてみた。 AVSpeechUtterance
  137. 137. 参考資料 • iOSアプリ開発メモメモ • http://iosmemo.ou-net.com/?p=314
  138. 138. 実機デモ
  139. 139. 今後の課題 • iPad,Mac対応。アスペクト比が違うので、何 か対応がいるのかな。 • 広告。ライトゲームだと無料+広告モデルが 主体だろうと思うので、使いこなせるように
  140. 140. 今後の課題 • Game center対応。Game centerが変わるよ うな があるので、変わってからにしようか なと。 • 課金。面構成のゲームだと無料+追加面とい うシステムも取れるのかなあと。
  141. 141. ゲームを作ってみて • いくつか感じたことを述べたいと思います。
  142. 142. ゲームを作ってみて • パフォーマンスを常に意識して作る。カクカク する動きはダメなので、そのような動きになら ないように常に気を使うこと。 • 速度落ちそうなことはやらない。 • 実機重要。Sprite KitはGPUの効果で高速化がな されているので、実機でないと60fpsでない。
  143. 143. ゲームを作ってみて • フレームワークを使えばわりと簡単にできる。 技術的には敷居は高くない。
  144. 144. ゲームを作ってみて • Sprite Kitの後に、Scene Kitという3Dゲーム フレームワークが控えているので(今はMac のみ)応用できそう。
  145. 145. ゲームを作ってみて • クラスの独立重要 • キャラクタをクラスで分割したり、シナリオ ファイルを作ったり、分業できるシステムで 作っておくと、一旦ゲームシステムを作れば、 あとは人海戦術で対応出来る。
  146. 146. ゲームを作ってみて • Apple TVとかきっとゲームできる用な感じで 出てくるんじゃないかと思う。
  147. 147. ゲームを作ってみて • だいたい作り方はわかったので、設定変えて オリジナルゲーム作ってみたいなあと。
  148. 148. ゲームを作ってみて • ガッポガッポは「アイデア」と「運」次第
  149. 149. ありがとうございました • デモ用のiPod touchを持ってきていますの で、休憩時・懇親会などに試してみて意見を 聞かせてください。
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×