news about forums links tools tutorials contact

Scrolling CTF Banner - author: ob1-kenobi

### Scrolling CTF Banner

#### Introduction

This tutorial goes through whats involved to create a custom scrolling banner like the one in DM-Morpeus, and how to use it in a map.

- Writing the script
- Overview of ClientScriptedTexture
- Overview of ScrollingMessageTexture
- Writing the BasicScrollingCTFMessage class
- Importing it to the map
- Setting it up for use

#### Writing the script

The scrolling message banner thats used in some UT maps comes in two parts, the message script actor and the scripted texture which is used to generate RenderTexture() calls in the script actor. Firstly, lets take a look at the ClientScriptedTexture class.

#### Overview of ClientScriptedTexture

    class ClientScriptedTexture expands Info;

The 'ScriptedTexture' property should be set up when you add the class to the level. When the game begins, the texture's 'NotifyActor' is set as this script actor. This will allow the actor to recieve 'RenderTexture()' calls when the texture is drawn during the game.

    var() Texture ScriptedTexture;

    /*
    Sets the notify actor propery of the ScriptedTexture to this class so that it will recieve
    RenderTexture() events from it.
    */
    simulated function BeginPlay()
    {
      if(ScriptedTexture != None)
        ScriptedTexture(ScriptedTexture).NotifyActor = Self;
    }

    /*
    Clears the NotifyActor property of the scripted texture when this actor is destoyed.
    */
    simulated function Destroyed()
    {
      if(ScriptedTexture != None)
        ScriptedTexture(ScriptedTexture).NotifyActor = None;
    }

    /*
    Called when the ScriptedTexture is rendered during the game.
    */
    simulated event RenderTexture(ScriptedTexture Tex)
    {
    }

    defaultproperties
    {
      bNoDelete=True
      bAlwaysRelevant=True
      RemoteRole=ROLE_SimulatedProxy
    }

Notice that the functions are all 'simulated' so that they can be called on the client. You don't need to worry about why they are simulated for the tutorial, just that they need to be made 'simulated' for it to work properly.

#### Overview of ScrollingMessageTexture

Now on to the banner code. For this tutorial, we'll be sub-classing the ScrollingMessageTexture and modify it slightly to make our own CTF banner. Here's the code for that class.

    class ScrollingMessageTexture expands ClientScriptedTexture;

    var() localized string ScrollingMessage;
    var localized string HisMessage, HerMessage;
    var() Font Font;
    var() color FontColor;
    var() bool bCaps;
    var() int PixelsPerSecond;
    var() int ScrollWidth;
    var() float YPos;
    var() bool bResetPosOnTextChange;

    var string OldText;
    var int Position;
    var float LastDrawTime;
    var PlayerPawn Player;

    /* parameters for ScrollingMessage:
       %p - local player name
       %h - his/her for local player
       %lp - leading player's name
       %lf - leading player's frags
    */

    simulated function FindPlayer()
    {
      local PlayerPawn P;

      foreach AllActors(class'PlayerPawn', P)
        if(Viewport(P.Player) != None)
          Player = P;
    }

