tyroney.com

WoW UI Tutorial

This is a basic tutorial for creating WoW UI mods from scratch.  It will cover UI basics, both XML and Lua widget creation, a bit of style, and just a touch of some semi-advanced topics including events and maybe even the new secure buttons.

Special thanks to all sorts of people.  The Wow UI modding community is full of great people, some you might recognize, and far more you've probably never heard of.  Blah blah blah, good feelings, etc.


This isn't done/checked.  Some code could be wrong,

though it should be fine.  It will become gradually more useful as time continues.  Once the basic thing is done, I'll think about adding extra "try this" kinda stuff with full code.  The code on this page isn't commented much at all, the final downloadable stuff will have comments where necessary. 


resources

Here are the links you'll want to know:
User Interface Customization tool
Extracts all of files from the default interface so you can dig through them.
the official forums
This is where proclaimations, patch notes, help, and things like that can be found. 
WoWWiki
User-edited resource full of documentation and such on nearly all of the API.
wowi IRC channel
Not a bad place for advice.  Usually.  Always great for venting about the forums.

intro

Welcome to the inner workings of the World of Warcraft interface!  Inside, you are only limited by your imagination, and the desires of the game designers as set forth in the API.  You may wonder what exactly this API is... as do most of us.  You see, it's constantly changing as the default UI evolves, and as blizzard refines all the details.  Because of this, this whole modding shebang is unsupported, and has no "Documentation" per se.  However, thanks to the great people involved, both inside and outside of Blizzard, there are many grrreat resources available to the aspiring WoW modder. 

files

Before we get moving, here's a quick rundown of how the files are arranged.  In the WoW directory, (once the game's run,) there's a folder named "Interface".  Inside there, is where addon folders will be found.  There are a bunch of placeholder folders for the addon parts of the default UI.  (like the auction house window.) 

- C Drive
  - Program Files
    - World of Warcraft
      + Cache
      + Data
      - Interface
        + Blizzard_AuctionUI
        + Blizzard_BattlefieldMinimap
        - testaddon
          · testaddon.toc
          · testaddon.xml
          · testaddon.Lua

An addon's folder has a name.  Inside the folder, you'll find a .toc file with the same name.  (same name with ".toc" added, of course)  There will also be XML or Lua files, and there might be images, or subfolders, or other stuff.  But for now that isn't important.  You might notice that none of the Blizzard "addons" have all that stuff in them.  That's because the Blizzard addons are special. 

## Interface: 20100
## Title: testaddon
## Author: test
## Notes: tests things

testaddon.xml

The toc file exists so that WoW can find out basic things about an addon like its name, description, and what files an addon needs to load.  There are quite a few extra things, but I'm not going to bother covering them here.  To the right is a basic toc file for the addon we're making.  Note the last line: if you're following along with the Lua-created widgets, you'll need to have "testaddon.lua" on the last line. 

XML vs. Lua

Now, a word about laying out widgets in XML and Lua.  As we progress, you'll probabaly notice some things.  One of the most obvious: the Lua is a bit more concise.  Personally, I like using XML to make templates, and to take advantage of easily connected code, and the ability to use things like the ADDON_LOADED event, which Lua created frames aren't around for.  Also, with the help of the Frame_XML file in the Errors folder, it can be easier to troubleshoot misbehaving frames made in XML.  Otherwise, it's entirely up to your preference as far as I'm concerned.


jumping in

Let's get going.  This all started with a miscellaneous request for a step-by-step example/tutorial, and the person asked for just an extra health bar with the player's name.  Seemed like a nice place to start, plenty of room for growth into something like a replacement player frame.  I'm not going to explain every single tag and function call as we go, since they're either self-explanatory, or you can just look them up at WoWWiki or the official forums stickies.  Most of the details for this tutorial were dug up in the official code, or hunted for at the Widget API page on WoWWiki.  But enough talking, let's go for it.

XML version

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\..\FrameXML\UI.xsd">

<Frame name="testaddon_Frame" parent="UIParent">
  <Size x="80" y="20" />
  <Anchors>
    <Anchor point="TOPLEFT" />
  </Anchors>

  <Layers>
    <Layer level="ARTWORK">
      <FontString name="$parentText" inherits="GameFontNormal" />
    </Layer>
  </Layers>

</Frame>
</Ui>

Lua version

local f=CreateFrame("FRAME","testaddon_Frame",UIParent);
f:SetWidth(80); f:SetHeight(20);
f:SetPoint("TOPLEFT",UIParent);
local fs = f:CreateFontString("$parentText","ARTWORK","GameFontNormal");
fs:SetAllPoints();

