030

· 60 minute read · 0 Comments

notes for handmade_hero_day_030

2014-12-26 @ 10:03

Assume the points are as if Casey is describing what to do. I will explicitly add TODO(bk) or Brian: with a note that I have for the reader.

In the explanation, variables, parameters and members will be highlighted. Likewise, functions, structs and constants will be bolded.

The following are marks that I use to help write the code. They are not to be added to the file and are simply me giving some sort of instructions in the code.

All code blocks start as follows:

Edit GameUpdateAndRender in handmade.cpp:

Where the first value Edit describes that you are making a change. Other actions would be View, Add, Rename or Remove.

GameUpdateAndRender will refer to either the struct or function that we are changing. When this is not included, it means we are simply changing the file itself.

The file being referenced (eg handmade.cpp) will always be included.

// act: Action of which to take // pos: Position of where within the struct or function to put the code. // rem: Remark about something of this code, but not something Casey notes.

Please note, sometimes in code when renaming/refactoring I will show all the lines that will change. It is not expected that you will write every line each time. A simple find/replace will suffice. I only include to be thorough.

Video Notes

Welcome to Handmade Hero everyone. This is of course a show where we make an entire game from scratch, using nothing but our bare hands. No libraries. No engine. And we are basically doing this because I really think there is something valuable to everyone who’s a serious game programmer going through the entire pipeline of a game from start to finish. Just so that they can see every last little thing that goes on in a full stack of a game.

I think it’s great for educational purposes. I think it makes you more powerful for when you do professional programming. I think it allows you to be the kind of programmer who works on an engine team. It allows you to be someone who can overcome limitations of the tools that they work with. It’s just very empowering to know how everything works, and that is exactly what we are doing. We are taking it slow and investigating every last little aspect of game programming as it comes up.

We are at the point where we have made our Win 32 prototyping layer, and we are building our game on top of it right now.

What we are doing this week is trying to imagine roughly the sorts of things our game is going to have to do so that we can start building our engine. And one of the things that I’ve said early on is in order to build a game engine you need a game and in order to build a game, you need a game engine.

Because if you’ve never built a game before, you won’t know what to put into your game engine. You won’t know how to structure or architect it correctly so that it’s not just a pain in the butt to use. And similarly, if you don’t have a game engine, you can’t really make a game.

We are in a chicken and egg scenario when we are making everything from scratch and so what you want to do is you want to use what I like to call exploration programming. You want to start to play around with making the game and the engine together, and you just want to play around with test code so that you could start to see what things should look like. And once you kind of get a sense of what a certain part looks like, then you can start to go and make the basis of the game engine.

And so that is what we are doing today. We are continuing with our exploration and just trying to get stuff on the screen and make stuff work. We are not caring about code quality. We are not caring about cleanliness or any other abstract concepts. We are just talking about let’s just write some code that does some “gamey” stuff and lets see what it looks like, so that we can start to pull the common pieces out of it and pull the structure out of it that we are going to need in order to make a game engine.

@0:02:54

So, if you are following along at home and you have pre-ordered the game on https://handmadehero.org, you should have gotten an email that has a link to download a zip file. Inside the zip file there are zips of every day. We are on Day 30, so you want to unzip Day 29 - yesterday’s work. Because that is what I am starting with today. And so if you unzip Day 29 and start working on that you will be exactly where I am right now.

0:03:10 Recap

And where am is is a good question. What we were doing is we were playing with a tile map, with walls and doors. We have a little character who moves around and we have it so they cannot move through these various pieces. And what we decided to do is just use rectangles for everything because we didn’t want to deal with bitmaps yet until we saw how we wanted to use them.

So what we were trying to do yesterday and what we want to finish up today, is we would like to be able to walk out of the tile map and onto another tile map. We want to be able to walk from one screen to another and have the map update properly.

So, that is what we are doing, and if I remember correctly, we had gotten part-way through it; but, we ran out of time on the stream to finish it up.

Let’s go ahead and take a look at where we actually were.

@0:04:30

So what we have done is within our GameUpdateAndRender call, we made some little arrays that represent the tile maps that we wanted to traverse.

View GameUpdateAndRender in handmade.cpp:

uint32 Tiles00[TILE_MAP_COUNT_Y][TILE_MAP_COUNT_X] =
{
    {1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1},
    {1, 1, 0, 0,  0, 1, 0, 0,  0, 0, 0, 0,  0, 1, 0, 0,  1},
    {1, 1, 0, 0,  0, 0, 0, 0,  1, 0, 0, 0,  0, 0, 1, 0,  1},
    {1, 0, 0, 0,  0, 0, 0, 0,  1, 0, 0, 0,  0, 0, 0, 0,  1},
    {1, 0, 0, 0,  0, 1, 0, 0,  1, 0, 0, 0,  0, 0, 0, 0,  0},
    {1, 1, 0, 0,  0, 1, 0, 0,  1, 0, 0, 0,  0, 1, 0, 0,  1},
    {1, 0, 0, 0,  0, 1, 0, 0,  1, 0, 0, 0,  1, 0, 0, 0,  1},
    {1, 1, 1, 1,  1, 0, 0, 0,  0, 0, 0, 0,  0, 1, 0, 0,  1},
    {1, 1, 1, 1,  1, 1, 1, 1,  0, 1, 1, 1,  1, 1, 1, 1,  1},
};

// rem:  Removed for brevity.

We created a two by two array of tile maps and assigned some initial properties.

View GameUpdateAndRender in handmade.cpp:

tile_map TileMaps[2][2];
TileMaps[0][0].CountX = TILE_MAP_COUNT_X;
TileMaps[0][0].County = TILE_MAP_COUNT_y;

TileMaps[0][0].UpperLeftX = -30;
TileMaps[0][0].UpperLeftY = 0;
TileMaps[0][0].TileWidth = 60;
TileMaps[0][0].TileHeight = 60;

// rem:  ...

Basically what we had was these were variables that we found we needed to compute where the player was in our tile map and to draw the tile map and so we pulled these out to a tile_map structure.

View tile_map in handmade.h:

struct tile_map
{
    int32 CountX;
    int32 CountY;

    real32 UpperLeftX;
    real32 UpperLeftY;
    real32 TileWidth;
    real32 TileHeight;

    uint32 *Tiles;
};

Then we realized we needed multiple tile maps, so we made a world structure.

View world in handmade.h:

struct world
{
    // TODO(casey):  Beginner's sparseness
    int32 TileMapCountX;
    int32 TileMapCountY;

    tile_map *TileMaps;
};

We haven’t made it sparse yet. It is actually pretty dense which we will talk about a little later.

@0:05:28

Basically we assigned everything to our World, but we never actually did anything with it.

Edit GameUpdateAndRender in handmade.cpp:

// rem:  Looking at the code, Casey decides to shuffle things around.
// act:  Move from before we initialize World, to after we assign World.TileMaps.
tile_map *TileMap = &TileMaps[0][0];

The PlayerWidth and PlayerHeight should actually moved up and should be referencing TileMaps[0][0], for we actually have some constant variables.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Move this code from after we declare TileMap
real32 PlayerWidth = 0.75f*TileMap->TileWidth;
real32 PlayerHeight = TileMap->TileHeight;
// act:  To after we assign TileHeight for TileMaps[0][0].  We also change from TileMap to
//       TileMaps[0][0]

real32 PlayerWidth = 0.75f*TileMaps[0][0]->TileWidth;
real32 PlayerHeight = TileMaps[0][0]->TileHeight;

Again, taking this one step at a time. There is a lot of stuff that we will want to do to clean up all of this stuff. We are literally just spamming the code out here. But I am just making little baby steps to put things into the right location.

So a lot of the stuff I wouldn’t be doing because I just know the right answers to some of this stuff. I could just sort of type it in. But if I didn’t already know what some of the right answers are…

0:06:36 About the process of writing code; Messy first, clean after

…this is not messy code that I’m just doing for the stream. This is actually how I recommend you do it. I recommend you type in awful code first. That’s whats ever the first thing you think of to get stuff on the screen. And then afterwards when you are satisfied it does the basic operations that it needs to do, only then go through and clean it up. Because otherwise you are spending a lot of time thinking about how to make the code clean, but you haven’t actually figured out which code you actually need to make clean yet.

So you end up going through all these cascades of writing clean code and fussing with that, only to realize it doesn’t do some of the operations that you needed it to do. And so I can’t stress enough how I really do not think it’s a good practice to write clean code when you are just exploring things. Write messy code and write the clean code only once. You save that effort of writing that clean code only when it actually is going to pay off.

Because code like this that’s just transient and that we are playing around with, isn’t going to stay around. So any time you spent making it clean, was full on wasted.

0:07:58 Passing information about tilemap

What I’d like to do is get ourselves working so we are thinking about where we are in this tile map space. You’ll notice that we had sort of a concept of where the player was, and this notion of where the player was is sort of inside one tile map.

View game_state in handmade.h:

struct game_state
{
    real32 PlayerX;
    real32 PlayerY;
}

But what I need to do, and we wrote this function GetTileMap yesterday, is a function that gives a tile_map, given a TileMapX and a TileMapY. And that TileMapX and TileMapY are basically indices into an array of tile maps to say which tile_map we are in, and then the PlayerX and PlayerY tells us where we are inside that tile map.

So what I need to do, is add a PlayerTileMapX and a PlayerTileMapY, to say here’s the tile map that the player’s in, and PlayerX and PlayerY is where they are inside the tile map.

Edit game_state in handmade.h:

// act:  Add to the top of the struct definition
int32 PlayerTileMapX;
int32 PlayerTileMapY;

