Refactored WebGLSprite to SegmentedSprite and SingularSprite, and refactored renderStrategy (container/spriteSheet) to spriteType (segmented/singular).

This commit is contained in:
Scott Erickson 2014-09-19 14:56:40 -07:00
parent 3c9b40e8f4
commit fbbfb6c0cc
9 changed files with 352 additions and 313 deletions

View file

@ -302,7 +302,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
updateBaseScale: ->
scale = 1
useRawScale = @isRaster or @thangType.get('renderStrategy') is 'container'
useRawScale = @isRaster or @thangType.get('spriteType') is 'container'
scale = @thangType.get('scale') or 1 if useRawScale
scale /= @options.resolutionFactor unless useRawScale
@baseScaleX = @baseScaleY = scale

View file

@ -1,32 +1,20 @@
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
module.exports = class WebGLSprite extends createjs.SpriteContainer
module.exports = class SegmentedSprite extends createjs.SpriteContainer
childMovieClips: null
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
@initialize(@spriteSheet)
if @thangType.get('renderStrategy') isnt 'container'
@singleChildSprite = new createjs.Sprite(@spriteSheet)
@addChild(@singleChildSprite)
@addEventListener 'tick', @handleTick
handleTick: (e) =>
if @lastTimeStamp
@tick(e.timeStamp - @lastTimeStamp)
@lastTimeStamp = e.timeStamp
destroy: ->
@handleTick = undefined
@removeAllEventListeners()
play: ->
@singleChildSprite?.play()
@paused = false
stop: ->
@singleChildSprite?.stop()
@paused = true
# CreateJS.Sprite-like interface
play: -> @paused = false
stop: -> @paused = true
gotoAndPlay: (actionName) -> @goto(actionName, false)
gotoAndStop: (actionName) -> @goto(actionName, true)
@ -34,93 +22,51 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
@currentAnimation = actionName
@baseMovieClip = @framerate = null
@actionNotSupported = false
action = @thangType.getActions()[actionName]
randomStart = actionName.startsWith('move')
reg = action.positions?.registration or @thangType.get('positions')?.registration or {x:0, y:0}
if action.animation
@framerate = (action.framerate ? 20) * (action.speed ? 1)
if @singleChildSprite
scale = @resolutionFactor * (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)
if @singleChildSprite.currentFrame is 0
@regX = -reg.x
@regY = -reg.y
@singleChildSprite.stop()
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').animations[action.animation].bounds
@singleChildSprite.x = bounds[0]
@singleChildSprite.y = bounds[1]
console.log 'bounds?', bounds
@singleChildSprite.scaleX = bounds[2] / (SPRITE_PLACEHOLDER_RADIUS * @resolutionFactor * 2)
@singleChildSprite.scaleY = bounds[3] / (SPRITE_PLACEHOLDER_RADIUS * @resolutionFactor * 2)
return
@singleChildSprite.framerate = action.framerate or 20
if randomStart and frames = @spriteSheet.getAnimation(animationName)?.frames
@singleChildSprite.currentAnimationFrame = Math.floor(Math.random() * frames.length)
@regX = -reg.x
@regY = -reg.y
@childMovieClips = []
@baseMovieClip = @buildMovieClip(action.animation)
@children = @baseMovieClip.children
@frames = action.frames
@frames = (parseInt(f) for f in @frames.split(',')) if @frames
@animLength = if @frames then @frames.length else @baseMovieClip.frameBounds.length
@currentFrame = if randomStart then Math.floor(Math.random() * @animLength) else 0
@baseMovieClip.gotoAndStop(@currentFrame)
@loop = action.loops isnt false
@goesTo = action.goesTo
@notifyActionNeedsRender(action) if @actionNotSupported
else
@regX = -reg.x
@regY = -reg.y
@childMovieClips = []
@baseMovieClip = @buildMovieClip(action.animation)
@children = @baseMovieClip.children
@frames = action.frames
@frames = (parseInt(f) for f in @frames.split(',')) if @frames
@animLength = if @frames then @frames.length else @baseMovieClip.frameBounds.length
@currentFrame = if randomStart then Math.floor(Math.random() * @animLength) else 0
@baseMovieClip.gotoAndStop(@currentFrame)
@loop = action.loops isnt false
@goesTo = action.goesTo
@notifyActionNeedsRender(action) if @actionNotSupported
if action.container
else if action.container
scale = @resolutionFactor * (action.scale ? @thangType.get('scale') ? 1)
if @singleChildSprite
@regX = -reg.x * scale
@regY = -reg.y * scale
animationName = @spriteSheetPrefix + actionName
@singleChildSprite.gotoAndStop(animationName)
if @singleChildSprite.currentFrame is 0
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').containers[action.container].b
@singleChildSprite.x = bounds[0]
@singleChildSprite.y = bounds[1]
@singleChildSprite.scaleX = bounds[2] / (SPRITE_PLACEHOLDER_RADIUS * 2)
@singleChildSprite.scaleY = bounds[3] / (SPRITE_PLACEHOLDER_RADIUS * 2)
return
@regX = -reg.x
@regY = -reg.y
@childMovieClips = []
containerName = @spriteSheetPrefix + action.container
sprite = new createjs.Sprite(@spriteSheet)
sprite.gotoAndStop(containerName)
if sprite.currentFrame is 0
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').containers[action.container].b
sprite.x = bounds[0]
sprite.y = bounds[1]
sprite.scaleX = bounds[2] / (SPRITE_PLACEHOLDER_RADIUS * 2 * scale)
sprite.scaleY = bounds[3] / (SPRITE_PLACEHOLDER_RADIUS * 2 * scale)
else
@regX = -reg.x
@regY = -reg.y
@childMovieClips = []
containerName = @spriteSheetPrefix + action.container
sprite = new createjs.Sprite(@spriteSheet)
sprite.gotoAndStop(containerName)
if sprite.currentFrame is 0
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').containers[action.container].b
sprite.x = bounds[0]
sprite.y = bounds[1]
sprite.scaleX = bounds[2] / (SPRITE_PLACEHOLDER_RADIUS * 2 * scale)
sprite.scaleY = bounds[3] / (SPRITE_PLACEHOLDER_RADIUS * 2 * scale)
else
sprite.scaleX = sprite.scaleY = 1 / scale
@children = [sprite]
sprite.scaleX = sprite.scaleY = 1 / scale
@children = [sprite]
return
notifyActionNeedsRender: (action) ->
@sprite.trigger('action-needs-render', @sprite, action)
@sprite?.trigger('action-needs-render', @sprite, action)
buildMovieClip: (animationName, mode, startPosition, loops) ->
raw = @thangType.get('raw')
animData = raw.animations[animationName]
@ -129,7 +75,7 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
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
@ -201,10 +147,15 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
return false
return args
handleTick: (e) =>
if @lastTimeStamp
@tick(e.timeStamp - @lastTimeStamp)
@lastTimeStamp = e.timeStamp
tick: (delta) ->
return unless @baseMovieClip and @framerate and not @paused
return if @paused
newFrame = @currentFrame + @framerate * delta / 1000
if newFrame > @animLength
if @goesTo
@gotoAndPlay(@goesTo)
@ -215,7 +166,7 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
@dispatchEvent('animationend')
else
newFrame = newFrame % @animLength
if @frames
prevFrame = Math.floor(newFrame)
nextFrame = Math.ceil(newFrame)
@ -230,17 +181,17 @@ module.exports = class WebGLSprite extends createjs.SpriteContainer
@baseMovieClip.gotoAndStop(newFrameIndex)
else
@baseMovieClip.gotoAndStop(newFrame)
@currentFrame = newFrame
# So, originally I thought I'd have to swap in MovieClips for parallel
# SpriteContainers between each frame, but turns out that's not the case.
# The WebGL rendering system treats the MovieClip like a SpriteContainer,
# which makes things simpler for me...
# For some reason, though, gotoAndStop doesn't seem to advance the children
# so I gotta do that manually.
movieClip.gotoAndStop(newFrame) for movieClip in @childMovieClips
getBounds: ->
@baseMovieClip.getBounds()

