// forked from checkmate's Saqoosha challenge for professionals // 静かな降雪 package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")] public class Main2 extends Sprite { // 定数 private const NUM_OF_FALL:uint = 5; // 同時に落ちる粒子の数 private const GRAVITY:Number = 0.9; // 重力 private const BOUNCE:Number = -0.0; // 跳ね返り private const BEND:Number = 0.0; // 跳ね返るときに曲がる率 private const FRICTION:Number = 0.45; // 空気抵抗 private const FLOOR_FRICTION:Number = 0.0; // 摩擦抵抗 private const LOCAL_FRICTION_BOOLEAN:Boolean = true; private const BOTTOM:uint = stage.stageHeight - 1; private const LETTER:String = "しんしん"; // 表示文字列 // パーティクルの色の設定 private function setParticleColor():uint { var c:uint = Math.random() * 0x33 + 0xCC; return rgbToHex(c, c, c); } // 変数 private var particleArray:Array; // パーティクル格納配列(繰り返し使うために待避) private var copyParticleArray:Array; // パーティクル格納配列(実際に使用する) private var canvasBitmapData:BitmapData; // 描画 BitmapData private var pixelizer:Pixelizer; // ピクセル化クラス // コンストラクタ public function Main2() { // Particle クラスの初期化 Particle.gravity = GRAVITY; Particle.bounce = BOUNCE Particle.bend = BEND; Particle.friction = FRICTION; Particle.floorFriction = FLOOR_FRICTION; Particle.localFrictionBoolrean = LOCAL_FRICTION_BOOLEAN; Particle.top = 0; Particle.bottom = BOTTOM; Particle.left = 0; Particle.right = stage.stageWidth; // 描画 BitmapData 生成 canvasBitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000); addChild(new Bitmap(canvasBitmapData)); // 案内表示 var signField:TextField = new TextField(); signField.text = "Stage を Click すると motion を再演します。"; signField.autoSize = TextFieldAutoSize.LEFT; signField.selectable = false; signField.blendMode = BlendMode.INVERT; addChild(signField); // テキストピクセライズ var pTextField:PixelizerTextField = new PixelizerTextField(); pTextField.text = LETTER; var textFormat:TextFormat = new TextFormat(null, 120); textFormat.letterSpacing = -5; pTextField.setTextFormat(textFormat); pixelizer = new Pixelizer(); pixelizer.addEventListener(Event.COMPLETE, scanCompleteHandler); pixelizer.scan(pTextField.bitmapData); } // ピクセライズ完了後の処理 // (ピクセル格納配列の生成) private function scanCompleteHandler(event:Event):void { // イテレータ生成 var iter:PixelizerIterator = pixelizer.iterator; pixelizer = null; // 配置オフセット計算 var offsetX:Number = (stage.stageWidth - iter.width) / 2; var offsetY:Number = 40;// (stage.stageHeight - iter.height) / 2; // イテレーション particleArray = []; while (iter.hasNext()) { var color:uint = iter.next(); var alpha:uint = getAlpha(color); if (alpha > 0x7F) { particleArray.push(new Particle(iter.x + offsetX, iter.y + offsetY, setParticleColor())); } } // イベント登録 stage.addEventListener(MouseEvent.CLICK, clickHandler); motion(); } // 実処理(モーションタイポ) private function motion():void { // 待避配列から使用配列を生成 copyParticleArray = []; for each (var original:Particle in particleArray) { copyParticleArray.push(original.clone()); } // Particle 格納配列をシャッフル shuffle(copyParticleArray); // 各 particle に動作開始時限値 var n:uint = copyParticleArray.length; for (var i:int = 0; i < n; i++) { copyParticleArray[i].counter = i / NUM_OF_FALL; } // イベント登録 addEventListener(Event.ENTER_FRAME, enterFrameHandler); } // フレームイベント private function enterFrameHandler(event:Event):void { canvasBitmapData.lock(); canvasBitmapData.fillRect(canvasBitmapData.rect, 0x000000); for each (var particle:Particle in copyParticleArray) { particle.update(); canvasBitmapData.setPixel(particle.x, particle.y, particle.color); } canvasBitmapData.unlock(); } // マウスイベント private function clickHandler(event:MouseEvent):void { removeEventListener(Event.ENTER_FRAME, enterFrameHandler); motion(); } // シャッフル private function shuffle(a:Array):void { var n:int; var t:*; var l:uint = a.length; while (l--) { n = Math.floor(Math.random() * (l+1)); t = a[l]; a[l] = a[n]; a[n] = t; } } // RGB → 0xNNNNNN private function rgbToHex(r:uint, g:uint, b:uint):uint { r = adjust(r); g = adjust(g); b = adjust(b); return r << 16 | g << 8 | b; } // 255 を超えていた場合は 255 に切り捨てる private function adjust(val:uint):uint { return Math.min(val, 0xFF); } // 32 bit color のアルファ値を取得 private function getAlpha(color:uint):uint { return (color >> 24) & 0xFF; } } } // パーティクルクラス class Particle { // static 変数 // 物理変数 public static var gravity:Number = 0.98; // 重力 public static var bounce:Number = -0.45; // 跳ね返り public static var bend:Number = 4.5; // 跳ね返るときに曲がる率 public static var friction:Number = 0.98; // 空気抵抗 public static var floorFriction:Number = 0.9; // 摩擦抵抗 public static var localFrictionBoolrean:Boolean = false; // ステージ領域 public static var top:uint; public static var bottom:uint; public static var left:uint; public static var right:uint; // 現在座標 public function get x():Number { return _x; } private var _x:Number; public function get y():Number { return _y; } private var _y:Number; // 色 public function get color():uint { return _color; } private var _color:uint; // 動作開始時限値 public function set counter(value:uint):void { _counter = value; } private var _counter:uint = 0; // ローカルな物理変数 private var localBounce:Number; // 跳ね返り private var localBend:Number; // 跳ね返るときに曲がる率 private var localFriction:Number; // 空気抵抗 // 速度 private var vx:Number = 0; private var vy:Number = 0; public function Particle(x:Number, y:Number, color:uint) { _x = x; _y = y; _color = color; // bounce、bend、friction のローカル補正 localBounce = bounce + (Math.random() * 2 - 1) / 4; localBend = (Math.random() < 0.5) ? bend : -bend; localBend += (Math.random() * 2 - 1); localFriction = (localFrictionBoolrean) ? friction - Math.random() : friction - Math.random() / 20; } public function clone():Particle { return new Particle(_x, _y, _color); } public function update():void { if (_counter > 0) { _counter--; } else { // 壁処理 if (_x > right) { _x = right; vx *= localBounce; vx *= floorFriction; localBend *= floorFriction; vy = localBend; } else if (_x < left) { _x = left; vx *= localBounce; vx *= floorFriction; localBend *= floorFriction; vy = localBend; } if (_y > bottom) { _y = bottom; vy *= localBounce; vy *= floorFriction; localBend *= floorFriction; vx = localBend; } else if (_y < top) { _y = top; vy *= localBounce; vy *= floorFriction; localBend *= floorFriction; vx = localBend; } // 座標更新 vx *= localFriction; vy *= localFriction; vy += gravity; _x += vx; _y += vy; } } } // ピクセル化クラス import flash.display.BitmapData; import flash.events.Event; import flash.events.EventDispatcher; class Pixelizer extends EventDispatcher { private var width:uint = 0; private var height:uint = 0; private var data:Vector.<uint>; // イテレータ public function get iterator():PixelizerIterator { return new PixelizerIterator(width, height, data); } // コンストラクタ public function Pixelizer() {} // スキャン public function scan(bmd:BitmapData):void { width = bmd.width; height = bmd.height; data = bmd.getVector(bmd.rect); // イベント発行 dispatchEvent(new Event(Event.COMPLETE)); } } // ピクセル化したデータのイテレータ class PixelizerIterator { // モードフラグ public static const NEXT:String = "next"; // 正順 public static const PREV:String = "prev"; // 逆順 // next()、nextCol() で取得したデータのX座標 public function get x():int { return positionX; } private var positionX:int = 0; // next()、nextRow() で取得したデータのY座標 public function get y():int { return positionY; } private var positionY:int = 0; // スキャンサイズ(幅) public function get width():uint { return _width; } private var _width:uint; // スキャンサイズ(高) public function get height():uint { return _height; } private var _height:uint; // データ格納 Vector private var data:Vector.<uint>; // 現在のイテレートモード private var mode:String = "next"; // イテレーションカウンター private var position:int = 0; public function PixelizerIterator(width:uint, height:uint, data:Vector.<uint>) { _width = width; _height = height; this.data = data; } // データの操作(1個ずつデータを呼び出す) // 外部から呼び出せる hasNext public function hasNext():Boolean { return (mode == "prev") ? _hasPrev() : _hasNext(); } // 外部から呼び出せる next public function next():uint { return (mode == "prev") ? _prev() : _next(); } // データの同一列(横)の操作(同一列の全てのデータを呼び出す) // 外部から呼び出せる hasNext public function hasNextRow():Boolean { return (mode == "prev") ? _hasPrevRow() : _hasNextRow(); } // 外部から呼び出せる next public function nextRow():Vector.<uint> { return (mode == "prev") ? _prevRow() : _nextRow(); } // データの同一行(縦)の操作(同一行のすべてのデータを呼び出す) // 外部から呼び出せる hasNext public function hasNextCol():Boolean { return (mode == "next") ? _hasNextCol() : _hasPrevCol(); } // 外部から呼び出せる next public function nextCol():Vector.<uint> { return (mode == "next") ? _nextCol() : _prevCol(); } // リセット public function reset(mode:String = "next"):void { this.mode = mode; var offset:uint = (mode == "next") ? 0 : 1; position = (data.length - 1) * offset; positionX = (_width - 1) * offset; positionY = (_height - 1) * offset; } // データの操作(1個ずつデータを呼び出す) // mode = "next" 時の hasNext private function _hasNext():Boolean { return position < data.length; } // mode = "prev" 時の hasNext private function _hasPrev():Boolean { return position > 0; } // mode = "next" 時の next private function _next():uint { positionX = position % _width; positionY = position / _width; return getData(position++); } // mode = "prev" 時の next private function _prev():uint { positionX = position % _width; positionY = position / _width; return getData(position--); } private function getData(idx:uint):uint { if (idx >= data.length) { throw new Error("getData#配列範囲外:" + idx + " length:" + data.length); } return data[idx]; } // データの同一列(横)の操作(同一列の全てのデータを呼び出す) // mode = "next" 時の hasNext private function _hasNextRow():Boolean { return positionY < _height; } // mode = "prev" 時の hasNext private function _hasPrevRow():Boolean { return positionY > -1; } // mode = "next" 時の next private function _nextRow():Vector.<uint> { return getRow(positionY++); } // mode = "prev" 時の next private function _prevRow():Vector.<uint> { return getRow(positionY--); } // next の実体 private function getRow(val:uint):Vector.<uint> { if (val >= _height) { throw new Error("getRow#配列範囲外:" + val + " _height:" + _height); } return data.slice(val * _width, (val + 1) * _width); } // データの同一行(縦)の操作(同一行のすべてのデータを呼び出す) // mode = "next" 時の hasNext private function _hasNextCol():Boolean { return positionX < _width; } // mode = "prev" 時の hasNext private function _hasPrevCol():Boolean { return positionX > -1; } // mode = "next" 時の next private function _nextCol():Vector.<uint> { return getCol(positionX++); } // mode = "prev" 時の next private function _prevCol():Vector.<uint> { return getCol(positionX--); } // next の実体 private function getCol(idx:uint):Vector.<uint> { if (idx >= _width) { throw new Error("getCol#配列範囲外:" + idx + " _width:" + _width); } var vector:Vector.<uint> = new Vector.<uint>(_height, true); for (var i:uint = 0; i < _height; i++) { vector[i] = data[_width * i + idx]; } return vector; } } // 文字列をピクセル化クラスへ投げ込むためのテキストフィールド import flash.display.BitmapData; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.text.TextField; import flash.text.TextFieldAutoSize; class PixelizerTextField extends TextField { // 透明 private const TRANSPARENT:uint = 0x00000000; public function PixelizerTextField() { autoSize = TextFieldAutoSize.LEFT; } public function get bitmapData():BitmapData { // 普通に TextField を BitmapData に draw var bmd1:BitmapData = new BitmapData(textWidth, textHeight, true, TRANSPARENT); bmd1.draw(this, new Matrix(1, 0, 0, 1, -2, -2)); // 上記 BitmapData のうち、文字である範囲を Rectangle として取得 var rect:Rectangle = bmd1.getColorBoundsRect(0xFF000000, TRANSPARENT, false); // 上記 Rectangle 部分のみの BitmapData を生成 var bmd2:BitmapData = new BitmapData(rect.width, rect.height, true, TRANSPARENT); bmd2.draw(bmd1, new Matrix(1, 0, 0, 1, -rect.x, -rect.y)); bmd1.dispose(); return bmd2; } } forked from: Saqoosha challenge for professionals(しんしん)