« July 2006 | Main | October 2006 »

August 25, 2006

企業が好む表現対策 Part3<磁石>::RM(JP)

磁石をflashで表現したがる人は多い。逃げ回る表現は企業は好きだ。吸い付く表現の次に好きである。マックOS風のメニューやナイキみたいのはさらに好きである。逃げ回る、吸い付く表現はUIとして使うと怒られる可能性があるが、キラキラ光るやキャストシャドーと同様、エフェクトとして利用すれば喜ばれることもある。もちろん企業は吹き出しも好きである。

逃げ回る表現
この表現をasで再現するにはムービークリップに速さを与えてマウスや他のオブジェクトの距離に比例するように速度に影響を与えて、向きを変えたり速さを変えたりすればよい。図のグラフのように距離が近ければ影響が大きければよいし、離れれば小さくなるようにすれば実現できそうだ。2つの距離が最も近いときにMaxForceが与えられる。

ベクトルで考える
物体を移動させるとき三角関数や平方根で距離や方向の計算をすることが必要となる。これはとても面倒なのでここではベクトルを使う。ベクトルは矢印だけで表現できる。まず紙面に図を描いて、足し算引き算とかけ算をするだけで最終的な物体の速度が割り出せる。あとでそれをコードにすればよい。flashにはもともとこの機能がないので、Vectorクラスを作成してある。このライブラリはダウンロードして自由に利用できる。

反発力
前述の通り四則演算だけで計算できる。ここではトラックしている赤い磁石はマウスで、マウスと黒い磁石が影響し合う関係にある。マウスの位置ベクトルと磁石の位置ベクトルを引き算して2つの物体間のベクトル(C)を計算する。このベクトルの長さは二点間の距離を表す。この距離から影響するフォースの大きさが求められる。グラフでわかるようにフォースが負になる可能性があるので、フォースが0より大きいときだけ磁石に影響を与えるようにする。フォースはベクトル(C)の向きを持つので、ベクトル(C)の単位ベクトルをフォースの長さ倍すればよい。

private function _execute():Void{
	var mouse:Vector = new Vector(_root._xmouse, _root._ymouse);
	for(var i in targetList){
		var magnet:Vector = new Vector(targetList[i]._x, targetList[i]._y);
		var magnetMinusMouse:Vector = Vector.minusVector(magnet, mouse);//ベクトル(C)
		var force:Number = -magnetMinusMouse.magnitude() / FieldStrength + MaxForce;
		if(force > 0){
			magnetMinusMouse.normalize();//単位ベクトル
			magnetMinusMouse._scale(force);//長さ倍
			dispatchEvent({type:"onForceDetected", data:{force:magnetMinusMouse, target:targetList[i]}});
		}
	}
}

黒い磁石
移動するムービークリップ(黒い磁石)に検出したフォースの影響を与えるには、算出したフォースをムービークリップの速度ベクトルに足し算するだけである。

public function addForce(v:Vector){
	this.v._add(v);
}

sourceも自由にダウンロードできる。

August 24, 2006

企業が好む表現対策 Part2<吹出し>::Roundtrip Clip(JP)

企業は吹き出しを使った表現方法が好きだ。吹き出しはクリックすると膨らむ、ロールオーバーすると膨らむ。マウスによるインタラクションは様々であるが、吹き出しが膨らみ終わるとコピーを表示するというのは共通している。そして何らかのインタラクションによって吹き出しを閉じる。なぜなら画面内を吹き出しで覆って他の表現要素を邪魔してしまうからである。吹き出しを使う場合、画面内の要素に対してスペースが小さいことが多く、すべての吹き出しを出したままにすることはない。今回は前回に引き続いて国内企業サイトでよく使われる表現を考えていく。

グラフィックを決定する
吹き出しは大きさが大きくなり最大の大きさになった時にコピーが表示される。閉じるときはその逆再生と考えて良いのでフレーム構成は30フレームなら正方向だけのアニメだけでよくなる。30フレーム目にコピーを配置すれば完全に膨らんだ状態でのみコピーが表示することができる。

