RBGLSprites Documentation

The RGBLSprites library provides a replacement for the REALbasic SpriteSurface
control that was recently deprecated and will eventually be removed from
REALbasic.

RGBLSprites is built on top of the RBGL library, which provides access to
cross-plaform, hardware-accelerated drawing in REALbasic via OpenGL declares.

Requirements

RBGLSprites should run on any Mac OS X 10.4 or higher system and any Windows
XP or Vista system.

RBGLSprites has been tested on RB2007r5 and RB2008r4. Earlier or later
versions may be supported but have not been tested.

Installation

To use RBGL in your project, first copy and paste the RBGL module from the
rbgl sprites.xml project into your own, then copy and paste the RBGLSprites
module. If you are upgrading from an earlier version, delete your existing
RBGL and RBGLSprites modules first.

Classes

RBGLSpriteSurface
-------------
-------------

The RBGLSpriteSurface class replaces the REALbasic SpriteSurface control.
Unlike the original SpriteSurface, it is a subclass of Canvas, and so
inherits all of its methods.

RBGLSpriteSurface Properties
--------------------------

ClickToStop as Boolean

  If this property is set to true, clicking the mouse while the
  RBGLSpriteSurface is running will cause it to stop. True by default. Unlike
  the original SpriteSurface, you cannot set this value in the IDE, you will
  have to set it in code if you wish to change it's value.

FrameSpeed as Integer

  This property is slightly misleadingly named - it should really be called
  "FramePeriod". It represents the length of a frame in ticks (60ths of a
  second). A value of 1 will yield a framespeed of 60fps, a value of 2 gives
  30fps, and so on. A value of zero means that the surface will render each
  frame as quickly as possible. This value currently defaults to 2, as this
  was the common setting for a lot of original SpriteSurface projects. Unlike
  the original SpriteSurface, you cannot set this value in the IDE, you will
  have to set it in code if you wish to change it's value.
  
ScrollX as Integer

  The horizontal scroll offset of the surface in pixels (read and write
  property).

ScrollY as Integer

  The vertical scroll offset of the surface in pixels (read and write property).

SurfaceTop as Integer
SurfaceLeft as Integer
SurfaceHeight as Integer
SurfaceWidth as Integer

  These properties were originally ued to specify the dimensions of the
  SpriteSurface when launched, but in the RBGLSpriteSurface they are completely
  ignored and retained only for backwards compatibility. Use the Top, Left,
  Width and Height properties of the RBGLSpriteSurface to control its dimensions
  instead, just like an ordinary Canvas control.
  
TileWidth as Integer
TileHeight as Integer

  In the original SpriteSurface, the size of background tiles was hard-coded to
  64x64 pixels. RBGLSpriteSurface uses this as the default, but with the
  TileWidth and TileHeight properties you can set the tile dimensions to
  anything you like. If you are not using the tiles, you may ge a slight
  performance boost from setting the tile width/height to match the
  RBGLSpriteSurface dimensions as it will not have to call the PaintTile
  event as often.

Backdrop as Picture

  This image will be tiled over the background of your RBGLSpriteSurface. It can
  be any size - it has no relationship to the TileWidth and TileHeight
  properties. It is drawn before the PaintTile events are called, so will appear
  underneath any other drawing that you do in the surface.
  
SpriteCount as Integer

  The number of active sprites attached to the surface. This number does not
  include sprites that have been marked for deletion but not yet removed.
  

RBGLSpriteSurface Methods
-------------------------

Attach (S as RBGLSprite)

  Use this method to attach a sprite to the surface. Note that a sprite may be
  assoicated only with a single surface at any given time, so if the sprite is
  already attached to another surface, it will be removed from that surface
  first.
  
Close

  Terminate execution of any current run event. The behaviour is currently
  identical to the Stop method.
  
KeyTest (KeyCode as Integer)

  Test if the specified key was pressed during the current frame. This method
  is equivalent to calling Keyboard.AsyncKeyDown().
  
NewSprite (Image as Picture, X as Integer, Y as Integer) as RBGLSprite

  Creates a new RBGLSprite with the specified position and image, attaches it to
  the RBGLSpriteSurface, then returns it. It is probably better to create your
  own RBGLSprite subclasses for new projects and instantiate them yourself
  using the new operator, but this method is retained for backwards
  compatibility.
  
