Why does rock image does not move alongside the mouse when in motion although I called the node and got its position-Godot Game Engine - position

I am trying to get my rock.png to move to the side(+x axis) when my mouse is moving. You can see that _target is the position of the event and the event is the mouse moving.
I then got the node image and set it to a variable.
In the else statement I made the rock.position the position of the _target and gave it somee space away from the _target
I want this to move because my camera moves and I want it to move with the flow of the camera
``
extends Camera2D
const zone = 10
onready var rock = get_node("rocks_png_1")
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
var _target = event.position
if _target.length() < zone:
self.position = Vector2(640,350)
else:
self.position = _target.normalized() * (_target.length() - zone) * 0.5
rock.position = Vector2(int(_target)+40, int(_target)+20)
``
From the code I used above I get this error
Invalid get Index 'position' (on base: 'TextureRact')
I tried just using the same code as I used in my self.position for the camera that made it move, but when I try it for the rock.position it gives me an error that tells me I need a Vector?

Invalid get Index 'position' (on base: 'TextureRact')
The error is telling you that TextureRect does not have a position property.
What happens is that a TextureRect is a Control which are positioned differently from Node2D (Camera2D is a Node2D). You can try using rect_position with it. Although I think you would be better off using a Sprite instead of a TextureRect (Sprite is a Node2D).
Notice that your TextureRect is a child of the Camera2D in the scene tree. Well, a Node2D would move with its parent automatically. So changing it to Sprite would negate the need of the line of code you are using to move it.
… Actually, due to the way Camera2D works (it has drag and limits) you might still want do something (depending what your goal is). So, know that the get_camera_screen_center method will give you the actual visual center of the screen (which is not the target of the Camera2D, again due to drag and limits).

Related

Is there an easier way to turn Canvas locations into Node2D locations

Ok, so I have a node tree in my main scene that is like this (simplified)
root
|- LevelHolder
|- Player
|- Camera2D
|- CanvasLayer
|- SomeUI
|- ...
|- Extents
|- TopLeft
|- BottomRight
And the situation I have is that my Canvas Layer has a full rect layout to stretch across the screen, and then my SomeUI element contains a bunch of UI elements that make up a HUD and a frame. I've got a Camera2D that allows me to zoom in and out on my player and level, so the editor's placement of the Node2D elements doesn't match the in-game view, since the Camera moves and zooms.
I don't want my player to be able to move underneath my HUD elements, so I've got these Extents objects in the CanvasLayer, which I can anchor to the top-left and bottom-right corners of the part of my HUD that I actually want the player to be able to walk around in.
So what I think I need to do is turn Canvas positions into positions I can set for my player.
After a lot of trial and error, this is what worked for me:
var tl = $CanvasLayer/Extents/TopLeft
var tlgp = tl.get_viewport_transform() * tl.get_global_transform()
$Player.topleft = self.get_viewport_transform().affine_inverse().xform(tlgp.origin)
var br = $CanvasLayer/Extents/BottomRight
var brgp = br.get_viewport_transform() * br.get_global_transform()
$Player.bottomright = self.get_viewport_transform().affine_inverse().xform(brgp.origin)
Which works, but seems like a lot of work, and it took me a lot of screwing around to find this all. And I can leave out self.get_global_transform() in this case because I know that root's global transform is the identity, but in general it'd be even more complicated.
So, my real question is have I just overcomplicated this? Is there some method I could use that would just do all of this for me (or something else entirely) that would let me easily place a Node2D underneath a Canvas element, no matter what size the window is, or how it's stretched or squashed?
What you are doing is correct.
Is it overkill? Perhaps. Assuming the UI does not change in way that the extends need to move, you could have the screen coordinates computed beforehand… Or compute them only when necessary.
Another thing to consider is how you are moving the Camera2D. If the camera is following the player, you can use "drag margins" that you use to specify how close the the border of the screen it has to get before the view begins to move. Although that might not be viable depending on the game.
And for another alternative, you could put the game world in a Viewport, and perhaps use a ViewportContainer which will be part of the UI. Although Viewports are a performance consideration - in particular for mobile - chances are you can afford this solution. This approach also allows to have a different resolution for the game world and the UI. See also How to make a silky smooth camera for pixelart games in Godot
This rest of this answer is confirmation of what you are doing, and hopefully making it easier to understand.
The official documentation has a formula for the screen position:
var screen_coord = get_viewport_transform() * (get_global_transform() * local_pos)
See Viewport and canvas transforms.
First of all, we don't need to use get_global_transform() since we have property for it:
var screen_coord = get_viewport_transform() * (global_transform * local_pos)
If we know we are dealing with a Control and we want its position (i.e. Vector2.ZERO in local space) we can write a shorter version:
var screen_coord = get_viewport_transform() * rect_global_position
On the other hand, for a Node2D (and again we want its position) we can do this:
var screen_coord = get_viewport_transform() * global_position
Which is the same as:
var screen_coord = get_viewport_transform() * global_transform.origin
By the way, you get the same result like this (Which is what you are doing):
var screen_coord = (get_viewport_transform() * global_transform).origin
The difference is that here we are composing the transformation, which is extra work.
However, since we want to place a Node2D in the screen coordinates of something else, we need the inverse process.
Given this equation:
screen_coord = get_viewport_transform() * global_position
We can compose the inverse of one of these transforms at the start of both sides, like this:
get_viewport_transform().affine_inverse() * screen_coord
=
get_viewport_transform().affine_inverse() * get_viewport_transform() * global_position
And - of course - get_viewport_transform().affine_inverse() * get_viewport_transform() cancels out (to an identity transform, which we can omit), so we have:
get_viewport_transform().affine_inverse() * screen_coord = global_position
Flip it, and you have the means to position the Node2D at a given screen position:
global_position = get_viewport_transform().affine_inverse() * screen_coord
Now we can combine the computation of the screen position and how we position a Node2D at a given screen position. For example:
var screen_coords := control.get_viewport_transform() * control.rect_global_position
node2D.global_position = node2D.get_viewport_transform().affine_inverse() * screen_coords
You can use xform instead of * if you prefer. However, be aware that xform returns Variant, so you lose type information.

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).