今回のサンプルを実行すると以下のようになる。sourceもダウンロードでき、さらに動いているサンプルは記事の最後にある。

RoundtripClipクラス
この吹き出しをムービークリップを拡張してRoundtripClipクラスとすることから始める。この表現で使われる動きを整理すると、このグラフィックには膨らむ、縮む、止まるという3つの動作があることがわかる。これを実現するメソッドは、expand()、compress()、stop()とする。この3つの動作には、フレームの再生、逆再生、停止の3つの動作が起きることがわかる。正方向から逆方向再生、またその逆が出来るreverse()メソッドも追加してある。

public function expand():Void{
	state.expand();
}
public function compress():Void{
	state.compress();
}
public function stop():Void{
	state.stop();
}
public function reverse():Void{
	state.reverse();
}

状態を作成する
stateは、以下の4つのメソッドを持つことを保証している。

interface IState {
	function expand():Void;
	function compress():Void;
	function stop():Void;
	function reverse():Void;
}
再生、逆再生、停止の3つの状態があるので、上のIStateを実装した3つのクラスが必要になる。下のForwardStateでは、expand()が実行されている状態、すなわち再生中なので、expand()メソッドを呼び出す必要はなく空になっている。再生中に呼び出せるのは、吹き出しを縮める、停止、反転だけである。
public function ForwardState(rtc:RoundtripClip) {
	this.rtc = rtc;
	rtc.onEnterFrame = function(){
		var next:Number = this._currentframe + 1;
		var end:Number = this._totalframes;
		if(next > end){
			this.stop();
		}else{
			this.gotoAndStop(next);//最終フレームまで再生ヘッドを移動する
		}
	};
}
public function expand():Void{}//何もしない(今は再生状態なので)
public function compress():Void{
	rtc.setState(rtc.getBackwardState());//逆再生状態にする
}
public function stop():Void{
	rtc.setState(rtc.getStopState());//停止状態にする
}
public function reverse():Void{
	rtc.setState(rtc.getBackwardState());//逆再生状態にする(今は再生状態なので)
}
ForwardState.as

丸が膨張した状態でクリックすると停止し、再度クリックすると縮小可能になる。

August 23, 2006

企業が好む表現対策 Part1<占い>::Circuit Maker(JP)

企業は占いが好きだ。TVラジオ新聞雑誌などメディア全般で日々頻繁に現れる占いは企業のウェブサイトでも当然使う機会が多い。今回制作したものは毎月一定のテキストを日々巡回して表示するもので、一般に占いなどで使われる仕組みである。通常のランダム生成していることが多くないだろうか。でもこれでは同じテキストが3日連続で出ることもある。しかし、固定のリストを埋め込む方法もあるが、常に同じものが同じ順番に必ず出てしまう。ここではあり得そうなオーダーを考えて2つの条件を用意した。
※ワードとはここでは画面に表示する文字列を表す。

必要な要素をまとめる
まず、絶対に必要とされる要素として、「一ヶ月単位でワードを繰り返したい」「毎日違うテキストを表示したい」と、「ワードは日数分は用意出来ない」ということはよくあるだろう。一ヶ月単位でワードを繰り返すということは、一ヶ月30日の時に20種類のワードが用意されていた場合、20日間は重なることなく20種類出現し、残りの10日間は再び20種類の中から10種類が現れる。どういう機能を載せて実現していくか並べてみると、

・XMLから一定数のワードリストを読み込む(修正のしやすさを考慮)
・毎日違うテキストを表示する(基本オーダーより)
・同じ日は同じテキストを表示する(基本オーダーより)
・月ごとにリストを再生成する(応用として、余裕があれば)

動作の概要
まず、xmlから読み込まれたドキュメントからワードの配列を生成する。次に、31日分の配列を作成、その中にワードをループで順に挿入する。ワードを入れた配列をシャッフルして順の一定さを無くす。今日の日付をindexとして配列からワードを取り出す。一回目だけ作成されたリストがローカル共有オブジェクトに保存されて、2回目以降の訪問では保存されたリストからワードが取り出されるので、毎回ランダムにはならなくなる。月ごとに違うリストを持たせることもできる。

