Skip to content

Program_LogSystem

bozar42 edited this page Feb 23, 2021 · 3 revisions

How To Build A Log System

< Back to Programming >

1

A log system prints a line of text on screen when something interesting happens. It is not an essential part of all Roguelike games. Games such as HyperRogue and Sproggiwood show the player everything by vivid animation. On the other hand, one can hardly figure out the situation without the help of combat log in DCSS. A sad truth about Axe Man, a Unity Roguelike game I am currently working on, is that the dungeon is filled with arcane ASCII symbols and I need a log system to tell the player the result of their actions. Here are the actual scripts:

2

The simplest log system contains two parts:

  • Trigger an event.
  • Print a string.

Such a system has various inputs and only a limited number of outputs. So we can start from the printing part. In Axe Man, the brief log is visible at the lower-right corner of the screen. Player can view the full log by pressing m.

IMAGE: Log system

Since the full log is not visible all the time, it seems that we need to store the text for later use. Let's add a third part to the system.

  • Trigger an event.
  • Store a string.
  • Print the string when necessary.

Printing text on the screen is not the topic of this article. It involves the interaction between the log system and UI objects, which is specific to every game. Our next task is to store and retrieve a message.

3

All the log messages are stored in a private List<string> fullLog. We can design an interface, ILogManager, like this:

public interface ILogManager
{
    void Add(string message);
    string GetLog(int reverseIndex);
}

GetLog() accepts an argument called reverseIndex, because we are more interested in new messages than older ones, so we would like to set the index of the last element in List<string> fullLog as 0, the second last one as 1, and so on.

public string GetLog(int reverseIndex)
{
    int index = fullLog.Count - reverseIndex - 1;
    if ((index < 0) || (index > fullLog.Count - 1))
    {
        return "";
    }
    return fullLog[index];
}

4

Let's take a look at ILogManager.Add(string message). We could embed a string into the code where an event is triggered. This is rather rigid because when we want to change the text, we have to edit and recompile the code. A better way is to retrieve text from an external file, which requires us to add another part to the system. Please refer to How To Load Data From XML Files for a general introduction.

  • Trigger an event.
  • Retrieve a string.
  • Store the string.
  • Print the string when necessary.

In Axe Man, we call LogData.GetStringData() for the retrieving task. We also need to update ILogManager.

public interface ILogData
{
    string GetStringData(LogMessage logMessage);
}

public interface ILogManager
{
    void Add(LogMessage logMessage);
    void Add(string message);
    string GetLog(int reverseIndex);
}

public void Add(LogMessage logMessage)
{
    string message = LogData.GetStringData(logMessage);
    Add(message);
}

LogMessage is a custom object. It contains two data tags which are releated to logData.xml.

public class LogMessage
{
    public LogMessage(LogCategoryTag category, LogMessageTag message) { }
}

5

Now comes to the last piece of the log system. Sometimes we need to print a string with dynamic data, which might be actor's name or attacking damage. In other words, we shall post-process the text from an XML file.

  • Trigger an event.
  • Retrieve a string.
  • Post-process the string.
  • Store the string.
  • Print the string when necessary.

Generally speaking, post-processing involves two tasks:

  • Retrieve a string with placeholders.
  • Replace the placeholders with actual data.

How to achieve these goals vary from game to game. In Axe Man, logData.xml contains text such as %ACTOR% hits you..

LogMessage has another constructor which accepts three arguments. Inside LogData.GetStringData(), we call TryReplacePlaceholder() to edit the text.

public class LogMessage
{
    public LogMessage(LogCategoryTag category, LogMessageTag message) { }
    public LogMessage(LogCategoryTag category, LogMessageTag message,
        ActorTag actor) { }
}

public string GetStringData(LogMessage logMessage)
{
    string actor = "%ACTOR%";
    string text = GetDataFromXML(logMessage);

    text = TryReplacePlaceholder(text, actor, logMessage.ActorTag);
    return text;
}

private string TryReplacePlaceholder(string source, string placeholder,
    ActorTag actorTag)
{
    string replace;
    string newText;

    // ActorTag.INVALID is the default value.
    if (actorTag != ActorTag.INVALID)
    {
        replace = ActorData.GetStringData(actorTag);
        newText = source.Replace(placeholder, replace);
    }
    return newText;
}

< Back to Programming >