How to invoke a function when changing position? - godot

I'm trying to invoke a function everytime the position of a position2D node is changed
I tried to override the setter as given in the docs like this:
extends Position2D
tool
func set_position(value):
print("changed position ",value)
self.position=value;
what am I missing here? is there a way to achieve what I'm trying?

The method set_position is not virtual. You are not overriding it. At best you are hiding it.
If you want to follow that approach, intercept the property instead, using _set (Notice that the documentation mentions that _set is virtual). The idea is that we are going to take the attempt to set position, do whatever we want to do when it is set, and then let it continue normally.
func _set(property:String, value) -> bool:
if property == "position":
print("changed position ",value)
return false
And you should see that when you modify the position in the inspector panel, you get the message.
I suspect that is not enough for you. In particular the above solution does not detect when the position changes because you drag the Position2D in the editor.
This is becase when you move the Position2D in the editor, Godot is not setting the position property. Instead it is (after digging in the source code) calling the undocumented method _edit_set_position… Which you can also call from GDScript! Anyway, the point is we cannot intercept it (and we cannot override it. Yes, I tried).
Instead we are going to use _notification. We are looking for NOTIFICATION_TRANSFORM_CHANGED, but to get that notification we need to enable it with set_notify_transform. The code looks like this:
tool
extends Position2D
func _enter_tree() -> void:
set_notify_transform(true)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
print("changed position", position)
With this approach you will also be able to react to when the user moves the Position2D in the editor by dragging it around or with the keyboard directional keys. Which are the cases where Godot uses _edit_set_position. Or any other means that we could not intercept with the initial approach.
You can use this approach if you need to do something every time the Node2D moves, but it does not move very often.
By the way, this also works for 3D.

Related

Which AnimationPlayer is currently shown in bottom panel Animation?

I'm trying to create my own plugin and I need to figure out which AnimationPlayer node is currently shown in the bottom Animation panel
and please do not suggest I use get_selection() because even if I select multiple (or none ) AnimationPlayer node(s), only one is show on the Animation Panel
so far my approach has been to try finding the node to the Animation panel using get_editor_interface() and then maybe viewing it's properties?
Edit
Based on the answer given by user #Theraot I tried this:
tool
extends EditorPlugin
func handles(object):
print('handles=>',object);
return true;
func edit(object):
print('edit=>',object);
func _enter_tree():
pass;
but using this I only get the selected nodes within the tree but even if I haven't selected any AnimationPlayer Node it still shows up in the animation panel
On your EditorPlugin implement handles to return true on any AnimationPlayer given to it, and Godot should call edit with the AnimationPlayer that is to be edited currently.
Example code:
tool
extends EditorPlugin
var edited_animation_player:AnimationPlayer
func handles(object:Object) -> bool:
return object is AnimationPlayer
func edit(object:Object) -> void:
edited_animation_player = object
print(edited_animation_player)
I was hoping the information of which AnimationPlayer is currently being edited in the Animation panel was perhaps stored in some node for the bottom panel?
Even if get the correct node from the interface:
var control:= Control.new()
var animation_player_editor:Control
add_control_to_bottom_panel(control, "puff")
for sibling in control.get_parent().get_children():
if (sibling as Control).get_class() == "AnimationPlayerEditor":
animation_player_editor = sibling
remove_control_from_bottom_panel(control)
if animation_player_editor != null:
print(animation_player_editor)
Which AnimationPlayer it is editing is not exposed to scripting. We can find a get_player method in the source code and a get_state method also in the source code, but we could only access them from C++, as they are not bound for scripting (source code).
In theory we should be able to get the EditorPlugin and call get_state on it, however it is not working for me:
var animation_player_editor_plugin:EditorPlugin
for sibling in get_parent().get_children():
if (sibling as Node).get_class() == "AnimationPlayerEditorPlugin":
animation_player_editor_plugin = sibling
if animation_player_editor_plugin != null:
print(animation_player_editor_plugin.call("get_state"))
Despite get_state being exposed on EditorPlugin, it appears to not be possible to call it.
By the way, we can see in the source code for AnimationPlayerEditorPlugin that it follows the handles and edit logic I used above.

Why is my game scene getting errors when the main scene is actually the menu in Godot

