TweeeStorm

先日の御茶ノ水アーチナイト向けに作ったtweiter表示部分のテスト版。最終的にはいくつもの要素を組み合わせて作り上げるモノの場合でも、なるべく単体テストできる状態で作り進めるようにしている。ちょっとした見栄えやCPU負荷の状況を確認した上で、結合してからの問題に対処したほうが、効率良い場合が多いからだ。

もちろん、結合後に大幅に変えてしまうことも少なくない。実際これも本番までには半分以上のコードを書き換えたはずだ。それでもアイデアを定着させ、問題点を早めに見つけ、切り分けるのにはむしろ効率的な手順なのだ。

TweeeStorm – wonderfl build flash online

twitterの検索から#nhkというハッシュタグを持つtweetを取得し、螺旋を描くように動かしている(nhkにした理由はなんとなくニュートラルで、アルファベットの検索文字で日本語のツイートがたくさんあるから)。ゆっくりと息をするように上下しているのは、各文字の中心軸が文字の中心に無いから。
また、Zソートはしていない。どうせ文字同士は重なってもこの出し方だとわかりにくいし。本番ではいくつかの工夫をして、一つ一つの文字同士のソートをしないまま、ほぼ自然に感じられる動きにした。
[sourcecode language=”as3″]
package {
import flash.display.Sprite;
import flash.events.Event;
/**
* …
* @author umhr
*/
[SWF(backgroundColor="0x000000",width=465,height=465)]
public class WonderflMain extends Sprite {
private var _biscuitBox:BiscuitBox = new BiscuitBox();
private var _serchBar:SerchBar = new SerchBar("#nhk");
public function WonderflMain():void {

addChild(_biscuitBox);

_serchBar.addEventListener(Event.COMPLETE, serchBar_complete);
_serchBar.visible = false;
addChild(_serchBar);
}

private function serchBar_complete(event:Event):void
{
_biscuitBox.setXML(_serchBar.loadedXML);
}
}
}

import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.text.TextField;
import flash.text.TextFieldType;
/**
* …
* @author umhr
*/
class SerchBar extends Sprite
{
private var _serchWord:String;
public var loadedXML:XML;
public function SerchBar(serchWord:String = "")
{
init(serchWord);
}
private function init(serchWord:String):void
{
_serchWord = serchWord;
if (stage) onInit();
else addEventListener(Event.ADDED_TO_STAGE, onInit);
}

private function onInit(event:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, onInit);
// entry point

//背景
var titleArea:Shape = new Shape();
titleArea.graphics.beginFill(0x39CCFF);
titleArea.graphics.drawRect(0, 0, 465, 23);
addChild(titleArea);
//Serchの文字
var title:TextField = new TextField();
title.text = "Serch:";
title.width = 33;
title.height = 18;
title.x = 2;
title.y = 2;
title.textColor = 0xFFFFFF;
addChild(title);
//テキスト入力欄。キーボードのキーを押し上げた際にonKeyUpが実行される
var inputArea:TextField = new TextField();
inputArea.text = _serchWord;
inputArea.width = 426;
inputArea.height = 18;
inputArea.x = 36;
inputArea.y = 2;
inputArea.border = true;
inputArea.borderColor = 0xFFFFFF;
inputArea.background = true;
inputArea.backgroundColor = 0x99DDFF;
inputArea.type = TextFieldType.INPUT;
inputArea.addEventListener(KeyboardEvent.KEY_UP, title_keyUp);
addChild(inputArea);

serch(inputArea.text);
}

private function title_keyUp(event:KeyboardEvent):void
{
//Enterキーのキーコード13だったら、serchメソッドを実行。引数には適すと入力欄の文字列
if (event.keyCode == 13) {
serch(event.currentTarget.text);
}
}

public function serch(serchWord:String):void {
//検索APIにリクエストを投げる
//http://search.twitter.com/crossdomain.xml上で許可をしているので
//特に指定しなくても自動的に確認して、許可を得られる

var xmlURL:String = "http://search.twitter.com/search.atom?q=";
//encodeURIComponent()を使うことによって、#や日本語にも対応
xmlURL += encodeURIComponent(serchWord);

var urlLoader:URLLoader = new URLLoader(new URLRequest(xmlURL));
urlLoader.addEventListener(Event.COMPLETE, urlLoader_complete);
}

