7

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 ^_^

The Last Melody
  • 165
  • 1
  • 3
  • 10
  • 2
    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 Dec 03 '12 at 14:31
  • 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 Dec 03 '12 at 14:56
  • @AStupidNoob Sorry, but your code doesn't seem to work, only get Error 1120 Access of undefined property ORIGINALX. – The Last Melody Dec 04 '12 at 07:38
  • Well, I removed ORIGINAL X, but it moved itself to the left in the lower corner and then started shaking – The Last Melody Dec 04 '12 at 07:45
  • Also, it only shakes sidewards – The Last Melody Dec 04 '12 at 07:47
  • @The Last Melody Yes, it wasn't supposed to work as is, you were supposed to make a variable which stored the original X position of the object and replace ORIGINALX with that. The aim is to ensure that the object never moves far from it's original point. By adding random numbers to its position, we can never be sure where it will be. By adding a random number to where it started from, we know that it will always be very close to its original spot. So just make a variable that stores the object's original position, and use that for ORIGINALX and ORIGINALY. It seems you got it working though... – AStupidNoob Dec 05 '12 at 01:23

4 Answers4

4

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.

Gone3d
  • 1,189
  • 1
  • 8
  • 20
3

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.

Mark Knol
  • 9,663
  • 3
  • 29
  • 44
  • I don't have any idea as to what I am even looking at, sorry ^_^ What would I need to change to make this work? – The Last Melody Dec 04 '12 at 07:43
  • You could copy/paste the 2 functions, and use this.shake(myMc, 10); where myMc should be the instance name of the movieclip that should shake. – Mark Knol Dec 04 '12 at 09:02
  • Thank you! This works like a dream ^_^ Also,how do I change the amount and time it is shaking? Found amount, now for the time ^_^ – The Last Melody Dec 04 '12 at 10:25
  • Also, I noticed that the shaking ended smooth, is there any way to make it start smooth and be rough until the object disappears? – The Last Melody Dec 04 '12 at 10:30
  • It now uses the same variable for time and amount. – Mark Knol Dec 04 '12 at 13:06
  • So, I cant make it shake lightly for aproximately 100 frames? (25 FPS) – The Last Melody Dec 04 '12 at 14:28
  • I've updated the post and the files on dropbox, hope that helps. – Mark Knol Dec 04 '12 at 14:37
  • This implementation will work, but might be better to use a TimerEvent as opposed to an Enter_Frame event. Much of the code in this implementation is just replicating what a timer does. Also, it would make it more flexible to set a duration as opposed to frames for both how long to shake and the frequency of shaking. – prototypical Dec 04 '12 at 22:50
  • Using a timer for this is complete overhead since there are no such things as "inbetween frames", so your only skipping random frames, or setting position more than it could show. The only wayba timer could be helpful in this case is to stop in at certain time, however (with minimal lag) you could use stage.frameRate*timeInMs to define the framecount to stop around that certain time too.. Correct me if im wrong. – Mark Knol Dec 05 '12 at 06:33
3

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();
    }
prototypical
  • 6,731
  • 3
  • 24
  • 34
0

-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;
}
AtomicRobot
  • 395
  • 1
  • 12