I am adding a main menu to my game. The thing I am doing is adding them in a scene and then changing the scene to the game scene. However, my game has errors which belong to other scenes that do not have an instance in the existing scene. I get errors such as:
Invalid get index 'HasEntered' (on base: 'null instance').
My entire project is here: https://github.com/Ripple-Studios/Godot-Wild-Jam-36-Game-Ripple-Studios
I would appreciate if someone would help me.
Thanks,
I had a look at the linked code.
What is happening is that the practice of getting nodes of the parent has come back to bite you. I'm talking code that looks like this:
get_parent().get_node(...)
You have an scene (HospitalScene.tscn) instanced in your main scene (MainMenu.tscn), which has code similar to the shown above.
The particular error you refer to comes from code that looks like this:
func _on_Area2D_body_entered(body):
var HospitalPosition = get_parent().get_node("Hospital")
if HospitalPosition.HasEntered == true:
HospitalPosition.isInHospital = false
This code is firing at the start because the Area2D is overlapping an StaticBody.
The code is trying to get a sibling "Hospital" which does not exist in the scene tree. And thus HospitalPosition is null. And trying to access HospitalPosition.HasEntered when HospitalPosition is null results in the error you mention:
Invalid get index 'HasEntered' (on base: 'null instance').
The scene is trying to reach a node outside of itself. And there is no guarantee that the scene will be instanced where such node is available. Thus, in each and every case of get_parent().get_node(...) this could happen.
In fact, when running the game, I get four error that look like this (but with different paths):
E 0:00:01.494 get_node: (Node not found: "Hospital" (relative to "/root/CanvasLayer/ParentSprite/Control").)
<C++ Error> Condition "!node" is true. Returned: nullptr
<C++ Source> scene/main/node.cpp:1325 # get_node()
<Stack Trace> HospitalScene.gd:28 # _on_Area2D_body_entered()
You could use get_node_or_null instead of get_node and check for null.
You could also export NodePath variables instead of hard-coding the paths.
Better decoupling strategies than checking if instances are valid include:
Connect to the signal from outside the scene. It is a common pattern in Godot to call down the scene tree, and signal up the scene tree. See Node communication (the right way).
Connect all the signals through an Autoload (singleton). Which is another common pattern in Godot called a Signal Bus or Event Bus. See Best practices: Godot GDScript - Event Bus.

Godot: Call external method

Lots of googling and I'm still not grasping what is probably a simple solution.
Scene: "Main". Contains a TileMap "Grid" with a script attached to it "Grid.gd".
Scene: "Player". Contains a KinematicBody2D "Player" with a script attached to it "Player.gd"
In Player.gd, I need to call a method in Grid.gd "_Calculate", pass it two variables, and have it return one variable.
var vNewPosition = Grid._Calculate(vPlayer, vInputDirection)
Error: The identifier "Grid" isn't declared in the current scope.
Obiously I need to reference the Grid.gd script somewhere to access it, but none of the many examples I have tried work.
Thanks in advance,
Josh
Given that
The Main scene has an instance of the Player scene
And
In Player.gd, I need to call a method in Grid.gd "_Calculate", pass it two variables, and have it return one variable.
I'll give you a few options.
The bad way
If Player knows the node path to Grid, it could use get_node to get it.
onready var _grid = get_node("../../grid")
Or something like that.
And then use it:
var vNewPosition = _grid._Calculate(vPlayer, vInputDirection)
This approach is, of course, not recommended. It will break if the node path is wrong.
The common way
Assuming Player does not know the node path (which is more likely), you can export a NodePath variable in Player.gd and use it to get the Grid:
exprort var grid_path:NodePath
onready var _grid = get_node(grid_path)
Then you need to set the Grid Path property in the Main scene to Grid.
This approach is better than the prior one, in that it does not depend on the path to Grid. However it still depends on a Grid being there. If Player makes no sense when there is no Grid, this approach is OK.
The good way
If Grid may or may not be there, and we want Player to work regardless, we can tweak the above approach:
exprort var grid_path:NodePath
onready var _grid = get_node_or_null(grid_path)
And remember to check if grid is null before using it:
if grid == null:
return # or whatever
var vNewPosition = grid._Calculate(vPlayer, vInputDirection)
Or like this:
var vNewPosition = grid._Calculate(vPlayer, vInputDirection) if grid != null else null
Which also allows us to specify a default value that vNewPosition will take when there is no grid (the null at the end of the line). And thus you can have the Player work when there is no Grid, and when there is it will use, and regardless of where (because Main tells it where it is).
This solution is not completely decoupled, because Player still knows grid is a thing that has _Calculate. Also we presume there is zero or one, not multiple. I can think of a way to approach that, but decoupling for the sake of decoupling is unnecessary. I'm calling this good enough for your use case. Yet, let me know if you want the over-engineered way.