View file

@ -0,0 +1,69 @@
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
module.exports = class WebGLSprite extends createjs.Sprite
childMovieClips: null
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
@initialize(@spriteSheet)
destroy: ->
@removeAllEventListeners()
gotoAndPlay: (actionName) -> @goto(actionName, false)
gotoAndStop: (actionName) -> @goto(actionName, true)
_gotoAndPlay: createjs.Sprite.prototype.gotoAndPlay
_gotoAndStop: createjs.Sprite.prototype.gotoAndStop
goto: (actionName, @paused=true) ->
@actionNotSupported = false
action = @thangType.getActions()[actionName]
randomStart = actionName.startsWith('move')
reg = action.positions?.registration or @thangType.get('positions')?.registration or {x:0, y:0}
if action.animation
@framerate = (action.framerate ? 20) * (action.speed ? 1)
scale = @resolutionFactor * (action.scale ? @thangType.get('scale') ? 1)
@regX = -reg.x * scale
@regY = -reg.y * scale
func = if @paused then '_gotoAndStop' else '_gotoAndPlay'
animationName = @spriteSheetPrefix + actionName
@[func](animationName)
if @currentFrame is 0
@stop()
@regX = -reg.x
@regY = -reg.y
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').animations[action.animation].bounds
@x = bounds[0]
@y = bounds[1]
@scaleX = bounds[2] / (SPRITE_PLACEHOLDER_RADIUS * @resolutionFactor * 2)
@scaleY = bounds[3] / (SPRITE_PLACEHOLDER_RADIUS * @resolutionFactor * 2)
return
@framerate = action.framerate or 20
if randomStart and frames = @spriteSheet.getAnimation(animationName)?.frames
@currentAnimationFrame = Math.floor(Math.random() * frames.length)
if action.container
scale = @resolutionFactor * (action.scale ? @thangType.get('scale') ? 1)
@regX = -reg.x * scale
@regY = -reg.y * scale
animationName = @spriteSheetPrefix + actionName
@_gotoAndStop(animationName)
if @currentFrame is 0
@notifyActionNeedsRender(action)
bounds = @thangType.get('raw').containers[action.container].b
@x = bounds[0]
@y = bounds[1]
@scaleX = bounds[2] / (SPRITE_PLACEHOLDER_RADIUS * 2)
@scaleY = bounds[3] / (SPRITE_PLACEHOLDER_RADIUS * 2)
return
@currentAnimation = actionName
return
notifyActionNeedsRender: (action) ->
@sprite.trigger('action-needs-render', @sprite, action)

