2014-09-15 13:53:20 -07:00
SpriteBuilder = require ' lib/sprites/SpriteBuilder '
2014-10-02 16:54:20 -07:00
# Put this on MovieClips
specialGoToAndStop = (frame) ->
if frame is @ currentFrame and @ childrenCopy
@ addChild ( @ childrenCopy . . . )
else
@ gotoAndStop ( frame )
@childrenCopy = @ children . slice ( 0 )
2014-09-19 14:56:40 -07:00
module.exports = class SegmentedSprite extends createjs . SpriteContainer
2014-09-17 15:47:25 -07:00
childMovieClips: null
2014-09-19 14:56:40 -07:00
2014-09-17 16:49:31 -07:00
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
2014-09-25 14:48:17 -07:00
@ spriteSheet . mcPool ? = { }
2014-11-18 15:26:26 -08:00
super ( @ spriteSheet )
2014-09-17 15:47:25 -07:00
@ addEventListener ' tick ' , @ handleTick
2014-09-19 14:56:40 -07:00
2014-09-17 15:47:25 -07:00
destroy: ->
@handleTick = undefined
2014-09-25 14:48:17 -07:00
@baseMovieClip.inUse = false if @ baseMovieClip
2014-09-17 15:47:25 -07:00
@ removeAllEventListeners ( )
2014-09-16 15:36:59 -07:00
2014-09-19 14:56:40 -07:00
# CreateJS.Sprite-like interface
2014-09-25 10:47:53 -07:00
play: -> @paused = false unless @ baseMovieClip and @ animLength > 1
2014-09-19 14:56:40 -07:00
stop: -> @paused = true
2014-09-15 13:53:20 -07:00
gotoAndPlay: (actionName) -> @ goto ( actionName , false )
gotoAndStop: (actionName) -> @ goto ( actionName , true )
goto: (actionName, @paused=true) ->
2014-09-23 12:08:50 -07:00
@ removeAllChildren ( )
2014-09-15 13:53:20 -07:00
@currentAnimation = actionName
2014-09-25 14:48:17 -07:00
@baseMovieClip.inUse = false if @ baseMovieClip
if @ childMovieClips
mc.inUse = false for mc in @ childMovieClips
@childMovieClips = @baseMovieClip = @framerate = @animLength = null
2014-09-19 13:50:14 -07:00
@actionNotSupported = false
2014-09-19 14:56:40 -07:00
2014-09-15 13:53:20 -07:00
action = @ thangType . getActions ( ) [ actionName ]
2014-09-16 15:36:59 -07:00
randomStart = actionName . startsWith ( ' move ' )
2014-09-23 11:37:05 -07:00
# because the resulting segmented image is set to the size of the movie clip, you can use
# the raw registration data without scaling it.
2014-09-17 15:47:25 -07:00
reg = action . positions ? . registration or @ thangType . get ( ' positions ' ) ? . registration or { x : 0 , y : 0 }
2014-09-24 16:53:38 -07:00
2014-09-15 13:53:20 -07:00
if action . animation
2014-09-24 16:53:38 -07:00
@regX = - reg . x
@regY = - reg . y
2014-09-15 13:53:20 -07:00
@framerate = ( action . framerate ? 20 ) * ( action . speed ? 1 )
2014-09-19 14:56:40 -07:00
@childMovieClips = [ ]
@baseMovieClip = @ buildMovieClip ( action . animation )
2014-09-25 14:48:17 -07:00
@baseMovieClip.inUse = true
2014-09-19 14:56:40 -07:00
@frames = action . frames
@frames = ( parseInt ( f ) for f in @ frames . split ( ' , ' ) ) if @ frames
2014-09-25 10:47:53 -07:00
@animLength = if @ frames then @ frames . length else @ baseMovieClip . timeline . duration
@paused = true if @ animLength is 1
2014-09-30 13:34:55 -07:00
if @ frames
if randomStart
@currentFrame = @ frames [ _ . random ( @ frames . length - 1 ) ]
else
@currentFrame = @ frames [ 0 ]
else
if randomStart
@currentFrame = then Math . floor ( Math . random ( ) * @ animLength )
else
@currentFrame = 0
2014-10-02 16:54:20 -07:00
@ baseMovieClip . specialGoToAndStop ( @ currentFrame )
for movieClip in @ childMovieClips
if movieClip . mode is ' single '
movieClip . specialGoToAndStop ( movieClip . startPosition )
else
movieClip . specialGoToAndStop ( @ currentFrame )
@ takeChildrenFromMovieClip ( @ baseMovieClip , @ )
2014-09-19 14:56:40 -07:00
@loop = action . loops isnt false
@goesTo = action . goesTo
@ notifyActionNeedsRender ( action ) if @ actionNotSupported
2014-09-24 16:53:38 -07:00
@scaleX = @scaleY = action . scale ? @ thangType . get ( ' scale ' ) ? 1
2014-09-19 14:56:40 -07:00
else if action . container
2014-09-24 16:53:38 -07:00
# All transformations will be done to the child sprite
@regX = @regY = 0
@scaleX = @scaleY = 1
2014-09-19 14:56:40 -07:00
@childMovieClips = [ ]
containerName = @ spriteSheetPrefix + action . container
sprite = new createjs . Sprite ( @ spriteSheet )
sprite . gotoAndStop ( containerName )
2014-09-23 12:08:50 -07:00
if sprite . currentFrame is 0 or @ usePlaceholders
sprite . gotoAndStop ( 0 )
2014-09-19 14:56:40 -07:00
@ notifyActionNeedsRender ( action )
bounds = @ thangType . get ( ' raw ' ) . containers [ action . container ] . b
2014-09-24 16:53:38 -07:00
actionScale = ( action . scale ? @ thangType . get ( ' scale ' ) ? 1 )
sprite.scaleX = actionScale * bounds [ 2 ] / ( SPRITE_PLACEHOLDER_WIDTH * @ resolutionFactor )
sprite.scaleY = actionScale * bounds [ 3 ] / ( SPRITE_PLACEHOLDER_WIDTH * @ resolutionFactor )
sprite.regX = ( SPRITE_PLACEHOLDER_WIDTH * @ resolutionFactor ) * ( ( - reg . x - bounds [ 0 ] ) / bounds [ 2 ] )
sprite.regY = ( SPRITE_PLACEHOLDER_WIDTH * @ resolutionFactor ) * ( ( - reg . y - bounds [ 1 ] ) / bounds [ 3 ] )
2014-09-17 15:47:25 -07:00
else
2014-09-24 16:53:38 -07:00
scale = @ resolutionFactor * ( action . scale ? @ thangType . get ( ' scale ' ) ? 1 )
sprite.regX = - reg . x * scale
sprite.regY = - reg . y * scale
sprite.scaleX = sprite.scaleY = 1 / @ resolutionFactor
2014-09-23 12:08:50 -07:00
@children = [ ]
@ addChild ( sprite )
2014-10-02 10:42:21 -07:00
else if action . goesTo
@ goto ( action . goesTo , @ paused )
return
2014-09-24 16:53:38 -07:00
@ scaleX *= - 1 if action . flipX
@ scaleY *= - 1 if action . flipY
@baseScaleX = @ scaleX
@baseScaleY = @ scaleY
2014-09-15 13:53:20 -07:00
return
2014-09-23 11:37:05 -07:00
2014-09-18 12:19:52 -07:00
notifyActionNeedsRender: (action) ->
2014-10-02 10:42:21 -07:00
@ lank ? . trigger ( ' action-needs-render ' , @ lank , action )
2014-09-19 14:56:40 -07:00
2014-09-15 13:53:20 -07:00
buildMovieClip: (animationName, mode, startPosition, loops) ->
2014-09-25 14:48:17 -07:00
key = JSON . stringify ( [ @ spriteSheetPrefix ] . concat ( arguments ) )
@ spriteSheet . mcPool [ key ] ? = [ ]
for mc in @ spriteSheet . mcPool [ key ]
if not mc . inUse
mc . gotoAndStop ( mc . currentFrame + 0.01 ) # just to make sure it has its children back
2014-09-28 10:33:24 -07:00
@childMovieClips = mc . childMovieClips
2014-09-25 14:48:17 -07:00
return mc
2014-09-15 13:53:20 -07:00
raw = @ thangType . get ( ' raw ' )
animData = raw . animations [ animationName ]
2014-09-25 10:47:53 -07:00
@lastAnimData = animData
2014-09-15 13:53:20 -07:00
locals = { }
2014-09-19 13:50:14 -07:00
_ . extend locals , @ buildMovieClipContainers ( animData . containers )
_ . extend locals , @ buildMovieClipAnimations ( animData . animations )
2014-09-19 14:56:40 -07:00
2014-09-16 15:36:59 -07:00
toSkip = { }
2014-09-17 15:47:25 -07:00
toSkip [ shape . bn ] = true for shape in animData . shapes
toSkip [ graphic . bn ] = true for graphic in animData . graphics
2014-09-15 13:53:20 -07:00
anim = new createjs . MovieClip ( )
anim . initialize ( mode ? createjs . MovieClip . INDEPENDENT , startPosition ? 0 , loops ? true )
2014-10-02 16:54:20 -07:00
anim.specialGoToAndStop = specialGoToAndStop
2014-09-15 13:53:20 -07:00
for tweenData in animData . tweens
stopped = false
tween = createjs . Tween
for func in tweenData
args = $ . extend ( true , [ ] , ( func . a ) )
2014-09-16 15:36:59 -07:00
if @ dereferenceArgs ( args , locals , toSkip ) is false
2014-09-25 10:47:53 -07:00
# console.debug 'Did not dereference args:', args
2014-09-15 13:53:20 -07:00
stopped = true
break
tween = tween [ func . n ] ( args . . . )
continue if stopped
anim . timeline . addTween ( tween )
anim.nominalBounds = new createjs . Rectangle ( animData . bounds . . . )
if animData . frameBounds
anim.frameBounds = ( new createjs . Rectangle ( bounds . . . ) for bounds in animData . frameBounds )
2014-10-03 09:36:47 -07:00
anim.childMovieClips = @ childMovieClips
2014-09-25 14:48:17 -07:00
@ spriteSheet . mcPool [ key ] . push ( anim )
2014-09-15 13:53:20 -07:00
return anim
buildMovieClipContainers: (localContainers) ->
map = { }
for localContainer in localContainers
2014-09-17 16:49:31 -07:00
outerContainer = new createjs . SpriteContainer ( @ spriteSheet )
innerContainer = new createjs . Sprite ( @ spriteSheet )
2014-09-19 13:50:14 -07:00
innerContainer . gotoAndStop ( @ spriteSheetPrefix + localContainer . gn )
2014-09-23 11:37:05 -07:00
if innerContainer . currentFrame is 0 or @ usePlaceholders
innerContainer . gotoAndStop ( 0 )
2014-09-19 13:50:14 -07:00
@actionNotSupported = true
bounds = @ thangType . get ( ' raw ' ) . containers [ localContainer . gn ] . b
innerContainer.x = bounds [ 0 ]
innerContainer.y = bounds [ 1 ]
2014-09-24 10:55:33 -07:00
innerContainer.scaleX = bounds [ 2 ] / ( SPRITE_PLACEHOLDER_WIDTH * @ resolutionFactor )
innerContainer.scaleY = bounds [ 3 ] / ( SPRITE_PLACEHOLDER_WIDTH * @ resolutionFactor )
2014-09-19 13:50:14 -07:00
else
innerContainer.scaleX = innerContainer.scaleY = 1 / ( @ resolutionFactor * ( @ thangType . get ( ' scale ' ) or 1 ) )
2014-09-17 16:49:31 -07:00
outerContainer . addChild ( innerContainer )
outerContainer . setTransform ( localContainer . t . . . )
outerContainer._off = localContainer . o if localContainer . o ?
outerContainer.alpha = localContainer . al if localContainer . al ?
map [ localContainer . bn ] = outerContainer
2014-09-15 13:53:20 -07:00
return map
buildMovieClipAnimations: (localAnimations) ->
map = { }
for localAnimation in localAnimations
animation = @ buildMovieClip ( localAnimation . gn , localAnimation . a . . . )
2014-09-25 14:48:17 -07:00
animation.inUse = true
2014-09-15 13:53:20 -07:00
animation . setTransform ( localAnimation . t . . . )
map [ localAnimation . bn ] = animation
2014-09-17 15:47:25 -07:00
@ childMovieClips . push ( animation )
2014-09-15 13:53:20 -07:00
return map
2014-09-16 15:36:59 -07:00
dereferenceArgs: (args, locals, toSkip) ->
2014-09-15 13:53:20 -07:00
for key , val of args
if locals [ val ]
args [ key ] = locals [ val ]
else if val is null
args [ key ] = { }
else if _ . isString ( val ) and val . indexOf ( ' createjs. ' ) is 0
args [ key ] = eval ( val ) # TODO: Security risk
else if _ . isObject ( val ) or _ . isArray ( val )
2014-09-16 15:36:59 -07:00
res = @ dereferenceArgs ( val , locals , toSkip )
2014-09-15 13:53:20 -07:00
return res if res is false
2014-09-16 15:36:59 -07:00
else if _ . isString ( val ) and toSkip [ val ]
2014-09-15 13:53:20 -07:00
return false
return args
2014-09-19 14:56:40 -07:00
handleTick: (e) =>
if @ lastTimeStamp
@ tick ( e . timeStamp - @ lastTimeStamp )
@lastTimeStamp = e . timeStamp
2014-09-15 13:53:20 -07:00
tick: (delta) ->
2014-09-23 12:08:50 -07:00
return if @ paused or not @ baseMovieClip
2014-09-25 12:43:51 -07:00
return @paused = true if @ animLength is 1
2014-09-15 13:53:20 -07:00
newFrame = @ currentFrame + @ framerate * delta / 1000
2014-09-19 14:56:40 -07:00
2014-09-15 13:53:20 -07:00
if newFrame > @ animLength
if @ goesTo
@ gotoAndPlay ( @ goesTo )
return
else if not @ loop
2014-09-17 15:47:25 -07:00
@paused = true
2014-09-15 13:53:20 -07:00
newFrame = @ animLength - 1
2014-10-02 10:42:21 -07:00
_ . defer => @ dispatchEvent ( ' animationend ' )
2014-09-15 13:53:20 -07:00
else
newFrame = newFrame % @ animLength
2014-09-19 14:56:40 -07:00
2014-09-25 13:26:05 -07:00
translatedFrame = newFrame
2014-09-25 12:43:51 -07:00
2014-09-15 13:53:20 -07:00
if @ frames
prevFrame = Math . floor ( newFrame )
nextFrame = Math . ceil ( newFrame )
if prevFrame is nextFrame
2014-09-25 13:26:05 -07:00
translatedFrame = @ frames [ newFrame ]
2014-09-15 13:53:20 -07:00
else if nextFrame is @ frames . length
2014-09-25 13:26:05 -07:00
translatedFrame = @ frames [ prevFrame ]
2014-09-15 13:53:20 -07:00
else
# interpolate between frames
pct = newFrame % 1
newFrameIndex = @ frames [ prevFrame ] + ( pct * ( @ frames [ nextFrame ] - @ frames [ prevFrame ] ) )
2014-09-25 13:26:05 -07:00
translatedFrame = newFrameIndex
2014-09-19 14:56:40 -07:00
2014-09-15 13:53:20 -07:00
@currentFrame = newFrame
2014-09-25 13:26:05 -07:00
return if translatedFrame is @ baseMovieClip . currentFrame
2014-09-15 13:53:20 -07:00
2014-10-02 16:54:20 -07:00
@ baseMovieClip . specialGoToAndStop ( translatedFrame )
for movieClip in @ childMovieClips
movieClip . specialGoToAndStop ( if movieClip . mode is ' single ' then movieClip . startPosition else newFrame )
2014-09-25 13:26:05 -07:00
@children = [ ]
2014-10-02 16:54:20 -07:00
@ takeChildrenFromMovieClip ( @ baseMovieClip , @ )
2014-09-28 10:33:24 -07:00
2014-10-02 16:54:20 -07:00
takeChildrenFromMovieClip: (movieClip, recipientContainer) ->
for child in movieClip . childrenCopy
2014-09-24 16:53:38 -07:00
if child instanceof createjs . MovieClip
2014-10-02 16:54:20 -07:00
childRecipient = new createjs . SpriteContainer ( @ spriteSheet )
@ takeChildrenFromMovieClip ( child , childRecipient )
for prop in [ ' regX ' , ' regY ' , ' rotation ' , ' scaleX ' , ' scaleY ' , ' skewX ' , ' skewY ' , ' x ' , ' y ' ]
childRecipient [ prop ] = child [ prop ]
recipientContainer . addChild ( childRecipient )
2014-09-24 16:53:38 -07:00
else
2014-10-02 16:54:20 -07:00
recipientContainer . addChild ( child )
2014-09-24 16:53:38 -07:00
2014-09-19 14:56:40 -07:00
2014-09-25 10:47:53 -07:00
# _getBounds: createjs.SpriteContainer.prototype.getBounds
# getBounds: -> @baseMovieClip?.getBounds() or @children[0]?.getBounds() or @_getBounds()