Menus and Abstractions
I started a blog entry yesterday about laundry or rather a metaphor about doing laundry. I’d explain it, but then well… as much as I like it as a metaphor I didn’t end up actually having a point. So instead let’s talk about something which does have a point - at least if you are a programmer - abstraction.
Abstraction is a way to reduce coupling by making things on a need to know basis. We want to reduce coupling because the more coupled the system is the harder it is to change or rather the more work it is to change. Since we value our time and hate doing this kind of work - let’s all agree we prefer implementing new features to fixing compiler errors when we change things we want good layers of abstraction.
So what exactly are good layers of abstraction? I have found that the answer is rather simple - they are layers that make sense. By which I mean if I want to abstract the inner workings of something it should appear to whatever using it to be something sensible. As Dave Farely would say “Code is Design.” Your code is not something which you write to just make your game go. If you treat it that way it will naturally become a mess, for the simple reason your design is a mess. Code is the way you communicate your design to the computer. Abstraction is a way to separate different parts of your code and hence your design. When you think about your system I am sure you will already have an intuitive separation like this - there is no way after all to think of your whole system at once. You will think of it as a collection of parts that fit together. Good abstraction comes when you figure out what each part is and how much of that part needs to be visible to other parts of the system.
That is all probably a bit abstract and what I find is if things are too abstract they seem complicated so let’s have an example from what I have been working on. I have made a system for customisable multi-tab menus - the idea is to be able to have a tab with all the units, a different tab with the pause menu and a third tab with the options menu. When you change tabs the navigation needs to update to a relevant menu item for controllers or if you decide to navigate with a keyboard and mouse. These menus also need to talk to navigation bars which allow you to navigate to the first / next / previous / last menu. Now what I had was a base class called InputUI which allowed hiding / showing of menus and accepted input. Then was UIMenuMakerBase which had part of the code to have a tabbed menu. Which then inherited a TabbedMenuMakerBase and CharacterMenuMakerBase from the rest of the required code to have a tabbed menu for button based and character displaying menu items. Finally classes are then inherited from either of those classes to provide any extra functionality. Finally UIMenuMakerBase implemented an interface which allowed it to be set in menu bars from a generic function because well… Did I mention the two generics in UIMenuMakerBase? Since you can’t serialise interfaces, the navigation bar had to be assigned to the menu.
All of which seems a bit of a mess and over complicated - because it was. For starters I didn’t like the functions to receive input in the base class. That function had an enum for input type as well (pretty gross really) and made it so that input was assigned in code in an unintuitive way which was less than ideal.
When I first made the script I was in love with generics, but as I have improved I have found as much as they have their place they usually need to be hidden in Unity since you can’t use generic Monobehaviours directly. More than that this is a bad system because it is hard to make sense of and critically it is hard to separate this code and test it. There is too much going on for one thing. Why is input mixed up in the menu code? Why is the code for tabbed menu split between two classes? So what was my solution? Well it is pretty simple in concept - put things where you would expect them. I need to pull the TabbedUIBase and CharacterMenuMakerBase code into the base class. However since I used the navigation bar script elsewhere I needed a better way to use it. So my first step was to add in another layer of abstraction. Now since I had too many layers you might think that was a bad choice. But the number of layers doesn’t matter really it is a case of do they actually abstract what you want? The extra layer of abstraction was MenuInputUI, the name perhaps is questionable but it is a class which has all the functionality for navigating tabs. Which means my other class which navigates through pages of text using a text mesh text component can use a navigation bar by inheriting from this class. More than that though something with tabs is a concept I can understand. If I find a bug in the tab navigation I can go to the tab navigation class and it will be there. Even better, that level of abstraction doesn’t require me to have any generics so I can remove the IMenuMaker interface the button bar was using and have a direct reference to this bar. Suddenly this complicated link is one component reference. When it came to merging the intermediate scripts for menus - well it was a matter of some clever refactoring which was a lot easier to do when I had fixed the abstraction in the base class. This saved me some duplicated code but so much more importantly meant that all the functionality was bundled together in the one base class. Even better, the parts the final classes changed were smaller and so easier to keep track of and work with.
Maybe all this sounds like pointless bragging but there is one core truth here. It is - put things where you expect them to be. Abstraction is all grouping ideas together consistently and in a way that matches your problem. The code in classes is only separated from the concepts it represents by accidental complexities of the system. Which is to say your code is just computer speak for what you are trying to do. In the same way you can verbose to increase your word count for a school report by adding a lot of words you can do the same for your programs. This kind of code is not nice to read let alone work with. Abstraction is like refining your points into sections and then adding in a page of contents. Imagine your code as one massive document, with each class as a contents page. Much like a book there are an infinite number of these pages available to you. Also much like any book a random collection of sentences that technically make sense aren’t going to make a good book. So try to make abstractions make sense and write code which you wouldn’t be ashamed to show to your mother.
I’d better get to making sure my game works with all these changes (and many more I haven’t mentioned). So that’s all from me for now.
Get Lords of Illic
Lords of Illic
Are your tactics good enough to become a Lord of Illic?
Status | Released |
Author | OrangeDrake |
Genre | Strategy |
Tags | Singleplayer, Turn-based Strategy |
More posts
- Version 19.044 days ago
- Version 18.666 days ago
- Domain Driven Design66 days ago
- Version 18.5Sep 03, 2024
- Burn away the ThornsAug 18, 2024
- Second Hand Musings Part 3: ShareableAug 13, 2024
- Version 18.4Jul 16, 2024
- Version 18.3May 27, 2024
- AIE LivestreamMay 13, 2024
- Version 18.2Apr 22, 2024
Leave a comment
Log in with itch.io to leave a comment.