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)
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.
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
- Small Basic: Dynamic Graphics
- Small Basic: Array Basics
- Small Basic: List Extension
- How do you get an image for your program?