Checking if key is being held down - godot

I've got a game where you place towers on the map, it's working fine they can click and the towers place the the build mode is set to false. I Want to allow the player to hold shift down then they can place multiply towers then when they release shift the build mode will change to false but i can't figure it out.
This is what i've got so far but doesn't seem to work as intended
func _input(event):
if event.is_action_released("ui_accept") and build_mode == true:
verify_and_build()
if event.is_action_released("multi_build"):
cancel_build_mode()
I've assigned ui_accept to left click and multi_build to Shift

You are going to get a call to _input per input event. And the event object you get as parameter in _input represents that single input.
If want to to this with the event object, it would be something like this:
func _input(event):
if event.is_action_pressed("multi_build"):
build_mode = true
if event.is_action_released("multi_build"):
build_mode = false
Alternatively, you can use Input to check if the action is currently pressed: Input.is_action_pressed("multi_build").

Related

EditorFileDialog or FileDialog popup from EditorScript

hello Godot's comunity !
I try to integrate in a project a way to import custom json file to generate Tilemap. So I use a EditorScript to generate it but now I want to open a file dialog popup to choose specific file in my disk. So i tried something like this :
tool
extends EditorScript
func _run():
var fileDialog = EditorFileDialog.new()
fileDialog.mode = EditorFileDialog.MODE_OPEN_FILE
fileDialog.access = EditorFileDialog.ACCESS_FILESYSTEM
fileDialog.popup()
...
but nothing happened. Only this in debugger :
scene/gui/control.cpp:2154 - Condition "!is_inside_tree()" is true.
scene/2d/canvas_item.cpp:469 - Condition "!is_inside_tree()" is true. Returned: get_transform()
scene/2d/canvas_item.cpp:939 - Condition "!is_inside_tree()" is true. Returned: Rect2()
scene/gui/control.cpp:2128 - Condition "!is_inside_tree()" is true.
scene/gui/control.cpp:2128 - Condition "!is_inside_tree()" is true.
./scene/main/node.h:269 - Condition "!data.tree" is true. Returned: nullptr
Any idea to do that ?
You need to give the FileDialog a viewport to be shown in.
You do so by getting the viewport of the editor:
var viewport = get_editor_interface().get_editor_viewport()
viewport.add_child(fileDialog)
this will show the dialog, but to ensure you do not run into followup problems:
The editor script is freed soon after running the run function, so connected signals will be not fired after choosing a file. (See here)
In the issue is the solution to secure the connected functions in the script are fired, by keeping the instance in the dialog. After you finished you should free the dialog to make sure you can run the script again, without closing the whole editor.
Complete code would look something like this:
tool
extends EditorScript
var fileDialog : EditorFileDialog = null
func _run():
fileDialog = EditorFileDialog.new()
fileDialog.mode = EditorFileDialog.MODE_OPEN_FILE
fileDialog.access = EditorFileDialog.ACCESS_FILESYSTEM
fileDialog.connect("file_selected", self, "on_file_selected")
var viewport = get_editor_interface().get_editor_viewport()
viewport.add_child(fileDialog)
fileDialog.set_meta("_created_by", self) # needed so the script is not directly freed after the run function. Would disconnect all signals otherwise
fileDialog.popup(Rect2(0,0, 700, 500)) # Giving the dialog a predefined size
print("end")
func on_file_selected(filename : String) :
print(filename)
if (fileDialog != null):
fileDialog.queue_free() # Dialog has to be freed in order for the script to be called again.

How to track the local position of a movable QTab?

