Integrated WebGLLayer and WebGLSprite with CocoSprite.

Disabled some features in CocoSprite that'll have to be added back later.
Moved more logic from CocoSprite, like determining registration points for a given animation, to WebGLSprite.
WebGLSprite can now display either rendering containers or SpriteSheets.
This commit is contained in:
Scott Erickson 2014-09-16 15:36:59 -07:00
parent 9343d1e645
commit 1ef3d150a8
6 changed files with 377 additions and 229 deletions

View file

@ -28,7 +28,6 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
groundLayer: null
textLayer: null
floatingLayer: null
frameRateFactor: 1 # TODO: use or lose?
thang: null
camera: null
spriteSheetCache: null
@ -79,110 +78,60 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@handledDisplayEvents = {}
@age = 0
@stillLoading = true
# if @thangType.isFullyLoaded()
# @setUpSprite()
# else
# @thangType.setProjection null
# @thangType.fetch() unless @thangType.loading
# @listenToOnce(@thangType, 'sync', @setUpSprite)
# @setUpPlaceholder()
if @thangType.isFullyLoaded() then @onThangTypeLoaded() else @listenToOnce(@thangType, 'sync', @onThangTypeLoaded)
setUpSprite: ->
toString: -> "<CocoSprite: #{@thang?.id}>"
onThangTypeLoaded: ->
@stillLoading = false
for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
AudioPlayer.preloadSoundReference sound for sound in sounds
if @thangType.get('raster')
@stillLoading = false
@actions = {}
@isRaster = true
@setUpRasterImage()
else
result = @buildSpriteSheet()
if _.isString result # async build
@listenToOnce @thangType, 'build-complete', @setUpSprite
@setUpPlaceholder()
else
@stillLoading = false
@actions = @thangType.getActions()
@buildFromSpriteSheet result
@createMarks()
@queueAction 'idle'
@actions = @thangType.getActions()
@createMarks()
@queueAction 'idle'
finishSetup: ->
@placeholder = null
@updateBaseScale()
@scaleFactorX = @thang.scaleFactorX if @thang?.scaleFactorX?
@scaleFactorX = @thang.scaleFactor if @thang?.scaleFactor?
@scaleFactorY = @thang.scaleFactorY if @thang?.scaleFactorY?
@scaleFactorY = @thang.scaleFactor if @thang?.scaleFactor?
@update true # Reflect initial scale and other state
setUpRasterImage: ->
raster = @thangType.get('raster')
image = new createjs.Bitmap('/file/'+raster)
@setImageObject image
$(image.image).one 'load', => @updateScale?()
@configureMouse()
@imageObject.sprite = @
@imageObject.layerPriority = @thang?.layerPriority ? @thangType.get 'layerPriority'
@imageObject.name = @thang?.spriteName or @thangType.get 'name'
reg = @getOffset 'registration'
@imageObject.regX = -reg.x
@imageObject.regY = -reg.y
@finishSetup()
setUpPlaceholder: ->
return if @placeholder or not @thang
shape = new createjs.Shape()
width = @thang.width * Camera.PPM
height = @thang.height * Camera.PPM * @options.camera.y2x
depth = @thang.depth * Camera.PPM * @options.camera.z2y * @options.camera.y2x
brightnessFuzzFactor = 1 + 0.1 * (Math.random() - 0.5)
makeColor = (brightnessFactor) => (Math.round(c * brightnessFuzzFactor * brightnessFactor) for c in (healthColors[@thang.team] ? [180, 180, 180]))
topColor = "rgba(#{makeColor(0.85).join(', ')},1)"
mainColor = "rgba(#{makeColor(0.75).join(', ')},1)"
ellipse = @thang.shape in ['ellipsoid', 'disc']
fn = if ellipse then 'drawEllipse' else 'drawRect'
shape.graphics.beginFill(mainColor)[fn](-width / 2, -height / 2, width, height).endFill()
if ellipse
shape.graphics.moveTo(-width / 2, 0).beginFill(mainColor).lineTo(-width / 2, -depth).lineTo(width / 2, -depth).lineTo(width / 2, 0).lineTo(-width / 2, 0).endFill()
else
shape.graphics.moveTo(-width / 2, 0).beginFill(mainColor).lineTo(-width / 2, -depth).lineTo(width / 2, -depth).lineTo(width / 2, 0).lineTo(-width / 2, 0).endFill()
shape.graphics.beginFill(topColor)[fn](-width / 2, -height / 2 - depth, width, height).endFill()
shape.layerPriority = @thang?.layerPriority ? @thangType.get 'layerPriority'
@setImageObject shape
@updatePosition true
@placeholder = shape
toString: -> "<CocoSprite: #{@thang?.id}>"
buildSpriteSheet: ->
options = _.extend @options, @thang?.getSpriteOptions?() ? {}
options.colorConfig = @options.colorConfig if @options.colorConfig
options.async = @options.async
@thangType.getSpriteSheet options
setImageObject: (newImageObject) ->
if parent = @imageObject?.parent
parent.removeChild @imageObject
parent.addChild newImageObject
parent.updateLayerOrder()
if @imageObject
@imageObject.removeAllEventListeners()
if parent = @imageObject.parent
parent.removeChild @imageObject
parent.addChild newImageObject
parent.updateLayerOrder()
@imageObject = newImageObject
buildFromSpriteSheet: (spriteSheet) ->
if spriteSheet
sprite = new createjs.Sprite(spriteSheet)
else
sprite = new createjs.Shape()
@setImageObject sprite
@addHealthBar()
@configureMouse()
# TODO: generalize this later?
@imageObject.sprite = @
@imageObject.layerPriority = @thang?.layerPriority ? @thangType.get 'layerPriority'
@imageObject.name = @thang?.spriteName or @thangType.get 'name'
@imageObject.on 'animationend', @playNextAction
@finishSetup()
# TODO: figure out how to do placeholders again
# setUpPlaceholder: ->
# return if @placeholder or not @thang
# shape = new createjs.Shape()
# width = @thang.width * Camera.PPM
# height = @thang.height * Camera.PPM * @options.camera.y2x
# depth = @thang.depth * Camera.PPM * @options.camera.z2y * @options.camera.y2x
# brightnessFuzzFactor = 1 + 0.1 * (Math.random() - 0.5)
# makeColor = (brightnessFactor) => (Math.round(c * brightnessFuzzFactor * brightnessFactor) for c in (healthColors[@thang.team] ? [180, 180, 180]))
# topColor = "rgba(#{makeColor(0.85).join(', ')},1)"
# mainColor = "rgba(#{makeColor(0.75).join(', ')},1)"
# ellipse = @thang.shape in ['ellipsoid', 'disc']
# fn = if ellipse then 'drawEllipse' else 'drawRect'
# shape.graphics.beginFill(mainColor)[fn](-width / 2, -height / 2, width, height).endFill()
# shape.graphics.moveTo(-width / 2, 0).beginFill(mainColor).lineTo(-width / 2, -depth).lineTo(width / 2, -depth).lineTo(width / 2, 0).lineTo(-width / 2, 0).endFill()
# shape.graphics.beginFill(topColor)[fn](-width / 2, -height / 2 - depth, width, height).endFill()
# shape.layerPriority = @thang?.layerPriority ? @thangType.get 'layerPriority'
# @setImageObject shape
# @updatePosition true
# @placeholder = shape
##################################################
# QUEUEING AND PLAYING ACTIONS
@ -214,14 +163,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@updateBaseScale()
return @updateActionDirection() unless action.animation or action.container
m = if action.container then 'gotoAndStop' else 'gotoAndPlay'
@imageObject.framerate = action.framerate or 20
@imageObject[m] action.name
reg = @getOffset 'registration'
@imageObject.regX = -reg.x
@imageObject.regY = -reg.y
if @currentRootAction.name is 'move' and action.frames
start = Math.floor(Math.random() * action.frames.length)
@imageObject.currentAnimationFrame = start
@imageObject[m]?(action.name)
hide: ->
@hiding = true
@ -261,66 +203,69 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@updateLabels()
showAreaOfEffects: ->
return unless @thang?.currentEvents
for event in @thang.currentEvents
continue unless event.startsWith 'aoe-'
continue if @handledDisplayEvents[event]
@handledDisplayEvents[event] = true
args = JSON.parse(event[4...])
pos = @options.camera.worldToSurface {x: args[0], y: args[1]}
circle = new createjs.Shape()
radius = args[2] * Camera.PPM
if args.length is 4
circle.graphics.beginFill(args[3]).drawCircle(0, 0, radius)
else
startAngle = args[4]
endAngle = args[5]
circle.graphics.beginFill(args[3])
.lineTo(0, 0)
.lineTo(radius * Math.cos(startAngle), radius * Math.sin(startAngle))
.arc(0, 0, radius, startAngle, endAngle)
.lineTo(0, 0)
circle.x = pos.x
circle.y = pos.y
circle.scaleY = @options.camera.y2x * 0.7
circle.scaleX = 0.7
circle.alpha = 0.2
circle
@options.groundLayer.addChild circle
createjs.Tween.get(circle)
.to({alpha: 0.6, scaleY: @options.camera.y2x, scaleX: 1}, 100, createjs.Ease.circOut)
.to({alpha: 0, scaleY: 0, scaleX: 0}, 700, createjs.Ease.circIn)
.call =>
return if @destroyed
@options.groundLayer.removeChild circle
delete @handledDisplayEvents[event]
# TODO: add back area of effects
# return unless @thang?.currentEvents
# for event in @thang.currentEvents
# continue unless event.startsWith 'aoe-'
# continue if @handledDisplayEvents[event]
#
# @handledDisplayEvents[event] = true
# args = JSON.parse(event[4...])
# pos = @options.camera.worldToSurface {x: args[0], y: args[1]}
# circle = new createjs.Shape()
# radius = args[2] * Camera.PPM
# if args.length is 4
# circle.graphics.beginFill(args[3]).drawCircle(0, 0, radius)
# else
# startAngle = args[4]
# endAngle = args[5]
# circle.graphics.beginFill(args[3])
# .lineTo(0, 0)
# .lineTo(radius * Math.cos(startAngle), radius * Math.sin(startAngle))
# .arc(0, 0, radius, startAngle, endAngle)
# .lineTo(0, 0)
# circle.x = pos.x
# circle.y = pos.y
# circle.scaleY = @options.camera.y2x * 0.7
# circle.scaleX = 0.7
# circle.alpha = 0.2
# circle
# @options.groundLayer.addChild circle
# createjs.Tween.get(circle)
# .to({alpha: 0.6, scaleY: @options.camera.y2x, scaleX: 1}, 100, createjs.Ease.circOut)
# .to({alpha: 0, scaleY: 0, scaleX: 0}, 700, createjs.Ease.circIn)
# .call =>
# return if @destroyed
# @options.groundLayer.removeChild circle
# delete @handledDisplayEvents[event]
showTextEvents: ->
return unless @thang?.currentEvents
for event in @thang.currentEvents
continue unless event.startsWith 'text-'
continue if @handledDisplayEvents[event]
@handledDisplayEvents[event] = true
options = JSON.parse(event[5...])
label = new createjs.Text options.text, "bold #{options.size or 16}px Arial", options.color or '#FFF'
shadowColor = {humans: '#F00', ogres: '#00F', neutral: '#0F0', common: '#0F0'}[@thang.team] ? '#000'
label.shadow = new createjs.Shadow shadowColor, 1, 1, 3
offset = @getOffset 'aboveHead'
[label.x, label.y] = [@imageObject.x + offset.x - label.getMeasuredWidth() / 2, @imageObject.y + offset.y]
@options.floatingLayer.addChild label
window.labels ?= []
window.labels.push label
label.alpha = 0
createjs.Tween.get(label)
.to({y: label.y-2, alpha: 1}, 200, createjs.Ease.linear)
.to({y: label.y-12}, 1000, createjs.Ease.linear)
.to({y: label.y-22, alpha: 0}, 1000, createjs.Ease.linear)
.call =>
return if @destroyed
@options.floatingLayer.removeChild label
# TODO: Add back text events
# return unless @thang?.currentEvents
# for event in @thang.currentEvents
# continue unless event.startsWith 'text-'
# continue if @handledDisplayEvents[event]
# @handledDisplayEvents[event] = true
# options = JSON.parse(event[5...])
# label = new createjs.Text options.text, "bold #{options.size or 16}px Arial", options.color or '#FFF'
# shadowColor = {humans: '#F00', ogres: '#00F', neutral: '#0F0', common: '#0F0'}[@thang.team] ? '#000'
# label.shadow = new createjs.Shadow shadowColor, 1, 1, 3
# offset = @getOffset 'aboveHead'
# [label.x, label.y] = [@imageObject.x + offset.x - label.getMeasuredWidth() / 2, @imageObject.y + offset.y]
# @options.floatingLayer.addChild label
# window.labels ?= []
# window.labels.push label
# label.alpha = 0
# createjs.Tween.get(label)
# .to({y: label.y-2, alpha: 1}, 200, createjs.Ease.linear)
# .to({y: label.y-12}, 1000, createjs.Ease.linear)
# .to({y: label.y-22, alpha: 0}, 1000, createjs.Ease.linear)
# .call =>
# return if @destroyed
# @options.floatingLayer.removeChild label
cache: ->
return # TODO: get rid of caching
bounds = @imageObject.getBounds()
@imageObject.cache 0, 0, bounds.width, bounds.height
#console.log 'just cached', @thang.id, 'which was at', @imageObject.x, @imageObject.y, bounds.width, bounds.height, 'with scale', Math.max(@imageObject.scaleX, @imageObject.scaleY)
@ -554,14 +499,15 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
Backbone.Mediator.publish ourEventName, newEvent
addHealthBar: ->
return unless @thang?.health? and 'health' in (@thang?.hudProperties ? []) and @options.floatingLayer
healthColor = healthColors[@thang?.team] ? healthColors['neutral']
healthOffset = @getOffset 'aboveHead'
bar = @healthBar = createProgressBar(healthColor, healthOffset)
bar.name = 'health bar'
bar.cache 0, -bar.height * bar.baseScale / 2, bar.width * bar.baseScale, bar.height * bar.baseScale
@options.floatingLayer.addChild bar
@updateHealthBar()
# TODO: Put back in health bars
# return unless @thang?.health? and 'health' in (@thang?.hudProperties ? []) and @options.floatingLayer
# healthColor = healthColors[@thang?.team] ? healthColors['neutral']
# healthOffset = @getOffset 'aboveHead'
# bar = @healthBar = createProgressBar(healthColor, healthOffset)
# bar.name = 'health bar'
# bar.cache 0, -bar.height * bar.baseScale / 2, bar.width * bar.baseScale, bar.height * bar.baseScale
# @options.floatingLayer.addChild bar
# @updateHealthBar()
getActionProp: (prop, subProp, def=null) ->
# Get a property or sub-property from an action, falling back to ThangType
@ -629,29 +575,30 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@updateEffectMarks() if @thang?.effectNames?.length or @previousEffectNames?.length
updateEffectMarks: ->
return if _.isEqual @thang.effectNames, @previousEffectNames
return if @stopped
for effect in @thang.effectNames
mark = @addMark effect, @options.floatingLayer, effect
mark.statusEffect = true
mark.toggle 'on'
mark.show()
if @previousEffectNames
for effect in @previousEffectNames
continue if effect in @thang.effectNames
mark = @marks[effect]
mark.toggle false
if @thang.effectNames.length > 1 and not @effectInterval
@rotateEffect()
@effectInterval = setInterval @rotateEffect, 1500
else if @effectInterval and @thang.effectNames.length <= 1
clearInterval @effectInterval
@effectInterval = null
@previousEffectNames = @thang.effectNames
# TODO: get effect marks working again
# return if _.isEqual @thang.effectNames, @previousEffectNames
# return if @stopped
# for effect in @thang.effectNames
# mark = @addMark effect, @options.floatingLayer, effect
# mark.statusEffect = true
# mark.toggle 'on'
# mark.show()
#
# if @previousEffectNames
# for effect in @previousEffectNames
# continue if effect in @thang.effectNames
# mark = @marks[effect]
# mark.toggle false
#
# if @thang.effectNames.length > 1 and not @effectInterval
# @rotateEffect()
# @effectInterval = setInterval @rotateEffect, 1500
#
# else if @effectInterval and @thang.effectNames.length <= 1
# clearInterval @effectInterval
# @effectInterval = null
#
# @previousEffectNames = @thang.effectNames
rotateEffect: =>
effects = (m.name for m in _.values(@marks) when m.on and m.statusEffect and m.mark)
@ -663,9 +610,10 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@marks[effects[@effectIndex]].show()
setHighlight: (to, delay) ->
@addMark 'highlight', @options.floatingLayer, 'highlight' if to
@marks.highlight?.highlightDelay = delay
@marks.highlight?.toggle to and not @dimmed
# TODO: get highlights working again
# @addMark 'highlight', @options.floatingLayer, 'highlight' if to
# @marks.highlight?.highlightDelay = delay
# @marks.highlight?.toggle to and not @dimmed
setDimmed: (@dimmed) ->
@marks.highlight?.toggle @marks.highlight.on and not @dimmed
@ -674,15 +622,17 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@options.thang = @thang
setDebug: (debug) ->
return unless @thang?.collides and @options.camera?
@addMark 'debug', @options.floatingLayer if debug
@marks.debug?.toggle debug
# TODO: get debugging shapes working again
# return unless @thang?.collides and @options.camera?
# @addMark 'debug', @options.floatingLayer if debug
# @marks.debug?.toggle debug
addLabel: (name, style) ->
@labels[name] ?= new Label sprite: @, camera: @options.camera, layer: @options.textLayer, style: style
@labels[name]
addMark: (name, layer, thangType=null) ->
return # TODO: figure out how to recreate marks
@marks[name] ?= new Mark name: name, sprite: @, camera: @options.camera, layer: layer ? @options.groundLayer, thangType: thangType
@marks[name]

