Firestarter (WIP and a couple questions...)

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

Ok, i got one of them working and my sprite is displaying fine now, but when i try to establish a screen vs world space the tile is still displaying relative to screen space. Which i kind of understand since Sprites::drawSelfMasked probably uses 0 as screen 0 as its drawing everything relative to the screen, but i dont want to move the world in relation to the camera so im not sure it would make sense to increment or decrement those values either. This is where i knew id get stuck lol. Heres a repo with my current code:

Yes, Sprites has no concept of ‘world space’, it expects screen space coordinates. You have to make sure the coordinates are properly transformed before you pass them to Sprites’ functions.

Actually, you do sort of want to move the world in relation to the camera, but only for the sake of calculating where the camera is looking.

I.e.

Sprites::drawSelfMasked(buildingLoc.x - camera.x, buildingLoc.y - camera.y, building0, 0);

To translate a coordinate into the position at which the camera will see it, you subtract the camera’s position. (Assuming the camera’s position refers to the top left of the viewport.)

(Note that the building moves opposite to the direction you press on the direction buttons because it’s the camera that’s moving, not the world.)

Ordinarily I’d draw a diagram to illustrate why this works, but I’d have to dig out my scanner and it’s getting late. I can do one either tomorrow or the day after if need be though.

Edit:
I knocked these together in paint…

This first image represents ‘world space’, including the camera’s coordinates and an object’s coordinates, before being translated into screen space.
WorldSpace

This second image represents screen space, which is what you get after subtracting the camera coordinates from the object, thus making the coordinates relative to the top left of the camera (i.e. the origin point, point (0, 0), of screen space).
ScreenSpace


Some other points:

  • Don’t call arduboy.flashlight in setup, that’s arduboy.begin's job (i.e. you’re effectively calling it twice).
  • You don’t need to call arduboy.clear in setup because you’re calling it in loop. (It does make a difference for the first frame, but nobody is going to notice.)
  • arduboy.setFrameRate(60); is technically redundant because 60 is the default fps, but if you’re planning to attempt to use a different framerate then it’s up to you whether you leave it in.
2 Likes

Ahh ok, that makes sense. And the rest of that is super helpful as well, thanks!

1 Like

Are there resources out there that actually explain how to create a tilemap using a 2D array? I spent quite a bit of time searching the forums for an answer but it seems all i could come.up with were theads confirming thats what i should do, though unfortunately nothing that explained how to actually do it. Thanks!

That really depends on what you mean by ‘create’. I.e. whether you mean the basics of drawing the array or whether you also mean loading the map into memory and so forth.

The absolute simplest way to render a tile map is to just have a 2D array of uint8_t, and then iterate through the whole array rendering each tile one by one, deciding which number represents what kind of tile.

I.e. it’s really just something like:

// A 2D array of tile types/tile indices
uint8_t world[height][width] { /* Fill the map data in as necessary */ };

void drawMap()
{
	for(uint8_t y = 0; y < height; ++y)
	{
		// Calculate the y position to draw the tile at
		int16_t drawY = ((y * tileHeight) - camera.y);
		
		for(uint8_t x = 0; x < width; ++x)
		{
			// Calculate the x position to draw the tile at:
			int16_t drawX = ((x * tileWidth) - camera.x);
			
			uint8_t tileType = world[y][x];
			
			// Assuming that your tile types are the same as
			// the frames used in your tilesheet. Otherwise
			// you'll need a way to determine the sprite index
			// from the tile type.
			Sprites::drawOverwrite(drawX, drawY, tilesheet, tileType);
		}
	}
}

Note however that drawing the whole map is inefficient. It’s much more efficient to only draw the tiles that are actually on screen if you can.

(Personally I also prefer to use enumerations so the tile types are named rather than just remembering what the numbers stand for, but that adds a bit of complexity.)


Crait gives a basic example in the Dino-Smasher tutorial:

Note that in addition to the shortcomings of my earlier example, this version also wastes twice as much memory by using int instead of uint8_t/unsigned char.

There’s also this thread:

Which isn’t a tutorial but does involve and discuss making a tile map, and resulted in this simple platformer demo:

This demonstrates drawing only the visible tiles and using an enumeration type to represent the tiles. (If you skip to the map drawing code you’ll see it’s effectively a more embellished version of the code I gave at the start of this reply.)

I also stumbled upon this:

Which was supposed to be demonstrating using hash functions to generate map tiles, but happens to include tile map rendering code. This demonstrates only drawing the visible parts of the map but doesn’t use an enumeration type (partly because all the tiles in this case are just different arrangements of stars).