private function urlLoader_complete(event:Event):void
{
loadedXML = new XML(event.target.data);
dispatchEvent(new Event(Event.COMPLETE));
}

}

import flash.display.Sprite;
import flash.events.Event;

/**
* …
* @author umhr
*/
class BiscuitBox extends Sprite
{
private var _biscuitList:Array/*Biscuit*/ = [];
private var _TweetDataList:Array/*TweetData*/ = [];
//private var _bulkLoader:BulkLoader;
private var _count:int;
private var _yend:int = 0;
public function BiscuitBox()
{
//this.graphics.beginFill(0xFFFF00, 0.5);
//this.graphics.drawRoundRect(0, 0, 100, 100, 8, 8);
//this.graphics.endFill();

//_bulkLoader = BulkLoader.getLoader("main");

default xml namespace = new Namespace("http://www.w3.org/2005/Atom");
}

public function setXML(serchedXML:XML):void {

var n:int = serchedXML.entry.length();
for (var i:int = 0; i < n; i++)
{
var xml:XML = serchedXML.entry[i];
var tweetData:TweetData = new TweetData();
tweetData.setLink(xml.link[1].@href);
tweetData.title = String(xml.title).replace(/\n/g, "");
tweetData.authorName = String(xml.author.name);
tweetData.id = String(xml.id);
//trace(tweetData.link);
_TweetDataList.push(tweetData);
//_bulkLoader.add(tweetData.link, { id:tweetData.id, type:"image", context:new LoaderContext(true) } );
}

//_bulkLoader.addEventListener("complete", bulkLoader_complete);
//_bulkLoader.addEventListener(ErrorEvent.ERROR, bulkLoader_error);
//_bulkLoader.start();
//trace("??")
bulkLoader_complete(null);
}

private function bulkLoader_complete(event:Event):void
{
//_bulkLoader.removeEventListener("complete", bulkLoader_complete);
var n:int = _TweetDataList.length;
for (var i:int = 0; i < n; i++)
{
addTweet(_TweetDataList[i], i);
}

addEventListener(Event.ENTER_FRAME, enterFrame);
}

private function addTweet(tweetData:TweetData, tweetIndex:int):void {
var text:String = tweetData.getText();
var rgb:uint = 0xFFFFFF * Math.random();

var n:int = text.length + 1;
for (var i:int = 0; i < n; i++)
{
var biscuit:Biscuit = new Biscuit();
biscuit.tweetIndex = tweetIndex;
biscuit.index = i;

var ty:int = i * 1;
//biscuit.proxyY = _yend + 465 – 100 + ty;
biscuit.proxyY = 465+ ty;
if (i == 0) {
biscuit.setImage(tweetData.link);
//biscuit.setBitmapData(_bulkLoader.getBitmapData(tweetData.id, true));
}else {
biscuit.setText(text.substr(i – 1, 1), rgb);
}
biscuit.isLast = (i == n – 1);
addChild(biscuit);
_biscuitList.push(biscuit);

}
//_yend += ty;
}

private var _targetBiscuits:int = 0;
private function enterFrame(e:Event):void
{
var n:int = _biscuitList.length;
if (n == 0) {
return;
}
for (var i:int = 0; i < n; i++)
{
if(_biscuitList[i]){
if (_targetBiscuits >= _biscuitList[i].tweetIndex) {
var speed:Number = (i % 12 + 12);
_biscuitList[i].proxyY -= 0.03 * (30-speed);
_biscuitList[i].proxyX = Math.sin(2 * Math.PI * (i / 50 – _count / 500)) * (10 + speed*9);
_biscuitList[i].proxyZ = Math.cos(2 * Math.PI * (i / 50 – _count / 500)) * (10 + speed*9);
_biscuitList[i].proxyRotation += 2;
}
}
}

_targetBiscuits = Math.floor(_count / 400);
render();
_count ++;
}
private function render():void {
var n:int = _biscuitList.length;
for (var i:int = 0; i < n; i++)
{
if(_biscuitList[i]){
_biscuitList[i].x = _biscuitList[i].proxyX + 465 * 0.5;
_biscuitList[i].y = _biscuitList[i].proxyY;
_biscuitList[i].scaleX = _biscuitList[i].scaleY = (_biscuitList[i].proxyZ+400)/500;
_biscuitList[i].rotation = _biscuitList[i].proxyRotation;
if (_biscuitList[i].y < -50) {
//_biscuitList[i].visible = false;
//_biscuitList[i].y = _biscuitList[i].index * 3 + 465;
//_biscuitList[i].
removeChild(_biscuitList[i]);
_biscuitList[i] = null;
}
}
}
}
}

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.text.TextFormat;

