Open Gankra opened 12 years ago
I'm going to put this here as well as that forum post. Basically I'm thinking we take the base dialog text and shove it into the parser to get back the following, and then shove that into the renderer to show it on screen.
//I'll use this line as a template throughout the exercise
//@aradia_talk:#even-though-'time'-is-really-just-a-figure-of-speech-here~dialogimg_kankri1 ARADIA: anyway those of us who are concerned with the _preservation_ of /0xFF0000/rea/0x00FF00/li/0x0000FF/ty/0x/ have been looking forward to this day for some time
class DialogLine
{
array meta[];
//Contains metadata for this line.
// ["@"] (Actor) "aradia"
// [":"] (Hashtags) "#even-though-'time'-is-really-just-a-figure-of-speech-here"
// ["~"] (Background) "dialogimg_kankri1"
string text;
//Contains the base text of the line shorn of formatting strings
// "ARADIA: anyway those of us who are concerned with the preservation of reality have been looking forward to this day for some time"
2darray format[][]
//Contains the formatting for the text at any change
//Possible formatting
// Currently used
// _ = toggles underlining
// /0xHEXHEX/ = change future text to "HEXHEX" hexadecimal color code
// /0x/ = change future text to the ("@")actor's color code
// Possible (Might be cool)
// /S:N/ = changes the "typing" speed
// [0]["index"] 0
// [0]["font"] "SburbFont"
// [0]["color"] "ariadaRed"
// [0]["underline"] false
//
// [1]["index"] 54
// [1]["font"] "SburbFont"
// [1]["color"] "ariadaRed"
// [1]["underline"] true
//
// [2]["index"] 67
// [2]["font"] "SburbFont"
// [2]["color"] "ariadaRed"
// [2]["underline"] false
//
// [3]["index"] 71
// [3]["font"] "SburbFont"
// [3]["color"] "FF0000"
// [3]["underline"] false
//
// [4]["index"] 74
// [4]["font"] "SburbFont"
// [4]["color"] "00FF00"
// [4]["underline"] false
//
// [5]["index"] 76
// [5]["font"] "SburbFont"
// [5]["color"] "0000FF"
// [5]["underline"] false
//
// [6]["index"] 78
// [6]["font"] "SburbFont"
// [6]["color"] "ariadaRed"
// [6]["underline"] false
}
Pre-existing FormatRange object: Sburb.FormatRange = function(minIndex,maxIndex,type,extra){ this.minIndex = minIndex; this.maxIndex = maxIndex; this.type = type; this.extra = typeof extra == "string"?extra:""; }
extra would hold, e.g. the colour to use if the type was colour.
[QUOTE=Mme Sparklecakes XVII;6676646]What's wrong with the current FormatRange object, as far as styles go? More generalized, and you can add/ignore them whenever.[/QUOTE]
Basically what I'm seeing as the problem is that they are broken apart when at any time you just want to know what font you should be applying to the text. You have to look at each range to decide what style to apply to the current letter.
With my method adding a letter looks like.
var curFormat = 0;
for (var curLetter = 0;curLetter<text.chars.length;curLetter++)
{
if (formats.length>curFormat+1&&formats[curFormat+1].index<=curLetter)
curFormat++;
addLetter(text.chars[curLetter],formats[curFormat]);
}
While yours is slightly more complicated since you have to loop through the entire formatting array to find all possible applicable formats on each letter. Then you need to pull up your current formatting, and make changes. My way replaces all formatting only when something changes which is somewhat quicker.
var curFormat = new Object();
for (var curLetter = 0;curLetter<text.chars.length;curLetter++)
{
for(var i = 0;formats.length>=i;++;)
{
if (curLetter>=formats[i].minIndex&&curLetter<=formats[i].maxIndex)
curFormat[formats[i].type]=formats[i].extra;
}
addLetter(text.chars[curLetter],curFormat);
}
My way should be done as follows: Put all FormatRanges in a priority queue (sorted by minIndex), actually could just be a sorted list since this one is fairly static. When a FormatRange is entered, put it on another priority queue (sorted by maxIndex)
As a bonus, you don't have to walk through every character and ask "okay, now?", you just have to look at the heads of the two queues and ask "when does my current style change?", and just jump to that. That means I can also render large blocks of text at once (generally a whole visual line, if there's no formatting on it), instead of character by character. I don't even need to consider characters which have no unique style.
I'm doubtful this is how I actually implemented it, because I rushed it.
Also no maintenance of an extra array the size of the the string (though you could replace your array with a map, honestly).
Hmm yeah. I guess they would be about the same then. Honestly for the most part I just didn't see it way down there at the bottom until I had already gutted the rendering parts.
For lines that only contain a single type of formatting my array would only have a single entry. If you weren't in the "typing" style of placement you would just grab the substring from the current format entry until the next one, plop them in a span, and set the span's style to whatever the format was.
I guess the only major thing that makes me prefer my method is the fact that since I would be rendering with html something like "_Underline\0xaaaaaa\ColorChange_NotUnderlinedButStillColorChanged\0x\" could mess up tag nesting.
I'm probably over thinking it really.
My syntax explicitly supports unbalanced tags.
A compromise would be to return objects that specify the complete formatting at a point in the text, but only for the characters where the formatting actually changes. This way, the fact that my syntax supports unbalanced tags is obfuscated away, but it still supports my ability to just ask "when will it change next". Also no need to maintain two priority queues and a stack of active effects. All you need to do is ask "when will the style change next and to what", and render all the text up until that point with the current styles, without doing anything extra.
A compromise would be to return objects that specify the complete formatting at a point in the text, but only for the characters where the formatting actually changes. This way, the fact that my syntax supports unbalanced tags is obfuscated away, but it still supports my ability to just ask "when will it change next".
This is exactly what my thing does. Whenever the formatting changes it stores the current formatting, and the index the change occurs at.
Also no need to maintain two priority queues and a stack of active effects. All you need to do is ask "when will the style change next and to what", and render all the text up until that point with the current styles, without doing anything extra.
Not sure what you are saying here, but it is time for me to head to work so I'll look at it again in the morning.
I got the impression from your description that you stored it for every character.
The second half I was discussing there was that my method is just heavy on datastructures.
So is the plan to split the Dialogger up into different parts now? If so it might be worthwhile to create an issue for each part so multiple people can work on them simultaneously without stepping on toes.
That would seem to be the plan, but we're trying to settle how much each part should do, and how they should communicate.
2 parts for all of text handing. One parts loads from the file and generates a completely generic object to be passed to the other part for doing whatever it takes to display it.
Right, but does the parsing object handle parsing conversations into "windows", or is that still a job for the Dialoger class?
I think 'windows' should be handled by the parsing. After all, I think the goal would be that, for whatever language you use, you should get the same thing rendered regardless of if you use XML or CrazyAndrewFormat. (If we allow multiple rendering engines, than at least consistency when switching languages on the same renderer).
By "windows" do you mean the automatic splitting of huge lines? If so the Dialoger should handle that, as windows may not be consistent in size or wrap method, resulting in different presentation.
I think ideally the parser just gives you the lines, and the renderer figures out how it has to break it up and apply the styling. Changing the renderer will change how it looks in more ways then the internals. Changing the parser shouldn't matter.
Right so Dialoger needs to be broken up as well too, then. And the parser part of FontEngine needs an expanded api so Dialoger can still know what's going on.
By "windows" I mean the @'s delimiting a new sprite/window.
I'm probably the most qualified to know what the hell it's supposed to be doing at the end of the day. I might start tackling this refactor some time tomorrow.
Oh, then yes the parser makes the "windows", parses the styling, etc etc.
The renderer has to figure out what to do if the text is too long, figure out how to take the (now generic) style information and make the text actually styled (or ignore it if it doesn't know how).
The idea is that there should be no need for any communication between parser and renderer other than "Hey here are the lines", and that any parser can work with any renderer.
Maybe I'll make FontEngine be a third class that wraps FontParser and FontRenderer. That way nothing has to juggle two objects.
Why not take all the text and UI elements, and just put them in a div floating over the canvas? None of them update fast enough or do anything fancy enough to justify manually redrawing them every frame.
I guess I'm kind of starting to sound like a broken record on this issue, but just hear me out.
We have an xml file call it uiElements.xml or something.
<sburb>
<ui>
<elements>
<element name="sound" position="5,5,50,50" command="toggleVolume" background="volumeControl"/>
<element name="leftActor" position="whatever"/>
<element name="rightActor" position="whatever"/>
<element name="chat" position="whatever"/>
<element name="hashtags" position="whatever"/>
<element name="endConversation" position="whatever" command="skipDialog">
> End Conversation.
</element>
<element name="data" position="whatever"/ "command="showSaveLoad"/>
<element name="pauseScreen" position="0,0,100%,100%" command="resumePlaying">
<center>
PUASED<br/>CLICK MOUSE TO RESUME<br/>FROM BELOW
</center>
</element>
<element name="chooser" position="whatever">
<select id="chooserSelectBox" size=5>
<option>The options will be replaced when the chooser is shown.</option>
</select>
</element>
</elements>
<states>
<state name="walkaround" show="canvas,sound,data,controls" setfocus="canvas"/>
<state name="dialog" show="sound,leftActor,rightActor,chat,endConversation" setfocus="chat"/>
<state name="pause" show="pauseScreen"/>
<state name="choose" show="chooser" setfocus="chooser"/>
</states>
</ui>
</sburb>
So we have that right? Okay now for each of those elements we create a div which is nested inside the main SBURBgameDiv. We transform the internal stuff like having command="toggleVolume" in the sound control thing into javascript event declarations like onclick="Sburb.performAction('toggleVolume')" or whatever it would be. The rest of the attributes will be passed on to the div so you can set CSS stuff here or later in code.
Only the elements listed in the show section of the current state will have their div shown. The "canvas" is exempt from being actually hidden, but it stops responding to events when it isn't shown in the current state.
The setfocus thing determines what segment recieves keypress events. Every div should have a getfocus event so if they aren't currently supposed to be visible they will just put focus back to the current setfocus target of the state they are in after firing whatever their command is if they have one.
As for the dialogs. We can leave the definition alone really. People can add inline html tags to include things like images or url links, and they will be parsed properly if we are using the divs. We might want to wrap CDATA tags around them.
<action class='meenahTalk1' sprite='latula' command='talk' name='Talk to Meenah.'><args>
@latula_happytalk LATULA: yo yo, p4ystubz my grrrl!
@meenah_idle MEENAH: shit tules
@meenah_talk:#passin-out-names-like-cheap-cuttlefish MEENAH: i forgot how many rad nicknames you like to cycle through
@meenah_fish MEENAH: you know i always thought paycheck was kind of dope why dont you just stick with that
@latula_happier:#WOO LATULA: r1ght on! 1 l1k3 th4t on3 too, p4ych3ck 1t 1z. H1GH F1V3 GRL!!!
@meenah_angry:#OOW MEENAH: no lets not OWWWWWW
@! -SNIP-
<!-- -->
</args></action>
I think this is a good setup for a dialog data format.
function dialog(text)
{
this.lines = new Array();
text = text.split("\n");
for (var i = 0;i<text.length;i++)
{
var line = text[i].match(/(@[^:\s]+)(:[^\s]*)?\s([^\n]*)/);
alert(text[i] + line);
if (!line)break;
var metadata = line[1].match(/([@_~%][^@_~%]+)/g);
if (line[2])metadata.push(line[2]);
//Metadata types can be determined by the first character
// @ = actor
// _ = sprite
// ~ = textbox image
// % = background image
// : = extra info/hashtags (must be last)
// We might want to add a metadata type for not parsing for formatting
// so people don't have to do a lot of underscore escaping
this.lines.push({meta: metadata, text: line[3], formatting = null});
}
}
function parseFormatting(dialog)
{
for (var i = 0;i<dialog.lines.length;i++)
{
var formattinginfo = new Array();
//Parse dialog.lines[i].text to extract the formatting into formattinginfo
//Strip out formatting codes from dialog.lines[i].text, and remove escape characters
//Set dialog.lines[i].text to the unformatted text
dialog.lines[i].formatting = formattinginfo;
}
}
//Example usage
var text = "@latula_happytalk LATULA: yo yo, p4ystubz my grrrl!\n @meenah_idle MEENAH: shit tules\n @meenah_talk:#passin-out-names-like-cheap-cuttlefish MEENAH: i forgot how many rad nicknames you like to cycle through\n @meenah_fish MEENAH: you know i always thought paycheck was kind of dope why dont you just stick with that\n @latula_happier:#WOO LATULA: r1ght on! 1 l1k3 th4t on3 too, p4ych3ck 1t 1z. H1GH F1V3 GRL!!!";
var latMeeDia = new dialog(text);
parseFormatting(latMeeDia);
So much typing. Nearly bedtime. Hands sore.
Because I'm burnt out on trying to render text with someone else's generalized textbox framework. I didn't feel div gave me the level of flexibility I wanted, and if it did, it would be an uphill battle. This is why I am refactoring the API, so that you can replace the way text is handled if you want. Also pushing pixels right onto the canvas is supremely compatible. If someone has weird plugins, or drop the system into a weird layout, suddenly your text-box is fucked. Mine's like "the fuck is css"?
Also, pulling things out of the canvas makes shit waaaay more complicated. Right now everything is sprites with animations. It's nice and easy for the engine to understand. It doesn't give a shit what the Dialoger is doing.
The HUD elements are already completely replaceable in SburbML. Everything about them visually, and functionally, is completely described in the SburbML. Almost everything about the Dialoger is too.
Swapping out the text rendering engine is NOT a standard use case. Neither the end-user nor the developer should really give a rats ass (beyond the syntax used). The FontEngine is basically the last bastion of hard-coded non-configurability under the tyranny of SburbML. I am hoping to change that. But if you want to change the internals, it's an open source project buddy, go nuts. If you don't like how we do things, you're free to fork. If you think we'd like your way, feel free to submit a pull request.
Also it seems all of us either have school or jobs to deal with. So we can't dedicate our whole days to this.
But hey, if you find a way to make a living off of this let me know.
I'm actually sustained by yelling on the internet.
I may have a tendency to get really fucking excited about whatever I start to do for the first few days. Possibly. Could be.
I get you on the fork issue. Think I'll do that for a bit.
You might want to look up where I edited in that wall of text and code to the last section. It actually has nothing to do with divs whatsoever. Just drop in the formatting decoder of your choice, and it is good to go.
By the way the wiki is kind of screwed up because it still is pointing to Gankro\Jterniaborn links. I fixed the home page. All I had to do was open it and save it, and it was fine.
I have submitted a tentative specification in #70
Specification is over here now: https://github.com/Gankro/Sburb/blob/text-engine/TextEngine.js
I do not recommend integrating this work into master until there is working code to substitute the current structure.
I will also not be doing further work on this effort as it simply does not interest me. The current system, while sloppy, is adequate for my purposes at this juncture in time.
The only reason I want this change is so that the editor can spit out XML instead of the current dialog format. I think it would be easier to export that way.
Still extremely low priority.
I wouldn't really see the editor as the ideal place to write the dialog anyway. I guess I can see doing it, but room building is the primary goal for me on that. Being able to visually place elements would be a huge boon. Right now my workflow amounts to checking coordinates in GIMP and writing them down in a book.
Have you looked into using an svg editor like inkscape? Import the sprite, put it where you want, open the object properties, set the id to the name, set the label to the class, and put whatever goes inside it into the description.
Then you can just write an xslt to turn it into whatever you want.
I'll keep plodding along with the text engine. I'm about 70% done with the parser. I'm thinking of moving all batching into the renderer because the parser has no need to worry itself about how shit fits on screen.
If you don't mind too much I want to shift around a little about how the data is stowed and passed internally, and maybe in the markup.
By preference it would be something like
<dialog>
<box name='talk' background='dialogBox'>
<style name='speakerLeft' position='90,120' textDimensions='150,30,350,220' spritePos='-300,450;100,450'/>
<style name='speakerRight' position='20,120' textDimensions='30,30,350,220' spritePos='950,450;550,450'/>
<style name='hidden' position='-1000,120'/>
<style name='alert' position='56,120' textDimensions="30,30,450,220"/>
</box>
<actor name='meenah' frameInterval='6' x='-180' y='-524' font="color: #77003c;" actortags="meenah;">
<sprites>
<animation name='_idle' sheet='meenah_idle'/>
<animation name='_talk' sheet='meenah_talk' length='2'/>
<animation name='_angry' sheet='meenah_angry'/>
</sprites>
</actor>
<actor name='ariada' frameInterval='6' x='-245' y='-504' font='color: #a10000;' actortags='ariada;aa;'>
<animation name='_happy' sheet='aradia_happy' />
<animation name='_happytalk' sheet='aradia_happytalk' length='2' />
</actor>
</dialog>
Right now I'm testing it with an html page which is working it's way up to be a basic dialog editor in it's own right.
Oh while we are talking about workflows. What javascript ide are you guys using. I just started using aptana, but it is kind of clunky, and doesn't seem to want to push to git.
Huge improvement over wordpad and notepad though.
GEdit and command line git are all you need.
Well, all you need if you are too lame to write with a magnetized needle.
Also, breaking XML back-compatibility seems like a really, really bad idea...
Nah the markup is fine as it is. Leave it be for now. Also the renderer already handles the vast majority of batching. All format parsing must be in the parser, or you're just writing FontEngine again.
The parser already has no notion of how things "fit". It just specifies "I definitely want the text broken up AT LEAST here, but if you want more break points than go nuts".
So what are you calling batching?
Is non-batched
@Bluh_bluh Huge bitch
@Bluh_bluh Huge bitch
@Bluh_bluh Huge bitch
While batched would be
@Bluh_bluh Huge bitch\nHuge bitch\nHuge bitch
???
Non batched is just straight up text.
e.g. "Blha blah blah"
as opposed to
"@what_huh blha blha blah"
Chooser would use non-batched. Actually might just use no-format.
But if we are parsing why would we ever return the @_~: tags?
I strip all that off, and shove it in the various data orifices in TextParser or what have you. actors[]backgrounds[] ect
Not sure where to get boxes[] from
I need to stop working on it, but internet dude is still wireing everything so can't sleep yet. If I keep coding my incoherence will be come unreadable code.
none mode: Do not interpret any syntax. Return the string verbatim with default stylings applied. nobatch mode: Do not interpret batch-related syntax (in my case, that would be not looking for @'s and the prefix stuff), but still parse inline styles ('s, #00ff00's...) full mode: Interpret this as a batch of dialogs (in my case, do look for @'s and the prefix stuff), and also parse inline styles.
Given: "@karkat_idle what what what! @karkat_angry who?" none mode: return "@karkat_idle what what what! @karkat_angry who?" no_batch mode: return "@karkatidle what what what! @karkatangry who?" full mode: return "what what what!", "who?"
Minor corrections, no_batch should have had all the underscores parsed out.
boxes[] is the set of custom boxes to use on specific batches. If the default is to be used jut put null at that index. That stuff's described in the wiki page on dialogs.
Dialoger did a lot of stuff.
That stuff's described in the wiki page on dialogs.
Pretty sure it isn't.
Unless are boxes the ~tags?
Right in there: @Karkat_Angry~background%box:#sweetPost background is background box is box Karkat is actor Karkat_Angry is animation
Okay yeah my bad. Got that now.
https://github.com/ikkonoishi/Sburb/tree/textengine Grab the text engine test bed html file and TextEngine.js, and see if the batching and styling are right.
I need to do more work on the renderer, and then I will start digging into the dialoger to get it working with the rest of the framework.
The idea has come up of creating a generic API so that:
(a) The way conversations are displayed (b) The way text formatting is parsed (c) The way text is rendered
Are split up into three separate replaceable modules. Don't like how we render text? Swap it out. Don't like our syntax? Swap it out. Want conversations to just be alerts? Swap it out.
As it stands, (a) is handled by the Dialoger class for the most part. However it performs some of the roles of (b). For example it parses the text into "batches" (the @ delimiters).
(b) and (c) are handled by FontEngine, however for the most part, FontEngine is already broken up into essentially two parts, the parser and the renderer, via the parser handing the rendering code FormatRange objects, that describe what to render. However it ultimately is one object and shares a bunch of stuff.
The questions would then be: Is it worth it? Should the batch parsing be moved out of Dialoger, and/or Dialoger split into two objects (batch parser, and batch renderer)? Should FontEngine be broken into two classes, FontParser and FontRenderer? Also how would this be architected?
With not much thought it would make sense to me to do it something like: DialogParser has a DialogRenderer has a FontParser has a FontRenderer, and the core engine basically just dumps the text to DialogParser and calls it a day. Similarly the command prompts just go straight to FontParser to handle themselves, I guess.