I am currently attempting to set boundaries so that tabs cannot move beyond on each end of my QTabBar. By default the movable tab just disappears into void whenever it is dragged outside of the QTabBar's area -
An example of the QTab being dragged outside of the QTabBar's Area.
This is fine for the most part, but visually I would like the tab to stop moving entirely once it reaches the edge of the QTabBar. I've attempted to accomplish this goal via Qt's event system, and currently only have support for the left boundary. Whenever the user clicks on a QTab, I record his mouse's initial position and the current tabs unmoved position and size. I then use these values to determine at which x-position the current tab would reach the edges of the QTabBar if the user were to move his mouse left. If this position is ever reached the event is filtered preventing any further movement of the tab.
def eventFilter(self, source, event):
if event.type() == QEvent.Type.MouseButtonPress:
self.startingpos = event.x()
self.tabOrigin = self.curTab.x()
self.tabRoom = self.startingpos - self.tabOrigin
if event.type() == QEvent.Type.MouseMove:
if self.curIndex != self.tabs.currentIndex():
self.curIndex = self.tabs.currentIndex()
self.curTab = self.tabs.tabBar().tabRect(self.curIndex)
if event.x() < self.tabRoom:
return True
return False
This tactic is effective unless the user quickly moves his mouse left. This causes the moving tab to get stuck at the last recorded position of the mouse before it went past the specified boundary, before reaching the QTabBar's edge -
An example of the QTab getting stuck before reaching the left side of the QTabBar.
I'm not exactly sure how to get around this issue. I understand that the moving tab is not actually the same one as the tab that is originally clicked. Is there anyway to access the position of the moving tab and manually set its position? If not, any advice about how else to go about solving this problem would be greatly appreciated.
Mouse events are not linear, they "skip" pixels if the user moves the mouse too fast.
The problem is that the "moving tab" is completely implemented internally, so there's no access to that tab (which, in fact, is temporarily a "fake" widget drawn over the remaining tabs).
A possible solution is to detect the current pressed tab, get the horizontal mouse position relative to that tab, check whether the movement goes beyond the limits (0 on the left, the right of the last tab for the right margin), synthesize a new mouse event based on that, and post that event.
def eventFilter(self, source, event):
if (event.type() == event.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton):
tabRect = source.tabRect(source.tabAt(event.pos()))
self.leftDistance = event.x() - tabRect.left()
self.rightMargin = tabRect.right() - event.x()
self.rightLimit = source.tabRect(self.tabs.count() - 1).right()
elif (event.type() == event.MouseMove and
event.buttons() == QtCore.Qt.LeftButton):
if event.x() - self.leftDistance < 0:
pos = QtCore.QPoint(self.leftDistance, event.y())
newEvent = QtGui.QMouseEvent(
event.type(), pos,
event.button(), event.buttons(),
event.modifiers())
QtWidgets.QApplication.postEvent(source, newEvent)
return True
elif event.x() + self.rightMargin > self.rightLimit:
pos = QtCore.QPoint(
self.rightLimit - self.rightMargin, event.y())
newEvent = QtGui.QMouseEvent(
event.type(), pos,
event.button(), event.buttons(),
event.modifiers())
QtWidgets.QApplication.postEvent(source, newEvent)
return True
return super().eventFilter(source, event)
Note that eventFilter() should always return the base implementation, otherwise you could face unexpected behavior in some situations.

Godot - Missing Nodes

This is difficult to explain with typing...
I have a GameController scene (Node2D) that holds 3 instanced scenes within:
Mouse (scenes/Mouse.tscn) - this just swaps the mouse cursor for a custom graphic
HeaderBar (scenes/HeaderBar.tscn) - this is the score/label that just sits up top
Messages (scenes/Messages.tscn) - this is the "popup" message box that displays text to the user
In the main scene (Level1.tscn) I instance the GameController scene and it "works" fine. The header bar is there with the score/label and the custom mouse cursor is there and the message box is there (but I have it hidden by default but if I toggle its visibility in the remote it will show up).
Here's where my confusion comes in...
If I attempt, in the GameController script, to manipulate any of those nodes in the GameController scene (the mouse, header, messages) they return as null and will throw an error. For example; if I try to update the score in the $HeaderBar/Control/score I get the following:
Invalid set index 'text' (on base: 'null instance') with value of type 'String'.
The code completion will autofill the node names as I type them (so it recognizes them in the group) but any attempt to reference/use them in script throws similar errors to above.
I'm very new to Godot so I'm sure it's just some misunderstanding I have on how this type of thing works but I'm stumped! Any insight is much appreciated!
UPDATE
I will try to simplify my explanation a bit (I have made some changes). Okay here is the object script:
extends StaticBody2D
onready var main = load("res://scenes/MainGame.gd").new()
func _ready():
pass
# mouse [left] clicked on object
func _on_trigger_input_event(viewport, event, shape_idx):
if Input.is_action_just_pressed("left-click"):
main.display_message("you [left] clicked on the object!")
the call to main.display_message() works. It does call the function but here is that function in the MainGame.gd
extends Node2D
onready var box = $Message/Control
onready var label = $Message/Control/Label
func _ready():
# hide the mouse cursor (will use custom cursor)
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
func display_message(msg):
label.text = msg
box.visible = true
It errors out because label (and box) are null. If I call display_message from the _ready function (in the MainGame.gd) it works as it should. Call it from outside (in the Object.gd) and the nodes are null for some reason.
This instances the scene as expected:
onready var main = load("res://scenes/MainGame.gd").new()
However, this code will run when the instanced scene is added to the scene tree:
onready var box = $Message/Control
onready var label = $Message/Control/Label
func _ready():
# hide the mouse cursor (will use custom cursor)
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
Yes, both onready variables and _ready will run when this is added to the scene tree.
And when do you add main to the scene tree? I don't see where.
Then when you do this:
main.display_message("you [left] clicked on the object!")
Both box and label will still be null (which is the default value).
Thus, add it to the scene tree, for example:
func _ready():
add_child(main)
Except that does not work either, does it? Look at the code again:
onready var main = load("res://scenes/MainGame.gd").new()
You are instancing a script. A script. Not a scene. It will not have its children nodes and so on. You want to instance a scene, for example:
onready var main = load("res://scenes/MainGame.tscn").instance()
However, I find it odd that you are doing this in a StaticBody2D. I don't think it makes sense that MainGame belongs to a StaticBody2D.
I suspect, you are instancing MainGame in multiple places expecting it be the same instance. But it is not the same instance, it is a new instance.
Here is where I suggest you make MainGame into a autoload, so there is a single instance that you can access from anywhere. However, perhaps that is not right either. Perhaps you should use a Signal Bus. See Why does this variable keep returning to its original value? which had a similar problem (they where trying to open a door, you are trying to show a message, but still).

