Context: For a legacy Flex/Actionscript drawing app, I need to add scaling of simple symbols. The app uses the Graffiti lib for drawing and the resulting shape data is stored as GraphicsPathCommands serialized to XML using the Degrafa lib for save and reload. I need to enable the user to scale these graphics and then get updated path data which can be serialized. The symbols are simple but more complicated than simple geometry. For example:
Question: I converted the SVG data for this symbol to Actionscript GraphicsPathCommands and am able to draw it, and of course translation is easy – but I don't know how I would scale it, given a bounding box defined by a user dragging out a marquee rectangle in the app.
Does anyone know of either an Actionscript way of transforming the command data, or a Javascript snippet for scaling SVG which I can port to Actionscript?
For reference, an example of the Actionscript GraphicsPathCommands for drawing a star is below.
public function DrawPathExample()
{
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
graphics.beginFill(0x003366);
graphics.drawPath(star_commands, star_coord);
}
Solution
A full solution for interactively scaling GraphicsPathCommand data is below. The path data was derived from an SVG put through this SVGParser. It generates path drawing commands in the form of graphics.lineTo(28.4,16.8);. A couple of utility functions separate the data from the commands and store them in Vectors so the data can be serialized. I don't need to use arbitrary SWGs so I just hardcoded the data.
package classes
{
import flash.display.GraphicsPathCommand;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
public class DrawSVG extends Sprite
{
private var startPt:Point = new Point();
private var selectRect:Rectangle = new Rectangle();
private var viewBox:Rectangle = new Rectangle();
protected var commands:Vector.<int> = new Vector.<int>();
protected var drawingData:Vector.<Number> = new Vector.<Number>();
protected var sourceDrawingData:Vector.<Number> = new Vector.<Number>();
public function DrawSVG()
{
super();
this.addEventListener(Event.ADDED_TO_STAGE, setup);
setupWomanData();
}
private function setup(event:Event):void
{
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
private function onMouseDown(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
// offset so graphic draws centered on click point
startPt = new Point(event.stageX - (viewBox.width /2), event.stageY - (viewBox.height /2));
selectRect = new Rectangle(startPt.x, startPt.y, viewBox.width, viewBox.height);
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, selectRect.width, selectRect.height);
}
private function onMouseMove(event:MouseEvent):void
{
selectRect.width = Math.max(viewBox.width, Math.abs(event.stageX - startPt.x));
selectRect.height = Math.max(viewBox.height, Math.abs(event.stageY - startPt.y));
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
this.graphics.clear();
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, viewBox.width * scaleFactor, viewBox.height * scaleFactor);
}
private function onMouseUp(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
createSprite(commands, drawingData);
}
private function drawSymbol(toScale:Number):void
{
drawingData.length = 0;
for (var i:int = 0; i < sourceDrawingData.length; i++) {
drawingData[i] = Math.max(sourceDrawingData[i], sourceDrawingData[i] * toScale);
drawingData[i] += i % 2 == 0 ? startPt.x : startPt.y ;
}
this.graphics.clear();
this.graphics.lineStyle();
this.graphics.beginFill(0xff0000);
this.graphics.drawPath(commands, drawingData);
this.graphics.endFill();
}
private function createSprite(command:Vector.<int>, coord:Vector.<Number>):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0xff);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
return s;
}
private function setupWomanData():void
{
commands = new Vector.<int>();
drawingData = new Vector.<Number>();
viewBox= new Rectangle(0, 0, 24.629, 52.336);
addMoveToCmd(12.31,10.3);
addCurveToCmd(13.37,10.3,14.3,9.89);
addCurveToCmd(15.24,9.48,15.94,8.78);
addCurveToCmd(16.64,8.08,17.05,7.14);
addCurveToCmd(17.46,6.2,17.46,5.15);
addCurveToCmd(17.46,4.1,17.05,3.16);
addCurveToCmd(16.64,2.23,15.94,1.52);
addCurveToCmd(15.24,0.82,14.3,0.41);
addCurveToCmd(13.37,0,12.31,0);
addCurveToCmd(11.26,0,10.33,0.41);
addCurveToCmd(9.39,0.82,8.69,1.52);
addCurveToCmd(7.98,2.23,7.57,3.16);
addCurveToCmd(7.16,4.1,7.16,5.15);
addCurveToCmd(7.16,6.2,7.57,7.14);
addCurveToCmd(7.98,8.08,8.69,8.78);
addCurveToCmd(9.39,9.48,10.33,9.89);
addCurveToCmd(11.26,10.3,12.31,10.3);
addLineToCmd(12.314,10.304);
addMoveToCmd(24.6,26.36);
addLineToCmd(20.7,12.77);
addCurveToCmd(20.62,12.3,20.39,11.91);
addCurveToCmd(20.15,11.51,19.81,11.23);
addCurveToCmd(19.47,10.94,19.04,10.78);
addCurveToCmd(18.61,10.62,18.14,10.62);
addLineToCmd(6.49,10.62);
addCurveToCmd(6.02,10.62,5.59,10.78);
addCurveToCmd(5.16,10.94,4.82,11.23);
addCurveToCmd(4.48,11.51,4.24,11.91);
addCurveToCmd(4.01,12.3,3.93,12.77);
addLineToCmd(0.03,26.36);
addCurveToCmd(0.01,26.4,0.01,26.45);
addCurveToCmd(-0.01,26.5,-0.01,26.55);
addCurveToCmd(0.01,26.6,0.01,26.65);
addCurveToCmd(0.02,26.69,0.03,26.74);
addCurveToCmd(-0.15,27.95,0.55,28.69);
addCurveToCmd(1.25,29.44,2.2,29.6);
addCurveToCmd(3.15,29.77,4.05,29.3);
addCurveToCmd(4.95,28.84,5.17,27.63);
addLineToCmd(6.85,21.37);
addLineToCmd(4.07,34.88);
addCurveToCmd(3.81,35.51,3.91,36.15);
addCurveToCmd(4,36.78,4.35,37.3);
addCurveToCmd(4.7,37.81,5.26,38.13);
addCurveToCmd(5.81,38.45,6.49,38.45);
addLineToCmd(6.78,38.45);
addLineToCmd(6.78,49.72);
addCurveToCmd(6.78,50.99,7.59,51.62);
addCurveToCmd(8.41,52.25,9.39,52.25);
addCurveToCmd(10.37,52.25,11.19,51.62);
addCurveToCmd(12,50.99,12,49.72);
addLineToCmd(12,38.45);
addLineToCmd(12.63,38.45);
addLineToCmd(12.63,49.72);
addCurveToCmd(12.63,50.99,13.44,51.62);
addCurveToCmd(14.26,52.25,15.24,52.25);
addCurveToCmd(16.22,52.25,17.04,51.62);
addCurveToCmd(17.85,50.99,17.85,49.72);
addLineToCmd(17.85,38.45);
addLineToCmd(18.14,38.45);
addCurveToCmd(18.82,38.45,19.38,38.13);
addCurveToCmd(19.93,37.81,20.28,37.3);
addCurveToCmd(20.63,36.78,20.72,36.14);
addCurveToCmd(20.81,35.51,20.56,34.87);
addLineToCmd(17.78,21.37);
addLineToCmd(19.45,27.58);
addCurveToCmd(19.67,28.79,20.57,29.27);
addCurveToCmd(21.47,29.75,22.43,29.6);
addCurveToCmd(23.38,29.45,24.08,28.7);
addCurveToCmd(24.78,27.96,24.6,26.74);
addCurveToCmd(24.61,26.69,24.62,26.65);
addCurveToCmd(24.63,26.6,24.63,26.55);
addCurveToCmd(24.63,26.5,24.62,26.45);
addCurveToCmd(24.62,26.4,24.6,26.36);
addLineToCmd(24.601,26.356);
}
protected function addCurveToCmd(p1:Number, p2:Number, p3:Number, p4:Number):void
{
commands.push(GraphicsPathCommand.CURVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
sourceDrawingData.push(p3);
sourceDrawingData.push(p4);
}
protected function addMoveToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.MOVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
protected function addLineToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.LINE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
}
}
Seems like there is a pretty straightforward way to do this. It looks like the only thing to scale are the coordinates themselves, so you may just apply a scale factor.
Based on your example:
public function ASEntryPoint() {
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
//reference shape to detect initial size
var s:Shape = shapeInRect(star_commands, star_coord);
var bounds:Rectangle = s.getBounds(s);
s.graphics.lineStyle(1);
s.graphics.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
addChild(s);
//fit to target
var targetSize:Rectangle = new Rectangle(150, 100, 75, 60);
//detect lesser factor - assuming you need to preserve proportions
var kx:Number = targetSize.width / (bounds.width);
var ky:Number = targetSize.height / (bounds.height);
var toUse:Number = kx < ky ? kx : ky;
//apply to coords
for (var i:int = 0; i < star_coord.length; i++) {
//size
star_coord[i] *= toUse;
//fix initial offset
star_coord[i] -= i % 2 == 0 ? bounds.x * toUse : bounds.y * toUse;
}
//draw
addChild(shapeInRect(star_commands, star_coord, targetSize));
}
private function shapeInRect(command:Vector.<int>, coord:Vector.<Number>, rect:Rectangle = null):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0x003366);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
if (rect){
s.graphics.lineStyle(1);
s.graphics.drawRect(0, 0, rect.width, rect.height);
s.x = rect.x;
s.y = rect.y;
}
return s;
}
I can connect the device and attach a custom cursor to one finger, but I can´t use any of the gestures to over/click a button or drag a sprite around, etc.
I´m using Starling in the project. To run this sample just create a Main.as, setup it with Starling and call this class.
My basic code:
package
{
import com.leapmotion.leap.Controller;
import com.leapmotion.leap.events.LeapEvent;
import com.leapmotion.leap.Finger;
import com.leapmotion.leap.Frame;
import com.leapmotion.leap.Gesture;
import com.leapmotion.leap.Hand;
import com.leapmotion.leap.InteractionBox;
import com.leapmotion.leap.Pointable;
import com.leapmotion.leap.ScreenTapGesture;
import com.leapmotion.leap.Vector3;
import starling.display.Shape;
import starling.display.Sprite;
import starling.events.Event;
import starling.events.TouchEvent;
/**
* ...
* #author miau
*/
public class LeapController extends Sprite
{
private var _controller:Controller;
private var _cursor:Shape;
private var _screenTap:ScreenTapGesture;
private var _displayWidth:uint = 800;
private var _displayHeight:uint = 600;
public function LeapController()
{
addEventListener(Event.ADDED_TO_STAGE, _startController);
}
private function _startController(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, _startController);
//adding controller
_controller = new Controller();
_controller.addEventListener( LeapEvent.LEAPMOTION_INIT, onInit );
_controller.addEventListener( LeapEvent.LEAPMOTION_CONNECTED, onConnect );
_controller.addEventListener( LeapEvent.LEAPMOTION_DISCONNECTED, onDisconnect );
_controller.addEventListener( LeapEvent.LEAPMOTION_EXIT, onExit );
_controller.addEventListener( LeapEvent.LEAPMOTION_FRAME, onFrame );
//add test button
_testButton.x = stage.stageWidth / 2 - _testButton.width / 2;
_testButton.y = stage.stageHeight / 2 - _testButton.height / 2;
addChild(_testButton);
_testButton.touchable = true;
_testButton.addEventListener(TouchEvent.TOUCH, doSomething);
//draw ellipse as a cursor
_cursor = new Shape();
_cursor.graphics.lineStyle(6, 0xFFE24F);
_cursor.graphics.drawEllipse(0, 0, 80, 80);
addChild(_cursor);
}
private function onFrame(e:LeapEvent):void
{
trace("ON FRAME STARTED");
var frame:Frame = e.frame;
var interactionBox:InteractionBox = frame.interactionBox;
// Get the first hand
if(frame.hands.length > 0){
var hand:Hand = frame.hands[0];
var numpointables:int = e.frame.pointables.length;
var pointablesArray:Array = new Array();
if(frame.pointables.length > 0 && frame.pointables.length < 2){
//trace("number of pointables: "+frame.pointables[0]);
for(var j:int = 0; j < frame.pointables.length; j++){
//var pointer:DisplayObject = pointablesArray[j];
if(j < numpointables){
var pointable:Pointable = frame.pointables[j];
var normal:Vector3 = pointable.tipPosition;
var normalized:Vector3 = interactionBox.normalizePoint(normal);
//pointable.isFinger = true;
_cursor.x = normalized.x * _displayWidth;
_cursor.y = _displayHeight - (normalized.y * _displayHeight);
_cursor.visible = true;
}else if (j == 0) {
_cursor.visible = false;
}
}
}
}
}
private function onExit(e:LeapEvent):void
{
trace("ON EXIT STARTED");
}
private function onDisconnect(e:LeapEvent):void
{
trace("ON DISCONNECT STARTED");
}
private function onConnect(e:LeapEvent):void
{
trace("ON CONNECT STARTED");
_controller.enableGesture( Gesture.TYPE_SWIPE );
_controller.enableGesture( Gesture.TYPE_CIRCLE );
_controller.enableGesture( Gesture.TYPE_SCREEN_TAP );
_controller.enableGesture( Gesture.TYPE_KEY_TAP );
}
private function onInit(e:LeapEvent):void
{
trace("ON INIT STARTED");
}
private function doSomething(e:TouchEvent):void
{
trace("I WAS TOUCHED!!!");
}
}
}
If a good code Samaritan can update this code to perform a screen tap gesture (or any interacion with any object), I will really appreciate this a lot.
Regards!
controller.enableGesture(Gesture.TYPE_SWIPE);
controller.enableGesture(Gesture.TYPE_SCREEN_TAP);
if(controller.config().setFloat("Gesture.Swipe.MinLength", 200.0) && controller.config().setFloat("Gesture.Swipe.MinVelocity", 500)) controller.config().save();
if(controller.config().setFloat("Gesture.ScreenTap.MinForwardVelocity", 30.0) && controller.config().setFloat("Gesture.ScreenTap.HistorySeconds", .5) && controller.config().setFloat("Gesture.ScreenTap.MinDistance", 1.0)) controller.config().save();
//etc...
Then catch it in the frame event listener:
private function onFrame( event:LeapEvent ):void
{
var frame:Frame = event.frame;
var gestures:Vector.<Gesture> = frame.gestures();
for ( var i:int = 0; i < gestures.length; i++ )
{
var gesture:Gesture = gestures[ i ];
switch ( gesture.type )
{
case Gesture.TYPE_SCREEN_TAP:
var screentap:ScreenTapGesture = ScreenTapGesture ( gesture);
trace ("ScreenTapGesture-> x: " + Math.round(screentap.position.x ) + ", y: "+ Math.round( screentap.position.y));
break;
case Gesture.TYPE_SWIPE:
var screenSwipe:SwipeGesture = SwipeGesture(gesture);
if(gesture.state == Gesture.STATE_START) {
//
}
else if(gesture.state == Gesture.STATE_STOP) {
//
trace("SwipeGesture-> direction: "+screenSwipe.direction + ", duration: " + screenSwipe.duration);
}
break;
default:
trace( "Unknown gesture type." )
}
}
}
When the event occurs, check the coordinates translated to the stage/screen and whether a hit test returns true.
EDIT: Considering I have no idea how to reliable get the touch point x/y (or better: how to translate them to the correct screen coordinates), I would probably do something like this in my onFrame event:
private function onFrame(event:LeapEvent):void {
var frame:Frame = event.frame;
var gestures:Vector.<Gesture> = frame.gestures();
var posX:Number;
var posY:Number;
var s:Shape;
if(frame.pointables.length > 0) {
var currentVector:Vector3 = screen.intersectPointable(frame.pointables[0], true); //get normalized vector
posX = 1920 * currentVector.x - stage.x; //NOTE: I hardcoded the screen res value, you can get it like var w:int = leap.locatedScreens()[0].widthPixels();
posY = 1080 * ( 1 - currentVector.y ) - stage.y; //NOTE: I hardcoded the screen res value, you can get it like var h:int = leap.locatedScreens()[0].heightPixels();
}
for(var i:int = 0; i < gestures.length; i++) {
var gesture:Gesture = gestures[i];
if(gesture.type == Gesture.TYPE_SCREEN_TAP) {
if(posX >= _button1.x &&
posX <= _button1.x + _button1.width &&
posY >= _button1.y &&
posY <= _button1.y + _button1.height) {
s = new Shape();
s.graphics.beginFill(0x00FF00);
s.graphics.drawCircle(0, 0, 10);
s.graphics.endFill();
s.x = posX;
s.y = posY;
stage.addChild(s);
trace("Lisa tocada!");
}
else {
s = new Shape();
s.graphics.beginFill(0xFF0000);
s.graphics.drawCircle(0, 0, 10);
s.graphics.endFill();
s.x = posX;
s.y = posY;
stage.addChild(s);
trace("Fallaste! Intentalo otra vez, tiempo: "+new Date().getTime());
}
}
}
}
I'm creating Flash "memory" game, Idea to discover 2 equal cards. I need to add "Timer" in top of window which count in how many seconds all cards will be descovered.
Here is my code:
package
{
import flash.display.MovieClip;
import Card;
import Boarder;
import BlueBoard;
import flash.events.MouseEvent;
import RedBoard;
import Snow;
public class MemoryGame extends MovieClip
{
private var _card:Card;
private var _boarder:Boarder;
private var _blueBoard:BlueBoard;
private var _cardX:Number;
private var _cardY:Number;
private var _firstCard:*;
private var _totalMatches:Number;
private var _currentMatches:Number;
private var _redBoard:RedBoard;
private var _snow:Snow;
private var _cards:Array;
public var _message:String;
public function MemoryGame()
{
_cards = new Array();
_totalMatches = 4;
_currentMatches = 0;
createCards();
}
private function createCards():void
{
_cardX = 45;
_cardY = 10;
for(var i:Number = 0; i < 2; i++)
{
_card = new Card();
addChild(_card);
_boarder = new Boarder();
_card.setType(_boarder);
_card.x = _cardX;
_card.y = _cardY;
_cardX += _card.width + 50;
_card.addEventListener(MouseEvent.CLICK, checkCards);
_cards.push(_card);
}
for(var j:Number = 0; j < 2; j++)
{
_card = new Card();
addChild(_card);
_blueBoard = new BlueBoard();
_card.setType(_blueBoard);
_card.x = _cardX;
_card.y = _cardY;
_cardX += _card.width + 50;
_card.addEventListener(MouseEvent.CLICK, checkCards);
_cards.push(_card);
}
_cardX = 45;
_cardY = _card.height + 30;
for(var k:Number = 0; k < 2; k++)
{
_card = new Card();
addChild(_card);
_redBoard = new RedBoard();
_card.setType(_redBoard);
_card.x = _cardX;
_card.y = _cardY;
_cardX += _card.width + 50;
_card.addEventListener(MouseEvent.CLICK, checkCards);
_cards.push(_card);
}
for(var l:Number = 0; l < 2; l++)
{
_card = new Card();
addChild(_card);
_snow = new Snow();
_card.setType(_snow);
_card.x = _cardX;
_card.y = _cardY;
_cardX += _card.width + 50;
_card.addEventListener(MouseEvent.CLICK, checkCards);
_cards.push(_card);
}
randomizeCards(_cards);
}
private function checkCards(event:MouseEvent):void
{
event.currentTarget.removeEventListener(MouseEvent.CLICK, checkCards);
if(_firstCard == undefined)
{
_firstCard = event.currentTarget;
}
else if(String(_firstCard._type) == String(event.currentTarget._type))
{
trace("match");
_message = "match";
message_txt.text = _message;
_firstCard = undefined;
_currentMatches ++;
if(_currentMatches >= _totalMatches)
{
trace("YOU WIN !!!");
_message = "YOU WIN !!!";
message_txt.text = _message;
}
}
else
{
trace("wrong");
_message = "wrong";
message_txt.text = _message;
_firstCard.gotoAndPlay("flipBack");
event.currentTarget.gotoAndPlay("flipBack");
_firstCard.addEventListener(MouseEvent.CLICK, checkCards);
event.currentTarget.addEventListener(MouseEvent.CLICK, checkCards);
_firstCard = undefined;
}
}
private function randomizeCards(cards:Array):void
{
var randomCard1:Number;
var randomCard2:Number;
var card1X:Number;
var card1Y:Number;
for(var i:Number = 0; i < 10; i++)
{
randomCard1 = Math.floor(Math.random() * cards.length);
randomCard2 = Math.floor(Math.random() * cards.length);
card1X = cards[randomCard1].x;
card1Y = cards[randomCard1].y;
cards[randomCard1].x = cards[randomCard2].x;
cards[randomCard1].y = cards[randomCard2].y
cards[randomCard2].x = card1X;
cards[randomCard2].y = card1Y;
}
}
}
}
EDIT:
And I have one more question. When I will add this game to PHP, how can I add username and his time to database? I need write code in Action Script (swf file) or I can do it in php later? I mean in php can I use any method to get time from swf file and write it to database?
Could you help me? Thank you very much.
Here is an example of how to use the Timer class (flash.utils.Timer) to accomplished what you're asking:
var timer:Timer; //import flash.utils.Timer;
var txtTime:TextField;
var tmpTime:Number; //this will store the time when the game is started
//your constructor:
public function MemoryGame()
{
timer = new Timer(1000); //create a new timer that ticks every second.
timer.addEventListener(TimerEvent.TIMER, tick, false, 0, true); //listen for the timer tick
txtTime = new TextField();
addChild(txtTime);
tmpTime = flash.utils.getTimer();
timer.start(); //start the timer
//....the rest of your code
}
private function tick(e:Event):void {
txtTime.text = showTimePassed(flash.utils.getTimer() - tmpTime);
}
//this function will format your time like a stopwatch
function showTimePassed(startTime:int):String {
var leadingZeroMS:String = ""; //how many leading 0's to put in front of the miliseconds
var leadingZeroS:String = ""; //how many leading 0's to put in front of the seconds
var time = getTimer() - startTime; //this gets the amount of miliseconds elapsed
var miliseconds = (time % 1000); // modulus (%) gives you the remainder after dividing,
if (miliseconds < 10) { //if less than two digits, add a leading 0
leadingZeroMS = "0";
}
var seconds = Math.floor((time / 1000) % 60); //this gets the amount of seconds
if (seconds < 10) { //if seconds are less than two digits, add the leading zero
leadingZeroS = "0";
}
var minutes = Math.floor( (time / (60 * 1000) ) ); //60 seconds times 1000 miliseocnds gets the minutes
return minutes + ":" + leadingZeroS + seconds + "." + leadingZeroMS + miliseconds;
}
//in your you-win block of code:
var score = flash.utils.getTimer() - tmpTime; //this store how many milliseconds it took them to complete the game.
This creates a timer that will tick every second, and update a text field with the current amount of seconds elapsed
As per the second part of your request, here is a way you can do this (the database work needs to be done in PHP, this shows you how to send the data to a php page)
var urlLoader:URLLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, scoreSaveResponse,false,0,true);
var request:URLRequest = new URLRequest("http://mysite.com/score.php");
var urlVars:URLVariables = new URLVariables();
urlVars.time = flash.utils.getTimer() - tmpTime;
urlVars.userName = "yourUserName";
//add any other parameters you want to pass to your PHP page
request.method = URLRequestMethod.POST;
urlLoader.data = urlVars;
urlLoader.load(request);
function scoreSaveResponse(e:Event):void {
//whatever you return from the php page is found in urlLoader.data
}
so this the code with it i am able to mix several tracks
with a Shader done in pixel bender.
the problem here i don't know when the mixing is finish or all the sound reache their end
to be able to save the bytearray into a file any Event or something like that
help plz ?
package
{
import flash.display.*;
import flash.media.*;
import flash.events.*;
import flash.net.*;
import flash.utils.*;
import fl.controls.Slider;
import org.bytearray.micrecorder.encoder.WaveEncoder;
[SWF(width='500', height='380', frameRate='24')]
public class AudioMixer extends Sprite{
[Embed(source = "sound2.mp3")] private var Track1:Class;
[Embed(source = "sound1.mp3")] private var Track2:Class;
[Embed(source = "mix.pbj",mimeType = "application/octet-stream")]
private var EmbedShader:Class;
private var shader:Shader = new Shader(new EmbedShader());
private var sound:Vector.<Sound> = new Vector.<Sound>();
private var bytes:Vector.<ByteArray> = new Vector.<ByteArray>();
private var sliders:Vector.<Slider> = new Vector.<Slider>();
private var graph:Vector.<Shape> = new Vector.<Shape>();
private var recBA:ByteArray = new ByteArray();
private var BUFFER_SIZE:int = 0x800;
public var playback:Sound = new Sound();
public var container:Sprite = new Sprite();
public var isEvent:Boolean = false;
public function AudioMixer():void{
container.y = stage.stageHeight * .5;
addChild(container);
sound.push(new Track1(), new Track2(),new Track2(),new Track2(),new Track2(),new Track2(),new Track2(),new Track2(),new Track2(),new Track2(),new Track2(),new Track2());
for(var i:int = 0; i < sound.length; i++){
var slider:Slider = new Slider();
slider.maximum = 1;
slider.minimum = 0;
slider.snapInterval = 0.025;
slider.value = 0.8;
slider.rotation += -90;
slider.x = i * 40 + 25;
container.addChild(slider);
sliders.push(slider);
var line:Shape = new Shape();
line.graphics.lineStyle(1, 0x888888);
line.graphics.drawRect(i * 40 + 14, 0, 5, -80);
line.graphics.endFill();
container.addChild(line);
var shape:Shape = new Shape();
shape.graphics.beginFill(0x00cc00);
shape.graphics.drawRect(i * 40 + 15, 0, 3, -80);
shape.graphics.endFill();
container.addChild(shape);
graph.push(shape);
}
playback.addEventListener(SampleDataEvent.SAMPLE_DATA, onSoundData);
playback.play();
}
private function onSoundData(event:SampleDataEvent):void {
for(var i:int = 0; i < sound.length; i++){
bytes[i] = new ByteArray();
bytes[i].length = BUFFER_SIZE * 4 * 2;
sound[i].extract(bytes[i], BUFFER_SIZE);
var volume:Number = 0;
bytes[i].position = 0;
for(var j:int = 0; j < BUFFER_SIZE; j++){
volume += Math.abs(bytes[i].readFloat());
volume += Math.abs(bytes[i].readFloat());
}
volume = (volume / (BUFFER_SIZE * .5)) * sliders[i].value;
shader.data['track' + (i + 1)].width = BUFFER_SIZE / 1024;
shader.data['track' + (i + 1)].height = 512;
shader.data['track' + (i + 1)].input = bytes[i];
shader.data['vol' + (i + 1)].value = [sliders[i].value];
graph[i].scaleY = volume;
}
var shaderJob:ShaderJob = new ShaderJob(shader,event.data,BUFFER_SIZE / 1024,512);
shaderJob.start(true);
var shaderJob2:ShaderJob = new ShaderJob(shader,recBA,BUFFER_SIZE / 1024,512);
shaderJob2.start(true);
}
}
}
You can tell when a shader has completed it's job using the ShaderEvent.COMPLETE listener. Like so:
shaderJob.addEventListener(ShaderEvent.COMPLETE, onShaderComplete);
private function onShaderComplete(e:Event):void
{
//Do Something here
}
See this link for more details.
One thing about your code though. You're doing this shader job inside of a sampleDataEvent and I can see this being problematic (possibly) in the sense that your mixing may be out of sync with your playback (that is, if you plan on mixing live and writing the mixed data back into the sound stream). Anyway that's perhaps an issue for a new question. This should solve your problem with needing to know when the mixing is complete.
Note you also need to add "false" to the shaderJob.start(false) function. From the documentation about the ShaderEvent.COMPLETE:
"Dispatched when a ShaderJob that executes asynchronously finishes processing the data using the shader. A ShaderJob instance executes asynchronously when the start() method is called with a false value for the waitForCompletion parameter."
Update
In response to your inquiry about how to only process inside the sampleDataEvent if the sound isnt being processed:
private var isProcessing:Boolean = false;
private function onSoundData(event:SampleDataEvent):void {
if(isProcessing != true){
for(var i:int = 0; i < sound.length; i++){
bytes[i] = new ByteArray();
bytes[i].length = BUFFER_SIZE * 4 * 2;
sound[i].extract(bytes[i], BUFFER_SIZE);
var volume:Number = 0;
bytes[i].position = 0;
for(var j:int = 0; j < BUFFER_SIZE; j++){
volume += Math.abs(bytes[i].readFloat());
volume += Math.abs(bytes[i].readFloat());
}
volume = (volume / (BUFFER_SIZE * .5)) * sliders[i].value;
shader.data['track' + (i + 1)].width = BUFFER_SIZE / 1024;
shader.data['track' + (i + 1)].height = 512;
shader.data['track' + (i + 1)].input = bytes[i];
shader.data['vol' + (i + 1)].value = [sliders[i].value];
graph[i].scaleY = volume;
}
var shaderJob:ShaderJob = new ShaderJob(shader,event.data,BUFFER_SIZE / 1024,512);
shaderJob.start(false);
shaderJob.addEventListener(ShaderEvent.COMPLETE, onShaderComplete);
var shaderJob2:ShaderJob = new ShaderJob(shader,recBA,BUFFER_SIZE / 1024,512);
shaderJob2.start(false);
}
}
private function onShaderComplete(e:ShaderEvent):void
{
//Do something here
isProcessing = false;
}