Then what I can do is when we are actually operating on the TileMaps, I could start to say which TileMap are we going to operate on here in the game loop. And we are going to operate on which ever one that the player is located in.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
tile_map *TileMap = &TileMaps[0][0];
// act:  With
tile_map *TileMap = &TileMaps[GameState->PlayerTileMapX][GameState->PlayerTileMapY];

Now if I remember correctly, our function GetTileMap also does bounds checking…

View GetTileMap in handmade.cpp:

if((TileMapX >= 0) && (TileMapX < World->TileMapCountX) &&
   (TileMapX >= 0) && (TileMapX < World->TileMapCountX))

… so what I can do is use that function to do the query for us; which is good because I just noticed that I typed in PlayerTileMapX and PlayerTileMapY backwards. The Y should come first.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
tile_map *TileMap = &TileMaps[GameState->PlayerTileMapX][GameState->PlayerTileMapY];
// act:  With
tile_map *TileMap = GetTileMap(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY);

Usually we think X is coming first and Y is coming second, but in C, because we tend to store things in order of Xs first and then Y, it ends up being backwards.

Array[Y][X];

So I kind of like to have accessors because then I can pass X first and then my Y second. And I can through bounds checking in there if I want to.

What we want to do here is Assert that our character is always on a TileMap.

Edit GameUpdateAndRender in handmade.cpp:

tile_map *TileMap = GetTileMap(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY);
Assert(TileMap);    // act:  Add this

We should never allow the player to be in some location that doesn’t actually have a TileMap associated with it. So for now, let’s just say that is true.

Our reference to GameState has to go after initialization because we have not initialized the GameState yet at that point.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Moved after !Memory->IsInitialized block

tile_map *TileMap = GetTileMap(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY);
Assert(TileMap);

TODO(bk): Casey launches the game and moves the character around.

We should still be on the same screen, and we are. So really all we need to do now is start thinking about moving the player when they would go off of the screen. And we also have to do some other things which is right now when it looks off that…

0:10:47 Checking if player is moving to a different tile-map

TileMap, we’re not actually looking onto another tile map. Because if you remember, we wrote the code to stopping us when we hit a wall. What that is basically doing is looking where the player would be on the next frame and is saying “is the location of where would be on the next frame in the tile map empty or not?”

Well, when we get to the edge, and it looks over onto “no man’s land”, we assume that if you were accessing off the TileMap that you are currently on, to look outside of it (left, right, top or bottom) we just assumed it to failed. But what we need to do now is start assuming that it is not failed, and assume that it actually could be another tile map that we have to look at and see what the actual value is.

If we look at IsTileMapPointEmpty, what we want to do is allow that function to start looking across multiple tile maps. So what we want is there is to have an equivalent check that will test IsWorldPointEmpty. This is going to take for parameters a World struct so that it can access multiple tile maps; and also take a TestTileMapX, a TestTileMapY, and then it will take the TestX and TestY.

Add IsWorldPointEmpty in handmade.cpp:

inline bool32
IsWorldPointEmpty(world *World, int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY)

I am just doing this speculatively for we don’t know what this function wants to do yet.

0:12:51 Finding the next tile-map and querying the point

Edit IsWorldPointEmpty in handmade.cpp:

{
    // act:  copy the definition of IsTileMapPointEmpty
    bool32 Empty = false;

    int32 PlayerTileX = TruncateReal32ToInt32((TestX - TileMap->UpperLeftX) / TileMap->TileWidth);
    int32 PlayerTileY = TruncateReal32ToInt32((TestY - TileMap->UpperLeftY) / TileMap->TileHeight);

    if((PlayerTileX >= 0) && (PlayerTileX < TileMap->CountX) &&
       (PlayerTileY >= 0) && (PlayerTileY < TileMap->CountY))
    {
        uint32 TileMapValue = GetTileValueUnchecked(TileMap, PlayerTileX, PlayerTileY);
        Empty = (TileMapValue == 0);
    }

    return(Empty);
}

If PlayerTileX and PlayerTileY are going to be outside of the boundary, what we were doing, is used a check to prevent us from ever doing anything in those cases. But what we could do instead is start looking at what happens if PlayerTileX is less than zero, let’s actually change what we were looking at. If it’s less than zero we are going to map into the next tile map over.

TODO(bk): Casey draws an example out to explain.

If I have a tile map here, and have some tile map next to it, previously what we were doing is figuring out where in this tile map you were. And maybe you walked off the left edge so the result was -1 say. And if it was -1, we assumed it’s blocked.

What we would like to do instead, is ask the next tile map before us, what they had, in their right most column. So if a tile map was n things wide, and they are numbered 0 through n-1 -as we do in C- when we look at the -1, what we sort of what to do is map -1 to n-1 on the previous tile map. And -2 is going to map to n-2, on the previous tile map.

So it’s n minus whatever X value is computed. It’s the value minus the width of the tile map in tiles, is the actual column that we want to look into. However, since X is negative, subtracting a negative number changes to addition.

So what I want to do here is take whatever the tile map was before hand, I want to take the tile map width (CountX) and add the PlayerTileX to it.

Edit IsWorldPointEmpty in handmade.cpp:

// pos:  After PlayerTileX and PlayerTileY declarations

if(PlayerTileX < 0)
{
    PlayerTileX = TileMap->CountX + PlayerTileX;
}

So what I have to actually get is the TileMap that I will be looking at. So the tile map that I will be looking at will be TestTileMapX minus one.

Edit IsWorldPointEmpty in handmade.cpp:

PlayerTileX = TileMap->CountX + PlayerTileX;
--TestTileMapX;     // act:  Add this

And the exact same thing would work for Y.

Edit IsWorldPointEmpty in handmade.cpp:

// pos:  Just below PlayerTileX < 0 block
if(PlayerTileY < 0)
{
    PlayerTileY = TileMap->CountY + PlayerTileY;
    --TestTileMapY;
}

The exact same thing would work if we were one off the bottom, we’d move to the previous tile map in Y, and we would query this selected tile on it.

Now one thing you will notice is that we do not actually know what TileMap we are looking at yet. So what we would have to do if we wanted each tile map was a different size, we’d have to do a ton of different math here because we’d have to also have other information.

TODO(bk): Casey demonstrates what he means by having tile maps of different sizes.

+-------------+
|             |
|             |
|             |
|             +---------+
|             |         |
|             |         |
|             |         |
+-------------+---------+

Let’s say that the tile maps were of different sizes, the right tile is the one we’re on and the left is a different one. Well now the math is more complicated because if we move over to the left, we’d also have a different Y offset than we used to, because the top is different. The Y offset for the right may be zero, but the Y offset for the left may be 7 or something.

But since all of our tile maps are supposed to be the same size, we actually don’t have to do that.

+-----------+------------+
|           |            |
|           |            |
|           |            |
|           |            |
|           |            |
+-----------+------------+

Which brings up the thing we talked about yesterday, and again I said don’t do things until you actually have to do them, these values that are accessed off the TileMap are actually constant for all the tile maps. We don’t allow tile maps to be multiple sizes yet and ever anticipate allowing you to do that because since we allow you to have as many tiles as you want, the only thing that would do is we would have it so you would zoom in or out.

So what I want to do is say these values are actually constant. So all of these stuff are not properties from tile_map, but rather properties of our world struct.

Edit tile_map in handmade.h:

// act:  Remove these from tile_map and move to world struct
int32 CountX;
int32 CountY;

real32 UpperLeftX;
real32 UpperLeftY;
real32 TileWidth;
real32 TileHeight;

Edit world in handmade.h:

// act:  Moved from tile_map struct.  Put at the top of the struct
int32 CountX;
int32 CountY;

real32 UpperLeftX;
real32 UpperLeftY;
real32 TileWidth;
real32 TileHeight;

I want a fully consistent space, so I want the player to always be seeing a screen that’s a certain number of tiles width and height, and that they walk around and it’s always consistent.

Edit IsWorldPointEmpty in handmade.cpp:

// rem:  Only showing lines that will change
// act:  Replace TileMap
int32 PlayerTileX = TruncateReal32ToInt32((TestX - TileMap->UpperLeftX) / TileMap->TileWidth);
int32 PlayerTileY = TruncateReal32ToInt32((TestY - TileMap->UpperLeftY) / TileMap->TileHeight);
    PlayerTileX = TileMap->CountX + PlayerTileX;
    PlayerTileY = TileMap->CountY + PlayerTileY;
if((PlayerTileX >= 0) && (PlayerTileX < TileMap->CountX) &&
   (PlayerTileY >= 0) && (PlayerTileY < TileMap->CountY))
// act:  With World
int32 PlayerTileX = TruncateReal32ToInt32((TestX - World->UpperLeftX) / World->TileWidth);
int32 PlayerTileY = TruncateReal32ToInt32((TestY - World->UpperLeftY) / World->TileHeight);
    PlayerTileX = World->CountX + PlayerTileX;
    PlayerTileY = World->CountY + PlayerTileY;
if((PlayerTileX >= 0) && (PlayerTileX < World->CountX) &&
   (PlayerTileY >= 0) && (PlayerTileY < World->CountY))

If we move off the end (being less than 0), we readjust what TileMap we are on. The exact same thing has to happen on the other side.

So if we were to happen to walk off the right edge, what we need to do is set the next tile map over.

TODO(bk): Casey demonstrates moving to the right tile

If we were on tile n-1, which is CountX, so if we add one we will be on n. But what we really want to be on is 0. We want to basically subtract out the count and just get how far we are away from the boundary, which will tell us where we are on the next tile map.

So to calculate PlayerTileX, we will simply subtract out what the CountX of the world.

Edit IsWorldPointEmpty in handmade.cpp:

// pos:  After the PlayerTileY < 0 block
if(PlayerTileX >= World->CountX)
{
    PlayerTileX = PlayerTileX - World->CountX;
    ++TestTileMapX;
}