Godot indexing and keyboard both at the same time

I have been trying to develop keyboard and touch indexed game, playable on PC browsers and phone. How can I index?
Tried some samples in Godot Sample menu. None of them helped. Either for keyboard or touch screen
func _unhandled_input(event):
if event is InputEventScreenTouch:
if event.pressed:
# Down
if !_os2own.has(event.index): # Defensively discard index if already known
var ptr_id = _find_free_pointer_id()
state[ptr_id] = event.position
_os2own[event.index] = ptr_id
else:
# Up
if _os2own.has(event.index): # Defensively discard index if not known
var ptr_id = _os2own[event.index]
state.erase(ptr_id)
_os2own.erase(event.index)
return true
Need to touching and clicking game. Both for APK and HTML
For simple input handling (e.g. pressed and released) you'll want to map input to actions. You can add actions via "Project Settings -> Input Map" or InputMap singleton.
From the "Project Settings -> Input Map" you can map mouse, keyboard, and controller input to actions.
For touch screens you can use a TouchScreenButton and set it's action. When pressed or released it will send that action event down the scene tree via _input(). TouchScreenButton hides the logic needed to write in _input() to handle presses and releases, such as: is this finger index new? which finger index moved last frame? is this finger area in bounds of input area? and more. While also having the advantage over a plain Button by also emitting an action and can have no texture as it uses a Shape for input detection.
So this creates an one-to-many relationship from actions to inputs like:
my_action -> left mouse button pressed,
-> controller r1 pressed,
-> right half of touch screen pressed,
-> control-shift-f pressed
Using the action
func _input(event):
if not event.is_action('my_action'):
return
if event.is_action_pressed('my_action'):
start_something()
else:
stop_something()
Going further
Since the post specified keyboard and touch input, I only covered press and release input action mapping. However, you can map complex inputs like gestures to actions as well. You can inherit from InputEvent or any of it's subclasses to extend or create a new event. You can map the new event class to an action and then manually process input to find an event. Then you stop the propagation of the event, form the event from your new event class, and then call Input.parse_input_event(my_new_event) to send it down the tree.
Custom Action
# SomeLeafNode.gd
class MyEvent extends InputEvent:
var my_custom_message = 'Hello, World'
func _ready():
InputMap.add_action('my_event')
InputMap.action_add_event('my_event', MyEvent.new())
func _input(event):
# ... logic to see if event could be MyEvent
get_tree().set_input_as_handled()
var my_event = MyEvent.new()
my_event.my_custom_message = 'Caught my event!'
Input.parse_input_event(my_event)
# SomeInputHandlingGameplayNode.gd
func _input(event):
if event.is_action('my_event'):
print(event.my_custom_message) # prints 'Caught my event!'

Hold-Hover drop down menu Delay Time

Im creating a drop down menu and i want to know if there is anyway to implement the following:
I need to keep the sub-menu open for like 1 sec if the user moves the mouse away from the tab he selected. Much likely like in current intel web page www.intel.com , here u hover over menu, but if u take the mouse away from the tab or the sub-menu is opens it takes a few to hide the sub menu.
Im using .mouseover from jquery to show the menu (a div) but i cant find a way to make it stay for a few moments.
Thanks in advance
This may be of service
What is the JavaScript version of sleep()?
If you want to do something in the interim setTimeout() takes the arguments as shown where continue execution is another subroutine. If you just want this one tab to work this way have mouseover call doStuff and set a boolean (e.g. mouseStillIn) to TRUE. When the mouse exits set this boolean to FALSE, call a recursive function everytime mouseStillIn is TRUE.
e.g.
var mouseStillIn : boolean = false;
function MouseIn()
{
mouseStillIn=true;
CheckMouse();
}
function CheckMouse()
{
if(mouseStillIn)
{
setTimeout(CheckMouse, 1000);
}
}
function MouseOut()
{
mouseStillIn=false;
}

Resources