Adding a Scoreboard GUI

Objects and child objects

When creating objects we are able to define an initialiser. This initialiser has an interesting feature. If you were to create an object like a SimGroup, which contains more objects you could do this:

%obj = new SimGroup(){};
%obj.addObject(new SimObject(){});
%obj.addObject(new SimObject(){});
//etc..

But the constructor allows us to instantiate the child objects inside the constructor:

%obj = new SimGroup(theGroup){
   new SimObject(){};
   new SimObject(){};
};

We often utilize this feature when writing new .gui files:

%guiContent = new GuiControl(ScoreBoardGui){
   new GuiBitmapBorderControl(){
      new GuiBitmapControl(){
      };
   };
};

This code will create a new control with a bitmap border inside, and a bitmap inside the border.

The Scoreboard GUI

I prefer writing GUIs in script because the editor feels a bit clumsy to me. Let's start by creating the baseline GUI file in data/CoinCollection/client/gui/scoreBoard.gui:

new GuiControl(ScoreBoardGUI) {
    position = "0 0";
    extent = "1024 768";
    profile = "GuiModelessDialogProfile";
    tooltipProfile = "GuiToolTipProfile";
    isContainer = "1";
    canSaveDynamicFields = "1";
    enabled = "1";
    noCursor = "1";
};

This is a simple control that basically fills the whole screen (starts at top left corner, scales down to the top right corner and it is anchored to the right and bottom sides).

Now we'll add the content inside of it piece by piece.

A centered container