View file

@ -1,19 +1,31 @@
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
CocoClass = require 'lib/CocoClass'
WebGLSprite = require './WebGLSprite'
module.exports = class WebGLLayer extends CocoClass # createjs.SpriteContainer
_.extend(WebGLLayer.prototype, Backbone.Events)
module.exports = class WebGLLayer extends createjs.SpriteContainer
actionRenderState: null
needToRerender: false
toRenderBundles: null
willRender: false
buildAutomatically: true
buildAsync: true
resolutionFactor: SPRITE_RESOLUTION_FACTOR
defaultActions: ['idle', 'die', 'move', 'move_back', 'move_side', 'move_fore', 'attack']
numThingsLoading: 0
cocoSprites: null
spriteSheet: null
spriteContainer: null
constructor: ->
super(arguments...)
@spriteSheet = @_renderNewSpriteSheet(false) # builds an empty spritesheet
@spriteContainer = new createjs.SpriteContainer(@spriteSheet)
@actionRenderState = {}
@toRenderBundles = []
@initialize(arguments...)
@cocoSprites = []
setDefaultActions: (@defaultActions) ->
@ -25,24 +37,54 @@ module.exports = class WebGLLayer extends createjs.SpriteContainer
key
addCocoSprite: (cocoSprite) ->
# build the animations for it
@cocoSprites.push cocoSprite
@loadThangType(cocoSprite.thangType)
@addDefaultActionsToRender(cocoSprite)
@setImageObjectToCocoSprite(cocoSprite)
# TODO: actually add it as a child
loadThangType: (thangType) ->
if not thangType.isFullyLoaded()
thangType.setProjection null
thangType.fetch() unless thangType.loading
@numThingsLoading++
@listenToOnce(thangType, 'sync', @somethingLoaded)
else if thangType.get('raster') and not thangType.loadedRaster
thangType.loadRasterImage()
@listenToOnce(thangType, 'raster-image-loaded', @somethingLoaded)
@numThingsLoading++
somethingLoaded: (thangType) ->
@numThingsLoading--
@loadThangType(thangType) # might need to load the raster image object
for cocoSprite in @cocoSprites
if cocoSprite.thangType is thangType
@addDefaultActionsToRender(cocoSprite)
@renderNewSpriteSheet()
addDefaultActionsToRender: (cocoSprite) ->
if cocoSprite.thangType.get('raster')
@upsertActionToRender(cocoSprite.thangType)
else
for action in _.values(cocoSprite.thangType.getActions())
continue unless action.name in @defaultActions
@upsertActionToRender(cocoSprite.thangType, action.name, cocoSprite.options.colorConfig)
upsertActionToRender: (thangType, actionName, colorConfig) ->
groupKey = @renderGroupingKey(thangType, actionName, colorConfig)
return if @actionRenderState[groupKey] isnt undefined
@actionRenderState[groupKey] = 'need-to-render'
@toRenderBundles.push({thangType: thangType, actionName: actionName, colorConfig: colorConfig})
return if @willRender
# @willRender = _.defer => @renderNewSpriteSheet()
return if @willRender or not @buildAutomatically
@willRender = _.defer => @renderNewSpriteSheet()
renderNewSpriteSheet: ->
@willRender = false
return if @numThingsLoading
@_renderNewSpriteSheet()
_renderNewSpriteSheet: (async) ->
async ?= @buildAsync
builder = new createjs.SpriteSheetBuilder()
groups = _.groupBy(@toRenderBundles, ((bundle) -> @renderGroupingKey(bundle.thangType, '', bundle.colorConfig)), @)
for bundleGrouping in _.values(groups)
@ -57,7 +99,20 @@ module.exports = class WebGLLayer extends createjs.SpriteContainer
@renderSpriteSheet(args...)
else
@renderRasterImage(thangType, builder)
builder.build()
if not _.size(groups)
emptiness = new createjs.Container()
emptiness.setBounds(0, 0, 1, 1)
builder.addFrame(emptiness)
if async
builder.buildAsync()
builder.on 'complete', @onBuildSpriteSheetComplete, @, true, builder
else
builder.build()
onBuildSpriteSheetComplete: (e, builder) ->
console.log 'done?', builder.spriteSheet
renderContainers: (thangType, colorConfig, actionNames, spriteSheetBuilder) ->
containersToRender = {}
@ -146,4 +201,26 @@ module.exports = class WebGLLayer extends createjs.SpriteContainer
bm = new createjs.Bitmap(thangType.rasterImage[0])
scale = thangType.get('scale') or 1
frame = spriteSheetBuilder.addFrame(bm, null, scale)
spriteSheetBuilder.addAnimation(@renderGroupingKey(thangType), [frame], false)
spriteSheetBuilder.addAnimation(@renderGroupingKey(thangType), [frame], false)
setImageObjectToCocoSprite: (cocoSprite) ->
if not cocoSprite.thangType.isFullyLoaded()
# just give a placeholder
sprite = new createjs.Sprite(@spriteSheet)
else if cocoSprite.thangType.get('raster')
sprite = new createjs.Sprite(@spriteSheet)
reg = cocoSprite.getOffset 'registration'
sprite.regX = -reg.x
sprite.regY = -reg.y
sprite.gotoAndStop(@renderGroupingKey(cocoSprite.thangType))
else
prefix = @renderGroupingKey(cocoSprite.thangType, null, cocoSprite.colorConfig) + '.'
sprite = new WebGLSprite(@spriteSheet, cocoSprite.thangType, prefix)
sprite.sprite = cocoSprite
sprite.layerPriority = cocoSprite.thang?.layerPriority ? cocoSprite.thangType.get 'layerPriority'
sprite.name = cocoSprite.thang?.spriteName or cocoSprite.thangType.get 'name'
cocoSprite.addHealthBar()
cocoSprite.setImageObject(sprite)

