Shaking effect - Flash CS6 ActionScript3.0
Asked Answered
F

4

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

Foret answered 3/12, 2012 at 14:10 Comment(6)
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.Phratry
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.Fetor
@Phratry Sorry, but your code doesn't seem to work, only get Error 1120 Access of undefined property ORIGINALX.Foret
Well, I removed ORIGINAL X, but it moved itself to the left in the lower corner and then started shakingForet
Also, it only shakes sidewardsForet
@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...Phratry
F
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.

Fetor answered 3/12, 2012 at 14:29 Comment(9)
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?Foret
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.Fetor
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 ^_^Foret
Also, I noticed that the shaking ended smooth, is there any way to make it start smooth and be rough until the object disappears?Foret
It now uses the same variable for time and amount.Fetor
So, I cant make it shake lightly for aproximately 100 frames? (25 FPS)Foret
I've updated the post and the files on dropbox, hope that helps.Fetor
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.Sing
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.Fetor
A
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.

Abduction answered 3/12, 2012 at 14:37 Comment(5)
You cannot just use a loop for this. You have to change the position values over time, using a timer or ENTER_FRAME event.Fetor
Very true - I was more worried about the algorithm - fixed - thanksAbduction
I tested this and the random works by far the best. Add the codeAbduction
Sorry, this is far beyond my understanding of Flash and AS 3.0 :-(Foret
Sorry to hear that. It's quite simple, just doing a lot of Sprites at once make things a little more complicated.Abduction
F
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.

Fetor answered 3/12, 2012 at 14:29 Comment(9)
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?Foret
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.Fetor
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 ^_^Foret
Also, I noticed that the shaking ended smooth, is there any way to make it start smooth and be rough until the object disappears?Foret
It now uses the same variable for time and amount.Fetor
So, I cant make it shake lightly for aproximately 100 frames? (25 FPS)Foret
I've updated the post and the files on dropbox, hope that helps.Fetor
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.Sing
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.Fetor
S
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();
    }
Sing answered 5/12, 2012 at 0:26 Comment(0)
P
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;
}
Perzan answered 3/12, 2012 at 14:27 Comment(2)
Yes but Brownian motion. They should set it every step from an original value.Phratry
You cannot just use a loop for this. You have to change the position values over time, using a timer or ENTER_FRAME event.Fetor

© 2022 - 2024 — McMap. All rights reserved.