This guide will be updated over time until I consider it sufficiently exhaustive. If you don't see this heading, I'm probably done.
SmileBASIC's lack of a central system for objects, structs, or classes leaves many programmers used to other languages bewildered. Managing state of a bunch of different objects, such as enemies, the player's inventory, etc. can become a hassle, or a big tangle of global state, if you aren't careful. However, if you're using sprites in your game (and it's likely you are) then you have some options.
In this reference guide, I will explore the concept of treating sprites themselves as "entities" that have their own state and behavior. Creation, destruction, and management will be achieved purely with vanilla SB features, and there will still be room for you to explore and improve this approach.
This guide assumes you know a fair bit about the SmileBASIC language (DEFs, functions etc.) and the average use cases of sprites. This isn't a beginner's course.
Table of Contents
contents
TODO
What's the Point?
Every good game has lots of entities. All of these entities have their own state. Entities often are spawned, removed, and respawned on the fly. In a language like SB with very limited data structures, this may be a very daunting and very difficult balancing act. A large number of global arrays, a bunch of helper functions, and complicated logic.
However, many good games have sprites; and if we're using sprites, why not let them own some of that state?
Yes, it
is possible, to an extent. Soon you'll see the broad horizons (and dangerous cliffs) when using sprite features to encapsulate state and behavior for entities and objects in your programs.
Built-In Sprite State
If you're here, you probably know how to use sprites to some degree. This bit is a bit important though, so I want to reinforce it.
On their own, sprites already
do hold some of their own state; however, much of this state pertains entirely to how they're drawn on-screen. For simple games, accessing this state might be enough. For example, you can read the position of any sprite on-screen with
SPOFS OUT.
SPOFS ID% OUT X%,Y%{,Z%}
As a general rule, if you can set a property of a sprite, you can read it.
SPCOLOR,
SPROT, you name it. These properties
are state, so you're halfway there, right?
Well, for more complex games, this probably isn't enough. For example,
SPROT and
SPOFS use integers, so you lose some precision if you want to use floats for your angles or positions. Plus, all this state is limiting since it's entirely concerned with the sprite's display on-screen. Again, for some code, this might be enough, but what if you need something more complex? What if each enemy needs a separate "world" position before being placed on-screen? What if a moving object's position needs to be a float for smoother movement, or an object has a stored velocity? What do you do, make a bunch of global values and call it a day?
SPVAR: A Sprite's Best Friend?
What if I told you you could store variables
inside a sprite? You'd probably be really excited, I'm sure! Let me temper your excitement for a second: it's not exactly what it sounds like, but it comes close.
Every sprite has eight internal slots for storing arbitrary data, numbered 0 through 7. Each slot can hold one float value. These slots persist inside this sprite until it is destroyed with
SPCLR, and they all begin with a default value of 0. You can think of each sprite having its own 8-element float array.
The
SPVAR functions are used to read and write to this "array."
'set slot 0 in sprite 12 to 3.45
SPVAR 12,0,3.45
'get slot 0's value in sprite 12 (will be 3.45)
VAR#=SPVAR(12,0)
An Adventurous Example
Imagine a game where you play a brave warrior slaying numerous beasts. Obviously, each monster needs its own HP count. In an object-oriented language, this is a piece of cake: make a
Monster class, give it an HP property, and allow it to change. We lack that luxury, though.
We
could use a fixed global array for holding monster HP, indexed by sprite ID number. Maybe we could create a complex object factory system and store data in complicated structures. At the end of the day, though, it's all global state, and it's all arrays. If we're creating sprites for each of these monsters, we might as well use them.
We can assign a specific
SPVAR slot to each monster's HP level. We'll go with 0, since that's easy. Let's have the player fight some slimes, who each have 50 HP.
Well first, we need a function to spawn a slime. Here, we'll assume the slime graphic is on
SPDEF template 22.
'make us some slimes!
DEF SPAWNSLIME
'get a free sprite ID
VAR ID%=SPSET(0,511,22)
'give the slime 50 HP
SPVAR ID%,0,50
END
Here, we get a new sprite ID and make it into a sprite (more on the
SPSET() function later.) Obvious things like sprite placement are omitted for clarity, of course. All that's important to us right now is the
abstract concept of the "sprite" as some kind of entity.
When the player attacks the slime, we can reduce its HP by the amount of damage done. If the slime's HP drops to (or below) 0, we can kill it and simply clear the sprite.
'read the enemy's HP value
VAR ENEMYHP%=SPVAR(EID%,0)
'decrease it by the damage done
DEC ENEMYHP%,DAMAGE%
'store the new HP value
SPVAR EID%,0,ENEMYHP%
'if HP hits 0, kill it
IF ENEMYHP%<=0 THEN
'kill animation, etc. in here.
SPCLR EID%
END
The enemy in question (here, the slime) owns its own HP counter, by way of its sprite. When we kill the enemy, we also clear its sprite, freeing it for use later.