I want to be able to call specific phrases by their assigned numbers in this JSON file, so when i call go(1) for example, it displays only the text that has 'Num' set as 1.
my JSON file:
[
{"Num":0, "Name":"Afely", "Emotion":"Neutral", "Text":"TEST1"},
{"Num":0, "Name":"Afely", "Emotion":"Neutral", "Text":"TEST2"},
{"Num":0, "Name":"Afely", "Emotion":"Neutral", "Text":"TEST3"},
{"Num":1, "Name":"Afely", "Emotion":"Neutral", "Text":"2TEST1"}
]
The Textbox code:
extends ColorRect
export var dialogPath = ""
export(float) var textSpeed = 0.005
var dialog
var phraseNum = 0
var finished = false
func go(phraseNum):
$Timer.wait_time = textSpeed
dialog = getDialog()
assert(dialog, "Dialog not found")
nextPhrase()
var f = File.new()
var img = dialog[phraseNum]["Emotion"] ".png"
$Portrait.texture = load(img)
func _unhandled_input(event):
if event is InputEventKey:
if event.pressed and event.scancode == KEY_Q:
if finished:
$NEXT.play()
nextPhrase()
else:
$Text.visible_characters = len($Text.text)
func getDialog() -> Array:
var f = File.new()
assert(f.file_exists(dialogPath), "File path does not exist")
f.open(dialogPath, File.READ)
var json = f.get_as_text()
var output = parse_json(json)
if typeof(output) == TYPE_ARRAY:
return output
else:
return []
func nextPhrase() -> void:
if phraseNum >= len(dialog):
queue_free()
return
finished = false
$Name.bbcode_text = dialog[phraseNum]["Name"]
$Text.bbcode_text = dialog[phraseNum]["Text"]
$Text.visible_characters = 0
while $Text.visible_characters < len($Text.text):
$Text.visible_characters = 1
$TEXT_AUDIO.play()
$Timer.start()
yield($Timer, "timeout")
finished = true
phraseNum = 1
return
how I call it:
$TextBox.show()
$TextBox.go(1)
and lastly, the tutorial I followed for it: https://www.youtube.com/watch?v=GzPvN5wsp7Y
How would I approach to do this?
CodePudding user response:
What you asked
What you are asking requires quite some extra work. Let us start with the JSON file:
[
{"Num":0, "Name":"Afely", "Emotion":"Neutral", "Text":"TEST1"},
{"Num":0, "Name":"Afely", "Emotion":"Neutral", "Text":"TEST2"},
{"Num":0, "Name":"Afely", "Emotion":"Neutral", "Text":"TEST3"},
{"Num":1, "Name":"Afely", "Emotion":"Neutral", "Text":"2TEST1"}
]
This will parse as an Array
(everything between [
and ]
), where each element is a Dictionary
(which are the ones between {
and }
). This means that you are going to need to iterate over the Array
, check every Num
.
Before we do that, we need to acknowledge that we would be using the name phraseNum
to representing two things:
- The index in the
dialog
Array
- The desired value of
Num
We are in this situation because of the source material you are using. They have phraseNum
as a parameter (here: func go(phraseNum)
) that hides a phraseNum
field (here var phraseNum = 0
).
This hinders communication. If I tell you do this with phraseNum
, which one is it? That is bound to give us trouble.
I'll be rewriting go
so it takes a Num
instead of the index in the dialog
Array
, so I'll keep phraseNum
for the index in the dialog
Array
, and have a different name for the parameter of go
.
Let us begin rewriting go
:
func go(num):
pass
Now, let us get all the dialogues:
func go(num):
dialog = getDialog()
We are going to iterate over them. Since I will want an index for phraseNum
, I will iterate using index:
func go(num):
dialog = getDialog()
for index in dialog.size():
pass
And we need to check if Num
matches. If it does, we got our index:
func go(num):
dialog = getDialog()
for index in dialog.size():
if num == dialog[index]["Num"]:
phraseNum = index
break
We need to handle the case where we didn't find it. Now, hmm… The source material only has an assert
, I'll keep that approach. So we need a way to know the code didn't find it…
func go(num):
var found := false
dialog = getDialog()
for index in dialog.size():
if num == dialog[index]["Num"]:
phraseNum = index
found = true
break
assert(found, "Dialog not found")
Next the you call nextPhrase()
, sure:
func go(num):
var found := false
dialog = getDialog()
for index in dialog.size():
if num == dialog[index]["Num"]:
phraseNum = index
found = true
break
assert(found, "Dialog not found")
nextPhrase()
And then a not used var f = File.new()
, I'll not add that.
And you set the portrait texture. Sure:
func go(num):
var found := false
dialog = getDialog()
for index in dialog.size():
if num == dialog[index]["Num"]:
phraseNum = index
found = true
break
assert(found, "Dialog not found")
nextPhrase()
var img = dialog[phraseNum]["Emotion"] ".png"
$Portrait.texture = load(img)
And I had skipped over the timer thing, I'll insert it now:
func go(num):
var found := false
dialog = getDialog()
for index in dialog.size():
if num == dialog[index]["Num"]:
phraseNum = index
found = true
break
assert(found, "Dialog not found")
$Timer.wait_time = textSpeed
nextPhrase()
var img = dialog[phraseNum]["Emotion"] ".png"
$Portrait.texture = load(img)
Something else
Now, you said you want only the phrases with the given Num
. This is open to interpretation.
To be clear, the code above will have the dialog start at the first instance of the Num
you ask for. But it will not end - nor skip - when it finds a different Num
. I don't know if you want that or not.
And we have a couple ways to do this. We could remember what was the num
and check against it in nextPhrase
. I really don't want to do that. So, I will give you an alternative approach: let us make a dialog array that only contains the elements we want.
It looks like this:
func go(num):
var every_dialog = getDialog()
dialog = []
for candidate in every_dialog:
if num == candidate["Num"]:
dialog.append(candidate)
assert(dialog, "Dialog not found")
phraseNum = 0
$Timer.wait_time = textSpeed
nextPhrase()
var img = dialog[phraseNum]["Emotion"] ".png"
$Portrait.texture = load(img)
Please notice that in this example we are not reading getDialog()
to dialog
. Instead we are building a dialog
array that only contains the entries we want. And we do that by iterating the result of getDialog()
(we add to the array with append
).
This is a subtle change in the meaning of dialog
, because it would no longer represent every entry form the JSON file. Instead it only represent the entries that will be displayed. Before those two things were the same, but they aren't anymore with this change.
What you didn't ask
The function
getDialog
reads from the JSON file. And you would be doing that every time you callgo
. You could instead do it once in_ready
.It is important that you understand what each variable represents, and also where you read and write them. Above I mention that the there is a subtle meaning of
dialog
do to the alternative change. You need to consider that to make this change.I strongly believe that
nextPhrase
should handle the timer, and the portrait. There should be no need to set those fromgo
.I want you to consider this alternative JSON file structure:
[ [ {"Name":"Afely", "Emotion":"Neutral", "Text":"TEST1"}, {"Name":"Afely", "Emotion":"Neutral", "Text":"TEST2"}, {"Name":"Afely", "Emotion":"Neutral", "Text":"TEST3"}, ], [ {"Name":"Afely", "Emotion":"Neutral", "Text":"2TEST1"} ] ]
Then you would get an
Array
ofArray
s, where each element of the nestedArray
is aDictionary
. Then you can "simply" get the nested array by index, instead of having to iterate over every element.The structure you get resembles the structure of the JSON file. Which also means you will have to change how you use it. For example, instead of
dialog[phraseNum]["Text"]
it could bedialog[num][phraseNum]["Text"]
.Now consider the source material. In this case we have a node that has both the responsibility of parsing JSON and the responsibility of displaying character dialogues. It would be easier to tinker with this code if these were separated from each other. Presumably the intention of the author is that you would have different JSON files for the different dialogues, so you would switch JSON file when necessary (which also explain why they read the JSON file each time).
But that is not what you asked.