実際に動作しているサンプルは記事の最後にある。このサンプルではXMLから取り出した2つの文字を使って表示する。このsourceも自由に利用出来るようにしてある。

var cm:CircuitMaker;
var cl:CircuitXML = new CircuitXML();
function onXMLLoad(){
	cm = new CircuitMaker(cl.getList(), "uranai", false);
	result.text = (cm.getString());
}
cl.addEventListener("onXMLLoad", this);
cl.load("norm.xml");

XMLデータの構造
これが読み込まれるXMLでとても単純な構造でワードリストが定義されている。wordタグが実際表示されるワードを持つ。

<?xml version="1.0" encoding="UTF-8" ?>
<circuit>
<word id="1">日は夕焼け</word>
<word id="2">日は夕立</word>
</circuit>
norm.xml

XMLローダ
CircuitXMLは中にXMLを持ち、上で定義したXMLドキュメントを読み込み、CircuitMakerで利用できるデータ(配列)に変換するところまでの役割を持つ。

public function load(url):Void{
	var xml:XML = new XML();
	var owner:CircuitXML = this;
	xml.ignoreWhite = true;
	xml.onLoad = function(success:Boolean):Void{
		if(success){
			owner._o.list = owner._xmlToArray(this);
			owner.dispatchEvent({type:"onXMLLoad"});
		}else{
			trace("Loader error.");
		}
	};
	xml.load(url);
}
xmlから配列を生成する。これにはこのXMLの構造がnorm.xmlのような形式であることが分かっていなければならない。
private function _xmlToArray(xml:XML):Array{
	var strList:Array = xml.firstChild.childNodes;
	var i:Number;
	var result:Array = new Array();
	for(i = 0; i < strList.length; i++){
		result.push(strList[i].firstChild.nodeValue);//<word>nodeValue</word>
	}
	return result;
}

本体の作成
テキストを日付毎に巡回して表示するために最も重要なCircuitMakerクラスではXMLローダにより作成された配列データを基にしてまずローカル共有オブジェクトにあるかを確認する。すでに作成されていた場合そのリスを使うが、無い場合31日分のワードリストを作成し、ローカルに保存する。ここでString型に一度変換してデータサイズを小さくする工夫をしている。updateEveryMonthフラグがtrueの場合、このリスト生成を毎月必ず行う。

public function CircuitMaker(strList:Array, soName:String, updateEveryMonth:Boolean) {

	var now:Date = new Date();
	var so:SharedObject = SharedObject.getLocal(soName, "/");
	if(so.data.list == undefined){
		so.data.list = _createList(strList, CIRCUIT_SIZE).join(DELEMITER);//String変換して保存
		so.data.month = now.getMonth();//リストを作成した月も保存
	}else{
		if(updateEveryMonth && so.data.month != now.getMonth()){//月が変わった時も作成
			so.data.list = _createList(strList, CIRCUIT_SIZE).join(DELEMITER);
			so.data.month = now.getMonth();
		}
	}
	_list = so.data.list.split(DELEMITER);
}
31日分の配列にワードリストからワードが割り当てられ、乱数でindex間を入れ替えることで配列の順番をバラバラに組み直す。ワードリストの数単位でシャッフルすることが重要である。31個まとめてシャッフルをするとワードが連続する可能性があるためである。
private function _createList(arr:Array, size:Number):Array{
	var i, count:Number = 0;
	var len:Number = arr.length;
	var result:Array = new Array();
	while(1){
		var unit:Array = new Array();
		for(i = 0; i < len; i++){//ワードリストを取り出す
			unit[i] = arr[i];
			if(++count == size){//31になったらやめる
				result.push(_shuffleList(unit));
				while(result.length > 1){
					result[0] = result[0].concat(result.pop());//配列同士の連結
				}
				return result[0];
			}
		}
		result.push(_shuffleList(unit));
	}
}
private function _shuffleList(arr:Array):Array{
	var i:Number = 0;
	var len:Number = arr.length;
	for(i = 0; i < len; i++){
		 var tmp:Object = arr[i];
		 var randomNum:Number = random(len);
		 arr[i] = arr[randomNum];
		 arr[randomNum] = tmp;
	 }
	return arr;
}

