engine/drawables/Sprite.bs

' @module BGE
namespace BGE


  class SpriteAnimation
    ' Name of the animation
    name as string
    ' Frames per second the animation should play at
    frameRate as integer
    ' Array of the regions of the each cell of this animation
    frameList as object
    ' Play mode for the sprite: loop, forward, reverse, pingpong
    playMode as string = "loop"


    '
    ' CReate a new SpriteAnimation
    '
    ' @param {string} name Name of the animation
    ' @param {object} frameList Wither an array of cell indexes, or an object {startFrame, frameCount}
    ' @param {integer} frameRate Frames per second the animation should play at
    ' @param {string} [playMode="loop"] Play mode for the sprite: loop, forward, reverse, pingpong
    function new(name as string, frameList as object, frameRate as integer, playMode = "loop" as string) as void
      m.name = name
      m.frameRate = frameRate
      m.playMode = lcase(playMode)
      m.setFrameList(frameList)
    end function

    private function setFrameList(frameList as object) as void
      m.frameList = []
      if invalid <> frameList.startFrame and invalid <> frameList.frameCount and frameList.startFrame >= 0 and frameList.frameCount >= 0
        for i = frameList.startFrame to frameList.startFrame + frameList.frameCount - 1
          m.frameList.push(i)
        end for
      else if 0 < frameList.count()
        m.frameList = frameList
      end if
    end function
  end class


  class Sprite extends AnimatedImage

    ' roBitmap to pick cells from
    spriteSheet as object
    ' Width of each animation cell in the sprite image in pixels
    cellWidth as integer
    ' Height of each animation cell in the sprite image in pixels
    cellHeight as integer

    ' Lookup map of animation name -> SpriteAnimation object
    animations as object = {}

    ' The current animation being played
    activeAnimation as SpriteAnimation = invalid

    function new(owner as GameEntity, canvasBitmap as object, spriteSheet as object, cellWidth as integer, cellHeight as integer, args = {} as object)
      super(owner, canvasBitmap, invalid, args)
      m.spriteSheet = spriteSheet
      m.cellWidth = cellWidth
      m.cellHeight = cellHeight
      m.width = cellWidth
      m.height = cellHeight
      m.setCellRegions()
      m.append(args)
    end function

    private function setCellRegions() as void
      cols = cint(m.spriteSheet.getWidth() / m.cellWidth)
      rows = cint(m.spriteSheet.getHeight() / m.cellHeight)
      m.regions = []
      for row = 0 to rows - 1
        for col = 0 to cols - 1

          cellRegion = CreateObject("roRegion", m.spriteSheet, col * m.cellWidth, row * m.cellHeight, m.cellWidth, m.cellHeight)
          m.regions.push(cellRegion)
        end for
      end for
    end function


    ' Apply a pre-translation to set the pivot point for the sprite cell
    '
    ' @param {float} x
    ' @param {float} y
    function applyPreTranslation(x as float, y as float) as void
      for each region in m.regions
        region.SetPreTranslation(x, y)
      end for
    end function

    function addAnimation(name as string, frameList as object, frameRate as integer, playMode = "loop" as string) as object
      if invalid = m.animations[name]
        spriteAnim = new SpriteAnimation(name, frameList, frameRate, playMode)
        m.animations[name] = spriteAnim
        return spriteAnim
      end if
      print `addAnimation() - An animation named '${name}' already exists`
      return invalid
    end function

    ' Play an animation from the set of animations in this SpriteSheet
    '
    ' @param {string} animationName
    function playAnimation(animationName as string) as void
      spriteAnim = m.animations[animationName]
      if invalid <> spriteAnim
        m.animationDurationMs = spriteAnim.frameList.count() * 1000.0 / spriteAnim.frameRate
      else
        print `playAnimation() - No animation named '${animationName}' exists`
      end if
      m.activeAnimation = spriteAnim
    end function


    protected override function getCellDrawIndex() as integer
      if invalid = m.activeAnimation
        return -1
      end if
      frameCount = m.activeAnimation.frameList.Count()
      currentTimeMs = m.animationTimer.TotalMilliseconds()

      if currentTimeMs > m.animationDurationMs
        if "loop" = m.activeAnimation.playMode
          currentTimeMs -= m.animationDurationMs
          m.animationTimer.RemoveTime(m.animationDurationMs)
        else if "forward" = m.activeAnimation.playMode or "reverse" = m.activeAnimation.playMode
          currentTimeMs = m.animationDurationMs
        else if "pingpong" = m.activeAnimation.playMode
          if currentTimeMs > (2 * m.animationDurationMs)
            currentTimeMs -= m.animationDurationMs
            m.animationTimer.RemoveTime(m.animationDurationMs * 2)
          else
            currentTimeMs = m.animationDurationMs - currentTimeMs
          end if
        end if
      end if
      tweenFunc = m.tweensReference[m.animationTween]
      if "reverse" = m.activeAnimation.playMode
        index = cint(tweenFunc(frameCount - 1, 0, currentTimeMs, m.animationDurationMs))
      else
        index = cint(tweenFunc(0, frameCount - 1, currentTimeMs, m.animationDurationMs))
      end if

      if index > frameCount - 1
        index = frameCount - 1
      else if index < 0
        index = 0
      end if
      return m.activeAnimation.frameList[index]
    end function

  end class

end namespace