Firestarter (WIP and a couple questions...)

Hello, I was trying to think of a good game idea that could make use of the FX chip for more than just saving sprites (just for things like levels or other game data)… not because I wanted to immediately jump into the FX stuff or anything, just so that I could account for this in my overall game outline and plan accordingly… well this made me take a step back since I’d never gotten proficient enough to make a regular Arduboy game (which is where I should start anyway to work my way up to FX stuff) so I started trying to think of a game that would be a good fit for the platform in general when I came across this, which I think would be a perfect fit (its actually the game from the anime FLCL, if anyone’s ever seen that)…

Gameplay: OCG & Anzu Play - Fire Starter: A FLCL Game - YouTube

And even better, the source code is written in C++:

The license is creative commons but it also states the graphics and music can’t be used without permission so I reached out to the creator and asked (since I would need to redraw the sprites in monochrome based off of theirs) and they said yes, but I have a few questions while trying to plan this… I was able to find the parts of their code which did the stuff I was looking for, but since it’s quite a bit more advanced than my current understanding I still have a few questions pertaining to what exactly it’s doing (and given that the code is 17 years old I wouldn’t expect the original developer to remember any of this stuff or wanna bug them too much about it, which brings me here)…

First of all, here is my overall outline for the game (and the order in which I plan to do things once I understand them a bit more)… anywhere on the list that i have a number denotes the relevant line numbers in MainGame.cpp which I’m using for reference, although in order to better understand what the code is doing I’ve also included some additional reference material I’m using to help me learn.

Create camera (136/137)
Create global coordinate system in Cartesian (230, 235)
Create global coordination system in isometric (230, 235)
Map layouts?
Line 58?
Lines 459 (randomly sets each cell type),
475/489 draws down left/right roads???
Line 540 draws bitmaps (then 553 merges roads automatically using binary ???)
What is line 677? Pause menu map?
Controls (201, 218)

Isometric stuff:

And as for my first couple questions…

-How could I loop global map coordinates so the map repeats? The original game has a finite map but I was wondering if there was some way to make it scroll so that the entire map just repeats… I’ve found a few different methods for this but not sure what would be the most sensible (my guess would be something involving modulo but I’m still not clear how this works or how I would implement it)

-And is there some way i could randomly generate the map(s) on PC and just insert the code into my game? The original game randomly generated the map at runtime, which I would prefer, but given the limited amount of RAM in the arduboy I’m not sure if I’ll be able to do this so was wondering if there was any way i could do it PC side and just insert whatever layout(s) this generates (then if it turns out I do have enough ram to work with at the end I could just edit my game).


1 Like

What might be cool is one “game” that is a map generator (maybe editor?) that could run on the Arduboy, and then you could have another “game” that is actually… so when you reboot it takes you back to bootloader and you can either start the game or go to the world maker, each their own .hex essentially.

You could even reskin the loader and make it so that when it boots it’s more bespoke to the game and as you switch through the menu it looks like your in the menu of the game itself.

Actually, could take advantage of that and think of some other play modes that could take advantage of that. A whole set of mini-games, you could put crafting off in it’s own game too.

It’s also possible to reset the Arduboy from code, so theoretically you could even make your game jump back to the “main menu” without having to do any special button press or power cycle.

Also, you may have noticed but I’m looking at launching a game jam for games that use the FX chip soon. So stay tuned!

1 Like

フリクリ。I’ve not seen it, but I’ve heard of it many moons ago.

I must admit CC BY-NC-SA 2.0 is a very odd choice for software code.

You’d need to do some rendering trickery so that you’re actually drawing the map multiple times. Beyond that you only really need to use the % (modulo) operator to constrain coordinates within the world space, assuming you are only handling unsigned/non-negative coordinates.

Fun fact: If you do this then your world effectively becomes a torus.
(Relevant TV Tropes article. Warning: TV tropes is an addictive substance, handle with care.)

You could potentially randomly generate the maps on the Arduboy even, but finding a decent way to generate them could be tricky simply because devising decent procedural generation algorithms can be tricky.

The limited RAM would add to the difficulty, but considering you need to be able to burn down the whole city, you’re going to have to be able to represent all the buildings in RAM anyway, otherwise burnt buildings won’t stay burnt.

Yes, it’s possible, and would give you more options for how you generate the world because having gigabytes of RAM to waste means you don’t need to be particularly efficient.