View file

@ -1,9 +1,10 @@
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
CocoClass = require 'lib/CocoClass'
WebGLSprite = require './WebGLSprite'
SegmentedSprite = require './SegmentedSprite'
SingularSprite = require './SingularSprite'
{SpriteContainerLayer} = require 'lib/surface/Layer'
NEVER_RENDER_ANYTHING = true # set to true to test placeholders
NEVER_RENDER_ANYTHING = false # set to true to test placeholders
module.exports = class WebGLLayer extends CocoClass
@ -124,7 +125,7 @@ module.exports = class WebGLLayer extends CocoClass
actionNames = (bundle.actionName for bundle in bundleGrouping)
args = [thangType, colorConfig, actionNames, builder]
if thangType.get('raw')
if thangType.get('renderStrategy') is 'container'
if thangType.get('spriteType') is 'segmented'
@renderContainers(args...)
else
@renderSpriteSheet(args...)
@ -305,8 +306,9 @@ module.exports = class WebGLLayer extends CocoClass
sprite.gotoAndStop(@renderGroupingKey(cocoSprite.thangType))
else
SpriteClass = if cocoSprite.thangType.get('spriteType') is 'segmented' then SegmentedSprite else SingularSprite
prefix = @renderGroupingKey(cocoSprite.thangType, null, cocoSprite.colorConfig) + '.'
sprite = new WebGLSprite(@spriteSheet, cocoSprite.thangType, prefix, @resolutionFactor)
sprite = new SpriteClass(@spriteSheet, cocoSprite.thangType, prefix, @resolutionFactor)
sprite.sprite = cocoSprite
sprite.layerPriority = cocoSprite.thang?.layerPriority ? cocoSprite.thangType.get 'layerPriority'

View file

@ -0,0 +1,121 @@
WebGLLayer = require 'lib/surface/WebGLLayer'
SegmentedSprite = require 'lib/surface/SegmentedSprite'
CocoSprite = require 'lib/surface/CocoSprite'
ThangType = require 'models/ThangType'
ogreMunchkinThangType = new ThangType(require 'test/app/fixtures/ogre-munchkin-m.thang.type')
ogreFangriderThangType = new ThangType(require 'test/app/fixtures/ogre-fangrider.thang.type')
describe 'SegmentedSprite', ->
segmentedSprite = null
showMe = ->
canvas = $('<canvas width="600" height="400"></canvas>').css('position', 'absolute').css('index', 1000).css('background', 'white')
$('body').append(canvas)
stage = new createjs.SpriteStage(canvas[0])
stage.addChild(segmentedSprite)
ticks = 0
listener = {
handleEvent: ->
return if ticks >= 100
ticks += 1
segmentedSprite.tick(arguments[0].delta)
stage.update()
}
createjs.Ticker.addEventListener "tick", listener
describe 'with Ogre Munchkin ThangType', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreMunchkinThangType.markToRevert()
ogreMunchkinThangType.set('spriteType', 'segmented')
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.segmentedSprite = segmentedSprite = new SegmentedSprite(sheet, ogreMunchkinThangType, prefix)
segmentedSprite.x = 200
segmentedSprite.y = 200
afterEach ->
ogreMunchkinThangType.revert()
it 'has gotoAndPlay, gotoAndStop, currentAnimation, and paused like a MovieClip or Sprite', ->
segmentedSprite.gotoAndPlay('move_fore')
expect(segmentedSprite.baseMovieClip).toBeDefined()
expect(segmentedSprite.paused).toBe(false)
segmentedSprite.gotoAndStop('move_fore')
expect(segmentedSprite.paused).toBe(true)
expect(segmentedSprite.currentAnimation).toBe('move_fore')
it 'has a tick function which moves the animation forward', ->
segmentedSprite.gotoAndPlay('attack')
segmentedSprite.tick(100) # one hundred milliseconds
expect(segmentedSprite.baseMovieClip.currentFrame).toBe(segmentedSprite.framerate*100/1000)
it 'will interpolate between frames of a custom frame set', ->
segmentedSprite.gotoAndPlay('littledance')
segmentedSprite.tick(1000)
expect(segmentedSprite.baseMovieClip.currentFrame).toBe(6)
segmentedSprite.tick(1000)
expect(segmentedSprite.baseMovieClip.currentFrame).toBe(2)
segmentedSprite.tick(500)
expect(segmentedSprite.baseMovieClip.currentFrame).toBe(4)
segmentedSprite.tick(500)
expect(segmentedSprite.baseMovieClip.currentFrame).toBe(6)
it 'emits animationend for animations where loops is false and there is no goesTo', ->
fired = false
segmentedSprite.gotoAndPlay('onestep')
segmentedSprite.on('animationend', -> fired = true)
segmentedSprite.tick(1000)
expect(fired).toBe(true)
describe 'with Ogre Fangrider ThangType', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreFangriderThangType.markToRevert()
ogreFangriderThangType.set('spriteType', 'segmented')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreFangriderThangType, {colorConfig: colorConfig})
layer.addCocoSprite(sprite)
sheet = layer.renderNewSpriteSheet()
prefix = layer.renderGroupingKey(ogreFangriderThangType, null, colorConfig) + '.'
window.segmentedSprite = segmentedSprite = new SegmentedSprite(sheet, ogreFangriderThangType, prefix)
segmentedSprite.x = 300
segmentedSprite.y = 300
afterEach ->
ogreFangriderThangType.revert()
it 'synchronizes animations with child movie clips properly', ->
segmentedSprite.gotoAndPlay('die')
segmentedSprite.tick(100) # one hundred milliseconds
expectedFrame = segmentedSprite.framerate*100/1000
expect(segmentedSprite.currentFrame).toBe(expectedFrame)
for movieClip in segmentedSprite.childMovieClips
expect(movieClip.currentFrame).toBe(expectedFrame)
it 'does not include shapes from the original animation', ->
segmentedSprite.gotoAndPlay('attack')
segmentedSprite.tick(230)
for child in segmentedSprite.children
expect(_.isString(child)).toBe(false)
it 'maintains the right number of shapes', ->
segmentedSprite.gotoAndPlay('idle')
lengths = []
for i in _.range(10)
segmentedSprite.tick(10)
expect(segmentedSprite.children.length).toBe(20)

