A little help with a tile-based map?

I’m guessing this is your attempt at finding a way to change the map whilst retaining both map dimensions?

If so then you should know that given:

uint8_t array[height][width];
uint8_t * pointer = &array[0][0];

The following is true:

  • array[y][x] == pointer[(y * width) + x]
  • &array[y][x] == &pointer[(y * width) + x]

I.e. when you index a multidimensional array with [y][x], the compiler treats the multidimensional array as a unidimensional array and generates code equivalent to [(y * width) + x]. You can do the same thing manually - translating two coordinates into a single flat index.

You could probably save yourself a lot of trouble if you have a poke around my old platformer demo. In this case I expect what you want is this bit:

There’s a few things you probably won’t understand, but focus on getIndex and getTile.

1 Like

I’ve stolen added in some basic collision, but it just doesn’t want to work :pensive:

The repo (GitHub - ArdFlamingo/untitled-platformer: idk) is updated and the current builds are in the build folder.

Is it maybe because getTile is inline or something?

I’m confused about a few things:

  • Are your player coordinates screen coordinates or world coordinates?
    • You appear to be drawing the map using the camera’s position but ignoring the camera when drawing the player.
  • What are the isLeft and isRight for?
  • You seem to be trying to move the player twice, once in the camera updating function and again in the collision code.
    • This possibly why the collision code isn’t working: the collisions have been detected and the player has been prevented from moving, and then you’re effectively ignoring that and moving the player anyway.
  • In the collision code you’re trying to change the velocity to 0 after the new positions have already been computed.
  • I don’t know what the significance of any of the ‘magic numbers’ are. (E.g. 58, 119, 64)

Also, is your player’s x and y the top left corner or the centre?

I think my platformer demo used the centre, which is likely why there’s a lot of halfTileHeight and halfTileWidth, which might be affecting things.


In the version you uploaded it isn’t.

Though I’ll note that:

  • You shouldn’t mix inline with extern, they contradict each other.
  • You don’t need to extern functions.

inline is needed on functions when you define them (i.e. provide the function body) inside a header.


Actually, I’ve found your problem.

I had a theory, so I wrote a quick function to print which block you’re travelling into, and the game always thinks you’re moving into a Sky block.

“Why is that?” I wondered. Then I saw this…

And this:

Spot the problem.

In fact, the fact you’re setting data to nullptr is also contributing:

Even if width and height held a correct value, data is a nullptr so you’re attempting to index a nullptr.

That’s part of the problem at least.

It’s definitely the main problem. I was having some issues getting it working by modifying the MapData code, but it turns out I was modifying the wrong copy of the file. Fixing that definitely fixed the collision problem, though as suspected my platformer was using the centre of an entity as its coordinates whereas yours is using the top left, so the collision code will still need to be modified.

It’ll also need to be modified to account for the use of float.

1 Like

I think screen coordinates. How might I make them world coordinates?

Those are flags for the camera to check if the camera is on either end of the map, and if so, the camera gets clamped, among other things.

The camera updating function is a function from the Camera class, so the x and y are actually the camera’s coordinates.

Those were temporary when I was testing the camera. Still not really done the camera, but I was meaning to switch those out with variables after.

The top left, but I guess I could try to make them centred.

I meant the version of getTile in your 2d platformer demo.

:flushed:

That’s how it was in the 2d platformer demo.

That was also how it was :flushed:

I don’t remember resetting the velocity

Those parts are, but the rest isn’t.

In my 2D platformer demo, the data pointer and the width and height aren’t just left as they are, they’re updated by other code so that the data points to the map and the width and height are the map dimensions (in terms of the number of horizontal and vertical tiles the map has - i.e. the number of columns and rows).

You brought parts of it across, but unlike the original you aren’t updating the data, width, or height. In fact you’ve ended up with having two lots of variables serving the same purpose: currentMapData and data are both attempting to be pointers to the current map, mapWidth and width are both attempting to represent the width of the map (i.e. the number of columns), and mapHeight and height are both attempting to represent the height of the map (i.e. the number of rows).

This is why you can’t just copy and paste code and expect it to work, you need to understand how the code works so you can adapt it for your use case. I.e. read the comments, pull it apart, ask questions.

Basically either the collision code needs to be modified to account for the x and y being the top left, or the x and y needs to become the centre, which means all the other code dealing with the x and y needs to change to account for it.

Either way works, but it has to be consistent.

It might be quicker to modify the collision code, though it will lose its ‘symmetry’.

I see…

That makes sense. I was getting confused because you’re defining one of Camera’s functions inside player.cpp.

Basically you draw the player relative to the camera, just as you are doing for tiles at the moment. (I.e. subtract the camera’s coordinates.)

