7
votes

This question is related to ActionScript 3.0 and Flash CS6

I am trying to make an object shake a bit in a certain for some seconds. I made it a "movieclip" and made this code:

import flash.events.TimerEvent;

var Machine_mc:Array = new Array();

var fl_machineshaking:Timer = new Timer(1000, 10);
fl_machineshaking.addEventListener (TimerEvent.TIMER, fl_shakemachine);
fl_machineshaking.start ();


function fl_shakemachine (event:TimerEvent):void {


 for (var i = 0; i < 20; i++) {

  Machine.x += Math.random() * 6 - 4;
  Machine.y += Math.random() * 6 - 4;
 }

}

When testing the movie I get multiple errors looking exactly like this one:

TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at Historieoppgave_fla::MainTimeline/fl_shakemachine()
    at flash.utils::Timer/_timerDispatch()
    at flash.utils::Timer/tick()

Also, the object doesnt shake, but it moves steadily upwards to the left a bit every tick.

To the point: I wish to know how I stop the script after the object is not in the stage/scene anymore and also how to make it shake around, as I do not see what is wrong with my script, please help, thank you ^_^

4
Ah! You've discovered "Brownian motion". Look it up :D What you really want is Machine.x = ORIGINALX + Math.random() * 6 - 4; so it makes a new spot every frame. Also note that random produces a number from 0-1. So the function produces numbers from [0-1]*6-4 = [0-6]-4 = [-4,2]. Hence it will, on average be -2. This is why it moves upwards and to the left on average. Just try to make it balanced: [0-1]*6-3 = [0-6]-3 = [-3,3], average = 0.AStupidNoob
In Flash, you should enable 'permit debugging' in your 'publish settings' to have more detailed errors. This also gives back the line numbers where your code is breaking.Mark Knol
@AStupidNoob Sorry, but your code doesn't seem to work, only get Error 1120 Access of undefined property ORIGINALX.The Last Melody
Well, I removed ORIGINAL X, but it moved itself to the left in the lower corner and then started shakingThe Last Melody
Also, it only shakes sidewardsThe Last Melody

4 Answers

3
votes

You have to remember the original start position and calculate the shake effect from that point. This is my shake effect for MovieClips. It dynamically adds 3 variables (startPosition, shakeTime, maxShakeAmount) to it. If you use classes, you would add them to your clips.

import flash.display.MovieClip;
import flash.geom.Point;

function shake(mc:MovieClip, frames:int = 10, maxShakeAmount:int = 30) : void 
{
    if (!mc._shakeTime || mc._shakeTime <= 0)
    {
        mc.startPosition = new Point(mc.x, mc.y);
        mc._shakeTime = frames;
        mc._maxShakeAmount = maxShakeAmount;
        mc.addEventListener(Event.ENTER_FRAME, handleShakeEnterFrame);
    }
    else
    {
        mc.startPosition = new Point(mc.x, mc.y);
        mc._shakeTime += frames;
        mc._maxShakeAmount = maxShakeAmount;
    }
}

function handleShakeEnterFrame(event:Event):void
{
    var mc:MovieClip = MovieClip(event.currentTarget);
    var shakeAmount:Number = Math.min(mc._maxShakeAmount, mc._shakeTime);
    mc.x = mc.startPosition.x + (-shakeAmount / 2 + Math.random() * shakeAmount);
    mc.y = mc.startPosition.y + (-shakeAmount / 2 + Math.random() * shakeAmount);

    mc._shakeTime--;

    if (mc._shakeTime <= 0)
    {
        mc._shakeTime = 0;
        mc.removeEventListener(Event.ENTER_FRAME, handleShakeEnterFrame);
    }
}

You can use it like this:

// shake for 100 frames, with max distance of 15px
this.shake(myMc, 100, 15);

BTW: In Flash, you should enable 'permit debugging' in your 'publish settings' to have more detailed errors. This also gives back the line numbers where your code is breaking.


update:
Code now with time / maximum distance separated.

4
votes

AStupidNube brought up a great point about the original position. So adding that to shaking that should be a back and forth motion, so don't rely on random values that may or may not get you what you want. Shaking also has a dampening effect over time, so try something like this:

Link to working code

http://wonderfl.net/c/eB1E - Event.ENTER_FRAME based

http://wonderfl.net/c/hJJl - Timer Based

http://wonderfl.net/c/chYC - Event.ENTER_FRAME based with extra randomness

**1 to 20 shaking items Timer Based code - see link above for ENTER_FRAME code••

package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.text.TextField;
import flash.utils.Timer;

public class testing extends Sprite {

    private var shakeButton:Sprite;
    private var graphic:Sprite;
    private var shakerPos:Array;
    private var shakers:Array;
    private var numShakers:int = 20;
    private var dir:int = 1;
    private var displacement:Number = 10;
    private var shakeTimer:Timer;

