A little help with a tile-based map?

I was going to limit myself to three threads, although I think that was quite unrealistic :stuck_out_tongue:

So far I’ve made a simple array of map data, but now I’m clueless. How do I draw the map? How do I give properties to each specific tile? (Besides ‘sky’ of couse)

I am aware that mapData.cpp is currently empty, that’s just there if I need it in the future since I already made it when testing a few things.

Thanks everyone!

(not the definitive repo for my game, just for the unfinished ‘i need help’ state)

Also on the side, is there a way (or a library) that enables you to draw sprites from images? (PNGs, JPEGs, etc.)

Ah, finally my foresight pays off…

(Incidentally, that thread and the linked repo are 1 year and 5 days old.)

By ‘properties’ I presume you mean things like whether it’s solid or breakable.

It depends whether you want to be able to change the properties of a tile on a per-tile basis or only for a particular type of tile. The former requires finding a way to encode the properties within the inidividual tiles, the latter is easier and uses less memory because you only have to record the type of a tile and then you can use functions to determine the properties of the tile type.

E.g. you could have an isSolid function for determining if a particular tile type is solid or not:

bool isSolid(TileType tileType)
{
	switch(tileType)
	{
		// Ground is solid
		case TileType::Ground:
			return true;

		// Assume all other types to be non-solid
		default:
			return false;
	}
}

As for how those properties actually function, you have to manually include the logic for that in your code. E.g. for a tile to be solid, you have to ensure that your movement code checks whether or not a tile is sold and prevents a character from walking inside a tile that’s supposed to be solid.

Theoretically the Arduboy could read something in PNG/JPEG format, but it would be a horrendously inefficient use of CPU and memory (and writing the code would be way too much effort - despite being ubiquitous, those formats are pretty complicated).

You’re much better off just converting them to the Arduboy’s native format. There are many converters available:

(Though you likely already have AB Sprite Editor installed. Now would be a good time to find out how to use it.)


Just to mention it:

  • That game object is unused, you already have a Game instance in your main .ino file.
  • It probably makes more sense for the gameState and arduboy variables to be members of Game, but it depends how comfortable you are with accessing member variables and getting other states/code to access those variables.
1 Like

You my friend, are a legend. This will help with the map and possibly the camera!

:flushed:

Coincidence? I think yes.

Oh, right. Does that mean I should put the objects (e.g.: Game game) at the bottom (after the class) in the header files?

1 Like

I suspect February is just a popular time for people to start programming projects.

No, you put the variable definition inside the Game class definition, and then you access it via the game object (e.g. gameObj.arduboy, gameObj.gameState).

I thought from the use of static const you’d know about member variables, but if not then maybe it’s best to leave them as globals for now.

(Though you will need to extern them later if you plan to use them in other files.)

1 Like

Perhaps there was a bit of a misunderstanding. I meant the object for the class, not for other things (like arduboy2). I’m tired of having multiple different object names, like in the .ino file, gameObj and in the game.cpp file, game.

I have another quick question: How could I integrate the camera stuff according to the currently loaded map? Quite a crude question, I know. What I mean is, right now, camera positions and other values are based on a certain map. The boundaries of the camera are also accordingly set to the first map’s height and width. Should I maybe just reset them manually after passing a level, and load the next map? Or is there a way to, possibly, get the current map, and base the camera positions, boundaries, etc., based on that?

Those aren’t different object names, those are entirely different objects.

The game you created in game.cpp is an entirely different object to gameObj, that’s why I was advising you to get rid of the one that wasn’t being used.

The way you’re supposed to use globals is to declare them (with extern) in a header (.h) file and then define them in a source (.cpp or .ino) file.

The extern allows other files that include the header to be aware of the existance of the variable without causing it to be defined multiple times. It’s a bit silly to need to do that, but C++ was based on C which was created during a time when computers were less powerful and compilers were less well-researched, so the way the header and source files work is a bit awkward and archaic.

Really it depends on where you’re storing your map. Are you:

  • Keeping the map in progmem
  • Keeping the map on the FX
  • Either of the above, but then loading in RAM to make it mutable (e.g. for things like breaking blocks)
  • Procedurally generating the maps into RAM

The example code I linked to keeps a map in RAM and pseudorandomly generates it on start-up - that’s by no means the only way to do it, there are plenty of others, and depending on what you’re actually planning to do you might need to do something different.

