Share via


Small Basic: Sprite Arrays

This article describes using arrays of sprites in a Small Basic program.  A sprite is an image that is moved in a graphical game, they may be aliens for example.

Often we want several copies of the sprites that come and go as the game proceeds and it is good practice to store them in arrays.  This article provides some explanation and suggestions about how to dynamically control sprites efficiently.

Loading sprite images

The image for a sprite exists in a file (e.g. jpg or png).  This file may be local as a file on your PC or be stored online as a URL.  If it is local then you will need to share the images with your game in zip download, therefore it may be better to host the file on the web if you can.

It is often best to create the sprite with a transparent boundary and with a carefully chosen width and height in pixels.

Once the sprite is created, it is most efficient to load it into your program once only using the ImageList method, and this is usually done before the game starts as it may take some time to download.  The loaded image may then be used many times for multiple copies of the sprite.  The following code is an example loading a sprite image from an online URL and testing it appears correctly.

sprite = ImageList.LoadImage("http://litdev.co.uk/game_images/football.png")
Shapes.AddImage(sprite)

Football Sprite

Arrays of sprites

We can now create an array with multiple copies of the sprite, using the loaded image, for example.

spriteImage  = ImageList.LoadImage("http://litdev.co.uk/game_images/football.png")
numSprite  = 10
For i =  1 To numSprite
  sprite[i] = Shapes.AddImage(spriteImage)
EndFor

The sprites all appear on top of each other and we can't do much with them yet.

Sprite properties

Sprites are images, but in a game they may have other properties as well such as position, velocity, status and others.  We need to store this information for each sprite in arrays as well.  There are several ways this could be done, but here is a suggestion to store all the information for a sprite in an array and have an array of these sprite arrays - a 2D array.

Arrays in Small Basic may be slow, but if we keep the number of elements to a minimum, this method has advantages.  It will also improve your programming to keep all associated logical data together and allows for easier sprite creation and deletion.

The code below is structured using subroutines, creating some sprites, positioning them randomly, then moving them about.  We could have the sprites rotating or have other features stored in the spriteData arrays.

gw = 600
gh = 600
GraphicsWindow.Width = gw
GraphicsWindow.Height = gh
 
CreateSprites()
 
'Game Loop
While ("True")
  start = Clock.ElapsedMilliseconds
  
  UpdateSprites()
  
  delay = 20  - (Clock.ElapsedMilliseconds - start)
  If (delay  > 0) Then
    Program.Delay(delay)
  EndIf
EndWhile
 
Sub CreateSprites
  spriteImage = ImageList.LoadImage("http://litdev.co.uk/game_images/football.png")
  'Sprite dimensions we use the half width and height
  spriteWidth = ImageList.GetWidthOfImage(spriteImage)/2
  spriteHeight = ImageList.GetHeightOfImage(spriteImage)/2
  
  numSprite = 10
  For i =  1 To numSprite
    spriteData["image"] = Shapes.AddImage(spriteImage)
    spriteData["Xpos"] = spriteWidth + Math.GetRandomNumber(gw-2*spriteWidth)
    spriteData["Ypos"] = spriteHeight + Math.GetRandomNumber(gh-2*spriteHeight)
    spriteData["Xvel"] = Math.GetRandomNumber(11)-6
    spriteData["Yvel"] = Math.GetRandomNumber(11)-6
    sprites[i] = spriteData
  EndFor
EndSub
 