navigation tilemaps without placing walkable tiles manually

I have a tilemap in godot, but all tiles are obstacles and provide collision. Only cells without any tile are walkable. I'm now trying to add navigation via Navigation2D node. As far as I see there is no way to tell it "everything is walkable, but not where these tiles are" (all one can say is "this part of the tile is walkable", but in my current setup there is no tile at the walkable space).
As a workaround I tried to set every cell with no tile to a "dummy tile" which is fully walkable with the following code:
func _ready():
for x in size.x:
for y in size.y:
var cell = get_cell(x, y)
if cell == -1:
set_cell(x, y, WALKABLE)
But the Navigation2D node does not recognize these tiles. If I place the WALKABLE tile manually, everything works as expected.
I think I might be hitting this issue, and need to call update_dirty_quadrants() but this does not fix the problem.
I tried this with the versions 3.0.6stable, 3.1Alpha2 and a recent commit 9a8569d (provided by godotwild), and the result was always the same.
Is there anyway to get navigation with tilemaps working, without placing every tile beforehand manually?
For anyone coming across this in the future, the 'dummy navigation tile' solution works now (using Godot 3.2.3). Put this script on the tilemap in question:
extends TileMap
# Empty/invisible tile marked as completely walkable. The ID of the tile should correspond
# to the order in which it was created in the tileset editor.
export(int) var _nav_tile_id := 0
func _ready() -> void:
# Find the bounds of the tilemap (there is no 'size' property available)
var bounds_min := Vector2.ZERO
var bounds_max := Vector2.ZERO
for pos in get_used_cells():
if pos.x < bounds_min.x:
bounds_min.x = int(pos.x)
elif pos.x > bounds_max.x:
bounds_max.x = int(pos.x)
if pos.y < bounds_min.y:
bounds_min.y = int(pos.y)
elif pos.y > bounds_max.y:
bounds_max.y = int(pos.y)
# Replace all empty tiles with the provided navigation tile
for x in range(bounds_min.x, bounds_max.x):
for y in range(bounds_min.y, bounds_max.y):
if get_cell(x, y) == -1:
set_cell(x, y, _nav_tile_id)
# Force the navigation mesh to update immediately
update_dirty_quadrants()
I couldn't find the ID from my tile in the autotiler (when checking in the editor, having the tilemap selected, these all show the same imagename.png 0). So instead of using the ID, I used the coordinates of the walkable blank tile in the Tileset. The code remains the same as LukeZaz', but with set_cell replaced by:
set_cell(x, y, 0, false, false, false, Vector2(7,4)); where Vector2(7,4) is the coordinate of the blank tile in my tileset that has the navigationpolygon on it. (Remember that coördinates start at 0)
(Godot 3.2.3.stable)