If by ‘boundaries’ you mean you’re clamping the camera so it won’t look outside the map, then yes those boundaries will need to be updated whenever you change map.

Depending on where you’re storing the maps you can either fetch that data directly from the read-only copy of the map in progmem/on the FX, or you can keep the map dimensions in a variable.

However and whenever you get your map dimensions, you’d just use them to limit the camera’s range of movement during the appropriate camera movement code.

Unless by ‘boundaries’ you’re referring to the part where the map is rendered and you’re trying to prevent the rendering going out of bounds?

1 Like

I know, that’s exactly the reason why I need to use different names.

:+1:

Currently keeping it in the progmem. I decided this platformer is a big leap already, and using the FX library won’t really make things any easier :wink:

I’m referring to the clamping.


I’ve updated the repository with the drawing code, but for some reason it just doesn’t want to work :( Nothing’s being displayed.

Also I was just doing my french homework and I was able to type ‘chocolate milk’ in french perfectly :slightly_smiling_face:

If you’re going to be having multiple maps in progmem and they’re going to be different sizes then you’ll need a way to track which one you’re using.

The way you do that is with pointers.

(I can’t remember how much I told you about pointers, but they’re basically slightly more advanced RAM addresses - more advanced because they remember the type of the object they point to.)

You’d use a pointer to point to the current map.

Getting the size of the map is a bit more awkward, because there’s several ways to do it and most of them have some kind of limitation or inelegance.

A few examples:

  • You could store the map size at the start of the map, which is efficient in terms of being able to easily access the size and in terms of efficiently storing it, but requires the array to be a plain old uint8_t/unsigned char array, which makes encoding the map a little bit more awkward, unless you’re prepared to write a map maker or a tool that generates the array using some form of input (e.g. takes an image and interprets specific coloured pixels as certain tile types).
  • You could store the map size and a pointer to the map data in a separate struct. This allows you to retain the use of TileType in the map definition, but adds an inefficiency in that you have to store a pointer to the map that otherwise wouldn’t be needed with the above technique. (Though thinking about it, it might be needed anyway, since you’ll likely want to construct an array of pointers to be able to properly sequence/index the maps.) Another advantage here is that the struct could be kept in either RAM or progmem, and if you keep a copy in RAM as your way of tracking the current map then it’ll already have the dimensions with it, which can be quite handy.
    • I did something similar with the Map class from my old platformer demo. You’re free to use that, provided you stick to the rules of the licence. Though it may need modifying to suit your needs. I don’t really know anything about what kind of game you’re trying to make other than “it’s a platformer”, so I can’t really be specific with advice without knowing more.
    • However, one thing that complicates matters here is the fact you’d be keeping your map data in a 2D array that might vary in either dimension, and to handle that with an ordinary pointer requires you to do some extra caculations for the indexing. I.e. you have to do pointer[x + (y * width)] to get the proper index, you can’t just do pointer[y][x].

Your map is stored in progmem, but you’re trying to read it as if it were in RAM.

To read data from progmem you have to use the special progmem reading functions.

In this case you need to do static_cast<TileType>(pgm_read_byte(&mapData.map0Data[tileY][tileX])).

  • mapData.map0Data[tileY][tileX] would ordinarily read the data as if it were in RAM, but &mapData.map0Data[tileY][tileX] instead gets a pointer to that byte of data, regardless of whether it’s in RAM or progmem.
  • pgm_read_byte takes the aforementioned pointer and reads a byte of memory from progmem at the address the pointer specifies.
  • static_cast<TileType> takes the aforementioned byte and converts it into a TileType value. (Technically this produces no machine code, it’s only for the benefit of the compiler, so it knows you have a TileType.)

Note that the map I had in the tile map demo was being kept in RAM, hence the absence of any progmem-reading code. The platformer demo on the other hand is keeping its map in progmem and thus uses progmem reading code to fetch tiles, though it’s broken down into several steps: calculating the absolute tile index from a pair of coordinates, getting a pointer to the tile at the specified absolute index, and reading a tile from progmem.


I know that hot chocolate is ‘chocolat chaude’ in French, but I don’t even know what ‘chocolate milk’ is. As far as I’m aware we don’t really have it here. (I only know it exists because of cartoons.) Unless it’s a chocolate milkshake?

1 Like

:man_facepalming:

Why do I always read data incorrectly

Um. Chocolate milk is just . . . uh . . . chocolate flavoured milk :moyai:


Looks like the map data is corrupted or something.

Untitled-Platformer.ino-arduboy.hex (19.0 KB)

ArduboyCapture

Got this exact same sussy image on my Arduboy too.

This is using TileType tileType = static_cast<TileType>(pgm_read_byte(&mapData.map0Data[tileY][tileX])); btw

I think I found the problem.

I would have found it sooner but my Arduino extension is playing up for some reason so I had to boot the IDE instead.

Anyway, you’ve ended up declaring your map as a non-static member variable of a class, which means it can’t be put into progmem. To fix that you’d have to make it static. You also have to mark it constexpr so the compiler will allow you to initialise the data in-place because of the rules for static member variables.

I discovered the issue by reading the warnings; the first warning says:

\mapData.h:31:9: warning: ‘__progmem__’ attribute ignored


After that there’s a linker error though…

Honestly, I think it would be far easier to just take it out of the MapData class, or to turn MapData into a namespace (in which case you’d have to say MapData::map0Data instead).

1 Like

I get this other weird looking map after making it static
Untitled-Platformer.ino-arduboy.hex (19.2 KB)

ArduboyCapture(1)

It wont let me make it constexpr too, because tileY (and most likely tileX as well) can’t be used in constant expressions.

Arduino: 1.8.19 (Mac OS X), Board: "Arduboy FX, Arduboy optimized core, Cathy3K (starts with menu)"

/Users/morbius/Downloads/ARDUBOY/Untitled-Platformer/game.cpp: In member function 'void Game::drawMap()':
game.cpp:69:118: error: the value of 'tileY' is not usable in a constant expression
             static constexpr TileType tileType = static_cast<TileType>(pgm_read_byte(&mapData.map0Data[tileY][tileX]));
                                                                                                                      ^
/Users/morbius/Downloads/ARDUBOY/Untitled-Platformer/game.cpp:53:17: note: 'uint8_t tileY' is not const
     for(uint8_t tileY = 0; tileY < MapData::map0Height; ++tileY)
                 ^~~~~
exit status 1
the value of 'tileY' is not usable in a constant expression


This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

What do you mean about ‘taking it out of the MapData class’? The map data itself?

You’d have to make the map inside the MapData class static constexpr, not the local tileType variable.

Any way, I decided to solve it with the namespace approach instead…

Here’s a commit that gets it working:

It needed half a dozen other steps because of other complications. E.g. as soon as I tried to put it in a namespace the fact cameraX and cameraY were global variables became an issue so I had to extern them and add a definition to the .cpp file.

(To be honest, I’m not even sure why your camera coordinates are mixed in with your map data. The camera might depend on the map data, but it’s not actually part of the map data, so really it belongs with your game code.)

I haven’t really mentioned namespaces before. Anything inside a namespace behaves more or less as it would if you declared/defined it outside of a namespace, except that to refer to it from other code you have to use the namespace’s name as a prefix along with the :: operator. An easy way to think of namespaces is that they’re a bit like folders for classes, functions, global variables et cetera, and the :: is a bit like a / in a file path.

There are other ways to do this, but I went for this approach because it was quick and easy and seemed closer to what you were already trying to do (and it gets rid of some of the complications of trying to have lots of static constants inside a class).


I feel like I’m glossing over things a bit, but it’s 11pm and I’m tired, so I’ll settle for handing you something that works.

Although looking at it I think the camera coordinates might possibly be looking away from the map. Commenting out the camera coordinates (/*- MapData::cameraY*/ and /*- MapData::cameraX*/) causes it to render properly, as does simply initialising cameraX and cameraY to 0…

1 Like

Lucky. I have to sleep at 9:30 :moyai:


Thanks for the help, I really appreciate it!


Any idea why the ground is at the middle-ish level of the screen?

I’m so dumb. The map is half the height I expected because I used a 4 when I should have used an 8. My bad hehehe

Not lucky at all. You have no idea how much I wish I could get to sleep earlier in the night.
(Maybe not 21:30, but 22:00 or 22:30 would be really nice.)

Depending on where the 4 was that might explain why the camera was off-screen before.

I don’t sleep very much for my age. Sometimes I wake up at 7:00 or 8:00 when I slept at like 11:00 the night before, and I’m not really tired. I think it has something to do with sleep ‘zones’ or ‘patterns’ or whatever they’re called.

The 4 was the height of the 2d map array. 64/8 (the tile size) is 8, not 4. I think I was under the assumption I was using 16 pixel big tiles.

Now looking at it, I did indeed make the array size 8. Looks like I just forgot to add the last 4 rows :/

Might I ask, what role exactly does the camera (cameraX, cameraY, etc) play? And how could you combine that with a player controller to move the map around?

Circadian rythm” probably.

That would explain it. TileType::Sky is the 0 value, so any uninitialised global TileType value will default to TileType::Sky, which is blank.

Firstly, I’ll refer you to this old comment:

Basically the idea is to make the screen represent a virtual camera into your virtual world.
The camera has a position in the world as if it were a real object, and is then used as a reference point for rendering.

Fundamentally the idea underpinning the use of a camera is the idea of having several different coordinate systems in use:

  • World coordinates (“world space”) - the coordinates that represent the positions of virtual objects inside your virtual world
  • Camera coordinates (“camera space”) - the coordinates that represent the positions of objects in the virtual world relative to the position of the camera.
  • Screen coordinates (“screen space”) - the coordinates of pixels on the screen.

And then there’s calculations that map each coordinate space to or from another.

There’s several different ways you can do things depending on what you find easiest/most useful. E.g. you could have the camera’s position represent the top left corner of the camera’s viewport, or you could have it represent the centre of the camera’s viewport.

If the camera is using its top left as the origin then camera space and screen space are effectively the same thing.

Some crude drawings to get the idea across:

World space:
WorldSpace

Camera/screen space:
ScreenSpace

I’m avoiding handing you any calculations simply because I don’t know which approach you’re intending to take or what sort of things you’d want to do with the camera.

The system you’ve got in place at the moment has the camera coordinates be the top left of the viewport (thus avoiding an extra transform from camera space to screen space).

There’s several ways depending on how you want the camera to behave.

You could make the camera always follow the player, or you could make it follow the player until the player gets near the bounds of the map and then clamp the view so the camera never looks outside the map, or you could make it even more conditional and have the camera behave differently depending on various different circumstances (which is one of the reasons larger desktop/console games sometimes have a dedicated camera programmer - to make sure the 3D camera behaves well). Half the point of having a camera with coordinates separate to your player is to allow for that sort of independent movement.

In the case of a top-left origin camera like the one you’ve currently got, you can centre the camera on the player with a little bit of simple maths: just subtract half the camera width and height from the player’s position. (I.e. camera.x = (player.x - (Arduboy2::width() / 2));, and likewise for y.)

1 Like

Never have I ever thought this would give me so much trouble.

I’m declaring an extern pointer like this:

extern TileType *currentMap;

Then defining it like this:

TileType *currentMap = map0Data;

I get the error, however, that it cannot convert a const TileType to a TileType *?


By defining a pointer-to-const like this:

TileType const *currentMap = map0Data

I get the error that it cannot convert a type ‘TileType()[24]’ to ‘const TileType.’

The 24 is the array width fyi.


I tried putting the const before the type (TileType) but still got the same error :(


Maybe I can cast it as an int to an int pointer then read it from progem and cast it to TileType when it is being accessed.

I get the error that TileType is not a castable type unfortunately :moyai:


Maybe I could just make references to all the map data then make a pointer to point at a reference?

Btw I updated the repository with the latest code.

Okay, I think I’ve figured it out. Apparently multi-dimensional array pointers are supposed to look like this:

uint8_t (*ptr)[8] = 3amData;

It seems to be working fine-for now at least :wink:

Oh, well. Did I really expect things to finally go my way? sigh

I get the error that it cannot change the pointer value (using the dereference operator (*)) because it’s ‘read-only data,’ which means my pointer-to-const isn’t working :(. More like a const-pointer-to-const. Anybody have any ideas about this?

:/

My bad, I wasn’t supposed to use the dereference operator. I was going to go rant about my success in the devlog right after posting this but I guess I’ll spare you guys :wink:

(And yes, I am indeed able to change the map during runtime)

Okay, things have gone astray yet again. Turns out I need to keep both maps the same width, otherwise the pointer wont work :slightly_frowning_face:

I’m just going to wait 'till tomorrow, this is really giving me a headache.