View file

@ -5,30 +5,63 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix) ->
@initialize(@spriteSheet)
if @thangType.get('renderStrategy') isnt 'container'
@singleChildSprite = new createjs.Sprite(@spriteSheet)
@addChild(@singleChildSprite)
play: ->
@singleChildSprite?.play()
@paused = false
stop: ->
@singleChildSprite?.stop()
@paused = true
gotoAndPlay: (actionName) -> @goto(actionName, false)
gotoAndStop: (actionName) -> @goto(actionName, true)
goto: (actionName, @paused=true) ->
@currentAnimation = actionName
action = @thangType.getActions()[actionName]
randomStart = actionName.startsWith('move')
if action.animation
@childSpriteContainers = []
@baseMovieClip = @buildMovieClip(action.animation)
@mirrorMovieClip(@baseMovieClip, @)
@framerate = (action.framerate ? 20) * (action.speed ? 1)
@frames = action.frames
@currentFrame = 0
if @frames
@frames = (parseInt(f) for f in @frames.split(','))
if @frames and @frames.length is 1
@baseMovieClip.gotoAndStop(@frames[0])
@paused = true
@loop = action.loops isnt false
@goesTo = action.goesTo
@animLength = if @frames then @frames.length else @baseMovieClip.frameBounds.length
reg = action.positions?.registration or @thangType.get('positions')?.registration or {x:0, y:0}
if @singleChildSprite
scale = SPRITE_RESOLUTION_FACTOR * (action.scale ? @thangType.get('scale') ? 1)
@regX = -reg.x * scale
@regY = -reg.y * scale
func = if @paused then 'gotoAndStop' else 'gotoAndPlay'
animationName = @spriteSheetPrefix + actionName
@singleChildSprite[func](animationName)
@singleChildSprite.framerate = action.framerate or 20
if randomStart and frames = @spriteSheet.getAnimation(animationName)?.frames
start = Math.floor(Math.random() * frames.length)
@singleChildSprite.currentAnimationFrame = start
else
@childSpriteContainers = []
@baseMovieClip = @buildMovieClip(action.animation)
@mirrorMovieClip(@baseMovieClip, @)
@frames = action.frames
@currentFrame = 0
if @frames
@frames = (parseInt(f) for f in @frames.split(','))
if @frames and @frames.length is 1
@baseMovieClip.gotoAndStop(@frames[0])
@paused = true
@loop = action.loops isnt false
@goesTo = action.goesTo
@animLength = if @frames then @frames.length else @baseMovieClip.frameBounds.length
@regX = -reg.x
@regY = -reg.y
if randomStart
@currentFrame = Math.floor(Math.random() * @animLength)
return
mirrorMovieClip: (movieClip, spriteContainer) ->
@ -42,6 +75,10 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
locals = {}
_.extend locals, @buildMovieClipContainers(animData.containers)
_.extend locals, @buildMovieClipAnimations(animData.animations)
toSkip = {}
toSkip[shape.bn] for shape in animData.shapes
toSkip[graphic.bn] for graphic in animData.graphics
anim = new createjs.MovieClip()
anim.initialize(mode ? createjs.MovieClip.INDEPENDENT, startPosition ? 0, loops ? true)
@ -51,7 +88,7 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
tween = createjs.Tween
for func in tweenData
args = $.extend(true, [], (func.a))
if @dereferenceArgs(args, locals) is false
if @dereferenceArgs(args, locals, toSkip) is false
console.log 'could not dereference args', args
stopped = true
break
@ -87,7 +124,7 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
@childSpriteContainers.push(childSpriteContainer)
return map
dereferenceArgs: (args, locals) ->
dereferenceArgs: (args, locals, toSkip) ->
for key, val of args
if locals[val]
args[key] = locals[val]
@ -96,14 +133,15 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
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)
res = @dereferenceArgs(val, locals, toSkip)
return res if res is false
else if _.isString(val)
else if _.isString(val) and toSkip[val]
return false
return args
tick: (delta) ->
return unless @framerate and not @paused
return if @singleChildSprite # this gets moved forward automatically
newFrame = @currentFrame + @framerate * delta / 1000
if newFrame > @animLength
@ -137,6 +175,7 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
if @childSpriteContainers
for childSpriteContainer in @childSpriteContainers
movieClip = childSpriteContainer.movieClip
continue unless movieClip.parent
index = movieClip.parent.getChildIndex(movieClip)
movieClip.gotoAndStop(newFrame)
movieClip.parent[index] = childSpriteContainer

