Skip to content

Latest commit

 

History

History
763 lines (476 loc) · 19.8 KB

README.pod

File metadata and controls

763 lines (476 loc) · 19.8 KB

NAME

SDLx::Tween - SDL Perl XS Tweening Library

SYNOPSIS

# simple linear tween
use SDLx::Tween::Timeline;

$sdlx_app = SDLx::App->new(...);

# timeline gets its ticks from the SDLx app controller move handler
$timeline = SDLx::Tween::Timeline->new(app => $sdlx_app);

$xy = [0, 0]; # tween the position stored in this array ref

# create tweens from the timeline, setting the target ($xy), the
# duration (1 second), and the final requested value ([640, 480])
$tween = $timeline->tween(on => $xy, t => 1_000, to => [640, 480]);

# tween will not do anything until started
$tween->start;

# xy will now be tweened between [0, 0] and [640, 480] for 1 second 
# and then the tween will stop
$sdlx_app->run;

# tween accessors
$ticks = $tween->get_cycle_start_time;
$bool  = $tween->is_active;
$ticks = $tween->get_duration;

# tween methods
$tween->set_duration($new_duration_in_ticks); # hasten/slow a tween
$tween->start($optional_ideal_cycle_start_time_in_ticks);  
$tween->stop;
$tween->pause($optional_ideal_pause_time_in_ticks);
$tween->resume($optional_ideal_resume_time_in_ticks);
$tween->tick($now_in_ticks); # called internally by timeline or SDLx app

# tweening an integer get/set accessor
$tween = $timeline->tween(
    on            => [radius => $circle], # set $circle->radius
    t             => 4_000,               # tween duration
    from          => 100,                 # initial value
    to            => 300,                 # final value
    round         => 1,                   # round values before setting
    bounce        => 1,                   # reverse when repeating
    forever       => 1,                   # continue forever
    ease          => 'p3_in_out',         # use easing function
);

# tweening 2D position using a non-linear path, repeat 4 times
$tween = $timeline->tween(
    on            => [xy => $circle],     # set values in a xy array ref field
    t             => 4_000,               # tween duration
    to            => [640,480],           # final value, initial taken from $xy
    repeat        => 4,                   # repeat tween 4 times
    path          => 'sine',              # use a sine path
    path_args     => {                    # sine path needs path_args
       {amp => 100, freq => 2},
    }
);

# tail behavior makes one position follow another at given speed
# the behavior makes the tail follow the head
# unlike other tweens, tails only allow the following 3 contructor args
$tail = $timeline->tail(
    speed => 50/1000,                     # advance a distance of 50 pixels a sec 
    head  => $head,                       # position to follow array ref
    tail  => $tail,                       # position to set array ref
);

DESCRIPTION

SDLx::Tween is a library for tweening Perl SDL elements. It lets you to move game objects (GOBs) around in various ways, rotate and scale things, animate sprites and colors, make GOBs spawn at a given rate, and generally bring about changes in the game over time. It lets you do these things declaratively, without writing complex SDLx::Controller move_handlers().

See "WHY" and "FEATURES" for an introduction to tweening, or continue for the technical details.

DECLARING TWEENING BEHAVIORS

THE TIMLINE

The timeline is the tween factory. It is created with an SDLx::App:

$timeline = SDLx::Tween::Timeline->new(sdlx_app => $app);

Now you can create tweens through the timeline, and control the cycle of all the timeline controlled tweens through the timeline: start/stop/pause/resume. You can create as many timelines as you like to control different tween groups.

THE TWEEN

A tween is a behavior of some game property vs. time. It sets the target game property using a proxy, according to some path, at a time computed by an easing function, which obeys certain cycle rules, for a given duration.

A tween is created from a timeline, which passes tick() to the tween from the SDLx::App move_handler. Once you create the tween you can control its cycle: set ideal cycle times, start/stop, pause/resume, and change cycle duration.

The simplest tween uses the method proxy and calls a game method with the tweened value. It uses the default linear path to tween a value between a range given by the range arg. Speed of change will be constant, because the tween uses the default linear easing function.

The simplest tween, animating a turret turning 180 degrees in 1 second:

$tween = $timeline->tween(
    on    => [angle => $turret],
    t     => 1_000,
    range => [0, pi],
);

