JapaneseFirefly
< ホタル >
定番ネタ?
別のネタの実装方法を考えている最中にhttp://wonderfl.net/c/28CUを見つけて
むしょうにホタルが作りたくなった。 過去にも同様のネタは見つけたけど見切り投稿。1月早いか
子供の頃に田舎で見た平家ボタルの群れを再現。点滅間隔や飛翔は源氏ボタルを参考にしました。
水路と草むら、右に木が1本立っている光景を心眼でとらえてください。
TODO:もう少し奥行き感が欲しいところ。とりあえず自分で決めたタイムアップがきたのでUP
20100506 微調整 マウスダウンすると時の流れが遅くなります
/**
* Copyright zendenmushi ( http://wonderfl.net/user/zendenmushi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/cegt
*/
// < ホタル >
//
// 定番ネタ?
// 別のネタの実装方法を考えている最中にhttp://wonderfl.net/c/28CUを見つけて
// むしょうにホタルが作りたくなった。 過去にも同様のネタは見つけたけど見切り投稿。1月早いか
//
// 子供の頃に田舎で見た平家ボタルの群れを再現。点滅間隔や飛翔は源氏ボタルを参考にしました。
// 水路と草むら、右に木が1本立っている光景を心眼でとらえてください。
//
// TODO:もう少し奥行き感が欲しいところ。とりあえず自分で決めたタイムアップがきたのでUP
//
// 20100506 微調整 マウスダウンすると時の流れが遅くなります
package
{
import adobe.utils.CustomActions;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filters.BlurFilter;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.utils.getTimer;
import net.hires.debug.Stats;
/**
* ...
* @author TM
*/
[SWF(width=465,height=465,backgroundColor=0x000000,frameRate=60)]
public class FireFlies extends Sprite
{
private var canvas : BitmapData;
private var back : BitmapData;
private var fireflyImage : BitmapData;
private var matrix3D : Matrix3D = new Matrix3D();
private var axis : Vector3D = new Vector3D();
private var zeroPoint : Point = new Point(0, 0);
private var appearCounter : int = 0;
private var nextAppear : int = 0;
private var syncInterval : int = 0;
private var first : Boolean = true;
private var mouseIsDown : Boolean = false;
private var lastUpdate : uint = 0;
private var updateInterval : Number = 0;
private var mouseReleased : Boolean = false;
private var fireflyRender : FireFlyRender;
private var flies : Vector.<FireFly> = new Vector.<FireFly>;
private var freep : int = -1;
private const itemlimit : int = 2000;
private var loaders : Array;
private const url:Array = [
//"./firefly2.png"
"http://assets.wonderfl.net/images/related_images/d/dd/dd4a/dd4a59866907a0ae71c7e03eb1669dd86fbb8ac0"
];
public function FireFlies()
{
Wonderfl.capture_delay( 50 );
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e : Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
loaders = new Array();
for (var i : int = 0; i < url.length; i++) {
loaders[i] = new Loader();
loaders[i].contentLoaderInfo.addEventListener(Event.COMPLETE, loaded);
loaders[i].load(new URLRequest(url[i]), new LoaderContext(true));
}
}
private var loaded_count : int = 0;
private function loaded(e : Event = null) : void
{
loaded_count++;
if (loaded_count < url.length) return;
addEventListener(Event.ENTER_FRAME, enterFrame);
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown); // スクリーンショットがあまりにもさびしいので、マウスダウンしている間、光を大きくする
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
fireflyImage = new BitmapData(72*2, 72, true, 0);
back = Bitmap(loaders[0].content).bitmapData;
var r : Rectangle = new Rectangle(0, back.height - 72, 72*2, 72);
fireflyImage.copyPixels(back, r , zeroPoint, null, null, true); // ホタル飛翔イメージ切り出し
back.fillRect(r, 0xff000000);
fireflyRender = new FireFlyRender(fireflyImage);
addChild(Bitmap(loaders[0].content));
canvas = new BitmapData(stage.stageWidth * 2, stage.stageHeight * 2, true, 0); // ホタルの光はstageの縦横2倍のキャンバスに描いて、縮小表示
var bmp : Bitmap = new Bitmap( canvas );
bmp.scaleX = 0.5;
bmp.scaleY = 0.5;
addChild(bmp);
//addChild(new Stats());
//addChild(new Bitmap(fireflyRender.pre));
ScreenCenter.x = stage.stageWidth / 2;
ScreenCenter.y = stage.stageHeight / 2;
ScreenSize.x = stage.stageWidth;
ScreenSize.y = stage.stageHeight;
graphics.clear();
graphics.beginFill(0, 1);
graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
graphics.endFill();
axis.x = 1;
axis.y = 0;
axis.z = 0;
matrix3D.identity();
matrix3D.appendRotation(0, axis);
matrix3D.appendTranslation(0, 0, 100);
first = true;
nextAppear = 60 * 3;
}
private function mouseUp(e:MouseEvent):void
{
mouseIsDown = false;
mouseReleased = true;
}
private function mouseDown(e:MouseEvent):void
{
mouseIsDown = true;
mouseReleased = false;
}
private function newItem(x : Number, y : Number, z : Number, dx : Number = 0, dy : Number = 0, dz : Number = 0 ) : FireFly
{
var cnt : int = flies.length;
if (((itemlimit > 0) && cnt >= itemlimit) && (freep >= cnt-1)) return null;
freep++;
if (freep == cnt) {
flies[cnt] = new FireFly(x,y,z, syncInterval, dx,dy,dz);
} else {
flies[freep].reGenerate(x, y, z, false, syncInterval, dx,dy,dz);
}
flies[freep].index = freep;
flies[freep].visible = true;
return flies[freep];
}
private function remove(index : int) : void
{
var cnt : int = flies.length;
var temp : FireFly = flies[index];
var lastp : int = freep;
temp.visible = false;
if (lastp != index) {
flies[index] = flies[lastp];
flies[index].index = index;
flies[lastp] = temp;
}
freep = lastp - 1;
}
private function enterFrame(e:Event):void
{
syncInterval = (syncInterval + 1) % 180;
appearCounter++;
if (appearCounter > nextAppear) {
if (first) {
newItem(0, 0, -100, 0, 0, 1).bodyVisible = true;
first = false;
} else {
newItem(Math.random() * 400 - 200, Math.random() * 50, -Math.random() * 50 + 25);
}
appearCounter = 0;
nextAppear -= 20;
if (nextAppear < 1) nextAppear = 1;
}
canvas.fillRect(canvas.rect, 0);
canvas.lock();
for (var i : int = freep; i >= 0; i--) {
var vanish : Boolean = !flies[i].stepFrame(matrix3D, back);
if (!vanish) {
if (mouseIsDown) {
// スクリーンショットがあまりにもさびしいので、マウスダウンしている間、光を大きくする。かなり反則
flies[i].size *= 4;
}
fireflyRender.renderSmall(canvas, flies[i]);
} else {
remove(i);
}
}
// 手前のホタル
for (i = freep; i >= 0; i--) {
fireflyRender.renderLarge(canvas, flies[i]);
}
canvas.unlock();
var tick : uint = getTimer();
var interval : uint = tick - lastUpdate;
function wait(msec : uint) : void {
while ( interval < msec ) { // ウェイトタイマー。 Flashはビジーループ大丈夫?
tick = getTimer();
interval = tick - lastUpdate;
}
}
if (mouseIsDown) {
wait( 50 );
updateInterval = interval;
} else if (mouseReleased) { // マウスボタンを離した際にゆっくりと速度を戻す
//trace(interval, updateInterval );
updateInterval -= 0.5;
if (interval < updateInterval) {
wait( updateInterval );
} else {
mouseReleased = false;
}
}
lastUpdate = tick;
}
}
}
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.filters.GlowFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Utils3D;
import flash.geom.Vector3D;
import flash.media.Video;
var ScreenCenter : Point = new Point();
var ScreenSize : Point = new Point();
class FireFlyLuminous
{
public static function compute(param : int, /*inout*/ colorRGB : Vector.<int>) : uint
{
if (param < 0x40) {
colorRGB[0] += (0xc0 - colorRGB[0]) / 8;
colorRGB[1] += (0xff - colorRGB[1]) / 8;
colorRGB[2] += (0x00 - colorRGB[2]) / 8;
return colorRGB[0] << 16 | colorRGB[1] << 8 | colorRGB[2];
} else {
if (colorRGB[0] > 16) {
colorRGB[0] += (0x0 - colorRGB[0]) / 16;
colorRGB[1] += (0x0 - colorRGB[1]) / 32;
colorRGB[2] += (0x0 - colorRGB[2]) / 32;
} else {
colorRGB[0] += (0x0 - colorRGB[0]) < 0 ? -1 : 0;
colorRGB[1] += (0x0 - colorRGB[1]) < 0 ? -1 : 0;
colorRGB[2] += (0x0 - colorRGB[2]) < 0 ? -1 : 0;
}
return colorRGB[0] << 16 | colorRGB[1] << 8 | colorRGB[2];
}
}
}
class FireFlyRender
{
private var sp : Sprite = new Sprite();
private var grp : Graphics = sp.graphics;
private var tempRect : Rectangle = new Rectangle();
private var destPoint : Point = new Point();
private var matrix2D : Matrix = new Matrix();
private var glow : GlowFilter = new GlowFilter(0xffff00, 1, 8, 8);
// プリレンダリングするサイズ範囲
private const PreRenderStartSize : Number = 1.5;
private const PreRenderEndSize : Number = 2;
private var pre : BitmapData;
private var prerenderedPos : Vector.<int> = new Vector.<int>;
private var fireflyBody : BitmapData;
private var colorTrans : ColorTransform = new ColorTransform(1, 1, 1, 0.8); // 20100506
private var vertices : Vector.<Number>;
private var uvs : Vector.<Number>;
private var faces : Vector.<int>;
public function FireFlyRender(fireflyImage : BitmapData)
{
var i : int, j : int, k : int, ss : Number;
fireflyBody = fireflyImage;
vertices = new Vector.<Number>;
uvs = new Vector.<Number>;
uvs[0] = 0; uvs[1] = 0;
uvs[2] = 0.5; uvs[3] = 0;
uvs[4] = 0.5; uvs[5] = 1;
uvs[6] = 0; uvs[7] = 1;
faces = new Vector.<int>;
faces[0] = 0; faces[1] = 1; faces[2] = 3;
faces[3] = 1; faces[4] = 2; faces[5] = 3;
sp.blendMode = BlendMode.ADD;
var h : int = 0;
for (i = 0; i <= (PreRenderEndSize-PreRenderStartSize)*2; i++) {
prerenderedPos[i] = h;
h += (PreRenderStartSize+i*0.5)*2+2;
}
pre = new BitmapData( PreRenderEndSize * 2 * 128, h, true, 0);
var lumi : Vector.<int> = new Vector.<int>;
for (j = 0; j <= (PreRenderEndSize-PreRenderStartSize)*2; j++) {
lumi[0] = 0;
lumi[1] = 0;
lumi[2] = 0;
ss = PreRenderStartSize + j * 0.5;
k = 0;
for (i = 0; i < 180; i++) {
var color : uint = FireFlyLuminous.compute(i, lumi);
if ((i < 12) || (i >= 64)) {
k++;
matrix2D.identity();
matrix2D.translate( k*(PreRenderEndSize+1)*2 + PreRenderEndSize, prerenderedPos[j]+ss+1 );
glow.color = color;
glow.blurX = 4;
glow.blurY = 4;
sp.filters = [glow];
grp.clear();
grp.lineStyle(0, 0, 0);
grp.beginFill( color, 1 );
grp.drawCircle(0, 0, ss);
grp.endFill();
pre.draw( sp , matrix2D);
}
}
}
}
public function renderSmall(target : BitmapData, firefly : FireFly) : void
{
if (!firefly.culling) {
// PreRenderStartSizeより小さいサイズはfillrectで描画
if (firefly.size < PreRenderStartSize) {
tempRect.x = firefly.x * 2 - firefly.size;
tempRect.y = firefly.y * 2 - firefly.size;
tempRect.width = firefly.size*2;
tempRect.height = firefly.size*2;
target.fillRect( tempRect, 0xff000000 | firefly.color);
}
} else { // 空に被っている場合は、影のみ表示
if (firefly.size > 2) {
tempRect.x = firefly.x * 2 - 1;
tempRect.y = firefly.y * 2 - 1;
tempRect.width = 2;
tempRect.height = 2;
target.fillRect( tempRect, 0xff202020 );
}
}
}
public function renderLarge(target : BitmapData, firefly : FireFly) : void
{
if (!firefly.culling) {
// PreRenderEndSize以上はプリレンダ
if ((firefly.size >= PreRenderStartSize) && (firefly.size <= PreRenderEndSize)) {
if (firefly.blink < 180) {
var k : int = firefly.blink >= 64 ? firefly.blink - 64 + 12 : firefly.blink < 12 ? firefly.blink : 12;
tempRect.x = k * (PreRenderEndSize+1) * 2;
tempRect.y = prerenderedPos[((firefly.size - PreRenderStartSize) * 2) >> 0];
tempRect.width = (PreRenderEndSize+1) * 2;
tempRect.height = firefly.size * 2+1;
destPoint.x = firefly.x * 2 - PreRenderEndSize;
destPoint.y = firefly.y * 2 - firefly.size;
target.copyPixels(pre, tempRect, destPoint, null, null, true);
}
} else if (firefly.size > PreRenderEndSize) {
// PreRenderEndSizeより大きいサイズはSpriteで描画
matrix2D.identity();
matrix2D.translate( firefly.x*2, firefly.y*2 );
glow.color = firefly.color;
var blurSize : int = firefly.size / 2 < 4 ? 4 : firefly.size / 2;
glow.blurX = blurSize ;
glow.blurY = blurSize ;
sp.filters = [glow];
grp.clear();
grp.lineStyle(0, 0, 0);
grp.beginFill( firefly.color, 1 );
grp.drawCircle(0, 0, firefly.size);
grp.endFill();
colorTrans.alphaMultiplier = 1;
target.draw( sp , matrix2D, colorTrans );
if ((firefly.z < 20) && (firefly.size < 200) && firefly.bodyVisible) { // 最初のホタルは体も表示
var scale : Number = firefly.size / 12;// (20 - firefly.z) / 20;
vertices[0] = -36*scale; vertices[1] = -36*scale;
vertices[2] = 36*scale; vertices[3] = -36*scale;
vertices[4] = 36*scale; vertices[5] = 36*scale;
vertices[6] = -36*scale; vertices[7] = 36*scale;
if (firefly.blink & 1) {
uvs[0] = 0;
uvs[2] = 0.5;
uvs[4] = 0.5;
uvs[6] = 0;
} else {
uvs[0] = 0.5+1/144;
uvs[2] = 1;
uvs[4] = 1;
uvs[6] = 0.5+1/144;
}
sp.filters = [];
grp.clear();
grp.beginBitmapFill( fireflyBody );
grp.drawTriangles(vertices, faces, uvs);
grp.endFill();
colorTrans.alphaMultiplier = (((firefly.color & 0xff00) >> 8) / 255.0) * ((20 - firefly.z) / 20); // 20100506
target.draw( sp , matrix2D, colorTrans );
}
}
}
}
}
class FireFly
{
public var index : int;
public var visible : Boolean = false;
public var x : Number;
public var y : Number;
public var z : Number;
public var color : uint;
public var bodyVisible : Boolean = false;
public var size : Number;
public var culling : Boolean;
public var blink : int = 0;
private var blinkInterval : int = 180;
private var luminousColor : Vector.<int> = new Vector.<int>;
private var flystate : int = 0;
private var landState : int = 0;
private var basepos : Vector3D = new Vector3D();
private var basedir : Vector3D = new Vector3D();
private var flypos : Vector.<Number> = new Vector.<Number>;
private var flyposCam : Vector.<Number> = new Vector.<Number>;
private var hiding : Boolean = false;
private var laucnhTimer : uint = 0;
private var flytimer : uint = 0;
public function FireFly(x : Number, y : Number, z : Number, blinkInit : int, dx : Number = 0, dy : Number = 0, dz : Number = 0 )
{
//filters = [glow];
flypos[0] = 0;
flypos[1] = 0;
flypos[2] = 0;
flyposCam[0] = 0;
flyposCam[1] = 0;
flyposCam[2] = 0;
reGenerate(x, y, z, false, blinkInit, dx, dy, dz);
}
public function reGenerate(x : Number, y : Number, z : Number, launch : Boolean, blinkInit : int, dx : Number = 0, dy : Number = 0, dz : Number = 0 ) : void
{
luminousColor[0] = 0;
luminousColor[1] = 0;
luminousColor[2] = 0;
flytimer = 60*5;
basepos.x = x;
basepos.y = y;
basepos.z = z;
if ((dx == 0) && (dy == 0) && (dz == 0)) {
var rad : Number = Math.random() * Math.PI * 2;
basedir.x = Math.cos(rad);
if (launch) {
basedir.y = -Math.random();
} else {
basedir.y = Math.random() * 2 - 1;
blink = blinkInit + int(Math.random()*2)*60;
}
basedir.z = Math.sin(rad);
} else {
basedir.x = dx;
basedir.y = dy;
basedir.z = dz;
blink = 0;
}
basedir.normalize();
blinkInterval = 180 + Math.random() * 10;
flystate = 0;// Math.random() * 255;
hiding = false;
}
public function stepFrame(camera : Matrix3D, back : BitmapData) : Boolean
{
//graphics.clear();
blink = (blink + 1) % blinkInterval;
flystate = (flystate + 1) & 0xff;
var rad : Number = (flystate * Math.PI / 128);
basepos.x += basedir.x/10;
basepos.y += basedir.y/10;
basepos.z += basedir.z/10;
if ((basepos.z < -50) && (basedir.z < 0) && (blink >= 179)) {
if (basepos.z < -80) {
basedir.z = -basedir.z;
} else if (Math.random() > 0.5) {
basedir.z = -basedir.z;
}
}
if (flytimer > 0) flytimer--;
else if (laucnhTimer > 0) {
laucnhTimer--;
if (laucnhTimer == 0) {
reGenerate(basepos.x, basepos.y, basepos.z, true, blink);
flystate = landState;
}
}
if ((basepos.x > 400) || (basepos.x < -400) /*|| (basepos.y > 0)*/ || (basepos.y < -100) || (basepos.z > 80) /*|| (basepos.z < -80)*/) {
hiding = true;
}
flypos[0] = basepos.x;
if (laucnhTimer == 0) flypos[1] = basepos.y - (Math.sin( rad )*16 + Math.cos(rad*4)*4)/10;
flypos[2] = basepos.z;
camera.transformVectors( flypos, flyposCam );
culling = flyposCam[2] < 0;
var land : Boolean = false;
if (!culling) {
var scale : Number = 100 / flyposCam[2];
flyposCam[0] *= scale;
flyposCam[1] *= scale;
//flyposCam[2] *= scale;
x = flyposCam[0] + ScreenCenter.x;
y = flyposCam[1] + ScreenCenter.y;
z = flyposCam[2];
culling = (y < 0);
var dat : uint = back.getPixel32(x, y) & 0xffffff;
if (dat == 0) { // 背景が黒ピクセル部分は木or草。 z座標によって、木の向こうか手前(あるいは止まるか)を判断
if (z > 60) culling = true;
else if (z > 40) land = true;
} else if (dat != 0x010101) { // 背景が0x010101部分以外は光らせない
culling = true;
}
}
if (((basepos.y > 50) || land) && (flytimer == 0) && (laucnhTimer == 0)) { // 着地
basedir.x = 0;
basedir.y = 0;
basedir.z = 0;
laucnhTimer = 60*10; // 10秒止まる
landState = flystate;
}
if (!culling) {
color = FireFlyLuminous.compute(blink, luminousColor);
size = 100 / flyposCam[2];
if (size >= 2) {
if ((x < 0) || (x > ScreenSize.x)) {
hiding = true;
}
}
}
if (hiding && (blink >= 179)) {
return false;
}
return true;
}
}