I went looking for some external ones, but nearly all the ones I found were far more complicated than what you need, or were too concerned with doing things ‘the OOP way’, or wasted too much memory because they’re designed for desktop computers rather than embedded systems.

Even the best one I could find uses tiles that are comprised of 4 sub-tiles and starts talking about Perlin noise halfway through.

This one from Mozilla is alright, though it’s JavaScript so it will look a bit weird compared to what you’d need to do in C++. (E.g. JavaScript doesn’t really have a concept of ‘integers’, hence floor crops up a lot. If you need the floor function to draw a tile map in C++ then you’ve possibly gone wrong somewhere.)

This one from tutsplus covers some useful theory, but the actual implementation is a bit questionable (and it’s ActionScript again).


In future if you can’t find a decent tutorial get a pencil and paper out and think about the problem.
Even a half-baked solution that sort-of works is better than nothing.

1 Like

It’s not a tutorial, but since you’re not the first person to ask me about making a tile map lately, I decided to make a demo that I can point people to.

Hopefully this will help. If you have any questions about it, feel free to ask.

1 Like

Awesome, thanks so much! Yeah, i had looked through all the aforementioned links (even both games you helped me with since they both employed a camera system) but still wasnt wuite seeing it- that definitely helps to clarify though, thanks!

Hmm, im a little unsure how to proceed since my building sprites arent all on the same sprite sheet (because they differ in size) so it wouldnt be as simple as populating the array with random frames… that and im unsure what to make my world size since the sprites are originally 38x36 (and even if i halved that would only fit 13 tiles which doesnt seem like much, so i might need to use a uint16_t instead)… also i dont want to draw my map the same way im creating it (since its supposed to be isometric) so that’s tripping me up a bit too

Sorry, I haven’t read all the thread in detail but can you convert each sprite individually with this converter > Arduboy Image Converter

With regards to the size constraints - welcome to the world of 128x64 pixel graphics. Its always a compromise between image fidelity and real-estate.

Then you’ll have to make do with arrays. E.g.

#include "Sprite0.h"
#include "Sprite1.h"
#include "Sprite2.h"
#include "Sprite3.h"

// For uint8_t
#include <stdint.h>

constexpr const uint8_t * tiles[]
{
	sprite0,
	sprite1,
	sprite2,
	sprite3,
};

And then instead of Sprites::drawOverwrite(drawX, drawY, tileSheet, tileIndex); you’d be doing Sprites::drawOverwrite(drawX, drawY, tiles[tileIndex], 0);.

(This is actually closer to how it would be done on desktop, it just happens to be the case that the other approach is cheaper on Arduboy.)

Though drawing your isometric tiles using this grid system won’t look pretty. The idea is just so you have a starting point and then you can either decide whether to try to upgrade to isometric or get some of your game logic working first.

If you want to go straight into figuring out isometric rendering then stick with your current images. If you want to work on the logic, you might want to throw together a few 2D placeholders (e.g. plain tiles with letters on them) to work with in the mean time.

I have absolutely no clue what you mean by this.

It’s isometric, but it’s still a grid. Whether you use a flat top-down view, an isometric view or even a 3D view, something with a 2D grid structure will still have a 2D grid structure.

The logic - a 2D array of tiles/buildings - doesn’t change. What changes is the rendering, and perhaps how the character moves around it.

To put it another way, if you had a carton of six eggs arranged 3x2, no matter how you rotate the carton it still has the same 3x2 arrangement of holes.

The same is true for the grid-based city in Fire Starter - whether you draw it top-down or with a side-on isometric view, the city still runs on a grid.

In fact, if you wanted you could do both an isometric and a 2D render of your city within the same game, be it as a mini map, for debug purposes or just to prove to yourself that it’s possible (and perhaps see the relationship between the two).

Thats actually the one ive been using, thanks!

Ah ok, that makes much more sense. Sorry, like i said im still pretty rusty on a lot of this stuff so i couldnt quite remember how id use it in an array (compared to a 2d array like im using for my map), thanks!

I think id rather start with getting the isometric part down first since thats what i anticipate will be one of the more difficult parts (aside from generating roads on the map), then once i get those parts out of the way the rest should go a lot smoother. I can just make 2d placeholders for now, just need to think a bit more on how the proportions would work.

Sorry, that was a typo, meant to say since my tiles are 38 wide even if i made them half the size i could only fit 13 if i used a uint8_t (which seems like itd be a small map) so i might want to use a uint16_t instead, but ill have to see how big it actually looks first anyway. Thanks!

Your best bet would probably be to make some mockups in an image editor (e.g. GIMP or MS Paint) and then cut away a 128x64 pixel window to see what sort of view you’d get on the Arduboy.

