Build Procedural UI with Callbacks and Manipulators

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
one of the most requested videos on this channel has been to build an inventory system so here we go today we're going to procedurally build the view for this inventory system that allows item drag and drop it will display a stack indicator and it's going to allow us to drag the entire panel around the screen and we're going to do it with UI toolkit but using Code not Clicks in the UI Builder we're going to look at two different ways of doing drag and drop using UI toolkit the second way is going to be to to implement a UI toolkit manipulator lots to do today so let's get started with a highle overview let's start by taking a quick look at a diagram here so I've got a setup very similar to the video from two weeks ago about MVP MVC so I've got a view a controller and a model here in the unity editor we're going to expose the entire system through a mono Behavior called inventory that'll access straight into the controller the controller is going to have two-way access to the model and the view so the model and the view are going to publish events and the controller will make all the decisions about what's going to happen in the system I want to design my system in such a way that there are clear seams in between each of these parts that really should be its own little unit and we're going to focus on one of those units today which is the view I want to build this system in such a way that in the future if I decide I don't like UI toolkit anymore I want to use yugui again or I want to use a better system like Nova I should be able to replace this entire view module with whatever system I want So today we're going to focus on just this one little area of the view and procedurally build our UI and everything it needs for the drag and drop functionality today we're going to build everything with code but if you want a primer on using the UI Builder that comes with unity I'll have a link to this excellent video from HJ at uim motion video in the description now we're still going to need an empty ux XML document to act as the root of our visuals so you can get a new one by going to create UI tool kit UI document I'm just going to call mine inventory and then I'm going to open it up in ryer if we take a look here you can see that there's quite a bit of metadata associated with this uxl and most of it is unnecessary this default document also has a namespace of engine if we jump over to one of these samples this one comes from quiz U they're using a namespace of UI and have completely different metadata now we could actually go with the bare minimum which would look something like this now here I don't have a name space at all so I think what I'm going to do is actually jump back to my inventory uxl document and I'm just going to strip away pretty much everything that is unnecessary we're not building an editor tool so these lines here can go we can bring this up a line and we can get rid of the XML version declaration now unless you're going to use UI Builder you will never make changes to this document again we're going to do everything procedurally and just use this as the root now if I come back into Unity here I can go onto my empty inventory game object and I'm going to add a new component and that component is going to be a UI document now you can create your own panel settings asset by going to create UI toolkit panel settings asset but I've already got one so I'm just going to choose mine with the selector and we also need a reference to the uxl document that we were just working on for every uxl document you probably want to also have a stylesheet so you can get one of those by going back into the create menu go down to to UI toolkit again and select stylesheet now I'm going to call this the same name as the XML document and I'm going to open it up in writer so here we can see it just has one element defined with no style at all now before we start writing styles and since we're not using UI Builder let's just make a little diagram of what we expect to accomplish so let's draw out all of our visual elements one by one starting with the ones closest to the root so at the very back of this pile I'm going to have a container that'll just stretch the entire width of the screen and then as a child of that we're going to have our actual inventory now the inventory is going to be between the container and all of the slots so we'll be able to put a background image on it and whatnot but absolutely positioned over top of this inventory I want to have a frame and another little background image with the words inventory on it so we'll have a frame and I'll make another one I'll just label it here as header the header can be its own VIs ual element but we can have a child of it that is a label and then we can easily change the text of it so I'll make one more visual element here that will represent our slots container so it'll just be basically a wrapper container around all of the slots and help them get positioned properly Above the Rest of the inventory visual elements each slot is going to have to contain a bit of logic so it is going to extend a visual element and be its own class um and that's just because all of these other ones are kind of dummy they're just placeholder stuff for for positioning and style the slots themselves need to contain a bit of logic so that they can react to pointer events I'm just going to stack these up here in the view in the order at which I expect them to appear in my tree and this will make sense as we start to procedurally assemble this whole tree like structure of visual elements so the slots will sit at the very top here but there's still one more thing that we want and that's a little icon that we can drag around so once we've selected something we're going to make a little ghost icon that will be most of the time invisible but once we've made a selection for dragging one of the slots around we're going to fill it up with an image and then we can drag and drop it as necessary the storage view is going to be the basis of this system it's going to have an array that's going to represent all of the slots within this particular storage unit now today we're just working on inventory but in the future we're going to have more now it's going to need a reference to the UI document component as well as that St Styles sheet that we created next I'm going to have some references both for the root and for its child the container just so it's easy to make queries later it's nice to have a reference to these top level elements next I'm going to create an abstract method here that will be responsible for creating our tree of visual elements and I'm going to pass in an argument with a default size of 20 now I know already the inventory I want now is 20 but if I use it for my hot bar later I might want to pass in 10 or six for now I'll kick this off with a co- routine in start but in the future I'll run it from the controller once the entire inventory system has been wired up together that's really enough for our storage view for now let's extend it with the inventory view class let's get rid of this red squiggly here by implementing The initialized View method now the initialized view is going to do everything required to set up the visual tree for us and initialize our slots because we're going to have a label at the top of this particular panel I'm just going to expose it as a string to the unity editor here with the serialized field as a first order of business here let's just Define our array as a new array of the size that we've passed in now from our UI document we can get the root visual element and assign that into the root variable and I'm just going to run root. CLE here that'll clear it out every time we decide to initialize the view in case we're running it multiple times for some reason we're also going to have a reference to the stylesheet so let's apply that to the root now we can begin constructing the rest of the tree so we'll start with the container the one that's going to stretch all the way across the screen and I'm going to use the extension method create child now this extension method and several others that I'm going to use have been discussed thoroughly on our Discord channel that you're welcome to join I'm sure we're going to have a lots more interesting conversations about UI and inventory systems there why don't we go and take a quick look at those extensions right now the main purpose of all of these custom extension methods is to make it so that we can chain methods together and have less code going on when we're constructing things procedurally so we're able to create children and attach them to parents uh I have two methods for that one is specifically for visual elements and one is for any type te we're also able to add a particular child to a parent we're able to add a CSS class style to a visual element and if we scroll down a little bit here we can also add a manipulator to a particular element we'll get to manipulators towards the end of this video all this code will be in the repository for you to have a look at afterwards as well let's keep going and make the first child of our container which is going to be that inventory back plate so we can create a child on the container and we're going to give it the style inventory the inventory is going to have two children which will be absolutely positioned that's the frame and then on top of that is going to be the header the header is going to have its own child which is a label and the label will have that panel name of inventory next in the order will be at the slots container I'm going to make it a child of the inventory and give it a style called slots container now we can iterate over the size and we'll create a new slot that will be a child of the slot's container slot is a class that extends visual element now it acts just like a visual element but it's going to have some additional Logic for us we'll give each of these a class of slot and then I also want to keep track of it in my slots array now we're almost finished building our tree if we take a quick step back and just look at this whole thing you can see that we're assembling it bit by bit with one thing becoming a sibling or a child of the next thing and we're giving most of these things their own style name we're going to get to styling in just a moment now let's have a look at this slot class that extends visual element I need each slot to know a little bit of information but it doesn't really need to know much about an actual item it needs to show pretty pictures to the user so they can click on them and move them around so it needs to know an image to show and I'm also going to show a stack label so if we're stacking items and we have a count that's greater than one I want to be able to show the user how many are actually there in the slot next I'm going to make use of the index of method that exists on visual elements and since every visual element also has a reference to its parent and this parent being the slots container only contains slots the index of method is actually going to give us the correct number of this slot within the slot container so the first one's going to be zero and it'll go all the way up to 19 now for convenience we're also going to pass in a little bit of data about what should be shown here on the screen the first is a serializable goodd that we're going to get to in the next video just representing the type of item that this is the other is going to be the actual Sprite that comes from the item details as well and that's because image and Sprite are not really the same thing I need to apply a texture from the Sprite onto the image so it's useful for me to store a reference to that here so in the instructor I can create the other visual elements that we need I'm going to create three of them here but I only need references to two of them because one in the middle is the slot frame that's going to sit over top of the image but underneath the stack count now finally we need a way to actually set this from the controller so we can put our dummy items in here for testing and we of course during runtime we're going to be wanting to set and unset this as well so we'll just have a method here called set and that'll accept our serializable good the Sprite and the quantity should it be different than zero so we'll set the item ID in the base Sprite that's the only data that we really care about then we're going to set the icon image if the base Sprite is not null let's get the texture from that otherwise it's null no image to show and then we can say the stack label text if the quantity is greater than one let's put the quantity in there otherwise empty string and then we'll decide if it's visible or not also based on whether the quantity is greater than one now we should also have a method just to clear this out and all that needs to do is set the ID to empty and set the image to null that's it now we should probably do something about our ghost icon too because we're going to want to apply a little bit of styling to that as well so if I come over here to the storage view again let's just have a static visual element that will represent the ghost icon now we can initialize this when we're initializing the rest of our tree so back here in inventory View I'll just add one more line so that we can have this ghost element be initialized as a child of the container and I'm going to give it a style name called ghost icon I'm also going to use the bring to front method this method makes sure that a particular visual element will be in front of all of its siblings Okay so we've got our whole tree constructed why don't we have a look at it and see what it looks like without any style at all so back here in unity I need to add that inventory view to to the inventory game object once I've got that in there it needs a few references the first one being this UI document I'm just going to drag that reference in there then it needs a reference to that empty stylesheet that we're going to start filling up now additionally I also need the inventory component itself that does all the wiring up of the controller and the model and the view together and that's going to provide our starting items which I've already created quite a few here so I'm just going to drag a bunch of them in here and I want a couple extra ones just so we can test the stacking as well well maybe I'll grab one more mushroom too and that should be enough to get us started but what happens when we press play now let's find out so all of our items showed up in a nice column that's great but how do we know without any styling that this structure is actually correct well Unity actually has a tool that is quite good and that's called the debugger within the debugger there's a button there called pick element and I can choose something from the screen to have a look at so now I've selected my entire tree here and I can have a look at the exact structure of it where the classes are applied and I can break it all the way down here right down to the stack label so it looks good I can see my ghost icon way down there at the bottom since this looks exactly what I had envisioned let's start applying a little bit of style bit by bit just by way of review let's come back to our uxl document you can see there's nothing in here still it's just the root there's nothing else and never will be now over in our empty stylesheet let's start working our way from the very bottom which would be the container and we go all the way to the topmost element so the container is going to Cascade its Styles down to everything else so I like to put things that I'm going to generally want set everywhere in here that's Flex grow one we want the width and height to be Auto no maximums I'm just going to have the column will be the default we're going to justify everything vertically and horizontally into the middle I've chosen a font that came with this asset pack and referenced it here as our default font for everything I'm going to drop a little Shadow and then I've also chosen the color yellow you can see in the column there writer is showing me what the colors are as well now I'll go on to the inventory the inventory is going to show our background which is a nice wooden panel I want it to be a fixed size for me I know that I want this inventory to be 300 pixels wide and no more than 500 pixels High I've added a little padding there just so I can keep the contents away from the edges let's go have a look in so we've got our wooden panel showing up we can now see the words inventory there even though it's in the wrong spot but you know what it looks not bad let's keep going here let's do something about that text so for the inventory header class I'm going to position it absolute I already know exactly where it is now remember absolute positioning is relative to the parent not the whole document so it's relative to the inventory not uh not the container or anything above it so I know the positions I want it centered I've got a nice background image I'm going to put under this and now take note of the last thing here the unity slice scale when you're using nine sliced images they may sometimes look wonky if they're too big to fit what you're trying to do you can scale them down or up using this attribute now I'm going to add one more thing here for the label I want the text to be bigger let's check it out now notice I'm still in play mode I never came out of play mode I just come back into the editor and hit controlr I can see my ch in real time that's one of the beautiful things about UI toolkit so that's looking pretty good let's keep going with the styling if I come back in here I'm going to add the inventory frame no surprises here it's almost the same as the header just a different image and different dimensions let's do a quick check crlr that looks pretty good let's move on to the slot container and the actual slots the slot container needs to be down from the top a little bit so I've added some padding there it needs to wrap and it needs to go in rows starting from the top left corner so that's why I've got Flex start on the Align items and justify items now each slot I know I want to be a specific size and that is 60x 60 with a margin of four around them each slot has a frame on top of it that needs to be positioned absolutely it's the exact same size positioned from the top left and I already have an image to go into that and next down here we have the actual slot icon now I'm not going to specify a size I just want it to fit into the slot now the frame that came with this gooey asset has little rounded Corners so I want to add a little bit of padding just so that my images of my icons don't stick out past the frame but when we pull them out of there they won't have the padding anymore so it's going to look kind of cool if they're just a little bit bigger let's have a look at how we're doing so far well that looks like a real inventory panel now it's not too bad of course we can't do anything with it there's no drag and drop yet um but we'll get to that in just a moment before we start doing the dragon drop let's make sure that we've got proper styles for everything else so the ghost icon needs to be the same size as the slot I'm just going to paste that in here it's an absolute thing that we're going to drag around and change its position of we'll start it as hidden let's also put in the Styles just for the stack the stack count is also going to be absolute but it starts in the bottom right corner and it's going to be over top of that frame and just needs to be a little bit smaller size so obviously it took me a little fiddling around figure out all the numbers and positioning and sizes and everything but with the runtime changes it's really not so bad we won't be able to test either of those last two Styles until we've implemented Dragon drop so let's move on to that so back over in the storage view class I'm going to add two more Fields here one is a Boolean to say whether we're dragging or not the other one will store a reference to the slot that is being dragged around now all visual elements allow you to register call backs so I'm going to register a call back back here on pointer down events so when the player clicks this slot I want to publish an event that event will publish the location of the pointer and it's going to let us know which slot actually got clicked to fire this event let's write the on pointer down method here we can say if it wasn't the leftmost button or the item ID is actually an empty guid let's return early otherwise fire the event sending in the position and a reference to this slot finally let's stop propagation on the event because we don't want other elements in the tree reacting to this particular event we've already handled it if we go back to the storage view we can listen to those events here so in our start method why don't we register a listener for each of the slots I can just have a for Loop here and I'll register each now we'll have a method that will handle it when one of the events from a slot gets fired we'll call that on pointer down so on pointer down will handle both of those arguments the position and the slot so in here we can do all of our logic to kind of transfer the information from the slot into the ghost icon so we'll set is dragging to true and let's set our original slot to be that slot just in case we need to kick back to it later I'm going to create a little helper method here that will help us move the ghost icon around remember that it's absolutely positioned so I have to move it by its style so let's use that method here now that we've set the position of our ghost icon correctly we can update its background image to be the text of the Sprite we can set the original slots image to null and we can hide the stack indicator finally let's apply a little bit of styles to the ghost icon we definitely want it to be visible and we could make it a little bit semi-opaque but I think I'm going to play with that for a little bit first and see if I actually like it and it could actually go into the stylesheet you know to be honest there's still two more events to handle that's moving around and what are we going to do when the player actually drops the item we don't want our view making any logical decisions about whether or not an it item belongs inside the model or not so what we're going to do is publish an event with the original slot and the destination the target slot and we can have our controller listen to that and it will make the decisions so we got that event there now we can't register these callbacks on mono behaviors but we do have a static visual element here which is the ghost icon we can register callbacks right onto that since we have a static reference to the ghost icon here in this class we can also handle any events that catches right here in this class too so let's scroll down a little bit and we'll have handlers for onp pointer move and onp pointer up let's do the move one first because it's pretty straightforward if we're not dragging bail out of here otherwise let's just update the position using our helper method that one's straightforward enough one pointer up is a little bit more complex I'll just give myself a little bit of room here again if we're not dragging there's no reason to be inside this method let's bail out now we're going to use some link to figure figure out which slot was actually closest to our drop point visual elements have a property called World bound and we can use the method on those which is called overlaps this will get us an inumerable of all the slots that are overlapping the ghost icon we'll put those in order by closest to farthest away and then we can get the first or default that'll get us the closest slot to the drop point now as long as that wasn't null we can actually invoke our event CU we know what the original SL was and we know what our Target slot was that's going to be the closest slot if we're not going to invoke that event let's just reset our original slots icon so it's as if nothing happened it's like maybe we dropped it off into nowhere now we can turn off dragging we can also set the original slot back to null we don't need it anymore and we can hide our ghost icon I think it's time for a little test if we come back in here reload the domain and press play voila let's see okay yeah mushroom comes out squid comes out mushroom stacks and we see two we can stack it up to three I can swap that one with the bones let's see I can swap these swap those the squids yeah nothing's really going to happen there in my dummy item details the mushrooms are the only ones that are allowed stacking so that's why it's behaving that way them the squids will just swap with each other okay last thing to do is to be able to move this whole panel around let's take another look at these extension methods now the with manipulator extension method is just running the ad manipulator regular method except it's going to return us the visual elements so that we can actually chain this together now in order to use it we actually have to create a manipulator this is actually a lot easier than you might think so I've created a class here that inherits from pointer manipulator what this is going to let us do is use all of those callbacks that we are registering for on visual elements before but we can wrap them up in a nice little class like this such that it becomes a little mini State machine I'll just import the missing members here which are to register and unregister for callbacks I'm just going to paste these right in here we want all the three same callbacks that we were using for the dragon drop except this time we're going to have them in a nice little package here the target is going to be whatever we apply this manipulator to and that's going to be the inventory panel manipulators also have something called an activator and I'm going to create one right now an activator is a condition upon which this manipulator decides whether it can work or not here I'm just going to add one activator and that is a filter that determines whether or not the player has clicked the left Mouse button now there's all kinds of activators you can use this is simple enough for this example we're going to use it in our first call back here so let's start with the pointer down event I'm going to add a couple Fields as well just to track things while we're moving the panel around that's a Boolean for is dragging and a vector 2 of the offset now in the onp pointer down method what we can first do is check the conditions of our activators are we allowed to actually perform this event if for some reason the is dragging flag is also true that would be a condition for bailing out early now let's set those two Fields the offset is going to be where we started our drag and then let's turn the flag on now let's actually capture that pointer what this does is when the target captures the pointer all the pointer events are sent to that element regardless of which element is under the pointer we can also stop propagation on this event because we don't need other elements in the tree actually trying to handle this event the target of the manipulator is going to do the handling now this manipulator would work on any visual element but in our case the target is going to be the inventory panel let's move on to the next method here for on pointer move this one's actually pretty straightforward too let's start with some basic guard Clauses if we're not dragging get out of here if we haven't captured the pointer get out of here we'll bail out early otherwise let's actually move this target around let's figure out what the Delta is between the local position of the event and the offset and we're just going to apply that to the target's transform position then we can also stop event propagation here as well the final Handler is just about as simple let's again let's have some guard Clauses here at the start so can't stop manipulation is kind of the inverse of can start manip population so if either of those were not true let's get out of here otherwise is dragging becomes false and then we can also release the pointer from the Target and stop event propagation again we're all done with the drag so that's a little bit more straightforward than our drag and drop for the slots right but here we can just apply it to one visual element the slots definitely had a lot more moving pieces but how can we actually get this working on the target well let's jump over to the inventory view again and it's simple because we already have an extension method for this right where we're creating our inventory we can just chain it again with the with manipulator extension method and add a new panel drag manipulator to the inventory that's all you have to do well other than test it out of course let's go have a look if I reload the domain and press play check it out I can grab it by just about anywhere where there isn't a slot filled and drag this thing wherever I want if I grabb a slot of course that's going to take precedence and it all works just the way I expect so perfect I'm happy with this so far so I hope you learned something or maybe more than one thing new today we're going to continue this next week where we dive into the controller and the model of this inventory system all the code for this project will be linked in the description as well as the assets that I've used feel free to post in the comments if you have any thoughts or questions or something you want to see in the coming weeks feel free to join in the discussions on our Discord server as well and if you haven't done so yet hit that like button subscribe and hit the Bell so you don't miss next week's episode I'll see you there
Info
Channel: git-amend
Views: 11,492
Rating: undefined out of 5
Keywords: unity tutorial, unity arcade game, unity game tutorial, unity, game development, game dev, game development unity, programming, c#, software development, learn to code, learn programming, unity tutorials, unity devlog, indiedev, gamedev, unity3d, unity ui toolkit, unity ui toolkit inventory drag drop, unity ui toolkit drag drop, unity inventory system
Id: MOiXqKFHAIs
Channel Id: undefined
Length: 28min 14sec (1694 seconds)
Published: Sun Feb 11 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.