And the same is true for Y.

Edit IsWorldPointEmpty in handmade.cpp:

// pos:  After the PlayerTileX >= World->CountX block
if(PlayerTileY >= World->CountY)
{
    PlayerTileY = PlayerTileY - World->CountY;
    ++TestTileMapY;
}

@0:20:14

So all I am doing with these is checking if we step out of bounds, let’s reduce it and go to the next one over. These four blocks are basically equivalent to the clipping of what we used to be doing, but instead of clipping, we are just going to look at the next TileMap, if we have to.

So once we do, we have now computed which TileMap we actually want.

Edit IsWorldPointEmpty in handmade.cpp:

// pos:  just after PlayerTileY >= World->CountY block

tile_map *TileMap = GetTileMap(World, PlayerTileMapX, PlayerTileMapY);

Hmm…instead of PlayerTile, this is really TestTile.

Edit IsWorldPointEmpty in handmade.cpp:

// rem:  I've only shown the lines that will change.
// act:  Rename PlayerTileX and PlayerTileY
int32 PlayerTileX = TruncateReal32ToInt32((TestX - World->UpperLeftX) / World->TileWidth);
int32 PlayerTileY = TruncateReal32ToInt32((TestY - World->UpperLeftY) / World->TileHeight);
if(PlayerTileX < 0)
    PlayerTileX = World->CountX + PlayerTileX;
if(PlayerTileY < 0)
    PlayerTileY = World->CountY + PlayerTileY;
if(PlayerTileX >= World->CountX)
    PlayerTileX = PlayerTileX - World->CountX;
if(PlayerTileY >= World->CountY)
    PlayerTileY = PlayerTileY - World->CountY;
tile_map *TileMap = GetTileMap(World, PlayerTileMapX, PlayerTileMapY);
if((PlayerTileX >= 0) && (PlayerTileX < World->CountX) &&
   (PlayerTileY >= 0) && (PlayerTileY < World->CountY))
    uint32 TileMapValue = GetTileValueUnchanged(TileMap, PlayerTileX, PlayerTileY);
// act:  With TestTileX and TestTileY
int32 TestTileX = TruncateReal32ToInt32((TestX - World->UpperLeftX) / World->TileWidth);
int32 TestTileY = TruncateReal32ToInt32((TestY - World->UpperLeftY) / World->TileHeight);
if(TestTileX < 0)
    TestTileX = World->CountX + TestTileX;
if(TestTileY < 0)
    TestTileY = World->CountY + TestTileY;
if(TestTileX >= World->CountX)
    TestTileX = TestTileX - World->CountX;
if(TestTileY >= World->CountY)
    TestTileY = TestTileY - World->CountY;
tile_map *TileMap = GetTileMap(World, TestTileMapX, TestTileMapY);
if((TestTileX >= 0) && (TestTileX < World->CountX) &&
   (TestTileY >= 0) && (TestTileY < World->CountY))
    uint32 TileMapValue = GetTileValueUnchanged(TileMap, TestTileX, TestTileY);

So the tile that we are testing for our call to GetTileMap, we are asking for the tile map that corresponds to the place that we were asked to look, adjusted by if we were out of bounds in any one of the four directions, and we are going to ask if that tile is empty.

What I’d like to do is since GetTileMap can fail, we could be asking for a tile that’s out of bounds, I would like to make IsTileMapPointEmpty guarded. When you call this function, it will check if TileMap exists, and if it doesn’t, we will assume that the tile was not empty.

Edit IsTileMapPointEmpty in handmade.cpp:

{
    bool32 Empty = false;

    // act:  Simply take the main chunk of the function and put it behind this check
    if(TileMap)
    {
        // rem: removed for brevity
    }

    return(Empty);
}

It will assume there is something is there and it’s blocking. That way, when we call GetTileMap, even if we fetch out of bounds, it doesn’t actually matter.

Edit IsWorldPointEmpty in handmade.cpp:

tile_map *TileMap = GetTileMap(World, TestTileMapX, TestTileMapY);
IsTileMapPointEmpty(TileMap, TestX, TestY);     // act:  Add this

@0:21:55

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Remove this code block
if((TestTileX >= 0) && (TestTileX < World->CountX) &&
   (TestTileY >= 0) && (TestTileY < World->CountY))
{
    uint32 TileMapValue = GetTileValueUnchanged(TileMap, TestTileX, TestTileY);
    Empty = (TileMapValue == 0);
}

Since we already have the tile value, we can actually just pass the TestX and TestY that we have already computed.

Edit IsTileMapPointEmpty in handmade.cpp:

// act:  Replace real32 for TestX and TestY
IsTileMapPointEmpty(tile_map *TileMap, real32 TestX, real32 TestY)
// act:  With int32
IsTileMapPointEmpty(tile_map *TileMap, int32 TestX, int32 TestY)

We have already computed the index into the tile map itself, so we are just going to go ahead and pass that directly.

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace TestX and TestY
IsTileMapPointEmpty(TileMap, TestX, TestY);
// act:  With TestTileX and TestTileY
IsTileMapPointEmpty(TileMap, TestTileX, TestTileY);

So we no longer need this code.

Edit IsTileMapPointEmpty in handmade.cpp:

// act:  Remove
int32 PlayerTileX = TruncateReal32ToInt32((TestX - TileMap->UpperLeftX) / TileMap->TileWidth);
int32 PlayerTileY = TruncateReal32ToInt32((TestY - TileMap->UpperLeftY) / TileMap->TileHeight);

Edit IsTileMapPointEmpty in handmade.cpp:

// rem:  I've only shown the lines that will change.
// act:  Rename TestX, TestY, PlayerTileX and PlayerTileY
IsTileMapPointEmpty(tile_map *TileMap, int32 TestX, int32 TestY)
        if((PlayerTileX >= 0) && (PlayerTileX < TileMap->CountX) &&
           (PlayerTileY >= 0) && (PlayerTileY < TileMap->CountY))
           uint32 TileMapValue = GetTileValueUnchecked(TileMap, PlayerTileX, PlayerTileY)
// act:  With TestTileX and TestTileY
IsTileMapPointEmpty(tile_map *TileMap, int32 TestTileX, int32 TestTileY)
        if((TestTileX >= 0) && (TestTileX < TileMap->CountX) &&
           (TestTileY >= 0) && (TestTileY < TileMap->CountY))
           uint32 TileMapValue = GetTileValueUnchecked(TileMap, TestTileX, TestTileY)

We will probably also want to Assert what is happening in our checked path.

Edit GetTileValueUncheckec in handmade.cpp:

// act:  Add to the top of the function.
Assert(TileMap);
Assert((TileX >= 0) && (TileX < TileMap->CountX) &&
       (TileY >= 0) && (TileY < TileMap->CountY));

@0:23:20

So what has to happen now is previously where tile_map was sufficient, we are now going to need the world pointer.

Edit IsTileMapPointEmpty in handmade.cpp:

// rem:  I've only shown the lines that will change.
// act:  Replace
IsTileMapPointEmpty(tile_map *TileMap, int32 TestTileX, int32 TestTileY)
        if((TestTileX >= 0) && (TestTileX < TileMap->CountX) &&
           (TestTileY >= 0) && (TestTileY < TileMap->CountY))
// act:  With - add World parameter, and change constant references to TileMap to World
IsTileMapPointEmpty(world *World, tile_map *TileMap, int32 TestTileX, int32 TestTileY)
        if((TestTileX >= 0) && (TestTileX < World->CountX) &&
           (TestTileY >= 0) && (TestTileY < World->CountY))

Because now the world pointer now has all the dimensional information in it. So basically everything that we were previously using the TileMap pointer for now has to have the World pointer.

Edit GetTileValueUnchecked in handmade.cpp:

// rem:  I've only shown the lines that will change.
// act:  Replace
GetTileValueUnchecked(tile_map *TileMap, int32 TileX, int32 TileY)
    Assert((TileX >= 0) && (TileX < TileMap->CountX) &&
           (TileY >= 0) && (TileY < TileMap->CountY))
    uint32 TileMapValue = TileMap->Tiles[TileY*TileMap->CountX + TileX];
// act:  With - add World parameter, and change constant references to TileMap to World
GetTileValueUnchecked(world *World, tile_map *TileMap, int32 TileX, int32 TileY)
    Assert((TileX >= 0) && (TileX < World->CountX) &&
           (TileY >= 0) && (TileY < World->CountY))
    uint32 TileMapValue = TileMap->Tiles[TileY*World->CountX + TileX];

Edit IsTileMapPointEmpty in handmade.cpp:

// act:  Replace
uint32 TileMapValue = GetTileValueUnchecked(TileMap, TestTileX, TestTileY);
// act:  With - add World parameter
uint32 TileMapValue = GetTileValueUnchecked(World, TileMap, TestTileX, TestTileY);

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace
IsTileMapPointEmpty(TileMap, TestTileX, TestTileY);
// act:  With - add World parameter
IsTileMapPointEmpty(World, TileMap, TestTileX, TestTileY);

We are just going through and cleaning up, and then we will fix any bugs that I have no doubt have.

So now, we are in a much better position clean this code up even, because now that we have the World setup, it can take all of the values that were set within each TileMap.

View GameUpdateAndRender in handmade.cpp:

tile_map TileMaps[2][2];
TileMaps[0][0].CountX = TILE_MAP_COUNT_X;
TileMaps[0][0].County = TILE_MAP_COUNT_y;

TileMaps[0][0].UpperLeftX = -30;
TileMaps[0][0].UpperLeftY = 0;
TileMaps[0][0].TileWidth = 60;
TileMaps[0][0].TileHeight = 60;