View file

@ -0,0 +1,48 @@
WebGLLayer = require 'lib/surface/WebGLLayer'
SingularSprite = require 'lib/surface/SingularSprite'
CocoSprite = require 'lib/surface/CocoSprite'
ThangType = require 'models/ThangType'
ogreMunchkinThangType = new ThangType(require 'test/app/fixtures/ogre-munchkin-m.thang.type')
describe 'SingularSprite', ->
singularSprite = null
showMe = ->
canvas = $('<canvas width="600" height="400"></canvas>').css('position', 'absolute').css('index', 1000).css('background', 'white')
$('body').append(canvas)
stage = new createjs.SpriteStage(canvas[0])
stage.addChild(singularSprite)
ticks = 0
listener = {
handleEvent: ->
return if ticks >= 100
ticks += 1
stage.update()
}
createjs.Ticker.addEventListener "tick", listener
describe 'with Ogre Munchkin ThangType and spriteType=spriteSheet', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreMunchkinThangType.markToRevert()
ogreMunchkinThangType.set('spriteType', 'singular')
actions = ogreMunchkinThangType.getActions()
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.singularSprite = singularSprite = new SingularSprite(sheet, ogreMunchkinThangType, prefix)
singularSprite.x = 200
singularSprite.y = 200
afterEach ->
ogreMunchkinThangType.revert()
it 'has the same interface as for when the ThangType uses the container spriteType', ->
singularSprite.gotoAndPlay('move_fore')
singularSprite.gotoAndStop('attack')

View file

