I have an animation player and I want to invoke a function anytime a keyframe gets added
like this:
extends AnimationPlayer
...
func key_added(track_indx,key_indx):
print("key added: ",key_indx," to track:",track_indx)
is something like this possible? is there any inbuilt function that I'm missing?
I did not have the time to fully experiment with it, but this should give you a direction you could look into.
first of you will need to alter the animation class to give you the needed event. To be able to use it in the editor keep in mind, that you need the tool flag:
extends Animation
tool
class_name ToolAnimation
signal key_added(track_idx, key_indx)
func track_insert_key ( track_idx : int, time : float, key, transition : float = 1) -> void:
.track_insert_key(track_idx, time, key, transition)
#need to find the key index of the key we added
var key_id = track_find_key ( track_idx, time, true )
emit_signal("key_added", track_idx, key_id)
All I do here is to overwrite the track_insert_key to search for the key Id after adding it and then emit a signal.
Now we need to tell our animation_player to add our newly created animations instead of normal animation classes so we change the script of the animation_player and override the add_animation function:
extends AnimationPlayer
tool
func add_animation(name : String, animation: Animation):
var tool_animation = ToolAnimation.new()
tool_animation.connect("key_added", self, "key_added")
.add_animation(name, tool_animation)
pass
func key_added(track_indx,key_indx):
print("key added: ",key_indx," to track:",track_indx)
Now everytime a key is added you should get into the key_added method.
This will only work for newly created animations, because existing ones will not have the toolanimation extensions. To add the feature to existing animations, you would need to deep copy them in your ready functions, for example.
Edit: As #cakelover pointed out in the comments: To alter existing animations, iterate over them and use their set_script() function.
Second thing I noticed, when trying it out was, that my key_added method was not called if a track is newly created and the first key is added simultaniously (basically pressing the key symbol on a property I did not track before). So thats something you should look into, if you also need the first key.
Related
I'm trying to disconnect a SceneTreeTimer to avoid a function being called on timeout
like this:
extends Node2D
onready var something = $Node2D
var timer
func abort():
timer.disconnect("timeout",something,"queue_free")
timer.emit_signal("timeout")
print("timer=>",timer)
func _ready():
timer=get_tree().create_timer(3)
timer.connect("timeout",something,"queue_free")
...
abort()
And while it does stop the timer from invoking the function
I'm still seeing the timer after aborting it, Output:
timer=>[SceneTreeTimer:1276]
Shouldn't it be something like this since it's time has elapsed?
timer=>[Deleted Object]
SceneTreeTimer unlike Node is a Reference.
If you have a look at the good old class diagram. You are going to see that some classes extend Node others Reference (including Resource) and other extend Object directly.
The classes that extend Reference are reference counted, they won't be deleted as long as you hold (a not WeakRef) reference to them.
While the classes that extend Node use explicit memory management, so they are deleted by calling free or queue_free on them.
Thus, drop the reference once you are no longer using the SceneTreeTimer:
func abort():
timer.disconnect("timeout",something,"queue_free")
timer.emit_signal("timeout")
timer = null
print("timer=>",timer) # null, duh
Godot will still emit the "timeout" signal, and when it does it releases its internal reference. We find this in "scene_tree.cpp" (source):
if (time_left < 0) {
E->get()->emit_signal("timeout");
timers.erase(E);
}
We can also experiment using a WeakRef to get a result similar to the one you expect. However, remember that since Godot is holding a reference internally the timer won't be deleted before its normal timeout.
extends Node2D
onready var something = $Node2D
var timer_ref:WeakRef
func abort():
var timer := timer_ref.get_ref() as SceneTreeTimer
timer.disconnect("timeout",something,"queue_free")
timer.emit_signal("timeout")
func _ready():
var timer := get_tree().create_timer(3)
# warning-ignore:return_value_discarded
timer.connect("timeout",something,"queue_free")
timer_ref = weakref(timer)
abort()
func _process(_delta: float) -> void:
print("timer=>", timer_ref.get_ref())
You should see it change from
timer=>[SceneTreeTimer:1234]
To
timer=>null
After 3 seconds, since that is the argument we gave to create_timer.
Trivia: Here you will get some number where I put "1234", that number is the instance id of the object. You can get it with get_instance_id and you can get the instance from the id with instance_from_id. We saw an example of instance_from_id in FauxBody2D.
You might also find it convenient to create an Autoload where you create, stop, and even pause your timers while keeping a API similar to create_timer, for example see Godot 4.0. how stop a auto call SceneTreeTimer?.
Addendum:
DON'T DO THIS
You might actually mess up with Godot. Since it is reference counted, and we can freely change the count, we can make it release the timer early:
var timer := timer_ref.get_ref() as SceneTreeTimer
timer.disconnect("timeout",something,"queue_free")
timer.emit_signal("timeout")
timer.unreference()
I tested this both on the debugger and on a release export, with Godot 3.5.1, and it didn't crash the game, not output any errors.
For clarity unreference is not the same as free, instead:
reference increases the count by one.
unreference decreases the count by one.
I'm calling unreference to cancel out the internal reference that Godot has.
We can confirm that the timer is being freed, either by using a weak reference or by looking at Godot's profiler. However, Godot has an internal list with references to the timers which are not being cleared properly.
I made this code to test out if the timer loop was being affected by the timers being released early by the above means.
extends Node2D
var can_fire := true
func _process(_delta: float) -> void:
var timer := get_tree().create_timer(60)
# warning-ignore:return_value_discarded
timer.unreference()
if can_fire:
can_fire = false
print("CREATED")
# warning-ignore:return_value_discarded
get_tree().create_timer(2).connect("timeout", self, "fire")
func fire() -> void:
print("FIRED")
can_fire = true
You might expect it to output FIRED each couple seconds. However, what I found out is that by using unreference on unrelated timers, we get the others to fire much faster.
My hypothesis is that Godot is keeping the dead reference in its internal list, then when another timer is allocated it takes the same memory. Then the timer counts faster because it appears multiple times in the list.
Removing unreference results in the expected behavior.
It does seem to exist still because calling "disconnect" function won't automatically free itself. Try doing timer.stop() instead.
I have a custom node with some export variables but when I remove the script and then reattach it the export variable values get lost,
so I used get_meta() to reassign the values like this:
tool
extends Node2D
export(int) var value=0 setget set_value
func set_value(new_val=null,initial=false):
print("Invoked set_value")
if(initial and self.has_meta("data")):
value=self.get_meta("data")
return
value=new_val
self.set_meta("data",value)
func _init():
set_value(null,true)
but there are 2 problems:
I have to write every setget function inside _init()
When I make changes to the script and save it the setget functions get invoked twice
Try this approach:
export var value:int = get_meta("value", 0) setget set_value
func set_value(new_val:int) -> void:
value = new_val
set_meta("value", value)
When the script is loaded it should execute the initialization which takes the value from metadata (or a default value if the metadata is not set). Godot, nor the core, will call the setter. Since the setter always sets the metadata, the value should not be lost.
When the script is modified Godot will set all the properties, which results in Godot calling all the setters (each setter will run only once). In this case Godot is setting the same value it had, so the value should not be lost.
This approach does not require _init.
I am coding a data class that is wanting to read a csv file to grab some information that is stored on the file. How ever, every way that I have tried to read the file will not work.
Here is what I have tried so far:
data class Bird(val birdNumIn: Int){
private var birdNum = birdNumIn
/**
* A function that searches the bird-data.csv file which is a list of birds to find the bird that was
* inputted into the class constructor and then add the values of the bird to the private variables.
*/
fun updateValues(){
var birdNumber = birdNum
var birdInfo: MutableList<String> = mutableListOf()
val minput = InputStreamReader(assets().open("bird-data.csv"), "UTF-8")
val reader = BufferedReader(minput)
}
How ever the assets().open() does not work. It returns an error of trying to open a file that does not exist, but the is in the assets folder, and the filename is spelt right.
I have tried many other methods on trying to read files, like using Java.io.File and using the path of the file.
If you would like to look at our whole project, please feel free to go to our github
What's the assets() function you're calling? This is just a bare data class, it has no connection to the Android environment it's running in, so unless you've injected an AssetManager instance (or a Context to pull it from) into your class, you can't access it.
You probably need to do this:
fun updateValues(context: Context){
val inputStream = context.assets.open("bird-data.csv")
val minput = InputStreamReader(inputStream, "UTF-8")
...
}
which requires your caller to have access to a Context.
Honestly from a quick look at your class, you might want to rework this. Instead of having a bunch of empty fields in your data class (which aren't part of the "data" by the way, only stuff in the constructor parameters is), and then having those updated later by the data class doing some IO, you might want to keep them as just basic stores of data, and create them when you read from your assets file.
So something like:
// all fixed values, initialised during construction
// Also you won't need to override toString now (unless you want to)
data class Bird(
val birdNum: Int
val nameOfBird: String
val birdFilePic: String
val birdFileSong: String
val alternativeName: String
val birdInfoFile: String
) { ... }
Then somewhere else
fun getBirbs(context: Context) {
// open CSV
// read the lines
val allBirds = lines.map {
// parse data for each bird, use it to construct a Bird object
}
}
or whatever you need to do, e.g. loading certain birds by ID.
That way your Bird class is just data and some functions/properties that work with it, it doesn't need a Context because it's not doing any I/O. Something else (that does have access to a Context) is responsible for loading your data and turning it into objects - deserialising it basically. And as soon as it's created, it's ready and initialised and immutable - you don't have to call update on it to get it actually initialised.
And if you ever wanted to do that a different way (e.g. loading a file from the internet) the data class wouldn't need to change, just the thing that does the loading. You could even have different loading classes! One that loads local data, one that fetches from the internet. The point is the separation of concerns, so it's possible to do this kind of thing because that functionality isn't baked into a class that's really about something else.
Up to you but just a thought! Especially if passing the context in like I suggested is a problem - that's a sign your design might need tweaking
I'm quite new to progamming. So here's my function to select a random image in my drawable folder.
fun generateimage(index:Int)
{var images=arrayOf(R.drawable.img1,....)
main_imageView.setImageResource(images[index])
This works as an image is shown randomly every time I start the application. But I would like to be able to know which image was selected. Preferably from retrieving the image name to string.
Possible duplicate of
How to get drawable name from imageView
However, a better implementation would be to have the value in a list of int and put your R.drawable items inside it. That was you can access which element is being shown.
You can do something like this:
fun main() {
val arrayImages = arrayOf("image1", "image2", "image3", "image4", "image5")
// in your case actual images.
val randomIndex = (0..arrayImages.size).random()
val randomImage = arrayImages[randomIndex]
println(randomImage) // in your case: main_imageView.setImageResource(randomImage)
}
Note that you need to assign the variable of randomImage so you can access it later on. I don't know what type "R.drawable.img1" is, but the object you set inside this array has to store the name of the file, so you can retrieve it that way.
I have a searchBar in a tableview, searching for results in a .csv file (coredata). The list is huge so the user has to scroll up many times to reach the search bar after the first search OR select the "A" letter in the Indexbar. Is there a way to add a button in the NavigationBar to show the searchBar when the user wants to get back to the beginning of the list? Thanks in advance.
searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
//assume a single section after a search
return (searchController.active) ? 1 : sectionTitles.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active {
return searchResults.count
} else {
// Return the number of rows in the section.
let wordKey = sectionTitles[section]
if let items = cockpitDict[wordKey] {
return items.count
}
return 0
}
}
I don't know how to send the user direct to a search bar, but there are always other ways to do things, what you can do is: Reload the storyboard and then it will show again the search bar in the initial states. Take a look on this interesting post: How do I perform an auto-segue in Xcode 6 using Swift? maybe you can go to another VC and return immediately with an simple animation so the user will not notice the tricker. :)
Yes, you can add a UIBarButtonItem to your navigation bar where the action you do will scroll the table back to the top.
Just replace the code from tableView.tableHeaderView = searchController.searchBar to navigationItem.titleView = searchController.searchBar which shows the searchBar on the NavigationBar.
But when you select the searchBar then it goes upward and might be not visible on the screen, so you can take the UISearchBar instead of UISearchController. For more information please look into these thread.
This is not exactly an answer to your question, but still a solution to your problem of quickly returning to the top of your table view. You could overwrite the table view's scrollsToTop: -property to return YES. By doing so, you will enable the user to jump to the top of the table by simply tapping the status bar. This is standard behavior in many stock apps, such as Contacts, Mail, Safari, and Photos.
Beware that only one scroll view / table view / collection view on the screen may return YES in order to achieve this behavior. In the case of multiple scroll views, you can alternatively implement the UIScrollViewDelegate -protocol's
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
callback and dynamically check, if the given scroll view should respond to the tap.
Although this is the Objective-C -way, I guess you should be able to transfer the concept over to Swift.
This approach has two advantages:
You will not clutter your navigation bar. Apples iOS Human Interface Guidelines explicitly suggest to
Avoid crowding a navigation bar with additional controls, even if it looks like there’s enough space. In general, a navigation bar should contain no more than the view’s current title, the back button, and one control that manages the view’s contents.
You will be consistent with the OS' standard behavior.