Program_LogSystem
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:
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
.
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.
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];
}
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) { }
}
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;
}
Home | Latest | Game List | Design | Programming