Not quite. viewport is almost certainly the camera (‘viewport’ is another name for ‘camera’ in programming terms), i.e. the camera already exists at this point.
I think those lines adjust the camera to be centred on the player whilst also preventing it from venturing outside the city limits. It’s a little bit hard to decipher though because it’s doing a lot of work at once and contains a lot of ‘magic numbers’. (E.g. I’m presuming the 100 is half the character width and 150 is half the character height, but I haven’t checked.)

This section is updating the player’s movement.

I believe the ifs are some kind of collision checking because they undo the movement that occurs previously (e.g. pos.x -= dx * 0.5; is the inverse operation of pos.x += dx * 0.5;).

updatePos's role is a bit less clear, but it’s certainly updating mx and my (whatever they are) based on the value of the colour channels of a pixel in the game’s ‘mask’ (whatever that is) at the player’s position. Without looking deeper I’m not sure what that achieves.

(Note that really it should be 230-238, otherwise you’re missing part of the picture. You can also look at it as two different but similar chunks: 230-233 and 235-238.)

Lines 58-65 is using an xor trick to do something with a chequerboard pattern.

Considering the code before it involves loading bitmaps, I think this is just applying a chequerboard effect to building bitmaps.

As to why it’s a chequerboard pattern:

  • (x & 1) is 1 when x is odd and 0 when x is even.
  • (y & 1) is 1 when y is odd and 0 when y is even.
  • ^ (xor):
    • 0 ^ 00
    • 0 ^ 11
    • 1 ^ 01
    • 1 ^ 10

Hence (x&1)^(y&1) becomes 1 when x is even and y is odd or vice versa, and 0 when x and y are both even or both odd, thus creating a chequerboard pattern.

It’s a derivative version of the famous ‘xor texture’:

That’s actually a constructor for the LevelMap class, but yes it’s creating a new block of cells and populating them with random values.

There’s actually a fair bit of advanced stuff going on here: constructors, the use of new [], using the allocated data as a flat array, and of course lots of pointers (but sadly very few references).

It’s placing the roads on the map, not actually drawing them as such (despite the comments saying ‘draw’). This is another example of logic trickery used to generate a pattern. x is increased every time y is odd and y is increased once on every iteration, and that continues either until x >= width or y >= height. (The actual code is while (x<width && y<height), but it’s easier to think about it in terms of when it stops iterating rather than what condition it continues to run under.)

Yes. But notably it actually draws them onto another bitmap that’s acting as a buffer, which presumably is later drawn onto the screen. I’m presuming this is to better facilitate 2-player mode or possibly post-processing effects.

The short version is that it’s using bitwise trickery and multiplication to (I presume) select the appropriate road type from what I presume would be a sprite sheet/texture atlas.

If you don’t understand bit shifting and other bitwise operations then you won’t really understand it, but if you do understand those then I should point out that:

  • roadtype |= 8; is the same as roadtype |= (1 << 3);
  • roadtype |= 4; is the same as roadtype |= (1 << 2);
  • roadtype |= 2; is the same as roadtype |= (1 << 1);
  • roadtype |= 1; is the same as roadtype |= (1 << 0);

(I prefer using the version with <<. The compiler will constant-fold anyway so there’s no overhead, and the code expresses the intent much better.)

What the road types actually signify would require more digging, but the code appears to be examining neighbouring cells to determine the road type.

Yes, it’s the pause menu map. (I discovered this by searching for ‘drawMap’ and noticing line 182.)

In regards to 201-210, see my comment about updatePos.
212-214 is Player's constructor. 215-217 is its destructor.
218-264 is definitely input handling code.

I’ll make a special mention that (dx||dy) is effectively equivalent to (dx != 0 || dy != 0), (dy&&dx) is effectively equivalent (dy != 0 && dx != 0), else if (dy) is effectively equivalent to else if (dy != 0) and if (dx) is effectively equivalent to if (dx != 0).

What’s actually going on is that the ints are being implicitly converted to bool because of the context, and implicit conversion to bool is effectively equivalent to != 0. These are nasty short-hand tricks sometimes used by some programmers and I abhor them because you end up with ‘l33t haxx0rs’ clever trick nonsense instead of code that just says what it means.

The code in this one is a bit more relevant because ActionScript 3 is closer to C++ than Python.