View file

@ -35,9 +35,14 @@ module.exports = class ThangType extends CocoModel
return @get('actions') or @get('raster') # needs one of these two things
loadRasterImage: ->
return if @loadingRaster or @loadedRaster
return unless raster = @get('raster')
@rasterImage = $("<img src='/file/#{raster}' />")
@rasterImage.on('load', => @trigger('raster-image-loaded'))
@loadingRaster = true
@rasterImage.one('load', =>
@loadingRaster = false
@loadedRaster = true
@trigger('raster-image-loaded', @))
getActions: ->
return {} unless @isFullyLoaded()

View file

@ -5,8 +5,13 @@ treeThangType = new ThangType(require 'test/app/fixtures/tree1.thang.type')
ogreMunchkinThangType = new ThangType(require 'test/app/fixtures/ogre-munchkin-m.thang.type')
describe 'WebGLLayer', ->
it 'creates containers for animated actions if set to renderStrategy=container', ->
layer = null
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
it 'creates containers for animated actions if set to renderStrategy=container', ->
ogreMunchkinThangType.set('renderStrategy', 'container')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreMunchkinThangType, {colorConfig: colorConfig})
@ -16,7 +21,6 @@ describe 'WebGLLayer', ->
expect(key in sheet.getAnimations()).toBe(true)
it 'creates the container for static actions if set to renderStrategy=container', ->
layer = new WebGLLayer()
treeThangType.set('renderStrategy', 'container')
sprite = new CocoSprite(treeThangType)
layer.addCocoSprite(sprite)
@ -25,7 +29,6 @@ describe 'WebGLLayer', ->
expect(key in sheet.getAnimations()).toBe(true)
it 'creates animations for animated actions if set to renderStrategy=spriteSheet', ->
layer = new WebGLLayer()
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreMunchkinThangType, {colorConfig: colorConfig})
@ -35,7 +38,6 @@ describe 'WebGLLayer', ->
expect(key in sheet.getAnimations()).toBe(true)
it 'creates animations for static actions if set to renderStrategy=spriteSheet', ->
layer = new WebGLLayer()
treeThangType.set('renderStrategy', 'spriteSheet')
sprite = new CocoSprite(treeThangType)
layer.addCocoSprite(sprite)
@ -44,7 +46,6 @@ describe 'WebGLLayer', ->
expect(key in sheet.getAnimations()).toBe(true)
it 'only renders frames used by actions when renderStrategy=spriteSheet', ->
layer = new WebGLLayer()
layer.setDefaultActions(['idle']) # uses the move side animation
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
@ -61,7 +62,6 @@ describe 'WebGLLayer', ->
bootsThangType = new ThangType(require 'test/app/fixtures/leather-boots.thang.type')
bootsThangType.loadRasterImage()
bootsThangType.once('raster-image-loaded', ->
layer = new WebGLLayer()
sprite = new CocoSprite(bootsThangType)
layer.addCocoSprite(sprite)
sheet = layer.renderNewSpriteSheet()
@ -69,4 +69,44 @@ describe 'WebGLLayer', ->
expect(key in sheet.getAnimations()).toBe(true)
done()
#$('body').attr('class', '').empty().css('background', 'white').append($(sheet._images))
)
)
it 'loads ThangTypes for CocoSprites that are added to it and need to be loaded', ->
thangType = new ThangType({_id: 1})
sprite = new CocoSprite(thangType)
layer.addCocoSprite(sprite)
expect(layer.numThingsLoading).toBe(1)
expect(jasmine.Ajax.requests.count()).toBe(1)
it 'loads raster images for ThangType', (done) ->
bootsThangTypeData = require 'test/app/fixtures/leather-boots.thang.type'
thangType = new ThangType({_id: 1})
sprite = new CocoSprite(thangType)
layer.addCocoSprite(sprite)
expect(layer.numThingsLoading).toBe(1)
jasmine.Ajax.requests.sendResponses({'/db/thang.type/1': bootsThangTypeData})
expect(layer.numThingsLoading).toBe(1)
thangType.once('raster-image-loaded', ->
expect(layer.numThingsLoading).toBe(0)
done()
)
it 'renders a new SpriteSheet only once everything has loaded', (done) ->
bootsThangTypeData = require 'test/app/fixtures/leather-boots.thang.type'
thangType1 = new ThangType({_id: 1})
thangType2 = new ThangType({_id: 2})
layer.addCocoSprite(new CocoSprite(thangType1))
expect(layer.numThingsLoading).toBe(1)
layer.addCocoSprite(new CocoSprite(thangType2))
expect(layer.numThingsLoading).toBe(2)
spyOn layer, '_renderNewSpriteSheet'
jasmine.Ajax.requests.sendResponses({'/db/thang.type/1': ogreMunchkinThangType.attributes})
expect(layer.numThingsLoading).toBe(1)
jasmine.Ajax.requests.sendResponses({'/db/thang.type/2': bootsThangTypeData})
expect(layer.numThingsLoading).toBe(1)
expect(layer._renderNewSpriteSheet).not.toHaveBeenCalled()
thangType2.once('raster-image-loaded', ->
expect(layer.numThingsLoading).toBe(0)
expect(layer._renderNewSpriteSheet).toHaveBeenCalled()
done()
)

