I'm working on a Pixi.js game in Coffeescript that mainly focuses on rotating hexes. Everything about the rotation is working correctly in terms of animating the rotation and the underlying board state recognizing the rotation. However, for certain hexes on the board (not all of them), rotating them causes other (not all) sprites on the stage to "shake". They momentarily shift to the side and then shift back again when the rotation finishes. I've tried searching for the problem all over; the one result I found was someone with a similar issue on a different site but had no answers: here.
I'm not sure where to start in terms of diagnosing the bug myself. If it's a issue with the canvas I could look there, but it may also be a Pixi issue. Here's a gif of the issue. When I click on the first hex (on right), many of the lines shake. When I click on the second hex (left), they do not.
The project is quite large at this point in terms of code. Here's the animation function, and helper methods:
## The frame count the window is on ##
@count = 0
### Moves the connector to the correct lit layer ###
@toLit = (connector) ->
try
@colorContainers[connector.color].unlit.removeChild(connector.panel)
catch
if @typeIsArray connector.color
if connector.color.length > 0
c = connector.color[0].toUpperCase()
else
c = Color.asString(Color.NONE).toUpperCase()
else
c = connector.color.toUpperCase()
if c is Color.asString(Color.NONE).toUpperCase()
@toUnlit(connector) ## Prevent lighting of color.NONE
else
@colorContainers[c.toUpperCase()].lit.addChild(connector.panel)
connector.linked = true
return
### Moves the connector to the correct unlit layer ###
@toUnlit = (connector) ->
try
@colorContainers[connector.color].lit.removeChild(connector.panel)
catch
if @typeIsArray connector.color
if connector.color.length > 0
c = connector.color[0]
else
c = Color.asString(Color.NONE)
else
c = connector.color
if connector.hex? and connector.hex instanceof Crystal
@colorContainers[Color.asString(Color.NONE).toUpperCase()].unlit.addChild(connector.panel)
else
@colorContainers[c.toUpperCase()].unlit.addChild(connector.panel)
connector.linked = false
return
### Creates a frame offset for the each color ###
@colorOffset = {}
for c in Color.values()
if not isNaN(c)
c = Color.fromString(c).toUpperCase()
else
c = c.toUpperCase()
@colorOffset[c] = Math.random() + 0.5
### Updates the pulse filter that controls lighting effects ###
@calcPulseFilter = (count) ->
for col, val of @colorContainers
pulse = val.lit.filters[0]
cont = (count + val.lit.pulseOffset)/val.lit.pulseLength
m = pulse.matrix
m[0] = Math.abs(Math.sin(cont * 2 * Math.PI)) * 0.5 + 0.5
m[5] = Math.abs(Math.sin(cont * 2 * Math.PI)) * 0.5 + 0.5
m[10] = Math.abs(Math.sin(cont * 2 * Math.PI)) * 0.5 + 0.5
m[15] = Math.abs(Math.sin(cont * 2 * Math.PI)) * 0.25 + 0.75
pulse.matrix = m
for cont in @goalContainer.children
if cont.children.length >= 2 and cont.filters.length >= 2
pulse = cont.filters[1]
correspondCont = @colorContainers[cont.children[0].color.toUpperCase()].lit
c = (count + correspondCont.pulseOffset)/correspondCont.pulseLength
m = pulse.matrix
if parseInt(cont.children[1].text.substring(0, 1)) >= parseInt(cont.children[1].text.substring(2))
m[0] = Math.abs(Math.sin(c * 2 * Math.PI)) * 0.5 + 0.5
m[5] = Math.abs(Math.sin(c * 2 * Math.PI)) * 0.5 + 0.5
m[10] = Math.abs(Math.sin(c * 2 * Math.PI)) * 0.5 + 0.5
m[15] = Math.abs(Math.sin(c * 2 * Math.PI)) * 0.25 + 0.75
else
m[0] = 1
m[5] = 1
m[10] = 1
m[15] = 1
pulse.matrix = m
return
### The animation function. Called by pixi and requests to be recalled ###
@animate = () ->
## Color animation
window.count += 1; ## Frame count
@calcPulseFilter(window.count)
rotSpeed = 1/5
tolerance = 0.000001 ## For floating point errors - difference below this is considered 'equal'
radTo60Degree = 1.04719755 ## 1 radian * this coefficient = 60 degrees
if (@BOARD?)
## Update text on goal
curLit = @BOARD.crystalLitCount()
goalContainer = @menu.children[@goalContainerIndex]
isWin = true ## True if this user has won - every goal set.
for pan in goalContainer.children
for spr in pan.children
if spr instanceof PIXI.Text and spr.color.toUpperCase() of curLit
spr.setText(curLit[spr.color.toUpperCase()] + spr.text.substring(1))
if curLit[spr.color.toUpperCase()] < parseInt(spr.text.substring(2))
isWin = false
if isWin and (not @winContainer?) and @showWinContainer
@gameOn = false
@makeWinGameContainer()
for h in @BOARD.allHexes()
##Update lighting of all hexes
if h.isLit().length > 0 and not h.backPanel.children[0].lit
h.backPanel.children[0].lit = true
if not (h instanceof Prism)
@toLit(h.backPanel.spr)
if h.isLit().length is 0 and h.backPanel.children[0].lit
h.backPanel.children[0].lit = false
if not (h instanceof Prism)
@toUnlit(h.backPanel.spr)
hLit = h.isLit()
if h instanceof Prism
## Adjust opacity of cores
for col, core of h.cores
if col.toLowerCase() not in hLit and core.alpha > 0
core.alpha = 0
else if col.toLowerCase() in hLit and core.alpha is 0
core.alpha = 0.75
nS = h.getNeighborsWithBlanks()
## Fixing lighting of connectors
for panel in h.colorPanels
col = panel.color.toLowerCase()
for connector in panel.children
for side in connector.sides
n = nS[side]
if n? and col in hLit and n.colorOfSide(n.indexLinked(h)) is col and not connector.linked
@toLit(connector)
for nPanel in n.colorPanels
for nConnector in nPanel.children
for nSide in nConnector.sides
if nSide is n.indexLinked(h) and not nConnector.linked
@toLit(nConnector)
else if connector.linked and col not in hLit
@toUnlit(connector)
if n?
for nPanel in n.colorPanels
for nConnector in nPanel.children
for nSide in nConnector.sides
if nSide is n.indexLinked(h) and not nConnector.linked
@toUnlit(nConnector)
### Rotation of a prism - finds a prism that wants to rotate and rotates it a bit. ###
### If this is the first notification that this prism wants to rotate, stops providing light. ###
### If the prism is now done rotating, starts providing light again ###
if h instanceof Prism and h.currentRotation isnt h.targetRotation
if h.canLight
h.canLight = false
h.light()
inc =
if (h.targetRotation - h.prevRotation) >= 0
rotSpeed
else
-rotSpeed
h.backPanel.rotation += inc * radTo60Degree
h.currentRotation += inc
for value in h.colorPanels
value.rotation += inc * radTo60Degree
if Math.abs(h.targetRotation - h.currentRotation) < tolerance
inc = (h.targetRotation - h.currentRotation)
h.backPanel.rotation += inc * radTo60Degree
h.currentRotation += inc
for value in h.colorPanels
value.rotation += inc * radTo60Degree
## Update side index of each sprite
for spr in value.children
newSides = []
for side in spr.sides
newSides.push((side + (h.currentRotation - h.prevRotation)) %% Hex.SIDES)
spr.sides = newSides
h.prevRotation = h.currentRotation
h.canLight = true
h.light()
### Spark and crystal color changing ###
if (h instanceof Spark or h instanceof Crystal) and h.toColor isnt ""
col = if (not isNaN(h.toColor))
Color.asString(h.toColor).toUpperCase()
else
h.toColor.toUpperCase()
h.backPanel.spr.color = col
@toLit(h.backPanel.spr)
h.toColor = ""
requestAnimFrame(animate )
@renderer.render(@stage)
return
If there are any other parts of the code you think are relevant to the bug let me know and I'll put those parts up. Any help or suggestions would be really great!