So, an object needs a few things to appear.  It has to have a size, a location, and something to show.  The frame has a size, it's location is determined by the anchor, and at this point you can't see anything, but it's really there, I promise.

Both the frame and the fontstring are anchored simply.  In the XML anchor, since it isn't specified, anchor is relative to the parent, and it's point to point.  (so the frame has it's topleft point lined up with UIParent's topleft point.  The fontstring is centered in the frame.)  In the Lua, it's all specified.  The most obvious difference is the final line of the Lua version, where we have to SetAllPoints() on the fontstring.  For various reasons, this happens automatically to the XML-created fontstring. 

Now, we want to have it stick your name in the fontstring, so we'll try just sticking a little code in the frame's OnLoad.  If I'm unlucky, the needed information (the name) might not be ready at that time, but we'll try it and find out.


adding code

Bam!  So I was able to just add an onload, set the text to the player's name, and get what I wanted.  On a slower system, or a bad day, you'll probably run into some "Unknown", but that's true of the whole UI, so I won't worry about that.  Find the appropriate code below. 

XML version

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\..\FrameXML\UI.xsd">

<Frame name="testaddon_Frame" parent="UIParent">
  <Size x="80" y="20" />
  <Anchors>
    <Anchor point="TOPLEFT" />
  </Anchors>

  <Layers>
    <Layer level="ARTWORK">
      <FontString name="$parentText" inherits="GameFontNormal" />
    </Layer>
  </Layers>

  <Scripts>
    <OnLoad>
      testaddon_FrameText:SetText(UnitName("player"));
    </OnLoad>
  </Scripts>

</Frame>
</Ui>

Lua version

local f=CreateFrame("FRAME","testaddon_Frame",UIParent);
f:SetWidth(80); f:SetHeight(20);
f:SetPoint("TOPLEFT",UIParent);
local fs = f:CreateFontString("$parentText","ARTWORK","GameFontNormal");
fs:SetAllPoints();
fs:SetText(UnitName("player"));

In the Lua version, I just called the SetText out right, since an Lua frame won't be able to fire and catch an OnLoad. 


border

Next, let's stick a border around it.  The easiest way is to use the basic boder like what's around the tooltip.  If you dig through Blizzard's XML, you'll find the tooltip has a <Backdrop> I'll go paste one into this, and see what happens.

XML version

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\..\FrameXML\UI.xsd">

<Frame name="testaddon_Frame" parent="UIParent">
  <Size x="80" y="20" />
  <Anchors>
    <Anchor point="TOPLEFT" />
  </Anchors>

  <Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
    <EdgeSize val="10" />
    <TileSize val="10" />
    <BackgroundInsets left="3" right="3" top="3" bottom="3"/>
  </Backdrop>

  <Layers>
    <Layer level="ARTWORK">
      <FontString name="$parentText" inherits="GameFontNormal" />
    </Layer>
  </Layers>

  <Scripts>
    <OnLoad>
      testaddon_FrameText:SetText(UnitName("player"));
    </OnLoad>
  </Scripts>

</Frame>
</Ui>

Lua version

local f=CreateFrame("FRAME","testaddon_Frame",UIParent);
f:SetWidth(80); f:SetHeight(20);
f:SetPoint("TOPLEFT",UIParent);
f:SetBackdrop({
  bgFile="Interface\\Tooltips\\UI-Tooltip-Background", 
  edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", 
  tile=1, tileSize=10, edgeSize=10, 
  insets={left=3, right=3, top=3, bottom=3}
});
local fs = f:CreateFontString("$parentText","ARTWORK","GameFontNormal");
fs:SetAllPoints();
fs:SetText(UnitName("player"));

For those of you playing along at home: notice the doubled slashes in the texture strings in the Lua version.  These are important.  I'd rather not explain specifically why, so don't ask me.  Here comes another picture, for those of you that like shiny things.


status bar

To add a health bar, I'll want to stick a Statusbar inside the frame.  I think I'll also move the thing down to the middle of the screen for now, to keep everything visible.  (it's really easy to make it draggable, but that's for extra credit later.)  For the XML version, I'm just copying the statusbar right out of the default player frame.  There are a few things that need to be set, so it'll take a little looking up methods (functions) for healthbar widgets. 

XML version

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\..\FrameXML\UI.xsd">

