AbilitiesFrom the beginning, I wanted to avoid this game being "just a Metroidvania", where everything is organized around leading you to pick-ups which give you abilities which enable you to backtrack and blah blah blah.
But I still wanted to have different abilities, just so I could play with the engine more. So I implemented them as different "types" of spider:
![](https://raw.githubusercontent.com/bayersglassey/geom2018/master/img/spider_types.png)
Their abilities are:
- Regular spider: nothing special
- Roller spider: can roll around sharp edges while crouching
- Aimer spider: can aim diagonally upwards (and thereby spit & jump diagonally)
- Flier spider: can fly at will
- Spikey spider: while crouching, extrudes spikes which make you invincible to "damage" from enemies
- Eyeball spider: can close its eye (not really an ability... eyeball spiders aren't meant to be playable, they're meant to be used as NPCs)
- Coin beast / purple person: nothing special, not a spider
...you were (and are) able to change between the different spider types by entering "doors" (glowing blue things).
However, this is strictly less interesting than being able to mix and match the various abilities.
So, what with the engine changes over the past few months (including the beefed-up scripting language), I switched the checks for whether you "have" a given ability from "is my stateset this one" to "is this scripting language variable true". With multiple scripting variables, that lets us mix and match the various abilities.
You can see this at work in the save file format below. The stateset is "anim/spider.fus" (the regular spider), but the "is_aimerspider" and "is_spikeyspider" variables are also set to T (true):
version: 0
game:
vars:
players:
:
keymap: 0
spawn: 25 30 0 n "data/maps/demo/worldmap.fus" "anim/spider.fus" "step"
body:
vars:
"is_rollerspider": F
"is_aimerspider": T
"is_spikeyspider": T
"is_flierspider": F
"is_birdspider": F
maps:
"data/maps/tutorial/worldmap.fus":
vars:
"made_it_underneath": F
"going_upwards": F
submaps:
"data/maps/demo/worldmap.fus":
vars:
"guide_passed_start": T
"guide_passed_start2": T
"eyespider_tunnels_ran_away_1": T
"eyespider_vines_ran_away_1": T
"eyespider_vines_ran_away_2": F
"eyespider_vines_ran_away_3": F
"eyespider_vines_ran_away_4": F
"jungle_cage": F
"vines3_door": F
"spider_passed_vines": F
"spider_passed_vines2": F
"eyespider_dodecas_ran_away_1": T
"map3_blocker": F
"map3x_blocker": F
submaps:
"start":
visited: t
"start2":
visited: t
"start3":
visited: t
"vines":
visited: t
...you can also see that the save file format is now "versioned", with a version of 0.
Next time I make breaking changes to the format, I'll bump it to version 1.
When you try to load a saved game, if its format is the wrong version, you now get a friendly message
to that effect, instead of the game crashing. O_o
...you can also see how the minimap is "populated" (and how that is saved): there are named "submaps" which have a "visited" property of "t" (true) or "f" (false).
Anyway, one issue with this approach (abilities controlled by variables instead of statesets) is that your appearance no longer tells you what abilities you have.
And in the case of the "spikey" spider, that's a particularly big issue, because your spikes are supposed to extrude when you crouch.
There might be a solution using a recently-added engine feature: labels (see below).
LabelsThe graphics engine's concept of an "image" is called a "rendergraph" (see
this post for gory details).
It's something which can be rendered at a particular position, and can be rotated and flipped, and can be animated (have multiple frames of animation, each representing a different image).
A rendergraph can't be customized or broken apart: the whole thing is either rendered or not.
So how can we have sprites which can be customized?..
For example, imagine a character who can wear different hats.
We need to use multiple rendergraphs to do this: for instance, we can use one rendergraph for the the character, and another rendergraph for the hat.
But rendergraphs are animated, and when the character's body moves, we presumably want the hat to stay on his head.
So this is where the concept of "labels" comes in: a label is a named point on a rendergraph, where other rendergraphs can be "attached".
So, the character's body's rendergraph can now be defined something like:
shapes:
"character_head":
labels:
# This is a label called "hat" located at position (0 1 -1 0),
# with a rotation of 2 (that is, 2*30 = 60 degrees) and not
# flipped ("f" is for false)
: "hat" (0 1 -1 0) 2 f
prismels:
# ...the prismels making up this rendergraph's image...
"character_body":
prismels:
# ...the prismels making up this rendergraph's image...
shapes:
# We make use of the head's rendergraph, rendering its prismels
# on top of our own, and inheriting its labels.
: "character_head" (0 0 -3 2) 0 f
"hat":
prismels:
# ...the prismels making up this rendergraph's image...
...each body (each sprite) keeps track of which rendergraphs are mapped to which labels.
So if our character is wearing a sombrero, then his body has stored a label-to-rendergraph mapping like: "hat" -> "sombrero".
The "label-to-rendergraph mapping" system is plugged into the scripting language, so all kinds of things are possible.
Currently, the spider's rendergraphs (there's one rendergraph for each animation) all have a "carrying" label (located on top of the spider's "nose"), and this is used to implement carryable items (such as eyeballs and "food", which is little green squares):
![](https://raw.githubusercontent.com/bayersglassey/geom2018/master/img/carryable-food.gif)