How to check if the mouse is over a gameObject in phaser 3?

I have a game object (specifically, a rectangle). I want to display text that follows the mouse only when hovering over the rectangle.
I tried using a rectangle.on('pointover', function(pointer) {...}) listener, but that only catches the initial mouse over event. It won't fire continuously to allow the text to follow the mouse.
I assume I need something in my update() method like:
if (rectangle.onPointerOver()) {
update text x and y from pointer
}
But I don't see any such method on the GameObject or Rectangle.
I also know I could naively find the x and y coordinates and length and width of the rectangle and check that against the pointer, but Phaser 3 must have a better way of doing it.
It turns out that adding a listener is the right approach, but the event I should be listening for is .on('pointermove', function(pointer, x, y, event) {}).
I am definitely late to the party here. Just wanted to fill in some details for anyone who finds this later!
You can definitely use the pointerover event when creating your rectangle, just like any other GameObject.
You just need to make sure you call setInteractive() on the game object first.
const rect = this.add.rectangle(200, 200, 148, 148, 0x6666ff);
rect.setInteractive()
rect.on("pointerover", () => {
rect.setStrokeStyle(4, 0xefc53f);
});
Also, for future reference:
I couldn't find the full list of available input events in the docs, but here they are in the git repo.
https://github.com/photonstorm/phaser/blob/master/src/input/events/index.js
Good luck 😊
I used something like this:
gameObject.setInteractive().on('pointerdown', function(pointer, localX, localY, event){
// ...
});
I don't know how to change this for hover, but maybe this helps someone.

How can I simulate hand rays on HoloLens 1?

I'm setting a new project which is intended to deploy to both HoloLens 1 and 2, and I'd like to use hand rays in both, or at least be able to simulate them on HoloLens 1 in preparation for HoloLens 2.
As far as I have got is:
Customizing the InputSimulationService to be gesture only (so I can test in editor)
Adding the GGVHand Controller Type to DefaultControllerPointer Options in the MRTK/Pointers section.
This gets it to show up and respond to clicks both in editor and device, but it does not use the hand coordinates and instead raycasts forward from 0,0,0, which suggests that the GGV Hand Controller is providing a GripPosition (of course with no rotation due to HL1) but not providing a Pointer Pose.
I imagine the cleanest way to do this would be to add a pointer pose to the GGV Hand controller, or add (estimated) rotation to the GripPosition and use this as the Pose Action in the ShellHandRayPointer. I can't immediately see where to customize/insert this in the MRTK.
Alternatively, I could customize the DefaultControllerPointer prefab but I am hesitant to do so as the MRTK seems to still be undergoing frequent changes and this would likely lead to upgrade headaches.
You could create a custom pointer that would set the pointer's rotation to be inferred based on the hand position, and then like you suggested use Grip Pose instead of Pointer Pose for the Pose Action.
The code of your custom pointer would look something like this:
// Note you could extend ShellHandRayPointer if you wanted the beam bending,
// however configuring that pointer requires careful setup of asset.
public class HL1HandRay : LinePointer
{
public override Quaternion Rotation
{
get
{
// Set rotation to be line from head to head, rotated a bit
float sign = Controller.ControllerHandedness == Handedness.Right ? -1f : 1f;
return Quaternion.Euler(0, sign * 35, 0) * Quaternion.LookRotation(Position - CameraCache.Main.transform.position, Vector3.up);
}
}
// We cannot use the base IsInteractionEnabled
// Because HL1 hands are always set to have their "IsInPointing pose" field as false
// You may want to do more thorough checks here, following BaseControllerPointer implementation
public override bool IsInteractionEnabled => IsFocusLocked || IsTracked;
}
Then create a new pointer prefab and configure your pointer profile to use the new pointer prefab. Creating your own prefab instead of modifying MRTK prefabs has advantage of ensuring that MRTK updates will not overwrite your prefabs.
Here's some captures of the simple pointer prefab I made to test this with relevant changes highlighted:
And then the components I used:

Resources