Perhaps even convert the mockup to a sprite and draw it on the Arduboy screen to give you an idea of what it would be like on-device.

Use a uint8_t/uint16_t for what? Player coordinates?
If so then yes, you’ll probably want either uint16_t or int16_t for your world coordinates.

Yeah thats what ive been doing (both of those things lol) but 13 tiles would be too large to all fit on screen anyway so i wont know how it looks til its drawn in real world coordinates and i can navigate the camera around it. But if it seems small once i get there then i can make it bigger and use a uint16_t instead. But i just made placeholder sprites for the buildings so i can populate the map and draw them in 2d first then work from there (since im still not entirely clear on how to translate that to isometric yet anyway)

Ok, i know the answer is out there but im having some difficulty sifting through all the information to find it (and when its going on a couple gohrs sesrching for something that someone else could probably answer in 5 minutes then im just gonna ask that question, sorry lol)…

…but i seem to be having trouble connecting the dots here. Since my 2d tiles are all the same size im able to just store them in an array and access to frames now, i also see how to get a random number between 0 and 3 (to represent the 4 building placeholder sprites) but i cant quite figure out where i would actually populate the world array (although im guessing it would look something like how im drawing it using for loops, right?). Ive updated the repo with its current state but nothing displays (since i know im not populating it yet)

Edit: Whoops, actually hang on a sec, now i see it in the tilemap demo you posted, sorry. Though im not sure i understand your use of static casting to tile type to random (both what static casting is and why you used it)

Actually, i am confused, i see where you generate the map and where you draw it, but im a little confused how youre using an enum class for TileType since i just need it to pick a random one of my four frames instead. Which i thought was established in your generateMap function but then i get an error when i compile pointing to TileType in that line

If you just want to randomly fill an array for now then:

The static_cast converts the random number generated by random to a TileType value.

‘Under the hood’ enumerations are secretly integers (or rather, they are represented by integers). In my tile map demo I explicitly tell the compiler to use uint8_t as the ‘underlying representation’ (i.e. the integer type) that the compiler will use to represent them. The named values (‘enumerators’) themselves then naturally take on values starting from 0 and increasing with each new declared value.

Thus Dirt0 actually has a value of 0, Dirt1 has a value of 1, Dirt2 has a value of 2 and Rock has a value of 3.

However, the data type is more than just its representation, so that doesn’t actually mean anything until you decide to either coerce an integer type into an enumeration type or an enumeration type into an integer type.

And that’s what static_cast does - it says to the compiler "trust me, I know what I’m doing, please pretend this integer value is actually a (for example) TileType" or vice versa.

Thus it’s possible to convert an integer that is in the suitable range (in this case between 0 and 3, inclusive) to the appropriate enumeration value.

(All the CPU is really doing is moving a few bits around (possibly discarding some if the source type is larger, or zero-padding in the opposite case), the conversion is there for the benefit of the programmer and the compiler.)

Without the code or error I couldn’t tell you want’s wrong.
(Though with enough context I could start to make guesses.)

Are you actually using TileType in the rest of your code?

The latest version on your GitHub isn’t using it anywhere, whereas in the demo I’m actually using it to define the tile map:

If it’s not being used consistently where needed then you’ll get errors.
(That’s not a bug, it’s a feature. That’s what stops you from writing nonsense like Tile::Rock + 5 Though you can opt in to that sort of behaviour if you find a need for it, but usually a getNextNthTile(Tile::Rock, 5) function would be better from self-documentation point of view, and because it allows you to have ‘wrap around’ behaviour.)

If you’re not using enumerations yet, then you don’t need the cast at all, you can just take the output of random() % numberOfTileTypes.

1 Like

Hmm, ok.

The code in the repo is up to date

No, thats what i was trying to say- im not using it because im trying to just use different frames of the same sprite

Ahh ok, ill try that. Thanks!

Sorry, i still havent had a chance to get back to this yet but was actually doing homework and the lesson was on switch statements so it got me wondering about enumerations… i think i understand why/how youre using them in your tile demo now but just wanted to clarify to be sure im following along correctly- basically youre choosing a random integer then casting this to a tile type (similarly to a list index) and the names Dirt0, etc are arbitrary/more for our sake to identify it when its also using the integer it just cast as a frame index in the sprite sheet (when it actually draws it). Unless im mistaken?

Also wanted to ask, why use enums and not just use a list? Came across something that said…

“Enums or enumerations are generally used when you expect the variable to select one value from the possible set of values. It increases the abstraction and enables you to focus more on values rather than worrying about how to store them. It is also helpful for code documentation and readability purposes.”

…but even after looking up more examples of enums i wasnt really seeing where/when it might be preferable so just figured id ask since i was curious

More the latter than the former.

