Browse Source

Export and import dialog text for editing in a text editor #86

Added support for Fountain export / import.
http://fountain.io/
master
chrisgregan 10 years ago
parent
commit
04a115a626
  1. 4
      Assets/Fungus/FungusScript/Editor/CommandEditor.cs
  2. 2
      Assets/Fungus/FungusScript/Editor/CommandListAdaptor.cs
  3. 17
      Assets/Fungus/FungusScript/Editor/FungusScriptEditor.cs
  4. 1
      Assets/Fungus/FungusScript/Editor/FungusScriptWindow.cs
  5. 8
      Assets/Fungus/FungusScript/Editor/SequenceEditor.cs
  6. 3
      Assets/Fungus/FungusScript/Scripts/Command.cs
  7. 187
      Assets/Fungus/FungusScript/Scripts/FountainExporter.cs
  8. 12
      Assets/Fungus/FungusScript/Scripts/FountainExporter.cs.meta
  9. 33
      Assets/Fungus/FungusScript/Scripts/FungusScript.cs
  10. 125
      Assets/Fungus/FungusScript/Scripts/StringsParser.cs

4
Assets/Fungus/FungusScript/Editor/CommandEditor.cs

@ -71,6 +71,10 @@ namespace Fungus
GUILayout.FlexibleSpace();
GUILayout.Label(new GUIContent("(" + t.commandId + ")"));
GUILayout.Space(10);
GUI.backgroundColor = Color.white;
bool enabled = t.enabled;
enabled = GUILayout.Toggle(enabled, new GUIContent());

2
Assets/Fungus/FungusScript/Editor/CommandListAdaptor.cs

@ -94,6 +94,7 @@ namespace Fungus
}
Command newCommand = Undo.AddComponent<Comment>(sequence.gameObject) as Command;
newCommand.commandId = fungusScript.NextCommandId();
fungusScript.ClearSelectedCommands();
fungusScript.AddSelectedCommand(newCommand);
@ -109,6 +110,7 @@ namespace Fungus
System.Type type = command.GetType();
Command newCommand = Undo.AddComponent(parentSequence.gameObject, type) as Command;
newCommand.commandId = newCommand.GetFungusScript().NextCommandId();
System.Reflection.FieldInfo[] fields = type.GetFields();
foreach (System.Reflection.FieldInfo field in fields)
{

17
Assets/Fungus/FungusScript/Editor/FungusScriptEditor.cs

@ -5,6 +5,7 @@ using System.Collections.Generic;
using Rotorz.ReorderableList;
using System.Linq;
using System.Reflection;
using System.IO;
namespace Fungus
{
@ -21,6 +22,7 @@ namespace Fungus
protected SerializedProperty colorCommandsProp;
protected SerializedProperty hideComponentsProp;
protected SerializedProperty runSlowDurationProp;
protected SerializedProperty saveSelectionProp;
protected SerializedProperty variablesProp;
protected virtual void OnEnable()
@ -29,6 +31,7 @@ namespace Fungus
colorCommandsProp = serializedObject.FindProperty("colorCommands");
hideComponentsProp = serializedObject.FindProperty("hideComponents");
runSlowDurationProp = serializedObject.FindProperty("runSlowDuration");
saveSelectionProp = serializedObject.FindProperty("saveSelection");
variablesProp = serializedObject.FindProperty("variables");
}
@ -44,6 +47,7 @@ namespace Fungus
EditorGUILayout.PropertyField(colorCommandsProp);
EditorGUILayout.PropertyField(hideComponentsProp);
EditorGUILayout.PropertyField(runSlowDurationProp);
EditorGUILayout.PropertyField(saveSelectionProp);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
@ -51,6 +55,15 @@ namespace Fungus
{
EditorWindow.GetWindow(typeof(FungusScriptWindow), false, "Fungus Script");
}
if (GUILayout.Button(new GUIContent("Export Text", "Export all story text in .fountain format.")))
{
FountainExporter.ExportStrings(fungusScript);
}
if (GUILayout.Button(new GUIContent("Import Text", "Import story text from a file in .fountain format.")))
{
FountainExporter.ImportStrings(fungusScript);
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
@ -213,7 +226,7 @@ namespace Fungus
derivedType.IsAssignableFrom(t)
).ToList();
}
}
}
}

1
Assets/Fungus/FungusScript/Editor/FungusScriptWindow.cs

@ -689,6 +689,7 @@ namespace Fungus
{
field.SetValue(newCommand, field.GetValue(command));
}
newCommand.commandId = fungusScript.NextCommandId();
newSequence.commandList.Add(newCommand);
}

8
Assets/Fungus/FungusScript/Editor/SequenceEditor.cs