/**
* …
* @author umhr
*/
class Biscuit extends Sprite
{
public var proxyX:Number = 0;
public var proxyY:Number = 0;
public var proxyZ:Number = 0;
public var proxyRotation:Number = 0;
public var tweetIndex:int;
public var index:int;
public var isLast:Boolean;
public function Biscuit()
{
//graphics.beginFill(0xFF0000, 0.5);
//graphics.drawRoundRect(0, 0, 30, 30, 8, 8);
//graphics.endFill();
}

public function setText(text:String, rgb:uint = 0xFFFFFF):void {
var textField:TextField = new TextField();
textField.defaultTextFormat = new TextFormat("_sans", 21, rgb, true);
textField.text = text;
textField.autoSize = "left";
textField.cacheAsBitmap = true;
//addChild(textField);

var bitmap:Bitmap = new Bitmap(new BitmapData(textField.width, textField.height, true, 0x00000000), "auto", true);
bitmap.bitmapData.draw(textField);
addChild(bitmap);
}

public function setBitmapData(bitmapData:BitmapData):void {
addChild(new Bitmap(bitmapData, "auto", true));
}

public function setImage(link:String):void {
//trace(link);
var loader:Loader = new Loader();
loader.load(new URLRequest(link),new LoaderContext(true));

var loaderInfo:LoaderInfo = loader.contentLoaderInfo;
loaderInfo.addEventListener(Event.COMPLETE, loaderInfo_complete);
}

private function loaderInfo_complete(event:Event):void
{
event.target.removeEventListener(Event.COMPLETE, loaderInfo_complete);
//trace(event.target.content.bitmapData);
setBitmapData(event.target.content.bitmapData);
}

}

import flash.display.BitmapData;
/**
* …
* @author umhr
*/
class TweetData
{
public var authorName:String;
public var authorURI:String;
public var title:String;
public var link:String;
public var id:String;
public function TweetData()
{

}

public function setLink(value:String):void
{
//アイコン画像のURLを取得
//var imgURL:String = xml.link[1].@href;
//bmpフォーマットのアイコン画像を使っている人も少なくないため、Flashで使えるファイルの拡張子かどうかを判別
var extention:String = value.substr( -4);
extention = extention.toLowerCase();
if (extention != ".jpg" && extention != ".gif" && extention != ".png") {
//Flashで使える画像の拡張子ではなかった場合にはデフォルトアイコンを指定
value = "http://a3.twimg.com/sticky/default_profile_images/default_profile_4_mini.png";
}
//画像のファイル名が日本語などの規定外の文字である場合を考慮しエンコードする
value = encodeURI(value); //*3
//小さい画像で十分なので、名前を差し替える
value = value.replace(/_normal\./, "_mini\.");

link = value;
}

public function getText():String {
var result:String = "";
result += authorName;
result += ":";
result += title;
return result;
}

public function toString():String {
var result:String = "";

var paramList:Array/*String*/ = ["title", "link"];

var n:int = paramList.length;
for (var i:int = 0; i < n; i++)
{
result += paramList[i] + ":" + this[paramList[i]];
if (i < n-1) {
result += ",";
}
}

return result;
}

}

[/sourcecode]