August 20, 2006

Genetic Algorythm Part3(JP)

前回までの2回に渡り、ムービークリップで作られた虫に遺伝的アルゴリズムを載せて、新しく生成される虫が徐々に世代を経るにつれて環境に適応していく様子をシミュレートした。この遺伝的アルゴリズムを単純に虫ではなく生態系に適応させたい。まず今回は前提となる生態系を作成する。作成する生態系にはテントウムシ、カマキリ、餌、巣4つのオブジェクトがある。テントウムシとカマキリは自己の中に様々な状態を持たせてあり、この状態を様々な条件で遷移することで振る舞いを変えることができる。


緑:テントウムシ、茶:カマキリ


虫はカマキリとテントウムシの基本機能を持つ。HPの制御と状態遷移を管理するメソッドを持ち、action()メソッドにより行動を実行する。

import ai.*;
interface ai.IBug{
//状態管理
	function getState():State;
	function setState(state:State):Void;
//タイムライン
	function setMotion(num:Number):Void;
//HP管理
	function recover(hp:Number):Void;
	function detract(hp:Number):Void;
//現在の状態の行動の実行
	function action(world:IWorld):Void;
	function getHp():Number;
	function getId():Number;
//歩き回る
	function move():Void;
}
テントウムシ
テントウムシはこの生態系の中心となるオブジェクトである。基本的に繁殖して個体数を増やすのが目的で、個体が持つ生命力である変数HPを維持しながら行動をする。この生態的のアルゴリズムでは有効に食料を探す仕組みを載せていないので長期間生きることはない。テントウムシは一定以下にHPが低下すると死亡する。カマキリという天敵があることでHPの維持はさらに困難になる。この生態系ではカマキリがテントウムシの生命力に直接影響させることはないが、移動中にカマキリを発見した場合、巣への避難行動に切り替わる。実際は天敵を発見するアルゴリズムをファジー化した方がより行動に幅が出る。例えばテントウムシのHPの量やカマキリとの距離や巣の距離などを判断して脅威度数や健康度数を割り出し、巣へ移動するべきか、単純に方向を変えるかといった判断をすることである。
import ai.*;
interface ai.ILadybug extends IBug{
	function getSpawnState():State;
	function getLoverState():State;
	function getForageState():State;
	function getDeadState():State;
	function getGoHomeState():State;
//オブジェクトを探す
	function findFood():Boolean;
	function findLover():ILadybug;
	function canSeePredator():Boolean;
//巣に着いたか
	function isInNest():Boolean;
//各行動
	function goHome():Void;
	function spawn():Void;
	function dead():Void;
//子供を産んだか
	function setChild():Void;
	function getChild():Boolean;
}

カマキリ
カマキリはテントウムシの行動を左右させる天敵の役目を持つ。ただし、テントウムシの生命力を減らすなどの直接影響は与えない。通常は索敵状態となりテントウムシを探し、見つけると即座に追跡を始める。カマキリも生命力を持ち一定値になるとすべての行動を停止して休憩を始め、一定量までHPを回復する行動へ遷移する。

import ai.*;
interface ai.IRearhorse extends IBug{
	function getScoutState():State;
	function getChaseState():State;
	function getBreakState():State;
//オブジェクトを探す
	function findPrey():ILadybug;
//追跡行動
	function chase(t:GraphicObject):Void;	
}
以下で生態系のプレビューがみれる。
ソースグラフィックもダウンロード可能なので色々試してもらいたい。ここでは各オブジェクト間の計算にベクトルを使っているので数学クラスライブラリも別に用意してある。

カマキリをドラッグすると自由な位置に移動できる。

August 16, 2006

Resizable Video

