Coding Quickie: Isometric Tiles

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello a nice quick video this time let's look at isometric tiles and as usual a quick demonstration of the application before we get going the isometric approach to tile rendering gives it a pseudo 3d feel so here we can see I'm selecting tiles but the tiles are no longer axis aligned or aligned with the edges of the screen so that's adds some complication about working out which tile is currently highlighted and you can get to an appreciable feel of depth by using sprites that are larger than the tile itself so here we've got some trees being planted different type of tree there and all of this simple demonstration is going to do is allow us to scroll through a fixed selection of tile types I really want to keep this one nice and quick and simple working with conventional to detail maps is something we have done many times on the one lone coda Channel we would assume a world that looks like this and here we would have an x-axis and here we would have a y-axis which would imply we have an origin at the top left here once we establish that our tiles have a width and a height in screen space ie how many pixels of the screen the tile occupies we can then quite easily translate the mouse location into the discrete form required to access the array that represents this tile map so assuming our mouse cursor is somewhere here we can take this mouse coordinates let's call it m X and my and what we want to work out is what is the coordinate of the cell that the mouse is within and this has always been simple because we allow integer division to sort all this out for us so we can assume that cell X is equal to the mouse's location X integer divided by the tiles width and it's the integer divided that's important because that will give us a whole number and discard any remainder as part of the division so that will always give us a location that represents the cell conveniently we can do exactly the same for Y but this time we'll do an integer divided by the tile height it could be that your tiles are square but I prefer to program it in a more flexible way sometimes it's also convenient no word as the mouseland lie within that particular cell well that's trivial too so let's assume this is called offset X we can take our mouse coordinate and we'll take the modulus with the tile width so this will give us a offset in screen pixels of how far the mouse coordinate is into the tile and naturally we have a corresponding for y sometimes it's helpful to normalize this value by dividing as well by tile width and tile height to give us a value between 0 and 1 to represent where the mouse is within the cell and if we know the overall width of our world then we can easily take our cells x and y location and convert that into a one dimensional index into an array that represents this world we have done this countless times on the channel it is simply going to be I equals C Y times width plus C X for regular viewers of the channel all of this should be very familiar when working with isometric tiles the rules aren't quite as simple simply because there is no axis alignment anywhere in our world map when rendered on the screen so I'm going to make some assumptions that up here is our 0 0 coordinate and along this edge is our x axis and along this edge is our y axis the first problem we have is that a particular tile is no longer square and so transforming the mouse coordinates as we did before is no longer really that applicable secondly if we look at adjacent tiles in this map we see that they aren't necessarily adjacent in the isometric map and so we need to think of things a little differently I've created some rudimentary isometric tile graphics and it's quite important that to get my approach to work I'm going to make an assumption that the aspect ratio of my tiles is 2 to 1 so the tile fundamentally is going to be twice as wide as it is high and in this instance I've chosen tiles which are 40 pixels wide and 20 pixels high and I've created a selection of them so along the top here I've got a highlighted yellow one which will present which one the cursor is over I've got an empty tile I've got one filled with grass and I've got some larger tiles here at the bottom which are populated with simple objects so let's start at first by simply rendering a plane of empty isometric tiles as usual I'm going to use my pixel game engine to do this so I've created a class called isometric demo and it doesn't have anything in it yet other than it will clear the screen to white so we don't load anything on on user create and per frame all we're drawing is the background at the window itself is constructed to be 512 pixels wide by 480 pixels high and each pixel is going to be represented by two screen pixels fortunately for this demonstration there isn't very much code about 100 lines I'm going to start by adding some variables that describe the world so here I've added a integer 2d vector to describe the size it's going to be 14 in the x-axis and 10 in the y-axis as established my isometric tiles and principally are going to be 40 pixels wide by 20 pixels high and I'm going to establish that the origin of my world that is worship the zero zero isometric tiles start to be rendered is going to be at five one and that means five isometric tiles across and one down from the top left of the screen I know I've got some graphics data to load so I'm going to create an OLC sprite pointer to store that and I'm going to create another pointer to store the 2d world array in on user create the first thing I'll do is load my PNG file that contains my isometric tile data it contains all of those sprites and will use the draw partial sprite to extract regions of it to draw to the screen and then I'm going to initialize my world array depending on the size of the world that we've specified earlier and I'm going to make sure that that's defaulted to all zeros which is going to represent an empty tile for each frame the first thing I'm going to do is clear the screen and I know I'm going to render the entire world for this application we're not going to do any clipping so I'm going to create two nested for loops that iterate through all of the cells in the world in this instance I've chosen to render Y first and then X because I want to render from the top of the screen towards the bottom and this is because conceptually bottom of the screen is closer to the player than the top of the screen so we want objects at the bottom to be drawn last we've already established that the world does not exist in the same space as the screen so we're going to need a transformation function I'll call that to screen and populate it in a minute but ultimately that should give us a 2d coordinate of were to draw a particular isometric tile on the screen now we just need to decide which type of tile we should be drawing and so here I'll take the y-coordinate and the x-coordinate and the width of the whole world to index the P world array if the value of the array at that location is zero that's going to be a blank tile I'll call this an invisible tile here and I'm going to use the pixel game engines draw a partial sprites function pass it to the transformed coordinates of the world so it's in screen space and then select the appropriate sprite out of the sprite sheets I showed earlier and for now that's it because I have a feeling I'm going to be using two screens several different places I'm going to create a small lambda function to do that transformation for me and I'll put that up here so this will take in an X and a Y in world space and give me the screen coordinates in return the image file I'm using has transparency it has two types of transparency one is simply there is a transparent pixel or there isn't and with this glowing rectangle we've got varying degrees of translucency per pixel the pixel game engine can render these differently and it's more optimal to choose the appropriate method before trying to draw the sprite since most of my graphics have a binary alpha value for the pixel I'm going to use the masking mode of the pixel game engine to perform the rendering this means if there is a pixel with any transparency at all it won't draw it and it's important that when you start playing with the pixel modes that at some point you set them back to normal because they're persistent once they're changed now let's have a look at this transformation to start with we know that each isometric tile is in fact a two dimensional sprite and so even though the tiles coordinate exists here at the top center the sprite coordinate actually exists here and therefore our transform will take this world coordinate and generate this screen space coordinate in returned we can deduce this transformation empirically just by looking at the data starting at zero zero we know the sprite needs to be in this location and relative to the world origin this is half the sprite width to the left if we increase just in the x axis of world space our next sprite is here so we have moved a long half the tile width and we've also moved down half the tile height and this is true for all increments in X so we can make an assumption that our screen x-coordinate contains a component like this world x x tile width over two and our screen y-coordinate similarly has x x tile height over two however our world is not one dimensional we're not going to just Traverse along the x axis we also have a y component which also influences both the screen X and screen y locations so very simple analysis again if we increase in just the y axis we can see we've moved backwards one tile width over two and we've also moved down one tile height over two so as Y increases we're going to move backwards in X so I'll subtract the Y component also tile width over two but they do move down the screen so it's plus the y-component tile height over - hopefully it's easy to see that these can be rearranged to the form SX is therefore X take Y multiplied by tile width over 2 and screen y coordinate is X plus y tile height over 2 we're almost done here but we must realize that the origin of the world in screen space is no longer the top-left of the screen so we have to also offset our screen X&Y position to this origin location this means our two screen lambda transform function isn't very complicated I'm going to make it return a integer 2d vector and here you can see I've taken the origin and multiplied it by the tile size and to that I add the offsets we've just calculated and as I ate through each location in the world I calculate its screen position and I draw the sprite so let's take a look very nice we specified that the world was 14 along the x axis which is along here and 10 along the y axis I've chosen those numbers just so the whole world fits on the screen you can of course implement your own panning and zooming using this approach but this video is specifically about how do we select the tiles and well tile selection implies that we need some mouse information so I'm creating a 2d vector and populating it with the mouse location in screen space as I've demonstrated at the start of this quick video we can work out approximately which cell the mouse is in by using integer division now that doesn't necessarily make much sense right now but bear with me and I'll explain shortly I also want to calculate the offset into the cell and you can see I'm using the modulus operator to do just that but hang on a sec these cells are in screened space in fact let's draw a were this cell is to visualize this effect to draw the cell in screen space we're currently hovering over I'm just going to draw a rectangle at the cells location multiplied by the size of the tile and I'm also going to throw in some on-screen text so we can visualize the coordinates so here you can see as I move the mouse around a screen cell is selected and if I move to the top left it even says cell 0 0 this is something we've done many many times this is traditionally how you would work with a 2d tile map but you'll notice that the cell only rarely lines up with a tile in our isometric grid and so it isn't actually possible for me to get the rectangle to go on alternate adjacent tiles also the cell value doesn't make much sense either here in world space this should be 0 0 but we can see that that's l5 1 and that's because we've offset from world space to screen space using our origin vector and as I move along the x-axis we can see both cell values are changing clearly the cell core it is not in world space I'm going to add another vector called selected and this vector I'm not going to use a lambda function to do it but we do need to have a method of transforming the cell in screen space back into world space and that will of course then represent our selected cell in the isometric tile the process for working out this transformation is not too dissimilar here I've drawn our screen space cell grid overlaid with our world space isometric grid and particularly this cell in the middle here in world space is 1:1 let's observe what happens as we transition around screen space as I move up one in world space I have decremented in both the x and y axes and as I move downwards I've incremented in both the x and y axes in world space as I move left I have decremented in the x axis but incremented in the y axis and as I move right I've incremented in the x axis but decremented in the y axis as before we're going to need a transformation that takes our now screen cell space coordinate and transforms it into a world coordinate and again it's a multi-component equation as both x and y in screen space factor into the individual x and y's in our world given that we already have the cell value we no longer care about the tile width and tile height so let's again consider our X component first we'll call it world X straight away we can see that our world x coordinate directly aligns with our cell y coordinate so taking the origin into account as well we know that world X is going to be reliant on cy- origin Y we also see that our world x coordinate directly aligns with our screen x coordinate which is very convenient so we can simply add that as well and in a similar fashion will calculate the transformation for Y in just the same way as X we see that our world's Y values are directly in line with our screen cell Y values so that's just a repeat but this time our world y-values are going in the opposite direction to our screen X so you can see here they're actually decrementing as our screen X is incrementing therefore very simply we're going to subtract our cell x value accounting for the offset so I'm just going to paste that directly into our transformation and at the bottom of the program I'm also going to output our selected cell value after we've drawn the world I want to draw the highlighted selected cell this cell had multiple levels of alpha in it so I'm going to use the pixel game engines alpha blending mode which is slow but we're only using it for a handful of pixels the current selected cell coordinates is in world space so I'm going to use the two screen lambda that we created earlier to convert that to screen space and then I'm going to draw the required graphic on top of what is already there so up here we'll have drawn the contents of the tile but down here we'll then overlay the content of the tile with a highlight so let's take a look well we can see that the highlighted cell is certainly being drawn wherever the red rectangle was before so that implies there hasn't really been any different but what we can see is that the selected cell value now this coordinate appear in the top left accurately reflects the selected cell in the world so as I move along this top edge that's our x-axis we can see that that's increasing but our y-value doesn't and similarly if I move a longer than Y axis we see that's increasing but our x value doesn't and we see that it actually works for anywhere in the screen so we'd probably want to put some boundary checks in later to make sure we can't choose elements that aren't in our world array but there's a problem straight away I can only select the cells that exist in the rectangular cell space of the screen how do I select these ones in between today I can't select it and while the answer is quite simple we're going to cheat recall that we now know which cell in screen space our mouse exists in and we've managed to work out which I sub metric tile in world space that screen cell corresponds to the problem is we can't access these tiles because they don't fully occupy a salad screen space we also managed to work out where the mouse exists so we know that the mouse must exist somewhere in this cell because that's given us this coordinates here and we also worked out the offset of the mouse into the cell therefore if we could detect that the mouse is in this region or this region or this region or this region we could bias our world coordinates accordingly to either increment or decrement along the x axis or y axis there are several approaches to this and you may expect because of the nature of this channel that will now do some geometry and look at line intersection equations and which side of the line a point lies but no I'm going to really brute-force this and I do it by creating a cheating tile so here I've created a solid tile it's the same dimensions as my other isometric tiles but the regions in the top corners are identifiable by a unique color and since this tile represents an on screen cell and I know the offset of the mouse into this cell I can determine quite easily which corner of the cell my mouse lies in just by looking at it the color by sampling that sprite at that location we've already got the offset into the cell of the mouse so I'm just going to call the get pixel function on the sauce byte at that location it's offset a little bit because this was a sprite containing all of the sprites but it's sampling from the right location to give me a pixel value and then once I've worked out what my selected cell is I'm going to modify that location by looking at the returned pixel color and adjusting the X&Y value accordingly let's take a look so now we can see as my mouse goes into the corner of the screen cell it's selecting the appropriate world cell underneath it and therefore I can put my mouse over any were in the isometric tile array and accurately get the cell underneath it let's just finish off this quick demonstration by adding the other graphics firstly I'm going to stop drawing the red rectangle we don't need that anymore and now that we have the selected cell accurately in world space I'm going to be responsible to the mouse button being clicked and all the mouse button being clicked is going to do is increment the integers stored in our world array at that location I'm also wrapping it round to the total number of sprites available and because our selected world value can be anywhere I'm going to guard our array with an if statement to make sure that it's within bounds and the bounds are in world space too so I don't need any fancy transforms for that I'm just comparing with the world size we specified at the start since the value of the graphics can now be something other than zero we're going to choose the appropriate sprites depending on their value there's nothing especially clever going on here but I will draw your attention to these sprites that exist along the bottom of the image these sprites are effectively to isometric tiles hi so I need to offset it by that before I draw it to make sure that they draw it in the correct location because I don't want to just draw the top half of this tree where the tile should be and so if the tile at a particular ID has height I'm taking that into account and so finally we can now click on a particular cell to color it in and the more times we click on a cell we increment the cells value allows us to scroll through the graphics and as we're always drawing from top to bottom we can be assured that things closer to the camera are drawn on top of those further away giving that lovely feel of depth and so that's that a quick and simple method of getting away from always having to use a top-down 2d tile perspective in your games you can add some pseudo 3d retro loveliness without much code or complexity at all as usual the source code from this project can be found on github in the link below I've just updated the pixel game engine so make sure that you have - if you've enjoyed this video a big thumbs up I would think about subscribing come on have a chat on the discord and until next time take care
Info
Channel: javidx9
Views: 74,683
Rating: undefined out of 5
Keywords: one lone coder, onelonecoder, learning, programming, tutorial, c++, beginner, olcconsolegameengine, command prompt, ascii, game, game engine, pixelgameengine, olc::pixelgameengine, isometric, tilemap, graphics, cell selection
Id: ukkbNKTgf5U
Channel Id: undefined
Length: 22min 13sec (1333 seconds)
Published: Sat Oct 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.