View file

@ -18,7 +18,7 @@ describe 'WebGLSprite', ->
ticks = 0
listener = {
handleEvent: ->
# return if ticks >= 100
return if ticks >= 100
webGLSprite.tick(arguments[0].delta)
stage.update()
ticks += 1
@ -28,6 +28,8 @@ describe 'WebGLSprite', ->
describe 'with Ogre Munchkin ThangType', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreMunchkinThangType.markToRevert()
ogreMunchkinThangType.set('renderStrategy', 'container')
actions = ogreMunchkinThangType.getActions()
@ -42,6 +44,8 @@ describe 'WebGLSprite', ->
sheet = layer.renderNewSpriteSheet()
prefix = layer.renderGroupingKey(ogreMunchkinThangType, null, colorConfig) + '.'
window.webGLSprite = webGLSprite = new WebGLSprite(sheet, ogreMunchkinThangType, prefix)
webGLSprite.x = 200
webGLSprite.y = 200
afterEach ->
ogreMunchkinThangType.revert()
@ -58,7 +62,7 @@ describe 'WebGLSprite', ->
expect(webGLSprite.paused).toBe(true)
it 'has a tick function which moves the animation forward', ->
webGLSprite.gotoAndPlay('move_fore')
webGLSprite.gotoAndPlay('attack')
webGLSprite.tick(100) # one hundred milliseconds
expect(webGLSprite.baseMovieClip.currentFrame).toBe(webGLSprite.framerate*100/1000)
@ -83,6 +87,8 @@ describe 'WebGLSprite', ->
describe 'with Ogre Fangrider ThangType', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreFangriderThangType.markToRevert()
ogreFangriderThangType.set('renderStrategy', 'container')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
@ -91,15 +97,46 @@ describe 'WebGLSprite', ->
sheet = layer.renderNewSpriteSheet()
prefix = layer.renderGroupingKey(ogreFangriderThangType, null, colorConfig) + '.'
window.webGLSprite = webGLSprite = new WebGLSprite(sheet, ogreFangriderThangType, prefix)
webGLSprite.x = 300
webGLSprite.y = 300
afterEach ->
ogreFangriderThangType.revert()
it 'has gotoAndPlay, gotoAndStop, and paused like a MovieClip or Sprite', ->
webGLSprite.gotoAndPlay('move_fore')
it 'synchronizes animations with child movie clips properly', ->
webGLSprite.gotoAndPlay('die')
webGLSprite.tick(100) # one hundred milliseconds
expectedFrame = webGLSprite.framerate*100/1000
expect(webGLSprite.baseMovieClip.currentFrame).toBe(expectedFrame)
expect(webGLSprite.currentFrame).toBe(expectedFrame)
for child in webGLSprite.childSpriteContainers
expect(child.movieClip.currentFrame).toBe(expectedFrame)
describe 'with Ogre Munchkin ThangType and renderStrategy=spriteSheet', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreMunchkinThangType.markToRevert()
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
actions = ogreMunchkinThangType.getActions()
# couple extra actions for doing some tests
actions.littledance = {animation:'enemy_small_move_side',framerate:1, frames:'0,6,2,6,2,8,0'}
actions.onestep = {animation:'enemy_small_move_side', loops: false}
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreMunchkinThangType, {colorConfig: colorConfig})
layer.addCocoSprite(sprite)
sheet = layer.renderNewSpriteSheet()
prefix = layer.renderGroupingKey(ogreMunchkinThangType, null, colorConfig) + '.'
window.webGLSprite = webGLSprite = new WebGLSprite(sheet, ogreMunchkinThangType, prefix)
webGLSprite.x = 200
webGLSprite.y = 200
afterEach ->
ogreMunchkinThangType.revert()
it 'has the same interface as for when the ThangType uses the container renderStrategy', ->
webGLSprite.gotoAndPlay('move_fore')
webGLSprite.gotoAndStop('attack')
showMe()