I introduced working with youtube API and playing a ripped flv file in the flash player. This sample shows a player that the video can be moved and zoomed up and down freely in the stage.

source

It is composed of four objects; a video, a guide, a handle and an area. A flv plays under the area object that is a draggable rectangle as the same size as itself. It is used to move a video around in the stage and the handle is used to zoom it in the same way. The guide object will appear when a user drags a handle to resize the video.

August 10, 2006

Genetic Algorythm Part2

The genetic feature was added in the version, and it uses three new classes; Env class, GeneticWorld class and GeneticBug class. A GeneticWorld class creates a graph upper on the stage how much bugs fit a world. It has a Env object that refers to four elements; temperature, water, food and predator, can be changed with sliders right below on the stage. A GeneticBug class also has a Env object. A bug has its own elements in it, and they will be estimated for which bug should weed out when it evolve. As time is going by in the world, the least fit bug will weed out, and the fitness of bugs will be more suitable for a circumstance of the world.

The change of fitness of any bugs caused by mutations at age will appear with a red vertical line. You can download the source.

August 6, 2006

NetStream.play() method

The play() method has a number of optional parameters.

The first parameter must be either a relative URI or the value false, which stops the current stream from playing.

ns.play(URI | false, [, start [, length [, flushPlaylist]]]);

The start parameter
-2 or omitted
plays the live stream at the URI.
plays the recorded stream at that location if the live stream is not found.
waits for the stream to be published if a recored stream is also not found.

-1
plays only a live stream at the URI.
waits for the stream to be published if a recored stream is not found.

0
plays only a recorded stream at the URI from its beginning.
returns an infomation object to NetStream.onStatus() if a recored stream is not found.

Greater than 0
plays only a recorded stream at the URI.
indicates how many seconds into the stream to begin playing.
returns an infomation object to NetStream.onStatus() if a recored stream is not found.

The length parameter
indicates how many seconds the stream should play. The stream plays until it ends if it is -1 or omitted. The length can be limited for live streams to play with the length parameter.

The Playlist
To create a playlist in the server can provide that the server treats a sequence of recorded streams as a continuous stream.

ns.play("intro", 5, 15, false);
ns.play("body", 5, 18, false);
ns.play("ending", 10, 7, false);

August 4, 2006

youtubeAPI(JP)

youtubeは著作権絡みで日々削除と戦っている熱い映像データベースの一つで、最近ではgoogleなどの大手でも映像データベースを用意しているなどwebでの映像の扱いも変わってきている。将来的には世界中で公開されている映像を映像の中からキーワードから検索できるようになる日も来るのではないだろうか。ここでは後述のAPIの25個の人気映像を取得する機能を使ってサンプルを作ってみる。youtubeAPIはdeveloperIDさえ取得すれば検索、一覧表示などの機能がアクションスクリプトから利用できる。これは、youtubeが開発者向けに提供しているAPIセットでアドビからAS3向けのAPIへの紹介も貼られているほどで、この利用にAS3が必ずしも必要というわけではない。APIには残念ながら映像を直接取得する機能は提供されていない。youtubeAPIはxml形式でurlや映像の名前や作者などのデータを返すが、flvを直接指定できるリンクを返すことはないので他のswfでflvを読込むには別の方法が必要になる。
source

このxmlのurlは映像を再生するプレーヤーのついたyoutubeサイト内のhtmlページへのリンクを表している。そこで今回このサンプルではhtmlの中をsubstr()とindexOf()を使って検索してflvに再生に必要なファイル名と変数を取り出すことにした。

映像の読込を試す
まず、フラッシュでの映像の再生の基本を復習してみる。ここで注意するのはこの記述を関数の中でテンポラリに作成するのではなく、コンスタントに利用できるスコープに記述すること。この記述を間違うと制作環境では再生するのに、サーバ上にアップロードした後に再生しないといった不具合が発生するようだ。

var conn:NetConnection=new NetConnection();
conn.connect(null);
var s:NetStream=new NetStream(conn);
vid.attachVideo(s);//vidはVideoオブジェクト
s.play("test.flv");