Now we will need to center the content inside the ScoreBoardGUI so it is centered on the screen. Add this to the GuiControl ScoreBoardGUI, (Remember how we did it? Read “Objects and childobjects again.").

new GuiPanel() {
    docking = "None";
    position = "370 271";
    extent = "283 226";
    horizSizing = "center";
    vertSizing = "height";
    profile = "ScoreBoardProfile";
    tooltipProfile = "GuiToolTipProfile";
};

This creates a new panel in the center of the screen. Extent means the size of the panel. This panel is 283px wide and 226px tall.

We make sure it is centered by letting position be (screenwidth/2)-(extent/2). In this case it is (1024/2)-(283/2), (Likewise for height).

We set vertSizing to height, which means that it will follow the height of the screen and thus scale vertically.

We set horizSizing to center, so that it wont scale horizontally. This is because a horizontal scaling would corrupt the design of the scoreboard.

Headers for the scores

We will be using a GuiTextListCtrl to list the players. We can consider it like a table. Since every new Text in the list is a row, and we can specify columns by tabs.

Therefore we need some headers for the values “Coins, Kills, Deaths” (yes we will add in some ‘kill each other’ feature in a later tutorial).

So put this inside the GuiPanel you just created:

new GuiTextCtrl() {
    text = "Coins";
    maxLength = "255";
    position = "104 2";
    extent = "33 18";
    profile = "ScoreBoardTextBoldProfile";
    tooltipProfile = "GuiToolTipProfile";
};
new GuiTextCtrl() {
    text = "Kills";
    maxLength = "255";
    position = "158 2";
    extent = "30 18";
    profile = "ScoreBoardTextBoldProfile";
    tooltipProfile = "GuiToolTipProfile";
};
new GuiTextCtrl() {
    text = "Deaths";
    maxLength = "255";
    position = "206 2";
    extent = "37 18";
    profile = "ScoreBoardTextBoldProfile";
    tooltipProfile = "GuiToolTipProfile";
};

As you can see the position value is not very high on these elements, that’s because their parent is the GuiPanel so their position is relative to the panel!

The scrollbars

If alot of players is joining, we will need some scrollbars. For this we need a GuiScrollCtrl placed inside of the GuiPanel.

new GuiScrollCtrl() {
    willFirstRespond = "1";
    hScrollBar = "alwaysOff";
    vScrollBar = "dynamic";
    lockHorizScroll = "1";
    lockVertScroll = "0";
    constantThumbHeight = "0";
    childMargin = "0 0";
    mouseWheelScrollSpeed = "-1";
    position = "0 24";
    extent = "228 202";
    horizSizing = "width";
    vertSizing = "height";
    profile = "ScoreBoardScrollProfile";
    tooltipProfile = "GuiToolTipProfile";
    isContainer = "1";
};

As you might be able to see, we don’t want a horizontal scrollbar so it is always off (hScrollBar = "alwaysOff") and the vertical scrollbar only shows if it is necessary (vScrollBar = "dynamic") beside from that, all these settings should be fairly straight forward.

Now last but not least we need the aforementioned GuiTextListCtrl placed inside the GuiScrollCtrl:

new GuiTextListCtrl(ScoreBoardGUIList) {
    columns = "0 98 153 200";
    fitParentWidth = "1";
    clipColumnText = "0";
    position = "0 0";
    extent = "228 8";
    horizSizing = "width";
    vertSizing = "height";
    profile = "ScoreBoardTextNormalProfile";
    tooltipProfile = "GuiToolTipProfile";
    isContainer = "1";
};

The important things to note here is the columns attribute, the fitParentWidth attribute and the clipColumnText attribute.

  • columns specifies where the columns will be (on the x-axis relative to the GuiTextListCtrl). As you might have noticed this matches the positions of the GuiText controls!

  • fitParentWidth this makes the TextListCtrl fill the scroll horizontally.

  • clipColumnText this makes sure that the columns don’t break or overwrite each other. If the string in this column is longer than the column it will be cut out.

The final structure

When you are done your structure for the GUI should look something like this:

  • GuiControl “ScoreboardGUI”

    • GuiPanel

      • GuiTextCtrl “Coins”

      • GuiTextCtrl “Kills”

      • GuiTextCtrl “Deaths”

      • GuiScrollCtrl

        • GuiTextListCtrl “ScoreBoardGUIList”

Styling the GUI

Here is one thing that you may have noticed and that I haven’t explained!

The profiles! The profile is to GUI objects what CSS is to HTML. They define the style of the GUI’s

I wont spend a lot of time with the profiles even tho they are quite important. Therefore we will use alot of the stock profiles and only create a single custom one.

In data/CoinCollection/client/gui/customProfiles.cs add the following snippet:

singleton GuiControlProfile(ScoreBoardProfile : GuiDefaultProfile) {
    opaque = "1";
    fillColor = "0 0 0 200";
    fillColorHL = "0 0 0 200";
    borderColor = "0 0 0 255";
    borderThickness = "5";
    border = "1";
};

singleton GuiControlProfile(ScoreBoardTextBoldProfile : GuiDefaultProfile) {
    fontType = "Arial Bold";
    fontColor = "255 255 255 255";
};

singleton GuiControlProfile(ScoreBoardTextNormalProfile : GuiDefaultProfile) {
    fontType = "Arial";
    fontColor = "255 255 255 255";
};

singleton GuiControlProfile(ScoreBoardScrollProfile : GuiDefaultProfile) {
    fontType = "Arial Bold";
    fontColor = "255 255 255 255";
};

This should be pretty easy to understand. All controls using the "ScoreBoardProfile" has a black background which is transparent and the border is 5 px wide, black and not transparent.

There is alot more settings that can be played with, for setting text color bevel etc.. This is not within the scope of this tutorial unfortunately!

Showing the scoreboard

Now we need to add some functionality to the scoreboard!

First we should be able to see it shouldn’t we?

At the bottom of data/CoinCollection/client/gui/scoreBoard.tscript add the following code:

//-----------------------------------------------------------------------------
// ScoreBoardGUI utility methods
//-----------------------------------------------------------------------------
function ScoreBoardGUI::toggle(%this)
{
    if (%this.isAwake())
        Canvas.popDialog(%this);
    else
        Canvas.pushDialog(%this);
}

function ScoreBoardGUI::clear(%this)
{
    // Override to clear the list.
    ScoreBoardGUIList.clear();
}

This toggles the ScoreBoardGUI so a call to ScoreBoardGUI.toggle() will make it pop up on the screen. If we then call toggle() again it will hide itself.

Now, this is just a function we need a way to call it. And calling it through the console is quite.. Clumsy.. So lets add a keybinding!

Adding an ActionMap

An ActionMap is a way to bind actions to inputs. We can push ActionMaps so that we can change keybindings temporarily and then pop them so we revert to whatever the bindings were previously.

In data/CoinCollection/client/actionMap.tscript add the following:

if (isObject( CoinCollectionMoveMap ))
    CoinCollectionMoveMap.delete();

new ActionMap(CoinCollectionMoveMap);
CoinCollectionMoveMap.humanReadableName = "Coin Collection Move Map";

CoinCollectionMoveMap.bind(keyboard, f, showScoreBoard);

And then add another file in data/CoinCollection/client/inputCommands.tscript with the showScoreBoard function:

function showScoreBoard(%val) {
    // %val == 1 on key down and 0 on key up
    if (%val) {
        ScoreBoardGUI.toggle();
    }
}

Now, we need to push this ActionMap on top of what keybinds we already have. We can do that in data/CoinCollection/CoinCollection.tscript, whenever a client connects or disconnects to a server:

function CoinCollection::onCreateClientConnection(%this) {
    CoinCollectionMoveMap.push();
}

function CoinCollection::onDestroyClientConnection(%this) {
    CoinCollectionMoveMap.pop();
}

Now the final thing is we need to execute all of these scripts we added if you haven't already, in data/CoinCollection/CoinCollection.tscript:

function CoinCollection::initClient(%this) {
    %this.queueExec("./client/gui/customProfiles.tscript");
    %this.queueExec("./client/gui/scoreBoard.gui");
    %this.queueExec("./client/gui/scoreBoard.tscript");

    %this.queueExec("./client/inputCommands.tscript");
    %this.queueExec("./client/actionMap.tscript");
    %this.queueExec("./client/commands.tscript");
}

Last updated