engine/GameEntity.bs

' @module BGE
namespace BGE



  ' Every thing (character, player, object, etc) in the game should extend this class.
  ' This class has a number of empty methods that are designed to be overridden in subclasses.
  ' For example, override `onInput()` to handle input event, and `onUpdate()` to handle updating each frame
  class GameEntity
    '-----Constants-----

    ' Constant - name of this Entity
    name as string
    ' Constant - Unique Id
    id as string

    private game as Game

    ' -----Variables-----

    ' Is this GameEntity enabled
    enabled as boolean = true
    ' Does this entity persist across room changes?
    persistent as boolean = false
    ' When the game is paused, does this entity pause too?
    pauseable as boolean = true
    ' zIndex is used when handling draw order in the game loop - lower zIndex is drawn first
    zIndex as integer = 0
    ' x position of where this entity is in game world
    x as float = 0.0
    ' y position of where this entity is in game world
    y as float = 0.0
    ' Speed in x/horizontal direction
    xspeed as float = 0.0
    ' Speed in y/vertical direction
    yspeed as float = 0.0

    ' Rotation of entity - applies to all images
    rotation = 0

    ' The colliders for this entity by name
    colliders as object = {}
    ' The array of images to draw for this entity
    images as object = []
    ' Associative array of images by name
    imagesByName as object = {}

    ' Game Entities can be tagged with any number of tags so they can be easily identified (e.g. "enemy", "wall", etc.)
    tagsList = new TagList()


    ' Creates a new GameEntity
    '
    ' @param {Game} game - The game engine that this entity is going to be assigned to
    ' @param {object} [args={}] Any extra properties to be added to this entity
    function new(game as Game, args = {} as object)
      m.game = game
      m.id = m.game.getNextGameEntityId()
      m.append(args)
    end function


    ' Is this still a valid entity?
    '
    ' @return {boolean} - true if still valid
    function isValid() as boolean
      return m.id <> invalid
    end function


    ' Marks this entity as invalid, so it will be cleared/destroyed at the end of the frame
    private function invalidate() as void
      m.id = invalid
    end function


    ' Method to be called when this entity is added to a Game.
    ' Override in subclass
    '
    ' @param {object} args
    function onCreate(args as object) as void
    end function


    ' Method for handling any updates based on time since previous frame
    '
    ' @param {float} deltaTime - milliseconds since last frame
    function onUpdate(deltaTime as float) as void
    end function



    ' Method for processing all collisions
    '
    ' @param {Collider} collider - the collider of this entity that collided
    ' @param {Collider} otherCollider - the collider of the other entity in the collision
    ' @param {GameEntity} otherEntity - the entity that owns the other collider
    function onCollision(collider as Collider, otherCollider as Collider, otherEntity as GameEntity) as void
    end function


    ' Method called each frame before drawing any images of this entity
    '
    ' @param {object} canvas - the canvas images will be drawn to
    function onDrawBegin(canvas as object) as void
    end function


    ' Method called each frame after drawing all images of this entity
    '
    ' @param {object} canvas - the canvas images were drawn to
    function onDrawEnd(canvas as object) as void
    end function


    ' Method to process input per frame
    '
    ' @param {GameInput} input - GameInput object for the last frame
    function onInput(input as GameInput) as void
    end function


    ' Method to process an ECP keyboard event
    ' @see  https://developer.roku.com/en-ca/docs/developer-program/debugging/external-control-api.md
    '
    ' @param {integer} char
    function onECPKeyboard(char as integer) as void
    end function


    ' Method to process an External Control Protocol event
    ' @see  https://developer.roku.com/en-ca/docs/references/brightscript/events/roinputevent.md
    '
    ' @param {object} data
    function onECPInput(data as object) as void
    end function


    ' Method to handle audio events
    ' @see  https://developer.roku.com/en-ca/docs/references/brightscript/events/roaudioplayerevent.md
    '
    ' @param {object} msg - roAudioPlayerEvent
    function onAudioEvent(msg as object) as void
    end function


    ' Called when the game pauses
    '
    function onPause() as void
    end function


    ' Called when the game unpauses
    '
    ' @param {integer} pauseTimeMs - The number of milliseconds the game was paused
    function onResume(pauseTimeMs as integer) as void
    end function


    ' Called on url event
    ' @see  https://developer.roku.com/en-ca/docs/references/brightscript/events/rourlevent.md
    '
    ' @param {object} msg - roUrlEvent
    function onUrlEvent(msg as object) as void
    end function


    ' General purpose event handler for in-game events.
    '
    ' @param {string} eventName - Event name that describes the event type
    ' @param {object} data - Any extra data to go along with the event
    function onGameEvent(eventName as string, data as object) as void
    end function



    ' Method called when the current room changes.
    ' This method is only called when the entity is marked as `persistant`,
    ' otherwise entities are destroyed on room changes.
    '
    ' @param {Room} newRoom - The next room
    function onChangeRoom(newRoom as Room) as void
    end function


    ' Method called when this entity is destroyed
    '
    function onDestroy() as void
    end function


    ' Adds a circle collider to this entity
    '
    ' @param {string} colliderName - Name of the collider (only one collider with the same name can be added)
    ' @param {float} radius - radius of the circle
    ' @param {float} [offset_x=0] - horizontal offset from entity position of centre of the circle
    ' @param {float} [offset_y=0] - vertical offset from entity position of centre of the circle
    ' @param {boolean} [enabled=true] - is this collider enabled?
    ' @return {object}  - the collider that was added, or `invalid` if it could not be added
    function addCircleCollider(colliderName as string, radius as float, offset_x = 0 as float, offset_y = 0 as float, enabled = true as boolean) as object
      collider = new CircleCollider(colliderName, {
        enabled: enabled,
        radius: radius,
        offset_x: offset_x,
        offset_y: offset_y
      })
      return m.addCollider(collider)
    end function


    ' Adds a rectangle collider to this entity
    '
    ' @param {string} colliderName - Name of the collider (only one collider with the same name can be added)
    ' @param {float} width - Width of rectangle
    ' @param {float} height - Height of rectangle
    ' @param {float} [offset_x=0] - horizontal offset from entity position of top left point of rectangle
    ' @param {float} [offset_y=0] - vertical offset from entity position of top left point of rectangle
    ' @param {boolean} [enabled=true] - is this collider enabled?
    ' @return {object}  - the collider that was added, or `invalid` if it could not be added
    function addRectangleCollider(colliderName as string, width as float, height as float, offset_x = 0 as float, offset_y = 0 as float, enabled = true as boolean) as object
      collider = new RectangleCollider(colliderName, {
        enabled: enabled,
        offset_x: offset_x,
        offset_y: offset_y,
        width: width,
        height: height
      })
      return m.addCollider(collider)
    end function


    ' Adds a collider that has already been constructed
    '
    ' @param {Collider} collider - the collider to add  (only one collider with the same name can be added)
    ' @return {Collider}  - the collider that was added, or `invalid` if it could not be added
    function addCollider(collider as Collider) as Collider
      colliderName = collider.name
      collider.setupCompositor(m.game, m.name, m.id, m.x, m.y)
      if m.colliders[colliderName] = invalid
        m.colliders[colliderName] = collider
      else
        print "addCollider() - Collider Name Already Exists: " + colliderName
        return invalid
      end if
      return collider
    end function


    ' Gets a collider based on its name
    '
    ' @param {string} colliderName - The name of the collider to find
    ' @return {Collider} - the collider (if found) otherwise `invalid`
    function getCollider(colliderName as string) as Collider
      if m.colliders.DoesExist(colliderName)
        return m.colliders[colliderName]
      else
        return invalid
      end if
    end function


    ' Removes a collider by its name
    '
    ' @param {string} colliderName - the name of the collider to remove
    function removeCollider(colliderName as string) as void
      if m.colliders[colliderName] <> invalid
        if type(m.colliders[colliderName].compositorObject) = "roSprite"
          m.colliders[colliderName].compositorObject.Remove()
        end if
        m.colliders.Delete(colliderName)
      end if
    end function


    ' Remove all colliders from this entity
    '
    function clearAllColliders() as void
      if invalid <> m.colliders
        for each colliderKey in m.colliders
          m.removeCollider(colliderKey)
        end for
      end if
    end function




    ' Adds a basic image (non-animated) to be drawn for this entity
    '
    ' @param {string} imageName - Name of the image
    ' @param {object} region - an `roRegion` of a bitmap to draw
    ' @param {object} [args={}] - any extra properties to set (e.g. offset_x, offset_y, rotation, scale_x, scale_y, etc.)
    ' @param {integer} [insertPosition=-1] - the position/order in the images array where the image should be added (defaults to being added at the end)
    ' @return {Image} - The image object that was added, or `invalid` if there was an error
    function addImage(imageName as string, region as object, args = {} as object, insertPosition = -1 as integer) as Image
      imageObject = new Image(m, m.game.getCanvas(), region, args) 'm as first arg
      return m.addImageObject(imageName, imageObject, insertPosition)
    end function


    ' Adds a animated image to be drawn for this entity. Animated images cycle through regions of a bitmap (e.g. spritesheet)
    '
    ' @param {string} imageName - Name of the image
    ' @param {object} regions - an array of `roRegion` of a bitmap to draw
    ' @param {object} [args={}] - any extra properties to set (e.g. offset_x, offset_y, rotation, scale_x, scale_y, etc.)
    ' @param {integer} [insertPosition=-1] - the position/order in the images array where the image should be added (defaults to being added at the end)
    ' @return {AnimatedImage} - The image object that was added, or `invalid` if there was an error
    function addAnimatedImage(imageName as string, regions as object, args = {} as object, insertPosition = -1 as integer) as AnimatedImage
      imageObject = new AnimatedImage(m, m.game.getCanvas(), regions, args)
      return m.addImageObject(imageName, imageObject, insertPosition)
    end function

    '
    ' @param {object} regions - an array of `roRegion` of a bitmap to draw

    ' Adds a Sprite to be drawn for this entity. Sprites can have specific animations configured buy choosing series of cells from a sprite sheet
    '
    ' @param {string} imageName - Name of the image
    ' @param {object} bitmap - the bitmap object to use for teh SpriteSheet (e.g response from game.getBitmap("bitmap_name"))
    ' @param {integer} cellWidth - the height in pixels of a dingle cell in the sprite
    ' @param {integer} cellHeight - the height in pixels of a dingle cell in the sprite
    ' @param {object} [args={}] - any extra properties to set (e.g. offset_x, offset_y, rotation, scale_x, scale_y, etc.)
    ' @param {integer} [insertPosition=-1] - the position/order in the images array where the image should be added (defaults to being added at the end)
    ' @return {Sprite} - The image object that was added, or `invalid` if there was an error
    function addSprite(imageName as string, spriteSheet as object, cellWidth as integer, cellHeight as integer, args = {} as object, insertPosition = -1 as integer) as Sprite
      imageObject = new Sprite(m, m.game.getCanvas(), spriteSheet, cellWidth, cellHeight, args)
      return m.addImageObject(imageName, imageObject, insertPosition)
    end function


    ' Adds any Image object to this entity
    '
    ' @param {string} imageName - Name of the image
    ' @param {Drawable} imageObject - The image to be added
    ' @param {integer} [insertPosition=-1] - the position/order in the images array where the image should be added (defaults to being added at the end)
    ' @return {Drawable} - The image object that was added, or `invalid` if there was an error
    function addImageObject(imageName as string, imageObject as Drawable, insertPosition = -1 as integer) as Drawable
      imageObject.name = imageName

      if m.getImage(imageObject.name) <> invalid
        print "addImageObject() - An image named - " + imageObject.name + " - already exists"
        return invalid
      end if

      m.imagesByName[imageObject.name] = imageObject
      if insertPosition = -1
        m.images.Push(imageObject)
      else if insertPosition = 0
        m.images.Unshift(imageObject)
      else if insertPosition < m.images.Count()
        BGE.ArrayInsert(m.images, insertPosition, imageObject)
      else
        m.images.Push(imageObject)
      end if

      return imageObject
    end function


    ' Gets an image by its name from the lookup table
    '
    ' @param {string} imageName - Name of image to get
    ' @return {Drawable}
    function getImage(imageName as string) as Drawable
      return m.imagesByName[imageName]
    end function


    ' Removes an image from the entity
    '
    ' @param {string} imageName - Name of image to remove
    function removeImage(imageName as string) as void
      m.imagesByName.Delete(imageName)
      if m.images.Count() > 0
        for i = 0 to m.images.Count() - 1
          if m.images[i].name = imageName
            m.images.Delete(i)
            exit for
          end if
        end for
      end if
    end function


    ' TODO: work on statics
    '
    ' @param {string} staticVariableName
    ' @return {dynamic}
    function getStaticVariable(staticVariableName as string) as dynamic
      if m.game.Statics.DoesExist(m.name) and m.game.Statics[m.name].DoesExist(staticVariableName)
        return m.game.Statics[m.name][staticVariableName]
      else
        return invalid
      end if
    end function


    ' TODO: work on statics
    '
    ' @param {string} staticVariableName
    ' @param {dynamic} staticVariableValue
    function setStaticVariable(staticVariableName as string, staticVariableValue as dynamic) as void
      if m.game.Statics.doesExist(m.name)
        m.game.Statics[m.name][staticVariableName] = staticVariableValue
      end if
    end function


    ' TODO: work on Interfaces
    '
    ' @param {string} interfaceName
    function addInterface(interfaceName as string) as void
      interfaceObj = {owner: m}
      m.game.Interfaces[interfaceName](interfaceObj)
      m[interfaceName] = interfaceObj
    end function


    ' TODO: work on Interfaces
    '
    ' @param {string} interfaceName
    ' @return {boolean}
    function hasInterface(interfaceName as string) as boolean
      return (m.doesExist(interfaceName) and m[interfaceName] <> invalid)
    end function

  end class
end namespace