mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 16:17:57 -05:00
276 lines
10 KiB
CoffeeScript
276 lines
10 KiB
CoffeeScript
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
|
|
|
|
# Put this on MovieClips
|
|
specialGoToAndStop = (frame) ->
|
|
if frame is @currentFrame and @childrenCopy
|
|
@addChild(@childrenCopy...)
|
|
else
|
|
@gotoAndStop(frame)
|
|
@childrenCopy = @children.slice(0)
|
|
|
|
module.exports = class SegmentedSprite extends createjs.SpriteContainer
|
|
childMovieClips: null
|
|
|
|
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
|
|
@spriteSheet.mcPool ?= {}
|
|
super(@spriteSheet)
|
|
@addEventListener 'tick', @handleTick
|
|
|
|
destroy: ->
|
|
@handleTick = undefined
|
|
@baseMovieClip.inUse = false if @baseMovieClip
|
|
@removeAllEventListeners()
|
|
|
|
# CreateJS.Sprite-like interface
|
|
|
|
play: -> @paused = false unless @baseMovieClip and @animLength > 1
|
|
stop: -> @paused = true
|
|
gotoAndPlay: (actionName) -> @goto(actionName, false)
|
|
gotoAndStop: (actionName) -> @goto(actionName, true)
|
|
|
|
goto: (actionName, @paused=true) ->
|
|
@removeAllChildren()
|
|
@currentAnimation = actionName
|
|
@baseMovieClip.inUse = false if @baseMovieClip
|
|
if @childMovieClips
|
|
mc.inUse = false for mc in @childMovieClips
|
|
@childMovieClips = @baseMovieClip = @framerate = @animLength = null
|
|
@actionNotSupported = false
|
|
|
|
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}
|
|
|
|
if action.animation
|
|
@regX = -reg.x
|
|
@regY = -reg.y
|
|
@framerate = (action.framerate ? 20) * (action.speed ? 1)
|
|
@childMovieClips = []
|
|
@baseMovieClip = @buildMovieClip(action.animation)
|
|
@baseMovieClip.inUse = true
|
|
@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
|
|
|
|
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
|
|
|
|
@baseMovieClip.specialGoToAndStop(@currentFrame)
|
|
for movieClip in @childMovieClips
|
|
if movieClip.mode is 'single'
|
|
movieClip.specialGoToAndStop(movieClip.startPosition)
|
|
else
|
|
movieClip.specialGoToAndStop(@currentFrame)
|
|
|
|
@takeChildrenFromMovieClip(@baseMovieClip, @)
|
|
@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)
|
|
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)
|
|
|
|
else if action.goesTo
|
|
@goto(action.goesTo, @paused)
|
|
return
|
|
|
|
@scaleX *= -1 if action.flipX
|
|
@scaleY *= -1 if action.flipY
|
|
@baseScaleX = @scaleX
|
|
@baseScaleY = @scaleY
|
|
return
|
|
|
|
notifyActionNeedsRender: (action) ->
|
|
@lank?.trigger('action-needs-render', @lank, action)
|
|
|
|
buildMovieClip: (animationName, mode, startPosition, loops) ->
|
|
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
|
|
@childMovieClips = mc.childMovieClips
|
|
return mc
|
|
|
|
raw = @thangType.get('raw')
|
|
animData = raw.animations[animationName]
|
|
@lastAnimData = animData
|
|
|
|
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
|
|
|
|
anim = new createjs.MovieClip()
|
|
anim.initialize(mode ? createjs.MovieClip.INDEPENDENT, startPosition ? 0, loops ? true)
|
|
anim.specialGoToAndStop = specialGoToAndStop
|
|
|
|
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
|
|
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)
|
|
|
|
anim.childMovieClips = @childMovieClips
|
|
|
|
@spriteSheet.mcPool[key].push(anim)
|
|
return anim
|
|
|
|
buildMovieClipContainers: (localContainers) ->
|
|
map = {}
|
|
for localContainer in localContainers
|
|
outerContainer = new createjs.SpriteContainer(@spriteSheet)
|
|
innerContainer = new createjs.Sprite(@spriteSheet)
|
|
innerContainer.gotoAndStop(@spriteSheetPrefix + localContainer.gn)
|
|
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
|
|
return map
|
|
|
|
buildMovieClipAnimations: (localAnimations) ->
|
|
map = {}
|
|
for localAnimation in localAnimations
|
|
animation = @buildMovieClip(localAnimation.gn, localAnimation.a...)
|
|
animation.inUse = true
|
|
animation.setTransform(localAnimation.t...)
|
|
map[localAnimation.bn] = animation
|
|
@childMovieClips.push(animation)
|
|
return map
|
|
|
|
dereferenceArgs: (args, locals, toSkip) ->
|
|
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)
|
|
return res if res is false
|
|
else if _.isString(val) and toSkip[val]
|
|
return false
|
|
return args
|
|
|
|
handleTick: (e) =>
|
|
if @lastTimeStamp
|
|
@tick(e.timeStamp - @lastTimeStamp)
|
|
@lastTimeStamp = e.timeStamp
|
|
|
|
tick: (delta) ->
|
|
return if @paused or not @baseMovieClip
|
|
return @paused = true if @animLength is 1
|
|
newFrame = @currentFrame + @framerate * delta / 1000
|
|
|
|
if newFrame > @animLength
|
|
if @goesTo
|
|
@gotoAndPlay(@goesTo)
|
|
return
|
|
else if not @loop
|
|
@paused = true
|
|
newFrame = @animLength - 1
|
|
_.defer => @dispatchEvent('animationend')
|
|
else
|
|
newFrame = newFrame % @animLength
|
|
|
|
translatedFrame = newFrame
|
|
|
|
if @frames
|
|
prevFrame = Math.floor(newFrame)
|
|
nextFrame = Math.ceil(newFrame)
|
|
if prevFrame is nextFrame
|
|
translatedFrame = @frames[newFrame]
|
|
else if nextFrame is @frames.length
|
|
translatedFrame = @frames[prevFrame]
|
|
else
|
|
# interpolate between frames
|
|
pct = newFrame % 1
|
|
newFrameIndex = @frames[prevFrame] + (pct * (@frames[nextFrame] - @frames[prevFrame]))
|
|
translatedFrame = newFrameIndex
|
|
|
|
@currentFrame = newFrame
|
|
return if translatedFrame is @baseMovieClip.currentFrame
|
|
|
|
@baseMovieClip.specialGoToAndStop(translatedFrame)
|
|
for movieClip in @childMovieClips
|
|
movieClip.specialGoToAndStop(if movieClip.mode is 'single' then movieClip.startPosition else newFrame)
|
|
|
|
@children = []
|
|
@takeChildrenFromMovieClip(@baseMovieClip, @)
|
|
|
|
takeChildrenFromMovieClip: (movieClip, recipientContainer) ->
|
|
for child in movieClip.childrenCopy
|
|
if child instanceof createjs.MovieClip
|
|
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)
|
|
else
|
|
recipientContainer.addChild(child)
|
|
|
|
|
|
# _getBounds: createjs.SpriteContainer.prototype.getBounds
|
|
# getBounds: -> @baseMovieClip?.getBounds() or @children[0]?.getBounds() or @_getBounds()
|