First of all I need to extend mad street cred to Nedarb7, who first introduced me to the interface system. I would be completely clueless about this without his efforts and patient guidance.
Secondly it is important for you to know that this is largely based on a tutorial I wrote for Noobasaurus in a PM with reference to his Hide and Seek game mode. I don't think he ended up using much of what we discussed but the reason I mention this is just because some of the comments in my code will mention things like hide and seek, or blindness, etc. So just keep that in mind, though I've done my best to adapt everything for a more general audience.
What is the purpose of this tutorial?
There are two main topics that I am going to cover. The first is how to set up a brand new, fully customizable HUD element using the interface framework, and the second is how to construct a menu. I can already hear people protesting and saying that is totally backwards, we should learn how to make a menu first... well the way my examples are set up it will actually be easier for me to do it in the reverse order, so have a little faith and it will (hopefully) become clear soon.
Last thing before we begin: My examples are primarily from my Assassination game mode, so if you want to see how these things work in-game, you can download the test map HERE.
Let's begin!
Part 1: Preparation
Pretty much everything in this tutorial will be taking place inside data_ABC (or whatever your 3-letter mod code is). Assume that all directories mentioned in this tutorial have data_ABC as the root unless I say otherwise.
The very first thing you need to understand about making this kind of custom HUD element is that it uses the game's interface system, not the HUD. So it is essentially a screen much like the pause menu or video options screen, but overlaid on top of the game while the player has control over their unit rather than while the game is paused/frozen.
Custom interface scripts need to be loaded through the map's mission.lvl. However, simply adding the script's name to the mission.req file doesn't add it in. The file needs to be called by another script that automatically gets processed every time the map loads... which is ifs_pausemenu.lua. Note: this only applies if you are adding an in-game menu or a HUD overlay - if you are making a shell screen such as a new campaign menu or customizing the main menu, use the standard shell editing techniques for Part 6 of this tutorial!
So, open up common/mission.req and add "ifs_pausemenu" at the bottom of the "script" section.
Now we need to make a change to the pause menu script itself, so that it will tell the game to load our custom interface file (which we will create in another step) when the map loads. Open common/scripts/ifs_pausemenu.lua and put the following code at the top:
Code: Select all
ScriptCB_DoFile("ifs_blindness")
We also need to load our (currently imaginary) interface script in mission.req, so go back to that file and add "ifs_blindness" anywhere above "ifs_pausemenu" in the script section. I'm pretty sure this order is important because we need ifs_blindness.lua to be already loaded in the game's memory when ifs_pausemenu.lua tries to execute it later.
So now we have the preliminary preparation work done - not too hard so far, right?
Part 2: Graphics
Obviously you can decide what kind of graphic will look best for the blindness effect, but here are a few guidelines:
- It must be a .tga file
- It must have an alpha channel, even if it's all white
- I think it needs to have dimensions that are powers of two (e.g. 32x256, 512X512, etc)
- The file needs to be saved to common/interface/
Once you have the graphic made, you'll need to make sure the game knows to load it. This should be done through a custom .lvl file. I use common.lvl for loading in all of my custom graphics. Open common/common.req, and if there is anything inside, delete it. Create a "texture" section and enter the name of your blindness effect .tga underneath (mine is called blackness.tga). So in this example mine would be:
Code: Select all
ucft
{
REQN
{
"texture"
"blackness"
}
}
Code: Select all
ReadDataFile("..\\..\\addon\\ABC\\data\\_LVL_PC\\common.lvl")
Code: Select all
ReadDataFile("dc:common.lvl")
Part 3: What Is the Interface?
This is where things get really interesting.
In common/scripts/, make a new .lua file and name it the same as what you entered at the top of ifs_pausemenu.lua. In my example, it will be called ifs_blindness.lua.
The interface is made up of a bunch of objects that inherit properties and variables from other objects. If you go high enough up the chain you unfortunately run into the black box of the ZeroEngine's C API. This means that you can't make new kinds of objects within the interface, but you can do a ton of stuff with what's already available. Before I show you an example of an interface screen, I'll briefly explain the main elements you will see.
- IFShellScreen - This is a table that defines the whole "screen". This is where you create buttons, images, text, etc. Right at the beginning a bunch of basic properties of the screen are defined. Important side note: in general, the shell uses 1 for yes/true and nil for no/false.
- functions Enter, Update, Exit, Input_Back, Input_Accept - These are functions within the screen that are run at the point given by their name (e.g. function Enter runs when you first enter the screen; Input_Accept runs when the game receives a left-click or Enter key-press). You can put any code you want inside these.
- IFContainer - A container is like a template that other objects can use to share common properties. Say I have 5 buttons and they all need to be 50 px wide and 10 px tall, I can make a container with the properties width=50 and height=10, and have all the buttons inherit from the container, instead of having to manually tell each one to use those dimensions.
- IFText - A text box.
- IFImage - An image.
- function doSomething(this) - You can define and execute a function that does stuff not already defined in IFShellScreen if that meets your needs. You could theoretically leave the NewIfShellScreen table blank and define your whole screen through a function.
- this - Refers to whatever screen is being defined.
- AddIFScreen() - A function that adds your screen to the list of screens, I believe. It's definitely important.
Munge Common and then you are good to go! If you struggle with this part or run into any problems... yeah...
I'm not going to cover the stuff you need to put in your game mode or map lua, but I will tell you that in order to see your new screen/HUD in-game you need to use ScriptCB_PushScreen("ifs_whatever") in your code. You can find the source files for Assassination Mode at my project directory website but really if you're this far along in the tutorial chances are you know what you need to do in order to set up global variables that can be read by ifs_yourifnamehere.lua so I'll let you take care of that yourself.
----------------------------------------------------------------------------------------------------------------
Part 6: Menus
There are so many different elements you can include in menus, some of which are very simple (buttons) and others which are needlessly complicated (looking at you, sliders). For this tutorial, I'm just going to show you a basic vertical button arrangement and a toggleable text box object. Remember when we're editing a menu in the shell (e.g. the single-player campaign menu or the video settings menu) we don't use the ifs_pausemenu hack, we use the regular shell editing procedure which is found in a different tutorial!
Conclusion
This is it for now. If it seems disorganized, let me know and I'll come back and edit things for clarity. I will also compile a list of the most helpful/commonly used global variables and functions involved in interface scripting and add that somewhere in here as well. Edit: See below, I've done it!
Helpful Functions, Callbacks, and Properties Directory
I just spent a couple hours putting together this wonderful list of some of the functions, callbacks, classes, properties, etc that I think are most relevant/helpful. This comprises probably 15% of the entirety of the interface library but the other 85% is a combination of helper functions that run in the background, and really confusing, niche stuff that has very few practical uses; I'll let you guys explore on your own if you find that my list is missing something you'd like to use.
Enjoy!