real32 PlayerWidth = 0.75f*TileMaps[0][0].TileWidth;
real32 PlayerHeight = TileMaps[0][0].TileHeight;

TileMaps[0][0].Tiles = (uint32 *)Tiles00;

TileMaps[0][1] = TileMaps[0][0];
TileMaps[0][1].Tiles = (uint32 *)Tiles01;

TileMaps[1][0] = TileMaps[0][0];
TileMaps[1][0].Tiles = (uint32 *)Tiles10;

TileMaps[1][1] = TileMaps[0][0];
TileMaps[1][1].Tiles = (uint32 *)Tiles11;

world World;
World.TileMapCountX = 2;
World.TileMapCountY = 2;

World.TileMaps = (tile_map *)TileMaps;

Edit GameUpdateAndRender in handmade.cpp:

// act:  Move the following code
TileMaps[0][0].CountX = TILE_MAP_COUNT_X;
TileMaps[0][0].County = TILE_MAP_COUNT_y;

TileMaps[0][0].UpperLeftX = -30;
TileMaps[0][0].UpperLeftY = 0;
TileMaps[0][0].TileWidth = 60;
TileMaps[0][0].TileHeight = 60;
// pos:  After we initialize World
world World;
World.TileMapCountX = 2;
World.TileMapCountY = 2;
// act:  Rename TileMaps[0][0] with World
World.CountX = TILE_MAP_COUNT_X;
World.County = TILE_MAP_COUNT_y;

World.UpperLeftX = -30;
World.UpperLeftY = 0;
World.TileWidth = 60;
World.TileHeight = 60;

Which means that the cloning of the TileMaps that was done, does not need to happen.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
TileMaps[0][0].Tiles = (uint32 *)Tiles00;

TileMaps[0][1] = TileMaps[0][0];
TileMaps[0][1].Tiles = (uint32 *)Tiles01;

TileMaps[1][0] = TileMaps[0][0];
TileMaps[1][0].Tiles = (uint32 *)Tiles10;

TileMaps[1][1] = TileMaps[0][0];
TileMaps[1][1].Tiles = (uint32 *)Tiles11;
// act:  With
TileMaps[0][0].Tiles = (uint32 *)Tiles00;
TileMaps[0][1].Tiles = (uint32 *)Tiles01;
TileMaps[1][0].Tiles = (uint32 *)Tiles10;
TileMaps[1][1].Tiles = (uint32 *)Tiles11;

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace TileMaps[0][0]
real32 PlayerWidth = 0.75f*TileMaps[0][0].TileWidth;
real32 PlayerHeight = TileMaps[0][0].TileHeight;
// act:  With World
real32 PlayerWidth = 0.75f*World.TileWidth;
real32 PlayerHeight = World.TileHeight;

@0:25:41

Now, we don’t want to query IsTileMapPointEmpty anymore. What we want is to call IsWorldPointEmpty for our testing. So now we need to pass in our World pointer, and where we believe the tile the player to be on, which is PlayerTileMapX and PlayerTileMapY from our GameState.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
if(IsTileMapPointEmpty(tileMap, NewPlayerX - 0.5f*PlayerWidth, NewPlayerY) &&
   IsTileMapPointEmpty(tileMap, NewPlayerX + 0.5f*PlayerWidth, NewPlayerY) &&
   IsTileMapPointEmpty(tileMap, NewPlayerX , NewPlayerY))
// act:  With
if(IsWorldPointEmpty(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY, 
                     NewPlayerX - 0.5f*PlayerWidth, NewPlayerY) &&
   IsWorldPointEmpty(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY, 
                     NewPlayerX + 0.5f*PlayerWidth, NewPlayerY) &&
   IsWorldPointEmpty(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY, 
                     NewPlayerX , NewPlayerY))

Moving on, it looks like we just need to pass the World pointer here.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
uint32 TileID = GetTileValueUnchecked(TileMap, Column, Row);
// act:  With - add World parameter
uint32 TileID = GetTileValueUnchecked(&World, TileMap, Column, Row);

And it looks like we want to use the World for all of these references to TileMap.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace TileMap
real32 MinX = TileMap->UpperLeftX + ((real32)Column)*TileMap->TileWidth;
real32 MinY = TileMap->UpperLeftY + ((real32)Row)*TileMap->TileHeight;
real32 MaxX = MinX + TileMap->TileWidth;
real32 MaxY = MinY + TileMap->TileHeight;
// act:  With World
real32 MinX = World.UpperLeftX + ((real32)Column)*World.TileWidth;
real32 MinY = World.UpperLeftY + ((real32)Row)*World.TileHeight;
real32 MaxX = MinX + World.TileWidth;
real32 MaxY = MinY + World.TileHeight;

@0:27:27

TODO(bk): Casey steps through the code and debugs to figure out our current state.

@0:32:16

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace
IsTileMapPointEmpty(World, TileMap, TestTileX, TestTileY);
// act:  With - Assign Empty the return value of IsTileMapPointEmpty
Empty = IsTileMapPointEmpty(World, TileMap, TestTileX, TestTileY);

TODO(bk): Casey again steps through the code.

@0:33:10

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
TileMaps[0][0].Tiles = (uint32 *)Tiles00;
TileMaps[0][1].Tiles = (uint32 *)Tiles01;
TileMaps[1][0].Tiles = (uint32 *)Tiles10;
TileMaps[1][1].Tiles = (uint32 *)Tiles11;
// act:  With
TileMaps[0][0].Tiles = (uint32 *)Tiles00;
TileMaps[0][1].Tiles = (uint32 *)Tiles10;  // act:  Tiles01 to Tiles10
TileMaps[1][0].Tiles = (uint32 *)Tiles01;  // actL  Tiles10 to Tiles01
TileMaps[1][1].Tiles = (uint32 *)Tiles11;

So now all we have to do is allow our character to actually change screens. And what we want to do here is reuse some work because you can can see with IsWorldPointEmpty already does the work that we have wrapped on a tile; but, we never get that information back. We never find out that it looked at a different tile.

So when we accept the move, what we would like to do is accept the fact that it moved to a new tile as well. And so what we want to do here is pull out the checks within IsWorldPointEmpty in some kind of convenient way.

Now you will notice this is again leading into exactly what I wanted to get into next. It turns out if you just write the code, it kind of guides you towards all of the right answers. That’s sort of the thing I’ve been harping on over and over.

So we are passing along all of this information: TestX, TestY, TestTileMapX, TestTileMapY. Essentially all of this is talking about a player’s location. They are all interrelated. You can’t really talk about TestX and TestY very effectively without talking about which test tile the player is in. And that’s going to be true over everyone that has a location that can be freely roaming.

0:36:07 Calculating the position in the new tile-map

So what we want to do, is probably have a way of bundling these things together so that we can return them. We would like to be able to call a GetCanonicalPosition function.

What I want to do is I want to pull all the test code out of IsWorldPointEmpty so that I can use it again, where I’m going to go ahead a TileMapX, a TileMapY and a real offset inside it. And if this real offset is outside of the TileMap, it will realign the TestTileMapX and TestTileMapY to correctly put it inside.

I want GetCanonicalPosition to take the same parameters as IsWorldPointEmpty, and I want it to return those properties as well.

Add GetCanonicalPosition in handmade.cpp:

// pos:  Just above IsWorldPointEmpty's definition
// rem:  We cannot return all four values
internal void  // rem:  int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY
GetCanonicalPosition(world *World, int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY)

So I need a struct that’s got this stuff so I can return it.

Add world_position in handmade.h:

// pos:  Just above tile_map definition
struct world_position
{
    int32 TileMapX;
    int32 TileMapY;

    real32 X;
    real32 Y;
};

So let’s make a simple function that will take in and return world_position by value.

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace
internal void
GetCanonicalPosition(world *World, int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY)
// act:  With
inline world_position
GetCanonicalPosition(world *World, world_position Pos)

All it does is do the truncation and the check.

Edit GetCanonicalPosition in handmade.cpp:

{
    // act:  Move this code from IsWorldPointEmpty
    int32 TestTileX = TruncateReal32ToInt32((TestX - World->UpperLeftX) / World->TileWidth);
    int32 TestTileY = TruncateReal32ToInt32((TestY - World->UpperLeftY) / World->TileHeight);

    if(TestTileX < 0)
    {
        TestTileX = World->CountX + TestTileX;
        --TestTileMapX;
    }

    if(TestTileY < 0)
    {
        TestTileY = World->CountY + TestTileY;
        --TestTileMapY;
    }

    if(TestTileX >= World->CountX)
    {
        TestTileX = TestTileX - World->CountX;
        ++TestTileMapX;
    }

    if(TestTileY >= World->CountY)
    {
        TestTileY = TestTileY - World->CountY;
        ++TestTileMapY;
    }
}

It’s going to take a ‘bogus’ position and it will realign it in canonical form and it will be within a one tile width boundary.

@0:38:23

Brian: Casey shuffles around code that I’ve already handled.

@0:39:06

Okay, we are going to GetCanonicalPosition, and we are going to change TestX and TestY into Pos.X and Pos.Y.

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace TestX and TestY
int32 TestTileX = TruncateReal32ToInt32((TestX - World->UpperLeftX) / World->TileWidth);
int32 TestTileY = TruncateReal32ToInt32((TestY - World->UpperLeftY) / World->TileHeight);
// act:  With Pos.X and Pos.Y
int32 TestTileX = TruncateReal32ToInt32((Pos.X - World->UpperLeftX) / World->TileWidth);
int32 TestTileY = TruncateReal32ToInt32((Pos.Y - World->UpperLeftY) / World->TileHeight);

And essentially, all of the stuff that we do for the tile map is now being done on Pos.

Edit GetCanonicalPosition in handmade.cpp:

// rem:  I only show the lines that will change
// act:  Replace TestTileMapX and TestTileMapY
--TestTileMapX;
--TestTileMapY;
++TestTileMapX;
++TestTileMapY;
// act:  With Pos.TileMapX and Pos.TileMapY
--Pos.TileMapX;
--Pos.TileMapY;
++Pos.TileMapX;
++Pos.TileMapY;

And basically, that does the canonicalization for us.

Now that we have GetCanonicalPosition, our IsWorldPointEmpty function is sort of useless.

View IsWorldPointEmpty in handmade.cpp:

internal bool32
IsWorldPointEmpty(world *World, int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY)
{
    bool32 Empty = false;

    tile_map *TileMap = GetTileMap(World, TestTileMapX, TestTileMapY);
    Empty = IsTileMapPointEmpty(World, TileMap, TestTileX, TestTileY);

    return(Empty);
}

All we need to do here is GetCanonicalPosition CanPos for the TestPos that we were looking at.

Edit IsWorldPointEmpty in handmade.cpp:

bool32 Empty = false;

// act:  Add this line
world_position CanPos = GetCanonicalPosition(World, TestPos);

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace  TestTileMapX and TestTileMapY
tile_map *TileMap = GetTileMap(World, TestTileMapX, TestTileMapY);
// act:  With CanPos.TileMapX and CanPos.TileMapY
tile_map *TileMap = GetTileMap(World, CanPos.TileMapX, CanPos.TileMapY);

Now that I think about it, since we are already computing in GetCanonicalPosition both TestTileX and TestTileY as well, I guess we would probably want to keep them as a fully world qualified position.

Edit world_position in handmade.h:

// pos:  After TileMapX/TileMapY

int32 TileX;
int32 TileY;

This is kind of interesting. There is different ways we can kind of use this. We probably don’t want to store these values, but it’s kind of the thing that gets produced the fully computed position. We can then think of the real32 X and Y as floating point offsets within a tile itself.

It’s like we need two of these structs. A canonical_position (what was world_position) and a raw_position.

Rename world_position to canonical_position in handmade.h:

// act:  Rename world_position
struct world_position
{
    // rem:  removed for brevity
};
// act:  With canonical_position
struct canonical_position
{
    // rem:  removed for brevity
};

I am not sure I actually want to keep this abstraction, but since we have about 15 minutes left I’m gonna show what it will look like to do it.

Add raw_position in handmade.h:

// act:  Add after canonical_position
struct raw_position
{
    int32 TileMapX;
    int32 TileMapY;

    real32 X;
    real32 Y;
};

You can think of it this way: there is a raw_position, which I was on a tile map and I had some random X and Y values that I wanted to test. And once I convert to a canonical_position, I know what tile that were in (TileX and TileY).

So now you can think of GetCanonicalPosition takes in a raw_position

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace both parameter and return type of world_position
inline world_position
GetCanonicalPosition(world *World, world_position Pos)
// act:  With raw_position and canonical_position respectively.
inline canonical_position
GetCanonicalPosition(world *World, raw_position Pos)

@0:41:48

And the result is a canonical_position.

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace TestTileX and TestTileY
int32 TestTileX = TruncateReal32ToInt32((Pos.X - World->UpperLeftX) / World->TileWidth);
int32 TestTileY = TruncateReal32ToInt32((Pos.Y - World->UpperLeftY) / World->TileHeight);
// act:  With initializing Result
canonical_position Result;

Result.TileMapX = Pos.TileMapX;
Result.TileMapY = Pos.TileMapY;

Result.TileX = TruncateReal32ToInt32((Pos.X - World->UpperLeftX) / World->TileWidth);
Result.TileY = TruncateReal32ToInt32((Pos.Y - World->UpperLeftY) / World->TileHeight);

And I did not want to change the incoming position, so fixing a bug before even testing it.

Edit GetCanonicalPosition in handmade.cpp:

// rem:  I only show the lines that will change
// act:  Replace Pos.TileMapX and Pos.TileMapY
--Pos.TileMapX;
--Pos.TileMapY;
++Pos.TileMapX;
++Pos.TileMapY;
// act:  With Result.TileMapX and Result.TileMapY
--Result.TileMapX;
--Result.TileMapY;
++Result.TileMapX;
++Result.TileMapY;

And then we have to figure out what our X and Y values are going to be. And this is something we have not had to handle yet. When we change tiles, we will have to deal with the floating position values of X and Y.

0:42:55 Storing X and Y positions relative to the tile

This is looking all that much better now that I think about it. When we go to the canonical_position, this X and Y value could actually be inside a tile as well. Which gets us out of the business of dealing with wrapping the X and the Y. Because remember, the X and the Y were wrong before. We never finished actually dealing with them. The X and the Y are offsets in the space of an entire tile map. So when we wrap those to the next tile, they would have been wrong. If we do them inside a tile, we are already dealing with wrapping the TileX and TileY, so if they are relative to a tile, it would just work.

So I am starting to like this and this is what explorative programming is all about. I’ve never done a tile map game before, so I am kind of finding out what I like how these things work. So I’m going to pursue this for it feels kind of good to have it be relative to a tile. It seems like it could save a little bit of work.

@0:43:50

Okay so entering GetCanonicalPosition, we have a TileMapX and TileMapY and we know that’s not going to change unless we wrap.

View GetCanonicalPosition in handmade.cpp:

Result.TileMapX = Pos.TileMapX;
Result.TileMapY = Pos.TileMapY;

We compute TileX and TileY from the offset X and Y within the tile map.

View GetCanonicalPosition in handmade.cpp:

Result.TileX = TruncateReal32ToInt32((Pos.X - World->UpperLeftX) / World->TileWidth);
Result.TileY = TruncateReal32ToInt32((Pos.Y - World->UpperLeftY) / World->TileHeight);

Anyway, I like where this is going so we are going to continue in this direction. Storing it relative to the tile just seems like a really good idea now that I think about it, but I just didn’t before. All of this is coming out kind of naturally in the course of seeing what the code had to do.

I can’t emphasize enough. It didn’t require any forethought. It just requires trying it, seeing what happens, and then finding out what is needed. And this is a much easier way to program than trying to think of everything upfront, especially when you have never done it before. Because you’re likely to guess wrong.

@0:45:00

So what I’d like to do now is re-figure out what the X and Y would be inside the tile. Let’s first subtract out the relative to the tile map for X and Y. So that’s the X and Y relative to the tile map, we then go ahead and truncate from real32 to int32.

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace
Result.TileX = TruncateReal32ToInt32((Pos.X - World->UpperLeftX) / World->TileWidth);
Result.TileY = TruncateReal32ToInt32((Pos.Y - World->UpperLeftY) / World->TileHeight);
// act:  With
real32 X = Pos.X - World->UpperLeftX;
real32 Y = Pos.Y - World->UpperLeftY;
Result.TileX = TruncateReal32ToInt32(X / World->TileWidth);
Result.TileY = TruncateReal32ToInt32(Y / World->TileHeight);

What we should do now is compute the offset as well. So to calculate Result.X, first I will take the product of TileX by the TileWidth. This is where the upper corner of the tile would be. Where this product would be the upper left part of the tile. While the product of Result.TileY and the TileHeight would be the top part of the tile. So for Result.X, I am going to take the original position Pos.X and subtract the product. And similar calculation for Result.Y.

Edit GetCanonicalPosition in handmade.cpp:

// pos:  Add after we truncate TileX and TileY

Result.X = Pos.X - Result.TileX*World->TileWidth;
Result.Y = Pos.Y - Result.TileY*World->TileHeight;

@0:46:15

So the X and Y of our canonical_position are tile-relative positions, and tell how far we are away from the upper left corner of a tile.

Edit canonical_position in handmade.h:

// pos:  Add this NOTE just before we declare X and Y
// NOTE(casey):  This is tile-relative X and Y
real32 X;
real32 Y;

While the X and Y of the raw_position are tile-map relative positions. So these X and Y tells us how far away from the upper left corner of the tile-map.

Edit raw_position in handmade.h:

// pos:  Add this NOTE just before we declare X and Y
// NOTE(casey):  Tile-map relative X and Y
real32 X;
real32 Y;

Now that our X and Y are relative, they are always going to be right, thus when we adjust which tile we are looking at, the TileX and TileY will now just be correct when we do the adjustment.

Edit GetCanonicalPosition handmade.cpp:

// rem:  Only showing the lines that will change
// act:  Replace
if(TestTileX < 0)
    TestTileX = World->CountX + TestTileX;
if(TestTileY < 0)
    TestTileY = World->CountY + TestTileY;
if(TestTileX >= World->CountX)
    TestTileX = TestTileX - World->CountX;
if(TestTileY >= World->CountY)
    TestTileY = TestTileY - World->CountY;
// act:  With
if(Result.TileX < 0)
    Result.TileX = World->CountX + Result.TileX;
if(Result.TileY < 0)
    Result.TileY = World->CountY + Result.TileY;
if(Result.TileX >= World->CountX)
    Result.TileX = Result.TileX - World->CountX;
if(Result.TileY >= World->CountY)
    Result.TileY = Result.TileY - World->CountY;

And we need to return Result.

Edit GetCanonicalPosition in handmade.cpp:

// pos:  End of the function

return(Result);

@0:47:23

Going back to IsWorldPointEmpty, when we call GetCanonicalPosition we pass TestPos, which is a raw_position.

View IsWorldPointEmpty in handmade.cpp:

internal bool32
IsWorldPointEmpty(world *World, int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY)
{
    bool32 Empty = false;

    world_position CanPos = GetCanonicalPosition(TestPos);      // rem:  We need a raw_position
    tile_map *TileMap = GetTileMap(World, CanPos.TestTileMapX, TestTileMapY);
    Empty = IsTileMapPointEmpty(World, TileMap, TestTileX, TestTileY);

    return(Empty);
}