PaintTile (X as Integer, Y As Integer)

  In the original SpriteSurface this would force the specified tile to be
  redrawn in the next frame. Currently the RBGLSpriteSurface redraws every
  visible tile each frame anyway, so this method does nothing. It is retained
  for backwards compatibility, and may be used for something in a future
  release.

Remove (S as RBGLSprite)

  Detaches the specified sprite from the surface.

Run ([Async as Boolean], [Priority As Integer])

  Runs the RBGLSpriteSurface event loop, causing the surface to be repeatedly
  redrawn and the collision and update events to be called for each frame. By
  default the Run method executes syncronously, which means that execution halts
  until the Stop or Close methods are called, or the user interrupts the
  execution some other way, such as by clicking. The RBGLSpriteSurface run
  method features an enhancement over the original however: The optional Async
  argument allows you to run the surface asyncronously using a thread. On
  Windows a timer is used by default instead of a thread because the program
  will crash on quit if the thread is still running. The timer performance is
  slightly worse than the thread, so you can override this with
  a constant if you need the higher performance and are prepared to work around
  the crashing issue. If the async property is set to true then the Run method
  will return immediately instead of blocking until the loop terminates. The
  optional Priority parameter can be used to control the thread priority (see
  the Thread documentation in the language reference for more detail). The
  Priority parameter is ignored when using a timer.
  
Scroll (dx as Integer, dy as Integer)

  Scrolls the surface by a given horizontal and vertical amount. This is
  equivalent to setting the ScrollX and ScrollY properties manually, but note
  that dx and dy are relative to the current scroll offsets, not absolute.
  
Stop ()

  Stops the execution of the Run loop. Currently this is the same as calling
  Close ().
  
Update ()

  Forces the NextFrame, Collision and PainTile events to be called a single
  time, prior to redrawing all the sprites. This method can be used to implement
  your own event loop instead of using the Run () method.
  
Clear ()

  Removes all sprites from the surface.
  

RBGLSpriteSurface Events
------------------------

Open ()

  Called when the RGLSpriteSurface is first created. This is a good opportunity
  to set properties such as FrameSpeed and disable ClickToStop.
  
Collision (S1 as RBGLSprite, S2 as RBGLSprite)

  This is called every time two sprites with suitable group values are
  deemed to have collided. You may delete or add sprites to the surface during
  the execution of this method - restrictions in the previous versions of this
  library have been eliminated. Note that collision logic may be better placed
  in the Collision event of the RBGLSprite itself (new to vesion 1.1).
  
NextFrame (dt as Double)

  This is called before all collision tests are performed and the view is
  redrawn. Game logic such as reading user input, or calculating AI decisions or
  physics should be performed in this event. The dt parameter is the number of
  seconds since the previous frame (usually < 1). You can easily use this to
  value to calculate the frame rate, or update time-based animations.

PaintTile (g as RBGLGraphics, xpos as Integer, ypos as Integer)

  This method is called after NextFrame and before the spries are drawn. It is
  called once for every tile that is visible in the RBGLSpace control. The g
  parameter is an RBGLGraphics class which can be used for doing arbitrary
  drawing using OpenGL. The Graphics object is clipped to the bounds of the
  tile being rendered.
  
PaintOverlay (g as RBGLGraphics)

  This method is called after the background, tiles and sprites have all been
  drawn. It allows you to draw content on top of the surface - this can be
  useful for drawing HUD elements, or special effects such as lightning or
  laser beams that are hard to achieve with sprites.


RBGLSprite
------------
------------

The RBGLSprite replaces the old REALbasic Sprite class. It has similar
functionality, but adds a few new features made possible by the use of OpenGL.

RBGLSprite Properties
------------------------

Group As Integer

  This property is used to control collision detection between sprites. If the
  group value of a sprite is set to zero, it will not collide with any other
  sprite. If it is set to a negative value then the sprite can collide with any
  other sprite that has a non-zero group value. If it is set to a positive value
  then the sprite can collide with any other sprite that does not have the same
  grop value, or a value of zero.
  
