You can try out my with my first animation here
Several months ago I bought Actionscript 3.0 Animation - Over Christmas I have had some time to play with some ideas. I am interested in visualisation tools - especially things that help technical people understand better what is happening in a complex environment. eg. visualisation of code execution or visualisation of business strategy.
Firstly some key summary points of what I learned from the book:
Sprites are the base class for all interactive elements on the visible screen which is called a Stage. (Actually all interactive elements are derived from DisplayObjectContainer but for the purposes of these animations we work mainly with sprites). The addChild(aSprite) method is what makes the sprite visible on the Stage. You can use removeChild(aSprite) to remove it from the stage. The sprite is not destroyed - just hiding in the wings and not visible to the user. Adding the sprite back onto the stage is a process known as reparenting.
The aSprite.graphics. draw api allow you draw lines and circles associated with that Sprite.
Changing the value of aSprite.rotation property causes the sprite to rotate that value in degrees. All lines drawn by the draw api associated with aSprite will rotate with it automatically.
aSprite.x
aSprite.y
properties values change the sprite position on the stage.
mouse events occur when you are over a sprite but are always available to the stage. ie. if you move over a ball spite and you can have listeners on the stage and the ball, both receiving events
stage.focus = aSprite;
allows you to capture keyboard events on a sprite instead of only on the stage
aSprite.mouseX aSprite.mouseY values are relative to the position of the sprite on the stage.
My first question is : Given that each sprite created has overhead, when do you create something as a seperate sprite?
I think the answer is:
- When you want to be able to capture events associated with it seperately - for example the handle of a interactive slider is created as sprite
- When you want to be able to move the object seperately - by simply changing the .x and .y properties
- When you you want to be able to rotate the object and have all graphics associated with the sprite rotate autmatically
- When you want to be able to show ( addChild() ) or remove the object (removeChild()) from the stage seperately
so a tree could be
- single tree sprite
- part of a forest sprite
- could have branches as sprites
all depending on what you want to happen in the animation.
Flash's coordinate system is a little messed up - they have x,y position 0,0 in the top left corner and angles (such as aSprite.rotation) run counteclockwise.
to find the angle between to points you can simply use
dx = mouseX - sprite.x;
dy = mouseY - sprite.y;
angleInRadians = Math.atan2( dy, dx );
sprite.rotation = angleInRadians * 180 / Math.PI;
Note: All of the trig functions expect and return radians but sprite.rotation requires 'angle in degrees'.
you can always get the distance between two points using pythagoras theorem
dx = x2 - x1;
dy = y2 - y1;
distance = Math.sqrt( (dx*dx) + (dy*dy) );
Side Note: Adding and removing event listeners in flash is lightweight - so its normal practice to call removeEventListener() when you are in a state where you want to ignore events - rather than have a ignoreEventX flag
In order to perform animation - we may have a set of event listeners for keyboard and mouse events and most importantly:
addEventListener( Event.ENTER_FRAME, onEnterFrame );
listener which is called at the framerate of the flashplayer
inside the onEnterFrame() method - the following code often seems to appear:
- for each seperate sprite on the stage,
- take each of the physical forces that are being applied to that sprite broken down as x and y components (use cos and sin to do this)
- sum these forces and calculate the sprites new position
- check if the sprite has now gone beyond the stage boundaries
- for each sprite that has not been already checked - check for a collision
- if a collision has occured apply interaction logic - could be collision mechanics or attaching the two sprites.
to convert any angle and force magnitude into its x and y components use
vx = Math.cos(angleInRadians) * speedInPixelsPerFrame;
vy = Math.sin( angleInRadians ) * speedInPixelsPerFrame;
We have to run a lot of calculations for each sprite on each frame - so it is common to simply cache the values of xy and vy and work with these directly. Also because it is simply about creating a realistic simulation and speed is actually in pixels per frame - constants like gravity force of G = 9.82 m/s2are simply not used - the developer experiments with the variable constants until a realistic animation is created.
Forces are simply "rate of change of velocity" - so an accelerating spaceship might be modeled as
vx += ax;
vy += ay;
ie. on each onEnterFrame() call - the x and y velocity vectors increase by a value of ax and ay. On the same onEnterFrame() call, the new position of the sprite is then calculated as
sprite.x += vx;
sprite.y += vy;
Gravity on earth is a constant - the distance component is ignored as the mass of the particle vs mass of earth are significantly different - hence we have a y acceleration of:
vy += gravityConstantValueChosenByDeveloper;
Friction force increases the faster something is moving so we use * in the formula:
vx *= frictionValueChosenByDeveloper;
vy *= frictionValueChosenByDeveloper;
Easing occurs when a something moves slower as it gets closer to its target - eg. a person runs up to a wall - The formula therefore calculates the distance between sprite and target
vx = (targetX - sprite.x) * easingValueChosenByDeveloper;
vy = (targetY - sprite.y) * easingValueChosenByDeveloper;
A spring pulls harder the further apart the sprite and the target are. ie. the formula incorporates the distance between the sprite and he target
ax = (targetX - sprite.x) * springStrength;
ay = (targetY - sprite.y) * springStrength;
Offsetting a target: When working with springs or easing its important to be able to calculate the actual target x y cooridates as they may not be the center of the sprite - for example: they could be the nose of the spaceship or end of a bat. In order to do this we use one of our standard trig formulae:
dx = sprite.x - knownX;
dy = sprite.y - knownY;
angleInRadians = Math.atan2( dy, dx );
actualTargetX = knownX + Math.cos( angleInRadians ) * distanceInAStraightLine;
actualTargetY = knownY + Math.cos( angleInRadians ) * distanceInAStraightLine;
Collision detection
basically you have decide if you want to use:
- flash's built in hit sprite1.hitTestObject( sprite2) - which assumes that the objects are rectangles
- a distance based collision detection calculation - which favours circular objects
The distance based collision detection calculation formula is simple
dx = sprite2.x - sprite1.x;
dy = sprite2.y - sprite1.y;
dist = Math.sqrt( dx*dx + dy*dy ); // pythagoras theorem
if ( dist > (sprite2.radius + sprite1.radius) ){
trace( "hit");
}
The book gives a simple trick to iterating over each of the sprites only once - this way if object A is not colliding with object B then we dont need to check if B is colliding with A