I’m pretty sure I explained world and camera/screen coordinates before, but perhaps I ought to go over them again when I’ve got the time to go through it?

That’s not inline either:

1 Like

:flushed:

I uh . . . well I

I’m too lazy to make a camera.cpp file :wink:

Huh. Maybe I made it inline at first because I got an error or something.

But also, how did you manage to get it working?

Really it would make more sense for Game to be responsible for updating both the player and the camera anyway.

You can’t do collisions without having knowledge about the world and you can’t render the player without knowledge about the camera, so instead of giving the player knowledge about the world and the camera (and thus introducing tighter coupling), it makes more sense that only the game knows about all three and acts as the glue that binds them together.

The quickest and easiest solution for now is to set width, height, and data to the values they should hold.

    const TileType *data = &map0Data[0][0];

    uint8_t width = mapWidth;
    uint8_t height = mapHeight;

But the problem here is that you then have two ‘current map’ pointers: currentMapData and data. Ideally you should get rid of one, and modify the code that was relying on it to rely on the new one instead.

Before you choose one ask yourself this: Do you want all of your maps to be the same size, or do you want them to be able to vary in size?

If you want them to be the same size, you are safe to scrap data and replace it with currentMapData, and to scrap width and height and replace them with mapWidth and mapHeight.

If you will want your maps to be different sizes at some point, you will be better off keeping data, width, and height. (If you go down this route, you will also need to modify Game::drawMap.)

Forgot to point this out.

That xVelocity will probably only impact the next frame, not the current frame.

1 Like

How so? Will I have to apply it to newX instead for some reason? Or before I assign the value of newX (and y) to x (and y)?

:flushed:

I’m ok with them being the same size. Besides the extra easiness for me (:wink: :smirk:) I guess it’ll make all the levels feel more equal.


When removing data and using currentMapData I get this error though:

/Users/amogus/Downloads/ARDUBOY/Untitled-Platformer/mapData.cpp: In function 'TileType MapData::getTile(float, float)':
/Users/amogus/Downloads/ARDUBOY/Untitled-Platformer/mapData.cpp:24:59: error: cannot convert 'const TileType (*)[24]' to 'const TileType*' in initialization
       const TileType * tilePointer = &currentMapData[index];
                                                           ^

exit status 1

Compilation error: cannot convert 'const TileType (*)[24]' to 'const TileType*' in initialization

Look at the top, the velocity has already been applied to produce newX and newY. From then on it’s completely ignored.

‘Solid’ objects in games aren’t really solid. What gives them the illusion of being solid is that the player is prevented from moving through them. To do that, the position that the player is about to move to (in this case that’s newX and newY) is checked to see if the player would be intersecting anything. If the player would interesect something, that movement is cancelled and the either stay where they are or are moved to somewhere more suitable.

Fancier collision detection actually ‘resolves’ the collision by taking into account the player’s velocity and possibly the properties of the thing being collided with, and if that thing is also moving then its velocity might also be included in the calculation. ‘Proper’ physics collisions can get very complicated, which generally isn’t necessary (or necessarily desirable) for a platformer, hence the version I used in my demo cuts corners.

You will do, because currentMapData is a different data type to what data was.

data was a pointer to a single tile, so when it it was given a single index and then its address was taken it returned the same thing: a pointer to a single tile. I.e. the type of &data[index] is the same type as data - they are both const TileType *.

currentMapData on the other hand is a pointer to an array of tiles, so if you feed it a single index and then take its address you get a pointer to an array of tiles. I.e. the type of &currentMapData[index] is the same type as currentMapData - they are both const TileType (*)[24].

Naturally you cannot assign a const TileType (*)[24] to a const TileType *. That’s basically what the error message is telling you:

cannot convert ‘const TileType ()[24]’ to 'const TileType

Essentially, as your maps are going to be fixed size and you’re using a pointer to a fixed size array instead of just a pointer to a sequence of tiles, the index trick is no longer necessary and you may scrap it in favour of directly indexing currentMapData as you already are in Game::drawMap. I.e. readTileTypeFromProgmem(&currentMapData[y][x]).

If you’d opted for variable size maps I would have offered to go into more detail about how the data and index version works, but as you are no longer doing that I can skip that bit for now.

1 Like

I’ve changed the code a little, to try and get it working with non-centred collision. It works I guess, but not very well.

ArduboyRecording

(At the end of the gif, I cannot move right)

The code’s all there on GitHub and I won’t be touching it.

I do have one theory however. Take a look at the letter, ‘L’. It has two lines, right? On the bottom left corner, the two lines intercept; collide; touch; overlap, etc. What if, the same thing has happening to the player? What if, let’s say, the bottom and left “hitboxes” are both occupying the bottom left corner of the player. So when it touches a tile, it triggers both a bottom and left collision? Could that be the reason why it’s all so funky near the end? Or is that why you opted for diamond shaped collision?

