godot invalid get index '-1'(on base 'array') during A.P.I - arrays

I'm making an A.P.I. that displays dialogue, choices, sprites, audio etc. etc., i was able to do it after looking up this video on youtube but i keep getting this error invalid get '-1' (on base 'array') here's my code so you can see
extends Control
onready var bg_rect = get_node("Media_Sys/BG_Rect")
onready var char_sprite = get_node("Media_Sys/Sprite")
onready var text_box = get_node("Dialog_Sys/Dialog_Text")
onready var text_nam = get_node("Dialog_Sys/Label/Name")
onready var click = get_node("ClickManager")
var player = []
var lu = "Lu"
var ciel = "Ciel"
func _ready():
click.request_ready()
func write(char_name_str, text_str=null):
if text_str == null:
text_str = char_name_str
char_name_str = ""
player.push_front({"type": "dialogue","name": char_name_str, "text": text_str})
func write_component():
text_nam.clear()
text_box.clear()
text_nam.add_text(player[player.size() - 1]["name"])#the debugger says the problem is here
text_box.add_text(player[player.size() - 1]["text"])#as well as here if you remove the first line.
player.pop_back()
func _input(event):
if event is InputEventMouseButton:
write_component()
I have some people say that it's because i didn't define the size of the array, i have some people say that it's really weird that my code somehow set the player as an integer. I've check the array docs page for this and came up with nothing, I've changed some minor things in the code but it still churns out the same invalid error.
extends Node
onready var g = get_parent()
var lu = "Lu"
var ciel = "Ciel"
func _ready():
g.write(lu, "hello")
One thing to note is that it does display the dialogue and name, i just get the error immediantly afterwards.

You initialize the player variable to be an empty list. Then, in write_component you attempt to access this list with the calculated index [player.size() - 1]. The issue is that if player is empty, this ends up as [0-1] or [-1] as an index, which is not valid in GDScript. Your code assumes Write will be called first (and it may, some of the time) so that player is not empty by the time write_component is called.
The most immediate solution is to check if player.size() == 0 before attempting to access the last element.
func write_component():
text_nam.clear()
text_box.clear()
if player.size() > 0:
text_nam.add_text(player[player.size() - 1]["name"])
text_box.add_text(player[player.size() - 1]["text"])
player.pop_back()

Related

Godot: read and output JSON file as Textbox according to the input

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?
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 call go. 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 from go.
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 of Arrays, where each element of the nested Array is a Dictionary. 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 be dialog[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.

Swift: Changing value of global variable via function.

I'm trying to change the value of a global variable transferredData in the function didTransferData(_ data: Data?). My code looks like this:
var transferredData: [Double] = [0.0]
func didTransferData(_ data: Data?) {
var dataArray = [CUnsignedChar](repeating:0, count: (data?.count)!)
data?.copyBytes(to: &dataArray, count: dataArray.count)
let dataInt = dataArray.filter({ Int(String($0)) != nil }).map({ Int(String($0))! })
let dataDouble = dataInt.map { Double($0) }
print("Data double: \(dataDouble)")
transferredData = dataDouble
}
Printing transferredData inside of didTransferData returns the correct array, containing all values of dataDouble. But when I try to access it in this function later on
func returnData() -> [Double]{
print("return Data: \(transferredData)")
return transferredData
}
it returns [0.0]. I'm not sure if I'm missing something crucial here, but I thought that even if I change the value of a global variable in a function, the new value should be accessible for every other functions, too.
Thanks for any advice!!
You said that this code is inside a class. The variable transferredData is not a global, it is a member variable.
There is one of these per object of the class. I suspect that you are not using the same object to make these two calls.
You could make the member shared between all objects by declaring it static, but I think it would be better to leave as is and arrange to use the same object.
EDIT: based on comment
If you write the code
let centralInstance = CentralViewController()
You will get a new object with its own transferredData member initially set to [0.0]. You need to either
Get a reference to the same VC object that has the data
Store the data some where else in an object that you can get back (since the VC might be gone)
To hack something that works (not recommended, but to help understand)
You could move transferredData out of the class and make it an actual global variable. I would do this only to help you get past this issue and understand what's going on.
You could also make it static, which makes all of the VC's share the same instance of the transferredData
When you understand what is going on, you can try to do something a little better (at least move it to its own model object, and not in the VC at all). The next simplest thing is some kind of singleton to manage it -- this is just a glorified global variable but puts you more on the road to a more typical solution.
Where is the problem:
$ swift
Welcome to Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1). Type :help for assistance.
1> var data:[Double] = [0.0]
2.
3. class MuckWithData {
4.
5. func showData () -> [Double] {
6. print ("Data: \(data)")
7. return data
8. }
9.
10. func modifyData () {
11. data = [1.0]
12. }
13. }
data: [Double] = 1 value {
[0] = 0
}
14>
15> var mucked = MuckWithData()
mucked: MuckWithData = {}
16> mucked.showData()
Data: [0.0]
$R0: [Double] = 1 value {
[0] = 0
}
17> mucked.modifyData()
18> mucked.showData()
Data: [1.0]
$R1: [Double] = 1 value {
[0] = 1
}
Probably your computation for dataDouble is actually [0.0] and thus it appears that transferredData didn't change, but it did.