Image As Picture

  This is the image that will be drawn for the sprite. It is an ordinary
  Picture, but will be converted to an OpenGL texture when the sprite is
  rendered. In the original SpriteSurface, if this picture contained an 8-bit
  mask, it would be ignored, and the 1-bit, white-as-transparent property would
  always be treated as true, irrespective of the Transparent property of the
  Picture. Whilst this would have been easy to emulate, it is not very useful
  behaviour, so in the RBGLSprite, an 8-bit mask image property can be used. By
  default the Transparent property of the image is set to true automatically
  when it is assigned to a Sprite for compatibility with old projects, but you
  can override this behaviour with the RespectImageTransparency property. If
  an 8-bit mask is specified for the image, the Transparent property will be
  ignored and the collision detection will be based on the 50% opacity threshold
  in the mask.
  
CollisionMask As Picture
  
  Sometimes the collidable area of your sprite does not match the visible area.
  For example you may have a sprite with a large translucent area that should
  still be collidable, or maybe your sprite is a mouse cursor, where only the
  tip is active. In these cases you can use the CollisionMask property to
  specify an separate image to use for collision. If the CollisionMask property
  is set it will be used for collisions in prefence over the Image property.

Priority As Integer

  Controls the order in which sprites are drawn. By default, sprites are drawn
  in the order in which they were added to the RBGLSpriteSurface. Sprites with
  a higher priority will always be drawn in front of those with a lower
  priority however, irrespective of the order they are added. Priority can be
  changed at any time, but frequent chnages to sorting order can impact 
  performance if there are a lot of sprites.
  
Opacity As Single

  New to RBGLSprite, the opacity property lets you make your sprite translucent.
  The default value is 1.0 (opaque), but can range from 1 to zero (fully
  transparent) and can be adjusted on the fly without changing the image.

Tint As Color

  Another new property for RBGLSprite, the Tint property lets you specify a
  colour whih will be belended with the sprite image as it is drawn. You could
  use this property to make a spaceship flash red when it is hit for example.
  The default value is white, which is equivalent to drawing the sprite with
  no colour blending at all.
  
Orientation As Double

  This property can be used to rotate sprites to an arbitrary angle. The
  value is measured in radians and rotation is relative to the centre of the
  current sprite image. Note: collision detection is pixel-perfect for rotated
  sprites, but detecting collisions between rotated sprites is slower than for
  sprites with Orientation equal to zero.
  
Centered As Boolean

  False by default, this handy new property determines if a sprite's X and Y
  coordinates are relatie to it's top-left-hand corner or its centre. By
  default they relate to the corner for backwards compatibility, but it is
  often easier to work with sprites that are centered, especially if rotation
  is involved.
  
Surface As RBGLSpriteSurface

  This is the RBGLSpriteSurface that the sprite is attached to. WHen a sprite
  is first created this will be nil unless the sprite was created using the
  RBGLSpriteSurface.NewSprite function. WHen you set this property to a given
  RBGLSpriteSurface, the sprite will be attached to that surface. If you set it
  to nil then the sprite will be detached.
  
X As Single
Y As Single

  The coodinates of the sprite relative to the RBGLSurface's origin. These
  values will differ from the onscreen coordinates of the sprie if the surface
  has been scrolled. Unlike the original Sprite, these values are specified as
  floating poin rather than integer for better precision when positioning
  sprites.
  
ScreenX As Single
ScreenY As Single

  The actual position of the sprite in the view (takes the current scroll
  values of the RBGLSurace into account). The name ScreenX is misleading
  because the coordinates will only be relative to the screen if the
  RBGLSprieSurface fills the whole screen. These coordinates are actually
  relative to the top-left corner of the RBGLSUrface control.
  
RespectImageTransparency As Boolean

  This property is shared between all RBGLSprites. If set to false, all sprite
  images will treat white pixels as transparent, otherwise they will respect
  the Transparent property of the specified Picture. This is set to false by
  default to mimic the behaviour of the built-in SpriteSurface.
  
DebugCollisions As Boolean

  If set to true, bounding circles and collision masks will be drawn in white
  in front of all the sprites as they are calculated. Note that this is a shared
  method so it applies to all sprites.
  
  
RBGLSprite Methods
---------------------

Close ()

  Removes the sprite from its parent RBGLSpriteSurface.