Also, what about the velocity when the player collides? I’m moving the camera using the player’s velocity, so even if the player is stuck, the camera would still move. I’m probably not using the camera right then, am I?

Looking at the code, it would seem you’re still using centred collision. All you’ve really done is replace two separate constants with a single constant.

The player drawing is accounting for the camera properly now though, which is why your camera is closer to moving as it’s supposed to.

That is indeed a problem when dealing with collisions, however it won’t be with the way I opted to do collisions.

The ‘diamond’ technique does actually avoid the problem and did play a part in my decision to use it, though the fact it’s much simpler than a lot of the other techniques was also a big reason for preferring it.

(Technically it’s not actually a diamond-shape, it’s just that the four points chosen happen to form a diamond. You could also think of it as forming a cross shape, but really it’s only 4 points, so I suppose calling it ‘edge midpoint collision’ is more accurate.)

Anyway, the reason that technique doesn’t have the corner issue is that the sprite’s corners aren’t actually factored into the collision at all. It’s taking the midpoint of each edge and using that, hence why the player slides off a block as soon as the middle of the base edge of the sprite is off the block: because that’s the point that’s being used to check for downward collisions.

The resulting effect is a bit wonky, but it’s simple and does the job and generally avoids oddities.

There are ways it could be improved, but I’d strongly advise that you make sure you actually understand what the collision code is doing before attempting anything more complex.

Read through the original commented version, bearing in mind that:

  • entity.x and entity.y refer to the centre of the entity, not the top left
  • tileWidth and tileHeight are also the width and height of all entities

Work it through manually on paper with some test cases, doing all the conditions and calculations manually. (You can use a calculator, but the point is that you actually look at how the calculations behave and understand what they’re doing.)

If you’re struggling to see what it’s doing I’ll try to find the time to talk you through it, but it’s best understood with some actual drawings/diagrams.

In the original platformer I was actually ‘cheating’ with the movement: there was no friction, the player’s horizontal velocity was only affected by the player’s input, and the vertical input was only affected by gravity and jumping.

What happens when two things collide in the real world is that some of the energy is absorbed, with the amount depending on how much mass the objects are and how ‘elastic’ the objects are, and the rest is retained, which is why if you throw a ball at a wall it bounces back in the opposite direction. (It also usually occurs via acceleration rather than directly affecting velocity.)

But platformers (especially 2D ones) usually ignore a lot of that because it’s quite complicated and doesn’t necessarily achieve good gameplay.

Without getting too hung up on accuracy, the velocity should theoretically be diminishing or even completely cancelling out when a collision happens. If it didn’t, the force of gravity would just keep increasing the player’s velocity to ridiculous levels and then the player would never be able to jump properly.

It depends how you want the camera to behave.

If you’re aiming for what I did in my platformer, (clamping the camera so that it doesn’t look outside the level,), the simplest thing to do is to not give the camera its own velocity in the first place, but to instead make it always look at the player, and then after that check to see if it’s looking off-screen, and if it is looking off-screen, to adjust it slightly so that it’s no longer looking off-screen.

Again, this is the sort of thing that you’re better off working out on paper. Ideally the kind of gridded paper you probably use in maths for drawing graphs since you can pretend the squares are pixels or tiles and the rules you figure out will then apply to the real tiles/pixels.

Again, I’m happy to go into further detail if you want, but I think having a go at working it out on paper first will help you later on. The more you understand about what’s going on and how the different parts work in relation to each other, the easier it will be to solve future problems. If you end up with a load of code and you don’t really understand what it’s doing, you’ll struggle to fix it and/or add to it later down the line.

1 Like

Okay uh, I’m dead. Why is this soo confusing. For the right tile collision, I’ve tried newX = (rightTileX - size) (size being the width/height of the player). It had super weird results though. This is just all so bizarre, you can try the .hex in the build folder on GitHub and it feels worse than Cyberpunk 2077 image

I assume the stuff automatically turns into integers, since TileType is uint8_t? Another flaw I see with the ‘teleporting’ collision is that if the player is like two tiles, the player would get teleported to the top of one of them, so then there’s no point in using floats. Maybe I’ll try again, but using integers. Who knows, maybe a speed of 1 could be nice.

I’ve got another idea: what if I disable movement input based on the collision? So if the collision is on the right side, I could disable the right movement. Same thing for the left side.

Okay, so I’ve updated the repo’s code and hex (in the build folder) with what my little idea, but isn’t really working out so great. Has anybody got any idea what I’m missing/doing wrong here?

I’m going to have a go re-designing the collision to use arduboy.collide() :slightly_smiling_face:
Okay nevermind I could only make a single huge hitbox :/ Back to the drawing board I guess.