How to add to an array of objects, an object that is inherited? (Swift, XCode)

var allCards = [CCard]
var cardsMade = 0
^^ is outside of my view controller class as to make it global and accessible in all other view controller files.
in another view controller i have
#IBAction func saveInfo(sender: UIButton) {
let a = CCreature(n: cName!, e: cExpansion, ec: cEnergy!, tc: cTurnCount!,
ef: cEffect!, at: cStrength!, ht:cHealth!, sp: cSpeed!, sb: false)
allCards.append(a)
cardsMade++}
so when the saveInfo button is pressed, i put all the information the user has typed (which were in UITextFields, and then saved into the corresponding vairables cEnergy etc..) into a subclass of CCard called CCreature, with all of the information. After I create the instance of the class, i am trying to store in the array allCards. I am getting an error on the line:
var allCards = [CCard]
Expected member name or constructor call after type name
And in the line where a is appended to the array i am getting this error:
Cannot convert value of type 'CCreature' to expected argument type 'inout Array < CCard >'
I was also getting this error message before, when my program was compiling:
fatal error: Array index out of range
Any ideas?
As you have it [CCard] is a type declaration, you could use it as:
var allCards : [CCard]
but, that won't actually initialize allCards to be useful, alternatively, you could actually create the array and initialize it using:
var allCards = [CCard]()
Where you're using the default array constructor
It's not the entirety of your example, because you didn't include a lot of the pieces, but stripped down to show usage, would be:
var allCards = [CCard]()
func saveInfo() {
let a = CCreature()
allCards.append(a)
}

Is it possible to access a String inside an Array, inside an Array in swift?

Is it possible to access a string inside an array that is inside another array? - Swift
for instance:
var a = 1
var b = 2
var maleDogs = ["Fido","Thor"]
var femaleDogs = ["Linn","Eva"]
var dogs = [maleDogs,femaleDogs]
And then do something like
dogs[a][b]
In this instance, I wanted to get "Thor" as an output, but it calls an error. (Inside playground)
Your code is completely correct, but note that Swift array indices are zero-based, so accessing index 2 causes an "Array index out of range" exception.
You can see the error message if you open the "Assistant Editor" for the Playground file
(View -> Assistant Editor -> Show Assistant Editor).
What you probably wanted is
var a = 0
var b = 1
var maleDogs = ["Fido","Thor"]
var femaleDogs = ["Linn","Eva"]
var dogs = [maleDogs,femaleDogs]
dogs[a][b] // Thor

Found nil while unwrapping an Optional value (Music Library)

var songList = NSMutableArray()
var player = AVPlayer()
var isSelected = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var songs = MPMediaQuery() // also tried var songs = MPMediaQuery.songsQuery()
var localSongs: NSArray = songs.items
songList = NSMutableArray(array: localSongs)
tableView.reloadData()
var song: MPMediaItem = songList[0] as MPMediaItem // <<- Error here
var currentItem = AVPlayerItem(URL: song.valueForProperty(MPMediaItemPropertyAssetURL) as NSURL)
player.replaceCurrentItemWithPlayerItem(currentItem)
player.play()
var songTitle: NSString = song.valueForProperty(MPMediaItemPropertyTitle) as NSString
songName.text = songTitle
var time = Float(CMTimeGetSeconds(player.currentItem.duration))
sliderOutlet.value = time
}
I'm building a music player, but the app crasher when i run in the simulator and gives this error: index 0 beyond bounds for empty array, and says this when i try run on iPhone : {
MediaLibrary} Database validation succeeded
fatal error: unexpectedly found nil while unwrapping an Optional value
EDIT: Seems like it's not accessing the local music library on my iPhone and adding songs to the array.
You're accessing an element without testing its size. So songList[0] is out of bounds, hence the crash.
If your media query returns no items, then localSongs and songlist will both be arrays of size 0. When you then try to reference a song with songList[0] you will be accessing index 0 in an empty array. Hence the crash.
Seems more like a problem with expecting the simulator to have music in it. Since it doesn't, songList.count == 0, which means songList[0] should generate an exception. Seems like the expected and desired behavior to me.
Put a check on count using if condition. You are accessing index which can be nil. If accessed it will through runtime error.
Preferred approach would it be wrapped explicitly with ?

Resources