CanCollideWith (S As RBGLSprite) As Boolean

  This method compares the Group properties of the two sprites to determine if
  they can collide with one another. If this method returns false, sprite
  collisions between the two sprites will not be tested for automatically, and
  the Collision event will not fire. You can still test if the sprites are
  colliding manually however by using the CollidingWith() method.

CollidingWith (S As RBGLSprite) As Boolean

  This method tests to see if the specified sprite is colliding with this one.
  Collision detection is pixel-perfect and the collision mask is derived from
  the image property. If the image has a full eight-bit alpha mask, a threshold
  of 50% opacity is used for the collision mask (this is not affected by the
  Opacity property of the sprite itself). The Group property is ignored for this
  test.
  
Update ()

  Triggers the NextFrame event. Usually called by the RBGLSprteSurface.

Draw (g as RBGLGraphics)

  This method draws the sprite with the specified opacity and blend colour into
  the passed RBGLGrpahics context. YOu can use this to draw a sprite directly
  inside an RBGLCanvas if you like, or to paste it into one of the background
  tiles.


RBGLSprite Events
------------------------
  
Collision (S as RBGLSprite)

  This is called every time the sprite collides with another. You may delete or
  add sprites (including the event owner) to the surface during the execution of
  this event.
  
NextFrame (dt as Double)

  This is called before all collision tests are performed and the sprite
  is drawn, but afte the RBGLSpriteSurface NextFrame event. The dt parameter is
  the number of seconds since the sprite was last updated (usually < 1). You
  can easily use this to value to calculate the frame rate, or update time-based
  animations.
  
Paint (g as RBGLGraphics)

  This event is called after the sprite image is drawn and allows you to draw
  text or special effects on top of the sprite. Drawing is positioned and
  oriented relative to the center of the sprite, but it is not clipped to the
  sprite area, so you could use it to draw, for example, a lightning bolt
  between two sprites - something that would be hard to do using ordinary sprite
  images. Note that you can also leave the Image property of the sprite as Nil
  and draw the sprite manually within the Paint event if you wish.

General Info

The rbgl sprites project contains the RBGLSprites module and the RBGL module
that it depends on, but no example of how to use either of them. To see them
in action, try running one of the demos in the examples folder, which are
separately documented in Examples.txt

The idea behind RBGL Sprites is to make it as easy to port existing
SpriteSurface projects as possible. The only changes you will have to make to
your project in most cases is to replace all references to Sprite and
SpriteSurface with RBGLSprite and RBGLSpriteSurface respectively, and replace
any instances of the SpriteSurface control in your window.

But RBGL Sprites' ease of use comes at a price to performance. In the majority
of cases, especially on new hardware you should see a slight performance boost,
but it some cases RBGLSpriteSurface may actually run slower than the original
SpriteSurface control. Check the porting tips section for advice on what to do
in these cases.

Porting Tips

So you've replaced all your Sprite and SpriteSurface instances, and you've got
it running, but it's not working the way you hoped? Let's fix that:

1) The screen is too small

Unlike the original SpriteSurface, RBGLSpriteSurface is restricted to the
physical size of the control in the Window. The SurfaceHeight and SurfaceWidth
properties are ignored. Make sure that your RBGLSpriteSurface control and
containing window are big enough. You can resize the control at runtime by
locking it to the window corners and resizing the window, just like a normal
Canvas.

2) Everything runs really slowly

RBGLSpriteSurface's frame speed is capped at 32fps by default, which may be
slower than your original SpriteSurface. You can't set this in the IDE like you
could with the SpriteSurface, so try adding the following line to the Open
event of your RBGLSpriteSurface control:

me.FrameSpeed = 0

(Substitute whatever value you were originally using for the zero)

2a) That didn't help, everything is still really slow, and jerky

Jerkiness may be caused by inefficient drawing in your PaintTile event. The
RBGLGraphics class has different performance characteristics to the normal
REALbasic Graphics object, and sometimes you will find that it draws more
slowly. Try commenting out the code in the PaintTile event and see if the
performance improves. If it does, re-enable your code bit-by-bit to work out
what the slow parts are. In particular check to see if you are drawing a lot
of strings or if you are creating images on the fly. Drawing strings is
currently quite slow with RBGL if you are not careful about caching. You can
find specific performance tips for drawing with the RBGLGraphics class in the
Instructions.txt file included with the standalone RBGL distribution, which
you can find at http://www.charcoaldesign.co.uk/oss#rbgl

