Hi there,
I'm wondering if there's any way to import a .fountain file into a dialogue system conversation? Or is there no way to make this happen aside from copying and pasting dialogue into the dialogue editor?
Thanks,
Matt
.fountain support
Re: .fountain support
Hi Matt,
If you're comfortable with a bit of scripting, make a custom importer. Duplicate the starter script ConverterWindowTemplate.cs and fill in code where the comments indicate to import your .fountain file.
If you're comfortable with a bit of scripting, make a custom importer. Duplicate the starter script ConverterWindowTemplate.cs and fill in code where the comments indicate to import your .fountain file.
Re: .fountain support
Hi Tony,
I Started working on a .fountain importer using CSVConverterWindow.cs as reference. My goal is to get the basic actor/conversant entries into a new conversation in order. I'll add things like sequences, variables, branching dialogue etc. in the dialogue editor after.
Here is the current state of my custom CopySourceToDialogueDatabase method. I keep getting non-specific "null reference exception" when trying to run this:
It reads the lines from the fountain file just fine, and I've been able to successfully parse out all the desired lines in testing. When trying to create dialogue entries and conversations I'm running into vague null reference exceptions.
In the CSV example, I noticed there are a ton of safeguards and that every possible variable is defined for entries, links, actors, etc etc. How granular do I need to be to create dialogue entries from code? Do I need to be as thorough as the CSV example or is there a minimum amount of information entries, conversations, and databases need to "pass muster"? Or is there a more simple mistake that I'm not seeing?
---------
Also I was wondering about adding the conversation to my main dialogue database instead of generating whole new database each time. Could I just use "DialogueManager.masterDatabase.AddConversation()"?
I Started working on a .fountain importer using CSVConverterWindow.cs as reference. My goal is to get the basic actor/conversant entries into a new conversation in order. I'll add things like sequences, variables, branching dialogue etc. in the dialogue editor after.
Here is the current state of my custom CopySourceToDialogueDatabase method. I keep getting non-specific "null reference exception" when trying to run this:
Code: Select all
//I decided to try making this work by adding a "#" in front of each actor's name in the screenplay software
//(and exported fountain file). Then it removes the # from the actorName, and reads the next line down as
//the dialogue text and assigns those to the new entry and adds each one to the new conversation.
protected override void CopySourceToDialogueDatabase(DialogueDatabase database)
{
LoadSourceFile();
Conversation newConversation = new Conversation();
for(int i = 0; i < sourceLines.Count; i++ )
{
if(sourceLines[i].StartsWith("#"))//# triggers the following code
{
string actorHashtagString = sourceLines[i];
string actorName = actorHashtagString.Remove(0,1);//Define actorName and remove hashtag
string dialogueText = sourceLines[i + 1];//Define dialogue text - always on the next line in fountain
DialogueEntry entry = new DialogueEntry(); //create new dialogue entry
entry.ActorID = Tools.StringToInt(actorName);//assign actorID to entry
entry.DialogueText = dialogueText;//assign dialogue text to entry
newConversation.dialogueEntries.Add(entry);//add entry to conversation
}
}
database.AddConversation(newConversation); //add newConversation to database
}
In the CSV example, I noticed there are a ton of safeguards and that every possible variable is defined for entries, links, actors, etc etc. How granular do I need to be to create dialogue entries from code? Do I need to be as thorough as the CSV example or is there a minimum amount of information entries, conversations, and databases need to "pass muster"? Or is there a more simple mistake that I'm not seeing?
---------
Also I was wondering about adding the conversation to my main dialogue database instead of generating whole new database each time. Could I just use "DialogueManager.masterDatabase.AddConversation()"?
Re: .fountain support
Hi,
Use the template property to create conversations and dialogue entries:
You can find similar example code here.
This will set the database variable to point to the dialogue database that's assigned to the open scene's Dialogue Manager. If the open scene doesn't have a Dialogue Manager, or if no database is assigned to the Dialogue Manager, this line will fail.
A more robust approach would be to add a field to your custom editor where you can assign your database.
If you add the conversation to your main database, you may want to check if the conversation is already present in the database. If so, you'll want to handle that somehow, such as deleting the existing version before creating the new version of the conversation.
If you don't want to deal with this, you can import into a new database. Then open the main database in the Dialogue Editor. In the Database section, use the Merge Database feature to merge the new database's contents into your main database.
Use the template property to create conversations and dialogue entries:
Code: Select all
protected override void CopySourceToDialogueDatabase(DialogueDatabase database)
{
LoadSourceFile();
int conversationID = template.GetNextConversationID(database);
Conversation newConversation = template.CreateConversation(conversationID, "Your Conversation Title");
// Create the <START> entry:
DialogueEntry startEntry = template.CreateDialogueEntry(0, conversationID, "START");
newConversation.dialogueEntries.Add(startEntry);
DialogueEntry prevEntry = startEntry;
for (int i = 0; i < sourceLines.Count; i++)
{
if (sourceLines[i].StartsWith("#"))//# triggers the following code
{
string actorHashtagString = sourceLines[i];
string actorName = actorHashtagString.Remove(0, 1);//Define actorName and remove hashtag
string dialogueText = sourceLines[i + 1];//Define dialogue text - always on the next line in fountain
int entryID = template.GetNextDialogueEntryID(newConversation);
DialogueEntry entry = template.CreateDialogueEntry(entryID, conversationID, "");
entry.ActorID = Tools.StringToInt(actorName);//assign actorID to entry
entry.DialogueText = dialogueText;//assign dialogue text to entry
newConversation.dialogueEntries.Add(entry);//add entry to conversation
// Link previous entry to this entry:
prevEntry.outgoingLinks.Add(new Link(conversationID, prevEntry.id, conversationID, entry.id));
prevEntry = entry;
}
}
database.AddConversation(newConversation); //add newConversation to database
}
Yes. A quick and dirty way is to add this line to the beginning of your CopySourceToDialogueDatabase() method:
Code: Select all
database = EditorTools.FindInitialDatabase();
A more robust approach would be to add a field to your custom editor where you can assign your database.
If you add the conversation to your main database, you may want to check if the conversation is already present in the database. If so, you'll want to handle that somehow, such as deleting the existing version before creating the new version of the conversation.
If you don't want to deal with this, you can import into a new database. Then open the main database in the Dialogue Editor. In the Database section, use the Merge Database feature to merge the new database's contents into your main database.
Re: .fountain support
Hi Tony,
Awesome, thanks for this. The template methods worked like a dream! This will save me hours of time.
For database merging, I found clicking on overwrite, then merge produced a desirable result. I'd rather have redundancy than accidentally overwrite something I didn't mean to, since I plan on going back and forth and testing a lot.
Here's what I ultimately cooked up in case someone else out there is trying to do something similar to me.
Basically at the start of the doc I added a [TITLE] [ACTOR] and [CONVERSANT] tag to define those things at the start.
Like this
[TITLE]Captain crunch's dilemma
[ACTOR]Captain Crunch
[CONVERSANT]Parrot
Placing @ at the beginning of a line indicates it's an actor, the next line gets turned into dialogue text.
If the actor isn't already in the database, it creates a new actor on the spot.
Thanks again!
Awesome, thanks for this. The template methods worked like a dream! This will save me hours of time.
For database merging, I found clicking on overwrite, then merge produced a desirable result. I'd rather have redundancy than accidentally overwrite something I didn't mean to, since I plan on going back and forth and testing a lot.
Here's what I ultimately cooked up in case someone else out there is trying to do something similar to me.
Basically at the start of the doc I added a [TITLE] [ACTOR] and [CONVERSANT] tag to define those things at the start.
Like this
[TITLE]Captain crunch's dilemma
[ACTOR]Captain Crunch
[CONVERSANT]Parrot
Placing @ at the beginning of a line indicates it's an actor, the next line gets turned into dialogue text.
If the actor isn't already in the database, it creates a new actor on the spot.
Thanks again!
Code: Select all
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using UnityEngine;
using UnityEditor;
namespace PixelCrushers.DialogueSystem
{
//*** Rename TemplateConverterPrefs here and in the ConverterWindowTemplate class definition below.
[Serializable]
public class FountainConverterPrefs : AbstractConverterWindowPrefs
{
//*** Add any settings your window needs to remember here.
}
//*** Rename ConverterWindowTemplate to the name of your converter class:
public class FountainConverterWindowTemplate : AbstractConverterWindow<FountainConverterPrefs>
{
//*** Set the source file extension here:
//7-21-23 : changed "txt" to "fountain"
public override string sourceFileExtension { get { return "fountain"; } }
//*** Set the EditorPrefs key to save preferences under:
//7-21-23 : changed to "PixelCrushers.DialogueSystem.FountainConverterSettings"
public override string prefsKey { get { return "PixelCrushers.DialogueSystem.FountainConverterSettings"; } }
//*** Customize this menu item:
[MenuItem("Tools/Pixel Crushers/Dialogue System/Import/Fountain", false, 1)]
public static void Init()
{
EditorWindow.GetWindow(typeof(FountainConverterWindowTemplate), false, "Fountain Import");
}
//*** Basic preferences are stored in a variable named 'prefs' of type Prefs. You can
//*** create a subclass of Prefs if you need to store additional data. If you do this,
//*** also override the ClearPrefs(), LoadPrefs(), and SavePrefs() methods.
//*** This is the main conversion routine.
//*** Read prefs.SourceFile (or whatever source data you need, if you've overridden
//*** the prefs object) and copy the data into the dialogue database object.
protected override void CopySourceToDialogueDatabase(DialogueDatabase database)
{
//Load the source file
LoadSourceFile();
Debug.Log("FountainConverterWindowTemplate : LoadSourceFile() ran!");
string thisTitle = "Untitled Conversation";//the title of the conversation (default, assigned below)
string s_thisActor = "Player";
string s_thisConversant = "NPC";
//---------- Find and add [TITLE]
for (int i = 0; i < sourceLines.Count; i++)//iterate through source
{
if (sourceLines[i].StartsWith("[TITLE]"))
{
string titleMarkedString = sourceLines[i];
string foundTitle = titleMarkedString.Remove(0, 7);//define foundTitle, remove [TITLE] markup
thisTitle = foundTitle;
Debug.Log("FountainConverterWindowTemplate : Title = " + thisTitle);
break;
}
}
//---------- Find and add [ACTOR]
for (int i = 0; i < sourceLines.Count; i++)//iterate
{
if (sourceLines[i].StartsWith("[ACTOR]"))
{
//check if it exists, if not - make a new one.
string actorMarkedString = sourceLines[i];
string foundActor = actorMarkedString.Remove(0, 7);
s_thisActor = foundActor;
if (database.GetActor(s_thisActor) == null)//Check if the actor already exists. if not, add a new one.
{
int actorID = template.GetNextActorID(database);
bool isThisPlayer = false;
//if (s_thisActor == "JOHNBOY") { isThisPlayer = true; }//check if it's player
Actor newActor = template.CreateActor(actorID, s_thisActor, isThisPlayer);//create new actor
database.actors.Add(newActor);//Add to Dialogue System Database
Debug.Log("FountainConverterWindowTemplate : New Actor created : " + s_thisActor + " | Actor ID = " + newActor.id);
}
break;
}
}
//---------- Find and add [CONVERSANT]
for (int i = 0; i < sourceLines.Count; i++)//iterate
{
if (sourceLines[i].StartsWith("[CONVERSANT]"))
{
string conversantMarkedString = sourceLines[i];
string foundConversant = conversantMarkedString.Remove(0, 12);
s_thisConversant = foundConversant;
if (database.GetActor(s_thisConversant) == null)
{
int actorID = template.GetNextActorID(database);
bool isThisPlayer = false;
//if (s_thisActor == "JOHNBOY") { isThisPlayer = true; }//check if it's player
Actor newActor = template.CreateActor(actorID, s_thisConversant, isThisPlayer);//create new actor
database.actors.Add(newActor);
Debug.Log("FountainConverterWindowTemplate : New Actor created : " + s_thisConversant + " | Actor ID = " + newActor.id);
}
break;
}
}
//Define conversationID and new conversation. Add title, actor, and conversant.
int conversationID = template.GetNextConversationID(database);
Conversation newConversation = template.CreateConversation(conversationID, thisTitle);
newConversation.ActorID = Tools.StringToInt(s_thisActor);
newConversation.ConversantID = Tools.StringToInt(s_thisConversant);
Debug.Log("FountainConverterWindowTemplate : New Conversation Created!");
//Create the <START> entry:
DialogueEntry startEntry = template.CreateDialogueEntry(0, conversationID, "START");
newConversation.dialogueEntries.Add(startEntry);//Add startEntry to newConversation
DialogueEntry prevEntry = startEntry;//Placeholder for prevEntry
string currentActor = s_thisActor;
string currentConversant = s_thisConversant;
string previousActor = s_thisConversant;
string previousConversant = s_thisActor;
Debug.Log("FountainConverterWindowTemplate : for Loop Started, " + sourceLines.Count + " source lines in fountan file!");
//Iterate through source file, every time an @ is detected,
for (int i = 0; i < sourceLines.Count; i++)
{
if (sourceLines[i].StartsWith("@"))
{
string actorMarkedString = sourceLines[i];
string actorName = actorMarkedString.Remove(0, 1);//Define actorName, remove @ markup
//If the actor is different than last time, change the actor/conversant. If it's a new actor, add them.
if (currentActor != actorName)//if the actor is different
{
//check if current actor is not previous speaker, and if currentActor is already in database
if(actorName != previousConversant && database.GetActor(actorName) != null)
{
//assign new current actor. Previous conversant.
previousActor = currentActor;
previousConversant = currentConversant;
currentActor = actorName;
currentConversant = previousActor; //(is this enough?)
Debug.Log("FountainConverterWindowTemplate : Current Actor = " + currentActor + " | Current Conversant = " + currentConversant);
Debug.Log("FountainConverterWindowTemplate : Previous Actor = " + previousActor + " | Previous Conversant = " + previousConversant);
}
else if(actorName != previousConversant && database.GetActor(actorName) == null)//if currentActor is NOT in database
{
//Add the new actor, than assign new current actor. Previous conversant.
int actorID = template.GetNextActorID(database);
Actor newActor = template.CreateActor(actorID, actorName, false);
database.actors.Add(newActor);
previousActor = currentActor;
previousConversant = currentConversant;
currentActor = actorName;
currentConversant = previousActor;
Debug.Log("FountainConverterWindowTemplate : Current Actor = " + currentActor + " | Current Conversant = " + currentConversant);
Debug.Log("FountainConverterWindowTemplate : Previous Actor = " + previousActor + " | Previous Conversant = " + previousConversant);
}
else
{
//swap previous/current actor/conversant (2 way convo)
previousActor = currentActor;
previousConversant = currentConversant;
currentActor = actorName;
currentConversant = previousActor;
Debug.Log("FountainConverterWindowTemplate : Current Actor = " + currentActor + " | Current Conversant = " + currentConversant);
Debug.Log("FountainConverterWindowTemplate : Previous Actor = " + previousActor + " | Previous Conversant = " + previousConversant);
}
}
string dialogueText = sourceLines[i + 1];//Define dialogue text (the next line down)
int entryID = template.GetNextDialogueEntryID(newConversation);//Define entryID from newConversation
DialogueEntry entry = template.CreateDialogueEntry(entryID, conversationID, ""); //create new dialogue entry
entry.ActorID = database.GetActor(currentActor).id;//Assign actorID and conversantID
entry.ConversantID = database.GetActor(currentConversant).id;
entry.DialogueText = dialogueText;//assign dialogue text to entry
newConversation.dialogueEntries.Add(entry);//add entry to conversation
Debug.Log("FountainConverterWindowTemplate : " + actorName + " : " + dialogueText);
//Debug.Log("FountainConverterWindowTemplate : entry actor/conversant check: actor = " + entry.ActorID + " conversant = " + entry.ConversantID);
//Link previous entry to this entry:
prevEntry.outgoingLinks.Add(new Link(conversationID, prevEntry.id, conversationID, entry.id));
prevEntry = entry;//shift prev entry to this entry
}
}
//add newConversation to database
database.AddConversation(newConversation);
Debug.Log("FountainConverterWindowTemplate : Conversation added to Database!");
}
//*** Uncomment this method and change it if you want to change the way the converter
//*** touches up the database after copying the source data. The base version of this
//*** method edits the START nodes of all conversations and sets their Sequence fields
//*** to None(). For example, if you know where the actors' portrait textures are,
//*** You can also call FindPortraitTextures(database, portraitFolder), which will
//*** assign the actors' portrait images based on their Textures fields.
//protected override void TouchUpDialogueDatabase(DialogueDatabase database) {
// base.TouchUpDialogueDatabase(database);
//}
//*** This is a subclass of AbstractConverterWindow. All methods in AbstractConverterWindow
//*** are overrideable, so you can really customize it however you want by overriding
//*** specific methods.
}
}
Re: .fountain support
Thanks for sharing your solution!