@ -528,12 +528,15 @@ namespace Fungus
{
return;
}
sequence.GetFungusScript().ClearSelectedCommands();
FungusScript fungusScript = sequence.GetFungusScript();
fungusScript.ClearSelectedCommands();
Command newCommand = Undo.AddComponent(sequence.gameObject, commandOperation.commandType) as Command;
sequence.GetFungusScript().AddSelectedCommand(newCommand);
newCommand.parentSequence = sequence;
newCommand.commandId = fungusScript.NextCommandId();
// Let command know it has just been added to the sequence
newCommand.OnCommandAdded(sequence);
@ -741,6 +744,7 @@ namespace Fungus
Command pastedCommand = commands.Last<Command>();
if (pastedCommand != null)
{
pastedCommand.commandId = fungusScript.NextCommandId();
fungusScript.selectedSequence.commandList.Insert(pasteIndex++, pastedCommand);
}
}

3
Assets/Fungus/FungusScript/Scripts/Command.cs

@ -31,6 +31,9 @@ namespace Fungus
public class Command : MonoBehaviour
{
[HideInInspector]
public int commandId = -1; // Invalid command id
[HideInInspector]
public string errorMessage = "";

187
Assets/Fungus/FungusScript/Scripts/FountainExporter.cs

@ -0,0 +1,187 @@
using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
namespace Fungus
{
/**
* Import and export a Fungus story in the .fountain screenplay format.
* The exported file contains special tags in note blocks which map the
* story text to the corresponding commands.
*/
public class FountainExporter
{
public static void ExportStrings(FungusScript fungusScript)
{
if (fungusScript == null)
{
return;
}
string path = EditorUtility.SaveFilePanel("Export strings", "",
fungusScript.name + ".txt", "");
if(path.Length == 0)
{
return;
}
// Write out character names
string exportText = "Title: " + fungusScript.name + "\n";
exportText += "Draft date: " + System.DateTime.Today.ToString("d") + "\n";
exportText += "\n";
// In every sequence, write out Say & Menu text in order
Sequence[] sequences = fungusScript.GetComponentsInChildren<Sequence>();
foreach (Sequence s in sequences)
{
// Check for any Say, Menu or Comment commands
bool hasText = false;
foreach (Command c in s.commandList)
{
System.Type t = c.GetType();
if (t == typeof(Say) ||
t == typeof(Menu) ||
t == typeof(Comment))
{
hasText = true;
}
}
if (!hasText)
{
continue;
}
exportText += "." + s.sequenceName.ToUpper() + "\n\n";
foreach (Command c in s.commandList)
{
if (c.GetType() == typeof(Say))
{
string idText = "";
Say say = c as Say;
if (say.character == null)
{
exportText += "NO CHARACTER\n";
}
else
{
exportText += say.character.nameText.ToUpper() + "\n";
}
idText += "[[Say," + c.commandId + "]]\n";
exportText += idText;
// Fountain requires blank dialogue lines to contain 2 spaces or else
// they will be interpreted as ACTION text.
string trimmedText = say.storyText.Trim();
string[] lines = trimmedText.Split(new [] { '\r', '\n' });
foreach (string line in lines)
{
string trimmed = line.Trim();
if (line.Length == 0)
{
exportText += " \n";
}
else
{
exportText += trimmed + "\n";
}
}
exportText += "\n";
}
else if (c.GetType() == typeof(Menu))
{
exportText += "MENU\n";
string idText = "";
Menu menu = c as Menu;
idText += "[[Menu," + c.commandId + "]]\n";
exportText += idText + menu.text.Trim() + "\n\n";
}
else if (c.GetType() == typeof(Comment))
{
string idText = "";
Comment comment = c as Comment;
idText += "[[Comment," + c.commandId + "]]\n";
exportText += idText + comment.commentText.Trim() + "\n\n";
}
}
}
File.WriteAllText(path, exportText);
}
public static void ImportStrings(FungusScript fungusScript)
{
string path = EditorUtility.OpenFilePanel("Import strings", "", "");
if(path.Length == 0)
{
return;
}
string stringsFile = File.ReadAllText(path);
StringsParser parser = new StringsParser();
List<StringsParser.StringItem> items = parser.ProcessText(stringsFile);
// Build dict of commands
Dictionary<int, Command> commandDict = new Dictionary<int, Command>();
foreach (Command c in fungusScript.gameObject.GetComponentsInChildren<Command>())
{
commandDict.Add (c.commandId, c);
}
foreach (StringsParser.StringItem item in items)
{
if (item.parameters.Length != 2)
{
continue;
}
string stringType = item.parameters[0];
if (stringType == "Say")
{
int commandId = int.Parse(item.parameters[1]);
Say sayCommand = commandDict[commandId] as Say;
if (sayCommand != null)
{
sayCommand.storyText = item.bodyText;
}
}
else if (stringType == "Menu")
{
int commandId = int.Parse(item.parameters[1]);
Menu menuCommand = commandDict[commandId] as Menu;
if (menuCommand != null)
{
menuCommand.text = item.bodyText;
}
}
else if (stringType == "Comment")
{
int commandId = int.Parse(item.parameters[1]);
Comment commentCommand = commandDict[commandId] as Comment;
if (commentCommand != null)
{
commentCommand.commentText = item.bodyText;
}
}
}
}
}
}

12
Assets/Fungus/FungusScript/Scripts/FountainExporter.cs.meta

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c91ad6ef6a0734046bd93dde4b0e59d1
timeCreated: 1426502899
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

33
Assets/Fungus/FungusScript/Scripts/FungusScript.cs

@ -12,6 +12,7 @@ namespace Fungus
* Visual scripting controller for the Fungus Script programming language.
* FungusScript objects may be edited visually using the Fungus Script editor window.
*/
[ExecuteInEditMode]
[AddComponentMenu("Fungus/Fungus Script")]
public class FungusScript : MonoBehaviour
{
@ -91,6 +92,38 @@ namespace Fungus
[Tooltip("Saves the selected sequence and commands when saving the scene.")]
public bool saveSelection = true;
/**
* Unique id to assign to the next created command.
* Increases monotonically every time a new command is added to the sequence.
*/
[SerializeField]
protected int nextCommandId = 0;
/**
* Returns the next command id to assign to a new command.
* Command ids increase monotically so they are guaranteed to
* be unique within a FungusScript.
*/
public int NextCommandId()
{
return nextCommandId++;
}
public virtual void OnEnable()
{
// Assign a command id to any command that doesn't have one yet.
// This should only happen after loading a legacy FungusScript
Command[] commands = GetComponentsInChildren<Command>();
foreach (Command command in commands)
{
if (command.commandId == -1)
{
command.commandId = NextCommandId();
Debug.Log("Assigning command id: " + command.commandId);
}
}
}
protected virtual Sequence CreateSequenceComponent(GameObject parent)
{
Sequence s = parent.AddComponent<Sequence>();

125
Assets/Fungus/FungusScript/Scripts/StringsParser.cs

@ -7,103 +7,90 @@ using Fungus;
namespace Fungus
{
/**
* Parses a text file using a simple format and adds string values to the global string table.
* The format is:
* $FirstString
* The first string text goes here
* $SecondString
* The second string text goes here
* # This is a comment line and will be ignored by the parser
* Parses an exported strings file using the Fountain file format for screenplays
* See http://fountain.io for details.
* We only support a small subset of Fountain markup, and use note tags to embed meta data to
* bind dialogue text to the corresponding Say / Menu commands.
*/
public class StringsParser : MonoBehaviour
public class StringsParser
{
public TextAsset stringsFile;
private enum ParseMode
public class StringItem
{
Idle,
Text,
};
public virtual void Start()
{
ProcessText(stringsFile.text);
public string[] parameters;
public string bodyText;
}
protected virtual void ProcessText(string text)
public virtual List<StringItem> ProcessText(string text)
{
List<StringItem> items = new List<StringItem>();
// Split text into lines. Add a newline at end to ensure last command is always parsed
string[] lines = Regex.Split(text + "\n", "(?<=\n)");
string blockBuffer = "";
ParseMode mode = ParseMode.Idle;
string blockTag = "";
for (int i = 0; i < lines.Length; ++i)
int i = 0;
while (i < lines.Length)
{
string line = lines[i];
bool newBlock = line.StartsWith("$");
string line = lines[i].Trim();
if (mode == ParseMode.Idle && !newBlock)
if (!(line.StartsWith("[[") && line.EndsWith("]]")))
{
// Ignore any text not preceded by a label tag
i++;
continue;
}
string newBlockTag = "";
if (newBlock)
{
newBlockTag = line.Replace ("\n", "");
}
bool endOfFile = (i == lines.Length-1);
string blockTag = line.Substring(2, line.Length - 4);
bool storeBlock = false;
if (newBlock)
// Find next empty line, #, [[ or eof
int start = i + 1;
int end = lines.Length - 1;
for (int j = start; j <= end; ++j)
{
storeBlock = true;
}
else if (mode == ParseMode.Text && endOfFile)
{
storeBlock = true;
if (!line.StartsWith("#"))
string line2 = lines[j].Trim();
if (line2.Length == 0 ||
line2.StartsWith("#") ||
line2.StartsWith("[["))
{
blockBuffer += line;
end = j;
break;
}
}
else
if (end > start)
{
if (!line.StartsWith("#"))
string blockBuffer = "";
for (int j = start; j <= end; ++j)
{
blockBuffer += line;
blockBuffer += lines[j].Trim() + "\n";
}
}
if (storeBlock)
{
if (blockTag.Length > 0 && blockBuffer.Length > 0)
blockBuffer = blockBuffer.Trim();
StringItem item = CreateItem(blockTag, blockBuffer);
if (item != null)
{
// Trim off last newline
blockBuffer = blockBuffer.TrimEnd( '\r', '\n', ' ', '\t');
items.Add(item);
}
}
// TODO: Store in a string table class
// GlobalVariables.SetString(blockTag, blockBuffer);
}
i = end + 1;
}
// Prepare to parse next block
mode = ParseMode.Idle;
if (newBlock)
{
mode = ParseMode.Text;
blockTag = newBlockTag;
}
return items;
}
blockBuffer = "";
}
protected StringItem CreateItem(string commandInfo, string bodyText)
{
string[] parameters = commandInfo.Split(new char[] { ',' });
if (parameters.Length > 0)
{
StringItem item = new StringItem();
item.parameters = parameters;
item.bodyText = bodyText;
return item;
}
return null;
}
}
}

Loading…
Cancel
Save