codecombat/app/lib/surface/SegmentedSprite.coffee

246 lines
9.2 KiB
CoffeeScript
Raw Normal View History

2014-09-15 16:53:20 -04:00
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
module.exports = class SegmentedSprite extends createjs.SpriteContainer
childMovieClips: null
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
2014-09-15 16:53:20 -04:00
@initialize(@spriteSheet)
@addEventListener 'tick', @handleTick
destroy: ->
@handleTick = undefined
@removeAllEventListeners()
# CreateJS.Sprite-like interface
play: -> @paused = false unless @baseMovieClip and @animLength > 1
stop: -> @paused = true
2014-09-15 16:53:20 -04:00
gotoAndPlay: (actionName) -> @goto(actionName, false)
gotoAndStop: (actionName) -> @goto(actionName, true)
goto: (actionName, @paused=true) ->
@removeAllChildren()
2014-09-15 16:53:20 -04:00
@currentAnimation = actionName
@baseMovieClip = @framerate = @animLength = null
@actionNotSupported = false
2014-09-15 16:53:20 -04:00
action = @thangType.getActions()[actionName]
randomStart = actionName.startsWith('move')
# because the resulting segmented image is set to the size of the movie clip, you can use
# the raw registration data without scaling it.
reg = action.positions?.registration or @thangType.get('positions')?.registration or {x:0, y:0}
2014-09-15 16:53:20 -04:00
if action.animation
@regX = -reg.x
@regY = -reg.y
2014-09-15 16:53:20 -04:00
@framerate = (action.framerate ? 20) * (action.speed ? 1)
@childMovieClips = []
@baseMovieClip = @buildMovieClip(action.animation)
@frames = action.frames
@frames = (parseInt(f) for f in @frames.split(',')) if @frames
@animLength = if @frames then @frames.length else @baseMovieClip.timeline.duration
@paused = true if @animLength is 1
@currentFrame = if randomStart then Math.floor(Math.random() * @animLength) else 0
@baseMovieClip.gotoAndStop(@currentFrame)
movieClip.gotoAndStop(@currentFrame) for movieClip in @childMovieClips
@takeChildrenFromMovieClip()
@loop = action.loops isnt false
@goesTo = action.goesTo
@notifyActionNeedsRender(action) if @actionNotSupported
@scaleX = @scaleY = action.scale ? @thangType.get('scale') ? 1
else if action.container
# All transformations will be done to the child sprite
@regX = @regY = 0
@scaleX = @scaleY = 1
@childMovieClips = []
containerName = @spriteSheetPrefix + action.container
sprite = new createjs.Sprite(@spriteSheet)
sprite.gotoAndStop(containerName)
@listenToEventsFor(sprite)
if sprite.currentFrame is 0 or @usePlaceholders
sprite.gotoAndStop(0)
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').containers[action.container].b
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])
else
scale = @resolutionFactor * (action.scale ? @thangType.get('scale') ? 1)
sprite.regX = -reg.x * scale
sprite.regY = -reg.y * scale
sprite.scaleX = sprite.scaleY = 1 / @resolutionFactor
@children = []
@addChild(sprite)
@scaleX *= -1 if action.flipX
@scaleY *= -1 if action.flipY
@baseScaleX = @scaleX
@baseScaleY = @scaleY
2014-09-15 16:53:20 -04:00
return
notifyActionNeedsRender: (action) ->
@sprite?.trigger('action-needs-render', @sprite, action)
2014-09-15 16:53:20 -04:00
buildMovieClip: (animationName, mode, startPosition, loops) ->
raw = @thangType.get('raw')
animData = raw.animations[animationName]
@lastAnimData = animData
2014-09-15 16:53:20 -04:00
movieClip = new createjs.MovieClip()
locals = {}
_.extend locals, @buildMovieClipContainers(animData.containers)
_.extend locals, @buildMovieClipAnimations(animData.animations)
toSkip = {}
toSkip[shape.bn] = true for shape in animData.shapes
toSkip[graphic.bn] = true for graphic in animData.graphics
2014-09-15 16:53:20 -04:00
anim = new createjs.MovieClip()
anim.initialize(mode ? createjs.MovieClip.INDEPENDENT, startPosition ? 0, loops ? true)
for tweenData in animData.tweens
stopped = false
tween = createjs.Tween
for func in tweenData
args = $.extend(true, [], (func.a))
if @dereferenceArgs(args, locals, toSkip) is false
# console.debug 'Did not dereference args:', args
2014-09-15 16:53:20 -04: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)
return anim
buildMovieClipContainers: (localContainers) ->
map = {}
for localContainer in localContainers
outerContainer = new createjs.SpriteContainer(@spriteSheet)
innerContainer = new createjs.Sprite(@spriteSheet)
innerContainer.gotoAndStop(@spriteSheetPrefix + localContainer.gn)
@listenToEventsFor(innerContainer)
if innerContainer.currentFrame is 0 or @usePlaceholders
innerContainer.gotoAndStop(0)
@actionNotSupported = true
bounds = @thangType.get('raw').containers[localContainer.gn].b
innerContainer.x = bounds[0]
innerContainer.y = bounds[1]
innerContainer.scaleX = bounds[2] / (SPRITE_PLACEHOLDER_WIDTH * @resolutionFactor)
innerContainer.scaleY = bounds[3] / (SPRITE_PLACEHOLDER_WIDTH * @resolutionFactor)
else
innerContainer.scaleX = innerContainer.scaleY = 1 / (@resolutionFactor * (@thangType.get('scale') or 1))
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 16:53:20 -04:00
return map
listenToEventsFor: (displayObject) ->
for event in ['mousedown', 'click', 'dblclick', 'pressmove', 'pressup']
displayObject.on event, @onMouseEvent, @
onMouseEvent: (e) -> @dispatchEvent(e)
2014-09-15 16:53:20 -04:00
buildMovieClipAnimations: (localAnimations) ->
map = {}
for localAnimation in localAnimations
animation = @buildMovieClip(localAnimation.gn, localAnimation.a...)
animation.setTransform(localAnimation.t...)
map[localAnimation.bn] = animation
@childMovieClips.push(animation)
2014-09-15 16:53:20 -04:00
return map
dereferenceArgs: (args, locals, toSkip) ->
2014-09-15 16:53:20 -04: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)
res = @dereferenceArgs(val, locals, toSkip)
2014-09-15 16:53:20 -04:00
return res if res is false
else if _.isString(val) and toSkip[val]
2014-09-15 16:53:20 -04:00
return false
return args
handleTick: (e) =>
if @lastTimeStamp
@tick(e.timeStamp - @lastTimeStamp)
@lastTimeStamp = e.timeStamp
2014-09-15 16:53:20 -04:00
tick: (delta) ->
return if @paused or not @baseMovieClip
return @paused = true if @animLength is 1
2014-09-15 16:53:20 -04:00
newFrame = @currentFrame + @framerate * delta / 1000
2014-09-15 16:53:20 -04:00
if newFrame > @animLength
if @goesTo
@gotoAndPlay(@goesTo)
return
else if not @loop
@paused = true
2014-09-15 16:53:20 -04:00
newFrame = @animLength - 1
@dispatchEvent('animationend')
else
newFrame = newFrame % @animLength
return if @currentFrame is newFrame
2014-09-15 16:53:20 -04:00
if @frames
prevFrame = Math.floor(newFrame)
nextFrame = Math.ceil(newFrame)
if prevFrame is nextFrame
@baseMovieClip.gotoAndStop(@frames[newFrame])
else if nextFrame is @frames.length
@baseMovieClip.gotoAndStop(@frames[prevFrame])
else
# interpolate between frames
pct = newFrame % 1
newFrameIndex = @frames[prevFrame] + (pct * (@frames[nextFrame] - @frames[prevFrame]))
@baseMovieClip.gotoAndStop(newFrameIndex)
else
@baseMovieClip.gotoAndStop(newFrame)
2014-09-15 16:53:20 -04:00
@currentFrame = newFrame
@children = []
2014-09-15 16:53:20 -04:00
movieClip.gotoAndStop(newFrame) for movieClip in @childMovieClips
@takeChildrenFromMovieClip()
takeChildrenFromMovieClip: ->
i = 0
while i < @baseMovieClip.children.length
child = @baseMovieClip.children[i]
if child instanceof createjs.MovieClip
newChild = new createjs.SpriteContainer(@spriteSheet)
j = 0
while j < child.children.length
grandChild = child.children[j]
if grandChild instanceof createjs.MovieClip
console.error('MovieClip Segmentedsprites not currently working at this depth!')
continue
newChild.addChild(grandChild)
for prop in ['regX', 'regY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY', 'x', 'y']
newChild[prop] = child[prop]
@addChild(newChild)
@alreadyLogged = true
i += 1
else
@addChild(child)
# _getBounds: createjs.SpriteContainer.prototype.getBounds
# getBounds: -> @baseMovieClip?.getBounds() or @children[0]?.getBounds() or @_getBounds()