htmlを検索する
先ほどの理由でflvはxmlから取得できない。そこでflvを取得するget_video.phpを利用する。このPHPファイルはyoutube上に存在し、ある変数を与えるとflvを返す。この変数はyoutubeサイトで使われているプレーヤーのswfにも与えているはずなので、htmlを検索すれば分かりそうだ。そこでまずlvにhtmlを読込み、onDataを使ってデータからSWFObjectの位置を検索する。onLoadはプロパティ=値型にパースが行われた後のデータになるので正しいデータの検索ができないのでここでは使わない。SWFObjectの位置をまず検索するのは、その後に検索したい変数がhtml内の至る所で使われているためである。html内のflv再生プレーヤーのswfの位置が分かったところで次に変数tと変数ncを取得する。このhtmlでflvを再生するために必要な変数は色々試した結果この2つである。

var lv:LoadVars=new LoadVars();
lv.onData = function(raw){
	var result:String = raw;
	var swfPos:Number = result.indexOf("SWFObject") + 9;
	var start:Number = result.indexOf("&t=", swfPos) + 3;
	var end:Number = result.indexOf("&nc=", start);
	var t:String = result.substr(start, end - start);
	URL = "http://www.youtube.com/get_video.php?
             video_id=" + v_id + "&t="+ t + "&.flv";
	};
lv.load("http://www.youtube.com/watch?v=" + xmlから取得できる映像のID);

取得したflvを再生する
URLを指定すれば再生が開始する。サーバが国内にないので若干バッファが足りないと映像が再生中に途切れてしまうことが多い。ここのサンプルでは5秒のバッファを用意した。アクションスクリプトでの初期値では足りない。

stream.setBufferTime(5);
stream.play(URL);
test.flvの再生

Spry Review Part3(JP)

In the second issue as a series of articles, I introduced how to use a loaded data with Spry in last month. The last of these, I will introduce how you use another data set in a data set.
You can see the preview here.

Namespace
It is used in the same way as xmlns in XML like "namespace::data" when you use the data in another name space. The "{dsAlbum::folder}" refers to a current value named "folder" in the dsAlbum.

var dsAlbum = new Spry.Data.XMLDataSet("album.xml", "/contents/album");
var dsPhoto = new Spry.Data.XMLDataSet("{dsAlbum::folder}.xml", "/album/pic");

The onChange event will be occur when a user clicks a different item in the list, and the "dsAlbum" in it will invoke the setCurrentRow() method with a value of the selected item. The method changes the current row in the data set. Next, the "dsPhoto" will invoke loadData() method with the current value to reload its own data set.

<div id="albumList" spry:region="dsAlbum">
アルバム<br>
<select onChange="dsAlbum.setCurrentRow(this.value);dsPhoto.loadData();" spry:repeatchildren="dsAlbum">
	<option value="{ds_RowID}">{name}</option>
</select>
</div>

The {} tags of a folder and a image will be displace with a real string dynamically. Notice that two data is set in the 'spry:detaionregion' attribute of a div tag.

<div id="photo" spry:detailregion="dsPhoto dsAlbum">
<img src="{dsAlbum::folder}/{dsPhoto::url}">
</div>

A xml loaded into dsPhoto (gab.xml)

<?xml version="1.0" encoding="UTF-8" ?><album><pic><url>IMG_4577_small.jpg</url></pic><pic><url>IMG_4580_small.jpg</url></pic><pic><url>IMG_4583_small.jpg</url></pic><pic><url>IMG_4598_small.jpg</url></pic><pic><url>IMG_4600_small.jpg</url></pic><pic><url>IMG_4603_small.jpg</url></pic><pic><url>IMG_4608_small.jpg</url></pic><pic><url>IMG_4609_small.jpg</url></pic><pic><url>IMG_4612_small.jpg</url></pic><pic><url>IMG_4627_small.jpg</url></pic><pic><url>IMG_4633_small.jpg</url></pic><pic><url>IMG_4639_small.jpg</url></pic><pic><url>IMG_4656_small.jpg</url>