When you use an enumeration it’s usually the actual underlying value that’s arbitrary and the name that’s the important part.

For example, you might have an enumeration that represents the days of the week:

enum class Weekday
{
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday,
	Sunday,
};

In this scenario you absolutely do not care how each day is represented, all you care about is that each value is distinct and that the data type itself is a distinct data type - i.e. that a Weekday is not just a plain old integer type because it doesn’t represent a number, it represents a day of the week.

For the most part, the same is true of a tile type. Ordinarily you don’t care what number is used to represent the tile, you only care that each tile value is distinct and that tiles are not integers, they are a distinct type of their own.

There are however certain scenarios where it’s justified to start making the actual numerical value significant, which I’ll get to in a moment…

I’m not quite sure what you mean by ‘a list’, so I’m going to continue by assuming you mean “Why not use integers?”.

I’m going to quote exactly what I said a few sentences ago:

you only care that each tile value is distinct and that tiles are not integers, they are a distinct type of their own.

If you start using integers to represent what should actually be a distinct datatype then you’ll start running into some problems.

Let’s pretend you’re using integers to represent weekdays for a moment. What is wednesday + 5? Here’s a clue, it’s not monday. It’s gibberish! What about monday * 10? Well, that’s even worse.

Days of the week are not integers, you shouldn’t be able to do arithmetic with them. You might want to work out something like “if today is wednesday, then what day will it be in 5 days time”, but that’s not the same thing as trying to work out wednesday + 5. It might look it, but it’s not.

If you use an enumeration (a proper enum class rather than an enum) then Weekday::Wednesday + 5 doesn’t compile. The compiler refuses to work with such nonsense, as well it should.

The same is true of tile types. Grass isn’t an integer, neither is dirt or a wall. TileType::Wall + 5 does not equal TileType::Dirt, it equals a compiler error.

This is part of an idea called ‘type safety’ - the idea that a data type should only allow you to perform operations on it when those operations make sense.

This is entirely correct, though I’ll clarify a few things.

When it says “focus more on values rather than worrying about how to store them”, what it really means is that you should focus on the things that the code is trying to represent (the tile types, the week days) rather than worrying about what underlying integer values are being used to represent them.

When it says “abstraction”, what it really means is that it allows you to write code where you can forget about those underlying integer values and pretend that for all intents and purposes you’re talking about days of the week or tile types.

The reality is that computers don’t understand days of the week or tile types. Computers are machines that operate on binary data, so anything you want them to process has to somehow be encoded in binary data. Abstraction is what allows humans to ascribe meaning to that data. It’s what allows us to say ‘0 means monday, 1 means tuesday, 2 means wednesday…’, and it’s what allows us to sometimes not care what the actual binary value is, to only care about what that value means to us humans. Sometimes the value does matter, but most of the time it’s the meaning we ascribe to it that’s the important part. That ‘ascribing of meaning’ is what data types achieve. A data type is what tells you when something is an integer and when it’s something more meaningful, like a day of the week or a tile type. A data type is a form of abstraction.


Now, that all said, there are cases where you might want to care about the actual value of the enumeration.

In this instance you have two such cases.
The first is that you need an integer value to select the appropriate sprite for the tile.
The second is that you need a way to use a random integer value to select a tile type (to be used when randomly filling the map).

There are actually other ways you could do this that would allow you to ignore what the actual tile values are. For the case where you need to select the appropriate sprite, you could have toTileIndex use a switch statement that gives back the right sprite index for the given tile type, and for the case where you’re randomly generating a map you could just have an array of tile values and use the random integer to pick from that list.

However, both of those approaches would consume more memory. If you were on a desktop system that wouldn’t really be an issue, you’d pick what to do based solely on design concerns, not technical concerns, but when you’re working with a constrained system you sometimes have to abandon good design in favour of what’s going to use less resources, and that’s exactly what’s going on here. Instead of ignoring the underlying value used to represent the tile types, that value is being exploited to produce code that’s more efficient than it otherwise would be.


You introduce a new data type when your data is not meaningfully representable by any of the data types you already have at your disposal.

You use a data structure (a struct) when the data you want to represent is an aggregation of several distinct components. (E.g. a point in space is an aggregation of coordinates - x, y, maybe z, an entity within a game (position, type of entity, …), a card in a game of Poker (suit, type)…)

You use an enumeration when the data you want to represent has a limited set of distinct values. (E.g. biological sex, days of the week, the state of a cell in Noughts & Crosses, the state of a hand in Rock Paper Scissors, the suit of a card in Poker…)

(There are other uses for enumerations, but let’s save that for another day. At least until you’ve learnt about bitwise operations or have a need for bitflags.)