Which means now instead of all the parameters that are passed into IsWorldPointEmpty, we can pass a raw_position.

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace signature parameters
IsWorldPointEmpty(world *World, int32 TestTileMapX, int32 TestTileMapY, real32 TestX, real32 TestY)
// act:  With
IsWorldPointEmpty(world *World, raw_position TestPos)

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace CanPos's type world_position
world_position CanPos = GetCanonicalPosition(TestPos);
// act:  With canonical_position
canonical_position CanPos = GetCanonicalPosition(TestPos);

To check if IsTileMapPointEmpty, the TileMapX and the TileMapY tell us what TileMap we are looking at. The TileX and TileY of that TileMap, tell us where in the tile we are.

Edit IsWorldPointEmpty in handmade.cpp:

// act:  Replace TestTileMapX, TestTileMapY, TestTileX and TestTileY
tile_map *TileMap = GetTileMap(World, CanPos.TestTileMapX, TestTileMapY);
Empty = IsTileMapPointEmpty(World, TileMap, TestTileX, TestTileY);
// act:  With  CanPos.TileMapX, CanPos.TileMapY, CanPos.TileX, and CanPos.TileY
tile_map *TileMap = GetTileMap(World, CanPos.TileMapX, CanPos.TileMapY);
Empty = IsTileMapPointEmpty(World, TileMap, CanPos.TileX, CanPos.TileY);

@0:48:14

In our GameUpdateAndRender, IsWorldPointEmpty is now going to take a raw_position.

PlayerPos will be our PlayerTileMapX, PlayerTileMapY with NewPlayerX and NewPlayerY positions.

PlayerLeft will be the same as PlayerPos, only that its X will subtract half the PlayerWidth.

Likewise for PlayerRight, it will be the same as PlayerPos, but X will add half the PlayerWidth.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
if(IsWorldPointEmpty(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY,
                     NewPlayerX - 0.5f*PlayerWidth, NewPlayerY) &&
   IsWorldPointEmpty(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY,
                     NewPlayerX + 0.5f*PlayerWidth, NewPlayerY) &&
   IsWorldPointEmpty(&World, GameState->PlayerTileMapX, GameState->PlayerTileMapY,
                     NewPlayerX, NewPlayerY))
// act:  With
raw_position PlayerPos = 
    {GameState->PlayerTileMapX, GameState->PlayerTileMapY,
     NewPlayerX, NewPlayerY};
raw_position PlayerLeft = PlayerPos;
PlayerLeft.X -= 0.5f*PlayerWidth;
raw_position PlayerRight = PlayerPos;
PlayerLeft.X += 0.5f*PlayerWidth;

if(IsWorldPointEmpty(&World, PlayerPos) &&
   IsWorldPointEmpty(&World, PlayerLeft) &&
   IsWorldPointEmpty(&World, PlayerRight))

@0:50:20

Now, we should be in a place where we are working a little more cleanly. Already, you can start to see it is looking cleaner for now we no longer have giant strings of variables that we are passing. Where we could have gotten the order wrong or confused about what is what.

View GameUpdateAndRender in handmade.cpp:

if(IsWorldPointEmpty(&World, PlayerPos) &&
   IsWorldPointEmpty(&World, PlayerLeft) &&
   IsWorldPointEmpty(&World, PlayerRight))

Now we are passing some bundles which look a little more intelligible.

View handmade.h:

struct canonical_position
{
    int32 TileMapX;
    int32 TileMapY;

    int32 TileX;
    int32 TileY;

    // NOTE(casey):  This is tile-relative X and Y
    real32 X;
    real32 Y;
};

struct raw_position
{
    int32 TileMapX;
    int32 TileMapY;

    // NOTE(casey):  Tile-map relative X and Y
    real32 X;
    real32 Y;
};

@0:50:46 TODO(bk): Casey steps through our code to see the current status.

So let’s take a look in here and see where we are at.

View GameUpdateAndRender in handmade.cpp:

// rem:  Set a breakpoint here
if(IsWorldPointEmpty(&World, PlayerPos) &&
   IsWorldPointEmpty(&World, PlayerLeft) &&
   IsWorldPointEmpty(&World, PlayerRight))

We’ve got a PlayerPos, and X and Y are both at 150.0, which is exactly what we expect.

|     Name    |   Value    |
|-------------|------------|
| PlayerPos   |            |
| -> TileMapX |          0 |
| -> TileMapY |          0 |
| -> X        | 150.000000 |
| -> Y        | 150.000000 |

Looking at our PlayerLeft and PlayerRight, that is good.

|     Name    |   Value    |
|-------------|------------|
| PlayerLeft  |            |
| -> TileMapX |          0 |
| -> TileMapY |          0 |
| -> X        | 127.500000 |
| -> Y        | 150.000000 |
|-------------|------------|
| PlayerRight |            |
| -> TileMapX |          0 |
| -> TileMapY |          0 |
| -> X        | 172.500000 |
| -> Y        | 150.000000 |
|-------------|------------|

Brian: Casey had made a typo and PlayerRight.X subtracted half of the PlayerWidth instead of added. But since this was a minor typo that was quickly caught, I did not feel it to be worth leaving in.

So let’s go into IsWorldPointEmpty by stepping into the function, and then look at our GetCanonicalPosition call (also stepping into the function).

Our GetCanonicalPosition call is working between two things, a canonical_position that it’s trying to produce, and a Pos that it gets in.

|     Name    |    Value    |
|-------------|-------------|
| Pos         |             |
| -> TileMapX |           0 |
| -> TileMapY |           0 |
| -> X        |  150.000000 |
| -> Y        |  150.000000 |

I’m going to compute the X and the Y, which is basically the X and the Y relative to the tile map itself.

| Name |   Value    |
|------|------------|
| X    | 180.000000 |
| Y    | 150.000000 |

It is exactly what we expected for there is an offset of negative 30 on the X.

So let’s grab the TileX and TileY and we see that it is 3 and 2.

|     Name    |   Value    |
|-------------|------------|
| Result      |            |
| -> TileMapX |          0 |
| -> TileMapY |          0 |
| -> TileX    |          3 |
| -> TileY    |          2 |
| -> X        | 0.00000000 |
| -> Y        | 0.00000000 |

Is that correct? I guess I don’t know where the character starts. With the position being at 180 / 150, and the tiles being 60 wide, it seems right.

|       Name       |         Value          |
|------------------|------------------------|
| World            |                        |
| -> CountX        | 17                     |
| -> CountY        | 9                      |
| -> UpperLeftX    | -30.0000000            |
| -> UpperLeftX    | 0.000000000            |
| -> TileWidth     | 60.0000000             |
| -> TileHeight    | 60.0000000             |
| -> TileMapCountX | 2                      |
| -> TileMapCountY | 2                      |
| -> TileMaps      | 0x00000000001ee850 ... |

So now we calculate Result.X and Result.Y, and that….

|     Name    |    Value    |
|-------------|-------------|
| Result      |             |
| -> TileMapX |           0 |
| -> TileMapY |           0 |
| -> TileX    |           3 |
| -> TileY    |           2 |
| -> X        | -30.0000000 |
| -> Y        |  30.0000000 |

…does not look correct at all.

Oh. Awesome.

So I did all the work, and then again didn’t use it.

View GetCanonicalPosition in handmade.cpp:

real32 X = Pos.X - World->UpperLeftX;
real32 Y = Pos.Y - World->UpperLeftY;
// rem:  removed for brevity

Result.X = Pos.X - Result.TileX*World->TileWidth;   // rem:  Looking at this assignment
Result.Y = Pos.Y - Result.TileY*World->TileHeight;

