退職のお知らせ
2010年6月30日を持ちまして、株式会社スナップを退職しました。
非常にゆったりとした環境で、様々な事を学ぶ事ができました。
育てて頂いた恩を返せず心残りとなっていますが、偉大な先輩方に追いつくべく、自分をもっと向上させるべく、今回の決断に至りました。
7月1日からは、メタルレッドにてお世話になります。
これからもよろしくお願いします。
和泉俊樹
2010年6月30日を持ちまして、株式会社スナップを退職しました。
非常にゆったりとした環境で、様々な事を学ぶ事ができました。
育てて頂いた恩を返せず心残りとなっていますが、偉大な先輩方に追いつくべく、自分をもっと向上させるべく、今回の決断に至りました。
7月1日からは、メタルレッドにてお世話になります。
これからもよろしくお願いします。
和泉俊樹
2010年6月13日,Adobe Station 5にて「Spark project 勉強会 SP3」が開催されました。
僕は「AIR2.0でマルチタッチ」という題で発表しました。
当日の動画はUstreamで録画されていますので、御覧下さい。
ルーレットの魔力で大トリになってしまったので、最後から10分くらいで発表しております。
当日の資料はこちらになります。
MultiTouchSpriteはSparkProjectにコミットしました。
Demoもコミットしてあります。
http://www.libspark.org/svn/as3/MultiTouchSprite/trunk/
DemoはFlashDevelopのプロジェクトとして読み込めます。
また、一つ前のエントリにて、少し解説をしています。
http://blog.alterna.in/2010/04/18-230815.php
いまのところ、touchPointIDをいい感じに取得することができないので、それも早いこと考えたいものです。
久しぶりの話す機会が、初SparkProject勉強会で、しかもStation5で、しかも大トリで。ということで緊張しました。今考えたら話す構成もめちゃくちゃだし、いろいろ反省すべき部分は多いですが、楽しかったです。
また、Ust録画してもらったりとか、レポートに掲載されたりとか初めての体験なのですごくうれしいです。
今年は人前に出る機会を増やしていけたらなーと思っていて、そのためにはいろいろ作らないとね!ってことです。
FlashPlayer10.1には、マルチタッチに対応したコンテンツに対応するために、新しいAPIが用意されています。
基本的には、Multitouch.inputModeを定義し、flash.events.TouchEventやflash.events.TransformGestureEventなどで値を受け取ることになります。
TransformGestureEventについては、すでにAdobe AIRデベロッパーセンターに掲載されている
「マルチタッチ機能を活用した AIR 2.0 アプリケーション Touch Viewer の紹介 | デベロッパーセンター」
が非常にわかりやすいです。
今回はTouchEventで効率的にタッチポイントを管理する方法について書きたいと思います。
TouchEventのタイプは
TOUCH_BEGIN
TOUCH_END
TOUCH_MOVE
TOUCH_OUT
TOUCH_OVER
TOUCH_ROLL_OUT
TOUCH_ROLL_OVER
TOUCH_TAP
以上の8つで、TouchEventではタッチ関連のプロパティが取得できます。タッチを管理していく上でメインになってくるのは touchPointID, localX, localYです。touchPointIDには、それぞれのタッチポイントにユニークなIDが割り振られており、localX, localYは、文字通りタッチポイントの座標です。
今回は、TOUCH_BEGIN, TOUCH_END, TOUCH_MOVEの3つにリスナを登録しておき、touchPointIDを添字としたPoint型のVectorにlocalX,localYを格納していく、という方法です。
実際には以下のようなコードになります。
public function MultiTouchSprite() { Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; _startPoint = new Vector.<Point>(10); _currentPoint = new Vector.<Point>(10); _previewPoint = new Vector.<Point>(10); for (var i:int = 0; i < 10; i++) { _startPoint[i] = new Point(); _currentPoint[i] = new Point(); _previewPoint[i] = new Point(); } addEventListener(TouchEvent.TOUCH_BEGIN, _touchBeginHandler); addEventListener(TouchEvent.TOUCH_MOVE, _touchMoveHandler); addEventListener(TouchEvent.TOUCH_END, _touchEndHandler); } private function _touchBeginHandler(e:TouchEvent):void { _startPoint[e.touchPointID].x = e.localX; _startPoint[e.touchPointID].y = e.localY; }
今回はstartPoint, currentPoint, previewPointとい3つのVectorを用意しました。これでcurrentPoint[touchPointID].x, currentPoint[touchPointID].yとかすると、座標が取れます。
あと、僕の環境だと、touchPointIDが2から始まるんですけど、これってなんなんですかね。
ほかの環境でもそうでしょうか。
現在のタッチ個数を得るために、touchPointというint型を用意しておき、TOUCH_BEGINで++, TOUCH_ENDで–しています。
private function _touchBeginHandler(e:TouchEvent):void { _touchPoints++; } private function _touchEndHandler(e:TouchEvent):void { _touchPoints--; }
また、今回はタップ、ダブルタップを使えるようにしました。
TouchEvent.TOUCH_TAPを使うと、TOUCH_BEGIN, TOUCH_END, TOUCH_MOVE, TOUCH_TAPと4つのイベントすべてが発生してしまうので、それを回避するためです。
その部分のコードはこんな感じになります。
private function _touchBeginHandler(e:TouchEvent):void { _touchDownTime = getTimer(); } private function _touchEndHandler(e:TouchEvent):void { _touchPoints--; if (_touchPoints == 0) _touching = false; if (getTimer() - _touchDownTime < TIME_DIFF) { var dist:Number = Math.sqrt(Math.pow(_currentPoint[e.touchPointID].x - _startPoint[e.touchPointID].x, 2) + Math.pow(_currentPoint[e.touchPointID].y - _startPoint[e.touchPointID].y, 2)); if (Math.abs(dist) < POS_DIFF) _touchTap(e); } } public function _touchTap(e:TouchEvent):void { _tapCnt++; if (!_timer.running) { _timer.addEventListener(TimerEvent.TIMER_COMPLETE, _timerHandler); _timer.start(); } } private function _timerHandler(e:TimerEvent):void { switch(_tapCnt) { case 1 : atSingleTap.call(); break; case 2 : atDoubleTap.call(); break; case 3 : atTripleTap.call(); break; default:break; } _tapCnt = 0; }
2つの条件を満たした時にTimer.start()させます。タイマー作動中にもう一度2つの条件を満たした場合はtapCountを増やし、timerイベントがディスパッチされたタイミングでtapCountを判定し、シングルタップ、ダブルタップを判断します。この処理を利用すれば、3回や4回など、それ以上の回数も取れます。
ネックはラグが発生することですかねー。
また、イベントのハンドリングはProgressionのCommandやAS2と同じように関数を与えておき、適切な場所でそいつを実行してやることにしました。
MultiTouchSprite.atTouchBegin = function():void { trace(currentPoint[touchObj.touchPointID]); };
最初はExTouchEventというのを作ってディスパッチしていたんですけど、Flash Pratform Campで、Mike Chambersがディスパッチはパフォーマンス低下するよ!って言ってたので変更しました。
また当初Array型を使ってPointを管理していたのをVector型に変更し、パフォーマンス向上を図っています。
MultiTouchSpriteというクラスとしてまとめておいたので、使ってもらえるとうれしいです。
http://alterna.in/demo/MultiTouchSprite/MultiTouchSprite.zip