This is the main entry point, and is the function that does most of the work. It calls the FindPlayer() function to try to find the local player (ie. the person actually playing the game) so that it can grab a reference to the GameReplicationInfo class, and it calls Replace() to replace the macros with the relavent text.

    simulated event RenderTexture(ScriptedTexture Tex)
    {
      local string Text;
      local PlayerReplicationInfo Leading, PRI;
      local int i;

The first part of the function tries to find the local player, and ends the function if no local player can be found, or the player doesn't have a GameReplicationInfo reference.

      if(Player == None)
        FindPlayer();

      if(Player == None || Player.PlayerReplicationInfo == None
        || Player.GameReplicationInfo == None)
        return;

The next part is involved with the posisioning of the scrolling message on the main texture. It does this by keeping track of the time that last frame was drawn, and adjusts it based on the time passed since the last render multiplyed by the value of PixelsPerSecond. If the message Position is less that the ScrollWidth, the position is reset.

      if(LastDrawTime == 0)
        Position = Tex.USize;
      else
        Position -= (Level.TimeSeconds-LastDrawTime) * PixelsPerSecond;

      if(Position < -ScrollWidth)
        Position = Tex.USize;

      LastDrawTime = Level.TimeSeconds;

The position has been worked out, the message string is processed and the macros are replaced with the relevant text. The code below replaces the %h macro with a gender specific string depending on the gender of the local player.

      Text = ScrollingMessage;

      if(Player.bIsFemale)
        Text = Replace(Text, "%h", HerMessage);
      else
        Text = Replace(Text, "%h", HisMessage);

The next section of code replaces the %p macro with the player name, and if either the %lf or %lp macros are in the message string replaces those with the leading players name and score.

      Text = Replace(Text, "%p", Player.PlayerReplicationInfo.PlayerName);
      if(InStr(Text, "%lf") != -1 || InStr(Text, "%lp") != -1)
      {
        // find the leading player
        Leading = None;

        for (i=0; i<32; i++)
        {
          if (Player.GameReplicationInfo.PRIArray[i] != None)
          {
            PRI = Player.GameReplicationInfo.PRIArray[i];
            if ( !PRI.bIsSpectator && (Leading==None || PRI.Score>Leading.Score) )
              Leading = PRI;
          }
        }
        if(Leading == None)
          Leading = Player.PlayerReplicationInfo;
        Text = Replace(Text, "%lp", Leading.PlayerName);
        Text = Replace(Text, "%lf", string(int(Leading.Score)));
      }

Finally, the string is changed to uppercase if necessary, saved to OldText and drawn on the Texture.

      if(bCaps)
        Text = Caps(Text);

      if(Text != OldText && bResetPosOnTextChange)
      {
        Position = Tex.USize;
        OldText = Text;
      }

      Tex.DrawColoredText( Position, YPos, Text, Font, FontColor );
    }

    simulated function string Replace(string Text, string Match,
                                                 string Replacement)
    {
      local int i;

      i = InStr(Text, Match);

      if(i != -1)
        return Left(Text, i) $ Replacement $
            Replace(Mid(Text, i+Len(Match)), Match, Replacement);
      else
        return Text;
    }

    defaultproperties
    {
         HisMessage="his"
         HerMessage="her"
         bResetPosOnTextChange=True
    }

#### Writing the BasicScrollingCTFMessage class

For the CTF message we need to use the team info rather than the players, so we need to get the information from the Teams[] array in TournamentGameReplicationInfo instead. Also, a message will be needed for when the teams scores are tied.

The scrolling message will need 2 macros, one for the leading teams name, and one for the leading teams score. These will be %t for the team name and %s for the teams score. Having the score displayed when the teams are tied will also be useful, but the %s macro can be used for this too as it doesn't matter which teams score is used when the teams are tied.

Here's the complete code for the class:

    class BasicScrollingCTFMessage extends ScrollingMessageTexture;

    // message used when both teams are tied
    var() localized string ScoresTiedMessage;

    /* parameters for ScrollingMessage:
       %t - leading team
       %s - leading team's score (also used for when teams are tied)
    */

    simulated event RenderTexture(ScriptedTexture Tex)
    {
      local string Text;
      local PlayerReplicationInfo Leading, PRI;
      local TournamentGameReplicationInfo GRI;
      local int i;
      local TeamInfo RedTeam, BlueTeam, LeadingTeam;
      local bool bTeamsTied;

      // get a reference to the GRI
      if(Player == None)
        FindPlayer();

      if(Player == None || Player.PlayerReplicationInfo == None
        || Player.GameReplicationInfo == None)
        return;

      if (!Player.GameReplicationInfo.bTeamGame)
        return;

      GRI = TournamentGameReplicationInfo(Player.GameReplicationInfo);

      // calculated the position of the message
      if(LastDrawTime == 0)
        Position = Tex.USize;
      else
        Position -= (Level.TimeSeconds-LastDrawTime) * PixelsPerSecond;

      if(Position < -ScrollWidth)
        Position = Tex.USize;

      LastDrawTime = Level.TimeSeconds;

      // parse the ScrollingMessage
      Text = ScrollingMessage;

      if(InStr(Text, "%t") != -1 || InStr(Text, "%s") != -1)
      {
        // find the leading team
        LeadingTeam = None;

        RedTeam = GRI.Teams[0];
        BlueTeam = GRI.Teams[1];

        if ((RedTeam == None) || (BlueTeam == None))
          return;

        if (RedTeam.Score > BlueTeam.Score)
          LeadingTeam = RedTeam;
        else if (RedTeam.Score < BlueTeam.Score)
          LeadingTeam = BlueTeam;
        else
        {
          // doesn't matter which team when scores are tied
          LeadingTeam = RedTeam;
           bTeamsTied = true;
        }

        if (bTeamsTied)
        {
          // use the ScoresTiedMessage instead if teams are tied
          Text = ScoresTiedMessage;
          Text = Replace(Text, "%s", string(int(LeadingTeam.Score)));
        }
        else
        {
          Text = Replace(Text, "%t",
            class'TeamScoreBoard'.default.TeamName[LeadingTeam.TeamIndex]);
          Text = Replace(Text, "%s", string(int(LeadingTeam.Score)));
        }
      }

      if(bCaps)
        Text = Caps(Text);

      if(Text != OldText && bResetPosOnTextChange)
      {
        Position = Tex.USize;
        OldText = Text;
      }

      // draw the message
      Tex.DrawColoredText( Position, YPos, Text, Font, FontColor );
    }