@ -3,9 +3,9 @@ Camera = require 'lib/surface/Camera'
World = require 'lib/world/world'
ThangType = require 'models/ThangType'
treeThangType = new ThangType(require 'test/app/fixtures/tree1.thang.type')
ogreMunchkinThangType = new ThangType(require 'test/app/fixtures/ogre-munchkin-m.thang.type')
ogreFangriderThangType = new ThangType(require 'test/app/fixtures/ogre-fangrider.thang.type')
treeData = require 'test/app/fixtures/tree1.thang.type'
munchkinData = require 'test/app/fixtures/ogre-munchkin-m.thang.type'
fangriderData = require 'test/app/fixtures/ogre-fangrider.thang.type'
describe 'SpriteBoss', ->
spriteBoss = null
@ -26,24 +26,23 @@ describe 'SpriteBoss', ->
world = new World()
world.thangs = [
# Set trees side by side with different render strategies
{id: 'Tree 1', spriteName: 'Tree 1', exists: true, pos: {x:10, y:-8}, action: 'idle', health: 20, maxHealth: 20, rotation: Math.PI/2, acts: true }
{id: 'Tree 2', spriteName: 'Full Render Tree', exists: true, pos: {x:8, y:-8}, action: 'idle', health: 20, maxHealth: 20, rotation: Math.PI/2, acts: true }
{id: 'Segmented Tree', spriteName: 'Segmented Tree', exists: true, pos: {x:10, y:-8}, action: 'idle', health: 20, maxHealth: 20, rotation: Math.PI/2, acts: true }
{id: 'Singular Tree', spriteName: 'Singular Tree', exists: true, pos: {x:8, y:-8}, action: 'idle', health: 20, maxHealth: 20, rotation: Math.PI/2, acts: true }
# Include a tree whose existence will change so we can test removing sprites
{id: 'Tree Will Disappear', spriteName: 'Tree 1', exists: true, pos: {x:0, y:0}, action: 'idle', health: 20, maxHealth: 20, rotation: Math.PI/2, acts: true }
{id: 'Disappearing Tree', spriteName: 'Singular Tree', exists: true, pos: {x:0, y:0}, action: 'idle', health: 20, maxHealth: 20, rotation: Math.PI/2, acts: true }
]
world.thangMap = {}
world.thangMap[thang.id] = thang for thang in world.thangs
# Set up thang types. Mix renderStrategies.
fullRenderOgreMunchkinThangType = ogreMunchkinThangType.clone()
fullRenderOgreMunchkinThangType.set({name:'Full Render Ogre', slug:'full-render-ogre'})
fullRenderTreeThangType = treeThangType.clone()
fullRenderTreeThangType.set({name:'Full Render Tree', slug:'full-render-tree'})
ogreMunchkinThangType.set('renderStrategy', 'container')
ogreFangriderThangType.set('renderStrategy', 'container')
treeThangType.set('renderStrategy', 'container')
thangTypes = [treeThangType, ogreMunchkinThangType, ogreFangriderThangType, fullRenderOgreMunchkinThangType, fullRenderTreeThangType]
fangrider = new ThangType($.extend({}, fangriderData, {spriteType:'segmented', name:'Fangrider', slug:'fangrider'}))
segmentedMunchkin = new ThangType($.extend({}, munchkinData, {spriteType:'segmented', name:'Segmented Munchkin', slug:'segmented-munchkin'}))
singularMunchkin = new ThangType($.extend({}, munchkinData, {spriteType:'singular', name:'Singular Munchkin', slug:'singular-munchkin'}))
segmentedTree = new ThangType($.extend({}, treeData, {spriteType:'segmented', name:'Segmented Tree', slug: 'segmented-tree'}))
singularTree = new ThangType($.extend({}, treeData, {spriteType:'singular', name:'Singular Tree', slug: 'singular-tree'}))
thangTypes = [fangrider, segmentedMunchkin, singularMunchkin, segmentedTree, singularTree]
# Build the Stage and SpriteBoss.
window.stage = stage = new createjs.SpriteStage(canvas[0])
@ -69,49 +68,49 @@ describe 'SpriteBoss', ->
spriteBoss.update(true)
# Test that the unrendered, static sprites aren't showing anything
midRenderExpectations.push([spriteBoss.sprites['Tree 1'].imageObject.children.length,1,'static container action'])
midRenderExpectations.push([spriteBoss.sprites['Tree 1'].imageObject.children[0].currentFrame,0,'static container action'])
midRenderExpectations.push([spriteBoss.sprites['Tree 1'].imageObject.children[0].paused,true,'static container action'])
midRenderExpectations.push([spriteBoss.sprites['Tree 2'].imageObject.children[0].currentFrame,0,'static spriteSheet action'])
midRenderExpectations.push([spriteBoss.sprites['Tree 2'].imageObject.children[0].paused,true,'static spriteSheet action'])
midRenderExpectations.push([spriteBoss.sprites['Segmented Tree'].imageObject.children.length,1,'static segmented action'])
midRenderExpectations.push([spriteBoss.sprites['Segmented Tree'].imageObject.children[0].currentFrame,0,'static segmented action'])
midRenderExpectations.push([spriteBoss.sprites['Segmented Tree'].imageObject.children[0].paused,true,'static segmented action'])
midRenderExpectations.push([spriteBoss.sprites['Singular Tree'].imageObject.currentFrame,0,'static singular action'])
midRenderExpectations.push([spriteBoss.sprites['Singular Tree'].imageObject.paused,true,'static singular action'])
defaultLayer.once 'new-spritesheet', ->
# Now make the world a little more complicated.
world.thangs = world.thangs.concat [
# four cardinal ogres, to test movement rotation and placement around a center point.
{id: 'Ogre N', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:0, y:8}, action: 'move', health: 10, maxHealth: 10, rotation: -Math.PI/2, acts: true, scaleFactorX: 1.5 }
{id: 'Ogre W', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:-8, y:0}, action: 'move', health: 5, maxHealth: 10, rotation: 0, acts: true, scaleFactorY: 1.5 }
{id: 'Ogre E', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:8, y:0}, action: 'move', health: 5, maxHealth: 10, rotation: Math.PI, acts: true, alpha: 0.5 }
{id: 'Ogre S', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:0, y:-8}, action: 'move', health: 5, maxHealth: 10, rotation: Math.PI/2, acts: true }
{id: 'Ogre N', spriteName: 'Segmented Munchkin', exists: true, pos: {x:0, y:8}, action: 'move', health: 10, maxHealth: 10, rotation: -Math.PI/2, acts: true, scaleFactorX: 1.5 }
{id: 'Ogre W', spriteName: 'Segmented Munchkin', exists: true, pos: {x:-8, y:0}, action: 'move', health: 5, maxHealth: 10, rotation: 0, acts: true, scaleFactorY: 1.5 }
{id: 'Ogre E', spriteName: 'Segmented Munchkin', exists: true, pos: {x:8, y:0}, action: 'move', health: 5, maxHealth: 10, rotation: Math.PI, acts: true, alpha: 0.5 }
{id: 'Ogre S', spriteName: 'Segmented Munchkin', exists: true, pos: {x:0, y:-8}, action: 'move', health: 5, maxHealth: 10, rotation: Math.PI/2, acts: true }
# Set ogres side by side with different render strategies
{id: 'FROgre', spriteName: 'Full Render Ogre', exists: true, pos: {x:-10, y:-8}, action: 'move', health: 10, maxHealth: 10, rotation: -Math.PI/2, acts: true, alpha: 0.5 }
{id: 'NotFROgre', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:-8, y:-8}, action: 'move', health: 10, maxHealth: 10, rotation: -Math.PI/2, acts: true }
{id: 'Singular Ogre', spriteName: 'Singular Munchkin', exists: true, pos: {x:-10, y:-8}, action: 'move', health: 10, maxHealth: 10, rotation: -Math.PI/2, acts: true, alpha: 0.5 }
{id: 'Segmented Ogre', spriteName: 'Segmented Munchkin', exists: true, pos: {x:-8, y:-8}, action: 'move', health: 10, maxHealth: 10, rotation: -Math.PI/2, acts: true }
# A line of ogres overlapping to test child ordering
{id: 'Ogre 1', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:-14, y:0}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Ogre 2', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:-13.5, y:1}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Ogre 3', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:-13, y:2}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Ogre 4', spriteName: 'Ogre Munchkin M', exists: true, pos: {x:-12.5, y:3}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Dying Ogre 1', spriteName: 'Segmented Munchkin', exists: true, pos: {x:-14, y:0}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Dying Ogre 2', spriteName: 'Segmented Munchkin', exists: true, pos: {x:-13.5, y:1}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Dying Ogre 3', spriteName: 'Segmented Munchkin', exists: true, pos: {x:-13, y:2}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
{id: 'Dying Ogre 4', spriteName: 'Segmented Munchkin', exists: true, pos: {x:-12.5, y:3}, action: 'die', health: 5, maxHealth: 10, rotation: 0, acts: true }
# Throw in a ThangType that contains nested MovieClips
{id: 'Fangrider 1', spriteName: 'Ogre Fangrider', exists: true, pos: {x:8, y:8}, action: 'move', health: 20, maxHealth: 20, rotation: 0, acts: true }
{id: 'Fangrider', spriteName: 'Fangrider', exists: true, pos: {x:8, y:8}, action: 'move', health: 20, maxHealth: 20, rotation: 0, acts: true }
]
_.find(world.thangs, {id: 'Tree Will Disappear'}).exists = false
_.find(world.thangs, {id: 'Disappearing Tree'}).exists = false
world.thangMap[thang.id] = thang for thang in world.thangs
spriteBoss.update(true)
# Test that the unrendered, animated sprites aren't showing anything
midRenderExpectations.push([spriteBoss.sprites['NotFROgre'].imageObject.children.length,10,'animated container action'])
for child in spriteBoss.sprites['NotFROgre'].imageObject.children
midRenderExpectations.push([child.children[0].currentFrame, 0, 'animated container action'])
midRenderExpectations.push([spriteBoss.sprites['FROgre'].imageObject.children[0].currentFrame,0,'animated spriteSheet action'])
midRenderExpectations.push([spriteBoss.sprites['FROgre'].imageObject.children[0].paused,true,'animated spriteSheet action'])
midRenderExpectations.push([spriteBoss.sprites['Segmented Ogre'].imageObject.children.length,10,'animated segmented action'])
for child in spriteBoss.sprites['Segmented Ogre'].imageObject.children
midRenderExpectations.push([child.children[0].currentFrame, 0, 'animated segmented action'])
midRenderExpectations.push([spriteBoss.sprites['Singular Ogre'].imageObject.currentFrame,0,'animated singular action'])
midRenderExpectations.push([spriteBoss.sprites['Singular Ogre'].imageObject.paused,true,'animated singular action'])
defaultLayer.once 'new-spritesheet', ->
showMe() # Uncomment to display this world when you run any of these tests.
# showMe() # Uncomment to display this world when you run any of these tests.
done()
beforeEach (done) -> init(done)
@ -166,10 +165,10 @@ describe 'SpriteBoss', ->
it 'orders sprites in the layer based on thang pos.y\'s', ->
container = spriteBoss.spriteLayers.Default.spriteContainer
l = spriteBoss.spriteLayers.Default.spriteContainer.children
i1 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Ogre 1'))
i2 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Ogre 2'))
i3 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Ogre 3'))
i4 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Ogre 4'))
i1 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Dying Ogre 1'))
i2 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Dying Ogre 2'))
i3 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Dying Ogre 3'))
i4 = container.getChildIndex(_.find(container.children, (c) -> c.sprite.thang.id is 'Dying Ogre 4'))
expect(i1).toBeGreaterThan(i2)
expect(i2).toBeGreaterThan(i3)
expect(i3).toBeGreaterThan(i4)