コード画面でコードをかき、ボタンを押すとレンダリングしてくれます。
なぜかiPad版もあります。iPad版は左にコード、右にレンダリング結果が表示されます。
@ll_koba_ll Processing jsやったらiPhone(iPad)で書いて実行とかできるのか
http://twitter.com/ll_koba_ll/status/11632081826
という@ll_koba_llさんのtwitterでの発言を見て、「あ、それほしい」となって、ここらへんの記事を読んだら意外と簡単そうだったので作ってみました。
まだまだほしいものがあったり(リファレンス的なものとか、twitpicへの投稿機能とか)しますが、通勤中にコードをかけるというのは素晴らしいです。
UITextViewのテキストを取り出して\nを削除し、UIWebViewでそのコードを実行してあげてるだけです。(すべての\nを削除しているために問題が発生しているみたいですが・・・)
あと作っているうちにiPhoneのオンライン版を発見したりとか本家がすごいのリリースしたりとかでびっくりしました。
でも、wonderP5だとhtmlとJSファイルをアプリ内にバンドルしているので、オフラインでも使えるよ!っていうのがイイトコだと思います。
ライセンス的にプロジェクトファイルの公開が可能かどうかがわからないので、問題がないことがわかれば公開します。
また、ベータ版として配信ということもできるかなーと思います。
完成度が上がれば、リリースもあるかもしれません。
埋め込みフォントって大変ですよね。
最近までずっとIDEから埋め込みをしていました。
パブリッシュに時間がかかるばかりで「もーいや!」と思っていたら、どうやらASから指定することができるようです。
FACEs: as3:フォントのダイナミックなローディング
CS3版 フォントのダイナミックなローディング | エントリー | _level0.KAYAC
ということでコマンドにしてみました。
Progression4です!
public function EmbedFontDynamicLoader(path:String, name:String = null, textfield:TextField = null, initObject:Object = null )
@param path フォントファイルへの相対パス
@param name フォントのリンケージ名
@param textfield フォントを適応するTextField
@param initObject initObject.sizeのようにTextFormatと同じように指定すると、TextFormatとして適応されます。
使い方1「読み込みを行い、TextFieldにフォント適応をする」
new EmbedFontDynamicLoader("font.swf", "KozukaGo_Pro_R", textField )
上記のようなコマンドを書くと、第三引数として指定したTextFieldに”Helvetica_Bold_16″が適応されます。
AIRで利用する際には、EmbedFontDyamicLoderコマンドの以下のコメントを外してください。
//_context.allowLoadBytesCodeExecution = true;
new EmbedFontDynamicLoader("font.swf", "KozukaGo_Pro_R", textField , { size:16, color:0xFF00FF } )
initObjectに、TextFormatの各種プロパティと一致する用に記述をすると、第三引数として指定したTextFieldにsetTextFormat()されます。
また、第三引数を省略した場合はinitObjectにtextFormatを指定していても無視されます。
必要なフォントファイルの読み込みだけを先に行い、適応は別に行う場合は上記のように記述します。
new EmbedFontDynamicLoader("font.swf")
読み込みを行い、Resourceに登録することで、読み込みの時間を短縮させたい時に利用します。
フォントファイルの読み込みにはLoadSWFコマンドを利用していますが、そのコマンドを実行するのみの処理になります。
僕はIndexSceneで使い方3で一気にswfファイルを読み込み、使い方2でTextFormatと一緒に指定するという使い方をよく使います。
コマンドの中身で、特別なことをしているわけではなく、CS3版のコードを使い、TextFormatを適応してあげただけです。
デモではIndexSceneにこんなコードを書いています。
package { import flash.text.TextField; import jp.progression.casts.*; import jp.progression.commands.display.*; import jp.progression.commands.lists.*; import jp.progression.commands.net.*; import jp.progression.commands.tweens.*; import jp.progression.commands.*; import jp.progression.data.*; import jp.progression.events.*; import jp.progression.executors.*; import jp.progression.scenes.*; /** * ... * @author ... */ public class IndexScene extends SceneObject { var textField:TextField; public function IndexScene() { textField = new TextField(); textField.text = "test"; container.addChild(textField); title = "EmbedFontDynamicLoaderDemo"; } protected override function atSceneLoad():void { addCommand( new EmbedFontDynamicLoader("font.swf", "KozukaGo_Pro_R", null ), 3, new EmbedFontDynamicLoader("font.swf", "KozukaGo_Pro_R", textField), 3, new EmbedFontDynamicLoader("font.swf", "KozukaGo_Pro_R", textField, { color:0x999999 } ) ); } } }
ソースファイル一式を置いておくので、ぜひいじってフィードバックください!書き換えてください!
http://alterna.in/demo/EmbedFontDynamicLoaderDemo/EmbedFontDynamicLoaderDemo.zip