Programming a Retro Pop-Up Menu System

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello in this video I wanted to create an easy-to-use menuing system and not just easy to use for the player but also easy to use for me the programmer and even though that might not sound like the most interesting of ideas it turns out that the implementation of such a thing will use several C++ tricks that we've not seen on the channel before and I think it turned out to be quite interesting and the solution is quite elegant but before we start let's take a look at what I'm building so the menu looks like this little pop-up menu and it was probably familiar to people that enjoy retro games of a certain role-playing game franchise and the idea is the following you have a list of commands that you can choose and you can see here that the list can be scrolled up and down so the panel of the menu is a fixed size and I can select one of the entries and a separate sub menu pops up and I can select one of those entries and we see a table of items that can be chosen too so the items within the panels do not need to be listed in just a vertical order a table can be created and we can see we can also lock out certain items from being chosen the scroll bars here on the right hand side of the panel are in some way intelligent so they inform the user whether there are options below or above the current mouse cursor at any point I can press the back button and to choose a different menu so let's choose a white magic this time and we get a different set of options I can't move the cursor to options that don't exist and it turns out you can also block off even submenu so it's basically a hierarchical tree structure where we can set the properties at each branch and leaf of the tree when I do select an option such as the attack there is no submenu so the system elicits a small event I can go and do something with that event later on in my program in this case it's got an ID 101 and I can see the name of it too I'm going to pop the menu back up again and this time I'm going to choose black and we'll choose bio 2 here so you can see the events appear in a nice consistent way in many ways this can be regarded as a graphical user interface and unlike many graphical user interface technologies I wanted to build it in such a way that it was easy for me to use and to demonstrate this rather oddly I'm going to look at some of the final code before we start programming fundamentally the system contains two objects the menu object and the menu manager the menu object is the tree we can access the levels of the tree by their name so main is the overall menu this is the attack submenu this is the magic submenu and if we wanted further sub menus we simply just make them exist - here I've created the white magic menu and rather than typing this out each time I can create a quick reference to that location and then add in the sub items the panel sizes are in some way intelligent about what size they need to be but they contain a two-dimensional table so the top-level menu if you remember was just a linear and vertical list so it's got one column of entries but for visible rows of entries and this allows me to control the size of the menu depending on where I want to place it on the screen in contrast the magic menus are a bit larger so here I'm setting the tables have three columns across and six visible rows down now even though I've specified a two-dimensional table I still add the items to the menu in a sequential order I can update some of the properties of the items simply by calling those properties whilst I'm creating the item in fact I can do this at any point after the item has been created so I can change the states of the item in real time depending on the conditions of the game so if you recall in the black magic menu there were two options which were disabled you see this set ID everywhere that's actually an optional extra I could very well use the string identifier to determine what event has occurred but sometimes it's easier to work with IDs in your program so wanted both options available so this is really an optional extra in fact anything like this is an optional extra once I've constructed the full menu I then build it and that goes and recursively constructs all of the panels to be the right size and sets of the layout and makes a few intelligent decisions about what to display and I tell the menu manager you're going to work with this menu object so the menu object is the tree the menu manager is the user into face to the tree and here I've linked user interface to that menu manager so as the user presses the arrow keys we're going to navigate around the menu system when they press the space key that's going to be a confirm action and several things might happen when you press the space key if there's a submenu available it'll pop up the submenu if there isn't it will assume that we're at the leaf of the tree and therefore what we've selected is an action and it will return the menu object associated with that location we can also press the back button and so if the returned the command object is not null pointer some sort of user action has occurred and we can interrogate that menu object and perform the relevant action as necessary the menu is also responsible for drawing itself in fact all I need to do is call this draw function of the menu manager it feels a little unusual looking at the implementation code at the start of the video but I wanted to put into context what we're really trying to do here I want to use a navigable intuitive menu it's also very easy for the programmer to use and you should compare this to regular graphical user interface frameworks and in fact it just so happens I have one I've not made a video about this before nor have I release the source code for it but it is actually a working and flexible windowing system for the pixel game engine so we've got Windows that can be selected and moved around we can choose the individual controls we've got controls that highlight on mouse cursor over and things such as text entry and different dialogues can be selected too for example in this case open a file this is a far more sophisticated graphical user interface framework but you pay the price for that sophistication because the implementation of it is also quite sophisticated it's highly object oriented where we create one layer and add other similar layers to it at certain locations and then we have an event system which passes around all the different events that the user can throw at it and then we need to build the dialogues and the frameworks in order to be shown so all of this is actually concealed away in a pixel game an extension as I've mentioned not yet released and we can see the construction of the dialogue so this was the dialogue with the check buttons and the test buttons on is a little bit more complicated we need to choose a particular component type set its properties set its location and then add it to a parent window and all of these controls just as in WX widgets and QT these are also eligible to be parent window so we can customize things at quite a fine level and this sort of approach is great for Windows based interfaces so if you've got a mouse and lots of options which are going to be fiddly and lots of menus and you need buttons and scroll bars and lists and all that sort of thing perfect but if you just want a quick pop-up contextual menu in your game then this object-oriented approach is considerably overkill now I notice some the concept of a full integrated GUI framework would be a really exciting video and maybe I'll make a video about that in the future but in this instance I wanted something that was simple and quick and easy to use and this this entire menu system could be put on top of whatever you're rendering in the background for example a battle scene and quite importantly I feel it doesn't require the use of the mouse so we could also tie in some controller input quite easily so let's get started but just before we do I wanted to bring your attention to a small community endeavor it's the community one lone coda calm blog and the idea is that members of the discord community analysts YouTube community can write technical articles about programming technology game reviews whatever the interest does as a community for others to see and it's early days yet so there's not a great many articles here but there are some already particularly talking about how to use the pixel game engine in certain ways this one for example is looking at how we can explore binary mathematics and the rule so far is that anything that's interesting to a member of the community is eligible to be included on the platform so if you're feeling creative and want to write for a platform that people do actually visit and will see the articles then shout out on the discord server for now and we'll get you set up now back to the menuing system we know that the menu system is going to consist of panels and each panel is going to contain a table and the table consists of a certain number of columns and a certain number of visible rows so in this case our table is two by four panel itself is a menu object and the table cells also contain menu object and as I add more menu objects to the table the table gets populated in the following order if we've got more objects than we have visible cells then you can conceptually assume these are being added below now let's suppose I take any one of these menu objects let's take D here I can specify the D is also and I don't need to specify that explicitly I just add children to it in the same way I've added a through n here and I do apologize if there's some issues with the sound at the moment there is quite a storm raging whilst I'm recording this video let's assume that for this menu object my table is going to be 1 comma 5 so it's only going to have one column of entries but it's going to have 5 visible rows and again as we add items to them the same story applies because D in this menu object is a menu object that has children we can identify it as being the title of some submenu that's going to appear and we'll do that by putting a small red icon pointing to the right so when the user moves the cursor to the D location and presses the action button will pop up the submenu belonging to item D and we can continue constructing our tree using this approach with as many levels as we like because menu objects can have a whole set of properties associated with them and in this video I'm really looking at a very simple one such as the ability to be enabled and disabled we can change how we draw the menu object in the table cell now don't forget the primary goal is to come up with a system which is easy for me to use and you'll notice we've not specified the dimensions of anything other than the sizes of the tables we want in our menus each menu object is able to return the dimensions of the space it requires to be viewed now in my simple menu system all we're looking at is one line of text but looking at this menu for example how do we determine how wide it needs to be well suppose we started to add items to it so we'll add cat dog orange I want to ensure that the largest command in the list is fully visible and so when we construct the menu object it will look at its children in order to work out which is the largest and use that dimension to influence the size of the panel the same thing happens in a two-dimensional table in this case orange is still our largest entry so all of our cell widths will be able to accommodate the word orange cat and dog will just be left justified and this gives a pleasing visual aesthetic of order a table cell will naturally be the same size as the largest object within the menu object but you might not want to draw them all right next to each other you're going to need some space around them too and I've called this paddock which is how much space do we want around our table cells to construct our menu we effectively build a tree of nothing but menu objects so a menu object can have many child menu objects which can have child menu objects which can have child menu objects etc etc each menu object is given a name so in this case our route menu is main and it's got two menu objects which don't have any further children attack and defend but it does have one that has two children magic so we'll make sure that magic is drawn with our little sub menu indicator effectively the names highlighted are the titles of the menu object and we differentiate between the menu object being a command or a sub menu based on whether or not it is a leaf of this structure and so the leaves we have here are attack and defend but also iced fire and our white magic spells to when the user presses confirm over one of these we can effectively fire an event back to the host system when a menu object is selected that's not a leaf then we'll open up the child menu so I think we've established now that all of the data is menu objects but to manage the cursor and the drawing of the menus we have a menu manager because this is a tree there is only one singular path from this item through to this item here and I want to display all of the menus that we've taken to get to that path and so the menu manager will maintain a sort of stack where our route menu system is always at the bottom of the stack and the menu currently being interacted with at the user is at the top of the stack the menu objects themselves are responsible for positioning a cursor and so when the user presses the up down left right and action keys we just forward those events on to whatever is at the top of the stack in this case we've got a single one column list so the left and right keys aren't real going to do anything but the up and down Keys will move our cursor accordingly when the user presses the back key all we need to do is pop whatever is off the top of the stack and naturally our focus returns to the menu object which is now at the top of the stack which should have been the preceding menu object so because we only have this singular path through to a final action a stack makes a really nice data structure to maintain our rendering order and deduce where we need to send the event to in this two-dimensional table menu of course the left and right keys now matter so the cursor can be moved anywhere within this table but if they are here and they press the action button we know that because this particular cell happens to have some children we return to the menu manager a new menu object to put on the top of its stack and therefore it opens up this menu object I mentioned a few minutes ago that these panels are actually windows into infinitely long tables ie tables with an infinite number of rows by maintaining what is our top visible row we can determine if we need to place indicators to the user that the menu can effectively be scrolled vertically in this case if there are more entered rows in the menu objects than are visible then yes we do need to display some sort of indication if the user has scrolled down a bit we can use this top visible row indicator and the dimensions of the table behind the menu object to determine these indicators in both directions I want the rendering of the panels to be in someways skinnable I don't want them to always look the same so I'm going to use the 9-patch approach to determine what the boundary and contents of a panel should look like now this approach is quite commonly seen on systems where the controls may have variable sizes and in principle it allows you to specify the four corners the horizontal boundaries the vertical boundaries and the fill of a rectangular shape on the screen of any dimension so for example I have a single sprite which contains the information I need to render my panel and it's split up into nine patches it just so happens that my patches happen to be 8 pixels by 8 pixels and I've chose this for a reason it's because the built-in font for the pixel game engine happens to be 8 by 8 pixels and so by working in patch space I can make sure everything is neat and tidy without having to do lots of cumbersome calculations so in this 9 patch layout I have my top left corner which looks something like this and my bottom right corner that's bottom left that one's bottom right and top right so I've got my 4 corners and then also specify my top boundary my bottom boundary the left boundary and the right boundary these can be graphically quite nice and I'll show you mine in a minute but then I'll fill in everything else and you'll notice I'll fill in this Center cell just with a plain background color needn't be plain it could be a texture that matches up with the cells either side of it to maintain a nice curved corner aesthetic regardless of what's in the background I've actually set these areas to transparent and in fact here is the sprite that I'm going to use so it's very small pixel art baked sprite and we can see my 9 patches so these are all 8 by 8 cells which I'll creatively snip out of this sprite in order to construct the final panel window I've also included some overlays for the indicators for more items up and down and a sub menu and I've included a 2x2 patch this approach allows me to change the look of the menuing system entirely even at run time if necessary I could even have animated panels and just change the sprite over time the menu system doesn't care all it cares about is the location of these primary objects in this source sprite I'm almost done with the slides now and I'm going to go through the code quite quickly for this video simply because a lot of it is of calculations of drawing positions it's not very interesting but there is one facet I do want to talk about and I think how do we store the data in principle I'm going to use a standard map and we've seen standard map on the channel before in the code at yourself role-playing game series effectively it allows you to store keys and value pairs so at a very pseudo conceptual level you could consider for example map Apple equals one map orange equals two and yes I really do mean we can use a string as the identifier so it looks like an array but we can effectively use another jet as the index to that array rather than just a numerical index like you would with a normal array or a vector but in my map instead of just pointing to a basic primitive type like that I'm going to store a menu object and all of my menu objects are going to have a map that holds their children so taking a menu object I could look at its associated map pass in the name of some submenu object which would return a new menu object so I could look at its map and look at the submenu object that's returned there and so forth so I've got this sort of recurrent data structure and by overloading the array access operator I can actually get rid of this bit which means I can access a specific menu object in an easy to use and very clear syntax manner and I'm going to start out by doing it like this but will quickly see there is a problem doing it this way but let's start writing some code as usual I'm going to use the OLC pixel game engine and here I've just got a blank pixel game engine project if you want to see how to create one of these in Visual Studio 2019 then click the little link above I've made a video explicitly about how to set up one of these projects in that environment and all this program does so far is it decides what the patch size is going to be which is going to be 8 by 8 pixels and it includes standard map it also then goes and loads the sprite asset that I need to draw the menus and as viewers of this channel know I love the retro aesthetic so I'm using quite large pixels this time to make it look Pixley so they're four by four screen pixels per game pixel we know that we're going to need a menu object class so let's throw that in straight away and in this menu object class I'm going to need a bunch of description variables what is its name going to be can you select the object I'm also going to associate an ID with the object this might just make it easier for filtering out the events later also going to store the number of rows that make up the table and to partner that I'm going to have a variable top visible row we're in the table do we start drawing from since we know there's a table let's store some properties of the visible table so I'm going to use the vector 2d integer type because it comes with the pixel game engine to do this so this is currently one column wide my zero rows hi I don't yet know what the cell size in the table needs to be I will calculate that later we also know that the cells can have some padding around them in this case I only want to pad in the x-axis and it's going to be two patches worth of padding between cells since the entire system relies on rendering in the spatial domain of patches these 8x8 pixel squares um I'm going to store how big is a patch which is just a 2d vector containing 8x8 but I also need to know the size of this if it becomes a panel in patches finally and most importantly we're also going to need our map and the maps going to take in a string as the key value but store menu objects these are going to represent the children I appreciate this might seem like a lot of data to immediately get started with but nearly all of it will be self calculated by the object don't forget the objective of this exercise is to make code that is easy to use I'm now going to throw in some accessor methods now in the past I've made fun of using getters and setters but they do sometimes serve a purpose I want the code to be easy to use so I don't want intellisense or the equivalent on other platforms to pop up with suggestions which aren't the correct ones for me to use so I'm adding in two constructors that set the name of the menu object here I've added a method set table which sets the table dimensions of the objects now interestingly it returns a pointer to itself and we'll see why this is important later I'm also going to have one which allows me to set the ID and again it returns a pointer to itself the same applies to an enable method so I've just added a few getters in there as well now here I'm going to write a function which returns the size in patches of this particular menu object this is not the same as the size of the panel this is the size required to display the name of the menu object so in this case it's simply the length of the string that represents the name and one if we wanted to develop this menuing system further where we had different types of cells for example the cells could contain icons or progress bars or something we could change the size of the cell based required to display this menu object here I also need to know whether this particular menu objects has lots of children so I'm just going to interrogate the state of our map and finally I'm going to overload the array access operator to just make accessing the map a little easier now let me just show you how this works I've got a menu object now I can create just a simple menu for example magic we know that this array access operator has now gone into our map now when you use a map and you use a key that it's not seen before it will create a new menu object and associate it with that key so even though we've not explicitly created any sub menus I can keep going this will internally construct the three menu objects required and link them together I can then choose which properties of this final object I want to set so set the ID I say it's 101 but because the set ID function also returns a reference to that object I can set the other properties too now if you're just starting out with C++ this approach may look quite strange but it does allow me to construct menus quickly with all of the information that I need without needing to store lots of in between linkages in this one line of code we've now created three menu objects that are linked together and we relied on the standard map function to do that generation for us and I'm quite happy for that to be the case because it means now I can add further sub objects to one of the in-between stages and it will semantically make sense in this first line this black sub menu was created so for this second line it already exists it knows which menu to access to add the ice object to going back to our menu object I'm going to add a method for it to draw it to self you'll notice that the menu object is external to the pixel game engine and it doesn't inherit from the pixel game engine either so in order to access the pixel game engines drawing routines I'll need to pass in a reference to the pixel game engine when I want to draw the menu I'm also going to pass in a pointer to the sprite which contains the nine patch and the 2d vector were to draw it on the screen fundamentally the draw self method is only going to be called if this menu object is indeed a panel ie it was an object that has some children but we don't know what the dimensions of that panel are just yet and I don't want to calculate it every single frame I can calculate all of these fixed dimensions once I've finished constructing my menu so I'll add to this build function which populates these variables down here with the relevant sizes for when the object is drawn now I don't want to have to call the build function for every single menu object there's absolutely no need to our menu objects are stored in a tree so we can recursively navigate that tree hierarchy as long as we only call the build menu on a root menu object and so that's exactly what I'm going to do I'm going to iterate through all of the children in my items map if there are no children this iteration won't occur if one of my children happens to have children of its own then I'm going to call the build function on that child and so this will then recursively navigate that whole tree but the object returned from a map when you're iterating through it is a standard pair where the first element of the pair is the name ie the key value and the second element is the actual object so for now I want to tell it to use the menu object component of the item returned from the map now things get a bit strange when we need to start thinking recursively whilst I'm iterating through all of these children I may as well at the same time determine what cell size I need for myself and recall that cell size is related to the string that identifies the menu object by iterating through them all and retaining the maximum dimensions then I get the largest cell size that I need to satisfactorily display all of my children knowing this I can then calculate the size of myself in patches and we'll see a bit of code that looks like this and I'm not going to go into too much detail explaining it because it's got some magic numbers in but these are just aesthetics for drawing in this case I'm taking the dimensions of the table multiplying it by the maximum cell size has been returned but then I'm also taking into account that padding between the cells now one of the metrics I do need to record for this particular menu object is how many rows are in its table I know the number of children I have because it's the size of my map from that I can calculate how many rows I need by dividing it by the number of columns that I have however if there is any remainder from this division then I need an additional row to display those remaining items and I can work out if I've got a remainder by taking the modulo of the number of columns and this ternary operation is testing to see is there any remainder if there is add one more row or don't so using this build function we now have enough information that if in the events this menu object is ever displayed as a panel we know how to draw it so let's go back to the draw cell function and do just that I know that my 9-patch sprite is going to have some transparency so I'm going to cache the current transparency mode of the pixel game engine the user could have set it to something they need I don't want my menu to silently change the state of the pixel game engine and therefore before we exit this function I'm going to restore the current pixel mode the user will never know the difference since we now know the size of the panel in patches we can draw it just with a pair of nested for-loops so here I've got one for the x-axis and one for the y-axis and we're going to go through patch by patch decide which patch out of our nine patch sprite do we need and draw it and I'm using the pixel game engine to draw a partial sprite function to effectively cut out the patch from the source sprite and draw it in the right location let's test it at this stage so in unusu create I've created a very small menu system here it's only one deep it's a small table which is one column by ten rows and I've added five items to it so the panel is actually going to be larger than the number of items and at the end I've called the build function on the menu object so it will have gone through and calculated the size is needed to display the children and for now in our news update I'm calling the draw self function directly on the main panel passing in the pixel game engine and the sprite we for I draw the menu however I'm just going to throw in a very quick hack to show the limitation about using standard map and it's down here a phone is in the draw cell function and all I'm going to do is draw the names of the menu objects as a children to this particular panel so I'm iterating through all of the children working out where on the screen they should be displayed and then drawing the name the key value of that menu object so let's just take a quick look forget for a moment that the panel's the wrong size what we see is that the order of the items is not what I specified here we've got attack defend escape items and magic we're in my code I've got attack magic defense item escape I want my menus to be constructed in the order that I construct them simply because it is customary to put the most popular options at the start of your menu so the user doesn't have to navigate them as much in fact what we see in game is that the items of the menu object are in alphabetical order and that's because map intrinsically sort by key value and so even though map has given us this fantastic level of flexibility it's actually letting us down when it comes to the ordering of the items fortunately there is a way around this where we can maintain the flexibility but also add order to our map and rather oddly I'm going to change my map from a map to an unordered map which sounds counterintuitive but the reasoning for this is as follows a standard map is actually a very sophisticated data structure and has a certain degree of computational complexity when using it so even though it's easy for us to use the computer has to do a lot of work in order to maintain the map since we don't care about the order of things in the map being maintained by standard map I might as well use a standard unordered map which is slightly more computationally efficient however as the name implies this will not enforce any ordering at all so it's still not solved the initial problem I want my objects to appear in the order that I create them so I need to store them in a data structure that maintains an order like that fortunately we have standard vector for that job so I'm going to store my menu objects in a standard vector and call these items but I can't access the vector like I did with my map instead what I'm going to use my map for is to store the indexes into the vector so our map is now going to be a map that links names of menus to integers and those integers are going to be the indexes into my vector this approach gives me the best of both worlds my vector is computationally simple to work with so we don't really get any performance penalty whilst using the menu it also maintains the order of things in which I've created them so aesthetically it's going to look like how I want it to look but I like the convenience of using the map access syntax in order to define the structure of my tree this change requires us to just change a couple of other locations our array index overload is no longer as valid as it should be but I want it to behave in the same way so the first thing I'll do is check in my map of item pointers is there an item that already exists with this name if there isn't then I'm going to create it so this is linking the name to the current index of the vector by taking the maximum size of the vector we know what the next index is going to be and then I'm going to push to that vector the newly constructed menu object item and here you can see I'm using the constructor where I associate the name of the object with the object so now items being a vector we can't use the string to index it but we can use the pointer returned by looking at the map that links the names with the pointers it's all good stuff this isn't it so in effect this function will behave exactly as it did before but it will allow us to maintain the ordering of the items because items is no longer a map there are a couple of other changes we'll need to make to in my little bit of hacked up test code we don't access first and second members of appear anymore the vector will just return the object completely the same applies in our build function we're no longer working directly with a map we're working with a vector so nicely this is simple a lot of the code note that I'm not making any changes to the construction so let's take a look now so now we can see that the ordering of the words is as we intended them to be you may have noticed something else they now all fit within the menu panel why is this we know that a cell gets its size from calling the menu objects get size function which is based on the s name string well in the previous bit of code where we had to gather the map we never constructed the object with a name so the name of all of the objects happen to be root now that we know that the size of the panel is related to the largest object we can test this principle so let's say I change one of these to defend is now the largest and press play the menu has adapted to accommodate the size of that item from a programming perspective this menu is now getting very simple to use indeed I'm now going to remove our hacked bit of code and put in the proper code for displaying the contents of a menu object we've drawn the panel in the background it's now time to draw the individual cells since we're working with a two-dimensional table which don't forget can be one dimensional in nature it's going to be useful for us to know what the top left visible item is and the bottom right visible item is because that will tell us which rows of the table we need to draw bottom right is calculated by simply adding the table dimensions to the top left so this could in principle become larger than the number of child items this menu object has so I'm going to clamp that just to make sure we don't go out of bounds on our child vector this means given the size of the panel that we've specified through the table dimensions I can work out how many visible items there are regardless of whether it's a 1d list or a 2d table and this allows me then to iterate through all of the visible items in a linear fashion many times on this channel we've worked out the 1d location based on a 2d coordinate it's Y times width plus X I must have said that over a hundred times now but this is a city where we want the opposite were given a 1d index and we want to work out a 2d coordinate we can do effectively the inverse or we can take our 1d eye and calculate our cell location x and y this will effectively propagate through the table from the top left along horizontally and then on to the next row all the way to the left all the way to the right and the next row left to right until we run out of visible items if I know the cell location I can work out where I want to start drawing so this is in patch space and this includes the padding once I've worked out where I need to join patch space I can work it out in screen space and since this is a panel and it's drawing the names of its menu objects as its children I'm going to call the drawstring function at the correct location I'm going to get the name of the item we're currently drawing and I'm also going to test is the item we're currently drawing enabled because if it is I'm going to draw it as white and if not I'm going to draw it grayed out so let's take a look well it's certainly drawing in the right color I'm just going to modify our test there to make it a little bit more sensible so I'll put that back down to defend but I'm also going to add a whole bunch of dummy items at the bottom and this is so I can test to see if my table is being calculated correctly so we've now got 1 2 3 4 5 6 7 8 9 10 11 12 13 14 items but our table can only display 10 of them nice it's adapted and it's not displaying anything beyond the bottom of our table however if I change our table dimensions now to 2 and 10 potentially the panel can display 20 items so here it is displaying all of the items with some empty space at the bottom but it's arrange them according to our table layout I change that to 3 by say 5 as a maximum number of rows to display a Gentile in a nice ordered fashion the padding that I keep mentioning is this spacing between the columns we don't have any padding in the y-axis though we could let's try it padding is currently fixed in its hard-coded so if I change the padding here to give me one patch worth of padding between the items vertically we can see a bigger table gets created now some of the complexity around that padding code is to handle the border around the edge I don't want to pad unnecessarily I just want to pad the space in between the items I'll put that back for now there are a few cosmetic enhancements we need to make to drawing the panel we need to place the indicator markers to let the user know whether there are items above or below the panel or the item is indeed the header of another menu once I've worked out which items are visible I can use my top visible row indicator which we've not updated yet but we will to determine which of the scroll markers are visible so if my top visible row is anything greater than zero then obviously I can move the menu upwards and all this is doing is choosing the correct location on the screen choosing the appropriate patch from my patch sprite and using the draw partial sprite to draw it in exactly the same way I can determine if I need to draw the bottom scroll marker and in a very similar way when I'm drawing the children I can also determine if the child has children of its own then it is the header of a submenu object and so again calculate the location in patch space then screen space choose the appropriate patch and draw it to the screen so now if I make sure that our menu cannot display all of the possible items that are contained in the menu we get a little indicator marker to say there are more items below here it's difficult to test this without the cursor so I think it's time to add that in it's important to remember the term menu object class is purely a data structure and I'm going to defer to a second class menu manager the responsibility of maintaining the cursor positions and which menus are drawn to the screen and this is a very simple class as a constructor that doesn't do anything but it does maintain a list of panels now I'm going to be using this list like a stack and I'm not using a stack explicitly because I need to access all of the elements in the stack now a stack will only give you access to the top I need to look at all of the elements sequentially on the stack in order to draw them because these are going to be the panels that are nested the menu manager is associated with a menu object they're an open function all the open function does is push the panel supplied on to the stack I'm also going to include a closed function which clears the stack the menu manager is also going to be responsible for drawing the panel's so in much the same way we pass in the pixel game engine and the sprite if there's no panels to draw it doesn't do anything otherwise it iterates through all of the panels in the stack and draws them starting off with the route and so that way it draws the panels on top of each other in the correct order each time it goes to a new panel I'm going to offset the top-left corner by a little bit to indicate that they're being stacked one could spend some time making this far more intelligent to ensure that the panel resides on the screen so if we have many menus with lots of depth eventually we're going to come off the screen there's nothing stopping is adding some intelligence here to make sure the panel remains visible at all times the menu manager will also handle user input so for example if the on OOP function is called it takes whichever panel is on the top of the stack or in this case the back of the list and cause an on up function we've not implemented those just yet but it demonstrates that by using a stack to maintain the panels we always know which one is going to receive user input and we can handle all of the other cases exactly the same way on back all that's going to do is pop the top of the stack which means whatever menu was under the current menu is now the top of the stack this effectively allows the user to go backwards back up the hierarchy of menus as well as having a back button we're also going to have a confirm button but I'll leave the implementation of that too a bit later and so this structure implies that our menu objects need to maintain their own cursor locations and this has the nice side effect that the cursor location is persistent between menus so if you're always going to the same location in a particular submenu then the cursor will appear there you could of course always reset the cursor as well if that was the effect you wanted since the menu object will now handle the cursor let's add in some variables to help it the first is a 2d variable which is which cell in the table is our cursor currently pointing at but don't forget the cells in the table are effectively a 1d array even though the displayed is 2d so I'm also going to have the index equivalent of that location I'm also going to create a vector which is the cursor in screen space so the menu manager knows would to draw the sprite of the little hand pointing at the item so we know we need to add on up left down and right functions to our menu objects I'm only really going to talk through on up because they're all quite similar whenever the user presses the up key we want to move our cursor up one cell in our table so I'm going to decrement the Y value of that variable we can't go beyond the boundaries of the table so I'm going to check for that too now recall that we have a top visible row that's if our table is larger than the panel allows us to view if our cursor goes beyond the top visible row then I want to decrement our top visible row so this will have the effect of shifting the entire table upwards or downwards that depends on your perspective and likewise we don't want our top visible row to go below zero so handling the cursor in 2d space is very simple but I want to make sure that we don't end up pointing to a location that doesn't contain an item as you've seen before if we don't have enough items to fully fill the table there are empty spaces and those empty spaces are void locations they could point to elements that don't exist in our children vector and since this is a routine we're going to be using after every movement I'm going to create a function called clamp cursor clamp cursor directly converts the 2d cell coordinate of our cursor location into our 1d value so we can access it in the vector however we want to make sure that that item is within the boundaries of the vector if it isn't then I want to clamp it to the maximum size now if I clamp this value in the one dimensional domain then I also need to clamp the cursors location in two dimensions this will have the effect of forcing the cursor to go to the leftmost item on a row of items that isn't fully occupied ie we're assuring that we're not ever pointing at an item in our 2-d table that doesn't exist in our 1d vector I feel this needs a bit of graphical clarification so we've got our 2d table but we know that our vector is one-dimensional and it will populate the items as I'm doing on the screen right now the problem occurs in this situation where there are more table cells than there are vector elements I never want to be able to point to these locations because even though they exist in our table they do not exist in our vector which would allow us to go out of bounds effectively on the vector on down very similar to on up except the opposite direction left and right they're actually considerably simpler because they don't concern themselves with the top visible row of the table now that the menu object maintains a cursor in 2d space around its table I also want to work out where the cursor is in screen space and I'm going to do that by piggybacking they draw cell function because we only ever need to calculate that location if the menu has been drawn so at the end of this function I'm going to calculate the screen space location of the cursor and it looks like a whole bunch of maths but it's very simple effectively we take the 2-dimensional coordinate of our cell and convert it into patch space and then we offset it into screen space we do that for both x and y now the only thing that makes this look a little bit more on will be than it should be for example having this minus patch and plus patch at the end is that the cursor sprite is 16 by 16 but the finger points in the top half of the sprite so I want the top half of the sprite to align with the item of text that it's pointing at I'll add to our menu object a quick get adjust to return that cursor position so now in our menu manager class we can interrogate whichever panel is at the top of the stack because that's where our cursor will start get the cursor location and draw the sprite and in a similar way before I'm just caching the current pixel mode so we don't interrupt with whatever the user is currently trying to draw and I cut out the hand sprite using the draw partial sprite function and give it the appropriate parameters to draw it on the screen now that we have a menu manager class it's time to give it control it's only necessary to use it in on user update so if the M key is pressed I'm going to open a particular menu object by giving it to the menu manager I'm then going to link the direction keys to the corresponding menu manager functions then I'm going to call the draw function of the menu manager to draw the system to take a look so another thing I'm going to press the M key and our menu is popped up we see a cursor it can be navigated around the menu and if I go beyond the bottom of the visible menu it starts to scroll down now just do that again so I'm only going to press down in this third column but see how the cursor moved to the middle that was our clamp cursor function coming into effect because there was no item in that bottom right hand corner we can also now verify that the scroll indicators are working quite nicely too so we've only got some finishing touches to add to this we now need to display if a particular item in this menu is in fact the header of a submenu and if it is how do we open that submenu in the menu manager we have this function on confirm and it's going to return a raw pointer to an object but we'll worry about that specifically in a minute if the user is press confirm and there are no menus being displayed it's just going to return null so a little quick safety check there otherwise whatever panel is at the top of our stack we're going to pass through this on confirm message in the system as it currently stands pressing confirm on an object will do one of two things it will either select that as an action to be returns to the user or instruct the menu manager to open up a submenu panel and we'll make that distinction based on whether or not the menu object currently being acted upon has children if it does have children then return the address of the child that's been selected else we're just going to return this and just whilst I'm here I'm going to add a little utility getter which also returns the address of the item that's currently under the cursor now the menu managers on confirm function will call the current top of the stack panels on confirm function and we'll get one of two responses if the point of returned is equal to the panel at the top of the stack then there are no children to go through we don't need to open up an the submenu this is the same as selecting the item in the first place this would be the leaf in our hierarchy of menu objects in which case I want to return the pointer to the menu object that represents that leaf and this will instruct the programmer later on that a command has occurred alternatively the on confirm function returned a different menu object than that that's currently at the top of the stack and this implies that a submenu needs to be opened so we'll simply just push that menu object onto our stack making it the new top so in on user update I can determine what happens when the on confirm button is pressed so let's just add that in if after the space bar has been pressed and command is null pointer then there's nothing interesting I can do all that's happened is a menu has opened but if the command actually points to something that isn't a null pointer then an action has been selected in which case I'm going to display that action on the screen and I'll just add in a quick temporary variable to store that also at this point choose to close the menu because I have selected fire or ice I now want that action to occur I don't need the menu anymore so we need to throw in a few things to test this I'm going to select these items here and I'm going to disable those to make sure that we can't select them and this item here magic we're going to add some sub menus to these ID values are in many ways superfluous so it doesn't matter that they're the same and we'll call it fire one five two five three by four five five five six five seven but I do want to specify that magic is a table and it's going to be too wide by four deep so let's take a look so I'll press the M key to open the menu and we can see that the magic menu happens to have the submenu indicator next to it we can also see that dummy four five and six are grayed out in fact if I press the space bar on those I can't select them but if I do press the space bar on something else I get the name of it and it's ID I can do that whatever I want let's pop the menu open again this time I'll choose the magic menu and we can see it appears as a submenu and I can't move my cursor into the locations are doing valid but I can't select one of the objects so let's select fire for and we can see that that's the item thrown out to the programmer so they can go and do the appropriate spell logic or whatever is required and so even though this has been a really quite complicated video on data structures it's yielded a result which is very easy for me as a programmer to use is aesthetically pleasing and easy for the operator to use and understand intuitively now I'll admit that complex menu structures do require a bit of time to set up but rather than just repeating the same strings over and over again because we overloaded the array operator we always get the object at that location so we can just treat that as the starting point for further sub menus and this allows you to create quite complicated and detailed menus with ease now on the surface you'd be forgiven for thinking this would be quite a boring topic to make a video about but as you've seen it's actually been quite a complicated one and to implement satisfactorily we've needed to look at standard maps on ordered maps vectors recursion references and operator overloading anyway if you've enjoyed this video please give me a big thumbs up have a think about subscribing check out the community dot one lone coda comm blog that's a new thing and I'm hoping it's going to gain some traction come and have a chat on the discord server and I'll see you next time take care
Info
Channel: javidx9
Views: 49,577
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, final fantasy, menu system, complexity, graphical user interface, controller input, std::map, std::vector, operator overloading, recursion, themes
Id: jde1Jq5dF0E
Channel Id: undefined
Length: 53min 43sec (3223 seconds)
Published: Sun Feb 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.