The whole process of a game analysis from CSV table to program running logic

I accidentally found that this article was on the TODO list three months ago, and I haven't written it yet. This time, I will add it. This paper mainly analyzes the overall process of a certain game from planning the table to running the program.

Hot Update Resources and CSV Tables

Unity code hot change Lua Paihe ILRuntime Pie, and the resource craze is more AssetBundle The game I want to analyze also supports hot swapping, but only uses resource hot swapping, and the main logic in the game is unchanged.

Since the main logic in the game is unchanged, someone will ask how the plot craze is realized? It is very simple. Make the configured CSV table more popular, read the table to generate a dynamic scenario behavior tree, and the main logic execution behavior tree will be fine.

Here is the game AssetBundle Scenario table structure in:

 Person, text, CV, command, command, command, command, command, command ,,, activemusic: no=0 file=BGM  End name=End,,,,, ,,, setflag:jump,,clearfg:,stopbgm:,bg:name=BG_1 path=black entereffect=fade entertime=5,waittime: time=2 Boy, "What's wrong? Why did you ask this suddenly?",,,,,,, //Omit a lot ,,, stopbgm:,bg:name=BG_1 path=black entereffect=fade entertime=1,waittime:time=7,,, ,,, jump:storage=01 target=jump,,,,,

The game stories are executed in sequence according to the CSV table. At the same time, according to the user-defined commands in the table, you can jump to the execution location or read other story tables to achieve the effect of story branching.

Analysis module analysis