I’ve updated the code to, hopefully, work with top-left-corner collision, but now the player is just teleporting up to the top.

ArduboyRecording

I just realized I forgot to un-comment the code in the collision that resets the velocity which is why it gets faster and faster. I’m too lazy to un-comment it right now :wink:

I had a very quick look and found that you’ve ended up writing newX when you should have written newY:

Also I think those are supposed to be < 0, not > 0.
(Come to think of it, I’m surprised the player is moving at all.)

1 Like

I found the newX thing yesterday, but I forgot to change the sign! I’ll try it out right now and see if it works :slightly_smiling_face:

(I probably copied and pasted the the thing and forgot to change it)

Edit: I’ve actually just removed the thing and just put y = newY so the player doesn’t stay at the top, but the teleporting is still there.

ArduboyRecording(1)

Do you think the tileX/Y variables might be incorrect?

Seems the problem is originating from these lines of code:

if(isSolid(bottomTile))
		{
        newY = (bottomTileY - size);

			  yVelocity = 0;
		}
if(isSolid(topTile))
		{
        newY = (topTileY + size);

			  yVelocity = 0;
		}

Am I perhaps not doing the y collision/resetting right?

Maybe I should just try the centred collision.

Edit: I think I need to multiply or something

I looked at the code again and I’ve spotted the problem…

newX is the position the player is trying to move to…

tileX is the x coordinate of the tile that the player is trying to move to…

rightX is the x coordinate of the right hand side of the rectangular area the player would occupy if the player moved to newX

rightTileX is the x coordinate of the tile that the player’s right hand side will be in if the player moves to newX

Which brings us to the actual problem…

You are trying to subtract the player’s horizontal size from the tile coordinate, not the player coordinate.

1 Like

Oh! I thought rightTileX meant the actual x cordinate of the tile.

I’ll give this a shot right now! Thanks!

Is this how it is supposed to be?

const float tileX = (newX / tileWidth);
    const float tileY = (newY / tileHeight);

    const float rightX = (newX + size);

    const float rightTileX = (rightX / tileWidth);

    const TileType rightTile = MapData::getTile(rightTileX, tileY);

    if(isSolid(rightTile))
		{
        newX = (rightX - size);

			  xVelocity = 0;

        canMoveRight = false;
		}
    
    else
    {
       canMoveRight = true;
    }

    const float leftX = (newX - size);
		
		const float leftTileX = (leftX / tileWidth);

		const TileType leftTile = MapData::getTile(leftTileX, tileY);

		if(isSolid(leftTile))
		{
        newX = (leftX + size);

			  xVelocity = 0;

        canMoveLeft = false;
		}

    else
    {
      canMoveLeft = true;
    }

		const float bottomY = (newY + size);

		const float bottomTileY = (bottomY / tileHeight);

		const TileType bottomTile = MapData::getTile(tileX, bottomTileY);

		if(isSolid(bottomTile))
		{
        newY = (bottomY - size);

			  yVelocity = 0;
		}

		const float topY = (newY - size);

		const float topTileY = (topY / tileHeight);

		const TileType topTile = MapData::getTile(tileX, topTileY);

		if(isSolid(topTile))
		{
        newY = (topY + size);

			  yVelocity = 0;
		}

Still doesn’t really work after swapping out all the rightTileX, leftTileX, etc. with rightX, leftX, etc.

Edit: It seems commenting out the velocity resetting in the collision code (e.g. xVelocity = 0) makes the collision not work at all.

ArduboyRecording

It seems leftX (bottom text) for example is just the x coordinate (top text) minus 8 and it constantly updates.
image

Probably because of this line const float leftX = (newX - size);

Did you also get rid of these lines?

It’s hard to tell what you’re looking at when the GitHub repo isn’t up-to-date.

Yes. Your code still retains some of the features of the centred version that haven’t been properly updated.

Coordinates

For top left coordinates:

  • leftX = player.x
  • rightX = player.x + playerWidth
  • topY = player.y
  • bottomY = player.y + playerHeight

For central coordinates:

  • leftX = player.x - (playerWidth / 2)
  • rightX = player.x + (playerWidth / 2)
  • topY = player.y - (playerHeight / 2)
  • bottomY = player.y + (playerHeight / 2)
1 Like

Yep. I actually just changed them to x = newX and same thing for the y axis; I’ll worry about out-of-bounds later.

:flushed: :flushed: :flushed:

:flushed: :flushed: :flushed:

Thanks! This is super helpful!!!

I’ll try these right now :slightly_smiling_face:


Edit: Now there’s some weird teleporting thing happening after changing the rightX, leftX, etc. stuff.

ArduboyRecording

1 Like