2b) It wasn't the PaintTile event

If your sprites are animated (the image changes each frame), how are you
generating your Sprite images? Are you chopping them out of a larger table of
images and creating a new Picture object each time the image changes? RBGL
has to convert images to OpenGL textures before it displays them the first
time, which is very slow. Subequently displaying the same image is very fast
though, but only if you use the exact same Picture object. If you create
Pictures on the fly each frame, things are really going to crawl. Try creating
all your Picture objects beforehand and storing them in an array. If that
still doesn't help, contact us at http://www.charcoaldesign.co.uk/contact and
we'll do our best to fix it for you.

2c) Nope, I'm caching all my Pictures properly and it's still slow

Try enabling the RBGLSprite.DebugCollisions property. This will display the
collision geometry used by RBGL as an overlay on top of your sprites. If a
sprite is idle (not colliding with anything) then it will be displayed with a
bounding rectangle and circle around it. If it is colliding or has collided
with something it will be painted white by the pixel-accurate collision
geometry. Calculating pixel-level collisions is expensive, so you don't want
to be doing it unless you need to. If you see sprites appearing all white when
they shouldn't be colliding with anything then check the values you are using
for Group on all yours sprites - it may be that you are giving sprites
different group values when they should never collide. Try to make sure than
any sprites you don't want to collide have the same group value, or a value
of zero. If you are using the group to distinugish between different types of
enemy that shouldn't collide with each other, consider using a different
approach, such as creating subclasses of RBGLSprite for each enemy and then
using the isa operator to distinguish between them in collisions. Also remember
that sprites with a negative group value can collide with each other even if
they have the same value - you will rarely want to use negative group values
in practice.

3) The RBGLSpriteSurface is leaking memory

RBGL uses aggressive caching of images and strings (which are converted to
images internally) to improve performance, but sometimes this caching causes
a memory leak. If you are creating images on the fly, or drawing computer or
user-generated strings each frame then RBGL will eat up more and more memory
until it crashes. Read up about RBGL String and Picture caching in the RBGL
Instructions.txt file, which you can find in the RBGL package at
http://www.charcoaldesign.co.uk/oss#rbgl

4) My sprite or background pictures aren't updating like they are supposed to

RBGL caches Picture objects. That means that if you use a Picture as an
RBGLSprite image, or draw it during a PaintTile event it becomes "locked",
and any subsequent time it is drawn, the cached version will be used, even if
you've modified the picture using its Graphics property. You should avoid
drawing to Pictures that you use with RBGL because the caching is vital to
getting good performance - it is better to create a new Picture object for
each distinct image. If you really can't do this, you can clear the cache
for a specific Picture Object by callingRBGLGraphics.UnCache(thePictureObject).
If you need to do dynamic drawing each frame, drawing directly into the
surfaces's PaintTile or PaintOverlay graphics property, or the sprite's own
Paint event graphics property will yield much better performance.

5) It runs okay, but I thought OpenGL was supposed to give me cool 3D graphics
effects...

Patience young grasshopper, the objective for the first release of RBGL
Sprites was just to get your code working with OpenGL in the first place. Once
you are drawing using OpenGL as the underlying technology, a lot of cool stuff
becomes possible. For the time being, here are a few things you can do with
RBGL Sprites that you can't do with a normal SpriteSurface:

- 8-bit transparent/translucent masks. Your sprites will look a lot better
with proper alpha masking.
- Really big sprites - you can use sprites that are hundreds of pixels wide
with little or no performance hit (for best results use power-of-two sizes).
- Arbitrary rotation - no need to draw your sprites at all possible angles
- Really big tiles - your background tiles aren't limited to 64x64 pixels any
more.
- Translucency and colour blending - you can tint and fade your sprites in
real time by adjusting their Opacity and Tint properties.
- Custom collision masks - the collidable part of a sprite doesn't have to
match its visible shape any more.
- Overlays - draw anything you like (text, special effects, etc) over the
top of your sprites.

If you've got ideas for features you'd like to see in RBGL Sprites then let
us know, but don't worry, we've got a lot of ideas of our own!

6) My problem wasn't covered here

Contact us at http://www.charcoaldesign.co.uk/contact and we'll try to help
solve your problem.