Sub UpdateSprites
  For i =  1 To numSprite
    spriteData = sprites[i] 'get current sprite array
    
    'Reposition sprite center
    spriteData["Xpos"] = spriteData["Xpos"] + spriteData["Xvel"]
    spriteData["Ypos"] = spriteData["Ypos"] + spriteData["Yvel"]
    
    'Bounce on walls
    If (spriteData["Xpos"] < spriteWidth) Then
      spriteData["Xpos"] = spriteWidth
      spriteData["Xvel"] = -spriteData["Xvel"]
    ElseIf (spriteData["Xpos"] > gw-spriteWidth) Then
      spriteData["Xpos"] = gw-spriteWidth
      spriteData["Xvel"] = -spriteData["Xvel"]
    EndIf
    If (spriteData["Ypos"] < spriteHeight) Then
      spriteData["Ypos"] = spriteHeight
      spriteData["Yvel"] = -spriteData["Yvel"]
    ElseIf (spriteData["Ypos"] > gh-spriteHeight) Then
      spriteData["Ypos"] = gh-spriteHeight
      spriteData["Yvel"] = -spriteData["Yvel"]
    EndIf
    
    'Move sprite center
    Shapes.Move(spriteData["image"],spriteData["Xpos"]-spriteWidth,spriteData["Ypos"]-spriteHeight)
    
    sprites[i] = spriteData 'save updated sprite array (it may have been modified)
  EndFor
EndSub

The code above may look a bit longer than required, but storing all the data for a sprite in an array and then having arrays of sprites is an efficient way to proceed if we want to create and destroy sprites during the game.

Recycling sprites

We often want sprites to appear and disappear.  One way to do this is to recycle the sprites from a 'pool', activating and using them as required.  This can be good for example to fire bullets or missiles, when we only ever need a limited number on screen at the same time.

The example below fires missiles when the mouse is clicked.  A new spriteData property "Status" is used to flag that missiles are active or not.  Inactive missiles are hidden and they are shown while they are active on the screen.

Missile Sprite

gw = 600
gh = 600
GraphicsWindow.Width = gw
GraphicsWindow.Height = gh
GraphicsWindow.MouseDown = OnMouseDown
 
CreateSprites()
 
'Game Loop
While ("True")
  start = Clock.ElapsedMilliseconds
  
  If (mouseDown) Then
    FireMissile()
    mouseDown = "False"
  EndIf
  UpdateSprites()
  
  delay = 20  - (Clock.ElapsedMilliseconds - start)
  If (delay  > 0) Then
    Program.Delay(delay)
  EndIf
EndWhile
 
Sub CreateSprites
  spriteImage = ImageList.LoadImage("http://litdev.co.uk/game_images/missile.png")
  'Sprite dimensions we use the half width and height
  spriteWidth = ImageList.GetWidthOfImage(spriteImage)/2
  spriteHeight = ImageList.GetHeightOfImage(spriteImage)/2
  
  numSprite = 50
  For i =  1 To numSprite
    spriteData["image"] = Shapes.AddImage(spriteImage)
    spriteData["Xpos"] = spriteWidth + Math.GetRandomNumber(gw-2*spriteWidth)
    spriteData["Ypos"] = gh-spriteHeight
    spriteData["Xvel"] = 0
    spriteData["Yvel"] = -5
    spriteData["Status"] = 0
    Shapes.HideShape(spriteData["image"])
    sprites[i] = spriteData
  EndFor
EndSub
 
Sub UpdateSprites
  For i =  1 To numSprite
    spriteData = sprites[i] 'get current sprite array
    
    If (spriteData["Status"] = 1) Then
      'Reposition sprite center
      spriteData["Xpos"] = spriteData["Xpos"] + spriteData["Xvel"]
      spriteData["Ypos"] = spriteData["Ypos"] + spriteData["Yvel"]
      
      'Move sprite center
      Shapes.Move(spriteData["image"],spriteData["Xpos"]-spriteWidth,spriteData["Ypos"]-spriteHeight)
      
      'Sprite finished with
      If (spriteData["Ypos"] < -spriteHeight) Then
        spriteData["Status"] = 0
        Shapes.HideShape(spriteData["image"])
      EndIf
      
      sprites[i] = spriteData 'save updated sprite array (it may have been modified)
    EndIf
  EndFor
EndSub
 
Sub FireMissile
  For i =  1 To numSprite
    spriteData = sprites[i] 'get current sprite array
    If (spriteData["Status"] = 0) Then
      spriteData["Status"] = 1
      Shapes.ShowShape(spriteData["image"])
      spriteData["Xpos"] = GraphicsWindow.MouseX
      spriteData["Ypos"] = gh-spriteHeight
      
      sprites[i] = spriteData 'save updated sprite array (it may have been modified)
      i = numSprite 'End loop
    EndIf
  EndFor