View file

@ -12,8 +12,8 @@ describe 'WebGLLayer', ->
layer.buildAutomatically = false
layer.buildAsync = false
it 'creates containers for animated actions if set to renderStrategy=container', ->
ogreMunchkinThangType.set('renderStrategy', 'container')
it 'creates containers for animated actions if set to spriteType=segmented', ->
ogreMunchkinThangType.set('spriteType', 'segmented')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreMunchkinThangType, {colorConfig: colorConfig})
layer.addCocoSprite(sprite)
@ -21,16 +21,16 @@ describe 'WebGLLayer', ->
key = layer.renderGroupingKey(ogreMunchkinThangType, 'head', colorConfig)
expect(key in sheet.getAnimations()).toBe(true)
it 'creates the container for static actions if set to renderStrategy=container', ->
treeThangType.set('renderStrategy', 'container')
it 'creates the container for static actions if set to spriteType=segmented', ->
treeThangType.set('spriteType', 'segmented')
sprite = new CocoSprite(treeThangType)
layer.addCocoSprite(sprite)
sheet = layer.renderNewSpriteSheet()
key = layer.renderGroupingKey(treeThangType, 'Tree_4')
expect(key in sheet.getAnimations()).toBe(true)
it 'creates animations for animated actions if set to renderStrategy=spriteSheet', ->
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
it 'creates animations for animated actions if set to spriteType=singular', ->
ogreMunchkinThangType.set('spriteType', 'singular')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreMunchkinThangType, {colorConfig: colorConfig})
layer.addCocoSprite(sprite)
@ -38,17 +38,17 @@ describe 'WebGLLayer', ->
key = layer.renderGroupingKey(ogreMunchkinThangType, 'idle', colorConfig)
expect(key in sheet.getAnimations()).toBe(true)
it 'creates animations for static actions if set to renderStrategy=spriteSheet', ->
treeThangType.set('renderStrategy', 'spriteSheet')
it 'creates animations for static actions if set to spriteType=singular', ->
treeThangType.set('spriteType', 'singular')
sprite = new CocoSprite(treeThangType)
layer.addCocoSprite(sprite)
sheet = layer.renderNewSpriteSheet()
key = layer.renderGroupingKey(treeThangType, 'idle')
expect(key in sheet.getAnimations()).toBe(true)
it 'only renders frames used by actions when renderStrategy=spriteSheet', ->
it 'only renders frames used by actions when spriteType=singular', ->
layer.setDefaultActions(['idle']) # uses the move side animation
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
ogreMunchkinThangType.set('spriteType', 'singular')
colorConfig = {team: {hue: 0, saturation: 1, lightness: 0.5}}
sprite = new CocoSprite(ogreMunchkinThangType, {colorConfig: colorConfig})
layer.addCocoSprite(sprite)
@ -121,7 +121,7 @@ describe 'WebGLLayer', ->
expect(layer._renderNewSpriteSheet).toHaveBeenCalled()
it 'recycles *containers* from previous sprite sheets, rather than building repeatedly from raw vector data', ->
treeThangType.set('renderStrategy', 'container')
treeThangType.set('spriteType', 'segmented')
sprite = new CocoSprite(treeThangType)
layer.addCocoSprite(sprite)
spyOn(SpriteBuilder.prototype, 'buildContainerFromStore').and.callThrough()
@ -130,7 +130,7 @@ describe 'WebGLLayer', ->
expect(SpriteBuilder.prototype.buildContainerFromStore.calls.count()).toBe(1)
it '*does not* recycle *containers* from previous sprite sheets when the resolutionFactor has changed', ->
treeThangType.set('renderStrategy', 'container')
treeThangType.set('spriteType', 'segmented')
sprite = new CocoSprite(treeThangType)
layer.addCocoSprite(sprite)
spyOn(SpriteBuilder.prototype, 'buildContainerFromStore').and.callThrough()
@ -140,7 +140,7 @@ describe 'WebGLLayer', ->
expect(SpriteBuilder.prototype.buildContainerFromStore.calls.count()).toBe(2)
it 'recycles *animations* from previous sprite sheets, rather than building repeatedly from raw vector data', ->
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
ogreMunchkinThangType.set('spriteType', 'singular')
sprite = new CocoSprite(ogreMunchkinThangType)
layer.addCocoSprite(sprite)
numFrameses = []
@ -156,7 +156,7 @@ describe 'WebGLLayer', ->
expect(SpriteBuilder.prototype.buildMovieClip.calls.count()).toBe(5)
it '*does not* recycles *animations* from previous sprite sheets when the resolutionFactor has changed', ->
ogreMunchkinThangType.set('renderStrategy', 'spriteSheet')
ogreMunchkinThangType.set('spriteType', 'singular')
sprite = new CocoSprite(ogreMunchkinThangType)
layer.addCocoSprite(sprite)
spyOn(SpriteBuilder.prototype, 'buildMovieClip').and.callThrough()

