I've imported several images into an actionScript 3 document. I've turned them all into symbols (movie clips) and given them instance names to reference from ActionScript.
Ok, so I'm putting the instances into an array so I can loop through them easily, but for some reason, whenever I'm putting in the instance name, I do a trace on the value in the array and it's giving me the symbol object back, rather than the instance object.
Basically trying to loop through the array to make each instance's visibility = false
Here's a sample:
var large_cap_extrusion_data: Array = new Array();
large_cap_extrusion_data[0] = large_cap_extrusion_menu_button;
large_cap_extrusion_data[1] = extrusion_border_large_cap
large_cap_extrusion_data[2] = "Large Cap";
large_cap_extrusion_data[3] = large_cap_main_menu_button;
var extrusion_data: Array = new Array();
extrusion_data[0] = large_cap_extrusion_data;
trace(extrusion_data[0][0]);
The traces gives:
[object large_cap_menu_button]
(the parent symbol)
rather than:
"large_cap_extrusion_menu_button"
I'd be very grateful if someone could tell me where I'm going wrong...
when you trace and object, by default it describes it type. What you want is the "name" property of the object.
Try this:
trace(extrusion_data[0][0].name);
that should give you the instance nema of the large_cap_menu_button rather than the class description. Either way, you have the right object I bet.
Related
I have been bashing my head about this and can't seem to figure it out. In another engine, I could make a struct, then make an array of that struct that I could then edit in the inspector. There seems to be no way of doing this that I can find in Godot.
I want to have a Resource that holds the starting Value and Type of multiple faces on a dice. For example, one side could have "2 Damage" while another has "Heal 3." (this is a first-time godot experiment inspired by Slice&Dice). Every tutorial I watch however makes it seem like, if I want to do so, I'd have to make a completely new Resource for each combination of Value and Type (Damage 1 Resource, Damage 2 Resource, etc.)
class_name DiceResource extends Resource
class DiceFaceData:
export var BaseValue = 0
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
func _init():
Type = 2
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
export(Array) var Faces = [DiceFaceData.new()]
I cannot get DiceFaceData to show up in the Inspector's array, or be on the list of object types for an array. Extending Object doesn't work. Extending Node means I have to instantiate it, which I don't want to do for an editor-only Resource.
I find it hard to imagine Godot doesn't have anything like this available. Is there anything I can load in the inspector as just data and not have to instantiate it? Another option is create two arrays, one with int and another Resource, but that seems inconvenient to fill out. Or should I just give up with Resources and make everything a Node attached to a Node attached to a Node? Thanks!
Godot version 3.4.3
EDIT: If you're someone coming from Unity or Unreal, what you're looking for is Resource. While compared to ScriptableObjects or DataAssets from those other engines, that's not the complete answer. You would think, because of the way those game engines handle it, you can only create custom SO or DA as assets in the filesystem/content browser, but you can also use Resources as instanced classes. Instead of creating a new Resource in the filesystem, you can use
export(Resource) var n = preload("res://MyResourceScript.gd").new()
In the inspector, you can choose from the list New MyResourceScript and create it. You won't be referencing an externally made Reference file, you'll be creating a custom one right there. And look at the below answer as well on good tips for using Resources in cool ways.
First of all, I want to say that I sympathize. Custom resources and the inspector do not work well. There is a solution on the work… However that does not mean that the only thing we can do is keep Waiting For Godot.
Observations on your code
About your code, I want to point out that DiceFaceData is not a resource type. You could write it like this:
class DiceFaceData extends Resource:
export var BaseValue = 0
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
func _init():
Type = 2
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
And… That solves nothing.
And, also, by the way, I remind you can put it on its own file:
class_name DiceFaceData
extends Resource:
export var BaseValue = 0
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
func _init():
Type = 2
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
And… That is not the solution either.
Something else I want to point out is that GDScript has types. See Static typing in GDScript. Use them. To illustrate…
This is a Variant with an ìnt value
var BaseValue = 0
This is an int, typed explicitly:
var BaseValue:int = 0
And this is an int, typed implicitly with type inference:
var BaseValue := 0
And if you were using types Godot would tell you that this is an error:
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
Because BaseValue is an int, and you setting a resource to it.
The Array of Resources problem
First of all, this is a Variant that happens to have an Array value, and it is exported as an Array:
export(Array) var Faces = []
Let us type it as an Array:
export(Array) var Faces := []
And sadly we cannot specify the type of the elements of the arrays in Godot 3.x (we need Godot 4.0 for that feature). However we can specify how we export it.
So, this is an Array exported as an Array of Resource:
export(Array, Resource) var Faces := []
See Exporting arrays.
Before you could not get your custom resource type to show up. And now you have the opposite problem: all the resource types show up. And this includes your custom resource type, if it in its own file.
You would guess that we need to specify the resource type we want:
export(Array, DiceFaceData) var Faces = []
And that would be correct if it were a build-in resource type. But it is a custom one. We are expecting this to be fixed in a future version. Meanwhile we will have to leave it with export(Array, Resource).
Mitigating the problem with an addon
To alleviate the pain of having all the possible resource types, consider using the addon "Improved resource picker" by MakovWait. You can find it on itch, or on github.
A proper solution
Anyway, we can do better. But you are going to need to make your script a tool script (you do that by putting tool on the top of the script, and it means that the code from the script can and will run on the editor).
We are going to define a setter with setget, and in there we are going to make sure the elements are of the correct type:
export(Array, Resource) var Faces = [] setget set_faces
func set_faces(new_value:Array) -> void:
Faces = []
for element in new_value:
element = element as DiceFaceData
if element == null:
element = DiceFaceData.new()
Faces.append(element)
Now, in the inspector panel when you increase the size of the array, Godot will insert a new null element to the array, which makes the setter we defined run, which will find that null and convert it to a new instance of your custom resource type, so you don't have to pick the resource type in the inspector panel at all.
A "hacky" solution
As you know, this does not work:
export(Array, DiceFaceData) var Faces = []
However, we can replace an export with _get_property_list. What happens is that Godot asks the object what properties it has to show up in the inspector panel. Godot does this by calling get_property_list And it will statically report the ones it found while parsing (the ones with export). However, Godot also defines a function _get_property_list where we can add more at run time.
See also Advanced exports.
Which begs the question, could we possibly make it work with _get_property_list? Kind of. The The code like this:
var Faces := []
func _get_property_list() -> Array:
return [
{
name = "Faces",
type = TYPE_ARRAY,
hint = 24,
hint_string = "17/17:DiceFaceData"
}
]
It will show up on the inspector as an array where the elements can only be of your custom resource type.
The issue is that it causes some error spam. Which you might or might not be OK with. It is your project, so it is up to you.
I know it looks like voodoo magic in part because we are using some undocumented stuff. If you want an explanation of that 24 and that 17/17: see How to add Array with hint and hint_string?.
About the sub-resources
Every tutorial I watch however makes it seem like, if I want to do so, I'd have to make a completely new Resource for each combination of Value and Type (Damage 1 Resource, Damage 2 Resource, etc.)
I'm not sure what you are getting to with "a completely new Resource", but yes. A resource is an instance of a resource type. And each of those combination would be a resource.
Perhaps "Damage", "Heal" and so on are resources too. Let us see… I'm guessing that is what the Type is for:
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
Godot would be showing all the resource types it is aware of, which is a pain. I'm going to suggest a different approach than those above for this: Make an String enumeration.
export(String, "Damage", "Heal") var Type:String
That will show up as a drop down list on the inspector panel, with the options you specified.
Why String and not int? Ah, because you can then do this if you so desire:
var type_resource := load("Resources/DiceFaceTypes/" + Type + ".tres")
I'm assuming that those have the code that actually does damage or heal or whatever.
Alright, but when you add a new type of dice face, you would have to come here and update it… Or do you? With the power of tool scripts we are going to update that list to reflect the files that actually exist!
First of all, we are not going to use export, so it will be just:
var Type:String
And now we can export it from _get_property_list. There we can query the files. But before we do that, so we are clear what we have to do, the following code is equivalent to the export we had before:
func _get_property_list() -> Array:
return [
{
name = "Type",
type = TYPE_STRING,
hint = PROPERTY_HINT_ENUM,
hint_string = "Damage,Heal"
}
]
No undocumented stuff here.
Our task is to build that hint_string with the names of the files. And that looks like this:
const path := "res://"
func _get_property_list() -> Array:
var hint_string := ""
var directory := Directory.new()
if OK != directory.open(path) or OK != directory.list_dir_begin(true):
push_error("Unable to read path: " + path)
return []
var file_name := directory.get_next()
while file_name != "":
if not directory.current_is_dir() and file_name.get_extension() == "tres":
if hint_string != "":
hint_string += ","
hint_string += file_name
file_name = directory.get_next()
directory.list_dir_end()
return [
{
name = "Type",
type = TYPE_STRING,
hint = PROPERTY_HINT_ENUM,
hint_string = hint_string
}
]
Ah, yes, set the path constant to the path of the folder where the resources types you have are.
Addendum post edit
I want to elaborate on this example:
export(Resource) var n = preload("res://MyResourceScript.gd").new()
Here we are exporting a variable n as a Resource, which will appear in the Inspector panel. The variable is currently a Variant, we could type it Resource:
export(Resource) var n:Resource = preload("res://MyResourceScript.gd").new()
And then we don't need to tell Godot to export it as a Resource, because it is a Resource:
export var n:Resource = preload("res://MyResourceScript.gd").new()
Something else we can do is preload into a const. To be clear, preloads are resolved at parse time. Like this:
const MyResourceScript := preload("res://MyResourceScript.gd")
export var n:Resource = MyResourceScript.new()
This way, if you need to use the same script in multiple places, you don't need to repeat the path.
However, you might not need the path at all. If in the script res://MyResourceScript.gd we add a class_name (at the top of the script):
class_name MyResourceScript
Then we don't need to use preload at all. That name will be available everywhere, and you can just use it:
export var n:Resource = MyResourceScript.new()
Where is that resource stored?
Potentially nowhere. Above we are telling Godot to create a new one when our it initializes our object (e.g. which could be a Node, or another Resource - because, yes, Resources can have Resources) and those would only exist in RAM.
However, if you modify the Resource from the Inspector panel, Godot needs to store those changes somewhere. Now, if you are editing a Node, by default they go to the scene file. If you are editing another Resource, then it goes to wherever that Resource is stored. To be clear, scenes are resources too (PackedScene). And, yes, that means a file can have multiple Resources (A main resurce and sub-resources). You could also tell Godot to store the Resource in its own file from the Inspector panel. The advantage of giving a file to a Resource is in reusing it in multiple places (multiple scenes, for example).
So, a Resource could be stored in a file, or not stored at all. And a resource file could have a Resource alone, or it could also have sub-resources as well.
I'll take a moment to remind you that scenes can have instances of other scenes inside. So, there is no line between scenes and the so called "prefabs" in Godot.
… Did you know?
You can save the resources you created in runtime, using ResourceSaver. Which could be a way to save player progress, for example. You can also load them using load or ResourceLoader (in fact, load is a shorthand for ResourceLoader.load).
In fact, if you can use load or preload on something, it is a Resource. Wait a minute, we did this above:
const MyResourceScript := preload("res://MyResourceScript.gd")
Yep. The Script is a Resource. And yes, you can create that kind of resources in runtime too. Create a GDScript object (GDScript.new()), set its source_code, and reload it. Then you can attach it to an Object (e.g. a Node) with set_script. You can now start thinking of meta-programming, or modding support.
I have solved the issue now, thanks for your help. I shouldn't have tried to save arrays with UITextViews, but I should have saved their text as strings instead. Here was the original question:
I have tried a lot, and googled a lot, but I can't solve this problem on my own. Whenever I try to save an array in userdefaults, it just is not working. I get the following error:
Thread 1: "Attempt to insert non-property list object (\n "<UITextView: 0x14001f800; frame = (0 0; 355 180); text = 'D'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x600003f01d10>; layer = <CALayer: 0x6000031c83e0>; contentOffset: {0, 0}; contentSize: {355, 30}; adjustedContentInset: {0, 0, 0, 0}>"\n) for key content"
I don't know what a non-property list object is. And I do not know how to solve the problem. Below is the lines of code that do not work.
var contentList: [Any] = []
let cl = defaults.array(forKey: "content")!
if cl.count != 0{
contentList += cl
}
contentList.append(label)
defaults.setValue(contentList, forKey: "content")
If I take out the last line of code by turning it into a comment everything runs just fine. How should I replace that line of code? I essentially want to save an array of UITextViews and make it larger every time I call a fucntion (this code is part of a larger function). The reason why I have created another two lists (cl and contentList) is that it helps me with a problem down the line. What I cannot understand however, is why the last line of code doesn't work. If anyone has any ideas, please help me, it would be much appreciated.
Use only String as stated in comments :
var contentList: [String] = []
let cl = defaults.array(forKey: "content")!
if cl.count != 0{
contentList += cl
}
If lbText = label.text {
contentList.append(lbText)
defaults.setValue(contentList, forKey: "content")
}
You can only store a very limited list of data types into UserDefaults, commonly referred to as "property list objects" (Since property list (or plist) files will only store the same data types.
To quote the Xcode docs on UserDefaults, in the section titled "Storing Default Objects":
A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary [or Data, String, NSNumber, Date, Array, or Dictionary types in Swift.] If you want to store any other type of object, you should typically archive it to create an instance of Data.
(I added the equivalent Swift types to the above quote in square brackets, since it looks like Apple hasn't updated it for Swift.)
That's worded a little awkwardly. The idea is that you can only store data of the types listed. Because the Array and Dictionary types are "container" types, you can store any combination of arrays and dictionaries that contain combinations of any of the above types. For example, you can store an array that contains a dictionary, 3 dates, 2 floats, a Double, some Data, and 2 arrays, and those dictionaries and arrays can contain other dictionaries and/or arrays.)
It is almost always wrong to archive UIView objects like UITextViews. You should save the text properties of your text views instead.
If you want to manage a vertical stack of UITextView objects, I suggest adding a vertical stack view to your user interface, and then writing code that adds or removes UITextView subviews to your stack view. You should be able to find plenty of examples of adding and removing objects from stack views online. (It's really easy.)
If you want to manage a scrolling list of feeds of arbitrary length, you might want to use a table view or collection view instead. Those require that you set up a data model and implement a "data source". That takes a little practice to get right, but is very powerful.
I am currently in a bit of a bind.
struct sectionWithDatesAsName {
var sectionName : String
var sectionObjects : [SoloTransactionModel]!
init(uniqueSectionName: String?, sectionObject: [SoloTransactionModel]?) {
sectionName = uniqueSectionName ?? "nil"
if let section = sectionObject {
sectionObjects = section.reversed()
}
}
}
I currently have an array of sectionWithDatesAsName. And I can work with it, display in the tableView among other things.
The bind comes up when I want to check some information in the sectionObject before displaying it on the tableView.
I want to check the type of the sectionObject which is saved in the object itself.
How do I check the information in the sectionObject without slowing down the app? Or have a horrible time complexity calculated?
(Note: I can't change the format of the struct has this has already been used by a whole lot of other processes)
Write a function in your sectionWithDatesAsName with the signature filteredSections(type: sectionType) -> sectionWithDatesAsName
(If you don't have the ability to edit the definition of sectionWithDatesAsName you can create an extension that adds the above function)
If the sectionWithDatesAsName is defined elsewhere, define this function in an extension.
When you call it, build a new sectionWithDatesAsName object by filtering the arrays to match the specified type.
Use the resulting filtered sectionWithDatesAsName object as the data model for your table view. It will be built once and used for the lifetime of the tableView, so you will pay an O(n) time cost to filter it once when you create it.
I have a custom class "Book" and I am trying to save two books in an array in the following way:
class ViewController: UIViewController, UITableViewDataSource,
{
testBook.setBookTitle(title: "Harry Potter")
testBook.setbookPage(page: 12)
myBookCollection.append(testBook)
// testBook = Book()
testBook.setBookTitle(title: "testing")
testBook.setbookPage(page: 66)
myBookCollection.append(testBook)
for books in myBookCollection
{
print(books.getBookTitle())
}
}
If I run the above I my books in the array are stored as ["testing","testing"] as opposed to ["Harry Potter","testing"].
However If I uncomment testbook = Book() it works fine. Can someone please explain why this is the case ?
thanks,
Reference vs. value type.
If you define Book as a class, when you add it to array, it does not copy our object. testBook and myBookCollection[0] are holding the same object. When you change either one, the other will be affected. If you add testbook = Book(), then testBook is now pointing to a different object in memory and what you do it it has no impact on myBookCollection[0].
You can change Book from a class (reference type) to a struct (value type). In that case, when you add it to an array, it will add a copy to the array so what you do with testBook after doesn't affect it.
Here's a video from WWDC that demonstrates the exact problem you had: Building Better App with Value Types in Swift
the array doesn’t hold a copy of your Book instance , it simply hold the pointer to the object(book). When you change the title and page you are not changing the array contents, you are changing the object which testbook is pointing to.
however if you uncomment the line testBook = Book() . You create a new instance of Book class and you set the pointer testbook to the newly created object and all the changes from this point is done on the new object and that’s why it works as it should.
And all of this is true if your are using a class in your array , if you use struct that’s another story
I have this array where I set the keys on the creation. Now in some point in my view I load some more information based on ids (the keys).
var colors = [
"37027" : UIColor(red:150/255, green:57/255, blue:103/255, alpha:1),
"12183" : UIColor(red:234/255, green:234/255, blue:55/255, alpha:1),
"44146" : UIColor(red:244/255, green:204/255, blue:204/255, alpha:1)
]
I want to add more colors to this array dynamically. How can I insert new items in the array setting the key? Something like
colors["25252"] = UIColor(red:244/255, green:204/255, blue:204/255, alpha:1)
The line above doesn't work, it is just to illustrate what I need.
Thanks for any help
Update: the code above is an example. Below the real code:
var placedBeacons : [BeaconStruct] = []
BeaconModel.fetchBeaconsFromSqlite(completionHandler: {
beacons in
for item in beacons{
self.placedBeacons["\(item.major):\(item.minor)"] = item
}
})
Error: Cannot subscript a value of type '[BeaconStruct]' with an index of type String
To match the key subscripting
self.placedBeacons["\(item.major):\(item.minor)"] = item
you have to declare placedBeacons as dictionary rather than an array
var placedBeacons = [String:BeaconStruct]()
It requires that item is of type BeaconStruct
The code you wrote, it should work. I have used such kind of code and was able to implement successfully. I just tested your code in my end and it's working for me. I declared colors variable globally in my class file and in view did load method added the second code to add another item in my colors array. After printing it out. My output shows full list of array with 4 items and the number of array count return 4 as well.
Please let me know, more details of your scenario so i can help you to figure it out the issue. but looks like it should work.