Why isn't my path2d position updating?

I created a new path2d following the instructions in the my first game article: http://docs.godotengine.org/en/3.0/getting_started/step_by_step/your_first_game.html
I wanted to move the "box" on screen so that I could see how the mobs spawn in, but when I ran the scene, it stayed off screen.
I created a new path2d, centered this one in the middle of the screen, and it works like I wanted it to, but now I moving this one in the editor doesn't update the position in game.
What's going on?
Thanks
func _on_mobtimer_timeout():
$mobtimer.wait_time = 0.1 + randf() / 2
$mobspawn/moblocation.set_offset(randi())
var mob = Mob.instance()
add_child(mob)
var direction = $mobspawn/moblocation.rotation + PI/2
mob.position = $mobspawn/moblocation.position
direction += rand_range(-PI/8, PI/8)
mob.rotation = direction
mob.set_linear_velocity(Vector2(rand_range(200, 200 + score * 30), 0).rotated(direction))
A Node2D's position property is relative to it's parent's position. The code from the Dodge The Creeps tutorial assumes that MobPath is located at 0, 0 and fails when that assumption is false.
In your case you are taking a MobSpawnLocation's position relative to MobPath and then setting it as the new Mob's global position.
Luckily Node2D's have another property that we can use in these circumstances global_position. It can be used like this:
mob.position = $mobspawn/moblocation.global_position
http://docs.godotengine.org/en/stable/classes/class_node2d.html#member-variables
This isn't a full solution, but I found a weird workaround. Instead of changing the position in the editor, if you use the nodes on the orange box (at the intersection of orange and blue), you can kind of alternate to move the box around.

Node.js/Nowjs - Moving a sprite and keeping track of time server-side

I am using node.js and express, and I am calling server-side functions and syncing variables using nowjs. Suppose the user is drawn as a sprite on the canvas. His x,y coordinates are kept server-side in a "position" array.
Server-side:
position = { x : 0; y : 0 }
updatePosition = function (a,b)
{
playerPosition.x += a;
playerPosition.y += b;
}
Client-side:
if keypress('right'){ updatePosition(32,0); }
These are pseudocode. When the user presses the 'right' button, the server-side "updatePosition" function is called, which adds 32 (pixels) to the x-coordinate of the "position" array. This array is then shared with the client, and the new position is drawn on the canvas using client-side function.
Suppose I don't want to draw the sprite at the new position instantly. I want to play a walking animation that gradually moves the sprite 32 pixels to the right, and takes say 1 second to complete. I might implement it this way:
User presses the 'right' button.
The animation starts playing client-side.
updatePosition is called server-side as usual.
When animation on the client finishes, check if the final position client-side matches the coordinates stored server side.
When the user presses the 'right' button/key, he cannot repeat the keypress until 1 second later. The 1 second long "walking" animation has to complete and final position checked with the server-side coordinates before he can press 'right' again to move his sprite.
Question: How do I keep track of the 1 second server side? I can't do it client-side because the user will be able to hack his client to reduce the animation time.
Is the solution to "timestamp" the position array? E.g. position = { x : 0; y : 0, time: 0 }. If the user presses the 'right' button again, the server would check to see if the last position update was greater than 1 second ago. If less than 1 second, the server ignores it.
Why not simply storing a "lock" in the user session?
session.editLock = new Date().getTime();
When another edit is triggered by the client, just:
if(session.editLock && new Date().getTime() - session.editLock > 1000) {
// Return error
}
Modifying the position object doesn't feel right to me. A position object is meant to store position, not time.

Resources