    public function testing() {
        this.shakers = new Array();
        this.shakerPos = new Array();
        this.addEventListener(Event.ADDED_TO_STAGE, this.init);
    }
    private function init(e:Event):void {
        this.stage.frameRate = 30;
        this.shakeTimer = new Timer(33, 20);
        this.shakeTimer.addEventListener(TimerEvent.TIMER, this.shake);
        this.graphics.beginFill(0x333333);
        this.graphics.drawRect(0,0,this.stage.stageWidth, this.stage.stageHeight);
        this.graphics.endFill();

        this.createShakers();

        this.shakeButton = this.createSpriteButton("Shake ");
        this.addChild(this.shakeButton);
        this.shakeButton.x = 10;
        this.shakeButton.y = 10;
        this.shakeButton.addEventListener(MouseEvent.CLICK, this.shakeCallback);
    }
    private function createSpriteButton(btnName:String):Sprite {
        var sBtn:Sprite = new Sprite();
        sBtn.name = btnName;
        sBtn.graphics.beginFill(0xFFFFFF);
        sBtn.graphics.drawRoundRect(0,0,80,20,5);
        var sBtnTF:TextField = new TextField();
        sBtn.addChild(sBtnTF);
        sBtnTF.text = btnName;
        sBtnTF.x = 5;
        sBtnTF.y = 3;
        sBtnTF.selectable = false;
        sBtn.alpha = .5;
        sBtn.addEventListener(MouseEvent.MOUSE_OVER, function(e:Event):void { sBtn.alpha = 1 });
        sBtn.addEventListener(MouseEvent.MOUSE_OUT, function(e:Event):void { sBtn.alpha = .5 });
        return sBtn;
    }
    private function createShakers():void {
        var graphic:Sprite;

        for(var i:int = 0;i < this.numShakers;i++) {
            graphic = new Sprite();
            this.addChild(graphic);
            graphic.graphics.beginFill(0xFFFFFF);
            graphic.graphics.drawRect(0,0,10,10);
            graphic.graphics.endFill();
            // add a 30 pixel margin for the graphic
            graphic.x = (this.stage.stageWidth-60)*Math.random()+30;
            graphic.y = (this.stage.stageWidth-60)*Math.random()+30;
            this.shakers[i] = graphic;
            this.shakerPos[i] = new Point(graphic.x, graphic.y);
        }
    }
    private function shakeCallback(e:Event):void {
        this.shakeTimer.reset();
        this.shakeTimer.start();
    }
    private function shake(e:TimerEvent):void {
        this.dir *= -1;
        var dampening:Number = (20 - e.target.currentCount)/20;
        for(var i:int = 0;i < this.numShakers;i++) {
            this.shakers[i].x = this.shakerPos[i].x + Math.random()*10*dir*dampening;
            this.shakers[i].y = this.shakerPos[i].y + Math.random()*10*dir*dampening;
        }
    }
}

}

Now this is a linear dampening, you can adjust as you see fit by squaring or cubing the values.

3
votes

Here is a forked version of the chosen answer, but is a bit more flexible in that it allows you to set the frequency as well. It's also based on time as opposed to frames so you can think in terms of time(ms) as opposed to frames when setting the duration and interval.

The usage is similar to the chosen answer :

shake (clipToShake, durationInMilliseconds, frequencyInMilliseconds, maxShakeRange);

This is just an example of what I meant by using a TimerEvent as opposed to a ENTER_FRAME. It also doesn't require adding dynamic variables to the MovieClips you are shaking to track time, shakeAmount, and starting position.

    public function shake(shakeClip:MovieClip, duration:Number = 3000, frequency:Number = 30, distance:Number = 30):void
    {
        var shakes:int = duration / frequency;
        var shakeTimer:Timer = new Timer(frequency, shakes);
        var startX:Number = shakeClip.x;
        var startY:Number = shakeClip.y;

        var shakeUpdate:Function = function(e:TimerEvent):void
            {
                shakeClip.x = startX + ( -distance / 2 + Math.random() * distance);
                shakeClip.y = startY + ( -distance / 2 + Math.random() * distance); 
            }

        var shakeComplete:Function = function(e:TimerEvent):void
            {
                shakeClip.x = startX;
                shakeClip.y = startY;
                e.target.removeEventListener(TimerEvent.TIMER, shakeUpdate);
                e.target.removeEventListener(TimerEvent.TIMER_COMPLETE, shakeComplete);
            }

        shakeTimer.addEventListener(TimerEvent.TIMER, shakeUpdate);
        shakeTimer.addEventListener(TimerEvent.TIMER_COMPLETE, shakeComplete);

        shakeTimer.start();
    }
0
votes

-4 <= Math.random() * 6 - 4 < 2

You add this offset to Machine.x 20 times, so chances for moving to the left is greater, than to the right.

It seems that you looking for something like this:

for each (var currentMachine:MovieClip in Machine_mc)
{
     currentMachine.x += Math.random() * 6 - 3;
     currentMachine.y += Math.random() * 6 - 3;
}