View file

@ -1,151 +0,0 @@
WebGLLayer = require 'lib/surface/WebGLLayer'
WebGLSprite = require 'lib/surface/WebGLSprite'
CocoSprite = require 'lib/surface/CocoSprite'
ThangType = require 'models/ThangType'
treeThangType = new ThangType(require 'test/app/fixtures/tree1.thang.type')
ogreMunchkinThangType = new ThangType(require 'test/app/fixtures/ogre-munchkin-m.thang.type')
ogreFangriderThangType = new ThangType(require 'test/app/fixtures/ogre-fangrider.thang.type')
describe 'WebGLSprite', ->
webGLSprite = null
showMe = ->
canvas = $('<canvas width="600" height="400"></canvas>').css('position', 'absolute').css('index', 1000).css('background', 'white')
$('body').append(canvas)
stage = new createjs.SpriteStage(canvas[0])
stage.addChild(webGLSprite)
ticks = 0
listener = {
handleEvent: ->
return if ticks >= 100
ticks += 1
webGLSprite.tick(arguments[0].delta)
stage.update()
}
createjs.Ticker.addEventListener "tick", listener
describe 'with Ogre Munchkin ThangType', ->
beforeEach ->
layer = new WebGLLayer()
layer.buildAutomatically = false
layer.buildAsync = false
ogreMunchkinThangType.markToRevert()
ogreMunchkinThangType.set('renderStrategy', 'container')
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 gotoAndPlay, gotoAndStop, and paused like a MovieClip or Sprite', ->
webGLSprite.gotoAndPlay('move_fore')
expect(webGLSprite.baseMovieClip).toBeDefined()
expect(webGLSprite.paused).toBe(false)
webGLSprite.gotoAndStop('move_fore')
expect(webGLSprite.paused).toBe(true)
showMe()
it 'has a tick function which moves the animation forward', ->
webGLSprite.gotoAndPlay('attack')
webGLSprite.tick(100) # one hundred milliseconds
expect(webGLSprite.baseMovieClip.currentFrame).toBe(webGLSprite.framerate*100/1000)
it 'will interpolate between frames of a custom frame set', ->
webGLSprite.gotoAndPlay('littledance')
webGLSprite.tick(1000)
expect(webGLSprite.baseMovieClip.currentFrame).toBe(6)
webGLSprite.tick(1000)
expect(webGLSprite.baseMovieClip.currentFrame).toBe(2)
webGLSprite.tick(500)
expect(webGLSprite.baseMovieClip.currentFrame).toBe(4)
webGLSprite.tick(500)
expect(webGLSprite.baseMovieClip.currentFrame).toBe(6)
it 'emits animationend for animations where loops is false and there is no goesTo', ->
fired = false
webGLSprite.gotoAndPlay('onestep')
webGLSprite.on('animationend', -> fired = true)
webGLSprite.tick(1000)
expect(fired).toBe(true)
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}}
sprite = new CocoSprite(ogreFangriderThangType, {colorConfig: colorConfig})
layer.addCocoSprite(sprite)
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 'synchronizes animations with child movie clips properly', ->
webGLSprite.gotoAndPlay('die')
webGLSprite.tick(100) # one hundred milliseconds
expectedFrame = webGLSprite.framerate*100/1000
expect(webGLSprite.currentFrame).toBe(expectedFrame)
for movieClip in webGLSprite.childMovieClips
expect(movieClip.currentFrame).toBe(expectedFrame)
it 'does not include shapes from the original animation', ->
webGLSprite.gotoAndPlay('attack')
webGLSprite.tick(230)
for child in webGLSprite.children
expect(_.isString(child)).toBe(false)
it 'maintains the right number of shapes', ->
webGLSprite.gotoAndPlay('idle')
lengths = []
for i in _.range(10)
webGLSprite.tick(10)
expect(webGLSprite.children.length).toBe(20)
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')