CYCLE CONTROL

A tween has a duration and a cycle start time. These define its cycle. Several tween constructor arguments help to control the tween cycle:

t

integer Ticks (milliseconds) duration of the tween from start to stop.

forever

boolean Repeat the cycle forever, restarting on each cycle completion.

repeat

integer Repeat the cycle n times, then stop.

bounce

bool If the cycle is repeating, on each cycle completion reverse the tween direction, bouncing the tween between its edge points.

Cycle control methods:

start/stop/pause/resume

These all take an optional ideal event time in SDL ticks, see "ACCURACY" for more info. The difference between start/stop and pause/resume is that start/stop resets the tween, while pause/resume works so that the tween starts from where it left off.

These can also be called on the SDLx::Tween::Timeline. It will broadcast the call to all tweens it has created.

get_cycle_start_time

Returns the tween cycle start time, in SDL ticks.

get/set_duration

Get/set the tween duration, in ticks.

PROXIES

The tween translates ticks from the SDLx::Controller into changes in game elements. To change these elements it uses a proxy. The proxy calls methods on your game objects, or changes array refs directly.

method proxy

If the tween constructor arg on is an array ref of 2 elements, a string and a blessed ref, eg:

# as part of the tween constructor arg hash
on => [method_name => $game_object],

Then the tween will use the method proxy. The tween value will be set by calling the given method on the given object.

If the path requires a from arg (e.g. linear path), and none is supplied, the method defined for the proxy is used to get the initial tween value. In this case the proxy method shoud be able to do get/set.

When using the method proxy you can use the optional round flag when creating the tween. If true, values will be rounded and made distinct by dropping repeated values.

array proxy

If the tween constructor arg on is an array ref of numbers, eg:

$pos = [320, 200];
...
# as part of the tween constructor arg hash
on => $pos,

Then the tween will use the array proxy. The elements of the array ref $pos will be changed directly by the tween. This is very fast, but you lose any semblance of encapsulation.

EASING

The deault tween changes in constant speed because it uses the linear easing function. The time used by the path to compute position of the tween value, advances in a linear rate.

By setting the ease arg in the tween constructor you can make time advance according to a non-linear curve. For example to make the tween go slow at first, then go fast:

# as part of the tween constructor arg hash
ease => 'p2_in',

This will cause time to advance in a quadratic curve. At normalized time $t where $t=$elapsed/$duration and $t is between 0 and 1, the p2_in tween will be where a linear tween would be at time $t**2.

All easing functions except linear ease have 3 variants: _in, _out, and _in_out. To get exponential easing on the forward dir of the tween, you use exponential_in easing. To get it on both dirs, you use exponential_in_out.

These are the available easing functions. See eg/03-easing.pl in the distribution for a visual explanation. See https://github.com/warrenm/AHEasing/blob/master/AHEasing/easing.c for a math explanation. The tweening functions originate from http://robertpenner.com/easing/penner_chapter7_tweening.pdf.

  • linear

  • p2

  • p3

  • p4

  • p5

  • sine

  • circular

  • exponential

  • elastic

  • back

  • bounce

PATHS

The simplest tween follows a linear path, the only option when tweening a 1 dimensional value. You can also use the linear path for tweening values up to 4D, by providing the tween with an array ref of size 4 as the range:

# constructing a tween in 4D
range => [[0, 0, 0, 0], [320, 200, 10, 640]],

When tweening 2D values, you can customize the path the tween takes through the plane. The path is given in the path arg of the tween constructor hash. Some paths also require a path_args key to configure the path. Here are paths available:

linear

Requires one of 2 constructor args: range or from + to. If no range and no from are given then the value is taken from the tween target using the proxy. Thus you can tween a GOB from its current position to another without specifying the current position twice. Here are the 3 options for using the default linear path:

# option 1: construct a tween only with "to", from is taken from the target
to       => [320, 200]

# option 2: provide "from" + "to"
from     => [  0,   0]
to       => [320, 200]

# option 3: provide "range"
range    => [[0, 0], [320, 200]]
sine

Tweens a value along a sine curve. Uses the same from + to setup as the linear path, but requires path_args with amplitude and frequency.