We intentionally made things relative to the tile map so that I could compute with the result, because I didn’t want to deal with things in the offset space. So I am going to replace the Pos.X and Pos.Y with the X and Y that I computed.

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace Pos.X` and Pos.Y
Result.X = Pos.X - Result.TileX*World->TileWidth;
Result.Y = Pos.Y - Result.TileY*World->TileHeight;
// act:  With X and Y
Result.X = X - Result.TileX*World->TileWidth;
Result.Y = Y - Result.TileY*World->TileHeight;

I can’t subtract out the position that I ended up computing for the upper left of the tile, if I don’t also take into account the UpperLeftX. So that is why I wanted to get rid of that ahead of time.

@0:54:22

What we could do, we could Assert our results of mapping into the tile space, that they are always within the bounds that would be allowed. We could say that once we put something into tile space it should never be allowed to be outside the size of a tile.

Edit GetCanonicalPosition in handmade.cpp:

// pos:  After we assign Result.X and Result.Y

Assert(Result.X >= 0);
Assert(Result.Y >= 0);
Assert(Result.X < World->TileWidth);
Assert(Result.Y < World->TileHeight);

View GetCanonicalPosition in handmade.cpp:

// rem:  Set a breakpoint here
if(Result.TileX < 0)

So now our result should always be within the tile.

|     Name    |    Value    |
|-------------|-------------|
| Result      |             |
| -> TileMapX |           0 |
| -> TileMapY |           0 |
| -> TileX    |           3 |
| -> TileY    |           2 |
| -> X        | 0.000000000 |
| -> Y        |  30.0000000 |

And that’s what we expect. We should never be negative for it is relative to the upper left of the tile.

After removing our breakpoint, we should be able to drive around and in theory all our stuff should still work. And it does seem to.

TODO(bk): Add pictures of walking around.

So the only thing we have left to do now, is to actually move where the player is on the tile map if he moves off screen.

0:56:07 Moving the player on to the new tilemap

We set ourselves up to do that so it should be pretty straight forward. Where we have PlayerPos in our GameUpdateAndRender, if we accept a new GameState, let’s go ahead and actually move the player to a new tile map where warranted.

View GameUpdateAndRender in handmade.cpp:

if(IsWorldPointEmpty(&World, PlayerPos) &&
   IsWorldPointEmpty(&World, PlayerLeft) &&
   IsWorldPointEmpty(&World, PlayerRight))
{
    GameState->PlayerX = NewPlayerX;
    GameState->PlayerY = NewPlayerY;
}

We can do that now pretty easily for we have GetCanonicalPosition, but there is something we have not yet quite done yet which makes things a little uglier than it should be. For now we can just call the function to get a CanPos for the player’s new position.

The problem is, the player is still in tile map relative coordinates.

Once we have the CanPos, that has a TileMapX and TileMapY which we can assign to our GameState. And for the PlayerX and PlayerY positions, we just need to take our CanPos.X and CanPos.Y, and add how far they were from the upper left corner. This would be our UpperLeftX plus the product of TileWidth and TileX, added to our X. Similar can be done for PlayerY.

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace
{
    GameState->PlayerX = NewPlayerX;
    GameState->PlayerY = NewPlayerY;
}
// act:  With
{
    canonical_position CanPos = GetCanonicalPosition(&World, PlayerPos);

    GameState->PlayerTileMapX = CanPos.TileMapX;
    GameState->PlayerTileMapY = CanPos.TileMapY;
    GameState->PlayerX = World.UpperLeftX + World.TileWidth*CanPos.TileX + CanPos.X;
    GameState->PlayerY = World.UpperLeftY + World.TileHeight*CanPos.TileY + CanPos.Y;
}

What I had to do is these positions are in screen space and take it and change to world space.

@0:59:11

We get an Assert after moving around.

View GetCanonicalPosition in handmade.cpp:

Assert(Result.Y >= 0);  // rem:  Assert was thrown here
|     Name    |    Value    |
|-------------|-------------|
| Pos         |             |
| -> TileMapX |           0 |
| -> TileMapY |           1 |
| -> X        |  482.799683 |
| -> Y        | -1.73331141 |
|-------------|-------------|
| Result      |             |
| -> TileMapX |           0 |
| -> TileMapY |           1 |
| -> TileX    |           8 |
| -> TileY    |           0 |
| -> X        |  32.7996826 |
| -> Y        | -1.73331141 |

1:00:49 Floor instead of truncate

Brian: Casey demonstrates what the issue we are having.

Edit GameUpdateAndRender in handmade.cpp:

// pos:  Just after the initial asserts.

real32 Float = -0.75f;
int32 Truncated = TruncateReal32ToInt32(Float);  // rem:  set a breakpoint here and step in

Brian: This is only for demonstration. Please remember to delete after testing.

You can see what I did, I passed in -0.75 and I asked it to truncate.

Brian: Debugging TruncateReal32ToInt32

|  Name  |    Value     |
|--------|--------------|
| Result |            0 |
|--------|--------------|
| Real32 | -0.750000000 |

Now all of our truncations have been working properly, but the reason it has been working is that it has always been positive numbers.

TODO(bk): Casey draws truncation

But if you think about what will happen when we are below zero, we want it to truncate down to negative one. Because what we wanted to do is we wanted to see where we still were in a tile map that imaginarily keeps going below zero.

Unfortunately, what C will actually do is truncate towards zero. So if you are negative, instead of truncating down to the next lower value, it truncates to the next value closer to zero. This is not what we wanted, we want something that truncates towards negative infinity.

We can do a simple hack if we want to enforce this. If we have the TruncateReal32ToInt32, we can basically have a FloorReal32ToInt32 function.

So what I really wanted is flooring instead of truncation. So in places that I used Truncate, I should use Floor.

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace
Result.TileX = TruncateReal32ToInt32(X / World->TileWidth);
Result.TileY = TruncateReal32ToInt32(Y / World->TileHeight);
// act:  Replace
Result.TileX = FloorReal32ToInt32(X / World->TileWidth);
Result.TileY = FloorReal32ToInt32(Y / World->TileHeight);

Unfortunately, we could use floorf which is located in math.h. This is not want we really want to do, but it’s near the end of the day and we don’t have time to implement it fully. So the first thing on Monday is talk about how to implement these math functions.

Add FloorReal32ToInt32 in handmade.cpp:

// TODO(casey):  HOW TO IMPLEMENT THESE MATH FUNCTIONS!!!!
#include "math.h"
inline int32
FloorReal32ToInt32(real32 Real32)
{
    int32 Result = (int32)floorf(Real32);
    return(Result);
}

@1:07:36

TODO(bk): Casey runs the game and is able to walk between all four tiles.

Ladies and Gentlemen, we already have a character walking around through a tile map.

@1:09:14

So next week is going to be a pretty serious week. We have a lot of things that we want to do. Looking at raw_position, is it even necessary anymore? Do we really need these raw positions?

Edit raw_position in handmade.h:

// TODO(casey):  Is this ever necessary?
struct raw_position
{
    // rem:  Removed for brevity.
};

Similarly in our game_state, our player state should be canonical position?

Edit game_state in handmade.h:

struct game_state
{
// TODO(casey):  Player state should be canonical position now?
    // rem:  Removed for brevity.
};

Hopefully you can see from the work that we’ve done today, it’s starting to get to the point where we really should start thinking about coordinate systems. Because now we actually have an idea of what our coordinate system is. We have a TileMapX and TileMapY. We have a TileX and a TileY. And we have a floating point offset. This gives us all the information we needed to actually make things work.

Is this a good coordinate system? What do coordinate systems mean in general? Maybe that’s what we sort of start to need to work on next week while we start to clean these things up. We also need to start to talk about vectors, maybe we should start representing X and Y as bundles because we are always using them together.

We have a lot of stuff to think about and it is looking really promising. We also have to implement a floor function.

Good shape Ladies and Gentlemen. This was a good week! I am feeling positive about this week.

Q & A - 030

##1:10:48 Q&A

1:11:52 Question: Can you talk about the inline keyword? Do you apply any strategies to make sure the compiler does not ignore the request?

So we are not really at the stage where we are doing any performance work, so when I put inline on functions, honestly that inline is more for me than it is for the compiler at this point. When I’m putting inline on functions, it’s me telling my future self that probably these things might be inlined; but it really has nothing to do with the performance.

Maybe (GetCanonicalPosition) should not be inlined. I am not at all trying to suggest that these are functions that should or should not be inlined when we actually go to do performance tuning. Those are actually me this might be something that might need to be inlined when we are way way down the road. It’s just a habit of me marking it.

When we get to performance tuning we talk more about that and looking at inlining functions. So don’t worry too much about that at this point in the game.

1:13:13 Question: Are we using C or C++?

We are using C++, but I typically say that we are programming in C so that people don’t get too confused for we are not using any C++ features. There’s one or two features in C++ that we use: operator overloading and function overloading are really the two things. So we are technically programming in C++; but, we won’t be using 90% of the new features that are in C++ that aren’t in C.

1:14:47 Question: How did we start the code for the game?

If you go to https://handmadehero.org , you can go to the Video Archive, and 100% of the coding has been archived. So anything you want to see, you can get.

1:15:07 Question: What’s so bad about including math.h?

Nothing is bad about including math.h, sorry, don’t get the wrong idea. You are welcome to include it. But I don’t want to use any of the C runtime library that we don’t have to use, so I’d like to go ahead and talk about how to get the processor to do the floor function directly by telling the compiler to use intrinsics here or something similar. So I want to go ahead and explore that topic a little bit, so we don’t keep dropping math.h around.

1:15:54 Question: Why not subtract 1 if input is under 0, instead of using floor()?

There is definitely a bunch of tricks here instead of doing the truncation. We don’t have to do it the way we are doing. We could check first to see if we are in bounds. But what I would like to do eventually is make this GetCanonicalPosition call actually work just right with math, so that you can actually have very large negative offsets and not have to ifs. I’m thinking we could probably get it to the point where it might even just be completely branchless? Meaning there’s no need to test anything, it just kind of does the canonicalization directly. And that might be faster and more efficient depending on the circumstances. So we will see.

So the answer is you could implement this in many different ways and which way you want to implement it would entirely depend on the circumstances and the requirements for it, so don’t take this as some kind of golden standard of how you should do it. Again, this is the exploratory part of things, so right now we are kind of not focusing on what is the absolute right thing to do always. It’s more about getting things working so we can decide what we want.

1:17:09 Question: I thought integers were faster to compute than floats, even if the cpu has a floating-point unit.

Question: A few days ago you said floating point math is the most CPU efficient. I thought integers were faster, even if the CPU has a floating point unit.

No, generally that is not true. Floating point math is usually fastest thing you can do currently, and the reason for that is doing the same operation in a fixed point integer requires of operations. Whereas the same operation could be done in one floating point instruction and typically the processor is fast at floating point instructions now as it is at integer instructions.

If you would like to go look at the agner fog tables that we talked about before.

http://www.agner.org/optimize/instruction_tables.pdf

If you look at the SSE intrinsics, but this is getting way ahead where we are, but to answer the question, you will notice that looking at the table and the functions packed for integer and for floating point, the instructions will take the same amount of time.

So there is no penalty for using floating point. Floating point tends to be as fast in terms of cycles taken. But, you do less operations. So if you want to do a floating point multiply, it’s one operation to do the multiply. Where if you want to do a fixed point multiply, it’s usually at least two operations.

So in general, floating point is always faster for everything that we are going to do, and so you really don’t want to use integer unless you need the properties of an integer.

1:19:43 Question: How do you decide when to pack things in a struct?

Question: You’ve mentioned before that you want to avoid forcing the caller of a function to pack arguments into a struct. I’m guessing sometimes it makes things cleaner and sometimes it does not, but how do you decide what way to go in any particular case?

There are two cases in terms of packing things into a structure. One is because both the caller and the thing being called tend to always operate on everything together. And that is the case that I’m trying to get to here with our exploratory coding.

The other is when only the caller or implementer wants those things. And in the former case, it is good. In the latter case it is bad.

So let me give you a very explicit demonstration.

Let’s say we have a DrawLine function that takes in four values.

void
DrawLine(float X, float Y, float X2, float Y2)
{

}

If I’m going to call the DrawLine function, I need to pass the correct values.

void
main()
{
    DrawLine(3.0f, 3.5f, 10.0f, 10.5f);
}

So lets look at two ways we can improve this by packing a good structure and a bad one.

A good one would be like lets say we have a point, which has an X and a Y.

struct point
{
    float X;
    float Y;
};

So I then change the DrawLine function to accept two points. And all that does is get unpacked and we are going to do the same draw call.

void
DrawLine(point P0, point P1)
{
    DrawLine(P0.X, P0.Y, P1.X, P1.Y);
}

This is a totally reasonable way to start compressing this API. Because it is very likely that whatever is going on in main, it’s actually thinking about those locations on the screen as X and Y. This is actually the common case.

The caller would probably be happy because that may be what they actually have already.

void
main()
{
    point P0 = {3.0f, 3.5f};
    point P1 = {10.0f, 10.5f};

    DrawLine(P0, P1);
}

And so, when you are doing your exploration, that’s a good thing to explore for that will compress the code on both sides. main is probably having a list of P values, and it iterates over those values.

point P[] = {...};
for(int PIndex = 0;
    PIndex < ArrayCount(P) - 1;
    ++PIndex)
{
    DrawLine(P[PIndex], P[PIndex + 1]);
}
// imagine instead of this:
DrawLine(P[PIndex].X, P[PIndex].Y, P[PIndex + 1].X, P[PIndex + 1].Y);
// the caller had this:
DrawLine(P[PIndex], P[PIndex + 1]);

So the compression that was done was better for both the caller and the function.

A bad example of how to do the compression would have been to say create a line structure.

struct line
{
    float X;
    float Y;

    float X2;
    float Y2;
};

So now our DrawLine just accepted a line structure. But now it would be hard to see the benefit for anyone other than the implementer of DrawLine.

void
DrawLine(line Line)
{
    DrawLine(Line.X, Line.Y, Line.X2, Line.Y2);
}

And looking at our earlier example, it would not even work. You would need to create a line first and assign all the variables just to pass to the function.

// imagine instead of this:
DrawLine(P[PIndex], P[PIndex + 1]);
// the caller had to do the following
line Line;
line.X = P[PIndex].X;
line.Y = P[PIndex].Y;
line.X2 = P[PIndex + 1].X2;
line.Y2 = P[PIndex + 1].Y2;
DrawLine(line);

And your only hope of recovering is to make some kind of constructing thing…

line
MakeLine(point P0, point P1)
{
    line Line;

    Line.X = P0.X;
    Line.Y = P0.Y;
    Line.X2 = P1.X;
    Line.Y2 = P1.Y;

    return(Line);
}

And so now all we’ve done is make it into the function that we had before.

// Needing to pass line
DrawLine(MakeLine(P[PIndex], P[PIndex + 1]));
// Needing to pass points
DrawLine(P[PIndex], P[PIndex + 1]);

Structure packing of parameters should make it easier for both sides to do their job. If most of the time it’s actually only making it easier for one side, and making it more difficult for the other, that is a bad decision.

1:27:28 Question: Why would we care about screen coordinates? Isn’t that just platform code.

Yes. That is basically where I am trying to get to. I didn’t want to just plop down and say that I already know how to write it. What I want to do is kind of show you how to get there. I wanted to start with screen coordinates, because that’s what you draw in; but, eventually we are going to get to, everything is operating in abstract coordinate space that is our game world.

We haven’t quite gotten there yet. But you can see we are getting closer. We are already at the point where now we are sort of not talking about screen coordinates in a bunch of the code; we are talking about tile world coordinates. However, these are still in pixels.

Edit canonical_position in handmade.h:

// NOTE(casey):  This is tile-relative X and Y
// TODO(casey):  These are still in pixels... :/    // act:  Add this TODO
real32 X;
real32 Y;

They are pixel offsets from the tile. That means if we were to change the size of the tile in terms of rendering, we actually also have to change the gameplay code and that’s not very good.

We are leveling ourselves up through the various stages, some of which you would just skip if you are an experienced game programmer because you know. But I don’t want to skip those things, I want to show you how we discover them. Because a lot of times, and like I said, I’m discovering things myself about how I want to do this for I have never done a tile-map game before.

It’s important to learn these attack skills and strategies that you will use to discover how to write things properly because otherwise you will only be able to write the things that I show you how to write. I want you to be able to write anything that you want after this. And what we are doing is slowly building towards that.

1:29:26 Question: Are you going to make walls obstruct the player the same way the player currently obstructs the walls?

TODO(bk): He is not sure yet and this is something we need to investigate. Perhaps the wall will partially obstruct the player, but we will wait till we can explore that in game.

1:30:17 Question: Can you explain the raw_position and canonical_position again?

What I found writing the code, was in order to do a bunch of the computations that we wanted to do, we had to have a three-tiered structure. We wanted to know what tile map we were on, we needed to know which tile we were on the tile map, and then inside that tile we needed to know the X and Y offset in pixels. This is because our character moves continuously. That’s what the canonical_position is.

The raw_position, which is something that may go away, is something we pass in that says which tile map we are on, but it’s X and Y are relative to the tile-map itself (the whole screen).

And we were using raw_position because that is what the outer code was using. It was just using where it was relative to the tile map because we wrote it to just moving a rectangle around on the screen.

What we are seeing is we are pushing away from raw_position and on the next stream we can just use canonical_position within our game_state.

1:32:16 Question: Instead of checking three points on the player, wouldn’t it be better to check only the side the player is moving towards?

TODO(bk): Casey says that it would be more efficient, but wouldn’t say it would be better. But we are just exploring right now and we are not worried about efficiency.

1:34:39 Question: Why not have a single set of coordinates for the player that maps the player to a world position?

TODO(bk): Casey talks about the limitations of using 32-bit floats and what it would not be a good idea for the game that he is designing.

1:42:14 Question: Are there any consequences of using inline in early development, i.e harder debugging?

TODO(bk): Casey states that there are no consequences. And there is even a compiler switch that you can disable them if they do become an issue.

1:43:22 Question: Wouldn’t it be better to call the X and Y variables something more descriptive?

Question: Wouldn’t it be better to call the X and Y variables in canonical_position TileRelativeX, TileRelativeY?

TODO(bk): Casey knows the person asking the question and links to their website. http://ernesto-rpg.tumblr.com/ Brian: Seems to have an updated link: http://ernestogame.com/

He’s asking whether we should use a better name than X and Y for our canonical_position. I am not against it, but was concerned with the verboseness in how many times we would be referencing them.

I do agree that it is better and clearer for people who are reading the code. Most of the time I do like verbose names, but every once and a while I shorten them. I am fine starting with a more verbose name and then shortening it if it appears to become too verbose.

Edit canonical_position in handmade.h:

// act:  Replace X and Y
real32 X;
real32 Y;
// act:  With TileRelX and TileRelY
real32 TileRelX;
real32 TileRelY;

Edit GetCanonicalPosition in handmade.cpp:

// act:  Replace Result.X and Result.Y
Result.X = X - Result.TileX*World->TileWidth;
Result.Y = Y - Result.TileY*World->TileHeight;

Assert(Result.X >= 0);
Assert(Result.Y >= 0);
Assert(Result.X < World->TileWidth);
Assert(Result.Y < World->TileHeight);
// act:  With Result.TileRelX and Result.TileRelY
Result.TileRelX = X - Result.TileX*World->TileWidth;
Result.TileRelY = Y - Result.TileY*World->TileHeight;

Assert(Result.TileRelX >= 0);
Assert(Result.TileRelY >= 0);
Assert(Result.TileRelX < World->TileWidth);
Assert(Result.TileRelY < World->TileHeight);

Edit GameUpdateAndRender in handmade.cpp:

// act:  Replace CanPos.X and CanPos.Y
GameState->PlayerX = World.UpperLeftX + World.TileWidth*CanPos.TileX + CanPos.X;
GameState->PlayerY = World.UpperLeftY + World.TileHeight*CanPos.TileY + CanPos.Y;
// act:  With CanPos.TileRelX and CanPos.TileRelY
GameState->PlayerX = World.UpperLeftX + World.TileWidth*CanPos.TileX + CanPos.TileRelX;
GameState->PlayerY = World.UpperLeftY + World.TileHeight*CanPos.TileY + CanPos.TileRelY;

We could even change to RelX and RelY in the future and at least you would know it is relative and then would need to look up what it was relative too.

I normally like to start from a more descriptive name and then reduce it.

1:45:52 Question: The game will happen in whole screens without any scrolling or camera movement?

TODO(bk): For the design of the game, he does not want any scrolling for most screens. But he does not want to eliminate the idea that we will never use cameras. But one of the design decisions is that every screen is a complete room.

1:48:13 Question: Could you explain how pointers/references are working or what you use them for and why?

TODO(bk): He points to the Intro to C videos that he did before Handmade Hero.

1:49:02 Question: Was there ever a point in your career where you used these unfortunate structure packing practices?

References

Extras

Casey uses Krita

For the drawing program, Casey uses Krita

ctime stats:

Contents


Please contact me at if you have any questions or comments.