#### Importing the new class

Ok, now that we have the code for the new scrolling message, its time to add it to a map.

There are two ways that you can do this. You can either compile the class as a separate package and add it to the map that way, or you can import the class directly in to a map.

If you compile it as a separate package, the map will need to load the package when the map is loaded and it will probably have to be listed on the server packages list to work. By importing it to the map, you remove the need for an external package. I'll cover this method first.

Both of these methods use UnrealEd2.

#### Importing the class in to the map

Ok, firt things first: Make a backup of your map.

Now open up the class browser (the one used for selecting an actor to add to a map) and select Actor->Info->ClientScriptedTexture->ScrollingMessageTexture. Right click on the ScrollingMessageTexture class and select "New...". Now fill in the boxes as the following:

Package: MyLevel
Class: BasicScrollingCTFMessage

Then click on "OK".

When the code window pops up, replace *all* the code in the window with the code for the BasicScrollingCTFMessage class from above. After this is done, select: Tools | Compile changed to compile the script.

If there are any errors, check that you have:
1) Sub-classed ScrollingMessageTexture. If you sub-classed the wrong class, restart UnrealEd and reload the backup (you did make a backup, right?).
2) Replaced *all* the text with the BasicScrollingCTFMessage code. Make sure that you replace all the code in the code window with the code for the custom CTF banner.

If it compiled ok, save the map. The class is now embedded in your CTF map.

#### Importing the class in to a .u package

If you'd rather not import the class in to your map, you can import it in to regular .u package and add it to your map from there. This will mean that the package has to be distributed with the map, and may cause complications if trying to download the map off a server.

Open up the class browser (the one used for selecting an actor to add to a map) and select Actor->Info->ClientScriptedTexture->ScrollingMessageTexture. Right click on the ScrollingMessageTexture class and select "New...". Now fill in the boxes as the following:

Package: name_of_package
Class: BasicScrollingCTFMessage

Where name_of_package is what you want the new package to be called.

When the code window pops up, replace *all* the code in the window with the code for the BasicScrollingCTFMessage class from above. After this is done, select: Tools | Compile changed to compile the script.

If there are any errors, check that you have:
1) Sub-classed ScrollingMessageTexture. If you sub-classed the wrong one, restart UnrealEd and sub-class ScrollingMessageTexture.
2) Replaced *all* the text with the BasicScrollingCTFMessage code. Make sure that you replace all the code in the code window with the code for the custom CTF banner.

If it compiled ok, select the new package from the package list in the class browser (if no packages are listed, select "View | Show packages" to display them) and then select "File | Save selected packages" to save the package.

#### Setting it up for use

I don't really want to go over how to set up a scrolling message banner, so I'll assume that you already know how and continue from there. Check out the UnrealEd sites for tutorials on it if you are unsure of how to set one up, or check out some of the maps that use them like DM-Morhpeus, etc.

Once you've set up the map surface with the scripted texture you want to use, add the custom BasicScrollingCTFMessage to the map instead of a ScrollingMessageTexture. Set up the properties just as you would a regular message banner, except set up the ScrollingMessage and ScoresTiedMessage to use the CTF macros instead, eg. "%t leads with %s captures." for ScrollingMessage, and "Both teams are tied with %s captures." for the ScoresTiedMessage.