range     => [[0, 0], [320, 200]]
path      => 'sine',
path_args => {amp => 100, freq => 2},
circular

Tweens a value along a circle with a given radius and center, between 2 angles.

path      => 'circle',
path_args => {
    center       => [320, 200],
    radius       => 100,
    begin_angle  => 0,
    end_angle    => 2*pi,
},
spiral

Tweens a value along a spiral.

path      => 'spiral',
path_args => {
    center       => [320, 200],
    begin_radius => 50,
    end_radius   => 150,
    begin_angle  => 0,
    rotations    => 3,
},
polyline

Tweens a value along an array of segments, specified by the xy coordinates of the waypoints. The tween will start at the 1st waypoint and continue until the last following a linear path.

path      => 'polyline',
path_args => { points => [
      [200, 200],
      [600, 200],
      [200, 400],
      [600, 400],
      [200, 200],
]},

COLOR TWEENING

Two special paths exists from tweening SDL colors in a linear path through the 4D space of RGBA.

fade

Tween the opacity of a color between 2 values. To tween the opacity of some red color from opaque to transparent:

path => 'fade',
from => 0xFF0000FF,
to   => 0x00,
rgba

Tween a linear path between 2 points in the 4D color space. To transform red into semi-transparent green:

path => 'rgba',
from => 0xFF0000FF,
to   => 0x00FF00AA,

SPAWNING IS TWEENING

If you need to spawn creeps, missiles, or whatever, you can tween the spawn method on your spawner with an integer wave number:

$spawn_tween = $timeline->tween(
    duration => 10_000,
    on       => [spawn_creep => $gob],
    range    => [0, 9],
    round    => 1,
);

Will call spawn_creep once a second for 10 seconds with the numbers 0 through 9 as the only arg. You can use the value as the wave number. You can also set an easing function to change the rate of spawning.

THE TAIL

The tail is a simple behavior for making one position follow another at a given velocity. It is constructed from the timeline, like tweens, and takes 3 constructor args: speed, head, and tail. The speed is given in changes per tick. Head and tail should be given as array refs. The tail behavior will move the position in the tail array ref towards the head array ref, even if the head position changes.

For example, if you want a game object to follow the cursor, create an array ref $cursor_pos and set its elements on mouse move. This will be the head. Create another array ref $gob_pos for the game object position. This will be the tail, whose elements will be changed by the behavior. It is the position array ref that you read in your paint handler. Then create the behavior:

$rail = $timeline->tail(
    speed => 100/1000,
    head  => $cursor_pos,
    tail  => $gob_pos,
);      

You can then control the tail as you would a tween.

The tail will stop when the distance to the head is smaller than 1, or when the head passes through the tail.

MEMORY MANAGEMENT

There are two issues with tween memory management: how do you keep a ref to the tween in game objects, and how does the tween keep ref to the game elements it changes.

The timeline only keeps weak refs to the tweens it creates, active or inactive. This means you must keep a strong ref to the tween somewhere, usually in the game object. When the game object goes out of the scope, the tween will stop, be destroyed, and cleaned out of the timeline automatically.

The tween only keeps weak refs to the game elements (objects or array refs) it changes. Usually other game object will have strong refs to them, as part of the game scene graph. When the game object that is the target of the tween goes out of scope, you must stop the tween, and never use it again. TODO add event for this and allow changing of targets.

ACCURACY

SDLx::Tween takes into account rounding errors, the inaccuracy of the SDLx::Controller move_handler, and the inaccuracy of time/distance limits on behaviors. Used correctly, 2 tweens on the same path, one with duration 1 sec and the other 2 sec, will always meet every 2 cycles, even 100 years later.

To get this absolute accuracy with no errors growing over time, you need to set ideal start/pause/resume times when controling tween cycles.

Here is an example of starting 2 tweens which is NOT accurate:

# dont do this!
$t1 = $timeline->tween(...);
$t1->start;
$t2 = $timeline->tween(...);
$t2->start;

$t1 and $t2 will not have the same cycle_start_time, and this applies to all cycle control methods. One way to get accuracy, is to start the tweens through the timeline:

# do this
$t1 = $timeline->tween(...);
$t2 = $timeline->tween(...);
$timeline->start;