I use dnSpy analysis Assembly-CSharp.dll In general, this file contains all the internal scripts when packaging.

  1. Load AB package
    The game has a singleton class GlobalData , in its Awake method, the resource list file will be read info.txt And try to parse, and then try to load all AssetBundle resources:

     if (File. Exists(SystemConfig. AB_PATH + "/info")) { string[] array = ((TextAsset)AssetBundle. LoadFromFile(SystemConfig. AB_PATH + "/info").LoadAsset("Assets/Resources/info.txt")).text.Split( new char[] { '\n' }); try { foreach (string text in array) { string key = text; this.SHAsset.Add(key, AssetBundle.LoadFromFile(SystemConfig. AB_PATH + "/" + text)); } } catch (Exception ex) { Debug.LogError(ex.Data); } }

    The resource list stores all the AssetBundle packages that need to be loaded in the form of one for each line

     background audio event [Omitted] scripts

    After reading the package, follow the line feed character \n Cut, and then read with foreach loop.

    Then there are some processing codes for configuration information archiving, including setting game resolution, full screen, volume, version migration, text scrolling speed, etc., which will not be discussed here.

    In another DialogManager Of Start Method, it loads the scenario CSV script to be used as required:

     if (string.IsNullOrEmpty(GlobalData.Instance.UserData.Dialog.ScriptFileName)) { GlobalData.Instance.UserData.Dialog.ScriptFileName = Utility.GetInitScriptFile(); GlobalData.Instance.UserData.Dialog.LineNum = SystemConfig.InitialLine; GlobalData.Instance.UserData.Dialog.LoadScript(GlobalData. Instance.UserData.Dialog.ScriptFileName); this.ProcessCommand(null); this.isSkipModeSet = false; return; } GlobalData.Instance.UserData.Dialog.LoadScript(GlobalData. Instance.UserData.Dialog.ScriptFileName); this.LoadElements(); if (SceneController. OldSceneName.ToLower(). Equals("videoplayer") || SceneController. OldSceneName.ToLower(). Equals("srpg")) { this.ProcessCommand(null); }

    among ScriptFileName The table name is stored. In the new game state, if the name is empty, read GetInitScriptFile() The returned table name, GetInitScriptFile Will read config.txt , the returned result is 00, that is, the first table.

    When reading the archive, ScriptFileName It will be assigned the name of the script used for archiving (chapter scripts are multiple independent csvs, and chapter scenario hotfixes are unlocked).

    UserData.Dialog.LoadScript It is the method to load the scenario csv, which cuts the CSV by line and stores it in the List.

     list = Regex.Split(Utility. GetTextAsset(Utility. GetScriptPath(path)).text, "\r\n"). ToList<string>();
  2. CSV transformation execution node
    The execution node is similar to the common behavior tree structure. We all know that the behavior tree has three types of control nodes: selection, sequence, and parallelism. Sequence nodes and selection nodes are the most commonly used in this game.

    The code above calls ProcessCommand Method, which is the method to start executing nodes, and it also bears the task of converting CSV into nodes.

     if (cmdData == null) { List<DialogCommand> currentLineCommands = this.getCurrentLineCommands(); cmdData = this.GetCommandData(currentLineCommands); }

    stay getCurrentLineCommands In, the control method of converting String to node is called:

     dialogCommands = DialogHelper.columnMapping(script[GlobalData.Instance.UserData.Dialog.LineNum]);

    stay columnMapping Medium, read first columns.csv Get the header definition, and then convert the text of the current line into a leaf node or control node according to the header definition:

     string[] strArrays = Utility. GetTextAsset(SystemConfig. COLUMN_MAPPING).text.Replace("\r\n", ""). Split(new char[] { ',' }); string[] strArrays1 = lineScript. Split(new char[] { ',' });

    First, use a DialogCommand Storage:

     public class DialogCommand { public DialogCommandType CommandType; public string Content; }

    After that, all the DialogCommand Merge into one by type ScriptData Medium, here ScriptData It is the base class of the behavior tree node.

     public class ScriptData { public string Name; public string Text; public string CVPath; public Dictionary<string, string> translation = new Dictionary<string, string>(); public List<ScriptAction> ActionList = new List<ScriptAction>(); }

    The conversion process is very simple. Let's briefly describe the meaning of ActionList.

    If the game is to be executed normally, it must contain some special operation commands, such as select dialog box, confirm dialog box, change background, change foreground character, play background music, set flag bit, jump story, etc. They control the logic of the game story to a certain extent, and can control the execution of the entire behavior tree. In order to enable the planner to directly configure tables, these functions are written into CSV in the form of custom commands, and when read, they are converted into different ScriptActions.

  3. Game main loop
    Back to the previous ProcessCommand , here the current step's ScriptData Then, execute the Action directly.

     foreach (ScriptAction scriptAction in cmdData. ActionList) { scriptAction.ExecuteCMD(this); }

    Action has many overridden subclasses, such as BGMAction, FadeBGAction, and so on, which are all used to implement the operation commands mentioned above.

    Execute after ShowDialog Method, this method will set the name, current conversation and other necessary information of the game, and play CV voice at the same time.

     this.DialogBox.transform.Find("Name"). Find("Text").gameObject. GetComponent<Text>().text = TextManager. ReplaceUserName(cmdData. Name); if (! string.IsNullOrEmpty(cmdData. CVPath)) { GlobalSoundPlayer.Instance.PlayCastVoice(Utility. GetAudioClip(Utility. GetCastVoicePath(cmdData. CVPath)), false, 0f); } else if (GlobalSoundPlayer. Instance.isCVPlaying() && GlobalData. Instance.UserData.SysEnv.IsVoiceBreak) { GlobalSoundPlayer.Instance.StopCastVoice(); }

    Finally, on DialogManager Of Update According to user input and system status, execute the behavior tree in turn.

At this point, the whole game is over from planning and scheduling to game logic.

What to learn

You can always learn something new by looking at other people's code, and this is no exception.

  • In some cases, all the file paths used are standardized and put into a public class, which is convenient to find and use, and also conducive to collaborative development by multiple people.
  • In some cases, nodes are generated on demand rather than all nodes at the beginning, which can avoid wasting intermediate nodes when nodes jump. However, this also has disadvantages. Every time a new node is generated, it may cause the running program to get stuck. It is necessary to comprehensively judge whether to use it.
Zimiao haunting blog (azimiao. com) All rights reserved. Please note the link when reprinting: https://www.azimiao.com/6993.html
Welcome to the Zimiao haunting blog exchange group: three hundred and thirteen million seven hundred and thirty-two thousand

Comment

*

*