(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)
// Ground is solid
// Assume all other types to be non-solid
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.
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?
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.)
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?
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:
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 classstatic 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…
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 :/
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:
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.)
Okay, I think I’ve figured it out. Apparently multi-dimensional array pointers are supposed to look like this:
uint8_t (*ptr) = 3amData;
It seems to be working fine-for now at least
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
(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
I’m just going to wait 'till tomorrow, this is really giving me a headache.