The timeline will make sure both tweens share the same cycle_start_time. Another way to get accuracy is to use the optional ideal time argument of the cycle control methods:

# or this
$start_time = SDL::get_ticks;
$t1 = $timeline->tween(...);
$t1->start($start_time);
$t2 = $timeline->tween(...);
$t2->start($start_time);

When chaining tweens, the 2nd tween ideal start time should be set as the 1st tween start time + the tween duration.

When spawning tweens, compute the ideal spawn time, and make that the cycle start time.

TODO sugarize this and allow implicit passing of ideal times for sequence/parallel/spawn tweens, then delete this section.

WHY

Writing Perl SDL game move handlers is hard. Consider a missile with 3 states:

  • firing - some sprite animation

  • flying towards enemy - need to update its position until it hits enemy

  • exploding - another sprite animation

The move handler for this game object (GOB) is hard to write, because it needs to:

  • update GOB properties

  • you must take into account acceleration and paths in the computation of these values

  • you need to set limits of the values, wait for the limits

  • GOBs need to act differently according to their state, so you need to manage that as well

  • it all must be very accurate, or animations will miss each other

  • it has to be fast- this code is run per each GOB per each update

As a game becomes more wonderful, the GOB move handlers become more hellish. Brave souls have done it, but even they could not do it in a way us mortals can reuse or even understand.

SDLx::Tween solves the missile requirements. Instead of writing a move handler, declare tweens on your GOBs. SDLx::Tween will take care of the move handler for you.

Instead of writing a move handler which updates the position of $my_gob from its current position to x=100 in 1 second, you can go:

$tween = $timline->tween(on => [x => $my_gob], to =>100, t => 1_000);

SDLx::Tween will setup the correct move handler.

According to http://en.wikipedia.org/wiki/Tweening:

"In the inbetweening workflow of traditional hand-drawn animation, the
senior or key artist would draw the keyframes ... and then would hand over
the scene to his or her assistant the inbetweener who does the rest."

Let SDLx-Tween be your inbetweener.

FEATURES

Perl SDL move handlers are rarely a simple linear progression. An ideal tweening library should feature:

  • tween any method, e.g. a Moose get/set accessor, or directly on an array

  • tween a property with several dimensions, e.g. xy position, some 4D color space

  • tween xy position not on a line, but on some curve

  • round the tween values, and pass only values when they change

  • smooth the motion with acceleration/deceleration using easing functions

  • make the tween bounce, repeat for N cycles or forever

  • pause/resume tweens

  • hasten/slow a tween, for example when creeps are suddenly given a speed bonus

  • follow a moving target, e.g. a homing missile with constant acceleration

  • chain tweens, paralellize tween, e.g start explode tween after reaching target

  • tween sprite frames, color/opacity/brightness/saturation/hue, volume/pitch, spawning, rotation, size, camera position

  • delay before/after tweens

  • rewind/ffw/reverse/seek tweens, and generaly play with elastic time for making the game faster or slower

SDLx::Tween doesn't do everything yet. See the TODO file in the distribution for planned features, and the docs above for supported features.

EXAMPLES

Tweening examples in the distribution dir eg/:

01-circle.pl

the hello world of tweening, a growing circle

02-starfield.pl

demo of 6000 concurrent tweens

03-easing.pl

demo of all easing functions

04-paths.pl

demo of all paths

05-colors.pl

demo of color transitions

06-colors.pl

demo of 100 tail behaviors

SEE ALSO

Development is at https://github.com/PerlGameDev/SDLx-Tween.

Interesting implementations of the tweening idea:

BUGS

Very little safety in XS code. Lose your ref to the tween target (object or array ref being set) and horrible things will happen on next tick.

AUTHOR

eilara <[email protected]>

Big thanks to:

Sam Hocevar, from 14 rue de Plaisance, 75014 Paris, France
https://raw.github.com/warrenm

For his most excellent AHEasing lib which SDLx-Tween uses for easing functions. The license is in the tweencee/ dir. The library is at https://github.com/warrenm/AHEasing.

Check that page for some great info about easing functions.

Huge thanks to Zohar Kelrich <[email protected]> for patient listening and advice.

COPYRIGHT AND LICENSE

Copyright (C) 2011 by Ran Eilam

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available.