<Frame name="testaddon_Frame" parent="UIParent">
  <Size x="80" y="20" />
  <Anchors>
    <Anchor point="CENTER" />
  </Anchors>

  <Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
    <EdgeSize val="10" />
    <TileSize val="10"/>
    <BackgroundInsets left="3" right="3" top="3" bottom="3"/>
  </Backdrop>

  <Layers>
    <Layer level="ARTWORK">
      <FontString name="$parentText" inherits="GameFontNormal" setallpoints="true" />
    </Layer>
  </Layers>

  <Scripts>
    <OnLoad>
      testaddon_FrameText:SetText(UnitName("player"));
    </OnLoad>
  </Scripts>


  <Frames>
  
    <StatusBar name="$parentHealthBar" inherits="TextStatusBar">
      <Size x="80" y="20"/>
      <Anchors>
        <Anchor point="TOP" relativepoint="BOTTOM" />
      </Anchors>
      <BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
      <Scripts>
        <OnLoad>
          this:SetMinMaxValues(0,UnitHealth("player"))
        </OnLoad>
      </Scripts>
    </StatusBar>

  </Frames>

</Frame>
</Ui>

Lua version

local f=CreateFrame("FRAME","testaddon_Frame",UIParent);
f:SetWidth(80); f:SetHeight(20);
f:SetPoint("CENTER",UIParent);
f:SetBackdrop({
  bgFile="Interface\\Tooltips\\UI-Tooltip-Background", 
  edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", 
  tile=1, tileSize=10, edgeSize=10, 
  insets={left=3, right=3, top=3, bottom=3}
});
local fs = f:CreateFontString("$parentText","ARTWORK","GameFontNormal");
fs:SetAllPoints();
fs:SetText(UnitName("player"));
local b=CreateFrame("STATUSBAR","$parentHealthBar",f,"TextStatusBar");
b:SetWidth(80); b:SetHeight(20);
b:SetPoint("TOP",f,"BOTTOM");
b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar");
b:SetMinMaxValues(0,UnitHealth("player"))

Booyeah.  When you want to get something like this going, be sure to check out WoWWiki for the extra arguments and options I'm leaving out here.  (minvalue, maxvalue, playerhealth...)  I should point something out: the statusbar is a child of the frame with the name text in it, though it doesn't have to be visually inside of it.  So it's in it, but it isn't in it.  But it is.  But it isn't...

But our statusbar still doesn't do anything.  And it looks ugly.  Form over function: we'll leave it ugly for now, and get it working.


grabbing some data

I'll just try adding a line of code to the OnUpdate to constantly grab the current health value, and set the statusbar to match it. 

So I dueled someone, and took a long, deep swim, and it updated just fine.  To be fair, I'll mention some details and problems. 

XML version

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\..\FrameXML\UI.xsd">

<Frame name="testaddon_Frame" parent="UIParent">
  <Size x="80" y="20" />
  <Anchors>
    <Anchor point="CENTER" />
  </Anchors>

  <Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
    <EdgeSize val="10" />
    <TileSize val="10"/>
    <BackgroundInsets left="3" right="3" top="3" bottom="3"/>
  </Backdrop>

  <Layers>
    <Layer level="ARTWORK">
      <FontString name="$parentText" inherits="GameFontNormal" setallpoints="true" />
    </Layer>
  </Layers>

  <Scripts>
    <OnLoad>
      testaddon_FrameText:SetText(UnitName("player"));
    </OnLoad>
    <OnUpdate>
      testaddon_FrameHealthBar:SetValue(UnitHealth("player"));
    </OnUpdate>
  </Scripts>


  <Frames>
  
    <StatusBar name="$parentHealthBar" inherits="TextStatusBar">
      <Size x="80" y="20"/>
      <Anchors>
        <Anchor point="TOP" relativepoint="BOTTOM" />
      </Anchors>
      <BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
      <Scripts>
        <OnLoad>
          this:SetMinMaxValues(0,UnitHealth("player"))
        </OnLoad>
      </Scripts>
    </StatusBar>

  </Frames>

</Frame>
</Ui>

Lua version

local f=CreateFrame("FRAME","testaddon_Frame",UIParent);
f:SetWidth(80); f:SetHeight(20);
f:SetPoint("CENTER",UIParent);
f:SetBackdrop({
  bgFile="Interface\\Tooltips\\UI-Tooltip-Background", 
  edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", 
  tile=1, tileSize=10, edgeSize=10, 
  insets={left=3, right=3, top=3, bottom=3}
});
local fs = f:CreateFontString("$parentText","ARTWORK","GameFontNormal");
fs:SetAllPoints();
fs:SetText(UnitName("player"));
local b=CreateFrame("STATUSBAR","$parentHealthBar",f,"TextStatusBar");
b:SetWidth(80); b:SetHeight(20);
b:SetPoint("TOP",f,"BOTTOM");
b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar");
b:SetMinMaxValues(0,UnitHealth("player"))
f:setScript("OnUpdate", function() b:SetValue(UnitHealth("player")) end);

section

XML version


Lua version