I'm trying to program a textbox that I can write in using the keyboard; but the backspace key doesn't erase text, nothing happens when I press it.
extends Node2D
onready var Text = $Panel/RichTextLabel
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
func _unhandled_input(event):
if event is InputEventKey:
if event.pressed == true:
match event.scancode:
KEY_A:
Text.append_bbcode("a")
KEY_BACKSPACE:
if Text.get_total_character_count() > 0:
print("0")
Text.visible_characters -= 1
pass
Godot already has a built-in TextEdit node that does exactly what you are trying to do.
A continuation of the previous question
How exactly do we detect collision from body_set_force_integration_callback?
For context, we have a body RID:
var _body:RID
And we set a callback with body_set_force_integration_callback:
Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved", 0)
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
pass
Before going further, I want to point out that the the final parameter of body_set_force_integration_callback is what we get in _user_data. But, if I set it to null Godot will not pass two arguments to the call, in which case I should define _body_moved with only the state parameter.
Godot will be calling our _body_moved every physics frame if the state of the body is active (i.e. not sleeping).
Note: We need to call body_set_max_contacts_reported and set how many contacts we want reported, for example:
Physics2DServer.body_set_max_contacts_reported(_body, 32)
Now, in the Physics2DDirectBodyState we get contacts, and we can ask what a few things about each contact, including the body:
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
for index in state.get_contact_count():
var body:RID = state.get_contact_collider(index)
var instance:Object = state.get_contact_collider_object(index)
If it is the body of a PhysicsBody2D, then instance will have it.
If we want to implement body_entered and body_exited, we need to keep track of the bodies. I'll keep a dictionary of the instances (i.e. PhysicsBody2D) and I'll use it to report get_colliding_bodies too.
Then we need to keep track of shapes for body_shape_entered and body_shape_exited, not only bodies. We can find them out like this:
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
for index in state.get_contact_count():
var body:RID = state.get_contact_collider(index)
var instance:Object = state.get_contact_collider_object(index)
var body_shape_index:int = state.get_contact_collider_shape(index)
var local_shape_index:int = state.get_contact_local_shape(index)
Notice they are not RID. They are the position of the shape in the body (so 0 is the first shape of the body, 1 is the second one, and so on). This means that we cannot keep track of the shapes separately from the bodies, because they shape indexes do not make sense without knowing to what body they belong. That means we cannot simple use two arrays of bodies like we did before.
Also, if we only have one shape - which was the case of the prior answer - we could ignore local_shape_index because it is always 0. In which case I only need a Dictionary indexed by body:RID of body_shape_index:int.
If I don't take that assumption, I struggle to decide the data structure.
I could use a Dictionary indexed by body:RID of Dictionary indexed by body_shape_index:int of local_shape_index:int, in which case I want helper methods to deal with it, which pushes me to make a class for it.
I could use a Dictionary indexed by body:RID of tuples of body_shape_index:int and local_shape_index:int. Except there is no tuple type, so I would cheat and use Vector2.
You know what? I'll cheat and use the Vector2.
signal body_entered(body)
signal body_exited(body)
signal body_shape_entered(body_rid, body, body_shape_index, local_shape_index)
signal body_shape_exited(body_rid, body, body_shape_index, local_shape_index)
var colliding_instances:Dictionary = {}
var colliding_shapes:Dictionary = {}
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
var old_colliding_shapes:Dictionary = colliding_shapes
var new_colliding_shapes:Dictionary = {}
colliding_shapes = {}
var instances:Dictionary = {}
for index in state.get_contact_count():
# get contact information
var body:RID = state.get_contact_collider(index)
var instance:Object = state.get_contact_collider_object(index)
var body_shape_index:int = state.get_contact_collider_shape(index)
var local_shape_index:int = state.get_contact_local_shape(index)
var vector := Vector2(body_shape_index, local_shape_index)
# add to instances
instances[body] = instance
# add to colliding_shapes
if not colliding_shapes.had(body):
colliding_shapes[body] = [vector]
else:
colliding_shapes[body].append(vector)
# remove from old_colliding_shapes or add to new_colliding_shapes
# there is room for optimization here
if (
old_colliding_shapes.has(body)
and old_colliding_shapes[body].has(vector)
):
old_colliding_shapes[body].erase(vector)
if old_colliding_shapes[body].size() == 0:
old_colliding_shapes.erase(body)
else:
if not new_colliding_shapes.had(body):
new_colliding_shapes[body] = [vector]
else:
new_colliding_shapes[body].append(vector)
for body in old_colliding_shapes.keys():
# get instance from old dictionary
var instance:Object = colliding_instances[body]
# emit
if not instances.has(body):
emit_signal("body_exited", body)
for vector in old_colliding_shapes[body]:
emit_signal(
"body_shape_exited",
body,
instance,
vector.x,
vector.y
)
for body in new_colliding_shapes.keys():
# get instance from new dictionary
var instance:Object = instances[body]
# emit
for vector in old_colliding_shapes[body]:
emit_signal(
"body_shape_entered",
body,
colliders[body],
vector.x,
vector.y
)
if not colliding_instances.has(body):
emit_signal("body_entered", body)
# swap instance dictionaries
colliding_instances = instances
func get_colliding_bodies() -> Array:
return colliding_instances.values()
The variable old_colliding_shapes begins with the shapes already known to be colliding, and in in the iteration we are removing each one we see. So at the end, it has the shapes that were colliding but no longer are.
The variable new_colliding_bodies begins empty, and in the iteration we are adding each shape we didn't remove from old_colliding_shapes, so at the end it has the shapes that are colliding that we didn't know about before.
Notice that old_colliding_shapes and new_colliding_bodies are mutually exclusive. If a body is in one it is not in the other because we only add the body to new_colliding_bodies when it is not in old_colliding_shapes. But since they have shapes and not bodies a body can appear in both. This why I need an extra check to emit "body_exited" and "body_entered".
I'm trying to animate an expandable staff by travel between nodes in AnimationTree Like this:
...
tool
export(String,"small", "mid", "full") var staff_mode = "small" setget set_staff_mode;
func set_staff_mode(new_val):
var ani_state;
if(self.has_node("/path/to/AnimationTree")):
ani_state=self.get_node("/path/to/AnimationTree")["parameters/playback"];
ani_state.start(staff_mode);
print(ani_state.is_playing());
ani_state.travel(new_val);
ani_state.stop();
staff_mode=new_val;
I haven't applied autoplay to small because I don't want a looping animation on the staff
(it only expands or compresses, no idle animation)
but for some reason it gives the error:
Can't travel to 'full' if state machine is not playing. Maybe you
need to enable Autoplay on Load for one of the nodes in your state
machine or call .start() first?
Edit:
I forgot to mention but I don't have any idle animation for my staff so I need to stop the animation after the transition is complete.
small, mid & full
(all of them are static modes of the staff depending upon the game how much the staff should extend)
are all 0.1sec single frame animations and I applied Xfade Time of 0.2 secs to show the transition
I simply need to transition from an existing animation state to another and then stop
New Answer
Apparently the solution on the old answer does not work for very short animations. And the workarounds begin to seem to much overhead for my taste. So, as alternative, let us get rid of the AnimationTree and work directly with AnimationPlayer. To do this, we will:
Increase the animation duration to be long enough for the "cross fade" time (e.g. 0.2 seconds).
Put the "cross fade" time in "Cross-Animation Blend Times". For each animation, select it on the Animation panel and then select "Edit Transition…" from the animation menu, that opens the "Cross-Animation Blend Times" where you can specify the transition time to the other animations (e.g. 0 to itself, 0.1 to "adjacent" animation, and so on).
Now we can simply ask the AnimationPlayer to play, something like this:
tool
extends Node2D
enum Modes {full, mid, small}
export(Modes) var staff_mode setget set_staff_mode
func set_staff_mode(new_val:int) -> void:
if staff_mode == new_val:
return
if not is_inside_tree():
return
var animation_player := get_node("AnimationPlayer") as AnimationPlayer
if not is_instance_valid(animation_player):
return
var target_animation:String = Modes.keys()[new_val]
animation_player.play(target_animation)
yield(animation_player, "animation_finished")
staff_mode = new_val
property_list_changed_notify()
I have opted to use an enum, because this will also allow me to "travel" between the animations. The idea is that we will make a for loop where we call the animations in order. Like this:
tool
extends Node2D
enum Modes {full, mid, small}
export(Modes) var staff_mode:int setget set_staff_mode
func set_staff_mode(new_val:int) -> void:
if staff_mode == new_val:
return
if not is_inside_tree():
return
var animation_player := get_node("AnimationPlayer") as AnimationPlayer
if not is_instance_valid(animation_player):
return
var old_val := staff_mode
staff_mode = new_val
var travel_direction = sign(new_val - old_val)
for mode in range(old_val, new_val + travel_direction, travel_direction):
var target_animation:String = Modes.keys()[mode]
animation_player.play(target_animation)
yield(animation_player, "animation_finished")
I have also decided to set staff_mode early so I can avoid property_list_changed_notify.
Concurrent calls may result in animation stopping early, since calling play stops the currently playing animation to play the new one. However, I don't think waiting for the current animation to end is correct. Also with so short animations, it should not be a problem.
Version using Tween
Using Tween will give you finer control, but it is also more work, because we are going to encode the animations in code… Which I will be doing with interpolate_property. Thankfully this is a fairly simple animation, so can manage without making the code too long.
Of course, you need to add a Tween node. We will not use AnimationPlayer nor AnimationTree. Tween will handle the interpolations (and you can even specify how to do the interpolations by adding the optional parameters of interpolate_property, which I'm not passing here).
This is the code:
tool
extends Node2D
enum Modes {full, mid, small}
export(Modes) var staff_mode:int setget set_staff_mode
func set_staff_mode(new_val:int) -> void:
if staff_mode == new_val:
return
if not is_inside_tree():
return
var tween := get_node("Tween") as Tween
if not is_instance_valid(tween):
return
var old_val := staff_mode
staff_mode = new_val
var travel_direction = sign(new_val - old_val)
for mode in range(old_val, new_val + travel_direction, travel_direction):
match mode:
Modes.full:
tween.interpolate_property($"1", "position", $"1".position, Vector2.ZERO, 0.2)
tween.interpolate_property($"1/2", "position", $"1/2".position, Vector2(0, -35), 0.2)
tween.interpolate_property($"1/2/3", "position", $"1/2/3".position, Vector2(0, -34), 0.2)
Modes.mid:
tween.interpolate_property($"1", "position", $"1".position, Vector2.ZERO, 0.2)
tween.interpolate_property($"1/2", "position", $"1/2".position, Vector2(0, -35), 0.2)
tween.interpolate_property($"1/2/3", "position", $"1/2/3".position, Vector2.ZERO, 0.2)
Modes.small:
tween.interpolate_property($"1", "position", $"1".position, Vector2.ZERO, 0.2)
tween.interpolate_property($"1/2", "position", $"1/2".position, Vector2.ZERO, 0.2)
tween.interpolate_property($"1/2/3", "position", $"1/2/3".position, Vector2.ZERO, 0.2)
tween.start()
yield(tween, "tween_all_completed")
What you can see here is that I have encoded the values from the tracks of from the AnimationPlayer in the source code. Using Tween I get to tell it to interpolate from whatever value the track has to the target position of each state.
I don't know if this performs better or worse compared to AnimationPlayer.
Old Answer
Alright, there are two sides to this problem:
travel is supposed to play the animation, so it is not instantaneous. Thus, if you call stop it will not be able to travel, and you get the error message you got.
Ah, but you cannot call start and travel back to back either. You need to wait the animation to start.
I'll start by not going from one state to the same:
func set_staff_mode(new_val:String) -> void:
if staff_mode == new_val:
return
We are going to need to get the AnimationTree, so we need to ge in the scene tree. Here I use yield so the method returns, and Godot resumes the execute after it gets the "tree_entered" signal:
if not is_inside_tree():
yield(self, "tree_entered")
The drawback of yield, is that it can cause an error if the Node is free before you get the signal. Thus, if you prefer to not use yield, we can do this instead:
if not is_inside_tree():
# warning-ignore:return_value_discarded
connect("tree_entered", self, "set_staff_mode", [new_val], CONNECT_ONESHOT)
return
Here CONNECT_ONESHOT ensures this signal is automatically disconnected. Also, Godot makes sure to disconnect any signals when freeing a Node so this does not have the same issue as yield. However, unlike yield it will not start in the middle of the method, instead it will call the method again.
Alright, we get the AnimationTree:
var animation_tree := get_node("/path/to/AnimationTree")
if not is_instance_valid(animation_tree):
return
And get the AnimationNodeStateMachinePlayback:
var ani_state:AnimationNodeStateMachinePlayback = animation_tree.get("parameters/playback")
Now, if it is not playing, we need to make it playing:
if not ani_state.is_playing():
ani_state.start(new_val)
And now the problem: we need to wait for the animation to start.
In lieu of a better solution, we going to pool for it:
while not ani_state.is_playing():
yield(get_tree(), "idle_frame")
Previously I was suggesting to get the AnimationPlayer so we can wait for "animation_started", but that does not work.
Finally, now that we know it is playing, we can use travel, and update the state:
ani_state.travel(new_val)
staff_mode = new_val
Don't call stop.
You might also want to call property_list_changed_notify() at the end, so Godot reads the new value of staff_mode, which it might not have registered because we didn't change it right away (instead we yielded before changing it). I suppose you could alternatively change the value earlier, before any yield.
By the way, if you want the mid animation to complete in the travel, change the connections going out of mid in the AnimationTree from "Immidiate" to "AtEnd".
Addendum on waiting for travel to end and stop
We can spin wait in a similar fashion as we did to wait for the AnimationNodeStateMachinePlayback to start playing. This time we need to pool two things:
What is the current state of the animation.
What is the playback position on that animation.
As long as the animation is not in the final state and as long as it has not reached the end of that animation, we let one frame pass and check again. Like this:
while (
ani_state.get_current_node() != new_val
or ani_state.get_current_play_position() < ani_state.get_current_length()
):
yield(get_tree(), "idle_frame")
Then you can call stop.
Furthermore, I'll add a check for is_playing. The reason is that this code is waiting for the AnimationTree to complete the state we told it to… But if you call travel again before it finished, it will go to new destination, and thus might never reach the state we expected, which result in spin waiting for ever.
And since it might not have arrived to the state we expected, I decided to query the final state instead of setting staff_mode to new_val. That part of the code now looks like this:
ani_state.travel(new_val)
while (
ani_state.is_playing() and (
ani_state.get_current_node() != new_val
or ani_state.get_current_play_position()
< ani_state.get_current_length()
)
):
yield(get_tree(), "idle_frame")
ani_state.stop()
staff_mode = ani_state.get_current_node()
property_list_changed_notify()
I am trying to implement a brightness slider into my game but am running into problems connecting the signal.
A global function is called, which should emit a signal.
extends Node
signal change_brightness(val)
func update_brightness(value):
emit_signal("change_brightness", value)
print(value)
The function successfully prints the value so the error is not to do with this.
The signal is connected to the below script.
extends WorldEnvironment
func _ready():
GlobalSettings.connect("change_brightness", self, "_on_brightness_updated")
func _on_brightness_updated(value):
print('hello')
environment.adjustment_brightness = value # sets new brightness value
Hello is not being printed showing the signal is not working.
Thanks
Just figured it out.
The WorldEnvironment Scene was not instanced within the scene.
Knew it would be something silly.
I am trying to modify the player's movement speed depending on if they are in a bush or not. This is the gist of what I am trying to accomplish:
const Grass = preload("res://World/Grass/Grass.tscn")
onready var grass = Grass.instance()
func move():
grass = Grass.instance()
if grass.player_in_grass():
velocity = move_and_slide(velocity / BUSH_FRICTION_MULTIPLIER)
else:
velocity = move_and_slide(velocity)
The issue that I am having is that I cannot figure out what the code to check should be. I have tried to create a player detection zone for the grass, switching its value when inside of it:
var player = null
func player_is_visible():
return player != null
func _on_PlayerDetectionZone_body_entered(body):
player = body
func _on_PlayerDetectionZone_body_exited(body):
player = null
And my Grass.gd looks like this:
onready var playerDetectionZone = $PlayerDetectionZone
func player_in_grass():
if playerDetectionZone.player != null:
return true
else:
return false
After all of this, I am hit with the error:
Invalid get index 'player' (on base: 'Nil').
The error forwards me to 'if playerDetectionZone.player != null:'.
What am I doing wrong? Should I be checking for this/doing this a different, easier way that you know of? All feedback appreciated. Thank you.
To sum it up:
The error is
Invalid get index 'player' (on base: 'Nil').
And the line of code where this error occurs is:
if playerDetectionZone.player != null:
(which is unfortunately not part of the posted code).
This error means that the variable playerDetectionZone has the value null. Assuming the rest of the code is as posted the problem should be located here:
onready var playerDetectionZone = $PlayerDetectionZone
Something is wrong with the node path ($PlayerDetectionZone). Spelling, capitalisation, maybe wrong position in the tree. Could be a number of things.
Edit: Based on your comment it is probably the wrong position in the tree. The line above only works if the PlayerDecetionZone node is a child of the grass node (the one with grass.gd attached).