Though I should point out that actually isometric grids still use cartesian coordinates. The real difference is in the rendering - i.e. how the world coordinates are mapped to screen coordinates.

Either way, the core of both articles are the functions that map the world coordinates to the projected isometric coordinates.

You may also find these useful:

An isometric game engine on the Arduboy would be great. That reminds me of games like Fallout which would be great fun to play on the Arduboy given the flash to hold all the content for the stories.

Just to throw it out there…

Ah yes, forgot about that. You are right. This is a good example.

1 Like

I’m here for the Chrono Trigger world map

Im definitely not opposed to any of this stuff once i get the base game done, i saw the game jam but figured i probably wouldnt have enough time lol.

Ah, i suppose that makes sense, i was just kind of confused since i didnt want to repeat world coordinates. Still not sure i understand how i would use the modulo operator for that.

And yeah, i found resources for treating the map as a torus or sphere (although it seems like the torus is the better of the two since there sphere would warp it a little bit) but nothing that realmy explained the modulo stuff very well.

That’s why I was hoping to follow the way they did it.

Well I know I would need to utilize the map regardless, I’m talking about generating it, im not sure if i could do that with the limited memory so might need to do it PC-side somehow and save the maps.

Although I would still like to try to do it as efficiently as possible just in case I am able to implement it on the Arduboy lol

Oh yeah, they establish that in the MainGame.h file but actually bring it into their game here, I should’ve added a note.

As for the bulk of your reply, the line numbers I added were just for me to roughly narrow down where these things were (so i wouldn’t have to go searching every single time), I know it isn’t the exact lines, it was meant to be more of a rough note (but all of that still definitely helps, thanks!)

I really don’t like python, that was just meant to illustrate the principal.

Yeah, that’s why I have both on the list, but I still wasn’t 100% sure where i would use one versus the other but rendering makes much more sense. Ill look at those other resources as well, thanks!

Also I saw escaper droid and was looking at the code before i initially posted, but it seems to be set up pretty differently from the firestarter code.

The modulo operator can be used to map a sequence of increasing integers to a sequence of repeating integers.


x x % 5
0 0
1 1
2 2
3 3
4 4
5 0
6 1
7 2
8 3
9 4
10 0
11 1
12 2
13 3
14 4
15 0

Thus you can use it to force coordinates to wrap around back to the start of the space, which means that you can use it to make sure that an entity never ventures beyond the limits of the world space.

Negative coordinates require a bit more work, but they’re doable.

Mapping the coordinates onto a torus isn’t actually the intent, it’s just what happens as a side effect when you make the coordinates of a 2D plane wrap back around to the start. (Theoretically to actually turn a 2D plane into a torus you’d have to stretch it a bit, but you can ignore that for the sake of argument.)

Mapping a 2D plane onto to a sphere would be far more complicated because a 2D plane does not map nicely and coincidentally onto a sphere. It is possible to make games where you’re more or less walking around a sphere (Mario Galaxy is the best example I can think of), but I think you’d need a 3D projection to do it, I can’t imagine a way that it would work with isometric graphics. At least, not unless you made your world 3D. But at any rate, the point is that it’s way too complicated and not really worth the effort (in this case).

It depends how much structure you want to the world. If your world is going to have roads forming a grid and then just a pseudorandom assortment of buildings and you aren’t worried too much about the spread of the buildings then it’s trivially easy, you’d just use a random number generator to select the buildings.

If you are worried about the number of each kind of building, you can still potentially get away with use using a PRNG to pseudorandomly place the buildings. Getting an even spread would be a bit more difficult, but still not too difficult.

If you want roads that are more winding or complex then it could be a bit more difficult, but even then I doubt you’d need many hardware resources.

It’s impossible to be more specific without knowing what you want your worlds to be like.
E.g. what types of buildings you’d have, how regular or twisty you want the roads to be.

Nor do I. I don’t hate it quite as much as JavaScript, but it’s certainly on my list of ‘least-liked languages’.

Both articles actually use the same coordinate mapping.
These two functions are equivalent - they express the same thing in different languages using different constructs:

public static function twoDToIso(pt:Point):Point{
	var tempPt:Point=new Point(0,0);
def cartToIso(point):
    isoX = point[0] - point[1]
    isoY = (point[0] + point[1])/2
    return [isoX, isoY]

The ActionScript version uses a class called Point, whilst the Python version appears to be using a list.

Unless you mean when you’d use twoDToIso/cartToIso versus isoTo2D/isoToCart?

What you ultimately need to understand is that the coordinate space your world uses will be a different coordinate space to what the screen uses. Your actual world will likely be a simple 2D grid, whereas what shows up on screen will be a sort of rotated slightly overhead view of it.

People talk about games having ‘cameras’, but there isn’t necessarily a camera object, the reality is that the ‘camera’ is actually a mapping between two coordinate systems - the world coordinates and the screen coordinates. In this case, the above functions represent that mapping from world to screen coordinates, and their inverse would be from screen coordinates to world coordinates.

Don’t get too hung up over what other people’s code is doing.
Understand the principles behind them (i.e. how isometric rendering works), and then express what your game needs in your own way with your own code.

As for where to start, I’d suggest doing things in the following order:

  • Start by figuring out the simple details.
    • What kind of buildings you want in your world.
    • Perhaps how your scoring will work and/or how people win or lose.
    • Ideally write these down as a kind of design document.
  • Then work on representing your world with a 2D array.
    • Maybe start by drawing it with a simple flat 2D tile-based rendering for simplicity.
  • Then work on rendering it in isometric form.
  • Then work on moving a character around the map.
  • Then work on making the map wrap around.

Figuring out the map generation can come later, after you’re actually able to draw and navigate maps.

Ohhh ok, that makes perfect sense, thank you!

I thought the game would be a good fit specifically because the tileset is so limited, there are only 4 building types, although roads need a total of like 15… I wasn’t really worried about the spread or building placement, my only concern was the road generation itself since that needs to follow some logic to generate properly and can’t just be random placement like the buildings (and im not even concerned with how complex the roads are so long as the placement makes sense lol)

…which is why I was just trying to use it as a point of reference since I obviously didn’t know what i was doing lol. I was also trying to differentiate what made the movement different in the firestarter code vs the escaper droid code

…which would be this. And I was also trying to better understand how how should organize code in general since I never properly learned. And the reason why I was focused on the camera part is because I knew I should made/use both screen and world coordinates, just didn’t learn how to properly set that up last time around either. Although that helps to clarify how i would set everything up now, thanks!

Yeah, I realized this, that’s why I ended up deleting my last post asking lol, i had the right idea where I should start getting into it then momentarily lost sight of that trying to outline things a bit lol. I wasn’t particularly worried about map generation or scrolling up front, was just trying to read/plan ahead a bit since I knew that’s where I’d hit a snag. But i just finished redrawing all the tile sprites so next i just need to give myself a quick refresher on sprites/tilesheets then I can get started on drawing/navigating the map. Thanks!

That makes life fairly easy. Particularly if there’s no extra restrictions as to which buildings are allowed to be next to each other.

Theoretically it’s 16, but if you’ll never have a road tile that isn’t connected to any other road tiles (and no variants) then yes it’s precisely 15.

In that case it shouldn’t be too difficult.

One option would be to randomly select a few tiles that would act as main junctions in one step and then decide how to connect them up in the next step. The fewer junction points you have, the cheaper it will be to do. The most expensive part would probably checking to ensure that the roads all connect up so you don’t end up with (for example) two unconnected loops of road.

Looking at the Firestarter video versus the Escaper Droid being played, the difference is that Escaper Droid constrains the droid to the grid and requires it to occupy a whole tile, whilst the characters in Firestarter can roam more freely and occupy a fraction of a tile.

How the buttons correspond to movement in the world might differ as well. Typically isometric code will either make the up button correspond to either moving towards the top left of the screen, moving towards the top right of the screen, or rarely moving towards the top of the screen.

Ironically what’s diagonal in screen space is horizontal or vertical in world space, and what’s horizontal or vertical in world space is diagonal in screen space thanks to the fact the camera effectively ends up facing the world at a 45 degree angle.

It depends whether you’re intending to use top level functions and global variables or classes with member variables, among other things.

Personally I prefer to have things divided into nicely separated classes so I can focus on small parts at a time rather than trying to think about too much at once, and I prefer to divide things into lots of files so I’m not spending too much time scrolling around a giant file hunting for the right function. (LoveTester is a good example because it’s very bare bones, so the structure is more obvious.)

It won’t be quite the same as how you’ll have to do it, but my platformer demo uses a camera along with tile-based rendering. I think I might have a better example somewhere, but I can’t remember where offhand.

If you need a better example I could throw something together.

Your best bet is to write it down. Keep a design document outlining all the game’s rules, what individual tasks you need to do, and roughly what order you need to do things in. It makes life a lot easier, especially if you can tick things off as you go.

Ok, i seem to be having issues getting started… when i try to upload the following code to my Arduboy FX, it successfully uploads then didplays nothing. Or more accurately, freezes on the arduboy fx loader screen (which i would think is due not clearing the screen, but i use arduboy.clear both in setup and my loop). Amy idea what i might be missing here? Sorry im a little rusty

#include "MainGame.h"
#include "GameSprites.h"
#include <Arduboy2.h>
Arduboy2 arduboy;
Sprites sprite;

Vector viewport;
Vector buildingLoc;

void setup() {
  //Initialize things here

uint8_t frame = 0;

void loop() {
  if (!arduboy.nextFrame()) {
  //Game code here
  sprite.drawSelfMasked(buildingLoc.x, buildingLoc.y, building0, 0 );

Use arduboy.begin(); instead of arduboy.boot(); and arduboy.flashlight(); because arduboy.begin(); calls some other useful functions. You should drop down to boot only if you’re running out of space and/or if you definitely know what you’re doing by using boot instead of begin.

75 fps is a bit of an unusual setting, you might want to stick to 60 (which happens to be the default) to start with until you have evidence that a higher fps won’t cause issues.

I think the root of your problem is that you’ve ended up killing off the code that does the actual work within loop because of where you’ve put your curly braces:

void loop() {
  if (!arduboy.nextFrame()) {
  //Game code here
  sprite.drawSelfMasked(buildingLoc.x, buildingLoc.y, building0, 0 );

You should either have:

void loop() {
  if (!arduboy.nextFrame()) {
  //Game code here
  sprite.drawSelfMasked(buildingLoc.x, buildingLoc.y, building0, 0 );


void loop() {
  if (!arduboy.nextFrame())
  //Game code here
  sprite.drawSelfMasked(buildingLoc.x, buildingLoc.y, building0, 0 );

You also don’t need an actual Sprites variable, you can just use the functions statically (e.g. as Sprites::drawSelfMasked instead of sprite.drawSelfMasked).

So altogether you should end up with something more like:

#include "MainGame.h"
#include "GameSprites.h"

#include <Arduboy2.h>

Arduboy2 arduboy;

Vector viewport;
Vector buildingLocation;

void setup()

uint8_t frame = 0;

void loop()
  if (!arduboy.nextFrame())
  //Game code here
  Sprites::drawSelfMasked(buildingLocation.x, buildingLocation.y, building0, 0);


Ohh whoops! Yeah, the example i used for the empty Arduboy program didnt have any curly braces around the return statement so i added them since that screws me up, only i did it in the wrong spot because… well, thst screws me up lol. Thanks!

Ohh, alright… i was under the impression it would be better to make a sprites variable since id be reusing it… is one allocated differently than the other? Seems making a sprites variable would add the variable to rom while using the function statically would use more ram instead (unless i was mistaken, that’s just whst i thought). Thanks!

Hmm… it displays now, but the sprite is showing up scrambled, my guess would be something involving how i exported it in gimp but im not seeing it

PROGMEM const unsigned char building0[] = {
38, 35,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x00, 0xc7,
   0x1f, 0x00, 0x00, 0x80, 0x1f, 0x7f, 0x00, 0x00, 0xc0, 0x7f, 0xfc, 0x01,
   0x00, 0xe0, 0xff, 0xf1, 0x07, 0x00, 0xf0, 0xff, 0xc7, 0x1f, 0x00, 0xf8,
   0xff, 0x1f, 0x7f, 0x00, 0xfc, 0xff, 0x7f, 0xfc, 0x01, 0xf8, 0xff, 0xff,
   0x01, 0x00, 0xe2, 0xff, 0xff, 0x03, 0x00, 0x8e, 0xff, 0xff, 0x01, 0x00,
   0x3c, 0xfe, 0xff, 0x00, 0x0f, 0xf0, 0xf8, 0x7f, 0xc0, 0x03, 0xcc, 0xe3,
   0x3f, 0xf0, 0x0c, 0x0c, 0x8f, 0x1f, 0x3c, 0x0c, 0xc0, 0x3c, 0x0e, 0xcf,
   0x00, 0xcc, 0xf0, 0x80, 0xc3, 0x0c, 0x0c, 0xcc, 0xc3, 0x0c, 0x0c, 0xcc,
   0x0c, 0x03, 0xcc, 0x0c, 0xcc, 0xc0, 0xc0, 0xc0, 0x0c, 0xcc, 0xcc, 0xc0,
   0xcc, 0x0c, 0xcc, 0x0c, 0x0c, 0xcc, 0x0c, 0xcc, 0xcc, 0xcc, 0xcc, 0x0c,
   0xcc, 0xcc, 0xc0, 0xcc, 0x0c, 0xc2, 0xcc, 0xcc, 0xcc, 0x10, 0xcc, 0xcc,
   0xcc, 0xcc, 0x0c, 0x30, 0xcc, 0xcc, 0x0c, 0x03, 0xc0, 0xcc, 0xcc, 0xcc,
   0x00, 0x00, 0xc3, 0xcc, 0x30, 0x00, 0x00, 0xcc, 0xcc, 0x0c, 0x00, 0x00,
   0x30, 0x0c, 0x03, 0x00, 0x00, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x33,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

What sprite function do you use and does the sprite contain a mask or not? also the sprite height should be a multiple of 8 pixels

That’s perfectly legal. The curly braces are only required if there’s more than one statement in the if's body.

(Technically speaking the curly braces actually form a single ‘block statement’, so the if still technically only has one statement as its body, it just happens to be a single statement that consists of a possibly empty list of other statements.)

The compiler optimises the variable away because the variable itself never actually gets used…

Sprites is a class with only static member functions and no member variables, so its size is effectively zero*. sprites.drawSelfMasked would produce the same code as Sprites::drawSelfMasked because they refer to the same function and the sprites object isn’t used (a ‘static’ member function doesn’t involve the use of an instance of the class). And then, because none of the functions actually use the variable, the compiler figures out that the variable is never used and effectively discards it as if it never existed in the first place.

Hence, you might as well just stick to using Sprites::drawSelfMasked because A) you’re effectively doing what the compiler would do anyway (not bothering to make a variable), and B) it’s easier to use Sprites because you only have to #include <Arduboy2.h>, whereas if you use a variable you have to make sure all of your code that needs to use that variable can see that variable.

* It would actually have a size of 1 byte for technical reasons, but I won’t bore you with that now.

You’ve got a broken picture of how RAM and progmem (what you’re calling ROM) are used - it’s the other way around.

A function consists of machine code, so it occupies progmem. If a function is never called (and its address is never taken) then it won’t be included in the output program. If a function is called anywhere in the program it will increase progmem usage because the function will be included in the program.

Global variables are likely to occupy RAM, but it depends on the circumstances. If they are modified anywhere in the program then they are almost guaranteed to occupy RAM. If they aren’t modified (especially if they are const or constexpr) then might be optimised away under the right circumstances, or possibly they might be put in progmem if the compiler is capable of doing so. It all depends on the circumstances.

Local variables may or may not occupy RAM depending on the circumstances. If they are small then they are most likely to occupy registers (small and fast blocks of memory built into the CPU itself), which makes them more efficient. (They can even end up using both at different stages of their lifetime, it depends on the circumstances.)

What did you export it as (.png, .bmp, .jpg?), and which converter did you use to get it into the Arduboy format?

drawSelfMasked… not using a mask of my own. And i thought i had used sprites of whatever size before (though maybe im misremebering), i was just going off the size of the original image.

Oh i know its perfectly legal, doesnt mean it doesnt still mess me up though lol.
And the rest of that makes much more sense now, thanks!

I imported a png to gimp then exported it as an xbm (for some reason i thought thats what i did before but maybe not), i couldnt find an image converter that worked without me needing to install a bunch of other stuff… thought i had exported it straight from gimp before without an image converter though

That’s your problem then.
XBM does not match the format that Arduboy uses. (I found this out a while back.)
Arduboy sprites are stored columnwise, XBMs are (as far as I know) stored rowwise.

Export as .png and pick a converter.

There’s a chance it might work with arduboy.drawSlowXYBitmap (as the ‘slow’ part should indicate, this is a bad idea), or that there’s some really obscure image format that does actually match the Arduboy’s requirements. I expect you’re probably just misremembering though.

Ahh ok, ill mess around a bit more with getting one of the image converters working then, thanks!