VectorPath — a new plugin for Tween Engine

A won­der­ful new plug-in for Tween Engine, which gives you the pos­si­bil­i­ty to bold­ly ani­mate sprites along a com­plex path WITHOUT any wor­ries con­cern­ing Besi­er curves. You just draw a path as #vec­tor­Shape, put it on the Stage and that is all. Below are some examples:

Example 1

We want to ani­mate sprite 2 along a path. The path is on sprite 1. The min­i­mum of code for this might be:
on begin­Sprite( me )
tween­er = script( “Tween­Lite” ).getInstance()
tweener.activatePlugin( “Vec­tor­Path­Plu­g­in” )
tweener.to( sprite(2), 3, [ #vectorPath:sprite(1)] )
end beginSprite
In this par­tic­u­lar case, for the sake of the code per­spicu­ity, I have used

on begin­Sprite( me )
tween­er = script( “Tween­Max” ).getInstance()
tweener.activatePlugin( “Vec­tor­Path­Plu­g­in” )
tweener.to( sprite(2), 3, [ #vectorPath:sprite(1), #repeat:-1, #delay:1] )
end beginSprite

Exam­ple 1 demo

The ani­ma­tion sequence begins from the cur­rent posi­tion of sprite 2 and devel­ops to the first point of the path.


Example 2

The #vec­tor­Path prop­er­ty can take two types of val­ues — sprite or #vec­tor­Shape type cast­mem­ber. See how the same exam­ple will look, if the #vec­tor­Shape val­ue is changed and instead of sprite 1 we indi­cate direct­ly its castmember.
on begin­Sprite( me )
tween­er = script( “Tween­Max” ).getInstance()
tweener.activatePlugin( “Vec­tor­Path­Plu­g­in” )
tweener.to( sprite(2), 3, [ #vectorPath:member(“path1”), #delay:1] )
end beginSprite

Exam­ple 2 demo

We do not need sprite 1 any more. This time the ani­ma­tion behaves as if the sprite 2 posi­tion was the ini­tial point of the path.


Example 3

This plug-in can be inte­grat­ed with Time­lineLite and Time­line­Max. Below is a exam­ple of this:

prop­er­ty myTimeline

on begin­Sprite( me )
tween­er = script( “Tween­Max” ).getInstance()
tweener.activatePlugin( “Vec­tor­Path­Plu­g­in” )

timelineSettings = [:]
timelineSettings.addProp( #delay, 1 )
timelineSettings.addProp( #onCom­plete, [ #handler:#reverseAnimation, #object:me ] )
timelineSettings.addProp( #onRe­ver­seC­om­plete, [ #handler:#playAnimation, #object:me ] )
myTime­line = script( “Time­lineLite” ).new( timelineSettings )

d = 4.25
e = “Sine.EaseOut”
p = sprite(1)
tweens = []
repeat with i = 2 to 8
tweens.append( tweener.to( sprite(i),d,[ #vectorPath:p, #orientToPath:true, #ease:e ] ) )
end repeat
myTimeline.appendMultiple( tweens, 0, “start”, 0.1 )
end beginSprite

on playAn­i­ma­tion( me, args )
myTimeline.play()
end playAnimation

on reverseAn­i­ma­tion( me, args )
myTimeline.reverse()
end reverseAnimation

This is how the final result should look like:

Exam­ple 3 demo

Example 4

In order to ani­mate 3D objects we direct­ly use a #vec­tor­Shape cast­mem­ber. For example:

on sceneReady( me, models )
tween­er = script( “Tween­Max” ).getInstance()
tweener.activatePlugin( “Vec­tor­Path­Plu­g­in” )
d = 1
repeat with i = 1 to count( models )
tweener.to( models[i], 3, [ #vectorPath:member(“path1”), #repeat:-1, #orientToPath:true, #ease:“Sine.EaseInOut”, #yoyo:true, #delay:d] )
d = d + 0.1
end repeat
end sceneReady

Exam­ple 4 demo

Here is the code of the plug-in itself. Add it as a #par­ent script under the name of “Vec­tor­Path­Plu­g­in” to the same cast where you have put the rest of the Twee­nEngine scripts.

Show code

[lin­go]
— “Vec­tor­Path­Plu­g­in” par­ent script
on get­Plug­in­Name( this )
return #Vec­tor­Path
end getPluginName

– @parent
prop­er­ty ancestor
prop­er­ty _RAD2DEG — #Float
prop­er­ty _orientData — #list
prop­er­ty _orient — #boolean
prop­er­ty _segments — #List
prop­er­ty _count — #Inte­ger
prop­er­ty _future — #Pro­plist
prop­er­ty _target — #Instance
prop­er­ty _targetType — #sym­bol; change fac­tor method — depends of the _target type — #sprite, #mod­el, etc.
prop­er­ty _startRot — #float; Orig­i­nal 2D object rotation

USAGE:
— tween­er = script( “Tween­Max” ).getInstance()
— tweener.activatePlugin( “Vec­tor­Path­Plu­g­in” )
— tweener.to( sprite(2), 5.25, [ #vectorPath:sprite(1), #orientToPath:true ] )
— OR
— tweener.to( sprite(2), 5.25, [ #vectorPath:member(“path1”), #orientToPath:true ] )

on new( me, s )
ances­tor = script( “Tween­Plu­g­in” ).rawNew()
callAnces­tor( #new, me )
me.propName = #vec­tor­path
me.overwriteProps = [ #x, #y ]
_future = [:]
_RAD2DEG = 180 / PI
_startRot = 0
return me
end new

on dis­pose( me )
callAnces­tor( #dis­pose, me )
_future.deleteAll()
_future = VOID
_target = VOID
_segments.deleteAll()
_segments = VOID
end dispose

on onInit­Tween( me, atar­get, aval­ue, atween )
if( atarget.is( #SpriteWrap­per ) ) then
_targetType = #two
_startRot = atween.target.getRotationZ()
else if( atarget.is( #NodeWrap­per ) ) then
_targetType = #three
else
return FALSE
end if

if( ilk( aval­ue ) = #sprite ) then
if( avalue.member.type <> #vec­tor­Shape ) then return FALSE
vlist = avalue.member.vertexList.duplicate()
vlist.addAt( 1, me._screenToSprite( point( atarget.getX(), atarget.getY() ), avalue ) )

_offset = map( point( avalue.member.width *0.5, avalue.member.height *0.5 ), avalue.member.rect, avalue.rect )
repeat with i = 1 to count( vlist )
ele­ment = vlist[i]
if( voidP( element[#handle1] ) ) then
element.addProp( #handle1, point( 0, 0 ) )
end if
if( voidP( element[#handle2] ) ) then
element.addProp( #handle2, point( 0, 0 ) )
end if
p = map( element.vertex, avalue.member.rect, rect( 0, 0, avalue.rect.width, avalue.rect.height ) ) + _offset
h1 = p + map( element.handle1, avalue.member.rect, rect( 0, 0, avalue.rect.width, avalue.rect.height ) )
h2 = p + map( element.handle2, avalue.member.rect, rect( 0, 0, avalue.rect.width, avalue.rect.height ) )
element.vertex = p
element.handle1 = h1
element.handle2 = h2
end repeat

else if( ilk( aval­ue ) = #mem­ber ) then
if( avalue.type <> #vec­tor­Shape ) then return FALSE
vlist = avalue.vertexList.duplicate()
— get max­i­mum x and y point coordinates
maxX = -(the maxinteger)
maxY = -(the maxinteger)
repeat with v in vlist
maxX = max( maxX, v.vertex.locH )
maxY = max( maxY, v.vertex.locV )
end repeat

— Mod­i­fy the orig­i­nal ver­texlist, addind maxX and maxY
— and the dif­fer­ence between tar­get and the first point of the path
— to “trans­late” the coor­di­nates to the new origin.
inv = 1
if( _targetType = #three ) then
inv = ‑1
end if

diff = point( atarget.getX(), atarget.getY() * inv ) — ( point( vlist[1].vertex.locH + maxX, vlist[1].vertex.locV + maxY ))
off­setx = diff.locH
off­se­ty = diff.locV
repeat with i = 1 to count( vlist )
ele­ment = vlist[ i ]
if( voidP( element[#handle1] ) ) then
element.addProp( #handle1, point( 0, 0 ) )
end if
if( voidP( element[#handle2] ) ) then
element.addProp( #handle2, point( 0, 0 ) )
end if
element.vertex = point( element.vertex.locH + maxX, element.vertex.locV + maxY ) + point( off­setx, offsety )
element.handle1 = element.vertex + element.handle1
element.handle2 = element.vertex + element.handle2
end repeat
end if

if( ilk( vlist ) <> #list ) then return FALSE
me.init( atween, vlist, FALSE )
return TRUE
end onInitTween

on _screenToSprite( me, apoint, asprite )
p = apoint — point( asprite.left, asprite.top ) — point( asprite.member.width*0.5, asprite.member.height*0.5 )
return [#ver­tex: p, #handle1:point(0,0), #handle2:point(0,0) ]
end _screenToSprite

on init( me, tween, beziers )
_target = tween.target
_segments = me.parseVectorPath( beziers )
_count = count( _segments )
enu­mer­ables = tween.vars
if ( enu­mer­ables[ #ori­ent­ToPath ] = TRUE ) then
if( _target.is( #SpriteWrap­per ) ) then
_orientData = [ [ #x, #y, #rota­tionZ, 0, 0.01 ] ]
else if( _target.is( #NodeWrap­per ) ) then
_orientData = [ [ #x, #y, #z, 0, 0.1 ] ]
end if
_orient = TRUE
else if ( ilk( enu­mer­ables[ #ori­ent­ToPath ] ) = #list ) then
_orientData = enu­mer­ables[ #ori­ent­ToPath ]
_orient = TRUE
end if
end init

on change­Fac­tor( me, n )
if ( n < 0 ) then
seg­ment = 1
else if ( n >= 1 ) then
seg­ment = _count
else
seg­ment = max( 1, ceil( _count * n ) )
end if

innerRa­tio = ( n — ( ( seg­ment — 1 ) * ( 1.0 / _count ) ) ) * _count
sec­tion = _segments[ segment ]
r2 = innerRa­tio * innerRatio
r3 = r2 * innerRatio
p0 = section[1]
p1 = section[2]
p2 = section[3]
repeat with i = 1 to 2
prop = me.overwriteProps[i]
c = 3 * ( p1[prop] — p0[prop] )
b = 3 * ( p2[prop] — p1[prop]) — c
a = section[4][prop] — p0[prop] — c — b
xval­ue = a * r3 + b * r2 + c * innerRa­tio + p0[prop]
if ilk( me._target ) = #instance then
call( prop, me._target, xvalue )
else
me._target[ prop ] = xvalue
end if
end repeat

if (_orient) then
i = count( _orientData )
curVals = [:]
repeat while ( i > 0 )
cotb = _orientData[ i ] –cur­rent ori­ent­To­Bezi­er Array
j = 3 — Iter­ate through cot­b’s props — #x, #y, #z / #rota­tionZ
if( ilk( me._target ) = #pro­pList ) then
repeat while( j > 0 )
curVals[cotb[j]] = me._target[cotb[j]]
j = j — 1
end repeat
else
repeat while( j > 0 )
curVals[cotb[j]] = call( sym­bol( “get” & cotb[ j ] ), me._target )
j = j — 1
end repeat
end if
i = i — 1
end repeat
old­Tar­get = me._target
me._target = _future
_orient = false
i = count( _orientData )

repeat while ( i > 0 )
cotb = _orientData[ i ] –cur­rent ori­ent­To­Bezi­er Array
me.changeFactor( n + max(cotb[4], cotb[5]) )
if( _targetType = #two ) then
toAdd = max( cotb[4], 0 )
dx = _future[cotb[1]] — curVals[cotb[1]]
dy = _future[cotb[2]] — curVals[cotb[2]]
call( cotb[ 3 ], old­Tar­get, atan( dy, dx ) * _RAD2DEG + toAdd + _startRot )
else if( _targetType = #three ) then
dx = _future[ cotb[1] ]
dy = _future[ cotb[2] ]
dz = _future[ cotb[3] ]
oldTarget._target.pointAt( vec­tor( dx, dy, dz ), vec­tor( 0, 0, 1 ) )
end if
i = i ‑1
end repeat
me._target = oldTarget
_orient = true
end if
end changeFactor 

on par­se­Vec­tor­Path( me, v )
cnt = count( v ) — 1
t = []
inv = 1
if( _targetType = #three ) then
inv = ‑1
end if
repeat with i = 1 to cnt
section = []
a = v[ i ]
b = v[ i + 1 ]
p1 = a.vertex
p2 = b.vertex
h1 = a.handle1
h2 = b.handle2
section.append( [ #x:p1.locH, #y:p1.locV * inv ] ) — first point
section.append( [ #x:h1.locH, #y:h1.locV * inv ] ) — first handle
section.append( [ #x:h2.locH, #y:h2.locV * inv ] ) — sec­ond handle
section.append( [ #x:p2.locH, #y:p2.locV * inv ] ) — sec­ond point
t.append( section )
end repeat
return t
end parseVectorPath
[/lingo]

 
Comments

Watch Main Designed A Under­take Watch Func­tions Best. Cata­ri­na Rance Gilda