EndSub
 
Sub OnMouseDown
  mouseDown = "True"
EndSub

Deleting sprites

Adding and deleting sprites during a game may be required in some cases, if for example we don't know what the sprite image will be until it is used or the number of sprites cannot easily be handled by a fixed size pool.  However, the sprite pool is often the best approach.

  • A sprite may be deleted if it is certain that it will not be needed again.  In addition to deleting the sprite data arrays, it is necessary to delete (Shapes.Remove) the sprite shape.  If un-deleted sprite shapes mount up your program may slow dramatically.
  • To delete elements from an array we set the array value to "".  However, if we delete items from an array, the array indexing will not remain sequential (1, 2, 3 ...) and we will have to use Array.GetAllIndices to get the current indices.
  • To add sprites to an array we need to use a previously unused index or we risk over-writing an existing sprite.

With these points in mind, this is the missile code adding and removing sprites to the missile array as required, for comparison with the 'pool' method.

gw = 600
gh = 600
GraphicsWindow.Width = gw
GraphicsWindow.Height = gh
GraphicsWindow.MouseDown = OnMouseDown
 
CreateSprites()
 
'Game Loop
While ("True")
  start = Clock.ElapsedMilliseconds
  
  If (mouseDown) Then
    FireMissile()
    mouseDown = "False"
  EndIf
  UpdateSprites()
  
  delay = 20  - (Clock.ElapsedMilliseconds - start)
  If (delay  > 0) Then
    Program.Delay(delay)
  EndIf
EndWhile
 
Sub CreateSprites
  spriteImage = ImageList.LoadImage("http://litdev.co.uk/game_images/missile.png")
  'Sprite dimensions we use the half width and height
  spriteWidth = ImageList.GetWidthOfImage(spriteImage)/2
  spriteHeight = ImageList.GetHeightOfImage(spriteImage)/2
  
  sprites = ""
  nextSprite = 1
EndSub
 
Sub UpdateSprites
  spriteIndices = Array.GetAllIndices(sprites)
  For i =  1 To Array.GetItemCount(spriteIndices)
    index = spriteIndices[i] 'current sprite index
    spriteData = sprites[index] 'get current sprite array
    
    'Reposition sprite center
    spriteData["Xpos"] = spriteData["Xpos"] + spriteData["Xvel"]
    spriteData["Ypos"] = spriteData["Ypos"] + spriteData["Yvel"]
    
    'Move sprite center
    Shapes.Move(spriteData["image"],spriteData["Xpos"]-spriteWidth,spriteData["Ypos"]-spriteHeight)
    
    'Sprite finished with
    If (spriteData["Ypos"] < -spriteHeight) Then
      Shapes.Remove(spriteData["image"])
      Sprites[index] = ""
    Else        
      sprites[index] = spriteData 'save updated sprite array (it may have been modified)
    EndIf
  EndFor
EndSub
 
Sub FireMissile
  spriteData["image"] = Shapes.AddImage(spriteImage)
  spriteData["Xpos"] = GraphicsWindow.MouseX
  spriteData["Ypos"] = gh-spriteHeight
  spriteData["Xvel"] = 0
  spriteData["Yvel"] = -5
  sprites[nextSprite] = spriteData
  nextSprite = nextSprite+1
EndSub
 
Sub OnMouseDown
  mouseDown = "True"
EndSub

Conclusion

While this article is directed towards handling sprite arrays, the methods used in the last example actually include good general techniques to consider as your programming develops.  They will transfer well to your next language after Small Basic, where you will probably encounter classes and collections.

  • Store all related data together and then create arrays or lists of these data if you need multiple copies of it.
  • Use subroutines to perform specific tasks.
  • Systematically keep track of when and where you create and destroy data.  In general, this discipline will result in faster, lower maintenance and efficient code with fewer bugs that are easier to detect and fix.

As a final suggestion, if the speed of Small Basic arrays becomes a limiting factor, then extension methods may be used to improve performance, for example the LDList object in the LitDev extension for Small Basic.  The LDList object is described in another TechNet article.


See Also

Other Languages